1 /* 2 * This file is subject to the terms and conditions of the GNU General Public 3 * License. See the file "COPYING" in the main directory of this archive 4 * for more details. 5 * 6 * Support for Kernel relocation at boot time 7 * 8 * Copyright (C) 2015, Imagination Technologies Ltd. 9 * Authors: Matt Redfearn ([email protected]) 10 */ 11 #include <asm/cacheflush.h> 12 #include <asm/sections.h> 13 #include <asm/setup.h> 14 #include <asm/timex.h> 15 #include <linux/elf.h> 16 #include <linux/kernel.h> 17 #include <linux/sched.h> 18 #include <linux/start_kernel.h> 19 #include <linux/string.h> 20 21 #define RELOCATED(x) ((void *)((long)x + offset)) 22 23 extern u32 _relocation_start[]; /* End kernel image / start relocation table */ 24 extern u32 _relocation_end[]; /* End relocation table */ 25 26 extern long __start___ex_table; /* Start exception table */ 27 extern long __stop___ex_table; /* End exception table */ 28 29 static inline u32 __init get_synci_step(void) 30 { 31 u32 res; 32 33 __asm__("rdhwr %0, $1" : "=r" (res)); 34 35 return res; 36 } 37 38 static void __init sync_icache(void *kbase, unsigned long kernel_length) 39 { 40 void *kend = kbase + kernel_length; 41 u32 step = get_synci_step(); 42 43 do { 44 __asm__ __volatile__( 45 "synci 0(%0)" 46 : /* no output */ 47 : "r" (kbase)); 48 49 kbase += step; 50 } while (kbase < kend); 51 52 /* Completion barrier */ 53 __sync(); 54 } 55 56 static int __init apply_r_mips_64_rel(u32 *loc_orig, u32 *loc_new, long offset) 57 { 58 *(u64 *)loc_new += offset; 59 60 return 0; 61 } 62 63 static int __init apply_r_mips_32_rel(u32 *loc_orig, u32 *loc_new, long offset) 64 { 65 *loc_new += offset; 66 67 return 0; 68 } 69 70 static int __init apply_r_mips_26_rel(u32 *loc_orig, u32 *loc_new, long offset) 71 { 72 unsigned long target_addr = (*loc_orig) & 0x03ffffff; 73 74 if (offset % 4) { 75 pr_err("Dangerous R_MIPS_26 REL relocation\n"); 76 return -ENOEXEC; 77 } 78 79 /* Original target address */ 80 target_addr <<= 2; 81 target_addr += (unsigned long)loc_orig & ~0x03ffffff; 82 83 /* Get the new target address */ 84 target_addr += offset; 85 86 if ((target_addr & 0xf0000000) != ((unsigned long)loc_new & 0xf0000000)) { 87 pr_err("R_MIPS_26 REL relocation overflow\n"); 88 return -ENOEXEC; 89 } 90 91 target_addr -= (unsigned long)loc_new & ~0x03ffffff; 92 target_addr >>= 2; 93 94 *loc_new = (*loc_new & ~0x03ffffff) | (target_addr & 0x03ffffff); 95 96 return 0; 97 } 98 99 100 static int __init apply_r_mips_hi16_rel(u32 *loc_orig, u32 *loc_new, long offset) 101 { 102 unsigned long insn = *loc_orig; 103 unsigned long target = (insn & 0xffff) << 16; /* high 16bits of target */ 104 105 target += offset; 106 107 *loc_new = (insn & ~0xffff) | ((target >> 16) & 0xffff); 108 return 0; 109 } 110 111 static int (*reloc_handlers_rel[]) (u32 *, u32 *, long) __initdata = { 112 [R_MIPS_64] = apply_r_mips_64_rel, 113 [R_MIPS_32] = apply_r_mips_32_rel, 114 [R_MIPS_26] = apply_r_mips_26_rel, 115 [R_MIPS_HI16] = apply_r_mips_hi16_rel, 116 }; 117 118 int __init do_relocations(void *kbase_old, void *kbase_new, long offset) 119 { 120 u32 *r; 121 u32 *loc_orig; 122 u32 *loc_new; 123 int type; 124 int res; 125 126 for (r = _relocation_start; r < _relocation_end; r++) { 127 /* Sentinel for last relocation */ 128 if (*r == 0) 129 break; 130 131 type = (*r >> 24) & 0xff; 132 loc_orig = (void *)(kbase_old + ((*r & 0x00ffffff) << 2)); 133 loc_new = RELOCATED(loc_orig); 134 135 if (reloc_handlers_rel[type] == NULL) { 136 /* Unsupported relocation */ 137 pr_err("Unhandled relocation type %d at 0x%pK\n", 138 type, loc_orig); 139 return -ENOEXEC; 140 } 141 142 res = reloc_handlers_rel[type](loc_orig, loc_new, offset); 143 if (res) 144 return res; 145 } 146 147 return 0; 148 } 149 150 /* 151 * The exception table is filled in by the relocs tool after vmlinux is linked. 152 * It must be relocated separately since there will not be any relocation 153 * information for it filled in by the linker. 154 */ 155 static int __init relocate_exception_table(long offset) 156 { 157 unsigned long *etable_start, *etable_end, *e; 158 159 etable_start = RELOCATED(&__start___ex_table); 160 etable_end = RELOCATED(&__stop___ex_table); 161 162 for (e = etable_start; e < etable_end; e++) 163 *e += offset; 164 165 return 0; 166 } 167 168 static inline void __init *determine_relocation_address(void) 169 { 170 /* 171 * Choose a new address for the kernel 172 * For now we'll hard code the destination 173 */ 174 return (void *)0xffffffff81000000; 175 } 176 177 static inline int __init relocation_addr_valid(void *loc_new) 178 { 179 if ((unsigned long)loc_new & 0x0000ffff) { 180 /* Inappropriately aligned new location */ 181 return 0; 182 } 183 if ((unsigned long)loc_new < (unsigned long)&_end) { 184 /* New location overlaps original kernel */ 185 return 0; 186 } 187 return 1; 188 } 189 190 void *__init relocate_kernel(void) 191 { 192 void *loc_new; 193 unsigned long kernel_length; 194 unsigned long bss_length; 195 long offset = 0; 196 int res = 1; 197 /* Default to original kernel entry point */ 198 void *kernel_entry = start_kernel; 199 200 kernel_length = (long)(&_relocation_start) - (long)(&_text); 201 bss_length = (long)&__bss_stop - (long)&__bss_start; 202 203 loc_new = determine_relocation_address(); 204 205 /* Sanity check relocation address */ 206 if (relocation_addr_valid(loc_new)) 207 offset = (unsigned long)loc_new - (unsigned long)(&_text); 208 209 if (offset) { 210 /* Copy the kernel to it's new location */ 211 memcpy(loc_new, &_text, kernel_length); 212 213 /* Perform relocations on the new kernel */ 214 res = do_relocations(&_text, loc_new, offset); 215 if (res < 0) 216 goto out; 217 218 /* Sync the caches ready for execution of new kernel */ 219 sync_icache(loc_new, kernel_length); 220 221 res = relocate_exception_table(offset); 222 if (res < 0) 223 goto out; 224 225 /* 226 * The original .bss has already been cleared, and 227 * some variables such as command line parameters 228 * stored to it so make a copy in the new location. 229 */ 230 memcpy(RELOCATED(&__bss_start), &__bss_start, bss_length); 231 232 /* The current thread is now within the relocated image */ 233 __current_thread_info = RELOCATED(&init_thread_union); 234 235 /* Return the new kernel's entry point */ 236 kernel_entry = RELOCATED(start_kernel); 237 } 238 out: 239 return kernel_entry; 240 } 241