From a23414beb6607dfd40d3245f7df9dd97a4e2c82b Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Thu, 10 Nov 2005 12:00:55 +1100 Subject: ppc/powerpc: workarounds for old Open Firmware versions This adds code to work around some problems with old versions of Open Firmware, such as on the early powermacs (7500 etc.) and the "Longtrail" CHRP machine. On these machines we have to claim the physical and virtual address ranges explicitly when claiming memory and then set up a V->P mapping. The Longtrail has more problems: setprop doesn't work, and we have to set an "allow-reclaim" variable to 0 in order to get claim on physical memory ranges to fail if the memory is already claimed. Signed-off-by: Paul Mackerras --- arch/powerpc/kernel/prom.c | 9 +- arch/powerpc/kernel/prom_init.c | 170 +++++++++++++++++++++++++++--------- arch/powerpc/platforms/chrp/setup.c | 4 +- arch/ppc/boot/include/of1275.h | 3 + arch/ppc/boot/of1275/Makefile | 2 +- arch/ppc/boot/of1275/call_prom.c | 74 ++++++++++++++++ arch/ppc/boot/of1275/claim.c | 97 +++++++++++++++----- arch/ppc/boot/of1275/finddevice.c | 19 +--- 8 files changed, 295 insertions(+), 83 deletions(-) create mode 100644 arch/ppc/boot/of1275/call_prom.c (limited to 'arch') diff --git a/arch/powerpc/kernel/prom.c b/arch/powerpc/kernel/prom.c index f645adb57534..5af39f866735 100644 --- a/arch/powerpc/kernel/prom.c +++ b/arch/powerpc/kernel/prom.c @@ -1264,7 +1264,14 @@ static int __init early_init_dt_scan_memory(unsigned long node, unsigned long l; /* We are scanning "memory" nodes only */ - if (type == NULL || strcmp(type, "memory") != 0) + if (type == NULL) { + /* + * The longtrail doesn't have a device_type on the + * /memory node, so look for the node called /memory@0. + */ + if (depth != 1 || strcmp(uname, "memory@0") != 0) + return 0; + } else if (strcmp(type, "memory") != 0) return 0; reg = (cell_t *)of_get_flat_dt_prop(node, "reg", &l); diff --git a/arch/powerpc/kernel/prom_init.c b/arch/powerpc/kernel/prom_init.c index 58f0917bd6b6..09db1bb9ec91 100644 --- a/arch/powerpc/kernel/prom_init.c +++ b/arch/powerpc/kernel/prom_init.c @@ -94,11 +94,17 @@ extern const struct linux_logo logo_linux_clut224; #ifdef CONFIG_PPC64 #define RELOC(x) (*PTRRELOC(&(x))) #define ADDR(x) (u32) add_reloc_offset((unsigned long)(x)) +#define OF_WORKAROUNDS 0 #else #define RELOC(x) (x) #define ADDR(x) (u32) (x) +#define OF_WORKAROUNDS of_workarounds +int of_workarounds; #endif +#define OF_WA_CLAIM 1 /* do phys/virt claim separately, then map */ +#define OF_WA_LONGTRAIL 2 /* work around longtrail bugs */ + #define PROM_BUG() do { \ prom_printf("kernel BUG at %s line 0x%x!\n", \ RELOC(__FILE__), __LINE__); \ @@ -128,10 +134,11 @@ struct prom_args { struct prom_t { ihandle root; - ihandle chosen; + phandle chosen; int cpu; ihandle stdout; ihandle mmumap; + ihandle memory; }; struct mem_map_entry { @@ -360,16 +367,36 @@ static void __init prom_printf(const char *format, ...) static unsigned int __init prom_claim(unsigned long virt, unsigned long size, unsigned long align) { - int ret; struct prom_t *_prom = &RELOC(prom); - ret = call_prom("claim", 3, 1, (prom_arg_t)virt, (prom_arg_t)size, - (prom_arg_t)align); - if (ret != -1 && _prom->mmumap != 0) - /* old pmacs need us to map as well */ + if (align == 0 && (OF_WORKAROUNDS & OF_WA_CLAIM)) { + /* + * Old OF requires we claim physical and virtual separately + * and then map explicitly (assuming virtual mode) + */ + int ret; + prom_arg_t result; + + ret = call_prom_ret("call-method", 5, 2, &result, + ADDR("claim"), _prom->memory, + align, size, virt); + if (ret != 0 || result == -1) + return -1; + ret = call_prom_ret("call-method", 5, 2, &result, + ADDR("claim"), _prom->mmumap, + align, size, virt); + if (ret != 0) { + call_prom("call-method", 4, 1, ADDR("release"), + _prom->memory, size, virt); + return -1; + } + /* the 0x12 is M (coherence) + PP == read/write */ call_prom("call-method", 6, 1, - ADDR("map"), _prom->mmumap, 0, size, virt, virt); - return ret; + ADDR("map"), _prom->mmumap, 0x12, size, virt, virt); + return virt; + } + return call_prom("claim", 3, 1, (prom_arg_t)virt, (prom_arg_t)size, + (prom_arg_t)align); } static void __init __attribute__((noreturn)) prom_panic(const char *reason) @@ -415,11 +442,52 @@ static int inline prom_getproplen(phandle node, const char *pname) return call_prom("getproplen", 2, 1, node, ADDR(pname)); } -static int inline prom_setprop(phandle node, const char *pname, - void *value, size_t valuelen) +static void add_string(char **str, const char *q) { - return call_prom("setprop", 4, 1, node, ADDR(pname), - (u32)(unsigned long) value, (u32) valuelen); + char *p = *str; + + while (*q) + *p++ = *q++; + *p++ = ' '; + *str = p; +} + +static char *tohex(unsigned int x) +{ + static char digits[] = "0123456789abcdef"; + static char result[9]; + int i; + + result[8] = 0; + i = 8; + do { + --i; + result[i] = digits[x & 0xf]; + x >>= 4; + } while (x != 0 && i > 0); + return &result[i]; +} + +static int __init prom_setprop(phandle node, const char *nodename, + const char *pname, void *value, size_t valuelen) +{ + char cmd[256], *p; + + if (!(OF_WORKAROUNDS & OF_WA_LONGTRAIL)) + return call_prom("setprop", 4, 1, node, ADDR(pname), + (u32)(unsigned long) value, (u32) valuelen); + + /* gah... setprop doesn't work on longtrail, have to use interpret */ + p = cmd; + add_string(&p, "dev"); + add_string(&p, nodename); + add_string(&p, tohex((u32)(unsigned long) value)); + add_string(&p, tohex(valuelen)); + add_string(&p, tohex(ADDR(pname))); + add_string(&p, tohex(strlen(RELOC(pname)))); + add_string(&p, "property"); + *p = 0; + return call_prom("interpret", 1, 1, (u32)(unsigned long) cmd); } /* We can't use the standard versions because of RELOC headaches. */ @@ -980,7 +1048,7 @@ static void __init prom_instantiate_rtas(void) rtas_inst = call_prom("open", 1, 1, ADDR("/rtas")); if (!IHANDLE_VALID(rtas_inst)) { - prom_printf("opening rtas package failed"); + prom_printf("opening rtas package failed (%x)\n", rtas_inst); return; } @@ -988,7 +1056,7 @@ static void __init prom_instantiate_rtas(void) if (call_prom_ret("call-method", 3, 2, &entry, ADDR("instantiate-rtas"), - rtas_inst, base) == PROM_ERROR + rtas_inst, base) != 0 || entry == 0) { prom_printf(" failed\n"); return; @@ -997,8 +1065,10 @@ static void __init prom_instantiate_rtas(void) reserve_mem(base, size); - prom_setprop(rtas_node, "linux,rtas-base", &base, sizeof(base)); - prom_setprop(rtas_node, "linux,rtas-entry", &entry, sizeof(entry)); + prom_setprop(rtas_node, "/rtas", "linux,rtas-base", + &base, sizeof(base)); + prom_setprop(rtas_node, "/rtas", "linux,rtas-entry", + &entry, sizeof(entry)); prom_debug("rtas base = 0x%x\n", base); prom_debug("rtas entry = 0x%x\n", entry); @@ -1089,10 +1159,6 @@ static void __init prom_initialize_tce_table(void) if (base < local_alloc_bottom) local_alloc_bottom = base; - /* Save away the TCE table attributes for later use. */ - prom_setprop(node, "linux,tce-base", &base, sizeof(base)); - prom_setprop(node, "linux,tce-size", &minsize, sizeof(minsize)); - /* It seems OF doesn't null-terminate the path :-( */ memset(path, 0, sizeof(path)); /* Call OF to setup the TCE hardware */ @@ -1101,6 +1167,10 @@ static void __init prom_initialize_tce_table(void) prom_printf("package-to-path failed\n"); } + /* Save away the TCE table attributes for later use. */ + prom_setprop(node, path, "linux,tce-base", &base, sizeof(base)); + prom_setprop(node, path, "linux,tce-size", &minsize, sizeof(minsize)); + prom_debug("TCE table: %s\n", path); prom_debug("\tnode = 0x%x\n", node); prom_debug("\tbase = 0x%x\n", base); @@ -1342,6 +1412,7 @@ static void __init prom_init_client_services(unsigned long pp) /* * For really old powermacs, we need to map things we claim. * For that, we need the ihandle of the mmu. + * Also, on the longtrail, we need to work around other bugs. */ static void __init prom_find_mmu(void) { @@ -1355,12 +1426,19 @@ static void __init prom_find_mmu(void) if (prom_getprop(oprom, "model", version, sizeof(version)) <= 0) return; version[sizeof(version) - 1] = 0; - prom_printf("OF version is '%s'\n", version); /* XXX might need to add other versions here */ - if (strcmp(version, "Open Firmware, 1.0.5") != 0) + if (strcmp(version, "Open Firmware, 1.0.5") == 0) + of_workarounds = OF_WA_CLAIM; + else if (strncmp(version, "FirmWorks,3.", 12) == 0) { + of_workarounds = OF_WA_CLAIM | OF_WA_LONGTRAIL; + call_prom("interpret", 1, 1, "dev /memory 0 to allow-reclaim"); + } else return; + _prom->memory = call_prom("open", 1, 1, ADDR("/memory")); prom_getprop(_prom->chosen, "mmu", &_prom->mmumap, sizeof(_prom->mmumap)); + if (!IHANDLE_VALID(_prom->memory) || !IHANDLE_VALID(_prom->mmumap)) + of_workarounds &= ~OF_WA_CLAIM; /* hmmm */ } #else #define prom_find_mmu() @@ -1382,16 +1460,17 @@ static void __init prom_init_stdout(void) memset(path, 0, 256); call_prom("instance-to-path", 3, 1, _prom->stdout, path, 255); val = call_prom("instance-to-package", 1, 1, _prom->stdout); - prom_setprop(_prom->chosen, "linux,stdout-package", &val, sizeof(val)); + prom_setprop(_prom->chosen, "/chosen", "linux,stdout-package", + &val, sizeof(val)); prom_printf("OF stdout device is: %s\n", RELOC(of_stdout_device)); - prom_setprop(_prom->chosen, "linux,stdout-path", - RELOC(of_stdout_device), strlen(RELOC(of_stdout_device))+1); + prom_setprop(_prom->chosen, "/chosen", "linux,stdout-path", + path, strlen(path) + 1); /* If it's a display, note it */ memset(type, 0, sizeof(type)); prom_getprop(val, "device_type", type, sizeof(type)); if (strcmp(type, RELOC("display")) == 0) - prom_setprop(val, "linux,boot-display", NULL, 0); + prom_setprop(val, path, "linux,boot-display", NULL, 0); } static void __init prom_close_stdin(void) @@ -1514,7 +1593,7 @@ static void __init prom_check_displays(void) /* Success */ prom_printf("done\n"); - prom_setprop(node, "linux,opened", NULL, 0); + prom_setprop(node, path, "linux,opened", NULL, 0); /* Setup a usable color table when the appropriate * method is available. Should update this to set-colors */ @@ -1884,9 +1963,11 @@ static void __init fixup_device_tree(void) /* interrupt on this revision of u3 is number 0 and level */ interrupts[0] = 0; interrupts[1] = 1; - prom_setprop(i2c, "interrupts", &interrupts, sizeof(interrupts)); + prom_setprop(i2c, "/u3@0,f8000000/i2c@f8001000", "interrupts", + &interrupts, sizeof(interrupts)); parent = (u32)mpic; - prom_setprop(i2c, "interrupt-parent", &parent, sizeof(parent)); + prom_setprop(i2c, "/u3@0,f8000000/i2c@f8001000", "interrupt-parent", + &parent, sizeof(parent)); #endif } @@ -1922,11 +2003,11 @@ static void __init prom_check_initrd(unsigned long r3, unsigned long r4) RELOC(prom_initrd_end) = RELOC(prom_initrd_start) + r4; val = RELOC(prom_initrd_start); - prom_setprop(_prom->chosen, "linux,initrd-start", &val, - sizeof(val)); + prom_setprop(_prom->chosen, "/chosen", "linux,initrd-start", + &val, sizeof(val)); val = RELOC(prom_initrd_end); - prom_setprop(_prom->chosen, "linux,initrd-end", &val, - sizeof(val)); + prom_setprop(_prom->chosen, "/chosen", "linux,initrd-end", + &val, sizeof(val)); reserve_mem(RELOC(prom_initrd_start), RELOC(prom_initrd_end) - RELOC(prom_initrd_start)); @@ -1969,14 +2050,15 @@ unsigned long __init prom_init(unsigned long r3, unsigned long r4, prom_init_client_services(pp); /* - * Init prom stdout device + * See if this OF is old enough that we need to do explicit maps + * and other workarounds */ - prom_init_stdout(); + prom_find_mmu(); /* - * See if this OF is old enough that we need to do explicit maps + * Init prom stdout device */ - prom_find_mmu(); + prom_init_stdout(); /* * Check for an initrd @@ -1989,7 +2071,7 @@ unsigned long __init prom_init(unsigned long r3, unsigned long r4, */ RELOC(of_platform) = prom_find_machine_type(); getprop_rval = RELOC(of_platform); - prom_setprop(_prom->chosen, "linux,platform", + prom_setprop(_prom->chosen, "/chosen", "linux,platform", &getprop_rval, sizeof(getprop_rval)); #ifdef CONFIG_PPC_PSERIES @@ -2050,21 +2132,23 @@ unsigned long __init prom_init(unsigned long r3, unsigned long r4, * Fill in some infos for use by the kernel later on */ if (RELOC(prom_memory_limit)) - prom_setprop(_prom->chosen, "linux,memory-limit", + prom_setprop(_prom->chosen, "/chosen", "linux,memory-limit", &RELOC(prom_memory_limit), sizeof(prom_memory_limit)); #ifdef CONFIG_PPC64 if (RELOC(ppc64_iommu_off)) - prom_setprop(_prom->chosen, "linux,iommu-off", NULL, 0); + prom_setprop(_prom->chosen, "/chosen", "linux,iommu-off", + NULL, 0); if (RELOC(iommu_force_on)) - prom_setprop(_prom->chosen, "linux,iommu-force-on", NULL, 0); + prom_setprop(_prom->chosen, "/chosen", "linux,iommu-force-on", + NULL, 0); if (RELOC(prom_tce_alloc_start)) { - prom_setprop(_prom->chosen, "linux,tce-alloc-start", + prom_setprop(_prom->chosen, "/chosen", "linux,tce-alloc-start", &RELOC(prom_tce_alloc_start), sizeof(prom_tce_alloc_start)); - prom_setprop(_prom->chosen, "linux,tce-alloc-end", + prom_setprop(_prom->chosen, "/chosen", "linux,tce-alloc-end", &RELOC(prom_tce_alloc_end), sizeof(prom_tce_alloc_end)); } diff --git a/arch/powerpc/platforms/chrp/setup.c b/arch/powerpc/platforms/chrp/setup.c index ecd32d5d85f4..4099ddab9205 100644 --- a/arch/powerpc/platforms/chrp/setup.c +++ b/arch/powerpc/platforms/chrp/setup.c @@ -361,7 +361,9 @@ static void __init chrp_find_openpic(void) printk(KERN_INFO "OpenPIC at %lx\n", opaddr); irq_count = NR_IRQS - NUM_ISA_INTERRUPTS - 4; /* leave room for IPIs */ - prom_get_irq_senses(init_senses, NUM_8259_INTERRUPTS, NR_IRQS - 4); + prom_get_irq_senses(init_senses, NUM_ISA_INTERRUPTS, NR_IRQS - 4); + /* i8259 cascade is always positive level */ + init_senses[0] = IRQ_SENSE_LEVEL | IRQ_POLARITY_POSITIVE; iranges = (unsigned int *) get_property(np, "interrupt-ranges", &len); if (iranges == NULL) diff --git a/arch/ppc/boot/include/of1275.h b/arch/ppc/boot/include/of1275.h index 69173df76db0..4ed88acfa73a 100644 --- a/arch/ppc/boot/include/of1275.h +++ b/arch/ppc/boot/include/of1275.h @@ -19,6 +19,9 @@ extern prom_entry of_prom_entry; /* function declarations */ +int call_prom(const char *service, int nargs, int nret, ...); +int call_prom_ret(const char *service, int nargs, int nret, + unsigned int *rets, ...); void * claim(unsigned int virt, unsigned int size, unsigned int align); int map(unsigned int phys, unsigned int virt, unsigned int size); void enter(void); diff --git a/arch/ppc/boot/of1275/Makefile b/arch/ppc/boot/of1275/Makefile index 02e6f235d7cb..0b979c004972 100644 --- a/arch/ppc/boot/of1275/Makefile +++ b/arch/ppc/boot/of1275/Makefile @@ -3,4 +3,4 @@ # lib-y := claim.o enter.o exit.o finddevice.o getprop.o ofinit.o \ - ofstdio.o read.o release.o write.o map.o + ofstdio.o read.o release.o write.o map.o call_prom.o diff --git a/arch/ppc/boot/of1275/call_prom.c b/arch/ppc/boot/of1275/call_prom.c new file mode 100644 index 000000000000..9479a3a2b8c7 --- /dev/null +++ b/arch/ppc/boot/of1275/call_prom.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 1996-2005 Paul Mackerras. + * + * 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 "of1275.h" +#include + +int call_prom(const char *service, int nargs, int nret, ...) +{ + int i; + struct prom_args { + const char *service; + int nargs; + int nret; + unsigned int args[12]; + } args; + va_list list; + + args.service = service; + args.nargs = nargs; + args.nret = nret; + + va_start(list, nret); + for (i = 0; i < nargs; i++) + args.args[i] = va_arg(list, unsigned int); + va_end(list); + + for (i = 0; i < nret; i++) + args.args[nargs+i] = 0; + + if (of_prom_entry(&args) < 0) + return -1; + + return (nret > 0)? args.args[nargs]: 0; +} + +int call_prom_ret(const char *service, int nargs, int nret, + unsigned int *rets, ...) +{ + int i; + struct prom_args { + const char *service; + int nargs; + int nret; + unsigned int args[12]; + } args; + va_list list; + + args.service = service; + args.nargs = nargs; + args.nret = nret; + + va_start(list, rets); + for (i = 0; i < nargs; i++) + args.args[i] = va_arg(list, unsigned int); + va_end(list); + + for (i = 0; i < nret; i++) + args.args[nargs+i] = 0; + + if (of_prom_entry(&args) < 0) + return -1; + + if (rets != (void *) 0) + for (i = 1; i < nret; ++i) + rets[i-1] = args.args[nargs+i]; + + return (nret > 0)? args.args[nargs]: 0; +} diff --git a/arch/ppc/boot/of1275/claim.c b/arch/ppc/boot/of1275/claim.c index 13169a5c4339..1ed3aeeff8ae 100644 --- a/arch/ppc/boot/of1275/claim.c +++ b/arch/ppc/boot/of1275/claim.c @@ -9,27 +9,84 @@ */ #include "of1275.h" +#include "nonstdio.h" -void * -claim(unsigned int virt, unsigned int size, unsigned int align) +/* + * Older OF's require that when claiming a specific range of addresses, + * we claim the physical space in the /memory node and the virtual + * space in the chosen mmu node, and then do a map operation to + * map virtual to physical. + */ +static int need_map = -1; +static ihandle chosen_mmu; +static phandle memory; + +/* returns true if s2 is a prefix of s1 */ +static int string_match(const char *s1, const char *s2) +{ + for (; *s2; ++s2) + if (*s1++ != *s2) + return 0; + return 1; +} + +static int check_of_version(void) +{ + phandle oprom, chosen; + char version[64]; + + oprom = finddevice("/openprom"); + if (oprom == OF_INVALID_HANDLE) + return 0; + if (getprop(oprom, "model", version, sizeof(version)) <= 0) + return 0; + version[sizeof(version)-1] = 0; + printf("OF version = '%s'\n", version); + if (!string_match(version, "Open Firmware, 1.") + && !string_match(version, "FirmWorks,3.")) + return 0; + chosen = finddevice("/chosen"); + if (chosen == OF_INVALID_HANDLE) { + chosen = finddevice("/chosen@0"); + if (chosen == OF_INVALID_HANDLE) { + printf("no chosen\n"); + return 0; + } + } + if (getprop(chosen, "mmu", &chosen_mmu, sizeof(chosen_mmu)) <= 0) { + printf("no mmu\n"); + return 0; + } + memory = (ihandle) call_prom("open", 1, 1, "/memory"); + if (memory == OF_INVALID_HANDLE) { + memory = (ihandle) call_prom("open", 1, 1, "/memory@0"); + if (memory == OF_INVALID_HANDLE) { + printf("no memory node\n"); + return 0; + } + } + printf("old OF detected\n"); + return 1; +} + +void *claim(unsigned int virt, unsigned int size, unsigned int align) { - struct prom_args { - char *service; - int nargs; - int nret; - unsigned int virt; - unsigned int size; - unsigned int align; - void *ret; - } args; + int ret; + unsigned int result; - args.service = "claim"; - args.nargs = 3; - args.nret = 1; - args.virt = virt; - args.size = size; - args.align = align; - args.ret = (void *) 0; - (*of_prom_entry)(&args); - return args.ret; + if (need_map < 0) + need_map = check_of_version(); + if (align || !need_map) + return (void *) call_prom("claim", 3, 1, virt, size, align); + + ret = call_prom_ret("call-method", 5, 2, &result, "claim", memory, + align, size, virt); + if (ret != 0 || result == -1) + return (void *) -1; + ret = call_prom_ret("call-method", 5, 2, &result, "claim", chosen_mmu, + align, size, virt); + /* 0x12 == coherent + read/write */ + ret = call_prom("call-method", 6, 1, "map", chosen_mmu, + 0x12, size, virt, virt); + return virt; } diff --git a/arch/ppc/boot/of1275/finddevice.c b/arch/ppc/boot/of1275/finddevice.c index 2c0f7cbb793e..0dcb1201b772 100644 --- a/arch/ppc/boot/of1275/finddevice.c +++ b/arch/ppc/boot/of1275/finddevice.c @@ -10,22 +10,7 @@ #include "of1275.h" -phandle -finddevice(const char *name) +phandle finddevice(const char *name) { - struct prom_args { - char *service; - int nargs; - int nret; - const char *devspec; - phandle device; - } args; - - args.service = "finddevice"; - args.nargs = 1; - args.nret = 1; - args.devspec = name; - args.device = OF_INVALID_HANDLE; - (*of_prom_entry)(&args); - return args.device; + return (phandle) call_prom("finddevice", 1, 1, name); } -- cgit v1.2.3-59-g8ed1b