x86, relocs: Move ELF relocation handling to C
[platform/adaptation/renesas_rcar/renesas_kernel.git] / arch / x86 / boot / compressed / misc.c
index 0319c88..434f077 100644 (file)
@@ -271,6 +271,79 @@ static void error(char *x)
                asm("hlt");
 }
 
+#if CONFIG_X86_NEED_RELOCS
+static void handle_relocations(void *output, unsigned long output_len)
+{
+       int *reloc;
+       unsigned long delta, map, ptr;
+       unsigned long min_addr = (unsigned long)output;
+       unsigned long max_addr = min_addr + output_len;
+
+       /*
+        * Calculate the delta between where vmlinux was linked to load
+        * and where it was actually loaded.
+        */
+       delta = min_addr - LOAD_PHYSICAL_ADDR;
+       if (!delta) {
+               debug_putstr("No relocation needed... ");
+               return;
+       }
+       debug_putstr("Performing relocations... ");
+
+       /*
+        * The kernel contains a table of relocation addresses. Those
+        * addresses have the final load address of the kernel in virtual
+        * memory. We are currently working in the self map. So we need to
+        * create an adjustment for kernel memory addresses to the self map.
+        * This will involve subtracting out the base address of the kernel.
+        */
+       map = delta - __START_KERNEL_map;
+
+       /*
+        * Process relocations: 32 bit relocations first then 64 bit after.
+        * Two sets of binary relocations are added to the end of the kernel
+        * before compression. Each relocation table entry is the kernel
+        * address of the location which needs to be updated stored as a
+        * 32-bit value which is sign extended to 64 bits.
+        *
+        * Format is:
+        *
+        * kernel bits...
+        * 0 - zero terminator for 64 bit relocations
+        * 64 bit relocation repeated
+        * 0 - zero terminator for 32 bit relocations
+        * 32 bit relocation repeated
+        *
+        * So we work backwards from the end of the decompressed image.
+        */
+       for (reloc = output + output_len - sizeof(*reloc); *reloc; reloc--) {
+               int extended = *reloc;
+               extended += map;
+
+               ptr = (unsigned long)extended;
+               if (ptr < min_addr || ptr > max_addr)
+                       error("32-bit relocation outside of kernel!\n");
+
+               *(uint32_t *)ptr += delta;
+       }
+#ifdef CONFIG_X86_64
+       for (reloc--; *reloc; reloc--) {
+               long extended = *reloc;
+               extended += map;
+
+               ptr = (unsigned long)extended;
+               if (ptr < min_addr || ptr > max_addr)
+                       error("64-bit relocation outside of kernel!\n");
+
+               *(uint64_t *)ptr += delta;
+       }
+#endif
+}
+#else
+static inline void handle_relocations(void *output, unsigned long output_len)
+{ }
+#endif
+
 static void parse_elf(void *output)
 {
 #ifdef CONFIG_X86_64
@@ -325,7 +398,8 @@ static void parse_elf(void *output)
 asmlinkage void decompress_kernel(void *rmode, memptr heap,
                                  unsigned char *input_data,
                                  unsigned long input_len,
-                                 unsigned char *output)
+                                 unsigned char *output,
+                                 unsigned long output_len)
 {
        real_mode = rmode;
 
@@ -365,6 +439,7 @@ asmlinkage void decompress_kernel(void *rmode, memptr heap,
        debug_putstr("\nDecompressing Linux... ");
        decompress(input_data, input_len, NULL, NULL, output, NULL, error);
        parse_elf(output);
+       handle_relocations(output, output_len);
        debug_putstr("done.\nBooting the kernel.\n");
        return;
 }