]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/powerpc/kernel/module_64.c
powerpc: Correct DSCR during TM context switch
[karo-tx-linux.git] / arch / powerpc / kernel / module_64.c
index f6544d7071d6b2a086bab27960f10f4cda65d99a..077d2ce6c5a7c64b2a51404e7e33fbb5004541b9 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/vmalloc.h>
 #include <linux/ftrace.h>
 #include <linux/bug.h>
+#include <linux/uaccess.h>
 #include <asm/module.h>
 #include <asm/firmware.h>
 #include <asm/code-patching.h>
 #define DEBUGP(fmt , ...)
 #endif
 
+#if defined(_CALL_ELF) && _CALL_ELF == 2
+#define R2_STACK_OFFSET 24
+
+/* An address is simply the address of the function. */
+typedef unsigned long func_desc_t;
+
+static func_desc_t func_desc(unsigned long addr)
+{
+       return addr;
+}
+static unsigned long func_addr(unsigned long addr)
+{
+       return addr;
+}
+static unsigned long stub_func_addr(func_desc_t func)
+{
+       return func;
+}
+
+/* PowerPC64 specific values for the Elf64_Sym st_other field.  */
+#define STO_PPC64_LOCAL_BIT    5
+#define STO_PPC64_LOCAL_MASK   (7 << STO_PPC64_LOCAL_BIT)
+#define PPC64_LOCAL_ENTRY_OFFSET(other)                                        \
+ (((1 << (((other) & STO_PPC64_LOCAL_MASK) >> STO_PPC64_LOCAL_BIT)) >> 2) << 2)
+
+static unsigned int local_entry_offset(const Elf64_Sym *sym)
+{
+       /* sym->st_other indicates offset to local entry point
+        * (otherwise it will assume r12 is the address of the start
+        * of function and try to derive r2 from it). */
+       return PPC64_LOCAL_ENTRY_OFFSET(sym->st_other);
+}
+#else
+#define R2_STACK_OFFSET 40
+
+/* An address is address of the OPD entry, which contains address of fn. */
+typedef struct ppc64_opd_entry func_desc_t;
+
+static func_desc_t func_desc(unsigned long addr)
+{
+       return *(struct ppc64_opd_entry *)addr;
+}
+static unsigned long func_addr(unsigned long addr)
+{
+       return func_desc(addr).funcaddr;
+}
+static unsigned long stub_func_addr(func_desc_t func)
+{
+       return func.funcaddr;
+}
+static unsigned int local_entry_offset(const Elf64_Sym *sym)
+{
+       return 0;
+}
+#endif
+
 /* 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) */
+       /* 28 byte jump instruction sequence (7 instructions). We only
+        * need 6 instructions on ABIv2 but we always allocate 7 so
+        * so we don't have to modify the trampoline load instruction. */
        u32 jump[7];
        u32 unused;
        /* Data for the above code */
-       struct ppc64_opd_entry opd;
+       func_desc_t funcdata;
 };
 
-/* We use a stub to fix up r2 (TOC ptr) and to jump to the (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 = {
-       0x3d820000, /* addis   r12,r2, <high> */
-       0x398c0000, /* addi    r12,r12, <low> */
+/*
+ * PPC64 uses 24 bit jumps, but we need to jump into other modules or
+ * the kernel which may be further.  So we jump to a stub.
+ *
+ * For ELFv1 we need to use this to set up the new r2 value (aka TOC
+ * pointer).  For ELFv2 it's the callee's responsibility to set up the
+ * new r2, but for both we need to save the old r2.
+ *
+ * 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 u32 ppc64_stub_insns[] = {
+       0x3d620000,                     /* addis   r11,r2, <high> */
+       0x396b0000,                     /* addi    r11,r11, <low> */
        /* Save current r2 value in magic place on the stack. */
