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