/* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2004, 2005 MIPS Technologies, Inc. All rights reserved. * Copyright (C) 2013 Imagination Technologies Ltd. * * VPE support module for loading a MIPS SP program into VPE1. The SP * environment is rather simple since there are no TLBs. It needs * to be relocatable (or partially linked). Initialize your stack in * the startup-code. The loader looks for the symbol __start and sets * up the execution to resume from there. To load and run, simply do * a cat SP 'binary' to the /dev/vpe1 device.
*/ #include <linux/kernel.h> #include <linux/device.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/list.h> #include <linux/vmalloc.h> #include <linux/elf.h> #include <linux/seq_file.h> #include <linux/string.h> #include <linux/syscalls.h> #include <linux/moduleloader.h> #include <linux/interrupt.h> #include <linux/poll.h> #include <linux/memblock.h> #include <asm/mipsregs.h> #include <asm/mipsmtregs.h> #include <asm/cacheflush.h> #include <linux/atomic.h> #include <asm/mips_mt.h> #include <asm/processor.h> #include <asm/vpe.h>
/* clean up and free everything */ void release_vpe(struct vpe *v)
{
list_del(&v->list); if (v->load_addr)
release_progmem(v->load_addr);
kfree(v);
}
/* Find some VPE program space */ void *alloc_progmem(unsignedlong 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
/* Update size with this section: return offset. */ staticlong get_offset(unsignedlong *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. */ staticvoid layout_sections(struct module *mod, const Elf_Ehdr *hdr,
Elf_Shdr *sechdrs, constchar *secstrings)
{ staticunsignedlongconst 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}
}; unsignedint 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]; struct module_memory *mod_mem;
if ((rel > 32768) || (rel < -32768)) {
pr_debug("VPE loader: apply_r_mips_gprel16: relative address 0x%x out of range of gp register\n",
rel); return -ENOEXEC;
}
staticint apply_r_mips_pc16(struct module *me, uint32_t *location,
Elf32_Addr v)
{ int rel;
rel = (((unsignedint)v - (unsignedint)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)) {
pr_debug("VPE loader: apply_r_mips_pc16: relative address out of range 0x%x\n",
rel); 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; * }
*/
/* * 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;
/* 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) { unsignedlong insn;
/* * The value for the HI16 had best be the same.
*/ if (v != l->value) {
pr_debug("VPE loader: apply_r_mips_lo16/hi16: 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;
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) {
pr_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)];
pr_warn("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;
}
}
/* Change all symbols so that sh_value encodes the pointer directly. */ staticvoid simplify_symbols(Elf_Shdr *sechdrs, unsignedint symindex, constchar *strtab, constchar *secstrings, unsignedint nsecs, struct module *mod)
{
Elf_Sym *sym = (void *)sechdrs[symindex].sh_addr; unsignedlong secbase, bssbase = 0; unsignedint 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. */
pr_debug("dump_elfsymbols: n %d\n", n); for (i = 1; i < n; i++) {
pr_debug(" i %d name <%s> 0x%x\n", i, strtab + sym[i].st_name,
sym[i].st_value);
}
} #endif
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.
*/ staticint vpe_elfload(struct vpe *v)
{
Elf_Ehdr *hdr;
Elf_Shdr *sechdrs; long err = 0; char *secstrings, *strtab = NULL; unsignedint len, i, symindex = 0, strindex = 0, relocate = 0; struct module mod; /* so we can re-use the relocations code */
v->load_addr = alloc_progmem(mod.mem[MOD_TEXT].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 = (unsignedlong)dest;
/* 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++) { constchar *strtab = (char *)sechdrs[strindex].sh_addr; unsignedint 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); elseif (sechdrs[i].sh_type == SHT_RELA)
err = apply_relocate_add(sechdrs, strtab,
symindex, i, &mod); if (err < 0) return err;
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 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((unsignedlong)v->load_addr,
(unsignedlong)v->load_addr + v->len);
if ((find_vpe_symbols(v, sechdrs, symindex, strtab, &mod)) < 0) { if (v->__start == 0) {
pr_warn("VPE loader: program does not contain a __start symbol\n"); return -ENOEXEC;
}
if (v->shared_ptr == NULL)
pr_warn("VPE loader: program does not contain vpe_shared symbol.\n" " Unable to use AMVP (AP/SP) facilities.\n");
}
pr_info(" elf loaded\n"); return 0;
}
/* checks VPE is unused and gets ready to load program */ staticint vpe_open(struct inode *inode, struct file *filp)
{ enum vpe_state state; struct vpe_notifications *notifier; struct vpe *v;
if (VPE_MODULE_MINOR != iminor(inode)) { /* assume only 1 device at the moment. */
pr_warn("VPE loader: only vpe1 is supported\n");
return -ENODEV;
}
v = get_vpe(aprp_cpu_index()); if (v == NULL) {
pr_warn("VPE loader: unable to get vpe\n");
return -ENODEV;
}
state = xchg(&v->state, VPE_STATE_INUSE); if (state != VPE_STATE_UNUSED) {
pr_debug("VPE loader: tc in use dumping regs\n");
/* this of-course trashes what was there before... */
v->pbuffer = vmalloc(P_SIZE); if (!v->pbuffer) {
pr_warn("VPE loader: unable to allocate memory\n"); return -ENOMEM;
}
v->plen = P_SIZE;
v->load_addr = NULL;
v->len = 0;
v->shared_ptr = NULL;
v->__start = 0;
return 0;
}
staticint vpe_release(struct inode *inode, struct file *filp)
{ #ifdef CONFIG_MIPS_VPE_LOADER_MT struct vpe *v;
Elf_Ehdr *hdr; int ret = 0;
v = get_vpe(aprp_cpu_index()); 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 {
pr_warn("VPE loader: ELF load failed.\n");
ret = -ENOEXEC;
}
} else {
pr_warn("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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.