ppc/powerpc: workarounds for old Open Firmware versions
authorPaul Mackerras <paulus@samba.org>
Thu, 10 Nov 2005 01:00:55 +0000 (12:00 +1100)
committerPaul Mackerras <paulus@samba.org>
Thu, 10 Nov 2005 01:00:55 +0000 (12:00 +1100)
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 <paulus@samba.org>
arch/powerpc/kernel/prom.c
arch/powerpc/kernel/prom_init.c
arch/powerpc/platforms/chrp/setup.c
arch/ppc/boot/include/of1275.h
arch/ppc/boot/of1275/Makefile
arch/ppc/boot/of1275/call_prom.c [new file with mode: 0644]
arch/ppc/boot/of1275/claim.c
arch/ppc/boot/of1275/finddevice.c

index f645adb..5af39f8 100644 (file)
@@ -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);
index 58f0917..09db1bb 100644 (file)
@@ -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));
        }
index ecd32d5..4099dda 100644 (file)
@@ -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)
index 69173df..4ed88ac 100644 (file)
@@ -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);
index 02e6f23..0b979c0 100644 (file)
@@ -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 (file)
index 0000000..9479a3a
--- /dev/null
@@ -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 <stdarg.h>
+
+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;
+}
index 13169a5..1ed3aee 100644 (file)
@@ -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;
 }
index 2c0f7cb..0dcb120 100644 (file)
 
 #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);
 }