900 lines · 21.3 KB · C
RawDownload
/* * 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> #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)) struct vpe_control 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)}; /* get the vpe associated with this minor */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 == VPE_MODULE_MINOR) {			res = v;			break;		}	}	spin_unlock(&vpecontrol.vpe_list_lock); 	return res;} /* get the vpe associated with this minor */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) */struct vpe *alloc_vpe(int minor){	struct vpe *v; 	v = kzalloc_obj(struct vpe);	if (v == NULL)		goto out; 	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 = VPE_MODULE_MINOR; out:	return v;} /* allocate a tc. At startup only tc0 is running, all other can be halted. */struct tc *alloc_tc(int index){	struct tc *tc; 	tc = kzalloc_obj(struct tc);	if (tc == 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 */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(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;} 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];			struct module_memory *mod_mem; 			mod_mem = &mod->mem[MOD_TEXT]; 			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_mem->size, s);		}	}} /* 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)) {		pr_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)) {		pr_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) {		pr_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_obj(*n);	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) {				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; 	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) {			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;		}	} 	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.

Lines 1–500 of 900.