1 /* 2 * Early boot support code for BootX bootloader 3 * 4 * Copyright (C) 2005 Ben. Herrenschmidt (benh@kernel.crashing.org) 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 9 * 2 of the License, or (at your option) any later version. 10 */ 11 12 #include <linux/kernel.h> 13 #include <linux/string.h> 14 #include <linux/init.h> 15 #include <generated/utsrelease.h> 16 #include <asm/sections.h> 17 #include <asm/prom.h> 18 #include <asm/page.h> 19 #include <asm/bootx.h> 20 #include <asm/btext.h> 21 #include <asm/io.h> 22 #include <asm/setup.h> 23 24 #undef DEBUG 25 #define SET_BOOT_BAT 26 27 #ifdef DEBUG 28 #define DBG(fmt...) do { bootx_printf(fmt); } while(0) 29 #else 30 #define DBG(fmt...) do { } while(0) 31 #endif 32 33 extern void __start(unsigned long r3, unsigned long r4, unsigned long r5); 34 35 static unsigned long __initdata bootx_dt_strbase; 36 static unsigned long __initdata bootx_dt_strend; 37 static unsigned long __initdata bootx_node_chosen; 38 static boot_infos_t * __initdata bootx_info; 39 static char __initdata bootx_disp_path[256]; 40 41 /* Is boot-info compatible ? */ 42 #define BOOT_INFO_IS_COMPATIBLE(bi) \ 43 ((bi)->compatible_version <= BOOT_INFO_VERSION) 44 #define BOOT_INFO_IS_V2_COMPATIBLE(bi) ((bi)->version >= 2) 45 #define BOOT_INFO_IS_V4_COMPATIBLE(bi) ((bi)->version >= 4) 46 47 #ifdef CONFIG_BOOTX_TEXT 48 static void __init bootx_printf(const char *format, ...) 49 { 50 const char *p, *q, *s; 51 va_list args; 52 unsigned long v; 53 54 va_start(args, format); 55 for (p = format; *p != 0; p = q) { 56 for (q = p; *q != 0 && *q != '\n' && *q != '%'; ++q) 57 ; 58 if (q > p) 59 btext_drawtext(p, q - p); 60 if (*q == 0) 61 break; 62 if (*q == '\n') { 63 ++q; 64 btext_flushline(); 65 btext_drawstring("\r\n"); 66 btext_flushline(); 67 continue; 68 } 69 ++q; 70 if (*q == 0) 71 break; 72 switch (*q) { 73 case 's': 74 ++q; 75 s = va_arg(args, const char *); 76 if (s == NULL) 77 s = "<NULL>"; 78 btext_drawstring(s); 79 break; 80 case 'x': 81 ++q; 82 v = va_arg(args, unsigned long); 83 btext_drawhex(v); 84 break; 85 } 86 } 87 va_end(args); 88 } 89 #else /* CONFIG_BOOTX_TEXT */ 90 static void __init bootx_printf(const char *format, ...) {} 91 #endif /* CONFIG_BOOTX_TEXT */ 92 93 static void * __init bootx_early_getprop(unsigned long base, 94 unsigned long node, 95 char *prop) 96 { 97 struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node); 98 u32 *ppp = &np->properties; 99 100 while(*ppp) { 101 struct bootx_dt_prop *pp = 102 (struct bootx_dt_prop *)(base + *ppp); 103 104 if (strcmp((char *)((unsigned long)pp->name + base), 105 prop) == 0) { 106 return (void *)((unsigned long)pp->value + base); 107 } 108 ppp = &pp->next; 109 } 110 return NULL; 111 } 112 113 #define dt_push_token(token, mem) \ 114 do { \ 115 *(mem) = _ALIGN_UP(*(mem),4); \ 116 *((u32 *)*(mem)) = token; \ 117 *(mem) += 4; \ 118 } while(0) 119 120 static unsigned long __init bootx_dt_find_string(char *str) 121 { 122 char *s, *os; 123 124 s = os = (char *)bootx_dt_strbase; 125 s += 4; 126 while (s < (char *)bootx_dt_strend) { 127 if (strcmp(s, str) == 0) 128 return s - os; 129 s += strlen(s) + 1; 130 } 131 return 0; 132 } 133 134 static void __init bootx_dt_add_prop(char *name, void *data, int size, 135 unsigned long *mem_end) 136 { 137 unsigned long soff = bootx_dt_find_string(name); 138 if (data == NULL) 139 size = 0; 140 if (soff == 0) { 141 bootx_printf("WARNING: Can't find string index for <%s>\n", 142 name); 143 return; 144 } 145 if (size > 0x20000) { 146 bootx_printf("WARNING: ignoring large property "); 147 bootx_printf("%s length 0x%x\n", name, size); 148 return; 149 } 150 dt_push_token(OF_DT_PROP, mem_end); 151 dt_push_token(size, mem_end); 152 dt_push_token(soff, mem_end); 153 154 /* push property content */ 155 if (size && data) { 156 memcpy((void *)*mem_end, data, size); 157 *mem_end = _ALIGN_UP(*mem_end + size, 4); 158 } 159 } 160 161 static void __init bootx_add_chosen_props(unsigned long base, 162 unsigned long *mem_end) 163 { 164 u32 val; 165 166 bootx_dt_add_prop("linux,bootx", NULL, 0, mem_end); 167 168 if (bootx_info->kernelParamsOffset) { 169 char *args = (char *)((unsigned long)bootx_info) + 170 bootx_info->kernelParamsOffset; 171 bootx_dt_add_prop("bootargs", args, strlen(args) + 1, mem_end); 172 } 173 if (bootx_info->ramDisk) { 174 val = ((unsigned long)bootx_info) + bootx_info->ramDisk; 175 bootx_dt_add_prop("linux,initrd-start", &val, 4, mem_end); 176 val += bootx_info->ramDiskSize; 177 bootx_dt_add_prop("linux,initrd-end", &val, 4, mem_end); 178 } 179 if (strlen(bootx_disp_path)) 180 bootx_dt_add_prop("linux,stdout-path", bootx_disp_path, 181 strlen(bootx_disp_path) + 1, mem_end); 182 } 183 184 static void __init bootx_add_display_props(unsigned long base, 185 unsigned long *mem_end, 186 int has_real_node) 187 { 188 boot_infos_t *bi = bootx_info; 189 u32 tmp; 190 191 if (has_real_node) { 192 bootx_dt_add_prop("linux,boot-display", NULL, 0, mem_end); 193 bootx_dt_add_prop("linux,opened", NULL, 0, mem_end); 194 } else 195 bootx_dt_add_prop("linux,bootx-noscreen", NULL, 0, mem_end); 196 197 tmp = bi->dispDeviceDepth; 198 bootx_dt_add_prop("linux,bootx-depth", &tmp, 4, mem_end); 199 tmp = bi->dispDeviceRect[2] - bi->dispDeviceRect[0]; 200 bootx_dt_add_prop("linux,bootx-width", &tmp, 4, mem_end); 201 tmp = bi->dispDeviceRect[3] - bi->dispDeviceRect[1]; 202 bootx_dt_add_prop("linux,bootx-height", &tmp, 4, mem_end); 203 tmp = bi->dispDeviceRowBytes; 204 bootx_dt_add_prop("linux,bootx-linebytes", &tmp, 4, mem_end); 205 tmp = (u32)bi->dispDeviceBase; 206 if (tmp == 0) 207 tmp = (u32)bi->logicalDisplayBase; 208 tmp += bi->dispDeviceRect[1] * bi->dispDeviceRowBytes; 209 tmp += bi->dispDeviceRect[0] * ((bi->dispDeviceDepth + 7) / 8); 210 bootx_dt_add_prop("linux,bootx-addr", &tmp, 4, mem_end); 211 } 212 213 static void __init bootx_dt_add_string(char *s, unsigned long *mem_end) 214 { 215 unsigned int l = strlen(s) + 1; 216 memcpy((void *)*mem_end, s, l); 217 bootx_dt_strend = *mem_end = *mem_end + l; 218 } 219 220 static void __init bootx_scan_dt_build_strings(unsigned long base, 221 unsigned long node, 222 unsigned long *mem_end) 223 { 224 struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node); 225 u32 *cpp, *ppp = &np->properties; 226 unsigned long soff; 227 char *namep; 228 229 /* Keep refs to known nodes */ 230 namep = np->full_name ? (char *)(base + np->full_name) : NULL; 231 if (namep == NULL) { 232 bootx_printf("Node without a full name !\n"); 233 namep = ""; 234 } 235 DBG("* strings: %s\n", namep); 236 237 if (!strcmp(namep, "/chosen")) { 238 DBG(" detected /chosen ! adding properties names !\n"); 239 bootx_dt_add_string("linux,bootx", mem_end); 240 bootx_dt_add_string("linux,stdout-path", mem_end); 241 bootx_dt_add_string("linux,initrd-start", mem_end); 242 bootx_dt_add_string("linux,initrd-end", mem_end); 243 bootx_dt_add_string("bootargs", mem_end); 244 bootx_node_chosen = node; 245 } 246 if (node == bootx_info->dispDeviceRegEntryOffset) { 247 DBG(" detected display ! adding properties names !\n"); 248 bootx_dt_add_string("linux,boot-display", mem_end); 249 bootx_dt_add_string("linux,opened", mem_end); 250 strlcpy(bootx_disp_path, namep, sizeof(bootx_disp_path)); 251 } 252 253 /* get and store all property names */ 254 while (*ppp) { 255 struct bootx_dt_prop *pp = 256 (struct bootx_dt_prop *)(base + *ppp); 257 258 namep = pp->name ? (char *)(base + pp->name) : NULL; 259 if (namep == NULL || strcmp(namep, "name") == 0) 260 goto next; 261 /* get/create string entry */ 262 soff = bootx_dt_find_string(namep); 263 if (soff == 0) 264 bootx_dt_add_string(namep, mem_end); 265 next: 266 ppp = &pp->next; 267 } 268 269 /* do all our children */ 270 cpp = &np->child; 271 while(*cpp) { 272 np = (struct bootx_dt_node *)(base + *cpp); 273 bootx_scan_dt_build_strings(base, *cpp, mem_end); 274 cpp = &np->sibling; 275 } 276 } 277 278 static void __init bootx_scan_dt_build_struct(unsigned long base, 279 unsigned long node, 280 unsigned long *mem_end) 281 { 282 struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node); 283 u32 *cpp, *ppp = &np->properties; 284 char *namep, *p, *ep, *lp; 285 int l; 286 287 dt_push_token(OF_DT_BEGIN_NODE, mem_end); 288 289 /* get the node's full name */ 290 namep = np->full_name ? (char *)(base + np->full_name) : NULL; 291 if (namep == NULL) 292 namep = ""; 293 l = strlen(namep); 294 295 DBG("* struct: %s\n", namep); 296 297 /* Fixup an Apple bug where they have bogus \0 chars in the 298 * middle of the path in some properties, and extract 299 * the unit name (everything after the last '/'). 300 */ 301 memcpy((void *)*mem_end, namep, l + 1); 302 namep = (char *)*mem_end; 303 for (lp = p = namep, ep = namep + l; p < ep; p++) { 304 if (*p == '/') 305 lp = namep; 306 else if (*p != 0) 307 *lp++ = *p; 308 } 309 *lp = 0; 310 *mem_end = _ALIGN_UP((unsigned long)lp + 1, 4); 311 312 /* get and store all properties */ 313 while (*ppp) { 314 struct bootx_dt_prop *pp = 315 (struct bootx_dt_prop *)(base + *ppp); 316 317 namep = pp->name ? (char *)(base + pp->name) : NULL; 318 /* Skip "name" */ 319 if (namep == NULL || !strcmp(namep, "name")) 320 goto next; 321 /* Skip "bootargs" in /chosen too as we replace it */ 322 if (node == bootx_node_chosen && !strcmp(namep, "bootargs")) 323 goto next; 324 325 /* push property head */ 326 bootx_dt_add_prop(namep, 327 pp->value ? (void *)(base + pp->value): NULL, 328 pp->length, mem_end); 329 next: 330 ppp = &pp->next; 331 } 332 333 if (node == bootx_node_chosen) { 334 bootx_add_chosen_props(base, mem_end); 335 if (bootx_info->dispDeviceRegEntryOffset == 0) 336 bootx_add_display_props(base, mem_end, 0); 337 } 338 else if (node == bootx_info->dispDeviceRegEntryOffset) 339 bootx_add_display_props(base, mem_end, 1); 340 341 /* do all our children */ 342 cpp = &np->child; 343 while(*cpp) { 344 np = (struct bootx_dt_node *)(base + *cpp); 345 bootx_scan_dt_build_struct(base, *cpp, mem_end); 346 cpp = &np->sibling; 347 } 348 349 dt_push_token(OF_DT_END_NODE, mem_end); 350 } 351 352 static unsigned long __init bootx_flatten_dt(unsigned long start) 353 { 354 boot_infos_t *bi = bootx_info; 355 unsigned long mem_start, mem_end; 356 struct boot_param_header *hdr; 357 unsigned long base; 358 u64 *rsvmap; 359 360 /* Start using memory after the big blob passed by BootX, get 361 * some space for the header 362 */ 363 mem_start = mem_end = _ALIGN_UP(((unsigned long)bi) + start, 4); 364 DBG("Boot params header at: %x\n", mem_start); 365 hdr = (struct boot_param_header *)mem_start; 366 mem_end += sizeof(struct boot_param_header); 367 rsvmap = (u64 *)(_ALIGN_UP(mem_end, 8)); 368 hdr->off_mem_rsvmap = ((unsigned long)rsvmap) - mem_start; 369 mem_end = ((unsigned long)rsvmap) + 8 * sizeof(u64); 370 371 /* Get base of tree */ 372 base = ((unsigned long)bi) + bi->deviceTreeOffset; 373 374 /* Build string array */ 375 DBG("Building string array at: %x\n", mem_end); 376 DBG("Device Tree Base=%x\n", base); 377 bootx_dt_strbase = mem_end; 378 mem_end += 4; 379 bootx_dt_strend = mem_end; 380 bootx_scan_dt_build_strings(base, 4, &mem_end); 381 /* Add some strings */ 382 bootx_dt_add_string("linux,bootx-noscreen", &mem_end); 383 bootx_dt_add_string("linux,bootx-depth", &mem_end); 384 bootx_dt_add_string("linux,bootx-width", &mem_end); 385 bootx_dt_add_string("linux,bootx-height", &mem_end); 386 bootx_dt_add_string("linux,bootx-linebytes", &mem_end); 387 bootx_dt_add_string("linux,bootx-addr", &mem_end); 388 /* Wrap up strings */ 389 hdr->off_dt_strings = bootx_dt_strbase - mem_start; 390 hdr->dt_strings_size = bootx_dt_strend - bootx_dt_strbase; 391 392 /* Build structure */ 393 mem_end = _ALIGN(mem_end, 16); 394 DBG("Building device tree structure at: %x\n", mem_end); 395 hdr->off_dt_struct = mem_end - mem_start; 396 bootx_scan_dt_build_struct(base, 4, &mem_end); 397 dt_push_token(OF_DT_END, &mem_end); 398 399 /* Finish header */ 400 hdr->boot_cpuid_phys = 0; 401 hdr->magic = OF_DT_HEADER; 402 hdr->totalsize = mem_end - mem_start; 403 hdr->version = OF_DT_VERSION; 404 /* Version 16 is not backward compatible */ 405 hdr->last_comp_version = 0x10; 406 407 /* Reserve the whole thing and copy the reserve map in, we 408 * also bump mem_reserve_cnt to cause further reservations to 409 * fail since it's too late. 410 */ 411 mem_end = _ALIGN(mem_end, PAGE_SIZE); 412 DBG("End of boot params: %x\n", mem_end); 413 rsvmap[0] = mem_start; 414 rsvmap[1] = mem_end; 415 if (bootx_info->ramDisk) { 416 rsvmap[2] = ((unsigned long)bootx_info) + bootx_info->ramDisk; 417 rsvmap[3] = rsvmap[2] + bootx_info->ramDiskSize; 418 rsvmap[4] = 0; 419 rsvmap[5] = 0; 420 } else { 421 rsvmap[2] = 0; 422 rsvmap[3] = 0; 423 } 424 425 return (unsigned long)hdr; 426 } 427 428 429 #ifdef CONFIG_BOOTX_TEXT 430 static void __init btext_welcome(boot_infos_t *bi) 431 { 432 unsigned long flags; 433 unsigned long pvr; 434 435 bootx_printf("Welcome to Linux, kernel " UTS_RELEASE "\n"); 436 bootx_printf("\nlinked at : 0x%x", KERNELBASE); 437 bootx_printf("\nframe buffer at : 0x%x", bi->dispDeviceBase); 438 bootx_printf(" (phys), 0x%x", bi->logicalDisplayBase); 439 bootx_printf(" (log)"); 440 bootx_printf("\nklimit : 0x%x",(unsigned long)klimit); 441 bootx_printf("\nboot_info at : 0x%x", bi); 442 __asm__ __volatile__ ("mfmsr %0" : "=r" (flags)); 443 bootx_printf("\nMSR : 0x%x", flags); 444 __asm__ __volatile__ ("mfspr %0, 287" : "=r" (pvr)); 445 bootx_printf("\nPVR : 0x%x", pvr); 446 pvr >>= 16; 447 if (pvr > 1) { 448 __asm__ __volatile__ ("mfspr %0, 1008" : "=r" (flags)); 449 bootx_printf("\nHID0 : 0x%x", flags); 450 } 451 if (pvr == 8 || pvr == 12 || pvr == 0x800c) { 452 __asm__ __volatile__ ("mfspr %0, 1019" : "=r" (flags)); 453 bootx_printf("\nICTC : 0x%x", flags); 454 } 455 #ifdef DEBUG 456 bootx_printf("\n\n"); 457 bootx_printf("bi->deviceTreeOffset : 0x%x\n", 458 bi->deviceTreeOffset); 459 bootx_printf("bi->deviceTreeSize : 0x%x\n", 460 bi->deviceTreeSize); 461 #endif 462 bootx_printf("\n\n"); 463 } 464 #endif /* CONFIG_BOOTX_TEXT */ 465 466 void __init bootx_init(unsigned long r3, unsigned long r4) 467 { 468 boot_infos_t *bi = (boot_infos_t *) r4; 469 unsigned long hdr; 470 unsigned long space; 471 unsigned long ptr, x; 472 char *model; 473 unsigned long offset = reloc_offset(); 474 475 reloc_got2(offset); 476 477 bootx_info = bi; 478 479 /* We haven't cleared any bss at this point, make sure 480 * what we need is initialized 481 */ 482 bootx_dt_strbase = bootx_dt_strend = 0; 483 bootx_node_chosen = 0; 484 bootx_disp_path[0] = 0; 485 486 if (!BOOT_INFO_IS_V2_COMPATIBLE(bi)) 487 bi->logicalDisplayBase = bi->dispDeviceBase; 488 489 /* Fixup depth 16 -> 15 as that's what MacOS calls 16bpp */ 490 if (bi->dispDeviceDepth == 16) 491 bi->dispDeviceDepth = 15; 492 493 494 #ifdef CONFIG_BOOTX_TEXT 495 ptr = (unsigned long)bi->logicalDisplayBase; 496 ptr += bi->dispDeviceRect[1] * bi->dispDeviceRowBytes; 497 ptr += bi->dispDeviceRect[0] * ((bi->dispDeviceDepth + 7) / 8); 498 btext_setup_display(bi->dispDeviceRect[2] - bi->dispDeviceRect[0], 499 bi->dispDeviceRect[3] - bi->dispDeviceRect[1], 500 bi->dispDeviceDepth, bi->dispDeviceRowBytes, 501 (unsigned long)bi->logicalDisplayBase); 502 btext_clearscreen(); 503 btext_flushscreen(); 504 #endif /* CONFIG_BOOTX_TEXT */ 505 506 /* 507 * Test if boot-info is compatible. Done only in config 508 * CONFIG_BOOTX_TEXT since there is nothing much we can do 509 * with an incompatible version, except display a message 510 * and eventually hang the processor... 511 * 512 * I'll try to keep enough of boot-info compatible in the 513 * future to always allow display of this message; 514 */ 515 if (!BOOT_INFO_IS_COMPATIBLE(bi)) { 516 bootx_printf(" !!! WARNING - Incompatible version" 517 " of BootX !!!\n\n\n"); 518 for (;;) 519 ; 520 } 521 if (bi->architecture != BOOT_ARCH_PCI) { 522 bootx_printf(" !!! WARNING - Usupported machine" 523 " architecture !\n"); 524 for (;;) 525 ; 526 } 527 528 #ifdef CONFIG_BOOTX_TEXT 529 btext_welcome(bi); 530 #endif 531 532 /* New BootX enters kernel with MMU off, i/os are not allowed 533 * here. This hack will have been done by the boostrap anyway. 534 */ 535 if (bi->version < 4) { 536 /* 537 * XXX If this is an iMac, turn off the USB controller. 538 */ 539 model = (char *) bootx_early_getprop(r4 + bi->deviceTreeOffset, 540 4, "model"); 541 if (model 542 && (strcmp(model, "iMac,1") == 0 543 || strcmp(model, "PowerMac1,1") == 0)) { 544 bootx_printf("iMac,1 detected, shutting down USB\n"); 545 out_le32((unsigned __iomem *)0x80880008, 1); /* XXX */ 546 } 547 } 548 549 /* Get a pointer that points above the device tree, args, ramdisk, 550 * etc... to use for generating the flattened tree 551 */ 552 if (bi->version < 5) { 553 space = bi->deviceTreeOffset + bi->deviceTreeSize; 554 if (bi->ramDisk >= space) 555 space = bi->ramDisk + bi->ramDiskSize; 556 } else 557 space = bi->totalParamsSize; 558 559 bootx_printf("Total space used by parameters & ramdisk: 0x%x\n", space); 560 561 /* New BootX will have flushed all TLBs and enters kernel with 562 * MMU switched OFF, so this should not be useful anymore. 563 */ 564 if (bi->version < 4) { 565 bootx_printf("Touching pages...\n"); 566 567 /* 568 * Touch each page to make sure the PTEs for them 569 * are in the hash table - the aim is to try to avoid 570 * getting DSI exceptions while copying the kernel image. 571 */ 572 for (ptr = ((unsigned long) &_stext) & PAGE_MASK; 573 ptr < (unsigned long)bi + space; ptr += PAGE_SIZE) 574 x = *(volatile unsigned long *)ptr; 575 } 576 577 /* Ok, now we need to generate a flattened device-tree to pass 578 * to the kernel 579 */ 580 bootx_printf("Preparing boot params...\n"); 581 582 hdr = bootx_flatten_dt(space); 583 584 #ifdef CONFIG_BOOTX_TEXT 585 #ifdef SET_BOOT_BAT 586 bootx_printf("Preparing BAT...\n"); 587 btext_prepare_BAT(); 588 #else 589 btext_unmap(); 590 #endif 591 #endif 592 593 reloc_got2(-offset); 594 595 __start(hdr, KERNELBASE + offset, 0); 596 } 597