Merge branch 'for-2.6.25' of git://git.kernel.org/pub/scm/linux/kernel/git/paulus...
[sfrench/cifs-2.6.git] / arch / powerpc / kernel / module_64.c
index ba34001fca8e8420b6c1aab0df0f9b0a43de3bda..3a82b02b784b0c8a4d8da7467b8c7d4410cf9ea4 100644 (file)
 #include <linux/moduleloader.h>
 #include <linux/err.h>
 #include <linux/vmalloc.h>
+#include <linux/bug.h>
 #include <asm/module.h>
 #include <asm/uaccess.h>
+#include <asm/firmware.h>
+#include <linux/sort.h>
+
+#include "setup.h"
 
 /* FIXME: We don't do .init separately.  To do this, we'd need to have
    a separate r2 value in the init and core section, and stub between
@@ -77,25 +82,23 @@ static struct ppc64_stub_entry ppc64_stub =
    different addend) */
 static unsigned int count_relocs(const Elf64_Rela *rela, unsigned int num)
 {
-       unsigned int i, j, ret = 0;
+       unsigned int i, r_info, r_addend, _count_relocs;
 
        /* FIXME: Only count external ones --RR */
-       /* Sure, this is order(n^2), but it's usually short, and not
-           time critical */
-       for (i = 0; i < num; i++) {
+       _count_relocs = 0;
+       r_info = 0;
+       r_addend = 0;
+       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 (ELF64_R_TYPE(rela[i].r_info) == R_PPC_REL24 &&
+                   (r_info != ELF64_R_SYM(rela[i].r_info) ||
+                    r_addend != rela[i].r_addend)) {
+                       _count_relocs++;
+                       r_info = ELF64_R_SYM(rela[i].r_info);
+                       r_addend = rela[i].r_addend;
                }
-               if (j == i) ret++;
-       }
-       return ret;
+
+       return _count_relocs;
 }
 
 void *module_alloc(unsigned long size)
@@ -114,6 +117,44 @@ void module_free(struct module *mod, void *module_region)
            table entries. */
 }
 
+static int relacmp(const void *_x, const void *_y)
+{
+       const Elf64_Rela *x, *y;
+
+       y = (Elf64_Rela *)_x;
+       x = (Elf64_Rela *)_y;
+
+       /* Compare the entire r_info (as opposed to ELF64_R_SYM(r_info) only) to
+        * make the comparison cheaper/faster. It won't affect the sorting or
+        * the counting algorithms' performance
+        */
+       if (x->r_info < y->r_info)
+               return -1;
+       else if (x->r_info > y->r_info)
+               return 1;
+       else if (x->r_addend < y->r_addend)
+               return -1;
+       else if (x->r_addend > y->r_addend)
+               return 1;
+       else
+               return 0;
+}
+
+static void relaswap(void *_x, void *_y, int size)
+{
+       uint64_t *x, *y, tmp;
+       int i;
+
+       y = (uint64_t *)_x;
+       x = (uint64_t *)_y;
+
+       for (i = 0; i < sizeof(Elf64_Rela) / sizeof(uint64_t); i++) {
+               tmp = x[i];
+               x[i] = y[i];
+               y[i] = tmp;
+       }
+}
+
 /* Get size of potential trampolines required. */
 static unsigned long get_stubs_size(const Elf64_Ehdr *hdr,
                                    const Elf64_Shdr *sechdrs)
@@ -129,6 +170,16 @@ static unsigned long get_stubs_size(const Elf64_Ehdr *hdr,
                        DEBUGP("Ptr: %p.  Number: %lu\n",
                               (void *)sechdrs[i].sh_addr,
                               sechdrs[i].sh_size / sizeof(Elf64_Rela));
+
+                       /* Sort the relocation information based on a symbol and
+                        * addend key. This is a stable O(n*log n) complexity
+                        * alogrithm but it will reduce the complexity of
+                        * count_relocs() to linear complexity O(n)
+                        */
+                       sort((void *)sechdrs[i].sh_addr,
+                            sechdrs[i].sh_size / sizeof(Elf64_Rela),
+                            sizeof(Elf64_Rela), relacmp, relaswap);
+
                        relocs += count_relocs((void *)sechdrs[i].sh_addr,
                                               sechdrs[i].sh_size
                                               / sizeof(Elf64_Rela));
@@ -339,7 +390,7 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
                        /* Simply set it */
                        *(u32 *)location = value;
                        break;
-                       
+
                case R_PPC64_ADDR64:
                        /* Simply set it */
                        *(unsigned long *)location = value;
@@ -395,11 +446,16 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
                        }
 
                        /* Only replace bits 2 through 26 */
-                       *(uint32_t *)location 
+                       *(uint32_t *)location
                                = (*(uint32_t *)location & ~0x03fffffc)
                                | (value & 0x03fffffc);
                        break;
 
+               case R_PPC64_REL64:
+                       /* 64 bits relative (used by features fixups) */
+                       *location = value - (unsigned long)location;
+                       break;
+
                default:
                        printk("%s: Unknown ADD relocation: %lu\n",
                               me->name,
@@ -413,38 +469,49 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
 
 LIST_HEAD(module_bug_list);
 
-int module_finalize(const Elf_Ehdr *hdr,
-               const Elf_Shdr *sechdrs, struct module *me)
+static const Elf_Shdr *find_section(const Elf_Ehdr *hdr,
+                                   const Elf_Shdr *sechdrs,
+                                   const char *name)
 {
        char *secstrings;
        unsigned int i;
 
-       me->arch.bug_table = NULL;
-       me->arch.num_bugs = 0;
-
-       /* Find the __bug_table section, if present */
        secstrings = (char *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
-       for (i = 1; i < hdr->e_shnum; i++) {
-               if (strcmp(secstrings+sechdrs[i].sh_name, "__bug_table"))
-                       continue;
-               me->arch.bug_table = (void *) sechdrs[i].sh_addr;
-               me->arch.num_bugs = sechdrs[i].sh_size / sizeof(struct bug_entry);
-               break;
-       }
+       for (i = 1; i < hdr->e_shnum; i++)
+               if (strcmp(secstrings+sechdrs[i].sh_name, name) == 0)
+                       return &sechdrs[i];
+       return NULL;
+}
 
-       /*
-        * Strictly speaking this should have a spinlock to protect against
-        * traversals, but since we only traverse on BUG()s, a spinlock
-        * could potentially lead to deadlock and thus be counter-productive.
-        */
-       list_add(&me->arch.bug_list, &module_bug_list);
+int module_finalize(const Elf_Ehdr *hdr,
+               const Elf_Shdr *sechdrs, struct module *me)
+{
+       const Elf_Shdr *sect;
+       int err;
+
+       err = module_bug_finalize(hdr, sechdrs, me);
+       if (err)
+               return err;
+
+       /* Apply feature fixups */
+       sect = find_section(hdr, sechdrs, "__ftr_fixup");
+       if (sect != NULL)
+               do_feature_fixups(cur_cpu_spec->cpu_features,
+                                 (void *)sect->sh_addr,
+                                 (void *)sect->sh_addr + sect->sh_size);
+
+       sect = find_section(hdr, sechdrs, "__fw_ftr_fixup");
+       if (sect != NULL)
+               do_feature_fixups(powerpc_firmware_features,
+                                 (void *)sect->sh_addr,
+                                 (void *)sect->sh_addr + sect->sh_size);
 
        return 0;
 }
 
 void module_arch_cleanup(struct module *mod)
 {
-       list_del(&mod->arch.bug_list);
+       module_bug_cleanup(mod);
 }
 
 struct bug_entry *module_find_bug(unsigned long bugaddr)