This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / arch / powerpc / platforms / powermac / bootx_init.c
diff --git a/arch/powerpc/platforms/powermac/bootx_init.c b/arch/powerpc/platforms/powermac/bootx_init.c
new file mode 100644 (file)
index 0000000..eacbfd9
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ *  Early boot support code for BootX bootloader
+ *
+ *  Copyright (C) 2005 Ben. Herrenschmidt (benh@kernel.crashing.org)
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation; either version
+ *  2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <asm/sections.h>
+#include <asm/prom.h>
+#include <asm/page.h>
+#include <asm/bootx.h>
+#include <asm/bootinfo.h>
+#include <asm/btext.h>
+#include <asm/io.h>
+
+#undef DEBUG
+#define SET_BOOT_BAT
+
+#ifdef DEBUG
+#define DBG(fmt...) do { bootx_printf(fmt); } while(0)
+#else
+#define DBG(fmt...) do { } while(0)
+#endif
+
+extern void __start(unsigned long r3, unsigned long r4, unsigned long r5);
+
+static unsigned long __initdata bootx_dt_strbase;
+static unsigned long __initdata bootx_dt_strend;
+static unsigned long __initdata bootx_node_chosen;
+static boot_infos_t * __initdata bootx_info;
+static char __initdata bootx_disp_path[256];
+
+/* Is boot-info compatible ? */
+#define BOOT_INFO_IS_COMPATIBLE(bi) \
+       ((bi)->compatible_version <= BOOT_INFO_VERSION)
+#define BOOT_INFO_IS_V2_COMPATIBLE(bi) ((bi)->version >= 2)
+#define BOOT_INFO_IS_V4_COMPATIBLE(bi) ((bi)->version >= 4)
+
+#ifdef CONFIG_BOOTX_TEXT
+static void __init bootx_printf(const char *format, ...)
+{
+       const char *p, *q, *s;
+       va_list args;
+       unsigned long v;
+
+       va_start(args, format);
+       for (p = format; *p != 0; p = q) {
+               for (q = p; *q != 0 && *q != '\n' && *q != '%'; ++q)
+                       ;
+               if (q > p)
+                       btext_drawtext(p, q - p);
+               if (*q == 0)
+                       break;
+               if (*q == '\n') {
+                       ++q;
+                       btext_flushline();
+                       btext_drawstring("\r\n");
+                       btext_flushline();
+                       continue;
+               }
+               ++q;
+               if (*q == 0)
+                       break;
+               switch (*q) {
+               case 's':
+                       ++q;
+                       s = va_arg(args, const char *);
+                       if (s == NULL)
+                               s = "<NULL>";
+                       btext_drawstring(s);
+                       break;
+               case 'x':
+                       ++q;
+                       v = va_arg(args, unsigned long);
+                       btext_drawhex(v);
+                       break;
+               }
+       }
+}
+#else /* CONFIG_BOOTX_TEXT */
+static void __init bootx_printf(const char *format, ...) {}
+#endif /* CONFIG_BOOTX_TEXT */
+
+static void * __init bootx_early_getprop(unsigned long base,
+                                        unsigned long node,
+                                        char *prop)
+{
+       struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node);
+       u32 *ppp = &np->properties;
+
+       while(*ppp) {
+               struct bootx_dt_prop *pp =
+                       (struct bootx_dt_prop *)(base + *ppp);
+
+               if (strcmp((char *)((unsigned long)pp->name + base),
+                          prop) == 0) {
+                       return (void *)((unsigned long)pp->value + base);
+               }
+               ppp = &pp->next;
+       }
+       return NULL;
+}
+
+#define dt_push_token(token, mem) \
+       do { \
+               *(mem) = _ALIGN_UP(*(mem),4); \
+               *((u32 *)*(mem)) = token; \
+               *(mem) += 4; \
+       } while(0)
+
+static unsigned long __init bootx_dt_find_string(char *str)
+{
+       char *s, *os;
+
+       s = os = (char *)bootx_dt_strbase;
+       s += 4;
+       while (s <  (char *)bootx_dt_strend) {
+               if (strcmp(s, str) == 0)
+                       return s - os;
+               s += strlen(s) + 1;
+       }
+       return 0;
+}
+
+static void __init bootx_dt_add_prop(char *name, void *data, int size,
+                                 unsigned long *mem_end)
+{
+       unsigned long soff = bootx_dt_find_string(name);
+       if (data == NULL)
+               size = 0;
+       if (soff == 0) {
+               bootx_printf("WARNING: Can't find string index for <%s>\n",
+                            name);
+               return;
+       }
+       if (size > 0x20000) {
+               bootx_printf("WARNING: ignoring large property ");
+               bootx_printf("%s length 0x%x\n", name, size);
+               return;
+       }
+       dt_push_token(OF_DT_PROP, mem_end);
+       dt_push_token(size, mem_end);
+       dt_push_token(soff, mem_end);
+
+       /* push property content */
+       if (size && data) {
+               memcpy((void *)*mem_end, data, size);
+               *mem_end = _ALIGN_UP(*mem_end + size, 4);
+       }
+}
+
+static void __init bootx_add_chosen_props(unsigned long base,
+                                         unsigned long *mem_end)
+{
+       u32 val;
+
+       if (bootx_info->kernelParamsOffset) {
+               char *args = (char *)((unsigned long)bootx_info) +
+                       bootx_info->kernelParamsOffset;
+               bootx_dt_add_prop("bootargs", args, strlen(args) + 1, mem_end);
+       }
+       if (bootx_info->ramDisk) {
+               val = ((unsigned long)bootx_info) + bootx_info->ramDisk;
+               bootx_dt_add_prop("linux,initrd-start", &val, 4, mem_end);
+               val += bootx_info->ramDiskSize;
+               bootx_dt_add_prop("linux,initrd-end", &val, 4, mem_end);
+       }
+       if (strlen(bootx_disp_path))
+               bootx_dt_add_prop("linux,stdout-path", bootx_disp_path,
+                                 strlen(bootx_disp_path) + 1, mem_end);
+}
+
+static void __init bootx_add_display_props(unsigned long base,
+                                          unsigned long *mem_end)
+{
+       bootx_dt_add_prop("linux,boot-display", NULL, 0, mem_end);
+       bootx_dt_add_prop("linux,opened", NULL, 0, mem_end);
+}
+
+static void __init bootx_dt_add_string(char *s, unsigned long *mem_end)
+{
+       unsigned int l = strlen(s) + 1;
+       memcpy((void *)*mem_end, s, l);
+       bootx_dt_strend = *mem_end = *mem_end + l;
+}
+
+static void __init bootx_scan_dt_build_strings(unsigned long base,
+                                              unsigned long node,
+                                              unsigned long *mem_end)
+{
+       struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node);
+       u32 *cpp, *ppp = &np->properties;
+       unsigned long soff;
+       char *namep;
+
+       /* Keep refs to known nodes */
+       namep = np->full_name ? (char *)(base + np->full_name) : NULL;
+               if (namep == NULL) {
+               bootx_printf("Node without a full name !\n");
+               namep = "";
+       }
+       DBG("* strings: %s\n", namep);
+
+       if (!strcmp(namep, "/chosen")) {
+               DBG(" detected /chosen ! adding properties names !\n");
+               bootx_dt_add_string("linux,platform", mem_end);
+               bootx_dt_add_string("linux,stdout-path", mem_end);
+               bootx_dt_add_string("linux,initrd-start", mem_end);
+               bootx_dt_add_string("linux,initrd-end", mem_end);
+               bootx_dt_add_string("bootargs", mem_end);
+               bootx_node_chosen = node;
+       }
+       if (node == bootx_info->dispDeviceRegEntryOffset) {
+               DBG(" detected display ! adding properties names !\n");
+               bootx_dt_add_string("linux,boot-display", mem_end);
+               bootx_dt_add_string("linux,opened", mem_end);
+               strncpy(bootx_disp_path, namep, 255);
+       }
+
+       /* get and store all property names */
+       while (*ppp) {
+               struct bootx_dt_prop *pp =
+                       (struct bootx_dt_prop *)(base + *ppp);
+
+               namep = pp->name ? (char *)(base + pp->name) : NULL;
+               if (namep == NULL || strcmp(namep, "name") == 0)
+                       goto next;
+               /* get/create string entry */
+               soff = bootx_dt_find_string(namep);
+               if (soff == 0)
+                       bootx_dt_add_string(namep, mem_end);
+       next:
+               ppp = &pp->next;
+       }
+
+       /* do all our children */
+       cpp = &np->child;
+       while(*cpp) {
+               np = (struct bootx_dt_node *)(base + *cpp);
+               bootx_scan_dt_build_strings(base, *cpp, mem_end);
+               cpp = &np->sibling;
+       }
+}
+
+static void __init bootx_scan_dt_build_struct(unsigned long base,
+                                             unsigned long node,
+                                             unsigned long *mem_end)
+{
+       struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node);
+       u32 *cpp, *ppp = &np->properties;
+       char *namep, *p, *ep, *lp;
+       int l;
+
+       dt_push_token(OF_DT_BEGIN_NODE, mem_end);
+
+       /* get the node's full name */
+       namep = np->full_name ? (char *)(base + np->full_name) : NULL;
+       if (namep == NULL)
+               namep = "";
+       l = strlen(namep);
+
+       DBG("* struct: %s\n", namep);
+
+       /* Fixup an Apple bug where they have bogus \0 chars in the
+        * middle of the path in some properties, and extract
+        * the unit name (everything after the last '/').
+        */
+       memcpy((void *)*mem_end, namep, l + 1);
+       namep = (char *)*mem_end;
+       for (lp = p = namep, ep = namep + l; p < ep; p++) {
+               if (*p == '/')
+                       lp = namep;
+               else if (*p != 0)
+                       *lp++ = *p;
+       }
+       *lp = 0;
+       *mem_end = _ALIGN_UP((unsigned long)lp + 1, 4);
+
+       /* get and store all properties */
+       while (*ppp) {
+               struct bootx_dt_prop *pp =
+                       (struct bootx_dt_prop *)(base + *ppp);
+
+               namep = pp->name ? (char *)(base + pp->name) : NULL;
+               /* Skip "name" */
+               if (namep == NULL || !strcmp(namep, "name"))
+                       goto next;
+               /* Skip "bootargs" in /chosen too as we replace it */
+               if (node == bootx_node_chosen && !strcmp(namep, "bootargs"))
+                       goto next;
+
+               /* push property head */
+               bootx_dt_add_prop(namep,
+                                 pp->value ? (void *)(base + pp->value): NULL,
+                                 pp->length, mem_end);
+       next:
+               ppp = &pp->next;
+       }
+
+       if (node == bootx_node_chosen)
+               bootx_add_chosen_props(base, mem_end);
+       if (node == bootx_info->dispDeviceRegEntryOffset)
+               bootx_add_display_props(base, mem_end);
+
+       /* do all our children */
+       cpp = &np->child;
+       while(*cpp) {
+               np = (struct bootx_dt_node *)(base + *cpp);
+               bootx_scan_dt_build_struct(base, *cpp, mem_end);
+               cpp = &np->sibling;
+       }
+
+       dt_push_token(OF_DT_END_NODE, mem_end);
+}
+
+static unsigned long __init bootx_flatten_dt(unsigned long start)
+{
+       boot_infos_t *bi = bootx_info;
+       unsigned long mem_start, mem_end;
+       struct boot_param_header *hdr;
+       unsigned long base;
+       u64 *rsvmap;
+
+       /* Start using memory after the big blob passed by BootX, get
+        * some space for the header
+        */
+       mem_start = mem_end = _ALIGN_UP(((unsigned long)bi) + start, 4);
+       DBG("Boot params header at: %x\n", mem_start);
+       hdr = (struct boot_param_header *)mem_start;
+       mem_end += sizeof(struct boot_param_header);
+       rsvmap = (u64 *)(_ALIGN_UP(mem_end, 8));
+       hdr->off_mem_rsvmap = ((unsigned long)rsvmap) - mem_start;
+       mem_end = ((unsigned long)rsvmap) + 8 * sizeof(u64);
+
+       /* Get base of tree */
+       base = ((unsigned long)bi) + bi->deviceTreeOffset;
+
+       /* Build string array */
+       DBG("Building string array at: %x\n", mem_end);
+       DBG("Device Tree Base=%x\n", base);
+       bootx_dt_strbase = mem_end;
+       mem_end += 4;
+       bootx_dt_strend = mem_end;
+       bootx_scan_dt_build_strings(base, 4, &mem_end);
+       hdr->off_dt_strings = bootx_dt_strbase - mem_start;
+       hdr->dt_strings_size = bootx_dt_strend - bootx_dt_strbase;
+
+       /* Build structure */
+       mem_end = _ALIGN(mem_end, 16);
+       DBG("Building device tree structure at: %x\n", mem_end);
+       hdr->off_dt_struct = mem_end - mem_start;
+       bootx_scan_dt_build_struct(base, 4, &mem_end);
+       dt_push_token(OF_DT_END, &mem_end);
+
+       /* Finish header */
+       hdr->boot_cpuid_phys = 0;
+       hdr->magic = OF_DT_HEADER;
+       hdr->totalsize = mem_end - mem_start;
+       hdr->version = OF_DT_VERSION;
+       /* Version 16 is not backward compatible */
+       hdr->last_comp_version = 0x10;
+
+       /* Reserve the whole thing and copy the reserve map in, we
+        * also bump mem_reserve_cnt to cause further reservations to
+        * fail since it's too late.
+        */
+       mem_end = _ALIGN(mem_end, PAGE_SIZE);
+       DBG("End of boot params: %x\n", mem_end);
+       rsvmap[0] = mem_start;
+       rsvmap[1] = mem_end;
+       rsvmap[2] = 0;
+       rsvmap[3] = 0;
+
+       return (unsigned long)hdr;
+}
+
+
+#ifdef CONFIG_BOOTX_TEXT
+static void __init btext_welcome(boot_infos_t *bi)
+{
+       unsigned long flags;
+       unsigned long pvr;
+
+       bootx_printf("Welcome to Linux, kernel " UTS_RELEASE "\n");
+       bootx_printf("\nlinked at        : 0x%x", KERNELBASE);
+       bootx_printf("\nframe buffer at  : 0x%x", bi->dispDeviceBase);
+       bootx_printf(" (phys), 0x%x", bi->logicalDisplayBase);
+       bootx_printf(" (log)");
+       bootx_printf("\nklimit           : 0x%x",(unsigned long)klimit);
+       bootx_printf("\nboot_info at     : 0x%x", bi);
+       __asm__ __volatile__ ("mfmsr %0" : "=r" (flags));
+       bootx_printf("\nMSR              : 0x%x", flags);
+       __asm__ __volatile__ ("mfspr %0, 287" : "=r" (pvr));
+       bootx_printf("\nPVR              : 0x%x", pvr);
+       pvr >>= 16;
+       if (pvr > 1) {
+           __asm__ __volatile__ ("mfspr %0, 1008" : "=r" (flags));
+           bootx_printf("\nHID0             : 0x%x", flags);
+       }
+       if (pvr == 8 || pvr == 12 || pvr == 0x800c) {
+           __asm__ __volatile__ ("mfspr %0, 1019" : "=r" (flags));
+           bootx_printf("\nICTC             : 0x%x", flags);
+       }
+#ifdef DEBUG
+       bootx_printf("\n\n");
+       bootx_printf("bi->deviceTreeOffset   : 0x%x\n",
+                    bi->deviceTreeOffset);
+       bootx_printf("bi->deviceTreeSize     : 0x%x\n",
+                    bi->deviceTreeSize);
+#endif
+       bootx_printf("\n\n");
+}
+#endif /* CONFIG_BOOTX_TEXT */
+
+void __init bootx_init(unsigned long r3, unsigned long r4)
+{
+       boot_infos_t *bi = (boot_infos_t *) r4;
+       unsigned long hdr;
+       unsigned long space;
+       unsigned long ptr, x;
+       char *model;
+       unsigned long offset = reloc_offset();
+
+       reloc_got2(offset);
+
+       bootx_info = bi;
+
+       /* We haven't cleared any bss at this point, make sure
+        * what we need is initialized
+        */
+       bootx_dt_strbase = bootx_dt_strend = 0;
+       bootx_node_chosen = 0;
+       bootx_disp_path[0] = 0;
+
+       if (!BOOT_INFO_IS_V2_COMPATIBLE(bi))
+               bi->logicalDisplayBase = bi->dispDeviceBase;
+
+#ifdef CONFIG_BOOTX_TEXT
+       btext_setup_display(bi->dispDeviceRect[2] - bi->dispDeviceRect[0],
+                           bi->dispDeviceRect[3] - bi->dispDeviceRect[1],
+                           bi->dispDeviceDepth, bi->dispDeviceRowBytes,
+                           (unsigned long)bi->logicalDisplayBase);
+       btext_clearscreen();
+       btext_flushscreen();
+#endif /* CONFIG_BOOTX_TEXT */
+
+       /*
+        * Test if boot-info is compatible.  Done only in config
+        * CONFIG_BOOTX_TEXT since there is nothing much we can do
+        * with an incompatible version, except display a message
+        * and eventually hang the processor...
+        *
+        * I'll try to keep enough of boot-info compatible in the
+        * future to always allow display of this message;
+        */
+       if (!BOOT_INFO_IS_COMPATIBLE(bi)) {
+               bootx_printf(" !!! WARNING - Incompatible version"
+                            " of BootX !!!\n\n\n");
+               for (;;)
+                       ;
+       }
+       if (bi->architecture != BOOT_ARCH_PCI) {
+               bootx_printf(" !!! WARNING - Usupported machine"
+                            " architecture !\n");
+               for (;;)
+                       ;
+       }
+
+#ifdef CONFIG_BOOTX_TEXT
+       btext_welcome(bi);
+#endif
+       /* New BootX enters kernel with MMU off, i/os are not allowed
+        * here. This hack will have been done by the boostrap anyway.
+        */
+       if (bi->version < 4) {
+               /*
+                * XXX If this is an iMac, turn off the USB controller.
+                */
+               model = (char *) bootx_early_getprop(r4 + bi->deviceTreeOffset,
+                                                    4, "model");
+               if (model
+                   && (strcmp(model, "iMac,1") == 0
+                       || strcmp(model, "PowerMac1,1") == 0)) {
+                       bootx_printf("iMac,1 detected, shutting down USB \n");
+                       out_le32((unsigned __iomem *)0x80880008, 1);    /* XXX */
+               }
+       }
+
+       /* Get a pointer that points above the device tree, args, ramdisk,
+        * etc... to use for generating the flattened tree
+        */
+       if (bi->version < 5) {
+               space = bi->deviceTreeOffset + bi->deviceTreeSize;
+               if (bi->ramDisk)
+                       space = bi->ramDisk + bi->ramDiskSize;
+       } else
+               space = bi->totalParamsSize;
+
+       bootx_printf("Total space used by parameters & ramdisk: %x \n", space);
+
+       /* New BootX will have flushed all TLBs and enters kernel with
+        * MMU switched OFF, so this should not be useful anymore.
+        */
+       if (bi->version < 4) {
+               bootx_printf("Touching pages...\n");
+
+               /*
+                * Touch each page to make sure the PTEs for them
+                * are in the hash table - the aim is to try to avoid
+                * getting DSI exceptions while copying the kernel image.
+                */
+               for (ptr = ((unsigned long) &_stext) & PAGE_MASK;
+                    ptr < (unsigned long)bi + space; ptr += PAGE_SIZE)
+                       x = *(volatile unsigned long *)ptr;
+       }
+
+       /* Ok, now we need to generate a flattened device-tree to pass
+        * to the kernel
+        */
+       bootx_printf("Preparing boot params...\n");
+
+       hdr = bootx_flatten_dt(space);
+
+#ifdef CONFIG_BOOTX_TEXT
+#ifdef SET_BOOT_BAT
+       bootx_printf("Preparing BAT...\n");
+       btext_prepare_BAT();
+#else
+       btext_unmap();
+#endif
+#endif
+
+       reloc_got2(-offset);
+
+       __start(hdr, KERNELBASE + offset, 0);
+}