-       0xf8410028, /* std     r2,40(r1) */
-       0xe96c0020, /* ld      r11,32(r12) */
-       0xe84c0028, /* ld      r2,40(r12) */
-       0x7d6903a6, /* mtctr   r11 */
-       0x4e800420  /* bctr */
-} };
+       0xf8410000|R2_STACK_OFFSET,     /* std     r2,R2_STACK_OFFSET(r1) */
+       0xe98b0020,                     /* ld      r12,32(r11) */
+#if !defined(_CALL_ELF) || _CALL_ELF != 2
+       /* Set up new r2 from function descriptor */
+       0xe84b0028,                     /* ld      r2,40(r11) */
+#endif
+       0x7d8903a6,                     /* mtctr   r12 */
+       0x4e800420                      /* bctr */
+};
+
+#ifdef CONFIG_DYNAMIC_FTRACE
+
+static u32 ppc64_stub_mask[] = {
+       0xffff0000,
+       0xffff0000,
+       0xffffffff,
+       0xffffffff,
+#if !defined(_CALL_ELF) || _CALL_ELF != 2
+       0xffffffff,
+#endif
+       0xffffffff,
+       0xffffffff
+};
+
+bool is_module_trampoline(u32 *p)
+{
+       unsigned int i;
+       u32 insns[ARRAY_SIZE(ppc64_stub_insns)];
+
+       BUILD_BUG_ON(sizeof(ppc64_stub_insns) != sizeof(ppc64_stub_mask));
+
+       if (probe_kernel_read(insns, p, sizeof(insns)))
+               return -EFAULT;
+
+       for (i = 0; i < ARRAY_SIZE(ppc64_stub_insns); i++) {
+               u32 insna = insns[i];
+               u32 insnb = ppc64_stub_insns[i];
+               u32 mask = ppc64_stub_mask[i];
+
+               if ((insna & mask) != (insnb & mask))
+                       return false;
+       }
+
+       return true;
+}
+
+int module_trampoline_target(struct module *mod, u32 *trampoline,
+                            unsigned long *target)
+{
+       u32 buf[2];
+       u16 upper, lower;
+       long offset;
+       void *toc_entry;
+
+       if (probe_kernel_read(buf, trampoline, sizeof(buf)))
+               return -EFAULT;
+
+       upper = buf[0] & 0xffff;
+       lower = buf[1] & 0xffff;
+
+       /* perform the addis/addi, both signed */
+       offset = ((short)upper << 16) + (short)lower;
+
+       /*
+        * Now get the address this trampoline jumps to. This
+        * is always 32 bytes into our trampoline stub.
+        */
+       toc_entry = (void *)mod->arch.toc + offset + 32;
+
+       if (probe_kernel_read(target, toc_entry, sizeof(*target)))
+               return -EFAULT;
+
+       return 0;
+}
+
+#endif
 
 /* Count how many different 24-bit relocations (different symbol,
    different addend) */
@@ -172,6 +308,7 @@ static unsigned long get_stubs_size(const Elf64_Ehdr *hdr,
        return relocs * sizeof(struct ppc64_stub_entry);
 }
 
