| /* | |
| * Note that prom_init() and anything called from prom_init() | |
| * may be running at an address that is different from the address | |
| * that it was linked at. References to static data items are | |
| * handled by compiling this file with -mrelocatable-lib. | |
| */ | |
| #include <linux/config.h> | |
| #include <linux/kernel.h> | |
| #include <linux/string.h> | |
| #include <linux/init.h> | |
| #include <linux/version.h> | |
| #include <linux/threads.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/ioport.h> | |
| #include <linux/pci.h> | |
| #include <linux/slab.h> | |
| #include <linux/bitops.h> | |
| #include <asm/sections.h> | |
| #include <asm/prom.h> | |
| #include <asm/page.h> | |
| #include <asm/irq.h> | |
| #include <asm/io.h> | |
| #include <asm/smp.h> | |
| #include <asm/bootx.h> | |
| #include <asm/system.h> | |
| #include <asm/mmu.h> | |
| #include <asm/pgtable.h> | |
| #include <asm/bootinfo.h> | |
| #include <asm/btext.h> | |
| #include <asm/pci-bridge.h> | |
| #include <asm/open_pic.h> | |
| #include <asm/cacheflush.h> | |
| #ifdef CONFIG_LOGO_LINUX_CLUT224 | |
| #include <linux/linux_logo.h> | |
| extern const struct linux_logo logo_linux_clut224; | |
| #endif | |
| /* | |
| * Properties whose value is longer than this get excluded from our | |
| * copy of the device tree. This way we don't waste space storing | |
| * things like "driver,AAPL,MacOS,PowerPC" properties. But this value | |
| * does need to be big enough to ensure that we don't lose things | |
| * like the interrupt-map property on a PCI-PCI bridge. | |
| */ | |
| #define MAX_PROPERTY_LENGTH 4096 | |
| #ifndef FB_MAX /* avoid pulling in all of the fb stuff */ | |
| #define FB_MAX 8 | |
| #endif | |
| #define ALIGNUL(x) (((x) + sizeof(unsigned long)-1) & -sizeof(unsigned long)) | |
| typedef u32 prom_arg_t; | |
| struct prom_args { | |
| const char *service; | |
| int nargs; | |
| int nret; | |
| prom_arg_t args[10]; | |
| }; | |
| struct pci_address { | |
| unsigned a_hi; | |
| unsigned a_mid; | |
| unsigned a_lo; | |
| }; | |
| struct pci_reg_property { | |
| struct pci_address addr; | |
| unsigned size_hi; | |
| unsigned size_lo; | |
| }; | |
| struct pci_range { | |
| struct pci_address addr; | |
| unsigned phys; | |
| unsigned size_hi; | |
| unsigned size_lo; | |
| }; | |
| struct isa_reg_property { | |
| unsigned space; | |
| unsigned address; | |
| unsigned size; | |
| }; | |
| struct pci_intr_map { | |
| struct pci_address addr; | |
| unsigned dunno; | |
| phandle int_ctrler; | |
| unsigned intr; | |
| }; | |
| static void prom_exit(void); | |
| static int call_prom(const char *service, int nargs, int nret, ...); | |
| static int call_prom_ret(const char *service, int nargs, int nret, | |
| prom_arg_t *rets, ...); | |
| static void prom_print_hex(unsigned int v); | |
| static int prom_set_color(ihandle ih, int i, int r, int g, int b); | |
| static int prom_next_node(phandle *nodep); | |
| static unsigned long check_display(unsigned long mem); | |
| static void setup_disp_fake_bi(ihandle dp); | |
| static unsigned long copy_device_tree(unsigned long mem_start, | |
| unsigned long mem_end); | |
| static unsigned long inspect_node(phandle node, struct device_node *dad, | |
| unsigned long mem_start, unsigned long mem_end, | |
| struct device_node ***allnextpp); | |
| static void prom_hold_cpus(unsigned long mem); | |
| static void prom_instantiate_rtas(void); | |
| static void * early_get_property(unsigned long base, unsigned long node, | |
| char *prop); | |
| prom_entry prom __initdata; | |
| ihandle prom_chosen __initdata; | |
| ihandle prom_stdout __initdata; | |
| static char *prom_display_paths[FB_MAX] __initdata; | |
| static phandle prom_display_nodes[FB_MAX] __initdata; | |
| static unsigned int prom_num_displays __initdata; | |
| static ihandle prom_disp_node __initdata; | |
| char *of_stdout_device __initdata; | |
| unsigned int rtas_data; /* physical pointer */ | |
| unsigned int rtas_entry; /* physical pointer */ | |
| unsigned int rtas_size; | |
| unsigned int old_rtas; | |
| boot_infos_t *boot_infos; | |
| char *bootpath; | |
| char *bootdevice; | |
| struct device_node *allnodes; | |
| extern char *klimit; | |
| static void __init | |
| prom_exit(void) | |
| { | |
| struct prom_args args; | |
| args.service = "exit"; | |
| args.nargs = 0; | |
| args.nret = 0; | |
| prom(&args); | |
| for (;;) /* should never get here */ | |
| ; | |
| } | |
| static int __init | |
| call_prom(const char *service, int nargs, int nret, ...) | |
| { | |
| va_list list; | |
| int i; | |
| struct prom_args prom_args; | |
| prom_args.service = service; | |
| prom_args.nargs = nargs; | |
| prom_args.nret = nret; | |
| va_start(list, nret); | |
| for (i = 0; i < nargs; ++i) | |
| prom_args.args[i] = va_arg(list, prom_arg_t); | |
| va_end(list); | |
| for (i = 0; i < nret; ++i) | |
| prom_args.args[i + nargs] = 0; | |
| prom(&prom_args); | |
| return prom_args.args[nargs]; | |
| } | |
| static int __init | |
| call_prom_ret(const char *service, int nargs, int nret, prom_arg_t *rets, ...) | |
| { | |
| va_list list; | |
| int i; | |
| struct prom_args prom_args; | |
| prom_args.service = service; | |
| prom_args.nargs = nargs; | |
| prom_args.nret = nret; | |
| va_start(list, rets); | |
| for (i = 0; i < nargs; ++i) | |
| prom_args.args[i] = va_arg(list, int); | |
| va_end(list); | |
| for (i = 0; i < nret; ++i) | |
| prom_args.args[i + nargs] = 0; | |
| prom(&prom_args); | |
| for (i = 1; i < nret; ++i) | |
| rets[i-1] = prom_args.args[nargs + i]; | |
| return prom_args.args[nargs]; | |
| } | |
| void __init | |
| prom_print(const char *msg) | |
| { | |
| const char *p, *q; | |
| if (prom_stdout == 0) | |
| return; | |
| for (p = msg; *p != 0; p = q) { | |
| for (q = p; *q != 0 && *q != '\n'; ++q) | |
| ; | |
| if (q > p) | |
| call_prom("write", 3, 1, prom_stdout, p, q - p); | |
| if (*q != 0) { | |
| ++q; | |
| call_prom("write", 3, 1, prom_stdout, "\r\n", 2); | |
| } | |
| } | |
| } | |
| static void __init | |
| prom_print_hex(unsigned int v) | |
| { | |
| char buf[16]; | |
| int i, c; | |
| for (i = 0; i < 8; ++i) { | |
| c = (v >> ((7-i)*4)) & 0xf; | |
| c += (c >= 10)? ('a' - 10): '0'; | |
| buf[i] = c; | |
| } | |
| buf[i] = ' '; | |
| buf[i+1] = 0; | |
| prom_print(buf); | |
| } | |
| static int __init | |
| prom_set_color(ihandle ih, int i, int r, int g, int b) | |
| { | |
| return call_prom("call-method", 6, 1, "color!", ih, i, b, g, r); | |
| } | |
| static int __init | |
| prom_next_node(phandle *nodep) | |
| { | |
| phandle node; | |
| if ((node = *nodep) != 0 | |
| && (*nodep = call_prom("child", 1, 1, node)) != 0) | |
| return 1; | |
| if ((*nodep = call_prom("peer", 1, 1, node)) != 0) | |
| return 1; | |
| for (;;) { | |
| if ((node = call_prom("parent", 1, 1, node)) == 0) | |
| return 0; | |
| if ((*nodep = call_prom("peer", 1, 1, node)) != 0) | |
| return 1; | |
| } | |
| } | |
| #ifdef CONFIG_POWER4 | |
| /* | |
| * Set up a hash table with a set of entries in it to map the | |
| * first 64MB of RAM. This is used on 64-bit machines since | |
| * some of them don't have BATs. | |
| */ | |
| static inline void make_pte(unsigned long htab, unsigned int hsize, | |
| unsigned int va, unsigned int pa, int mode) | |
| { | |
| unsigned int *pteg; | |
| unsigned int hash, i, vsid; | |
| vsid = ((va >> 28) * 0x111) << 12; | |
| hash = ((va ^ vsid) >> 5) & 0x7fff80; | |
| pteg = (unsigned int *)(htab + (hash & (hsize - 1))); | |
| for (i = 0; i < 8; ++i, pteg += 4) { | |
| if ((pteg[1] & 1) == 0) { | |
| pteg[1] = vsid | ((va >> 16) & 0xf80) | 1; | |
| pteg[3] = pa | mode; | |
| break; | |
| } | |
| } | |
| } | |
| extern unsigned long _SDR1; | |
| extern PTE *Hash; | |
| extern unsigned long Hash_size; | |
| static void __init | |
| prom_alloc_htab(void) | |
| { | |
| unsigned int hsize; | |
| unsigned long htab; | |
| unsigned int addr; | |
| /* | |
| * Because of OF bugs we can't use the "claim" client | |
| * interface to allocate memory for the hash table. | |
| * This code is only used on 64-bit PPCs, and the only | |
| * 64-bit PPCs at the moment are RS/6000s, and their | |
| * OF is based at 0xc00000 (the 12M point), so we just | |
| * arbitrarily use the 0x800000 - 0xc00000 region for the | |
| * hash table. | |
| * -- paulus. | |
| */ | |
| hsize = 4 << 20; /* POWER4 has no BATs */ | |
| htab = (8 << 20); | |
| call_prom("claim", 3, 1, htab, hsize, 0); | |
| Hash = (void *)(htab + KERNELBASE); | |
| Hash_size = hsize; | |
| _SDR1 = htab + __ilog2(hsize) - 18; | |
| /* | |
| * Put in PTEs for the first 64MB of RAM | |
| */ | |
| memset((void *)htab, 0, hsize); | |
| for (addr = 0; addr < 0x4000000; addr += 0x1000) | |
| make_pte(htab, hsize, addr + KERNELBASE, addr, | |
| _PAGE_ACCESSED | _PAGE_COHERENT | PP_RWXX); | |
| #if 0 /* DEBUG stuff mapping the SCC */ | |
| make_pte(htab, hsize, 0x80013000, 0x80013000, | |
| _PAGE_ACCESSED | _PAGE_NO_CACHE | _PAGE_GUARDED | PP_RWXX); | |
| #endif | |
| } | |
| #endif /* CONFIG_POWER4 */ | |
| /* | |
| * If we have a display that we don't know how to drive, | |
| * we will want to try to execute OF's open method for it | |
| * later. However, OF will probably fall over if we do that | |
| * we've taken over the MMU. | |
| * So we check whether we will need to open the display, | |
| * and if so, open it now. | |
| */ | |
| static unsigned long __init | |
| check_display(unsigned long mem) | |
| { | |
| phandle node; | |
| ihandle ih; | |
| int i, j; | |
| char type[16], *path; | |
| static unsigned char default_colors[] = { | |
| 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0xaa, | |
| 0x00, 0xaa, 0x00, | |
| 0x00, 0xaa, 0xaa, | |
| 0xaa, 0x00, 0x00, | |
| 0xaa, 0x00, 0xaa, | |
| 0xaa, 0xaa, 0x00, | |
| 0xaa, 0xaa, 0xaa, | |
| 0x55, 0x55, 0x55, | |
| 0x55, 0x55, 0xff, | |
| 0x55, 0xff, 0x55, | |
| 0x55, 0xff, 0xff, | |
| 0xff, 0x55, 0x55, | |
| 0xff, 0x55, 0xff, | |
| 0xff, 0xff, 0x55, | |
| 0xff, 0xff, 0xff | |
| }; | |
| const unsigned char *clut; | |
| prom_disp_node = 0; | |
| for (node = 0; prom_next_node(&node); ) { | |
| type[0] = 0; | |
| call_prom("getprop", 4, 1, node, "device_type", | |
| type, sizeof(type)); | |
| if (strcmp(type, "display") != 0) | |
| continue; | |
| /* It seems OF doesn't null-terminate the path :-( */ | |
| path = (char *) mem; | |
| memset(path, 0, 256); | |
| if (call_prom("package-to-path", 3, 1, node, path, 255) < 0) | |
| continue; | |
| /* | |
| * If this display is the device that OF is using for stdout, | |
| * move it to the front of the list. | |
| */ | |
| mem += strlen(path) + 1; | |
| i = prom_num_displays++; | |
| if (of_stdout_device != 0 && i > 0 | |
| && strcmp(of_stdout_device, path) == 0) { | |
| for (; i > 0; --i) { | |
| prom_display_paths[i] | |
| = prom_display_paths[i-1]; | |
| prom_display_nodes[i] | |
| = prom_display_nodes[i-1]; | |
| } | |
| } | |
| prom_display_paths[i] = path; | |
| prom_display_nodes[i] = node; | |
| if (i == 0) | |
| prom_disp_node = node; | |
| if (prom_num_displays >= FB_MAX) | |
| break; | |
| } | |
| for (j=0; j<prom_num_displays; j++) { | |
| path = prom_display_paths[j]; | |
| node = prom_display_nodes[j]; | |
| prom_print("opening display "); | |
| prom_print(path); | |
| ih = call_prom("open", 1, 1, path); | |
| if (ih == 0 || ih == (ihandle) -1) { | |
| prom_print("... failed\n"); | |
| for (i=j+1; i<prom_num_displays; i++) { | |
| prom_display_paths[i-1] = prom_display_paths[i]; | |
| prom_display_nodes[i-1] = prom_display_nodes[i]; | |
| } | |
| if (--prom_num_displays > 0) { | |
| prom_disp_node = prom_display_nodes[j]; | |
| j--; | |
| } else | |
| prom_disp_node = 0; | |
| continue; | |
| } else { | |
| prom_print("... ok\n"); | |
| call_prom("setprop", 4, 1, node, "linux,opened", 0, 0); | |
| /* | |
| * Setup a usable color table when the appropriate | |
| * method is available. | |
| * Should update this to use set-colors. | |
| */ | |
| clut = default_colors; | |
| for (i = 0; i < 32; i++, clut += 3) | |
| if (prom_set_color(ih, i, clut[0], clut[1], | |
| clut[2]) != 0) | |
| break; | |
| #ifdef CONFIG_LOGO_LINUX_CLUT224 | |
| clut = PTRRELOC(logo_linux_clut224.clut); | |
| for (i = 0; i < logo_linux_clut224.clutsize; | |
| i++, clut += 3) | |
| if (prom_set_color(ih, i + 32, clut[0], | |
| clut[1], clut[2]) != 0) | |
| break; | |
| #endif /* CONFIG_LOGO_LINUX_CLUT224 */ | |
| } | |
| } | |
| if (prom_stdout) { | |
| phandle p; | |
| p = call_prom("instance-to-package", 1, 1, prom_stdout); | |
| if (p && p != -1) { | |
| type[0] = 0; | |
| call_prom("getprop", 4, 1, p, "device_type", | |
| type, sizeof(type)); | |
| if (strcmp(type, "display") == 0) | |
| call_prom("setprop", 4, 1, p, "linux,boot-display", | |
| 0, 0); | |
| } | |
| } | |
| return ALIGNUL(mem); | |
| } | |
| /* This function will enable the early boot text when doing OF booting. This | |
| * way, xmon output should work too | |
| */ | |
| static void __init | |
| setup_disp_fake_bi(ihandle dp) | |
| { | |
| #ifdef CONFIG_BOOTX_TEXT | |
| int width = 640, height = 480, depth = 8, pitch; | |
| unsigned address; | |
| struct pci_reg_property addrs[8]; | |
| int i, naddrs; | |
| char name[32]; | |
| char *getprop = "getprop"; | |
| prom_print("Initializing fake screen: "); | |
| memset(name, 0, sizeof(name)); | |
| call_prom(getprop, 4, 1, dp, "name", name, sizeof(name)); | |
| name[sizeof(name)-1] = 0; | |
| prom_print(name); | |
| prom_print("\n"); | |
| call_prom(getprop, 4, 1, dp, "width", &width, sizeof(width)); | |
| call_prom(getprop, 4, 1, dp, "height", &height, sizeof(height)); | |
| call_prom(getprop, 4, 1, dp, "depth", &depth, sizeof(depth)); | |
| pitch = width * ((depth + 7) / 8); | |
| call_prom(getprop, 4, 1, dp, "linebytes", | |
| &pitch, sizeof(pitch)); | |
| if (pitch == 1) | |
| pitch = 0x1000; /* for strange IBM display */ | |
| address = 0; | |
| call_prom(getprop, 4, 1, dp, "address", | |
| &address, sizeof(address)); | |
| if (address == 0) { | |
| /* look for an assigned address with a size of >= 1MB */ | |
| naddrs = call_prom(getprop, 4, 1, dp, "assigned-addresses", | |
| addrs, sizeof(addrs)); | |
| naddrs /= sizeof(struct pci_reg_property); | |
| for (i = 0; i < naddrs; ++i) { | |
| if (addrs[i].size_lo >= (1 << 20)) { | |
| address = addrs[i].addr.a_lo; | |
| /* use the BE aperture if possible */ | |
| if (addrs[i].size_lo >= (16 << 20)) | |
| address += (8 << 20); | |
| break; | |
| } | |
| } | |
| if (address == 0) { | |
| prom_print("Failed to get address\n"); | |
| return; | |
| } | |
| } | |
| /* kludge for valkyrie */ | |
| if (strcmp(name, "valkyrie") == 0) | |
| address += 0x1000; | |
| #ifdef CONFIG_POWER4 | |
| #if CONFIG_TASK_SIZE > 0x80000000 | |
| #error CONFIG_TASK_SIZE cannot be above 0x80000000 with BOOTX_TEXT on G5 | |
| #endif | |
| { | |
| extern boot_infos_t disp_bi; | |
| unsigned long va, pa, i, offset; | |
| va = 0x90000000; | |
| pa = address & 0xfffff000ul; | |
| offset = address & 0x00000fff; | |
| for (i=0; i<0x4000; i++) { | |
| make_pte((unsigned long)Hash - KERNELBASE, Hash_size, va, pa, | |
| _PAGE_ACCESSED | _PAGE_NO_CACHE | | |
| _PAGE_GUARDED | PP_RWXX); | |
| va += 0x1000; | |
| pa += 0x1000; | |
| } | |
| btext_setup_display(width, height, depth, pitch, 0x90000000 | offset); | |
| disp_bi.dispDeviceBase = (u8 *)address; | |
| } | |
| #else /* CONFIG_POWER4 */ | |
| btext_setup_display(width, height, depth, pitch, address); | |
| btext_prepare_BAT(); | |
| #endif /* CONFIG_POWER4 */ | |
| #endif /* CONFIG_BOOTX_TEXT */ | |
| } | |
| /* | |
| * Make a copy of the device tree from the PROM. | |
| */ | |
| static unsigned long __init | |
| copy_device_tree(unsigned long mem_start, unsigned long mem_end) | |
| { | |
| phandle root; | |
| unsigned long new_start; | |
| struct device_node **allnextp; | |
| root = call_prom("peer", 1, 1, (phandle)0); | |
| if (root == (phandle)0) { | |
| prom_print("couldn't get device tree root\n"); | |
| prom_exit(); | |
| } | |
| allnextp = &allnodes; | |
| mem_start = ALIGNUL(mem_start); | |
| new_start = inspect_node(root, NULL, mem_start, mem_end, &allnextp); | |
| *allnextp = NULL; | |
| return new_start; | |
| } | |
| static unsigned long __init | |
| inspect_node(phandle node, struct device_node *dad, | |
| unsigned long mem_start, unsigned long mem_end, | |
| struct device_node ***allnextpp) | |
| { | |
| int l; | |
| phandle child; | |
| struct device_node *np; | |
| struct property *pp, **prev_propp; | |
| char *prev_name, *namep; | |
| unsigned char *valp; | |
| np = (struct device_node *) mem_start; | |
| mem_start += sizeof(struct device_node); | |
| memset(np, 0, sizeof(*np)); | |
| np->node = node; | |
| **allnextpp = PTRUNRELOC(np); | |
| *allnextpp = &np->allnext; | |
| if (dad != 0) { | |
| np->parent = PTRUNRELOC(dad); | |
| /* we temporarily use the `next' field as `last_child'. */ | |
| if (dad->next == 0) | |
| dad->child = PTRUNRELOC(np); | |
| else | |
| dad->next->sibling = PTRUNRELOC(np); | |
| dad->next = np; | |
| } | |
| /* get and store all properties */ | |
| prev_propp = &np->properties; | |
| prev_name = ""; | |
| for (;;) { | |
| pp = (struct property *) mem_start; | |
| namep = (char *) (pp + 1); | |
| pp->name = PTRUNRELOC(namep); | |
| if (call_prom("nextprop", 3, 1, node, prev_name, namep) <= 0) | |
| break; | |
| mem_start = ALIGNUL((unsigned long)namep + strlen(namep) + 1); | |
| prev_name = namep; | |
| valp = (unsigned char *) mem_start; | |
| pp->value = PTRUNRELOC(valp); | |
| pp->length = call_prom("getprop", 4, 1, node, namep, | |
| valp, mem_end - mem_start); | |
| if (pp->length < 0) | |
| continue; | |
| #ifdef MAX_PROPERTY_LENGTH | |
| if (pp->length > MAX_PROPERTY_LENGTH) | |
| continue; /* ignore this property */ | |
| #endif | |
| mem_start = ALIGNUL(mem_start + pp->length); | |
| *prev_propp = PTRUNRELOC(pp); | |
| prev_propp = &pp->next; | |
| } | |
| if (np->node != 0) { | |
| /* Add a "linux,phandle" property" */ | |
| pp = (struct property *) mem_start; | |
| *prev_propp = PTRUNRELOC(pp); | |
| prev_propp = &pp->next; | |
| namep = (char *) (pp + 1); | |
| pp->name = PTRUNRELOC(namep); | |
| strcpy(namep, "linux,phandle"); | |
| mem_start = ALIGNUL((unsigned long)namep + strlen(namep) + 1); | |
| pp->value = (unsigned char *) PTRUNRELOC(&np->node); | |
| pp->length = sizeof(np->node); | |
| } | |
| *prev_propp = NULL; | |
| /* get the node's full name */ | |
| l = call_prom("package-to-path", 3, 1, node, | |
| mem_start, mem_end - mem_start); | |
| if (l >= 0) { | |
| char *p, *ep; | |
| np->full_name = PTRUNRELOC((char *) mem_start); | |
| *(char *)(mem_start + l) = 0; | |
| /* Fixup an Apple bug where they have bogus \0 chars in the | |
| * middle of the path in some properties | |
| */ | |
| for (p = (char *)mem_start, ep = p + l; p < ep; p++) | |
| if ((*p) == '\0') { | |
| memmove(p, p+1, ep - p); | |
| ep--; | |
| } | |
| mem_start = ALIGNUL(mem_start + l + 1); | |
| } | |
| /* do all our children */ | |
| child = call_prom("child", 1, 1, node); | |
| while (child != 0) { | |
| mem_start = inspect_node(child, np, mem_start, mem_end, | |
| allnextpp); | |
| child = call_prom("peer", 1, 1, child); | |
| } | |
| return mem_start; | |
| } | |
| unsigned long smp_chrp_cpu_nr __initdata = 0; | |
| /* | |
| * With CHRP SMP we need to use the OF to start the other | |
| * processors so we can't wait until smp_boot_cpus (the OF is | |
| * trashed by then) so we have to put the processors into | |
| * a holding pattern controlled by the kernel (not OF) before | |
| * we destroy the OF. | |
| * | |
| * This uses a chunk of high memory, puts some holding pattern | |
| * code there and sends the other processors off to there until | |
| * smp_boot_cpus tells them to do something. We do that by using | |
| * physical address 0x0. The holding pattern checks that address | |
| * until its cpu # is there, when it is that cpu jumps to | |
| * __secondary_start(). smp_boot_cpus() takes care of setting those | |
| * values. | |
| * | |
| * We also use physical address 0x4 here to tell when a cpu | |
| * is in its holding pattern code. | |
| * | |
| * -- Cort | |
| * | |
| * Note that we have to do this if we have more than one CPU, | |
| * even if this is a UP kernel. Otherwise when we trash OF | |
| * the other CPUs will start executing some random instructions | |
| * and crash the system. -- paulus | |
| */ | |
| static void __init | |
| prom_hold_cpus(unsigned long mem) | |
| { | |
| extern void __secondary_hold(void); | |
| unsigned long i; | |
| int cpu; | |
| phandle node; | |
| char type[16], *path; | |
| unsigned int reg; | |
| /* | |
| * XXX: hack to make sure we're chrp, assume that if we're | |
| * chrp we have a device_type property -- Cort | |
| */ | |
| node = call_prom("finddevice", 1, 1, "/"); | |
| if (call_prom("getprop", 4, 1, node, | |
| "device_type", type, sizeof(type)) <= 0) | |
| return; | |
| /* copy the holding pattern code to someplace safe (0) */ | |
| /* the holding pattern is now within the first 0x100 | |
| bytes of the kernel image -- paulus */ | |
| memcpy((void *)0, _stext, 0x100); | |
| flush_icache_range(0, 0x100); | |
| /* look for cpus */ | |
| *(unsigned long *)(0x0) = 0; | |
| asm volatile("dcbf 0,%0": : "r" (0) : "memory"); | |
| for (node = 0; prom_next_node(&node); ) { | |
| type[0] = 0; | |
| call_prom("getprop", 4, 1, node, "device_type", | |
| type, sizeof(type)); | |
| if (strcmp(type, "cpu") != 0) | |
| continue; | |
| path = (char *) mem; | |
| memset(path, 0, 256); | |
| if (call_prom("package-to-path", 3, 1, node, path, 255) < 0) | |
| continue; | |
| reg = -1; | |
| call_prom("getprop", 4, 1, node, "reg", ®, sizeof(reg)); | |
| cpu = smp_chrp_cpu_nr++; | |
| #ifdef CONFIG_SMP | |
| smp_hw_index[cpu] = reg; | |
| #endif /* CONFIG_SMP */ | |
| /* XXX: hack - don't start cpu 0, this cpu -- Cort */ | |
| if (cpu == 0) | |
| continue; | |
| prom_print("starting cpu "); | |
| prom_print(path); | |
| *(ulong *)(0x4) = 0; | |
| call_prom("start-cpu", 3, 0, node, | |
| (char *)__secondary_hold - _stext, cpu); | |
| prom_print("..."); | |
| for ( i = 0 ; (i < 10000) && (*(ulong *)(0x4) == 0); i++ ) | |
| ; | |
| if (*(ulong *)(0x4) == cpu) | |
| prom_print("ok\n"); | |
| else { | |
| prom_print("failed: "); | |
| prom_print_hex(*(ulong *)0x4); | |
| prom_print("\n"); | |
| } | |
| } | |
| } | |
| static void __init | |
| prom_instantiate_rtas(void) | |
| { | |
| ihandle prom_rtas; | |
| prom_arg_t result; | |
| prom_rtas = call_prom("finddevice", 1, 1, "/rtas"); | |
| if (prom_rtas == -1) | |
| return; | |
| rtas_size = 0; | |
| call_prom("getprop", 4, 1, prom_rtas, | |
| "rtas-size", &rtas_size, sizeof(rtas_size)); | |
| prom_print("instantiating rtas"); | |
| if (rtas_size == 0) { | |
| rtas_data = 0; | |
| } else { | |
| /* | |
| * Ask OF for some space for RTAS. | |
| * Actually OF has bugs so we just arbitrarily | |
| * use memory at the 6MB point. | |
| */ | |
| rtas_data = 6 << 20; | |
| prom_print(" at "); | |
| prom_print_hex(rtas_data); | |
| } | |
| prom_rtas = call_prom("open", 1, 1, "/rtas"); | |
| prom_print("..."); | |
| rtas_entry = 0; | |
| if (call_prom_ret("call-method", 3, 2, &result, | |
| "instantiate-rtas", prom_rtas, rtas_data) == 0) | |
| rtas_entry = result; | |
| if ((rtas_entry == -1) || (rtas_entry == 0)) | |
| prom_print(" failed\n"); | |
| else | |
| prom_print(" done\n"); | |
| } | |
| /* | |
| * We enter here early on, when the Open Firmware prom is still | |
| * handling exceptions and the MMU hash table for us. | |
| */ | |
| unsigned long __init | |
| prom_init(int r3, int r4, prom_entry pp) | |
| { | |
| unsigned long mem; | |
| ihandle prom_mmu; | |
| unsigned long offset = reloc_offset(); | |
| int i, l; | |
| char *p, *d; | |
| unsigned long phys; | |
| prom_arg_t result[3]; | |
| char model[32]; | |
| phandle node; | |
| int rc; | |
| /* Default */ | |
| phys = (unsigned long) &_stext; | |
| /* First get a handle for the stdout device */ | |
| prom = pp; | |
| prom_chosen = call_prom("finddevice", 1, 1, "/chosen"); | |
| if (prom_chosen == -1) | |
| prom_exit(); | |
| if (call_prom("getprop", 4, 1, prom_chosen, "stdout", | |
| &prom_stdout, sizeof(prom_stdout)) <= 0) | |
| prom_exit(); | |
| /* Get the full OF pathname of the stdout device */ | |
| mem = (unsigned long) klimit + offset; | |
| p = (char *) mem; | |
| memset(p, 0, 256); | |
| call_prom("instance-to-path", 3, 1, prom_stdout, p, 255); | |
| of_stdout_device = p; | |
| mem += strlen(p) + 1; | |
| /* Get the boot device and translate it to a full OF pathname. */ | |
| p = (char *) mem; | |
| l = call_prom("getprop", 4, 1, prom_chosen, "bootpath", p, 1<<20); | |
| if (l > 0) { | |
| p[l] = 0; /* should already be null-terminated */ | |
| bootpath = PTRUNRELOC(p); | |
| mem += l + 1; | |
| d = (char *) mem; | |
| *d = 0; | |
| call_prom("canon", 3, 1, p, d, 1<<20); | |
| bootdevice = PTRUNRELOC(d); | |
| mem = ALIGNUL(mem + strlen(d) + 1); | |
| } | |
| prom_instantiate_rtas(); | |
| #ifdef CONFIG_POWER4 | |
| /* | |
| * Find out how much memory we have and allocate a | |
| * suitably-sized hash table. | |
| */ | |
| prom_alloc_htab(); | |
| #endif | |
| mem = check_display(mem); | |
| prom_print("copying OF device tree..."); | |
| mem = copy_device_tree(mem, mem + (1<<20)); | |
| prom_print("done\n"); | |
| prom_hold_cpus(mem); | |
| klimit = (char *) (mem - offset); | |
| node = call_prom("finddevice", 1, 1, "/"); | |
| rc = call_prom("getprop", 4, 1, node, "model", model, sizeof(model)); | |
| if (rc > 0 && !strncmp (model, "Pegasos", 7) | |
| && strncmp (model, "Pegasos2", 8)) { | |
| /* Pegasos 1 has a broken translate method in the OF, | |
| * and furthermore the BATs are mapped 1:1 so the phys | |
| * address calculated above is correct, so let's use | |
| * it directly. | |
| */ | |
| } else if (offset == 0) { | |
| /* If we are already running at 0xc0000000, we assume we were | |
| * loaded by an OF bootloader which did set a BAT for us. | |
| * This breaks OF translate so we force phys to be 0. | |
| */ | |
| prom_print("(already at 0xc0000000) phys=0\n"); | |
| phys = 0; | |
| } else if (call_prom("getprop", 4, 1, prom_chosen, "mmu", | |
| &prom_mmu, sizeof(prom_mmu)) <= 0) { | |
| prom_print(" no MMU found\n"); | |
| } else if (call_prom_ret("call-method", 4, 4, result, "translate", | |
| prom_mmu, &_stext, 1) != 0) { | |
| prom_print(" (translate failed)\n"); | |
| } else { | |
| /* We assume the phys. address size is 3 cells */ | |
| phys = result[2]; | |
| } | |
| if (prom_disp_node != 0) | |
| setup_disp_fake_bi(prom_disp_node); | |
| /* Use quiesce call to get OF to shut down any devices it's using */ | |
| prom_print("Calling quiesce ...\n"); | |
| call_prom("quiesce", 0, 0); | |
| /* Relocate various pointers which will be used once the | |
| kernel is running at the address it was linked at. */ | |
| for (i = 0; i < prom_num_displays; ++i) | |
| prom_display_paths[i] = PTRUNRELOC(prom_display_paths[i]); | |
| #ifdef CONFIG_SERIAL_CORE_CONSOLE | |
| /* Relocate the of stdout for console autodetection */ | |
| of_stdout_device = PTRUNRELOC(of_stdout_device); | |
| #endif | |
| prom_print("returning 0x"); | |
| prom_print_hex(phys); | |
| prom_print("from prom_init\n"); | |
| prom_stdout = 0; | |
| return phys; | |
| } | |
| /* | |
| * early_get_property is used to access the device tree image prepared | |
| * by BootX very early on, before the pointers in it have been relocated. | |
| */ | |
| static void * __init | |
| early_get_property(unsigned long base, unsigned long node, char *prop) | |
| { | |
| struct device_node *np = (struct device_node *)(base + node); | |
| struct property *pp; | |
| for (pp = np->properties; pp != 0; pp = pp->next) { | |
| pp = (struct property *) (base + (unsigned long)pp); | |
| if (strcmp((char *)((unsigned long)pp->name + base), | |
| prop) == 0) { | |
| return (void *)((unsigned long)pp->value + base); | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* 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) | |
| void __init | |
| bootx_init(unsigned long r4, unsigned long phys) | |
| { | |
| boot_infos_t *bi = (boot_infos_t *) r4; | |
| unsigned long space; | |
| unsigned long ptr, x; | |
| char *model; | |
| boot_infos = PTRUNRELOC(bi); | |
| if (!BOOT_INFO_IS_V2_COMPATIBLE(bi)) | |
| bi->logicalDisplayBase = NULL; | |
| #ifdef CONFIG_BOOTX_TEXT | |
| btext_init(bi); | |
| /* | |
| * 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)) { | |
| btext_drawstring(" !!! WARNING - Incompatible version of BootX !!!\n\n\n"); | |
| btext_flushscreen(); | |
| } | |
| #endif /* CONFIG_BOOTX_TEXT */ | |
| /* 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 *) early_get_property | |
| (r4 + bi->deviceTreeOffset, 4, "model"); | |
| if (model | |
| && (strcmp(model, "iMac,1") == 0 | |
| || strcmp(model, "PowerMac1,1") == 0)) { | |
| out_le32((unsigned *)0x80880008, 1); /* XXX */ | |
| } | |
| } | |
| /* Move klimit to enclose device tree, args, ramdisk, etc... */ | |
| if (bi->version < 5) { | |
| space = bi->deviceTreeOffset + bi->deviceTreeSize; | |
| if (bi->ramDisk) | |
| space = bi->ramDisk + bi->ramDiskSize; | |
| } else | |
| space = bi->totalParamsSize; | |
| klimit = PTRUNRELOC((char *) bi + 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) { | |
| /* | |
| * 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; | |
| } | |
| #ifdef CONFIG_BOOTX_TEXT | |
| /* | |
| * Note that after we call btext_prepare_BAT, we can't do | |
| * prom_draw*, flushscreen or clearscreen until we turn the MMU | |
| * on, since btext_prepare_BAT sets disp_bi.logicalDisplayBase | |
| * to a virtual address. | |
| */ | |
| btext_prepare_BAT(); | |
| #endif | |
| } |