Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Note that prom_init() and anything called from prom_init() |
| 3 | * may be running at an address that is different from the address |
| 4 | * that it was linked at. References to static data items are |
| 5 | * handled by compiling this file with -mrelocatable-lib. |
| 6 | */ |
| 7 | |
| 8 | #include <linux/config.h> |
| 9 | #include <linux/kernel.h> |
| 10 | #include <linux/string.h> |
| 11 | #include <linux/init.h> |
| 12 | #include <linux/version.h> |
| 13 | #include <linux/threads.h> |
| 14 | #include <linux/spinlock.h> |
| 15 | #include <linux/ioport.h> |
| 16 | #include <linux/pci.h> |
| 17 | #include <linux/slab.h> |
| 18 | #include <linux/bitops.h> |
| 19 | |
| 20 | #include <asm/sections.h> |
| 21 | #include <asm/prom.h> |
| 22 | #include <asm/page.h> |
| 23 | #include <asm/irq.h> |
| 24 | #include <asm/io.h> |
| 25 | #include <asm/smp.h> |
| 26 | #include <asm/bootx.h> |
| 27 | #include <asm/system.h> |
| 28 | #include <asm/mmu.h> |
| 29 | #include <asm/pgtable.h> |
| 30 | #include <asm/bootinfo.h> |
| 31 | #include <asm/btext.h> |
| 32 | #include <asm/pci-bridge.h> |
| 33 | #include <asm/open_pic.h> |
| 34 | #include <asm/cacheflush.h> |
| 35 | |
| 36 | #ifdef CONFIG_LOGO_LINUX_CLUT224 |
| 37 | #include <linux/linux_logo.h> |
| 38 | extern const struct linux_logo logo_linux_clut224; |
| 39 | #endif |
| 40 | |
| 41 | /* |
| 42 | * Properties whose value is longer than this get excluded from our |
| 43 | * copy of the device tree. This way we don't waste space storing |
| 44 | * things like "driver,AAPL,MacOS,PowerPC" properties. But this value |
| 45 | * does need to be big enough to ensure that we don't lose things |
| 46 | * like the interrupt-map property on a PCI-PCI bridge. |
| 47 | */ |
| 48 | #define MAX_PROPERTY_LENGTH 4096 |
| 49 | |
| 50 | #ifndef FB_MAX /* avoid pulling in all of the fb stuff */ |
| 51 | #define FB_MAX 8 |
| 52 | #endif |
| 53 | |
| 54 | #define ALIGNUL(x) (((x) + sizeof(unsigned long)-1) & -sizeof(unsigned long)) |
| 55 | |
| 56 | typedef u32 prom_arg_t; |
| 57 | |
| 58 | struct prom_args { |
| 59 | const char *service; |
| 60 | int nargs; |
| 61 | int nret; |
| 62 | prom_arg_t args[10]; |
| 63 | }; |
| 64 | |
| 65 | struct pci_address { |
| 66 | unsigned a_hi; |
| 67 | unsigned a_mid; |
| 68 | unsigned a_lo; |
| 69 | }; |
| 70 | |
| 71 | struct pci_reg_property { |
| 72 | struct pci_address addr; |
| 73 | unsigned size_hi; |
| 74 | unsigned size_lo; |
| 75 | }; |
| 76 | |
| 77 | struct pci_range { |
| 78 | struct pci_address addr; |
| 79 | unsigned phys; |
| 80 | unsigned size_hi; |
| 81 | unsigned size_lo; |
| 82 | }; |
| 83 | |
| 84 | struct isa_reg_property { |
| 85 | unsigned space; |
| 86 | unsigned address; |
| 87 | unsigned size; |
| 88 | }; |
| 89 | |
| 90 | struct pci_intr_map { |
| 91 | struct pci_address addr; |
| 92 | unsigned dunno; |
| 93 | phandle int_ctrler; |
| 94 | unsigned intr; |
| 95 | }; |
| 96 | |
| 97 | static void prom_exit(void); |
| 98 | static int call_prom(const char *service, int nargs, int nret, ...); |
| 99 | static int call_prom_ret(const char *service, int nargs, int nret, |
| 100 | prom_arg_t *rets, ...); |
| 101 | static void prom_print_hex(unsigned int v); |
| 102 | static int prom_set_color(ihandle ih, int i, int r, int g, int b); |
| 103 | static int prom_next_node(phandle *nodep); |
| 104 | static unsigned long check_display(unsigned long mem); |
| 105 | static void setup_disp_fake_bi(ihandle dp); |
| 106 | static unsigned long copy_device_tree(unsigned long mem_start, |
| 107 | unsigned long mem_end); |
| 108 | static unsigned long inspect_node(phandle node, struct device_node *dad, |
| 109 | unsigned long mem_start, unsigned long mem_end, |
| 110 | struct device_node ***allnextpp); |
| 111 | static void prom_hold_cpus(unsigned long mem); |
| 112 | static void prom_instantiate_rtas(void); |
| 113 | static void * early_get_property(unsigned long base, unsigned long node, |
| 114 | char *prop); |
| 115 | |
| 116 | prom_entry prom __initdata; |
| 117 | ihandle prom_chosen __initdata; |
| 118 | ihandle prom_stdout __initdata; |
| 119 | |
| 120 | static char *prom_display_paths[FB_MAX] __initdata; |
| 121 | static phandle prom_display_nodes[FB_MAX] __initdata; |
| 122 | static unsigned int prom_num_displays __initdata; |
| 123 | static ihandle prom_disp_node __initdata; |
| 124 | char *of_stdout_device __initdata; |
| 125 | |
| 126 | unsigned int rtas_data; /* physical pointer */ |
| 127 | unsigned int rtas_entry; /* physical pointer */ |
| 128 | unsigned int rtas_size; |
| 129 | unsigned int old_rtas; |
| 130 | |
| 131 | boot_infos_t *boot_infos; |
| 132 | char *bootpath; |
| 133 | char *bootdevice; |
| 134 | struct device_node *allnodes; |
| 135 | |
| 136 | extern char *klimit; |
| 137 | |
| 138 | static void __init |
| 139 | prom_exit(void) |
| 140 | { |
| 141 | struct prom_args args; |
| 142 | |
| 143 | args.service = "exit"; |
| 144 | args.nargs = 0; |
| 145 | args.nret = 0; |
| 146 | prom(&args); |
| 147 | for (;;) /* should never get here */ |
| 148 | ; |
| 149 | } |
| 150 | |
| 151 | static int __init |
| 152 | call_prom(const char *service, int nargs, int nret, ...) |
| 153 | { |
| 154 | va_list list; |
| 155 | int i; |
| 156 | struct prom_args prom_args; |
| 157 | |
| 158 | prom_args.service = service; |
| 159 | prom_args.nargs = nargs; |
| 160 | prom_args.nret = nret; |
| 161 | va_start(list, nret); |
| 162 | for (i = 0; i < nargs; ++i) |
| 163 | prom_args.args[i] = va_arg(list, prom_arg_t); |
| 164 | va_end(list); |
| 165 | for (i = 0; i < nret; ++i) |
| 166 | prom_args.args[i + nargs] = 0; |
| 167 | prom(&prom_args); |
| 168 | return prom_args.args[nargs]; |
| 169 | } |
| 170 | |
| 171 | static int __init |
| 172 | call_prom_ret(const char *service, int nargs, int nret, prom_arg_t *rets, ...) |
| 173 | { |
| 174 | va_list list; |
| 175 | int i; |
| 176 | struct prom_args prom_args; |
| 177 | |
| 178 | prom_args.service = service; |
| 179 | prom_args.nargs = nargs; |
| 180 | prom_args.nret = nret; |
| 181 | va_start(list, rets); |
| 182 | for (i = 0; i < nargs; ++i) |
| 183 | prom_args.args[i] = va_arg(list, int); |
| 184 | va_end(list); |
| 185 | for (i = 0; i < nret; ++i) |
| 186 | prom_args.args[i + nargs] = 0; |
| 187 | prom(&prom_args); |
| 188 | for (i = 1; i < nret; ++i) |
| 189 | rets[i-1] = prom_args.args[nargs + i]; |
| 190 | return prom_args.args[nargs]; |
| 191 | } |
| 192 | |
| 193 | void __init |
| 194 | prom_print(const char *msg) |
| 195 | { |
| 196 | const char *p, *q; |
| 197 | |
| 198 | if (prom_stdout == 0) |
| 199 | return; |
| 200 | |
| 201 | for (p = msg; *p != 0; p = q) { |
| 202 | for (q = p; *q != 0 && *q != '\n'; ++q) |
| 203 | ; |
| 204 | if (q > p) |
| 205 | call_prom("write", 3, 1, prom_stdout, p, q - p); |
| 206 | if (*q != 0) { |
| 207 | ++q; |
| 208 | call_prom("write", 3, 1, prom_stdout, "\r\n", 2); |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | static void __init |
| 214 | prom_print_hex(unsigned int v) |
| 215 | { |
| 216 | char buf[16]; |
| 217 | int i, c; |
| 218 | |
| 219 | for (i = 0; i < 8; ++i) { |
| 220 | c = (v >> ((7-i)*4)) & 0xf; |
| 221 | c += (c >= 10)? ('a' - 10): '0'; |
| 222 | buf[i] = c; |
| 223 | } |
| 224 | buf[i] = ' '; |
| 225 | buf[i+1] = 0; |
| 226 | prom_print(buf); |
| 227 | } |
| 228 | |
| 229 | static int __init |
| 230 | prom_set_color(ihandle ih, int i, int r, int g, int b) |
| 231 | { |
| 232 | return call_prom("call-method", 6, 1, "color!", ih, i, b, g, r); |
| 233 | } |
| 234 | |
| 235 | static int __init |
| 236 | prom_next_node(phandle *nodep) |
| 237 | { |
| 238 | phandle node; |
| 239 | |
| 240 | if ((node = *nodep) != 0 |
| 241 | && (*nodep = call_prom("child", 1, 1, node)) != 0) |
| 242 | return 1; |
| 243 | if ((*nodep = call_prom("peer", 1, 1, node)) != 0) |
| 244 | return 1; |
| 245 | for (;;) { |
| 246 | if ((node = call_prom("parent", 1, 1, node)) == 0) |
| 247 | return 0; |
| 248 | if ((*nodep = call_prom("peer", 1, 1, node)) != 0) |
| 249 | return 1; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | #ifdef CONFIG_POWER4 |
| 254 | /* |
| 255 | * Set up a hash table with a set of entries in it to map the |
| 256 | * first 64MB of RAM. This is used on 64-bit machines since |
| 257 | * some of them don't have BATs. |
| 258 | */ |
| 259 | |
| 260 | static inline void make_pte(unsigned long htab, unsigned int hsize, |
| 261 | unsigned int va, unsigned int pa, int mode) |
| 262 | { |
| 263 | unsigned int *pteg; |
| 264 | unsigned int hash, i, vsid; |
| 265 | |
| 266 | vsid = ((va >> 28) * 0x111) << 12; |
| 267 | hash = ((va ^ vsid) >> 5) & 0x7fff80; |
| 268 | pteg = (unsigned int *)(htab + (hash & (hsize - 1))); |
| 269 | for (i = 0; i < 8; ++i, pteg += 4) { |
| 270 | if ((pteg[1] & 1) == 0) { |
| 271 | pteg[1] = vsid | ((va >> 16) & 0xf80) | 1; |
| 272 | pteg[3] = pa | mode; |
| 273 | break; |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | extern unsigned long _SDR1; |
| 279 | extern PTE *Hash; |
| 280 | extern unsigned long Hash_size; |
| 281 | |
| 282 | static void __init |
| 283 | prom_alloc_htab(void) |
| 284 | { |
| 285 | unsigned int hsize; |
| 286 | unsigned long htab; |
| 287 | unsigned int addr; |
| 288 | |
| 289 | /* |
| 290 | * Because of OF bugs we can't use the "claim" client |
| 291 | * interface to allocate memory for the hash table. |
| 292 | * This code is only used on 64-bit PPCs, and the only |
| 293 | * 64-bit PPCs at the moment are RS/6000s, and their |
| 294 | * OF is based at 0xc00000 (the 12M point), so we just |
| 295 | * arbitrarily use the 0x800000 - 0xc00000 region for the |
| 296 | * hash table. |
| 297 | * -- paulus. |
| 298 | */ |
| 299 | hsize = 4 << 20; /* POWER4 has no BATs */ |
| 300 | htab = (8 << 20); |
| 301 | call_prom("claim", 3, 1, htab, hsize, 0); |
| 302 | Hash = (void *)(htab + KERNELBASE); |
| 303 | Hash_size = hsize; |
| 304 | _SDR1 = htab + __ilog2(hsize) - 18; |
| 305 | |
| 306 | /* |
| 307 | * Put in PTEs for the first 64MB of RAM |
| 308 | */ |
| 309 | memset((void *)htab, 0, hsize); |
| 310 | for (addr = 0; addr < 0x4000000; addr += 0x1000) |
| 311 | make_pte(htab, hsize, addr + KERNELBASE, addr, |
| 312 | _PAGE_ACCESSED | _PAGE_COHERENT | PP_RWXX); |
| 313 | #if 0 /* DEBUG stuff mapping the SCC */ |
| 314 | make_pte(htab, hsize, 0x80013000, 0x80013000, |
| 315 | _PAGE_ACCESSED | _PAGE_NO_CACHE | _PAGE_GUARDED | PP_RWXX); |
| 316 | #endif |
| 317 | } |
| 318 | #endif /* CONFIG_POWER4 */ |
| 319 | |
| 320 | |
| 321 | /* |
| 322 | * If we have a display that we don't know how to drive, |
| 323 | * we will want to try to execute OF's open method for it |
| 324 | * later. However, OF will probably fall over if we do that |
| 325 | * we've taken over the MMU. |
| 326 | * So we check whether we will need to open the display, |
| 327 | * and if so, open it now. |
| 328 | */ |
| 329 | static unsigned long __init |
| 330 | check_display(unsigned long mem) |
| 331 | { |
| 332 | phandle node; |
| 333 | ihandle ih; |
| 334 | int i, j; |
| 335 | char type[16], *path; |
| 336 | static unsigned char default_colors[] = { |
| 337 | 0x00, 0x00, 0x00, |
| 338 | 0x00, 0x00, 0xaa, |
| 339 | 0x00, 0xaa, 0x00, |
| 340 | 0x00, 0xaa, 0xaa, |
| 341 | 0xaa, 0x00, 0x00, |
| 342 | 0xaa, 0x00, 0xaa, |
| 343 | 0xaa, 0xaa, 0x00, |
| 344 | 0xaa, 0xaa, 0xaa, |
| 345 | 0x55, 0x55, 0x55, |
| 346 | 0x55, 0x55, 0xff, |
| 347 | 0x55, 0xff, 0x55, |
| 348 | 0x55, 0xff, 0xff, |
| 349 | 0xff, 0x55, 0x55, |
| 350 | 0xff, 0x55, 0xff, |
| 351 | 0xff, 0xff, 0x55, |
| 352 | 0xff, 0xff, 0xff |
| 353 | }; |
| 354 | const unsigned char *clut; |
| 355 | |
| 356 | prom_disp_node = 0; |
| 357 | |
| 358 | for (node = 0; prom_next_node(&node); ) { |
| 359 | type[0] = 0; |
| 360 | call_prom("getprop", 4, 1, node, "device_type", |
| 361 | type, sizeof(type)); |
| 362 | if (strcmp(type, "display") != 0) |
| 363 | continue; |
| 364 | /* It seems OF doesn't null-terminate the path :-( */ |
| 365 | path = (char *) mem; |
| 366 | memset(path, 0, 256); |
| 367 | if (call_prom("package-to-path", 3, 1, node, path, 255) < 0) |
| 368 | continue; |
| 369 | |
| 370 | /* |
| 371 | * If this display is the device that OF is using for stdout, |
| 372 | * move it to the front of the list. |
| 373 | */ |
| 374 | mem += strlen(path) + 1; |
| 375 | i = prom_num_displays++; |
| 376 | if (of_stdout_device != 0 && i > 0 |
| 377 | && strcmp(of_stdout_device, path) == 0) { |
| 378 | for (; i > 0; --i) { |
| 379 | prom_display_paths[i] |
| 380 | = prom_display_paths[i-1]; |
| 381 | prom_display_nodes[i] |
| 382 | = prom_display_nodes[i-1]; |
| 383 | } |
| 384 | } |
| 385 | prom_display_paths[i] = path; |
| 386 | prom_display_nodes[i] = node; |
| 387 | if (i == 0) |
| 388 | prom_disp_node = node; |
| 389 | if (prom_num_displays >= FB_MAX) |
| 390 | break; |
| 391 | } |
| 392 | |
| 393 | for (j=0; j<prom_num_displays; j++) { |
| 394 | path = prom_display_paths[j]; |
| 395 | node = prom_display_nodes[j]; |
| 396 | prom_print("opening display "); |
| 397 | prom_print(path); |
| 398 | ih = call_prom("open", 1, 1, path); |
| 399 | if (ih == 0 || ih == (ihandle) -1) { |
| 400 | prom_print("... failed\n"); |
| 401 | for (i=j+1; i<prom_num_displays; i++) { |
| 402 | prom_display_paths[i-1] = prom_display_paths[i]; |
| 403 | prom_display_nodes[i-1] = prom_display_nodes[i]; |
| 404 | } |
| 405 | if (--prom_num_displays > 0) { |
| 406 | prom_disp_node = prom_display_nodes[j]; |
| 407 | j--; |
| 408 | } else |
| 409 | prom_disp_node = 0; |
| 410 | continue; |
| 411 | } else { |
| 412 | prom_print("... ok\n"); |
| 413 | call_prom("setprop", 4, 1, node, "linux,opened", 0, 0); |
| 414 | |
| 415 | /* |
| 416 | * Setup a usable color table when the appropriate |
| 417 | * method is available. |
| 418 | * Should update this to use set-colors. |
| 419 | */ |
| 420 | clut = default_colors; |
| 421 | for (i = 0; i < 32; i++, clut += 3) |
| 422 | if (prom_set_color(ih, i, clut[0], clut[1], |
| 423 | clut[2]) != 0) |
| 424 | break; |
| 425 | |
| 426 | #ifdef CONFIG_LOGO_LINUX_CLUT224 |
| 427 | clut = PTRRELOC(logo_linux_clut224.clut); |
| 428 | for (i = 0; i < logo_linux_clut224.clutsize; |
| 429 | i++, clut += 3) |
| 430 | if (prom_set_color(ih, i + 32, clut[0], |
| 431 | clut[1], clut[2]) != 0) |
| 432 | break; |
| 433 | #endif /* CONFIG_LOGO_LINUX_CLUT224 */ |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | if (prom_stdout) { |
| 438 | phandle p; |
| 439 | p = call_prom("instance-to-package", 1, 1, prom_stdout); |
| 440 | if (p && p != -1) { |
| 441 | type[0] = 0; |
| 442 | call_prom("getprop", 4, 1, p, "device_type", |
| 443 | type, sizeof(type)); |
| 444 | if (strcmp(type, "display") == 0) |
| 445 | call_prom("setprop", 4, 1, p, "linux,boot-display", |
| 446 | 0, 0); |
| 447 | } |
| 448 | } |
| 449 | |
| 450 | return ALIGNUL(mem); |
| 451 | } |
| 452 | |
| 453 | /* This function will enable the early boot text when doing OF booting. This |
| 454 | * way, xmon output should work too |
| 455 | */ |
| 456 | static void __init |
| 457 | setup_disp_fake_bi(ihandle dp) |
| 458 | { |
| 459 | #ifdef CONFIG_BOOTX_TEXT |
| 460 | int width = 640, height = 480, depth = 8, pitch; |
| 461 | unsigned address; |
| 462 | struct pci_reg_property addrs[8]; |
| 463 | int i, naddrs; |
| 464 | char name[32]; |
| 465 | char *getprop = "getprop"; |
| 466 | |
| 467 | prom_print("Initializing fake screen: "); |
| 468 | |
| 469 | memset(name, 0, sizeof(name)); |
| 470 | call_prom(getprop, 4, 1, dp, "name", name, sizeof(name)); |
| 471 | name[sizeof(name)-1] = 0; |
| 472 | prom_print(name); |
| 473 | prom_print("\n"); |
| 474 | call_prom(getprop, 4, 1, dp, "width", &width, sizeof(width)); |
| 475 | call_prom(getprop, 4, 1, dp, "height", &height, sizeof(height)); |
| 476 | call_prom(getprop, 4, 1, dp, "depth", &depth, sizeof(depth)); |
| 477 | pitch = width * ((depth + 7) / 8); |
| 478 | call_prom(getprop, 4, 1, dp, "linebytes", |
| 479 | &pitch, sizeof(pitch)); |
| 480 | if (pitch == 1) |
| 481 | pitch = 0x1000; /* for strange IBM display */ |
| 482 | address = 0; |
| 483 | call_prom(getprop, 4, 1, dp, "address", |
| 484 | &address, sizeof(address)); |
| 485 | if (address == 0) { |
| 486 | /* look for an assigned address with a size of >= 1MB */ |
| 487 | naddrs = call_prom(getprop, 4, 1, dp, "assigned-addresses", |
| 488 | addrs, sizeof(addrs)); |
| 489 | naddrs /= sizeof(struct pci_reg_property); |
| 490 | for (i = 0; i < naddrs; ++i) { |
| 491 | if (addrs[i].size_lo >= (1 << 20)) { |
| 492 | address = addrs[i].addr.a_lo; |
| 493 | /* use the BE aperture if possible */ |
| 494 | if (addrs[i].size_lo >= (16 << 20)) |
| 495 | address += (8 << 20); |
| 496 | break; |
| 497 | } |
| 498 | } |
| 499 | if (address == 0) { |
| 500 | prom_print("Failed to get address\n"); |
| 501 | return; |
| 502 | } |
| 503 | } |
| 504 | /* kludge for valkyrie */ |
| 505 | if (strcmp(name, "valkyrie") == 0) |
| 506 | address += 0x1000; |
| 507 | |
| 508 | #ifdef CONFIG_POWER4 |
| 509 | #if CONFIG_TASK_SIZE > 0x80000000 |
| 510 | #error CONFIG_TASK_SIZE cannot be above 0x80000000 with BOOTX_TEXT on G5 |
| 511 | #endif |
| 512 | { |
| 513 | extern boot_infos_t disp_bi; |
| 514 | unsigned long va, pa, i, offset; |
| 515 | va = 0x90000000; |
| 516 | pa = address & 0xfffff000ul; |
| 517 | offset = address & 0x00000fff; |
| 518 | |
| 519 | for (i=0; i<0x4000; i++) { |
| 520 | make_pte((unsigned long)Hash - KERNELBASE, Hash_size, va, pa, |
| 521 | _PAGE_ACCESSED | _PAGE_NO_CACHE | |
| 522 | _PAGE_GUARDED | PP_RWXX); |
| 523 | va += 0x1000; |
| 524 | pa += 0x1000; |
| 525 | } |
| 526 | btext_setup_display(width, height, depth, pitch, 0x90000000 | offset); |
| 527 | disp_bi.dispDeviceBase = (u8 *)address; |
| 528 | } |
| 529 | #else /* CONFIG_POWER4 */ |
| 530 | btext_setup_display(width, height, depth, pitch, address); |
| 531 | btext_prepare_BAT(); |
| 532 | #endif /* CONFIG_POWER4 */ |
| 533 | #endif /* CONFIG_BOOTX_TEXT */ |
| 534 | } |
| 535 | |
| 536 | /* |
| 537 | * Make a copy of the device tree from the PROM. |
| 538 | */ |
| 539 | static unsigned long __init |
| 540 | copy_device_tree(unsigned long mem_start, unsigned long mem_end) |
| 541 | { |
| 542 | phandle root; |
| 543 | unsigned long new_start; |
| 544 | struct device_node **allnextp; |
| 545 | |
| 546 | root = call_prom("peer", 1, 1, (phandle)0); |
| 547 | if (root == (phandle)0) { |
| 548 | prom_print("couldn't get device tree root\n"); |
| 549 | prom_exit(); |
| 550 | } |
| 551 | allnextp = &allnodes; |
| 552 | mem_start = ALIGNUL(mem_start); |
| 553 | new_start = inspect_node(root, NULL, mem_start, mem_end, &allnextp); |
| 554 | *allnextp = NULL; |
| 555 | return new_start; |
| 556 | } |
| 557 | |
| 558 | static unsigned long __init |
| 559 | inspect_node(phandle node, struct device_node *dad, |
| 560 | unsigned long mem_start, unsigned long mem_end, |
| 561 | struct device_node ***allnextpp) |
| 562 | { |
| 563 | int l; |
| 564 | phandle child; |
| 565 | struct device_node *np; |
| 566 | struct property *pp, **prev_propp; |
| 567 | char *prev_name, *namep; |
| 568 | unsigned char *valp; |
| 569 | |
| 570 | np = (struct device_node *) mem_start; |
| 571 | mem_start += sizeof(struct device_node); |
| 572 | memset(np, 0, sizeof(*np)); |
| 573 | np->node = node; |
| 574 | **allnextpp = PTRUNRELOC(np); |
| 575 | *allnextpp = &np->allnext; |
| 576 | if (dad != 0) { |
| 577 | np->parent = PTRUNRELOC(dad); |
| 578 | /* we temporarily use the `next' field as `last_child'. */ |
| 579 | if (dad->next == 0) |
| 580 | dad->child = PTRUNRELOC(np); |
| 581 | else |
| 582 | dad->next->sibling = PTRUNRELOC(np); |
| 583 | dad->next = np; |
| 584 | } |
| 585 | |
| 586 | /* get and store all properties */ |
| 587 | prev_propp = &np->properties; |
| 588 | prev_name = ""; |
| 589 | for (;;) { |
| 590 | pp = (struct property *) mem_start; |
| 591 | namep = (char *) (pp + 1); |
| 592 | pp->name = PTRUNRELOC(namep); |
| 593 | if (call_prom("nextprop", 3, 1, node, prev_name, namep) <= 0) |
| 594 | break; |
| 595 | mem_start = ALIGNUL((unsigned long)namep + strlen(namep) + 1); |
| 596 | prev_name = namep; |
| 597 | valp = (unsigned char *) mem_start; |
| 598 | pp->value = PTRUNRELOC(valp); |
| 599 | pp->length = call_prom("getprop", 4, 1, node, namep, |
| 600 | valp, mem_end - mem_start); |
| 601 | if (pp->length < 0) |
| 602 | continue; |
| 603 | #ifdef MAX_PROPERTY_LENGTH |
| 604 | if (pp->length > MAX_PROPERTY_LENGTH) |
| 605 | continue; /* ignore this property */ |
| 606 | #endif |
| 607 | mem_start = ALIGNUL(mem_start + pp->length); |
| 608 | *prev_propp = PTRUNRELOC(pp); |
| 609 | prev_propp = &pp->next; |
| 610 | } |
| 611 | if (np->node != 0) { |
| 612 | /* Add a "linux,phandle" property" */ |
| 613 | pp = (struct property *) mem_start; |
| 614 | *prev_propp = PTRUNRELOC(pp); |
| 615 | prev_propp = &pp->next; |
| 616 | namep = (char *) (pp + 1); |
| 617 | pp->name = PTRUNRELOC(namep); |
| 618 | strcpy(namep, "linux,phandle"); |
| 619 | mem_start = ALIGNUL((unsigned long)namep + strlen(namep) + 1); |
| 620 | pp->value = (unsigned char *) PTRUNRELOC(&np->node); |
| 621 | pp->length = sizeof(np->node); |
| 622 | } |
| 623 | *prev_propp = NULL; |
| 624 | |
| 625 | /* get the node's full name */ |
| 626 | l = call_prom("package-to-path", 3, 1, node, |
| 627 | mem_start, mem_end - mem_start); |
| 628 | if (l >= 0) { |
| 629 | np->full_name = PTRUNRELOC((char *) mem_start); |
| 630 | *(char *)(mem_start + l) = 0; |
| 631 | mem_start = ALIGNUL(mem_start + l + 1); |
| 632 | } |
| 633 | |
| 634 | /* do all our children */ |
| 635 | child = call_prom("child", 1, 1, node); |
| 636 | while (child != 0) { |
| 637 | mem_start = inspect_node(child, np, mem_start, mem_end, |
| 638 | allnextpp); |
| 639 | child = call_prom("peer", 1, 1, child); |
| 640 | } |
| 641 | |
| 642 | return mem_start; |
| 643 | } |
| 644 | |
| 645 | unsigned long smp_chrp_cpu_nr __initdata = 0; |
| 646 | |
| 647 | /* |
| 648 | * With CHRP SMP we need to use the OF to start the other |
| 649 | * processors so we can't wait until smp_boot_cpus (the OF is |
| 650 | * trashed by then) so we have to put the processors into |
| 651 | * a holding pattern controlled by the kernel (not OF) before |
| 652 | * we destroy the OF. |
| 653 | * |
| 654 | * This uses a chunk of high memory, puts some holding pattern |
| 655 | * code there and sends the other processors off to there until |
| 656 | * smp_boot_cpus tells them to do something. We do that by using |
| 657 | * physical address 0x0. The holding pattern checks that address |
| 658 | * until its cpu # is there, when it is that cpu jumps to |
| 659 | * __secondary_start(). smp_boot_cpus() takes care of setting those |
| 660 | * values. |
| 661 | * |
| 662 | * We also use physical address 0x4 here to tell when a cpu |
| 663 | * is in its holding pattern code. |
| 664 | * |
| 665 | * -- Cort |
| 666 | * |
| 667 | * Note that we have to do this if we have more than one CPU, |
| 668 | * even if this is a UP kernel. Otherwise when we trash OF |
| 669 | * the other CPUs will start executing some random instructions |
| 670 | * and crash the system. -- paulus |
| 671 | */ |
| 672 | static void __init |
| 673 | prom_hold_cpus(unsigned long mem) |
| 674 | { |
| 675 | extern void __secondary_hold(void); |
| 676 | unsigned long i; |
| 677 | int cpu; |
| 678 | phandle node; |
| 679 | char type[16], *path; |
| 680 | unsigned int reg; |
| 681 | |
| 682 | /* |
| 683 | * XXX: hack to make sure we're chrp, assume that if we're |
| 684 | * chrp we have a device_type property -- Cort |
| 685 | */ |
| 686 | node = call_prom("finddevice", 1, 1, "/"); |
| 687 | if (call_prom("getprop", 4, 1, node, |
| 688 | "device_type", type, sizeof(type)) <= 0) |
| 689 | return; |
| 690 | |
| 691 | /* copy the holding pattern code to someplace safe (0) */ |
| 692 | /* the holding pattern is now within the first 0x100 |
| 693 | bytes of the kernel image -- paulus */ |
| 694 | memcpy((void *)0, _stext, 0x100); |
| 695 | flush_icache_range(0, 0x100); |
| 696 | |
| 697 | /* look for cpus */ |
| 698 | *(unsigned long *)(0x0) = 0; |
| 699 | asm volatile("dcbf 0,%0": : "r" (0) : "memory"); |
| 700 | for (node = 0; prom_next_node(&node); ) { |
| 701 | type[0] = 0; |
| 702 | call_prom("getprop", 4, 1, node, "device_type", |
| 703 | type, sizeof(type)); |
| 704 | if (strcmp(type, "cpu") != 0) |
| 705 | continue; |
| 706 | path = (char *) mem; |
| 707 | memset(path, 0, 256); |
| 708 | if (call_prom("package-to-path", 3, 1, node, path, 255) < 0) |
| 709 | continue; |
| 710 | reg = -1; |
| 711 | call_prom("getprop", 4, 1, node, "reg", ®, sizeof(reg)); |
| 712 | cpu = smp_chrp_cpu_nr++; |
| 713 | #ifdef CONFIG_SMP |
| 714 | smp_hw_index[cpu] = reg; |
| 715 | #endif /* CONFIG_SMP */ |
| 716 | /* XXX: hack - don't start cpu 0, this cpu -- Cort */ |
| 717 | if (cpu == 0) |
| 718 | continue; |
| 719 | prom_print("starting cpu "); |
| 720 | prom_print(path); |
| 721 | *(ulong *)(0x4) = 0; |
| 722 | call_prom("start-cpu", 3, 0, node, |
| 723 | (char *)__secondary_hold - _stext, cpu); |
| 724 | prom_print("..."); |
| 725 | for ( i = 0 ; (i < 10000) && (*(ulong *)(0x4) == 0); i++ ) |
| 726 | ; |
| 727 | if (*(ulong *)(0x4) == cpu) |
| 728 | prom_print("ok\n"); |
| 729 | else { |
| 730 | prom_print("failed: "); |
| 731 | prom_print_hex(*(ulong *)0x4); |
| 732 | prom_print("\n"); |
| 733 | } |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | static void __init |
| 738 | prom_instantiate_rtas(void) |
| 739 | { |
| 740 | ihandle prom_rtas; |
| 741 | prom_arg_t result; |
| 742 | |
| 743 | prom_rtas = call_prom("finddevice", 1, 1, "/rtas"); |
| 744 | if (prom_rtas == -1) |
| 745 | return; |
| 746 | |
| 747 | rtas_size = 0; |
| 748 | call_prom("getprop", 4, 1, prom_rtas, |
| 749 | "rtas-size", &rtas_size, sizeof(rtas_size)); |
| 750 | prom_print("instantiating rtas"); |
| 751 | if (rtas_size == 0) { |
| 752 | rtas_data = 0; |
| 753 | } else { |
| 754 | /* |
| 755 | * Ask OF for some space for RTAS. |
| 756 | * Actually OF has bugs so we just arbitrarily |
| 757 | * use memory at the 6MB point. |
| 758 | */ |
| 759 | rtas_data = 6 << 20; |
| 760 | prom_print(" at "); |
| 761 | prom_print_hex(rtas_data); |
| 762 | } |
| 763 | |
| 764 | prom_rtas = call_prom("open", 1, 1, "/rtas"); |
| 765 | prom_print("..."); |
| 766 | rtas_entry = 0; |
| 767 | if (call_prom_ret("call-method", 3, 2, &result, |
| 768 | "instantiate-rtas", prom_rtas, rtas_data) == 0) |
| 769 | rtas_entry = result; |
| 770 | if ((rtas_entry == -1) || (rtas_entry == 0)) |
| 771 | prom_print(" failed\n"); |
| 772 | else |
| 773 | prom_print(" done\n"); |
| 774 | } |
| 775 | |
| 776 | /* |
| 777 | * We enter here early on, when the Open Firmware prom is still |
| 778 | * handling exceptions and the MMU hash table for us. |
| 779 | */ |
| 780 | unsigned long __init |
| 781 | prom_init(int r3, int r4, prom_entry pp) |
| 782 | { |
| 783 | unsigned long mem; |
| 784 | ihandle prom_mmu; |
| 785 | unsigned long offset = reloc_offset(); |
| 786 | int i, l; |
| 787 | char *p, *d; |
| 788 | unsigned long phys; |
| 789 | prom_arg_t result[3]; |
| 790 | char model[32]; |
| 791 | phandle node; |
| 792 | int rc; |
| 793 | |
| 794 | /* Default */ |
| 795 | phys = (unsigned long) &_stext; |
| 796 | |
| 797 | /* First get a handle for the stdout device */ |
| 798 | prom = pp; |
| 799 | prom_chosen = call_prom("finddevice", 1, 1, "/chosen"); |
| 800 | if (prom_chosen == -1) |
| 801 | prom_exit(); |
| 802 | if (call_prom("getprop", 4, 1, prom_chosen, "stdout", |
| 803 | &prom_stdout, sizeof(prom_stdout)) <= 0) |
| 804 | prom_exit(); |
| 805 | |
| 806 | /* Get the full OF pathname of the stdout device */ |
| 807 | mem = (unsigned long) klimit + offset; |
| 808 | p = (char *) mem; |
| 809 | memset(p, 0, 256); |
| 810 | call_prom("instance-to-path", 3, 1, prom_stdout, p, 255); |
| 811 | of_stdout_device = p; |
| 812 | mem += strlen(p) + 1; |
| 813 | |
| 814 | /* Get the boot device and translate it to a full OF pathname. */ |
| 815 | p = (char *) mem; |
| 816 | l = call_prom("getprop", 4, 1, prom_chosen, "bootpath", p, 1<<20); |
| 817 | if (l > 0) { |
| 818 | p[l] = 0; /* should already be null-terminated */ |
| 819 | bootpath = PTRUNRELOC(p); |
| 820 | mem += l + 1; |
| 821 | d = (char *) mem; |
| 822 | *d = 0; |
| 823 | call_prom("canon", 3, 1, p, d, 1<<20); |
| 824 | bootdevice = PTRUNRELOC(d); |
| 825 | mem = ALIGNUL(mem + strlen(d) + 1); |
| 826 | } |
| 827 | |
| 828 | prom_instantiate_rtas(); |
| 829 | |
| 830 | #ifdef CONFIG_POWER4 |
| 831 | /* |
| 832 | * Find out how much memory we have and allocate a |
| 833 | * suitably-sized hash table. |
| 834 | */ |
| 835 | prom_alloc_htab(); |
| 836 | #endif |
| 837 | mem = check_display(mem); |
| 838 | |
| 839 | prom_print("copying OF device tree..."); |
| 840 | mem = copy_device_tree(mem, mem + (1<<20)); |
| 841 | prom_print("done\n"); |
| 842 | |
| 843 | prom_hold_cpus(mem); |
| 844 | |
| 845 | klimit = (char *) (mem - offset); |
| 846 | |
| 847 | node = call_prom("finddevice", 1, 1, "/"); |
| 848 | rc = call_prom("getprop", 4, 1, node, "model", model, sizeof(model)); |
| 849 | if (rc > 0 && !strncmp (model, "Pegasos", 7) |
| 850 | && strncmp (model, "Pegasos2", 8)) { |
| 851 | /* Pegasos 1 has a broken translate method in the OF, |
| 852 | * and furthermore the BATs are mapped 1:1 so the phys |
| 853 | * address calculated above is correct, so let's use |
| 854 | * it directly. |
| 855 | */ |
| 856 | } else if (offset == 0) { |
| 857 | /* If we are already running at 0xc0000000, we assume we were |
| 858 | * loaded by an OF bootloader which did set a BAT for us. |
| 859 | * This breaks OF translate so we force phys to be 0. |
| 860 | */ |
| 861 | prom_print("(already at 0xc0000000) phys=0\n"); |
| 862 | phys = 0; |
| 863 | } else if (call_prom("getprop", 4, 1, prom_chosen, "mmu", |
| 864 | &prom_mmu, sizeof(prom_mmu)) <= 0) { |
| 865 | prom_print(" no MMU found\n"); |
| 866 | } else if (call_prom_ret("call-method", 4, 4, result, "translate", |
| 867 | prom_mmu, &_stext, 1) != 0) { |
| 868 | prom_print(" (translate failed)\n"); |
| 869 | } else { |
| 870 | /* We assume the phys. address size is 3 cells */ |
| 871 | phys = result[2]; |
| 872 | } |
| 873 | |
| 874 | if (prom_disp_node != 0) |
| 875 | setup_disp_fake_bi(prom_disp_node); |
| 876 | |
| 877 | /* Use quiesce call to get OF to shut down any devices it's using */ |
| 878 | prom_print("Calling quiesce ...\n"); |
| 879 | call_prom("quiesce", 0, 0); |
| 880 | |
| 881 | /* Relocate various pointers which will be used once the |
| 882 | kernel is running at the address it was linked at. */ |
| 883 | for (i = 0; i < prom_num_displays; ++i) |
| 884 | prom_display_paths[i] = PTRUNRELOC(prom_display_paths[i]); |
| 885 | |
| 886 | #ifdef CONFIG_SERIAL_CORE_CONSOLE |
| 887 | /* Relocate the of stdout for console autodetection */ |
| 888 | of_stdout_device = PTRUNRELOC(of_stdout_device); |
| 889 | #endif |
| 890 | |
| 891 | prom_print("returning 0x"); |
| 892 | prom_print_hex(phys); |
| 893 | prom_print("from prom_init\n"); |
| 894 | prom_stdout = 0; |
| 895 | |
| 896 | return phys; |
| 897 | } |
| 898 | |
| 899 | /* |
| 900 | * early_get_property is used to access the device tree image prepared |
| 901 | * by BootX very early on, before the pointers in it have been relocated. |
| 902 | */ |
| 903 | static void * __init |
| 904 | early_get_property(unsigned long base, unsigned long node, char *prop) |
| 905 | { |
| 906 | struct device_node *np = (struct device_node *)(base + node); |
| 907 | struct property *pp; |
| 908 | |
| 909 | for (pp = np->properties; pp != 0; pp = pp->next) { |
| 910 | pp = (struct property *) (base + (unsigned long)pp); |
| 911 | if (strcmp((char *)((unsigned long)pp->name + base), |
| 912 | prop) == 0) { |
| 913 | return (void *)((unsigned long)pp->value + base); |
| 914 | } |
| 915 | } |
| 916 | return NULL; |
| 917 | } |
| 918 | |
| 919 | /* Is boot-info compatible ? */ |
| 920 | #define BOOT_INFO_IS_COMPATIBLE(bi) ((bi)->compatible_version <= BOOT_INFO_VERSION) |
| 921 | #define BOOT_INFO_IS_V2_COMPATIBLE(bi) ((bi)->version >= 2) |
| 922 | #define BOOT_INFO_IS_V4_COMPATIBLE(bi) ((bi)->version >= 4) |
| 923 | |
| 924 | void __init |
| 925 | bootx_init(unsigned long r4, unsigned long phys) |
| 926 | { |
| 927 | boot_infos_t *bi = (boot_infos_t *) r4; |
| 928 | unsigned long space; |
| 929 | unsigned long ptr, x; |
| 930 | char *model; |
| 931 | |
| 932 | boot_infos = PTRUNRELOC(bi); |
| 933 | if (!BOOT_INFO_IS_V2_COMPATIBLE(bi)) |
| 934 | bi->logicalDisplayBase = NULL; |
| 935 | |
| 936 | #ifdef CONFIG_BOOTX_TEXT |
| 937 | btext_init(bi); |
| 938 | |
| 939 | /* |
| 940 | * Test if boot-info is compatible. Done only in config |
| 941 | * CONFIG_BOOTX_TEXT since there is nothing much we can do |
| 942 | * with an incompatible version, except display a message |
| 943 | * and eventually hang the processor... |
| 944 | * |
| 945 | * I'll try to keep enough of boot-info compatible in the |
| 946 | * future to always allow display of this message; |
| 947 | */ |
| 948 | if (!BOOT_INFO_IS_COMPATIBLE(bi)) { |
| 949 | btext_drawstring(" !!! WARNING - Incompatible version of BootX !!!\n\n\n"); |
| 950 | btext_flushscreen(); |
| 951 | } |
| 952 | #endif /* CONFIG_BOOTX_TEXT */ |
| 953 | |
| 954 | /* New BootX enters kernel with MMU off, i/os are not allowed |
| 955 | here. This hack will have been done by the boostrap anyway. |
| 956 | */ |
| 957 | if (bi->version < 4) { |
| 958 | /* |
| 959 | * XXX If this is an iMac, turn off the USB controller. |
| 960 | */ |
| 961 | model = (char *) early_get_property |
| 962 | (r4 + bi->deviceTreeOffset, 4, "model"); |
| 963 | if (model |
| 964 | && (strcmp(model, "iMac,1") == 0 |
| 965 | || strcmp(model, "PowerMac1,1") == 0)) { |
| 966 | out_le32((unsigned *)0x80880008, 1); /* XXX */ |
| 967 | } |
| 968 | } |
| 969 | |
| 970 | /* Move klimit to enclose device tree, args, ramdisk, etc... */ |
| 971 | if (bi->version < 5) { |
| 972 | space = bi->deviceTreeOffset + bi->deviceTreeSize; |
| 973 | if (bi->ramDisk) |
| 974 | space = bi->ramDisk + bi->ramDiskSize; |
| 975 | } else |
| 976 | space = bi->totalParamsSize; |
| 977 | klimit = PTRUNRELOC((char *) bi + space); |
| 978 | |
| 979 | /* New BootX will have flushed all TLBs and enters kernel with |
| 980 | MMU switched OFF, so this should not be useful anymore. |
| 981 | */ |
| 982 | if (bi->version < 4) { |
| 983 | /* |
| 984 | * Touch each page to make sure the PTEs for them |
| 985 | * are in the hash table - the aim is to try to avoid |
| 986 | * getting DSI exceptions while copying the kernel image. |
| 987 | */ |
| 988 | for (ptr = ((unsigned long) &_stext) & PAGE_MASK; |
| 989 | ptr < (unsigned long)bi + space; ptr += PAGE_SIZE) |
| 990 | x = *(volatile unsigned long *)ptr; |
| 991 | } |
| 992 | |
| 993 | #ifdef CONFIG_BOOTX_TEXT |
| 994 | /* |
| 995 | * Note that after we call btext_prepare_BAT, we can't do |
| 996 | * prom_draw*, flushscreen or clearscreen until we turn the MMU |
| 997 | * on, since btext_prepare_BAT sets disp_bi.logicalDisplayBase |
| 998 | * to a virtual address. |
| 999 | */ |
| 1000 | btext_prepare_BAT(); |
| 1001 | #endif |
| 1002 | } |