powerpc/prom_init: Evaluate mem kernel parameter for early allocation
authorBenjamin Krill <ben@codiert.org>
Mon, 27 Jul 2009 22:02:39 +0000 (22:02 +0000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Thu, 20 Aug 2009 00:29:25 +0000 (10:29 +1000)
Evaluate mem kernel parameter for early memory allocations. If mem is set
no allocation in the region above the given boundary is allowed. The current
code doesn't take care about this and allocate memory above the given mem
boundary.

Signed-off-by: Benjamin Krill <ben@codiert.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
arch/powerpc/kernel/prom_init.c

index d942404779c1d2d871cd7024ae045e4448a2aa44..864334b337a356c9f8615790eb82a56da4f13715 100644 (file)
@@ -190,6 +190,8 @@ static int __initdata of_platform;
 
 static char __initdata prom_cmd_line[COMMAND_LINE_SIZE];
 
+static unsigned long __initdata prom_memory_limit;
+
 static unsigned long __initdata alloc_top;
 static unsigned long __initdata alloc_top_high;
 static unsigned long __initdata alloc_bottom;
@@ -484,6 +486,67 @@ static int __init prom_setprop(phandle node, const char *nodename,
        return call_prom("interpret", 1, 1, (u32)(unsigned long) cmd);
 }
 
+/* We can't use the standard versions because of RELOC headaches. */
+#define isxdigit(c)    (('0' <= (c) && (c) <= '9') \
+                        || ('a' <= (c) && (c) <= 'f') \
+                        || ('A' <= (c) && (c) <= 'F'))
+
+#define isdigit(c)     ('0' <= (c) && (c) <= '9')
+#define islower(c)     ('a' <= (c) && (c) <= 'z')
+#define toupper(c)     (islower(c) ? ((c) - 'a' + 'A') : (c))
+
+unsigned long prom_strtoul(const char *cp, const char **endp)
+{
+       unsigned long result = 0, base = 10, value;
+
+       if (*cp == '0') {
+               base = 8;
+               cp++;
+               if (toupper(*cp) == 'X') {
+                       cp++;
+                       base = 16;
+               }
+       }
+
+       while (isxdigit(*cp) &&
+              (value = isdigit(*cp) ? *cp - '0' : toupper(*cp) - 'A' + 10) < base) {
+               result = result * base + value;
+               cp++;
+       }
+
+       if (endp)
+               *endp = cp;
+
+       return result;
+}
+
+unsigned long prom_memparse(const char *ptr, const char **retptr)
+{
+       unsigned long ret = prom_strtoul(ptr, retptr);
+       int shift = 0;
+
+       /*
+        * We can't use a switch here because GCC *may* generate a
+        * jump table which won't work, because we're not running at
+        * the address we're linked at.
+        */
+       if ('G' == **retptr || 'g' == **retptr)
+               shift = 30;
+
+       if ('M' == **retptr || 'm' == **retptr)
+               shift = 20;
+
+       if ('K' == **retptr || 'k' == **retptr)
+               shift = 10;
+
+       if (shift) {
+               ret <<= shift;
+               (*retptr)++;
+       }
+
+       return ret;
+}
+
 /*
  * Early parsing of the command line passed to the kernel, used for
  * "mem=x" and the options that affect the iommu
@@ -491,9 +554,8 @@ static int __init prom_setprop(phandle node, const char *nodename,
 static void __init early_cmdline_parse(void)
 {
        struct prom_t *_prom = &RELOC(prom);
-#ifdef CONFIG_PPC64
        const char *opt;
-#endif
+
        char *p;
        int l = 0;
 
@@ -521,6 +583,15 @@ static void __init early_cmdline_parse(void)
                        RELOC(prom_iommu_force_on) = 1;
        }
 #endif
+       opt = strstr(RELOC(prom_cmd_line), RELOC("mem="));
+       if (opt) {
+               opt += 4;
+               RELOC(prom_memory_limit) = prom_memparse(opt, (const char **)&opt);
+#ifdef CONFIG_PPC64
+               /* Align to 16 MB == size of ppc64 large page */
+               RELOC(prom_memory_limit) = ALIGN(RELOC(prom_memory_limit), 0x1000000);
+#endif
+       }
 }
 
 #ifdef CONFIG_PPC_PSERIES
@@ -1026,6 +1097,29 @@ static void __init prom_init_mem(void)
                        RELOC(alloc_bottom) = PAGE_ALIGN(RELOC(prom_initrd_end));
        }
 
+       /*
+        * If prom_memory_limit is set we reduce the upper limits *except* for
+        * alloc_top_high. This must be the real top of RAM so we can put
+        * TCE's up there.
+        */
+
+       RELOC(alloc_top_high) = RELOC(ram_top);
+
+       if (RELOC(prom_memory_limit)) {
+               if (RELOC(prom_memory_limit) <= RELOC(alloc_bottom)) {
+                       prom_printf("Ignoring mem=%x <= alloc_bottom.\n",
+                               RELOC(prom_memory_limit));
+                       RELOC(prom_memory_limit) = 0;
+               } else if (RELOC(prom_memory_limit) >= RELOC(ram_top)) {
+                       prom_printf("Ignoring mem=%x >= ram_top.\n",
+                               RELOC(prom_memory_limit));
+                       RELOC(prom_memory_limit) = 0;
+               } else {
+                       RELOC(ram_top) = RELOC(prom_memory_limit);
+                       RELOC(rmo_top) = min(RELOC(rmo_top), RELOC(prom_memory_limit));
+               }
+       }
+
        /*
         * Setup our top alloc point, that is top of RMO or top of
         * segment 0 when running non-LPAR.
@@ -1041,6 +1135,7 @@ static void __init prom_init_mem(void)
        RELOC(alloc_top_high) = RELOC(ram_top);
 
        prom_printf("memory layout at init:\n");
+       prom_printf("  memory_limit : %x (16 MB aligned)\n", RELOC(prom_memory_limit));
        prom_printf("  alloc_bottom : %x\n", RELOC(alloc_bottom));
        prom_printf("  alloc_top    : %x\n", RELOC(alloc_top));
        prom_printf("  alloc_top_hi : %x\n", RELOC(alloc_top_high));
@@ -2395,6 +2490,10 @@ 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, "/chosen", "linux,memory-limit",
+                            &RELOC(prom_memory_limit),
+                            sizeof(prom_memory_limit));
 #ifdef CONFIG_PPC64
        if (RELOC(prom_iommu_off))
                prom_setprop(_prom->chosen, "/chosen", "linux,iommu-off",