Name: New Module Loader Base: PPC64 support Author: Rusty Russell Status: Experimental Depends: Module/module-base.patch.gz D: This patch provides basic PPC64 support for modules. diff -urNp -I \$.*\$ --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.27/include/asm-ppc64/module.h working-2.5.27-modules/include/asm-ppc64/module.h --- linux-2.5.27/include/asm-ppc64/module.h Wed Feb 20 17:57:18 2002 +++ working-2.5.27-modules/include/asm-ppc64/module.h Wed Jul 24 10:55:17 2002 @@ -1,18 +1,36 @@ #ifndef _ASM_PPC64_MODULE_H #define _ASM_PPC64_MODULE_H -/* - * This file contains the PPC architecture specific module code. - * - * Copyright (C) 2001 PPC 64 Team, IBM Corp - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - */ -#define module_map(x) vmalloc(x) -#define module_unmap(x) vfree(x) -#define arch_init_modules(x) do { } while (0) -#define module_arch_init(x) (0) +/* There's actually a third entry here, but it's unused */ +struct ppc64_opd_entry +{ + void *funcaddr; + unsigned long r2; +}; + +/* Like PPC32, we need little trampolines to do > 24-bit jumps (into + the kernel itself). But on PPC64, these need to be used for every + jump, actually, to reset r2 (TOC+0x8000). */ +struct ppc64_stub_entry +{ + /* 28 byte jump instruction sequence (7 instructions) */ + unsigned char jump[28]; + unsigned char unused[4]; + /* Data for the above code */ + struct ppc64_opd_entry opd; +}; + +struct mod_arch_specific +{ + /* End of core == start of stubs. */ + unsigned int stubs_offset; + + /* What section is the TOC? */ + unsigned int toc_section; +}; + +#define Elf_Shdr Elf64_Shdr +#define Elf_Sym Elf64_Sym +#define Elf_Ehdr Elf64_Ehdr + #endif /* _ASM_PPC64_MODULE_H */ diff -urNp -I \$.*\$ --exclude TAGS -X /home/rusty/current-dontdiff --minimal linux-2.5.27/arch/ppc64/kernel/module.c working-2.5.27-modules/arch/ppc64/kernel/module.c --- linux-2.5.27/arch/ppc64/kernel/module.c Thu Jan 1 10:00:00 1970 +++ working-2.5.27-modules/arch/ppc64/kernel/module.c Wed Jul 24 15:54:59 2002 @@ -0,0 +1,513 @@ +#include +#include + +/* FIXME: This is wrong. We really need to allocate a GOT in which we + can put the OPD pointers, within range of the TOC ptr. Then have + two sets of stubs: init stubs and core stubs. + + The problem is that this requires (in our current model) moving the + TOC section to the end, where we can place the GOT adjacent to it. + Of course, I don't know what we do if sizeof(TOC) + sizeof(GOT) > + 64k, or we have two-level TOCs... + + Using a magic allocator which places modules within 32MB solves all + this. --RR. */ + +/* We use a stub to fix up r2 (TOC ptr) and to jump to the (usually + external) function which may be more than 24-bits away. We could + simply patch the new r2 value and function pointer into the stub, + but it's significantly shorter to put these values at the end of + the stub code, and patch the stub address (32-bits relative to the + TOC ptr, r2) into the stub. */ +static struct ppc64_stub_entry ppc64_stub = +{ .jump = { + 0x3d, 0x82, 0x00, 0x00, /* addis r12,r2, */ + 0x39, 0x8c, 0x00, 0x00, /* addi r12,r12, */ + /* Save current r2 value in magic place on the stack. */ + 0xf8, 0x41, 0x00, 0x28, /* std r2,40(r1) */ + 0xe9, 0x6c, 0x00, 0x20, /* ld r11,32(r12) */ + 0xe8, 0x4c, 0x00, 0x28, /* ld r2,40(r12) */ + 0x7d, 0x69, 0x03, 0xa6, /* mtctr r11 */ + 0x4e, 0x80, 0x04, 0x20 /* bctr */ +} }; + +/* Count how many different 24-bit relocations (different symbol, + different addend) */ +static unsigned int count_relocs(const Elf64_Rela *rela, unsigned int num) +{ + unsigned int i, j, ret = 0; + + /* Sure, this is order(n^2), but it's usually short, and not + time critical */ + for (i = 0; i < num; i++) { + /* Only count 24-bit relocs, others don't need stubs */ + if (ELF64_R_TYPE(rela[i].r_info) != R_PPC_REL24) + continue; + for (j = 0; j < i; j++) { + /* If this addend appeared before, it's + already been counted */ + if (rela[i].r_info == rela[j].r_info + && rela[i].r_addend == rela[j].r_addend) + break; + } + if (j == i) ret++; + } + return ret; +} + +static void *alloc_and_zero(unsigned long size) +{ + void *ret; + + ret = vmalloc(size); + if (!ret) ret = ERR_PTR(-ENOMEM); + else memset(ret, 0, size); + + return ret; +} + +/* Get size of potential trampolines required. */ +static unsigned long get_stubs_size(const Elf64_Ehdr *hdr, + const Elf64_Shdr *sechdrs) +{ + /* One extra reloc so it's always 0-funcaddr terminated */ + unsigned long relocs = 1; + unsigned i; + + /* Every relocated section... */ + for (i = 1; i < hdr->e_shnum; i++) { + if (sechdrs[i].sh_type == SHT_RELA) { + printk("Found relocations in section %u\n", i); + printk("Ptr: %p. Number: %lu\n", + (void *)hdr + sechdrs[i].sh_offset, + sechdrs[i].sh_size / sizeof(Elf64_Rela)); + relocs += count_relocs((void *)hdr + + sechdrs[i].sh_offset, + sechdrs[i].sh_size + / sizeof(Elf64_Rela)); + } + } + + printk("Looks like a total of %lu stubs, max\n", relocs); + return relocs * sizeof(struct ppc64_stub_entry); +} + +static unsigned int get_section(const char *name, + unsigned int num_sections, + const Elf64_Shdr *sechdrs, + const char *secstrings) +{ + unsigned int i; + + for (i = 1; i < num_sections; i++) { + if (strcmp(secstrings + sechdrs[i].sh_name, name) == 0) + return i; + } + return 0; +} + +/* Allocating separate core and init too hard: place together. */ +void *module_core_alloc(const Elf64_Ehdr *hdr, + const Elf64_Shdr *sechdrs, + const char *secstrings, + struct module *me) +{ + me->arch.toc_section + = get_section(".toc", hdr->e_shnum, sechdrs, secstrings); + if (!me->arch.toc_section) { + printk("%s: no TOC found\n", me->name); + return ERR_PTR(-ENOEXEC); + } + + me->core_size += me->init_size; + me->arch.stubs_offset = me->core_size; + me->core_size += get_stubs_size(hdr, sechdrs); + + return alloc_and_zero(me->core_size); +} + +void *module_init_alloc(const Elf64_Ehdr *hdr, + const Elf64_Shdr *sechdrs, + const char *secstrings, + struct module *me) +{ + return NULL; +} + +int apply_relocate(Elf64_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + printk(KERN_ERR "module %s: Non-ADD RELOCATION unsupported\n", + me->name); + return -ENOEXEC; +} + +/* Both low and high 16 bits are added as SIGNED additions, so if low + 16 bits has high bit set, high 16 bits must be adjusted. These + macros do that (stolen from binutils). */ +#define PPC_LO(v) ((v) & 0xffff) +#define PPC_HI(v) (((v) >> 16) & 0xffff) +#define PPC_HA(v) PPC_HI ((v) + 0x8000) + +/* Patch stub to reference function's OPD entry or function itself: + see module_finalize */ +static inline int create_stub(struct ppc64_stub_entry *entry, + uint64_t toc, /* Our TOC */ + unsigned long func, + unsigned long r2) +{ + Elf64_Half *loc1, *loc2; + long reladdr; + + *entry = ppc64_stub; + + loc1 = (Elf64_Half *)&entry->jump[2]; + loc2 = (Elf64_Half *)&entry->jump[6]; + + /* Stub uses address relative to r2, which is set to the TOC + + 0x8000. */ + reladdr = (uint64_t)entry - (toc + 0x8000); + if (reladdr > 0x7FFFFFFF || reladdr < -(0x80000000L)) { + printk("Address %p of stub out of range of %p.\n", + (void *)reladdr, (void *)toc); + return 0; + } + printk("Stub %p get data from reladdr %li\n", + entry, reladdr); + + *loc1 = PPC_HA(reladdr); + *loc2 = PPC_LO(reladdr); + entry->opd.funcaddr = (void *)func; + entry->opd.r2 = r2; + + printk("Stub: %08X %08X %08X %08X %08X %08X %08X: %p %p\n", + ((uint32_t *)entry->jump)[0], + ((uint32_t *)entry->jump)[1], + ((uint32_t *)entry->jump)[2], + ((uint32_t *)entry->jump)[3], + ((uint32_t *)entry->jump)[4], + ((uint32_t *)entry->jump)[5], + ((uint32_t *)entry->jump)[6], + (void *)entry->opd.funcaddr, + (void *)entry->opd.r2); + return 1; +} + +/* Given ".function" reference, return address of "function" opd entry */ +static uint64_t find_function(const char *name, + Elf_Shdr *sechdrs, + unsigned int symindex, + const char *strtab, + struct module *me, + int *external) +{ + struct kernel_symbol_group *kg; + uint64_t val; + + if (name[0] != '.') + return 0; + + val = find_symbol_internal(sechdrs, symindex, strtab, name+1, &kg); + + if (kg) *external = 1; + else *external = 0; + + printk("Function %s is at %p (%s)\n", + name+1, (void *)val, *external ? "external" : "internal"); + return val; +} + +/* Create stub for this OPD address */ +static uint64_t stub_for_addr(Elf64_Shdr *sechdrs, + uint64_t addr, + uint64_t r2, + struct module *me) +{ + struct ppc64_stub_entry *stubs; + unsigned int i; + + printk("Looking for stub for %p\n", (void *)addr); + + /* Find this stub, or if that fails, the next avail. entry */ + stubs = me->module_core + me->arch.stubs_offset; + for (i = 0; stubs[i].opd.funcaddr; i++) { + if (stubs[i].opd.funcaddr == (void *)addr) { + printk("Reusing stub %u (%p) for %p\n", + i, &stubs[i], (void *)addr); + return (uint64_t)&stubs[i]; + } + } + printk("Here for %p\n", (void *)addr); + + if (!create_stub(&stubs[i], + sechdrs[me->arch.toc_section].sh_offset, + addr, r2)) { + printk("Stub creation FAILED for %u (%p)\n", i, (void *)addr); + return (uint64_t)-EINVAL; + } + printk("CREATED stub %u for %p\n", i, (void *)addr); + + return (uint64_t)&stubs[i]; +} + +/* With the stub we have chosen, PLT entries have to be 32-bit of r2 + (ie. close to TOC section). So we put them all in the core. */ +static uint64_t do_stub_call(Elf64_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + void *location, + Elf64_Sym *sym, + struct module *me, + int *external) +{ + uint64_t func; + + printk("Doing stub for %lu (%s - %u)\n", + (unsigned long)location, + strtab + sym->st_name, + sym->st_name); + func = find_function(strtab + sym->st_name, + sechdrs, symindex, strtab, me, external); + if (!func) { + printk("Can't find function `%s'\n", strtab + sym->st_name); + return (uint64_t)-ENOENT; + } + + return stub_for_addr(sechdrs, func, 0, me); +} + +/* We expect a noop next: if it is, replace it with instruction to + restore r2. */ +static int restore_r2(uint32_t *instruction) +{ + if (*instruction != 0x60000000) { + printk("Expect noop after relocate, got %08x\n", *instruction); + return 0; + } + *instruction = 0xe8410028; /* ld r2,40(r1) */ + return 1; +} + +int apply_relocate_add(Elf64_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + unsigned int i; + Elf64_Rela *rela = (void *)sechdrs[relsec].sh_offset; + Elf64_Sym *sym; + uint64_t *location; + uint64_t value; + int external; + + printk("Applying ADD relocate section %u to %u\n", relsec, + sechdrs[relsec].sh_info); + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rela); i++) { + /* This is where to make the change */ + location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_offset + + rela[i].r_offset; + /* This is the symbol it is referring to */ + sym = (Elf64_Sym *)sechdrs[symindex].sh_offset + + ELF64_R_SYM(rela[i].r_info); + + printk("RELOC at %p: %li-type as %s (%lu) + %li\n", + location, ELF64_R_TYPE(rela[i].r_info), + strtab + sym->st_name, sym->st_value, + rela[i].r_addend); + + /* REL24 references to (external) .function won't + resolve; deal with that below */ + if (!sym->st_value + && ELF64_R_TYPE(rela[i].r_info) != R_PPC_REL24) { + printk("%s: Unknown symbol %s (index %u)\n", + me->name, strtab + sym->st_name, + sym->st_shndx); + return -ENOENT; + } + /* `Everything is relative'. */ + value = sym->st_value + rela[i].r_addend; + + switch (ELF64_R_TYPE(rela[i].r_info)) { + case R_PPC64_ADDR32: + /* Simply set it */ + printk("Setting location %p to 32-bit value %u\n", + location, (unsigned int)value); + *(uint32_t *)location = value; + break; + + case R_PPC64_ADDR64: + /* Simply set it */ + *(uint64_t *)location = value; + printk("Setting location %p to 64-bit value %p\n", + location, (void *)value); + break; + + case R_PPC64_TOC: + /* Make it point 0x8000 into the TOC (this + gives the value maximum span in an + instruction which uses a signed offset) */ + *(uint64_t *)location + = sechdrs[me->arch.toc_section].sh_offset + + 0x8000; + printk("Setting location %p to TOC (%p)\n", + location, + (void *)sechdrs[me->arch.toc_section].sh_offset + + 0x8000); + break; + + case R_PPC64_TOC16_DS: + /* Subtact TOC pointer */ + value -= sechdrs[me->arch.toc_section].sh_offset + + 0x8000; + if ((value & 3) != 0 || value + 0x8000 > 0xffff) { + printk("%s: bad TOC16_DS relocation (%lu)\n", + me->name, value); + return -ENOEXEC; + } + *((uint16_t *) location) + = (*((uint16_t *) location) & ~0xfffc) + | (value & 0xfffc); + printk("Modifying location %p by TOC (%p) => %i\n", + location, + (void *)sechdrs[me->arch.toc_section].sh_offset + + 0x8000, + *(uint16_t *)location); + break; + + case R_PPC_REL24: + if (sym->st_name == 0) { + /* Alan Modra says: + + this is tricky because the + assembler decides that a reference + within one object file to a local + function can be optimised to a + reference to the section sym + some + offset. that works normally as + there's no need to access the opd + entry. you've broken normal + assumptions about linking to static + functions. :( */ + external = 0; + } else { + /* Always go through stub if going external */ + value = do_stub_call(sechdrs, + strtab, + symindex, + location, + sym, + me, + &external); + } + if (IS_ERR((void *)value)) + return value; + + /* Convert value to relative */ + value -= (uint64_t)location; + if (value + 0x2000000 > 0x3ffffff || (value & 3) != 0){ + printk("REL24 relocation %li out of range!\n", + (long int)value); + return -ENOEXEC; + } + + /* Only replace bits 2 through 26 */ + printk("REL24 value = %016lX. location = %016lX\n", + value, (uint64_t)location); + printk("Location before: %08X.\n", + *(uint32_t *)location); + *(uint32_t *)location + = (*(uint32_t *)location & ~0x03fffffc) + | (value & 0x03fffffc); + printk("Location after: %08X.\n", + *(uint32_t *)location); + printk("ie. jump to %08X+%016lX = %016lX\n", + *(uint32_t *)location & 0x03fffffc, + (uint64_t)location, + (*(uint32_t *)location & 0x03fffffc) + + (uint64_t)location); + if (external && !restore_r2((uint32_t *)location + 1)) + return -ENOEXEC; + break; + + default: + printk("Unknown ADD relocation: %lu\n", + ELF64_R_TYPE(rela[i].r_info)); + return -ENOEXEC; + } + } + + return 0; +} + +/* + * The exception table needs to be sorted because we use the macros + * which put things into the exception table in a variety of segments + * such as the prep, pmac, chrp, etc. segments as well as the init + * segment and the main kernel text segment. + */ +static inline void +sort_ex_table(struct exception_table_entry *start, + struct exception_table_entry *finish) +{ + struct exception_table_entry el, *p, *q; + + /* insertion sort */ + for (p = start + 1; p < finish; ++p) { + /* start .. p-1 is sorted */ + if (p[0].insn < p[-1].insn) { + /* move element p down to its right place */ + el = *p; + q = p; + do { + /* el comes before q[-1], move q[-1] up one */ + q[0] = q[-1]; + --q; + } while (q > start && el.insn < q[-1].insn); + *q = el; + } + } +} + +/* Free memory returned from module_core_alloc/module_init_alloc */ +void module_free(struct module *mod, void *module_region) +{ + vfree(module_region); + /* FIXME: If module_region == mod->init_region, trim exception + table entries. */ +} + +int module_finalize(const Elf_Ehdr *hdr, + const Elf_Shdr *sechdrs, + struct module *me) +{ + struct ppc64_stub_entry *stubs; + unsigned int i; + char *secstrings; + + /* Here is where we copy the OPD entry into the stub: we don't + do it ealier in case it's actually in the same module, and + hasn't been relocated yet. */ + stubs = me->module_core + me->arch.stubs_offset; + for (i = 0; stubs[i].opd.funcaddr; i++) { + struct ppc64_opd_entry *opd; + + /* We mark opd pointers by setting r2 to 0: otherwise + it's a function pointer already. */ + if (stubs[i].opd.r2 == 0) { + /* We put the opd entry ptr in the funcaddr member. */ + opd = (void *)stubs[i].opd.funcaddr; + stubs[i].opd = *opd; + } + } + + /* Sort extable section it */ + secstrings = (void *)sechdrs[hdr->e_shstrndx].sh_offset; + i = get_section("__ex_table", hdr->e_shnum, sechdrs, secstrings); + sort_ex_table((void *)sechdrs[i].sh_offset, + (void *)sechdrs[i].sh_offset + sechdrs[i].sh_size); + return 0; +}