[PATCH v2 05/11] MIPS: Kernel: Add relocate.c

2016-03-31 Thread Matt Redfearn
arch/mips/kernel/relocate.c contains the functions necessary to relocate
the kernel elsewhere in memory

The kernel makes a copy of itself at the new address. It uses the
relocation table inserted by the relocs tool to fix symbol references
within the new image.

If copy/relocation is sucessful then the entry point of the new kernel
is returned, otherwise fall back to starting the kernel in place.

Signed-off-by: Matt Redfearn 
---

Changes in v2: None

 arch/mips/kernel/Makefile   |   2 +
 arch/mips/kernel/relocate.c | 240 
 2 files changed, 242 insertions(+)
 create mode 100644 arch/mips/kernel/relocate.c

diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 68e2b7db9348..debffb89d51a 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -84,6 +84,8 @@ obj-$(CONFIG_I8253)   += i8253.o
 
 obj-$(CONFIG_GPIO_TXX9)+= gpio_txx9.o
 
+obj-$(CONFIG_RELOCATABLE)  += relocate.o
+
 obj-$(CONFIG_KEXEC)+= machine_kexec.o relocate_kernel.o crash.o
 obj-$(CONFIG_CRASH_DUMP)   += crash_dump.o
 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
new file mode 100644
index ..742cc7a50dad
--- /dev/null
+++ b/arch/mips/kernel/relocate.c
@@ -0,0 +1,240 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Support for Kernel relocation at boot time
+ *
+ * Copyright (C) 2015, Imagination Technologies Ltd.
+ * Authors: Matt Redfearn (matt.redfe...@imgtec.com)
+ */
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define RELOCATED(x) ((void *)((long)x + offset))
+
+extern u32 _relocation_start[];/* End kernel image / start relocation 
table */
+extern u32 _relocation_end[];  /* End relocation table */
+
+extern long __start___ex_table;/* Start exception table */
+extern long __stop___ex_table; /* End exception table */
+
+static inline u32 __init get_synci_step(void)
+{
+   u32 res;
+
+   __asm__("rdhwr  %0, $1" : "=r" (res));
+
+   return res;
+}
+
+static void __init sync_icache(void *kbase, unsigned long kernel_length)
+{
+   void *kend = kbase + kernel_length;
+   u32 step = get_synci_step();
+
+   do {
+   __asm__ __volatile__(
+   "synci  0(%0)"
+   : /* no output */
+   : "r" (kbase));
+
+   kbase += step;
+   } while (kbase < kend);
+
+   /* Completion barrier */
+   __sync();
+}
+
+static int __init apply_r_mips_64_rel(u32 *loc_orig, u32 *loc_new, long offset)
+{
+   *(u64 *)loc_new += offset;
+
+   return 0;
+}
+
+static int __init apply_r_mips_32_rel(u32 *loc_orig, u32 *loc_new, long offset)
+{
+   *loc_new += offset;
+
+   return 0;
+}
+
+static int __init apply_r_mips_26_rel(u32 *loc_orig, u32 *loc_new, long offset)
+{
+   unsigned long target_addr = (*loc_orig) & 0x03ff;
+
+   if (offset % 4) {
+   pr_err("Dangerous R_MIPS_26 REL relocation\n");
+   return -ENOEXEC;
+   }
+
+   /* Original target address */
+   target_addr <<= 2;
+   target_addr += (unsigned long)loc_orig & ~0x03ff;
+
+   /* Get the new target address */
+   target_addr += offset;
+
+   if ((target_addr & 0xf000) != ((unsigned long)loc_new & 
0xf000)) {
+   pr_err("R_MIPS_26 REL relocation overflow\n");
+   return -ENOEXEC;
+   }
+
+   target_addr -= (unsigned long)loc_new & ~0x03ff;
+   target_addr >>= 2;
+
+   *loc_new = (*loc_new & ~0x03ff) | (target_addr & 0x03ff);
+
+   return 0;
+}
+
+
+static int __init apply_r_mips_hi16_rel(u32 *loc_orig, u32 *loc_new, long 
offset)
+{
+   unsigned long insn = *loc_orig;
+   unsigned long target = (insn & 0x) << 16; /* high 16bits of target 
*/
+
+   target += offset;
+
+   *loc_new = (insn & ~0x) | ((target >> 16) & 0x);
+   return 0;
+}
+
+static int (*reloc_handlers_rel[]) (u32 *, u32 *, long) __initdata = {
+   [R_MIPS_64] = apply_r_mips_64_rel,
+   [R_MIPS_32] = apply_r_mips_32_rel,
+   [R_MIPS_26] = apply_r_mips_26_rel,
+   [R_MIPS_HI16]   = apply_r_mips_hi16_rel,
+};
+
+int __init do_relocations(void *kbase_old, void *kbase_new, long offset)
+{
+   u32 *r;
+   u32 *loc_orig;
+   u32 *loc_new;
+   int type;
+   int res;
+
+   for (r = _relocation_start; r < _relocation_end; r++) {
+   /* Sentinel for last relocation */
+   if (*r == 0)
+   break;
+
+   type = (*r >> 24) & 0xff;
+   loc_orig = (void *)(kbase_old + ((*r & 

[PATCH v2 05/11] MIPS: Kernel: Add relocate.c

2016-03-31 Thread Matt Redfearn
arch/mips/kernel/relocate.c contains the functions necessary to relocate
the kernel elsewhere in memory

The kernel makes a copy of itself at the new address. It uses the
relocation table inserted by the relocs tool to fix symbol references
within the new image.

If copy/relocation is sucessful then the entry point of the new kernel
is returned, otherwise fall back to starting the kernel in place.

Signed-off-by: Matt Redfearn 
---

Changes in v2: None

 arch/mips/kernel/Makefile   |   2 +
 arch/mips/kernel/relocate.c | 240 
 2 files changed, 242 insertions(+)
 create mode 100644 arch/mips/kernel/relocate.c

diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 68e2b7db9348..debffb89d51a 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -84,6 +84,8 @@ obj-$(CONFIG_I8253)   += i8253.o
 
 obj-$(CONFIG_GPIO_TXX9)+= gpio_txx9.o
 
+obj-$(CONFIG_RELOCATABLE)  += relocate.o
+
 obj-$(CONFIG_KEXEC)+= machine_kexec.o relocate_kernel.o crash.o
 obj-$(CONFIG_CRASH_DUMP)   += crash_dump.o
 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c
new file mode 100644
index ..742cc7a50dad
--- /dev/null
+++ b/arch/mips/kernel/relocate.c
@@ -0,0 +1,240 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Support for Kernel relocation at boot time
+ *
+ * Copyright (C) 2015, Imagination Technologies Ltd.
+ * Authors: Matt Redfearn (matt.redfe...@imgtec.com)
+ */
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define RELOCATED(x) ((void *)((long)x + offset))
+
+extern u32 _relocation_start[];/* End kernel image / start relocation 
table */
+extern u32 _relocation_end[];  /* End relocation table */
+
+extern long __start___ex_table;/* Start exception table */
+extern long __stop___ex_table; /* End exception table */
+
+static inline u32 __init get_synci_step(void)
+{
+   u32 res;
+
+   __asm__("rdhwr  %0, $1" : "=r" (res));
+
+   return res;
+}
+
+static void __init sync_icache(void *kbase, unsigned long kernel_length)
+{
+   void *kend = kbase + kernel_length;
+   u32 step = get_synci_step();
+
+   do {
+   __asm__ __volatile__(
+   "synci  0(%0)"
+   : /* no output */
+   : "r" (kbase));
+
+   kbase += step;
+   } while (kbase < kend);
+
+   /* Completion barrier */
+   __sync();
+}
+
+static int __init apply_r_mips_64_rel(u32 *loc_orig, u32 *loc_new, long offset)
+{
+   *(u64 *)loc_new += offset;
+
+   return 0;
+}
+
+static int __init apply_r_mips_32_rel(u32 *loc_orig, u32 *loc_new, long offset)
+{
+   *loc_new += offset;
+
+   return 0;
+}
+
+static int __init apply_r_mips_26_rel(u32 *loc_orig, u32 *loc_new, long offset)
+{
+   unsigned long target_addr = (*loc_orig) & 0x03ff;
+
+   if (offset % 4) {
+   pr_err("Dangerous R_MIPS_26 REL relocation\n");
+   return -ENOEXEC;
+   }
+
+   /* Original target address */
+   target_addr <<= 2;
+   target_addr += (unsigned long)loc_orig & ~0x03ff;
+
+   /* Get the new target address */
+   target_addr += offset;
+
+   if ((target_addr & 0xf000) != ((unsigned long)loc_new & 
0xf000)) {
+   pr_err("R_MIPS_26 REL relocation overflow\n");
+   return -ENOEXEC;
+   }
+
+   target_addr -= (unsigned long)loc_new & ~0x03ff;
+   target_addr >>= 2;
+
+   *loc_new = (*loc_new & ~0x03ff) | (target_addr & 0x03ff);
+
+   return 0;
+}
+
+
+static int __init apply_r_mips_hi16_rel(u32 *loc_orig, u32 *loc_new, long 
offset)
+{
+   unsigned long insn = *loc_orig;
+   unsigned long target = (insn & 0x) << 16; /* high 16bits of target 
*/
+
+   target += offset;
+
+   *loc_new = (insn & ~0x) | ((target >> 16) & 0x);
+   return 0;
+}
+
+static int (*reloc_handlers_rel[]) (u32 *, u32 *, long) __initdata = {
+   [R_MIPS_64] = apply_r_mips_64_rel,
+   [R_MIPS_32] = apply_r_mips_32_rel,
+   [R_MIPS_26] = apply_r_mips_26_rel,
+   [R_MIPS_HI16]   = apply_r_mips_hi16_rel,
+};
+
+int __init do_relocations(void *kbase_old, void *kbase_new, long offset)
+{
+   u32 *r;
+   u32 *loc_orig;
+   u32 *loc_new;
+   int type;
+   int res;
+
+   for (r = _relocation_start; r < _relocation_end; r++) {
+   /* Sentinel for last relocation */
+   if (*r == 0)
+   break;
+
+   type = (*r >> 24) & 0xff;
+   loc_orig = (void *)(kbase_old + ((*r & 0x00ff) << 2));
+