📄 vpe.c
字号:
/* * 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 enviroment 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. * * You'll need to have the following device files. * mknod /dev/vpe0 c 63 0 * mknod /dev/vpe1 c 63 1 */#include <linux/config.h>#include <linux/kernel.h>#include <linux/module.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 <asm/atomic.h>#include <asm/cpu.h>#include <asm/processor.h>#include <asm/system.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))static char module_name[] = "vpe";static int major;/* 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#define MAX_VPES 16enum 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; u32 len; char *pbuffer; u32 plen; 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;};struct tc { enum tc_state state; int index; /* parent VPE */ struct vpe *pvpe; /* The list of TC's with this VPE */ struct list_head tc; /* The global list of tc's */ struct list_head list;};struct vpecontrol_ { /* Virtual processing elements */ struct list_head vpe_list; /* Thread contexts */ struct list_head tc_list;} vpecontrol;static void release_progmem(void *ptr);static void dump_vpe(struct vpe * v);extern void save_gp_address(unsigned int secbase, unsigned int rel);/* get the vpe associated with this minor */struct vpe *get_vpe(int minor){ struct vpe *v; list_for_each_entry(v, &vpecontrol.vpe_list, list) { if (v->minor == minor) return v; } printk(KERN_DEBUG "VPE: get_vpe minor %d not found\n", minor); return NULL;}/* get the vpe associated with this minor */struct tc *get_tc(int index){ struct tc *t; list_for_each_entry(t, &vpecontrol.tc_list, list) { if (t->index == index) return t; } printk(KERN_DEBUG "VPE: get_tc index %d not found\n", index); return NULL;}struct tc *get_tc_unused(void){ struct tc *t; list_for_each_entry(t, &vpecontrol.tc_list, list) { if (t->state == TC_STATE_UNUSED) return t; } printk(KERN_DEBUG "VPE: All TC's are in use\n"); return NULL;}/* allocate a vpe and associate it with this minor (or index) */struct vpe *alloc_vpe(int minor){ struct vpe *v; if ((v = kzalloc(sizeof(struct vpe), GFP_KERNEL)) == NULL) { printk(KERN_WARNING "VPE: alloc_vpe no mem\n"); return NULL; } INIT_LIST_HEAD(&v->tc); list_add_tail(&v->list, &vpecontrol.vpe_list); v->minor = minor; return v;}/* allocate a tc. At startup only tc0 is running, all other can be halted. */struct tc *alloc_tc(int index){ struct tc *t; if ((t = kzalloc(sizeof(struct tc), GFP_KERNEL)) == NULL) { printk(KERN_WARNING "VPE: alloc_tc no mem\n"); return NULL; } INIT_LIST_HEAD(&t->tc); list_add_tail(&t->list, &vpecontrol.tc_list); t->index = index; return t;}/* clean up and free everything */void release_vpe(struct vpe *v){ list_del(&v->list); if (v->load_addr) release_progmem(v); kfree(v);}void 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_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); 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_vpeconf0(); printk("VPEConf0 0x%lx MVP %ld\n", val, (val & VPECONF0_MVP) >> VPECONF0_MVP_SHIFT);}/* Find some VPE program space */static void *alloc_progmem(u32 len){#ifdef CONFIG_MIPS_VPE_LOADER_TOM /* this means you must tell linux to use less memory than you physically have */ return (void *)((max_pfn * PAGE_SIZE) + KSEG0);#else // simple grab some mem for now return kmalloc(len, GFP_KERNEL);#endif}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(&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_ERR "apply_r_mips_gprel16: relative address out of range 0x%x %d\n", rel, 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_ERR "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_ERR "module %s: dangerous relocation mod4\n", me->name); 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; /* Sign extend the addend we extract from the lo insn. */ vallo = ((insnlo & 0xffff) ^ 0x8000) - 0x8000; if (mips_hi16_list != NULL) { struct mips_hi16 *l; l = mips_hi16_list; while (l != NULL) { struct mips_hi16 *next; unsigned long insn; /* * The value for the HI16 had best be the same. */ if (v != l->value) { printk("%d != %d\n", v, l->value); goto out_danger; } /* * 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_danger: printk(KERN_ERR "module %s: dangerous " "relocation\n", me->name); 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};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 ) { printk(KERN_DEBUG "relocation error 0x%x sym refer <%s> value 0x%x " "type 0x%x r_info 0x%x\n", (unsigned int)location, strtab + sym->st_name, v, r_info, ELF32_R_TYPE(r_info)); } if (res) return res; } return 0;}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 int 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 ret = 0, 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; } 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; } } return ret;}#ifdef DEBUG_ELFLOADERstatic void dump_elfsymbols(Elf_Shdr * sechdrs, unsigned int symindex, const char *strtab, struct module *mod)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -