David S. Miller | 64d329e | 2007-10-27 00:17:01 -0700 | [diff] [blame] | 1 | /* btfixup.c: Boot time code fixup and relocator, so that |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 2 | * we can get rid of most indirect calls to achieve single |
| 3 | * image sun4c and srmmu kernel. |
| 4 | * |
| 5 | * Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) |
| 6 | */ |
| 7 | |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 8 | #include <linux/kernel.h> |
| 9 | #include <linux/init.h> |
| 10 | #include <asm/btfixup.h> |
| 11 | #include <asm/page.h> |
| 12 | #include <asm/pgalloc.h> |
| 13 | #include <asm/pgtable.h> |
| 14 | #include <asm/oplib.h> |
| 15 | #include <asm/system.h> |
| 16 | #include <asm/cacheflush.h> |
| 17 | |
| 18 | #define BTFIXUP_OPTIMIZE_NOP |
| 19 | #define BTFIXUP_OPTIMIZE_OTHER |
| 20 | |
| 21 | extern char *srmmu_name; |
| 22 | static char version[] __initdata = "Boot time fixup v1.6. 4/Mar/98 Jakub Jelinek (jj@ultra.linux.cz). Patching kernel for "; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 23 | static char str_sun4c[] __initdata = "sun4c\n"; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 24 | static char str_srmmu[] __initdata = "srmmu[%s]/"; |
| 25 | static char str_iommu[] __initdata = "iommu\n"; |
| 26 | static char str_iounit[] __initdata = "io-unit\n"; |
| 27 | |
| 28 | static int visited __initdata = 0; |
| 29 | extern unsigned int ___btfixup_start[], ___btfixup_end[], __init_begin[], __init_end[], __init_text_end[]; |
| 30 | extern unsigned int _stext[], _end[], __start___ksymtab[], __stop___ksymtab[]; |
| 31 | static char wrong_f[] __initdata = "Trying to set f fixup %p to invalid function %08x\n"; |
| 32 | static char wrong_b[] __initdata = "Trying to set b fixup %p to invalid function %08x\n"; |
| 33 | static char wrong_s[] __initdata = "Trying to set s fixup %p to invalid value %08x\n"; |
| 34 | static char wrong_h[] __initdata = "Trying to set h fixup %p to invalid value %08x\n"; |
| 35 | static char wrong_a[] __initdata = "Trying to set a fixup %p to invalid value %08x\n"; |
| 36 | static char wrong[] __initdata = "Wrong address for %c fixup %p\n"; |
| 37 | static char insn_f[] __initdata = "Fixup f %p refers to weird instructions at %p[%08x,%08x]\n"; |
| 38 | static char insn_b[] __initdata = "Fixup b %p doesn't refer to a SETHI at %p[%08x]\n"; |
| 39 | static char insn_s[] __initdata = "Fixup s %p doesn't refer to an OR at %p[%08x]\n"; |
| 40 | static char insn_h[] __initdata = "Fixup h %p doesn't refer to a SETHI at %p[%08x]\n"; |
| 41 | static char insn_a[] __initdata = "Fixup a %p doesn't refer to a SETHI nor OR at %p[%08x]\n"; |
| 42 | static char insn_i[] __initdata = "Fixup i %p doesn't refer to a valid instruction at %p[%08x]\n"; |
| 43 | static char fca_und[] __initdata = "flush_cache_all undefined in btfixup()\n"; |
| 44 | static char wrong_setaddr[] __initdata = "Garbled CALL/INT patch at %p[%08x,%08x,%08x]=%08x\n"; |
| 45 | |
| 46 | #ifdef BTFIXUP_OPTIMIZE_OTHER |
| 47 | static void __init set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value) |
| 48 | { |
| 49 | if (!fmangled) |
| 50 | *addr = value; |
| 51 | else { |
| 52 | unsigned int *q = (unsigned int *)q1; |
| 53 | if (*addr == 0x01000000) { |
| 54 | /* Noped */ |
| 55 | *q = value; |
| 56 | } else if (addr[-1] == *q) { |
| 57 | /* Moved */ |
| 58 | addr[-1] = value; |
| 59 | *q = value; |
| 60 | } else { |
| 61 | prom_printf(wrong_setaddr, addr-1, addr[-1], *addr, *q, value); |
| 62 | prom_halt(); |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | #else |
David S. Miller | 64d329e | 2007-10-27 00:17:01 -0700 | [diff] [blame] | 67 | static inline void set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 68 | { |
| 69 | *addr = value; |
| 70 | } |
| 71 | #endif |
| 72 | |
| 73 | void __init btfixup(void) |
| 74 | { |
| 75 | unsigned int *p, *q; |
| 76 | int type, count; |
| 77 | unsigned insn; |
| 78 | unsigned *addr; |
| 79 | int fmangled = 0; |
| 80 | void (*flush_cacheall)(void); |
| 81 | |
| 82 | if (!visited) { |
| 83 | visited++; |
| 84 | printk(version); |
Adrian Bunk | 5110bd2 | 2008-08-31 20:59:37 -0700 | [diff] [blame] | 85 | if (ARCH_SUN4C) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 86 | printk(str_sun4c); |
| 87 | else { |
| 88 | printk(str_srmmu, srmmu_name); |
| 89 | if (sparc_cpu_model == sun4d) |
| 90 | printk(str_iounit); |
| 91 | else |
| 92 | printk(str_iommu); |
| 93 | } |
| 94 | } |
| 95 | for (p = ___btfixup_start; p < ___btfixup_end; ) { |
| 96 | count = p[2]; |
| 97 | q = p + 3; |
| 98 | switch (type = *(unsigned char *)p) { |
| 99 | case 'f': |
| 100 | count = p[3]; |
| 101 | q = p + 4; |
| 102 | if (((p[0] & 1) || p[1]) |
| 103 | && ((p[1] & 3) || (unsigned *)(p[1]) < _stext || (unsigned *)(p[1]) >= _end)) { |
| 104 | prom_printf(wrong_f, p, p[1]); |
| 105 | prom_halt(); |
| 106 | } |
| 107 | break; |
| 108 | case 'b': |
| 109 | if (p[1] < (unsigned long)__init_begin || p[1] >= (unsigned long)__init_text_end || (p[1] & 3)) { |
| 110 | prom_printf(wrong_b, p, p[1]); |
| 111 | prom_halt(); |
| 112 | } |
| 113 | break; |
| 114 | case 's': |
| 115 | if (p[1] + 0x1000 >= 0x2000) { |
| 116 | prom_printf(wrong_s, p, p[1]); |
| 117 | prom_halt(); |
| 118 | } |
| 119 | break; |
| 120 | case 'h': |
| 121 | if (p[1] & 0x3ff) { |
| 122 | prom_printf(wrong_h, p, p[1]); |
| 123 | prom_halt(); |
| 124 | } |
| 125 | break; |
| 126 | case 'a': |
| 127 | if (p[1] + 0x1000 >= 0x2000 && (p[1] & 0x3ff)) { |
| 128 | prom_printf(wrong_a, p, p[1]); |
| 129 | prom_halt(); |
| 130 | } |
| 131 | break; |
| 132 | } |
| 133 | if (p[0] & 1) { |
| 134 | p[0] &= ~1; |
| 135 | while (count) { |
| 136 | fmangled = 0; |
| 137 | addr = (unsigned *)*q; |
| 138 | if (addr < _stext || addr >= _end) { |
| 139 | prom_printf(wrong, type, p); |
| 140 | prom_halt(); |
| 141 | } |
| 142 | insn = *addr; |
| 143 | #ifdef BTFIXUP_OPTIMIZE_OTHER |
| 144 | if (type != 'f' && q[1]) { |
| 145 | insn = *(unsigned int *)q[1]; |
| 146 | if (!insn || insn == 1) |
| 147 | insn = *addr; |
| 148 | else |
| 149 | fmangled = 1; |
| 150 | } |
| 151 | #endif |
| 152 | switch (type) { |
| 153 | case 'f': /* CALL */ |
| 154 | if (addr >= __start___ksymtab && addr < __stop___ksymtab) { |
| 155 | *addr = p[1]; |
| 156 | break; |
| 157 | } else if (!q[1]) { |
| 158 | if ((insn & 0xc1c00000) == 0x01000000) { /* SETHI */ |
| 159 | *addr = (insn & 0xffc00000) | (p[1] >> 10); break; |
| 160 | } else if ((insn & 0xc1f82000) == 0x80102000) { /* OR X, %LO(i), Y */ |
| 161 | *addr = (insn & 0xffffe000) | (p[1] & 0x3ff); break; |
| 162 | } else if ((insn & 0xc0000000) != 0x40000000) { /* !CALL */ |
| 163 | bad_f: |
| 164 | prom_printf(insn_f, p, addr, insn, addr[1]); |
| 165 | prom_halt(); |
| 166 | } |
| 167 | } else if (q[1] != 1) |
| 168 | addr[1] = q[1]; |
| 169 | if (p[2] == BTFIXUPCALL_NORM) { |
| 170 | norm_f: |
| 171 | *addr = 0x40000000 | ((p[1] - (unsigned)addr) >> 2); |
| 172 | q[1] = 0; |
| 173 | break; |
| 174 | } |
| 175 | #ifndef BTFIXUP_OPTIMIZE_NOP |
| 176 | goto norm_f; |
| 177 | #else |
| 178 | if (!(addr[1] & 0x80000000)) { |
| 179 | if ((addr[1] & 0xc1c00000) != 0x01000000) /* !SETHI */ |
| 180 | goto bad_f; /* CALL, Bicc, FBfcc, CBccc are weird in delay slot, aren't they? */ |
| 181 | } else { |
| 182 | if ((addr[1] & 0x01800000) == 0x01800000) { |
| 183 | if ((addr[1] & 0x01f80000) == 0x01e80000) { |
| 184 | /* RESTORE */ |
| 185 | goto norm_f; /* It is dangerous to patch that */ |
| 186 | } |
| 187 | goto bad_f; |
| 188 | } |
| 189 | if ((addr[1] & 0xffffe003) == 0x9e03e000) { |
| 190 | /* ADD %O7, XX, %o7 */ |
| 191 | int displac = (addr[1] << 19); |
| 192 | |
| 193 | displac = (displac >> 21) + 2; |
| 194 | *addr = (0x10800000) + (displac & 0x3fffff); |
| 195 | q[1] = addr[1]; |
| 196 | addr[1] = p[2]; |
| 197 | break; |
| 198 | } |
| 199 | if ((addr[1] & 0x201f) == 0x200f || (addr[1] & 0x7c000) == 0x3c000) |
| 200 | goto norm_f; /* Someone is playing bad tricks with us: rs1 or rs2 is o7 */ |
| 201 | if ((addr[1] & 0x3e000000) == 0x1e000000) |
| 202 | goto norm_f; /* rd is %o7. We'd better take care. */ |
| 203 | } |
| 204 | if (p[2] == BTFIXUPCALL_NOP) { |
| 205 | *addr = 0x01000000; |
| 206 | q[1] = 1; |
| 207 | break; |
| 208 | } |
| 209 | #ifndef BTFIXUP_OPTIMIZE_OTHER |
| 210 | goto norm_f; |
| 211 | #else |
| 212 | if (addr[1] == 0x01000000) { /* NOP in the delay slot */ |
| 213 | q[1] = addr[1]; |
| 214 | *addr = p[2]; |
| 215 | break; |
| 216 | } |
| 217 | if ((addr[1] & 0xc0000000) != 0xc0000000) { |
| 218 | /* Not a memory operation */ |
| 219 | if ((addr[1] & 0x30000000) == 0x10000000) { |
| 220 | /* Ok, non-memory op with rd %oX */ |
| 221 | if ((addr[1] & 0x3e000000) == 0x1c000000) |
| 222 | goto bad_f; /* Aiee. Someone is playing strange %sp tricks */ |
| 223 | if ((addr[1] & 0x3e000000) > 0x12000000 || |
| 224 | ((addr[1] & 0x3e000000) == 0x12000000 && |
| 225 | p[2] != BTFIXUPCALL_STO1O0 && p[2] != BTFIXUPCALL_SWAPO0O1) || |
| 226 | ((p[2] & 0xffffe000) == BTFIXUPCALL_RETINT(0))) { |
| 227 | /* Nobody uses the result. We can nop it out. */ |
| 228 | *addr = p[2]; |
| 229 | q[1] = addr[1]; |
| 230 | addr[1] = 0x01000000; |
| 231 | break; |
| 232 | } |
| 233 | if ((addr[1] & 0xf1ffffe0) == 0x90100000) { |
| 234 | /* MOV %reg, %Ox */ |
| 235 | if ((addr[1] & 0x3e000000) == 0x10000000 && |
| 236 | (p[2] & 0x7c000) == 0x20000) { |
| 237 | /* Ok, it is call xx; mov reg, %o0 and call optimizes |
| 238 | to doing something on %o0. Patch the patch. */ |
| 239 | *addr = (p[2] & ~0x7c000) | ((addr[1] & 0x1f) << 14); |
| 240 | q[1] = addr[1]; |
| 241 | addr[1] = 0x01000000; |
| 242 | break; |
| 243 | } |
| 244 | if ((addr[1] & 0x3e000000) == 0x12000000 && |
| 245 | p[2] == BTFIXUPCALL_STO1O0) { |
| 246 | *addr = (p[2] & ~0x3e000000) | ((addr[1] & 0x1f) << 25); |
| 247 | q[1] = addr[1]; |
| 248 | addr[1] = 0x01000000; |
| 249 | break; |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | *addr = addr[1]; |
| 255 | q[1] = addr[1]; |
| 256 | addr[1] = p[2]; |
| 257 | break; |
| 258 | #endif /* BTFIXUP_OPTIMIZE_OTHER */ |
| 259 | #endif /* BTFIXUP_OPTIMIZE_NOP */ |
| 260 | case 'b': /* BLACKBOX */ |
| 261 | /* Has to be sethi i, xx */ |
| 262 | if ((insn & 0xc1c00000) != 0x01000000) { |
| 263 | prom_printf(insn_b, p, addr, insn); |
| 264 | prom_halt(); |
| 265 | } else { |
| 266 | void (*do_fixup)(unsigned *); |
| 267 | |
| 268 | do_fixup = (void (*)(unsigned *))p[1]; |
| 269 | do_fixup(addr); |
| 270 | } |
| 271 | break; |
| 272 | case 's': /* SIMM13 */ |
| 273 | /* Has to be or %g0, i, xx */ |
| 274 | if ((insn & 0xc1ffe000) != 0x80102000) { |
| 275 | prom_printf(insn_s, p, addr, insn); |
| 276 | prom_halt(); |
| 277 | } |
| 278 | set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x1fff)); |
| 279 | break; |
| 280 | case 'h': /* SETHI */ |
| 281 | /* Has to be sethi i, xx */ |
| 282 | if ((insn & 0xc1c00000) != 0x01000000) { |
| 283 | prom_printf(insn_h, p, addr, insn); |
| 284 | prom_halt(); |
| 285 | } |
| 286 | set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10)); |
| 287 | break; |
| 288 | case 'a': /* HALF */ |
| 289 | /* Has to be sethi i, xx or or %g0, i, xx */ |
| 290 | if ((insn & 0xc1c00000) != 0x01000000 && |
| 291 | (insn & 0xc1ffe000) != 0x80102000) { |
| 292 | prom_printf(insn_a, p, addr, insn); |
| 293 | prom_halt(); |
| 294 | } |
| 295 | if (p[1] & 0x3ff) |
| 296 | set_addr(addr, q[1], fmangled, |
| 297 | (insn & 0x3e000000) | 0x80102000 | (p[1] & 0x1fff)); |
| 298 | else |
| 299 | set_addr(addr, q[1], fmangled, |
| 300 | (insn & 0x3e000000) | 0x01000000 | (p[1] >> 10)); |
| 301 | break; |
| 302 | case 'i': /* INT */ |
| 303 | if ((insn & 0xc1c00000) == 0x01000000) /* %HI */ |
| 304 | set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10)); |
| 305 | else if ((insn & 0x80002000) == 0x80002000 && |
| 306 | (insn & 0x01800000) != 0x01800000) /* %LO */ |
| 307 | set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x3ff)); |
| 308 | else { |
| 309 | prom_printf(insn_i, p, addr, insn); |
| 310 | prom_halt(); |
| 311 | } |
| 312 | break; |
| 313 | } |
| 314 | count -= 2; |
| 315 | q += 2; |
| 316 | } |
| 317 | } else |
| 318 | p = q + count; |
| 319 | } |
| 320 | #ifdef CONFIG_SMP |
| 321 | flush_cacheall = (void (*)(void))BTFIXUPVAL_CALL(local_flush_cache_all); |
| 322 | #else |
| 323 | flush_cacheall = (void (*)(void))BTFIXUPVAL_CALL(flush_cache_all); |
| 324 | #endif |
| 325 | if (!flush_cacheall) { |
| 326 | prom_printf(fca_und); |
| 327 | prom_halt(); |
| 328 | } |
| 329 | (*flush_cacheall)(); |
| 330 | } |