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 <matt.redfearn@xxxxxxxxxx> --- arch/mips/kernel/Makefile | 2 + arch/mips/kernel/relocate.c | 232 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 arch/mips/kernel/relocate.c diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile index d982be1ea1c3..694d54b1e7bf 100644 --- a/arch/mips/kernel/Makefile +++ b/arch/mips/kernel/Makefile @@ -83,6 +83,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 000000000000..3cb97ab25a5f --- /dev/null +++ b/arch/mips/kernel/relocate.c @@ -0,0 +1,232 @@ +/* + * 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.redfearn@xxxxxxxxxx) + */ +#include <asm/cacheflush.h> +#include <asm/sections.h> +#include <asm/setup.h> +#include <asm/timex.h> +#include <linux/elf.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/start_kernel.h> +#include <linux/string.h> + +extern long _relocation_start; /* End kernel image / start relocation table */ +extern long _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__ __volatile__("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) & 0x03ffffff; + + 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 & ~0x03ffffff; + + /* Get the new target address */ + target_addr = (long)target_addr + offset; + + if ((target_addr & 0xf0000000) != ((unsigned long)loc_new & 0xf0000000)) { + pr_err("R_MIPS_26 REL relocation overflow\n"); + return -ENOEXEC; + } + + target_addr -= (unsigned long)loc_new & ~0x03ffffff; + target_addr >>= 2; + + *loc_new = (*loc_new & ~0x03ffffff) | (target_addr & 0x03ffffff); + + 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 & 0xffff) << 16; /* high 16bits of target */ + + target += offset; + + *loc_new = (insn & ~0xffff) | ((target >> 16) & 0xffff); + 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 = (u32 *)&_relocation_start; r < (u32 *)&_relocation_end; r++) { + /* Sentinel for last relocation */ + if (*r == 0) + break; + + type = (*r >> 24) & 0xff; + loc_orig = (void *)(kbase_old + ((*r & 0x00ffffff) << 2)); + loc_new = (void *)((unsigned long)loc_orig + offset); + + if (reloc_handlers_rel[type] == NULL) + /* Unsupported relocation */ + return -ENOEXEC; + + res = reloc_handlers_rel[type](loc_orig, loc_new, offset); + if (res) + return res; + } + + return 0; +} + +/* + * The exception table is filled in by a tool after vmlinux is linked. + * It must be relocated separately since there will not be any relocation + * information for it filled in by the linker. + */ +static int __init relocate_exception_table(long offset) +{ + unsigned long *etable_start, *etable_end, *e; + + etable_start = (void *)((unsigned long)&__start___ex_table + offset); + etable_end = (void *)((unsigned long)&__stop___ex_table + offset); + + for (e = etable_start; e < etable_end; e++) + *e += offset; + + return 0; +} + +static inline void __init *determine_relocation_address(void) +{ + /* + * Choose a new address for the kernel + * For now we'll hard code the destination + */ + return (void *)0xffffffff81000000; +} + +static inline int __init relocation_addr_valid(void *loc_new) +{ + if ((unsigned long)loc_new & 0x0000ffff) + return 0; /* Inappropriately aligned new location */ + if ((unsigned long)loc_new < (unsigned long)&_end) + return 0; /* New location overlaps original kernel */ + return 1; +} + +void __init *relocate_kernel(void) +{ + void *loc_new; + unsigned long kernel_length; + long offset = 0; + int res = 1; + + kernel_length = (long)(&_relocation_start) - (long)(&_text); + loc_new = determine_relocation_address(); + + /* Sanity check relocation address */ + if (relocation_addr_valid(loc_new)) + offset = (unsigned long)loc_new - (unsigned long)(&_text); + + if (offset) { + /* Copy the kernel to it's new location */ + memcpy(loc_new, &_text, kernel_length); + + /* Perform relocations on the new kernel */ + res = do_relocations(&_text, loc_new, offset); + + if (res == 0) { + /* Sync the caches ready for execution of new kernel */ + sync_icache(loc_new, kernel_length); + + res = relocate_exception_table(offset); + } + } + + if (res == 0) { + void *bss_new = (void *)((long)&__bss_start + offset); + long bss_length = (long)&__bss_stop - (long)&__bss_start; + /* + * The original .bss has already been cleared, and + * some variables such as command line parameters + * stored to it so make a copy in the new location. + */ + memcpy(bss_new, &__bss_start, bss_length); + + /* The current thread is now within the relocated image */ + __current_thread_info = (void *)((long)&init_thread_union + offset); + + /* Return the new kernel's entry point */ + return (void *)((long)start_kernel + offset); + } else { + /* + * Something went wrong in the relocation process + * Just boot the original kernel + */ + return start_kernel; + } +} -- 2.1.4