diff options
author | Srikant Patnaik | 2015-01-13 15:08:24 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-13 15:08:24 +0530 |
commit | 97327692361306d1e6259021bc425e32832fdb50 (patch) | |
tree | fe9088f3248ec61e24f404f21b9793cb644b7f01 /arch/mips/kernel/vpe.c | |
parent | 2d05a8f663478a44e088d122e0d62109bbc801d0 (diff) | |
parent | a3a8b90b61e21be3dde9101c4e86c881e0f06210 (diff) | |
download | FOSSEE-netbook-kernel-source-97327692361306d1e6259021bc425e32832fdb50.tar.gz FOSSEE-netbook-kernel-source-97327692361306d1e6259021bc425e32832fdb50.tar.bz2 FOSSEE-netbook-kernel-source-97327692361306d1e6259021bc425e32832fdb50.zip |
dirty fix to merging
Diffstat (limited to 'arch/mips/kernel/vpe.c')
-rw-r--r-- | arch/mips/kernel/vpe.c | 1620 |
1 files changed, 1620 insertions, 0 deletions
diff --git a/arch/mips/kernel/vpe.c b/arch/mips/kernel/vpe.c new file mode 100644 index 00000000..f6f91523 --- /dev/null +++ b/arch/mips/kernel/vpe.c @@ -0,0 +1,1620 @@ +/* + * Copyright (C) 2004, 2005 MIPS Technologies, Inc. All rights reserved. + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +/* + * VPE support module + * + * Provides support for loading a MIPS SP program on VPE1. + * The SP environment is rather simple, no tlb's. It needs to be relocatable + * (or partially linked). You should initialise your stack in the startup + * code. This loader looks for the symbol __start and sets up + * execution to resume from there. The MIPS SDE kit contains suitable examples. + * + * To load and run, simply cat a SP 'program file' to /dev/vpe1. + * i.e cat spapp >/dev/vpe1. + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/vmalloc.h> +#include <linux/elf.h> +#include <linux/seq_file.h> +#include <linux/syscalls.h> +#include <linux/moduleloader.h> +#include <linux/interrupt.h> +#include <linux/poll.h> +#include <linux/bootmem.h> +#include <asm/mipsregs.h> +#include <asm/mipsmtregs.h> +#include <asm/cacheflush.h> +#include <linux/atomic.h> +#include <asm/cpu.h> +#include <asm/mips_mt.h> +#include <asm/processor.h> +#include <asm/vpe.h> +#include <asm/kspd.h> + +typedef void *vpe_handle; + +#ifndef ARCH_SHF_SMALL +#define ARCH_SHF_SMALL 0 +#endif + +/* If this is set, the section belongs in the init part of the module */ +#define INIT_OFFSET_MASK (1UL << (BITS_PER_LONG-1)) + +/* + * The number of TCs and VPEs physically available on the core + */ +static int hw_tcs, hw_vpes; +static char module_name[] = "vpe"; +static int major; +static const int minor = 1; /* fixed for now */ + +#ifdef CONFIG_MIPS_APSP_KSPD +static struct kspd_notifications kspd_events; +static int kspd_events_reqd; +#endif + +/* grab the likely amount of memory we will need. */ +#ifdef CONFIG_MIPS_VPE_LOADER_TOM +#define P_SIZE (2 * 1024 * 1024) +#else +/* add an overhead to the max kmalloc size for non-striped symbols/etc */ +#define P_SIZE (256 * 1024) +#endif + +extern unsigned long physical_memsize; + +#define MAX_VPES 16 +#define VPE_PATH_MAX 256 + +enum vpe_state { + VPE_STATE_UNUSED = 0, + VPE_STATE_INUSE, + VPE_STATE_RUNNING +}; + +enum tc_state { + TC_STATE_UNUSED = 0, + TC_STATE_INUSE, + TC_STATE_RUNNING, + TC_STATE_DYNAMIC +}; + +struct vpe { + enum vpe_state state; + + /* (device) minor associated with this vpe */ + int minor; + + /* elfloader stuff */ + void *load_addr; + unsigned long len; + char *pbuffer; + unsigned long plen; + unsigned int uid, gid; + char cwd[VPE_PATH_MAX]; + + unsigned long __start; + + /* tc's associated with this vpe */ + struct list_head tc; + + /* The list of vpe's */ + struct list_head list; + + /* shared symbol address */ + void *shared_ptr; + + /* the list of who wants to know when something major happens */ + struct list_head notify; + + unsigned int ntcs; +}; + +struct tc { + enum tc_state state; + int index; + + struct vpe *pvpe; /* parent VPE */ + struct list_head tc; /* The list of TC's with this VPE */ + struct list_head list; /* The global list of tc's */ +}; + +struct { + spinlock_t vpe_list_lock; + struct list_head vpe_list; /* Virtual processing elements */ + spinlock_t tc_list_lock; + struct list_head tc_list; /* Thread contexts */ +} vpecontrol = { + .vpe_list_lock = __SPIN_LOCK_UNLOCKED(vpe_list_lock), + .vpe_list = LIST_HEAD_INIT(vpecontrol.vpe_list), + .tc_list_lock = __SPIN_LOCK_UNLOCKED(tc_list_lock), + .tc_list = LIST_HEAD_INIT(vpecontrol.tc_list) +}; + +static void release_progmem(void *ptr); + +/* get the vpe associated with this minor */ +static struct vpe *get_vpe(int minor) +{ + struct vpe *res, *v; + + if (!cpu_has_mipsmt) + return NULL; + + res = NULL; + spin_lock(&vpecontrol.vpe_list_lock); + list_for_each_entry(v, &vpecontrol.vpe_list, list) { + if (v->minor == minor) { + res = v; + break; + } + } + spin_unlock(&vpecontrol.vpe_list_lock); + + return res; +} + +/* get the vpe associated with this minor */ +static struct tc *get_tc(int index) +{ + struct tc *res, *t; + + res = NULL; + spin_lock(&vpecontrol.tc_list_lock); + list_for_each_entry(t, &vpecontrol.tc_list, list) { + if (t->index == index) { + res = t; + break; + } + } + spin_unlock(&vpecontrol.tc_list_lock); + + return res; +} + +/* allocate a vpe and associate it with this minor (or index) */ +static struct vpe *alloc_vpe(int minor) +{ + struct vpe *v; + + if ((v = kzalloc(sizeof(struct vpe), GFP_KERNEL)) == NULL) + return NULL; + + INIT_LIST_HEAD(&v->tc); + spin_lock(&vpecontrol.vpe_list_lock); + list_add_tail(&v->list, &vpecontrol.vpe_list); + spin_unlock(&vpecontrol.vpe_list_lock); + + INIT_LIST_HEAD(&v->notify); + v->minor = minor; + + return v; +} + +/* allocate a tc. At startup only tc0 is running, all other can be halted. */ +static struct tc *alloc_tc(int index) +{ + struct tc *tc; + + if ((tc = kzalloc(sizeof(struct tc), GFP_KERNEL)) == NULL) + goto out; + + INIT_LIST_HEAD(&tc->tc); + tc->index = index; + + spin_lock(&vpecontrol.tc_list_lock); + list_add_tail(&tc->list, &vpecontrol.tc_list); + spin_unlock(&vpecontrol.tc_list_lock); + +out: + return tc; +} + +/* clean up and free everything */ +static void release_vpe(struct vpe *v) +{ + list_del(&v->list); + if (v->load_addr) + release_progmem(v); + kfree(v); +} + +static void __maybe_unused dump_mtregs(void) +{ + unsigned long val; + + val = read_c0_config3(); + printk("config3 0x%lx MT %ld\n", val, + (val & CONFIG3_MT) >> CONFIG3_MT_SHIFT); + + val = read_c0_mvpcontrol(); + printk("MVPControl 0x%lx, STLB %ld VPC %ld EVP %ld\n", val, + (val & MVPCONTROL_STLB) >> MVPCONTROL_STLB_SHIFT, + (val & MVPCONTROL_VPC) >> MVPCONTROL_VPC_SHIFT, + (val & MVPCONTROL_EVP)); + + val = read_c0_mvpconf0(); + printk("mvpconf0 0x%lx, PVPE %ld PTC %ld M %ld\n", val, + (val & MVPCONF0_PVPE) >> MVPCONF0_PVPE_SHIFT, + val & MVPCONF0_PTC, (val & MVPCONF0_M) >> MVPCONF0_M_SHIFT); +} + +/* Find some VPE program space */ +static void *alloc_progmem(unsigned long len) +{ + void *addr; + +#ifdef CONFIG_MIPS_VPE_LOADER_TOM + /* + * This means you must tell Linux to use less memory than you + * physically have, for example by passing a mem= boot argument. + */ + addr = pfn_to_kaddr(max_low_pfn); + memset(addr, 0, len); +#else + /* simple grab some mem for now */ + addr = kzalloc(len, GFP_KERNEL); +#endif + + return addr; +} + +static void release_progmem(void *ptr) +{ +#ifndef CONFIG_MIPS_VPE_LOADER_TOM + kfree(ptr); +#endif +} + +/* Update size with this section: return offset. */ +static long get_offset(unsigned long *size, Elf_Shdr * sechdr) +{ + long ret; + + ret = ALIGN(*size, sechdr->sh_addralign ? : 1); + *size = ret + sechdr->sh_size; + return ret; +} + +/* Lay out the SHF_ALLOC sections in a way not dissimilar to how ld + might -- code, read-only data, read-write data, small data. Tally + sizes, and place the offsets into sh_entsize fields: high bit means it + belongs in init. */ +static void layout_sections(struct module *mod, const Elf_Ehdr * hdr, + Elf_Shdr * sechdrs, const char *secstrings) +{ + static unsigned long const masks[][2] = { + /* NOTE: all executable code must be the first section + * in this array; otherwise modify the text_size + * finder in the two loops below */ + {SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL}, + {SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL}, + {SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL}, + {ARCH_SHF_SMALL | SHF_ALLOC, 0} + }; + unsigned int m, i; + + for (i = 0; i < hdr->e_shnum; i++) + sechdrs[i].sh_entsize = ~0UL; + + for (m = 0; m < ARRAY_SIZE(masks); ++m) { + for (i = 0; i < hdr->e_shnum; ++i) { + Elf_Shdr *s = &sechdrs[i]; + + // || strncmp(secstrings + s->sh_name, ".init", 5) == 0) + if ((s->sh_flags & masks[m][0]) != masks[m][0] + || (s->sh_flags & masks[m][1]) + || s->sh_entsize != ~0UL) + continue; + s->sh_entsize = + get_offset((unsigned long *)&mod->core_size, s); + } + + if (m == 0) + mod->core_text_size = mod->core_size; + + } +} + + +/* from module-elf32.c, but subverted a little */ + +struct mips_hi16 { + struct mips_hi16 *next; + Elf32_Addr *addr; + Elf32_Addr value; +}; + +static struct mips_hi16 *mips_hi16_list; +static unsigned int gp_offs, gp_addr; + +static int apply_r_mips_none(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + return 0; +} + +static int apply_r_mips_gprel16(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + int rel; + + if( !(*location & 0xffff) ) { + rel = (int)v - gp_addr; + } + else { + /* .sbss + gp(relative) + offset */ + /* kludge! */ + rel = (int)(short)((int)v + gp_offs + + (int)(short)(*location & 0xffff) - gp_addr); + } + + if( (rel > 32768) || (rel < -32768) ) { + printk(KERN_DEBUG "VPE loader: apply_r_mips_gprel16: " + "relative address 0x%x out of range of gp register\n", + rel); + return -ENOEXEC; + } + + *location = (*location & 0xffff0000) | (rel & 0xffff); + + return 0; +} + +static int apply_r_mips_pc16(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + int rel; + rel = (((unsigned int)v - (unsigned int)location)); + rel >>= 2; // because the offset is in _instructions_ not bytes. + rel -= 1; // and one instruction less due to the branch delay slot. + + if( (rel > 32768) || (rel < -32768) ) { + printk(KERN_DEBUG "VPE loader: " + "apply_r_mips_pc16: relative address out of range 0x%x\n", rel); + return -ENOEXEC; + } + + *location = (*location & 0xffff0000) | (rel & 0xffff); + + return 0; +} + +static int apply_r_mips_32(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + *location += v; + + return 0; +} + +static int apply_r_mips_26(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + if (v % 4) { + printk(KERN_DEBUG "VPE loader: apply_r_mips_26 " + " unaligned relocation\n"); + return -ENOEXEC; + } + +/* + * Not desperately convinced this is a good check of an overflow condition + * anyway. But it gets in the way of handling undefined weak symbols which + * we want to set to zero. + * if ((v & 0xf0000000) != (((unsigned long)location + 4) & 0xf0000000)) { + * printk(KERN_ERR + * "module %s: relocation overflow\n", + * me->name); + * return -ENOEXEC; + * } + */ + + *location = (*location & ~0x03ffffff) | + ((*location + (v >> 2)) & 0x03ffffff); + return 0; +} + +static int apply_r_mips_hi16(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + struct mips_hi16 *n; + + /* + * We cannot relocate this one now because we don't know the value of + * the carry we need to add. Save the information, and let LO16 do the + * actual relocation. + */ + n = kmalloc(sizeof *n, GFP_KERNEL); + if (!n) + return -ENOMEM; + + n->addr = location; + n->value = v; + n->next = mips_hi16_list; + mips_hi16_list = n; + + return 0; +} + +static int apply_r_mips_lo16(struct module *me, uint32_t *location, + Elf32_Addr v) +{ + unsigned long insnlo = *location; + Elf32_Addr val, vallo; + struct mips_hi16 *l, *next; + + /* Sign extend the addend we extract from the lo insn. */ + vallo = ((insnlo & 0xffff) ^ 0x8000) - 0x8000; + + if (mips_hi16_list != NULL) { + + l = mips_hi16_list; + while (l != NULL) { + unsigned long insn; + + /* + * The value for the HI16 had best be the same. + */ + if (v != l->value) { + printk(KERN_DEBUG "VPE loader: " + "apply_r_mips_lo16/hi16: \t" + "inconsistent value information\n"); + goto out_free; + } + + /* + * Do the HI16 relocation. Note that we actually don't + * need to know anything about the LO16 itself, except + * where to find the low 16 bits of the addend needed + * by the LO16. + */ + insn = *l->addr; + val = ((insn & 0xffff) << 16) + vallo; + val += v; + + /* + * Account for the sign extension that will happen in + * the low bits. + */ + val = ((val >> 16) + ((val & 0x8000) != 0)) & 0xffff; + + insn = (insn & ~0xffff) | val; + *l->addr = insn; + + next = l->next; + kfree(l); + l = next; + } + + mips_hi16_list = NULL; + } + + /* + * Ok, we're done with the HI16 relocs. Now deal with the LO16. + */ + val = v + vallo; + insnlo = (insnlo & ~0xffff) | (val & 0xffff); + *location = insnlo; + + return 0; + +out_free: + while (l != NULL) { + next = l->next; + kfree(l); + l = next; + } + mips_hi16_list = NULL; + + return -ENOEXEC; +} + +static int (*reloc_handlers[]) (struct module *me, uint32_t *location, + Elf32_Addr v) = { + [R_MIPS_NONE] = apply_r_mips_none, + [R_MIPS_32] = apply_r_mips_32, + [R_MIPS_26] = apply_r_mips_26, + [R_MIPS_HI16] = apply_r_mips_hi16, + [R_MIPS_LO16] = apply_r_mips_lo16, + [R_MIPS_GPREL16] = apply_r_mips_gprel16, + [R_MIPS_PC16] = apply_r_mips_pc16 +}; + +static char *rstrs[] = { + [R_MIPS_NONE] = "MIPS_NONE", + [R_MIPS_32] = "MIPS_32", + [R_MIPS_26] = "MIPS_26", + [R_MIPS_HI16] = "MIPS_HI16", + [R_MIPS_LO16] = "MIPS_LO16", + [R_MIPS_GPREL16] = "MIPS_GPREL16", + [R_MIPS_PC16] = "MIPS_PC16" +}; + +static int apply_relocations(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + Elf32_Rel *rel = (void *) sechdrs[relsec].sh_addr; + Elf32_Sym *sym; + uint32_t *location; + unsigned int i; + Elf32_Addr v; + int res; + + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + Elf32_Word r_info = rel[i].r_info; + + /* This is where to make the change */ + location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + /* This is the symbol it is referring to */ + sym = (Elf32_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(r_info); + + if (!sym->st_value) { + printk(KERN_DEBUG "%s: undefined weak symbol %s\n", + me->name, strtab + sym->st_name); + /* just print the warning, dont barf */ + } + + v = sym->st_value; + + res = reloc_handlers[ELF32_R_TYPE(r_info)](me, location, v); + if( res ) { + char *r = rstrs[ELF32_R_TYPE(r_info)]; + printk(KERN_WARNING "VPE loader: .text+0x%x " + "relocation type %s for symbol \"%s\" failed\n", + rel[i].r_offset, r ? r : "UNKNOWN", + strtab + sym->st_name); + return res; + } + } + + return 0; +} + +static inline void save_gp_address(unsigned int secbase, unsigned int rel) +{ + gp_addr = secbase + rel; + gp_offs = gp_addr - (secbase & 0xffff0000); +} +/* end module-elf32.c */ + + + +/* Change all symbols so that sh_value encodes the pointer directly. */ +static void simplify_symbols(Elf_Shdr * sechdrs, + unsigned int symindex, + const char *strtab, + const char *secstrings, + unsigned int nsecs, struct module *mod) +{ + Elf_Sym *sym = (void *)sechdrs[symindex].sh_addr; + unsigned long secbase, bssbase = 0; + unsigned int i, n = sechdrs[symindex].sh_size / sizeof(Elf_Sym); + int size; + + /* find the .bss section for COMMON symbols */ + for (i = 0; i < nsecs; i++) { + if (strncmp(secstrings + sechdrs[i].sh_name, ".bss", 4) == 0) { + bssbase = sechdrs[i].sh_addr; + break; + } + } + + for (i = 1; i < n; i++) { + switch (sym[i].st_shndx) { + case SHN_COMMON: + /* Allocate space for the symbol in the .bss section. + st_value is currently size. + We want it to have the address of the symbol. */ + + size = sym[i].st_value; + sym[i].st_value = bssbase; + + bssbase += size; + break; + + case SHN_ABS: + /* Don't need to do anything */ + break; + + case SHN_UNDEF: + /* ret = -ENOENT; */ + break; + + case SHN_MIPS_SCOMMON: + printk(KERN_DEBUG "simplify_symbols: ignoring SHN_MIPS_SCOMMON " + "symbol <%s> st_shndx %d\n", strtab + sym[i].st_name, + sym[i].st_shndx); + // .sbss section + break; + + default: + secbase = sechdrs[sym[i].st_shndx].sh_addr; + + if (strncmp(strtab + sym[i].st_name, "_gp", 3) == 0) { + save_gp_address(secbase, sym[i].st_value); + } + + sym[i].st_value += secbase; + break; + } + } +} + +#ifdef DEBUG_ELFLOADER +static void dump_elfsymbols(Elf_Shdr * sechdrs, unsigned int symindex, + const char *strtab, struct module *mod) +{ + Elf_Sym *sym = (void *)sechdrs[symindex].sh_addr; + unsigned int i, n = sechdrs[symindex].sh_size / sizeof(Elf_Sym); + + printk(KERN_DEBUG "dump_elfsymbols: n %d\n", n); + for (i = 1; i < n; i++) { + printk(KERN_DEBUG " i %d name <%s> 0x%x\n", i, + strtab + sym[i].st_name, sym[i].st_value); + } +} +#endif + +/* We are prepared so configure and start the VPE... */ +static int vpe_run(struct vpe * v) +{ + unsigned long flags, val, dmt_flag; + struct vpe_notifications *n; + unsigned int vpeflags; + struct tc *t; + + /* check we are the Master VPE */ + local_irq_save(flags); + val = read_c0_vpeconf0(); + if (!(val & VPECONF0_MVP)) { + printk(KERN_WARNING + "VPE loader: only Master VPE's are allowed to configure MT\n"); + local_irq_restore(flags); + + return -1; + } + + dmt_flag = dmt(); + vpeflags = dvpe(); + + if (!list_empty(&v->tc)) { + if ((t = list_entry(v->tc.next, struct tc, tc)) == NULL) { + evpe(vpeflags); + emt(dmt_flag); + local_irq_restore(flags); + + printk(KERN_WARNING + "VPE loader: TC %d is already in use.\n", + t->index); + return -ENOEXEC; + } + } else { + evpe(vpeflags); + emt(dmt_flag); + local_irq_restore(flags); + + printk(KERN_WARNING + "VPE loader: No TC's associated with VPE %d\n", + v->minor); + + return -ENOEXEC; + } + + /* Put MVPE's into 'configuration state' */ + set_c0_mvpcontrol(MVPCONTROL_VPC); + + settc(t->index); + + /* should check it is halted, and not activated */ + if ((read_tc_c0_tcstatus() & TCSTATUS_A) || !(read_tc_c0_tchalt() & TCHALT_H)) { + evpe(vpeflags); + emt(dmt_flag); + local_irq_restore(flags); + + printk(KERN_WARNING "VPE loader: TC %d is already active!\n", + t->index); + + return -ENOEXEC; + } + + /* Write the address we want it to start running from in the TCPC register. */ + write_tc_c0_tcrestart((unsigned long)v->__start); + write_tc_c0_tccontext((unsigned long)0); + + /* + * Mark the TC as activated, not interrupt exempt and not dynamically + * allocatable + */ + val = read_tc_c0_tcstatus(); + val = (val & ~(TCSTATUS_DA | TCSTATUS_IXMT)) | TCSTATUS_A; + write_tc_c0_tcstatus(val); + + write_tc_c0_tchalt(read_tc_c0_tchalt() & ~TCHALT_H); + + /* + * The sde-kit passes 'memsize' to __start in $a3, so set something + * here... Or set $a3 to zero and define DFLT_STACK_SIZE and + * DFLT_HEAP_SIZE when you compile your program + */ + mttgpr(6, v->ntcs); + mttgpr(7, physical_memsize); + + /* set up VPE1 */ + /* + * bind the TC to VPE 1 as late as possible so we only have the final + * VPE registers to set up, and so an EJTAG probe can trigger on it + */ + write_tc_c0_tcbind((read_tc_c0_tcbind() & ~TCBIND_CURVPE) | 1); + + write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~(VPECONF0_VPA)); + + back_to_back_c0_hazard(); + + /* Set up the XTC bit in vpeconf0 to point at our tc */ + write_vpe_c0_vpeconf0( (read_vpe_c0_vpeconf0() & ~(VPECONF0_XTC)) + | (t->index << VPECONF0_XTC_SHIFT)); + + back_to_back_c0_hazard(); + + /* enable this VPE */ + write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() | VPECONF0_VPA); + + /* clear out any left overs from a previous program */ + write_vpe_c0_status(0); + write_vpe_c0_cause(0); + + /* take system out of configuration state */ + clear_c0_mvpcontrol(MVPCONTROL_VPC); + + /* + * SMTC/SMVP kernels manage VPE enable independently, + * but uniprocessor kernels need to turn it on, even + * if that wasn't the pre-dvpe() state. + */ +#ifdef CONFIG_SMP + evpe(vpeflags); +#else + evpe(EVPE_ENABLE); +#endif + emt(dmt_flag); + local_irq_restore(flags); + + list_for_each_entry(n, &v->notify, list) + n->start(minor); + + return 0; +} + +static int find_vpe_symbols(struct vpe * v, Elf_Shdr * sechdrs, + unsigned int symindex, const char *strtab, + struct module *mod) +{ + Elf_Sym *sym = (void *)sechdrs[symindex].sh_addr; + unsigned int i, n = sechdrs[symindex].sh_size / sizeof(Elf_Sym); + + for (i = 1; i < n; i++) { + if (strcmp(strtab + sym[i].st_name, "__start") == 0) { + v->__start = sym[i].st_value; + } + + if (strcmp(strtab + sym[i].st_name, "vpe_shared") == 0) { + v->shared_ptr = (void *)sym[i].st_value; + } + } + + if ( (v->__start == 0) || (v->shared_ptr == NULL)) + return -1; + + return 0; +} + +/* + * Allocates a VPE with some program code space(the load address), copies the + * contents of the program (p)buffer performing relocatations/etc, free's it + * when finished. + */ +static int vpe_elfload(struct vpe * v) +{ + Elf_Ehdr *hdr; + Elf_Shdr *sechdrs; + long err = 0; + char *secstrings, *strtab = NULL; + unsigned int len, i, symindex = 0, strindex = 0, relocate = 0; + struct module mod; // so we can re-use the relocations code + + memset(&mod, 0, sizeof(struct module)); + strcpy(mod.name, "VPE loader"); + + hdr = (Elf_Ehdr *) v->pbuffer; + len = v->plen; + + /* Sanity checks against insmoding binaries or wrong arch, + weird elf version */ + if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0 + || (hdr->e_type != ET_REL && hdr->e_type != ET_EXEC) + || !elf_check_arch(hdr) + || hdr->e_shentsize != sizeof(*sechdrs)) { + printk(KERN_WARNING + "VPE loader: program wrong arch or weird elf version\n"); + + return -ENOEXEC; + } + + if (hdr->e_type == ET_REL) + relocate = 1; + + if (len < hdr->e_shoff + hdr->e_shnum * sizeof(Elf_Shdr)) { + printk(KERN_ERR "VPE loader: program length %u truncated\n", + len); + + return -ENOEXEC; + } + + /* Convenience variables */ + sechdrs = (void *)hdr + hdr->e_shoff; + secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; + sechdrs[0].sh_addr = 0; + + /* And these should exist, but gcc whinges if we don't init them */ + symindex = strindex = 0; + + if (relocate) { + for (i = 1; i < hdr->e_shnum; i++) { + if (sechdrs[i].sh_type != SHT_NOBITS + && len < sechdrs[i].sh_offset + sechdrs[i].sh_size) { + printk(KERN_ERR "VPE program length %u truncated\n", + len); + return -ENOEXEC; + } + + /* Mark all sections sh_addr with their address in the + temporary image. */ + sechdrs[i].sh_addr = (size_t) hdr + sechdrs[i].sh_offset; + + /* Internal symbols and strings. */ + if (sechdrs[i].sh_type == SHT_SYMTAB) { + symindex = i; + strindex = sechdrs[i].sh_link; + strtab = (char *)hdr + sechdrs[strindex].sh_offset; + } + } + layout_sections(&mod, hdr, sechdrs, secstrings); + } + + v->load_addr = alloc_progmem(mod.core_size); + if (!v->load_addr) + return -ENOMEM; + + pr_info("VPE loader: loading to %p\n", v->load_addr); + + if (relocate) { + for (i = 0; i < hdr->e_shnum; i++) { + void *dest; + + if (!(sechdrs[i].sh_flags & SHF_ALLOC)) + continue; + + dest = v->load_addr + sechdrs[i].sh_entsize; + + if (sechdrs[i].sh_type != SHT_NOBITS) + memcpy(dest, (void *)sechdrs[i].sh_addr, + sechdrs[i].sh_size); + /* Update sh_addr to point to copy in image. */ + sechdrs[i].sh_addr = (unsigned long)dest; + + printk(KERN_DEBUG " section sh_name %s sh_addr 0x%x\n", + secstrings + sechdrs[i].sh_name, sechdrs[i].sh_addr); + } + + /* Fix up syms, so that st_value is a pointer to location. */ + simplify_symbols(sechdrs, symindex, strtab, secstrings, + hdr->e_shnum, &mod); + + /* Now do relocations. */ + for (i = 1; i < hdr->e_shnum; i++) { + const char *strtab = (char *)sechdrs[strindex].sh_addr; + unsigned int info = sechdrs[i].sh_info; + + /* Not a valid relocation section? */ + if (info >= hdr->e_shnum) + continue; + + /* Don't bother with non-allocated sections */ + if (!(sechdrs[info].sh_flags & SHF_ALLOC)) + continue; + + if (sechdrs[i].sh_type == SHT_REL) + err = apply_relocations(sechdrs, strtab, symindex, i, + &mod); + else if (sechdrs[i].sh_type == SHT_RELA) + err = apply_relocate_add(sechdrs, strtab, symindex, i, + &mod); + if (err < 0) + return err; + + } + } else { + struct elf_phdr *phdr = (struct elf_phdr *) ((char *)hdr + hdr->e_phoff); + + for (i = 0; i < hdr->e_phnum; i++) { + if (phdr->p_type == PT_LOAD) { + memcpy((void *)phdr->p_paddr, + (char *)hdr + phdr->p_offset, + phdr->p_filesz); + memset((void *)phdr->p_paddr + phdr->p_filesz, + 0, phdr->p_memsz - phdr->p_filesz); + } + phdr++; + } + + for (i = 0; i < hdr->e_shnum; i++) { + /* Internal symbols and strings. */ + if (sechdrs[i].sh_type == SHT_SYMTAB) { + symindex = i; + strindex = sechdrs[i].sh_link; + strtab = (char *)hdr + sechdrs[strindex].sh_offset; + + /* mark the symtab's address for when we try to find the + magic symbols */ + sechdrs[i].sh_addr = (size_t) hdr + sechdrs[i].sh_offset; + } + } + } + + /* make sure it's physically written out */ + flush_icache_range((unsigned long)v->load_addr, + (unsigned long)v->load_addr + v->len); + + if ((find_vpe_symbols(v, sechdrs, symindex, strtab, &mod)) < 0) { + if (v->__start == 0) { + printk(KERN_WARNING "VPE loader: program does not contain " + "a __start symbol\n"); + return -ENOEXEC; + } + + if (v->shared_ptr == NULL) + printk(KERN_WARNING "VPE loader: " + "program does not contain vpe_shared symbol.\n" + " Unable to use AMVP (AP/SP) facilities.\n"); + } + + printk(" elf loaded\n"); + return 0; +} + +static void cleanup_tc(struct tc *tc) +{ + unsigned long flags; + unsigned int mtflags, vpflags; + int tmp; + + local_irq_save(flags); + mtflags = dmt(); + vpflags = dvpe(); + /* Put MVPE's into 'configuration state' */ + set_c0_mvpcontrol(MVPCONTROL_VPC); + + settc(tc->index); + tmp = read_tc_c0_tcstatus(); + + /* mark not allocated and not dynamically allocatable */ + tmp &= ~(TCSTATUS_A | TCSTATUS_DA); + tmp |= TCSTATUS_IXMT; /* interrupt exempt */ + write_tc_c0_tcstatus(tmp); + + write_tc_c0_tchalt(TCHALT_H); + mips_ihb(); + + /* bind it to anything other than VPE1 */ +// write_tc_c0_tcbind(read_tc_c0_tcbind() & ~TCBIND_CURVPE); // | TCBIND_CURVPE + + clear_c0_mvpcontrol(MVPCONTROL_VPC); + evpe(vpflags); + emt(mtflags); + local_irq_restore(flags); +} + +static int getcwd(char *buff, int size) +{ + mm_segment_t old_fs; + int ret; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + + ret = sys_getcwd(buff, size); + + set_fs(old_fs); + + return ret; +} + +/* checks VPE is unused and gets ready to load program */ +static int vpe_open(struct inode *inode, struct file *filp) +{ + enum vpe_state state; + struct vpe_notifications *not; + struct vpe *v; + int ret; + + if (minor != iminor(inode)) { + /* assume only 1 device at the moment. */ + pr_warning("VPE loader: only vpe1 is supported\n"); + + return -ENODEV; + } + + if ((v = get_vpe(tclimit)) == NULL) { + pr_warning("VPE loader: unable to get vpe\n"); + + return -ENODEV; + } + + state = xchg(&v->state, VPE_STATE_INUSE); + if (state != VPE_STATE_UNUSED) { + printk(KERN_DEBUG "VPE loader: tc in use dumping regs\n"); + + list_for_each_entry(not, &v->notify, list) { + not->stop(tclimit); + } + + release_progmem(v->load_addr); + cleanup_tc(get_tc(tclimit)); + } + + /* this of-course trashes what was there before... */ + v->pbuffer = vmalloc(P_SIZE); + if (!v->pbuffer) { + pr_warning("VPE loader: unable to allocate memory\n"); + return -ENOMEM; + } + v->plen = P_SIZE; + v->load_addr = NULL; + v->len = 0; + + v->uid = filp->f_cred->fsuid; + v->gid = filp->f_cred->fsgid; + +#ifdef CONFIG_MIPS_APSP_KSPD + /* get kspd to tell us when a syscall_exit happens */ + if (!kspd_events_reqd) { + kspd_notify(&kspd_events); + kspd_events_reqd++; + } +#endif + + v->cwd[0] = 0; + ret = getcwd(v->cwd, VPE_PATH_MAX); + if (ret < 0) + printk(KERN_WARNING "VPE loader: open, getcwd returned %d\n", ret); + + v->shared_ptr = NULL; + v->__start = 0; + + return 0; +} + +static int vpe_release(struct inode *inode, struct file *filp) +{ + struct vpe *v; + Elf_Ehdr *hdr; + int ret = 0; + + v = get_vpe(tclimit); + if (v == NULL) + return -ENODEV; + + hdr = (Elf_Ehdr *) v->pbuffer; + if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) == 0) { + if (vpe_elfload(v) >= 0) { + vpe_run(v); + } else { + printk(KERN_WARNING "VPE loader: ELF load failed.\n"); + ret = -ENOEXEC; + } + } else { + printk(KERN_WARNING "VPE loader: only elf files are supported\n"); + ret = -ENOEXEC; + } + + /* It's good to be able to run the SP and if it chokes have a look at + the /dev/rt?. But if we reset the pointer to the shared struct we + lose what has happened. So perhaps if garbage is sent to the vpe + device, use it as a trigger for the reset. Hopefully a nice + executable will be along shortly. */ + if (ret < 0) + v->shared_ptr = NULL; + + vfree(v->pbuffer); + v->plen = 0; + + return ret; +} + +static ssize_t vpe_write(struct file *file, const char __user * buffer, + size_t count, loff_t * ppos) +{ + size_t ret = count; + struct vpe *v; + + if (iminor(file->f_path.dentry->d_inode) != minor) + return -ENODEV; + + v = get_vpe(tclimit); + if (v == NULL) + return -ENODEV; + + if ((count + v->len) > v->plen) { + printk(KERN_WARNING + "VPE loader: elf size too big. Perhaps strip uneeded symbols\n"); + return -ENOMEM; + } + + count -= copy_from_user(v->pbuffer + v->len, buffer, count); + if (!count) + return -EFAULT; + + v->len += count; + return ret; +} + +static const struct file_operations vpe_fops = { + .owner = THIS_MODULE, + .open = vpe_open, + .release = vpe_release, + .write = vpe_write, + .llseek = noop_llseek, +}; + +/* module wrapper entry points */ +/* give me a vpe */ +vpe_handle vpe_alloc(void) +{ + int i; + struct vpe *v; + + /* find a vpe */ + for (i = 1; i < MAX_VPES; i++) { + if ((v = get_vpe(i)) != NULL) { + v->state = VPE_STATE_INUSE; + return v; + } + } + return NULL; +} + +EXPORT_SYMBOL(vpe_alloc); + +/* start running from here */ +int vpe_start(vpe_handle vpe, unsigned long start) +{ + struct vpe *v = vpe; + + v->__start = start; + return vpe_run(v); +} + +EXPORT_SYMBOL(vpe_start); + +/* halt it for now */ +int vpe_stop(vpe_handle vpe) +{ + struct vpe *v = vpe; + struct tc *t; + unsigned int evpe_flags; + + evpe_flags = dvpe(); + + if ((t = list_entry(v->tc.next, struct tc, tc)) != NULL) { + + settc(t->index); + write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~VPECONF0_VPA); + } + + evpe(evpe_flags); + + return 0; +} + +EXPORT_SYMBOL(vpe_stop); + +/* I've done with it thank you */ +int vpe_free(vpe_handle vpe) +{ + struct vpe *v = vpe; + struct tc *t; + unsigned int evpe_flags; + + if ((t = list_entry(v->tc.next, struct tc, tc)) == NULL) { + return -ENOEXEC; + } + + evpe_flags = dvpe(); + + /* Put MVPE's into 'configuration state' */ + set_c0_mvpcontrol(MVPCONTROL_VPC); + + settc(t->index); + write_vpe_c0_vpeconf0(read_vpe_c0_vpeconf0() & ~VPECONF0_VPA); + + /* halt the TC */ + write_tc_c0_tchalt(TCHALT_H); + mips_ihb(); + + /* mark the TC unallocated */ + write_tc_c0_tcstatus(read_tc_c0_tcstatus() & ~TCSTATUS_A); + + v->state = VPE_STATE_UNUSED; + + clear_c0_mvpcontrol(MVPCONTROL_VPC); + evpe(evpe_flags); + + return 0; +} + +EXPORT_SYMBOL(vpe_free); + +void *vpe_get_shared(int index) +{ + struct vpe *v; + + if ((v = get_vpe(index)) == NULL) + return NULL; + + return v->shared_ptr; +} + +EXPORT_SYMBOL(vpe_get_shared); + +int vpe_getuid(int index) +{ + struct vpe *v; + + if ((v = get_vpe(index)) == NULL) + return -1; + + return v->uid; +} + +EXPORT_SYMBOL(vpe_getuid); + +int vpe_getgid(int index) +{ + struct vpe *v; + + if ((v = get_vpe(index)) == NULL) + return -1; + + return v->gid; +} + +EXPORT_SYMBOL(vpe_getgid); + +int vpe_notify(int index, struct vpe_notifications *notify) +{ + struct vpe *v; + + if ((v = get_vpe(index)) == NULL) + return -1; + + list_add(¬ify->list, &v->notify); + return 0; +} + +EXPORT_SYMBOL(vpe_notify); + +char *vpe_getcwd(int index) +{ + struct vpe *v; + + if ((v = get_vpe(index)) == NULL) + return NULL; + + return v->cwd; +} + +EXPORT_SYMBOL(vpe_getcwd); + +#ifdef CONFIG_MIPS_APSP_KSPD +static void kspd_sp_exit( int sp_id) +{ + cleanup_tc(get_tc(sp_id)); +} +#endif + +static ssize_t store_kill(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct vpe *vpe = get_vpe(tclimit); + struct vpe_notifications *not; + + list_for_each_entry(not, &vpe->notify, list) { + not->stop(tclimit); + } + + release_progmem(vpe->load_addr); + cleanup_tc(get_tc(tclimit)); + vpe_stop(vpe); + vpe_free(vpe); + + return len; +} + +static ssize_t show_ntcs(struct device *cd, struct device_attribute *attr, + char *buf) +{ + struct vpe *vpe = get_vpe(tclimit); + + return sprintf(buf, "%d\n", vpe->ntcs); +} + +static ssize_t store_ntcs(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct vpe *vpe = get_vpe(tclimit); + unsigned long new; + char *endp; + + new = simple_strtoul(buf, &endp, 0); + if (endp == buf) + goto out_einval; + + if (new == 0 || new > (hw_tcs - tclimit)) + goto out_einval; + + vpe->ntcs = new; + + return len; + +out_einval: + return -EINVAL; +} + +static struct device_attribute vpe_class_attributes[] = { + __ATTR(kill, S_IWUSR, NULL, store_kill), + __ATTR(ntcs, S_IRUGO | S_IWUSR, show_ntcs, store_ntcs), + {} +}; + +static void vpe_device_release(struct device *cd) +{ + kfree(cd); +} + +struct class vpe_class = { + .name = "vpe", + .owner = THIS_MODULE, + .dev_release = vpe_device_release, + .dev_attrs = vpe_class_attributes, +}; + +struct device vpe_device; + +static int __init vpe_module_init(void) +{ + unsigned int mtflags, vpflags; + unsigned long flags, val; + struct vpe *v = NULL; + struct tc *t; + int tc, err; + + if (!cpu_has_mipsmt) { + printk("VPE loader: not a MIPS MT capable processor\n"); + return -ENODEV; + } + + if (vpelimit == 0) { + printk(KERN_WARNING "No VPEs reserved for AP/SP, not " + "initializing VPE loader.\nPass maxvpes=<n> argument as " + "kernel argument\n"); + + return -ENODEV; + } + + if (tclimit == 0) { + printk(KERN_WARNING "No TCs reserved for AP/SP, not " + "initializing VPE loader.\nPass maxtcs=<n> argument as " + "kernel argument\n"); + + return -ENODEV; + } + + major = register_chrdev(0, module_name, &vpe_fops); + if (major < 0) { + printk("VPE loader: unable to register character device\n"); + return major; + } + + err = class_register(&vpe_class); + if (err) { + printk(KERN_ERR "vpe_class registration failed\n"); + goto out_chrdev; + } + + device_initialize(&vpe_device); + vpe_device.class = &vpe_class, + vpe_device.parent = NULL, + dev_set_name(&vpe_device, "vpe1"); + vpe_device.devt = MKDEV(major, minor); + err = device_add(&vpe_device); + if (err) { + printk(KERN_ERR "Adding vpe_device failed\n"); + goto out_class; + } + + local_irq_save(flags); + mtflags = dmt(); + vpflags = dvpe(); + + /* Put MVPE's into 'configuration state' */ + set_c0_mvpcontrol(MVPCONTROL_VPC); + + /* dump_mtregs(); */ + + val = read_c0_mvpconf0(); + hw_tcs = (val & MVPCONF0_PTC) + 1; + hw_vpes = ((val & MVPCONF0_PVPE) >> MVPCONF0_PVPE_SHIFT) + 1; + + for (tc = tclimit; tc < hw_tcs; tc++) { + /* + * Must re-enable multithreading temporarily or in case we + * reschedule send IPIs or similar we might hang. + */ + clear_c0_mvpcontrol(MVPCONTROL_VPC); + evpe(vpflags); + emt(mtflags); + local_irq_restore(flags); + t = alloc_tc(tc); + if (!t) { + err = -ENOMEM; + goto out; + } + + local_irq_save(flags); + mtflags = dmt(); + vpflags = dvpe(); + set_c0_mvpcontrol(MVPCONTROL_VPC); + + /* VPE's */ + if (tc < hw_tcs) { + settc(tc); + + if ((v = alloc_vpe(tc)) == NULL) { + printk(KERN_WARNING "VPE: unable to allocate VPE\n"); + + goto out_reenable; + } + + v->ntcs = hw_tcs - tclimit; + + /* add the tc to the list of this vpe's tc's. */ + list_add(&t->tc, &v->tc); + + /* deactivate all but vpe0 */ + if (tc >= tclimit) { + unsigned long tmp = read_vpe_c0_vpeconf0(); + + tmp &= ~VPECONF0_VPA; + + /* master VPE */ + tmp |= VPECONF0_MVP; + write_vpe_c0_vpeconf0(tmp); + } + + /* disable multi-threading with TC's */ + write_vpe_c0_vpecontrol(read_vpe_c0_vpecontrol() & ~VPECONTROL_TE); + + if (tc >= vpelimit) { + /* + * Set config to be the same as vpe0, + * particularly kseg0 coherency alg + */ + write_vpe_c0_config(read_c0_config()); + } + } + + /* TC's */ + t->pvpe = v; /* set the parent vpe */ + + if (tc >= tclimit) { + unsigned long tmp; + + settc(tc); + + /* Any TC that is bound to VPE0 gets left as is - in case + we are running SMTC on VPE0. A TC that is bound to any + other VPE gets bound to VPE0, ideally I'd like to make + it homeless but it doesn't appear to let me bind a TC + to a non-existent VPE. Which is perfectly reasonable. + + The (un)bound state is visible to an EJTAG probe so may + notify GDB... + */ + + if (((tmp = read_tc_c0_tcbind()) & TCBIND_CURVPE)) { + /* tc is bound >vpe0 */ + write_tc_c0_tcbind(tmp & ~TCBIND_CURVPE); + + t->pvpe = get_vpe(0); /* set the parent vpe */ + } + + /* halt the TC */ + write_tc_c0_tchalt(TCHALT_H); + mips_ihb(); + + tmp = read_tc_c0_tcstatus(); + + /* mark not activated and not dynamically allocatable */ + tmp &= ~(TCSTATUS_A | TCSTATUS_DA); + tmp |= TCSTATUS_IXMT; /* interrupt exempt */ + write_tc_c0_tcstatus(tmp); + } + } + +out_reenable: + /* release config state */ + clear_c0_mvpcontrol(MVPCONTROL_VPC); + + evpe(vpflags); + emt(mtflags); + local_irq_restore(flags); + +#ifdef CONFIG_MIPS_APSP_KSPD + kspd_events.kspd_sp_exit = kspd_sp_exit; +#endif + return 0; + +out_class: + class_unregister(&vpe_class); +out_chrdev: + unregister_chrdev(major, module_name); + +out: + return err; +} + +static void __exit vpe_module_exit(void) +{ + struct vpe *v, *n; + + device_del(&vpe_device); + unregister_chrdev(major, module_name); + + /* No locking needed here */ + list_for_each_entry_safe(v, n, &vpecontrol.vpe_list, list) { + if (v->state != VPE_STATE_UNUSED) + release_vpe(v); + } +} + +module_init(vpe_module_init); +module_exit(vpe_module_exit); +MODULE_DESCRIPTION("MIPS VPE Loader"); +MODULE_AUTHOR("Elizabeth Oldham, MIPS Technologies, Inc."); +MODULE_LICENSE("GPL"); |