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