+/* Still needed for ELFv2, for .TOC. */
 static void dedotify_versions(struct modversion_info *vers,
                              unsigned long size)
 {
@@ -182,7 +319,7 @@ static void dedotify_versions(struct modversion_info *vers,
                        memmove(vers->name, vers->name+1, strlen(vers->name));
 }
 
-/* Undefined symbols which refer to .funcname, hack to funcname */
+/* Undefined symbols which refer to .funcname, hack to funcname (or .TOC.) */
 static void dedotify(Elf64_Sym *syms, unsigned int numsyms, char *strtab)
 {
        unsigned int i;
@@ -208,7 +345,7 @@ static Elf64_Sym *find_dot_toc(Elf64_Shdr *sechdrs,
 
        for (i = 1; i < numsyms; i++) {
                if (syms[i].st_shndx == SHN_UNDEF
-                   && strcmp(strtab + syms[i].st_name, ".TOC.") == 0)
+                   && strcmp(strtab + syms[i].st_name, "TOC.") == 0)
                        return &syms[i];
        }
        return NULL;
@@ -278,12 +415,12 @@ static inline unsigned long my_r2(Elf64_Shdr *sechdrs, struct module *me)
 /* Patch stub to reference function and correct r2 value. */
 static inline int create_stub(Elf64_Shdr *sechdrs,
                              struct ppc64_stub_entry *entry,
-                             struct ppc64_opd_entry *opd,
+                             unsigned long addr,
                              struct module *me)
 {
        long reladdr;
 
-       *entry = ppc64_stub;
+       memcpy(entry->jump, ppc64_stub_insns, sizeof(ppc64_stub_insns));
 
        /* Stub uses address relative to r2. */
        reladdr = (unsigned long)entry - my_r2(sechdrs, me);
@@ -296,33 +433,31 @@ static inline int create_stub(Elf64_Shdr *sechdrs,
 
        entry->jump[0] |= PPC_HA(reladdr);
        entry->jump[1] |= PPC_LO(reladdr);
-       entry->opd.funcaddr = opd->funcaddr;
-       entry->opd.r2 = opd->r2;
+       entry->funcdata = func_desc(addr);
        return 1;
 }
 
-/* Create stub to jump to function described in this OPD: we need the
+/* Create stub to jump to function described in this OPD/ptr: we need the
    stub to set up the TOC ptr (r2) for the function. */
 static unsigned long stub_for_addr(Elf64_Shdr *sechdrs,
-                                  unsigned long opdaddr,
+                                  unsigned long addr,
                                   struct module *me)
 {
        struct ppc64_stub_entry *stubs;
-       struct ppc64_opd_entry *opd = (void *)opdaddr;
        unsigned int i, num_stubs;
 
        num_stubs = sechdrs[me->arch.stubs_section].sh_size / sizeof(*stubs);
 
        /* Find this stub, or if that fails, the next avail. entry */
        stubs = (void *)sechdrs[me->arch.stubs_section].sh_addr;
-       for (i = 0; stubs[i].opd.funcaddr; i++) {
+       for (i = 0; stub_func_addr(stubs[i].funcdata); i++) {
                BUG_ON(i >= num_stubs);
 
-               if (stubs[i].opd.funcaddr == opd->funcaddr)
+               if (stub_func_addr(stubs[i].funcdata) == func_addr(addr))
                        return (unsigned long)&stubs[i];
        }
 
-       if (!create_stub(sechdrs, &stubs[i], opd, me))
+       if (!create_stub(sechdrs, &stubs[i], addr, me))
                return 0;
 
        return (unsigned long)&stubs[i];
@@ -337,7 +472,8 @@ static int restore_r2(u32 *instruction, struct module *me)
                       me->name, *instruction);
                return 0;
        }
-       *instruction = 0xe8410028;      /* ld r2,40(r1) */
+       /* ld r2,R2_STACK_OFFSET(r1) */
+       *instruction = 0xe8410000 | R2_STACK_OFFSET;
        return 1;
 }
 
@@ -462,7 +598,8 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
                                        return -ENOENT;
                                if (!restore_r2((u32 *)location + 1, me))
                                        return -ENOEXEC;
-                       }
+                       } else
+                               value += local_entry_offset(sym);
 
                        /* Convert value to relative */
                        value -= (unsigned long)location;
@@ -491,6 +628,23 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
                         */
                        break;
 
+               case R_PPC64_REL16_HA:
+                       /* Subtract location pointer */
+                       value -= (unsigned long)location;
+                       value = ((value + 0x8000) >> 16);
+                       *((uint16_t *) location)
+                               = (*((uint16_t *) location) & ~0xffff)
+                               | (value & 0xffff);
+                       break;
+
+               case R_PPC64_REL16_LO:
+                       /* Subtract location pointer */
+                       value -= (unsigned long)location;
+                       *((uint16_t *) location)
+                               = (*((uint16_t *) location) & ~0xffff)
+                               | (value & 0xffff);
+                       break;
+
                default:
                        printk("%s: Unknown ADD relocation: %lu\n",
                               me->name,