]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - arch/powerpc/kernel/module_64.c
powerpc/module: Fix stubs for BE
[mirror_ubuntu-zesty-kernel.git] / arch / powerpc / kernel / module_64.c
index d7222495e24c68439e19e088eb59048d72ab9e46..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>
 
 #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
    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;
 };
 
 /*
@@ -72,8 +125,8 @@ struct ppc64_stub_entry
  * 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 = {
+
+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. */
@@ -81,11 +134,78 @@ static struct ppc64_stub_entry ppc64_stub =
        0xe98b0020,                     /* ld      r12,32(r11) */
 #if !defined(_CALL_ELF) || _CALL_ELF != 2
        /* Set up new r2 from function descriptor */
-       0xe84b0026,                     /* ld      r2,40(r11) */
+       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) */
@@ -225,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;
@@ -295,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);
@@ -313,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];
@@ -480,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;