851216d
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
851216d
From: Daniel Axtens <dja@axtens.net>
851216d
Date: Mon, 6 Feb 2023 10:03:22 -0500
851216d
Subject: [PATCH] ieee1275: support runtime memory claiming
851216d
851216d
On powerpc-ieee1275, we are running out of memory trying to verify
851216d
anything. This is because:
851216d
851216d
 - we have to load an entire file into memory to verify it. This is
851216d
   difficult to change with appended signatures.
851216d
 - We only have 32MB of heap.
851216d
 - Distro kernels are now often around 30MB.
851216d
851216d
So we want to be able to claim more memory from OpenFirmware for our heap
851216d
at runtime.
851216d
851216d
There are some complications:
851216d
851216d
 - The grub mm code isn't the only thing that will make claims on
851216d
   memory from OpenFirmware:
851216d
851216d
    * PFW/SLOF will have claimed some for their own use.
851216d
851216d
    * The ieee1275 loader will try to find other bits of memory that we
851216d
      haven't claimed to place the kernel and initrd when we go to boot.
851216d
851216d
    * Once we load Linux, it will also try to claim memory. It claims
851216d
      memory without any reference to /memory/available, it just starts
851216d
      at min(top of RMO, 768MB) and works down. So we need to avoid this
851216d
      area. See arch/powerpc/kernel/prom_init.c as of v5.11.
851216d
851216d
 - The smallest amount of memory a ppc64 KVM guest can have is 256MB.
851216d
   It doesn't work with distro kernels but can work with custom kernels.
851216d
   We should maintain support for that. (ppc32 can boot with even less,
851216d
   and we shouldn't break that either.)
851216d
851216d
 - Even if a VM has more memory, the memory OpenFirmware makes available
851216d
   as Real Memory Area can be restricted. Even with our CAS work, an LPAR
851216d
   on a PowerVM box is likely to have only 512MB available to OpenFirmware
851216d
   even if it has many gigabytes of memory allocated.
851216d
851216d
What should we do?
851216d
851216d
We don't know in advance how big the kernel and initrd are going to be,
851216d
which makes figuring out how much memory we can take a bit tricky.
851216d
851216d
To figure out how much memory we should leave unused, I looked at:
851216d
851216d
 - an Ubuntu 20.04.1 ppc64le pseries KVM guest:
851216d
    vmlinux: ~30MB
851216d
    initrd:  ~50MB
851216d
851216d
 - a RHEL8.2 ppc64le pseries KVM guest:
851216d
    vmlinux: ~30MB
851216d
    initrd:  ~30MB
851216d
851216d
So to give us a little wriggle room, I think we want to leave at least
851216d
128MB for the loader to put vmlinux and initrd in memory and leave Linux
851216d
with space to satisfy its early allocations.
851216d
851216d
Allow other space to be allocated at runtime.
851216d
851216d
Tested-by: Stefan Berger <stefanb@linux.ibm.com>
851216d
Signed-off-by: Daniel Axtens <dja@axtens.net>
851216d
(cherry picked from commit a5c710789ccdd27a84ae4a34c7d453bd585e2b66)
851216d
[rharwood: _start?]
851216d
---
851216d
 grub-core/kern/ieee1275/init.c | 270 ++++++++++++++++++++++++++++++++++++++---
851216d
 docs/grub-dev.texi             |   7 +-
851216d
 2 files changed, 257 insertions(+), 20 deletions(-)
851216d
851216d
diff --git a/grub-core/kern/ieee1275/init.c b/grub-core/kern/ieee1275/init.c
851216d
index c8d551759d..85af8fa97b 100644
851216d
--- a/grub-core/kern/ieee1275/init.c
851216d
+++ b/grub-core/kern/ieee1275/init.c
851216d
@@ -46,13 +46,26 @@
851216d
 #endif
851216d
 #include <grub/lockdown.h>
851216d
 
851216d
-/* The maximum heap size we're going to claim */
851216d
+/* The maximum heap size we're going to claim at boot. Not used by sparc. */
851216d
 #ifdef __i386__
851216d
 #define HEAP_MAX_SIZE		(unsigned long) (64 * 1024 * 1024)
851216d
-#else
851216d
+#else /* __powerpc__ */
851216d
 #define HEAP_MAX_SIZE		(unsigned long) (32 * 1024 * 1024)
851216d
 #endif
851216d
 
851216d
+/* RMO max. address at 768 MB */
851216d
+#define RMO_ADDR_MAX		(grub_uint64_t) (768 * 1024 * 1024)
851216d
+
851216d
+/*
851216d
+ * The amount of OF space we will not claim here so as to leave space for
851216d
+ * the loader and linux to service early allocations.
851216d
+ *
851216d
+ * In 2021, Daniel Axtens claims that we should leave at least 128MB to
851216d
+ * ensure we can load a stock kernel and initrd on a pseries guest with
851216d
+ * a 512MB real memory area under PowerVM.
851216d
+ */
851216d
+#define RUNTIME_MIN_SPACE (128UL * 1024 * 1024)
851216d
+
851216d
 extern char _end[];
851216d
 
851216d
 #ifdef __sparc__
851216d
@@ -147,16 +160,52 @@ grub_claim_heap (void)
851216d
 				 + GRUB_KERNEL_MACHINE_STACK_SIZE), 0x200000);
851216d
 }
851216d
 #else
851216d
-/* Helper for grub_claim_heap.  */
851216d
+/* Helpers for mm on powerpc. */
851216d
+
851216d
+/*
851216d
+ * How much memory does OF believe exists in total?
851216d
+ *
851216d
+ * This isn't necessarily the true total. It can be the total memory
851216d
+ * accessible in real mode for a pseries guest, for example.
851216d
+ */
851216d
+static grub_uint64_t rmo_top;
851216d
+
851216d
 static int
851216d
-heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
-	   void *data)
851216d
+count_free (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
+	    void *data)
851216d
 {
851216d
-  unsigned long *total = data;
851216d
+  if (type != GRUB_MEMORY_AVAILABLE)
851216d
+    return 0;
851216d
+
851216d
+  /* Do not consider memory beyond 4GB */
851216d
+  if (addr > 0xffffffffULL)
851216d
+    return 0;
851216d
+
851216d
+  if (addr + len > 0xffffffffULL)
851216d
+    len = 0xffffffffULL - addr;
851216d
+
851216d
+  *(grub_uint32_t *) data += len;
851216d
+
851216d
+  return 0;
851216d
+}
851216d
+
851216d
+static int
851216d
+regions_claim (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
+	      unsigned int flags, void *data)
851216d
+{
851216d
+  grub_uint32_t total = *(grub_uint32_t *) data;
851216d
+  grub_uint64_t linux_rmo_save;
851216d
 
851216d
   if (type != GRUB_MEMORY_AVAILABLE)
851216d
     return 0;
851216d
 
851216d
+  /* Do not consider memory beyond 4GB */
851216d
+  if (addr > 0xffffffffULL)
851216d
+    return 0;
851216d
+
851216d
+  if (addr + len > 0xffffffffULL)
851216d
+    len = 0xffffffffULL - addr;
851216d
+
851216d
   if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_PRE1_5M_CLAIM))
851216d
     {
851216d
       if (addr + len <= 0x180000)
851216d
@@ -169,10 +218,6 @@ heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
 	}
851216d
     }
851216d
 
851216d
-  /* Never exceed HEAP_MAX_SIZE  */
851216d
-  if (*total + len > HEAP_MAX_SIZE)
851216d
-    len = HEAP_MAX_SIZE - *total;
851216d
-
851216d
   /* In theory, firmware should already prevent this from happening by not
851216d
      listing our own image in /memory/available.  The check below is intended
851216d
      as a safeguard in case that doesn't happen.  However, it doesn't protect
851216d
@@ -184,6 +229,108 @@ heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
       len = 0;
851216d
     }
851216d
 
851216d
+  /*
851216d
+   * Linux likes to claim memory at min(RMO top, 768MB) and works down
851216d
+   * without reference to /memory/available. (See prom_init.c::alloc_down)
851216d
+   *
851216d
+   * If this block contains min(RMO top, 768MB), do not claim below that for
851216d
+   * at least a few MB (this is where RTAS, SML and potentially TCEs live).
851216d
+   *
851216d
+   * We also need to leave enough space for the DT in the RMA. (See
851216d
+   * prom_init.c::alloc_up)
851216d
+   *
851216d
+   * Finally, we also want to make sure that when grub loads the kernel,
851216d
+   * it isn't going to use up all the memory we're trying to reserve! So
851216d
+   * enforce our entire RUNTIME_MIN_SPACE here:
851216d
+   *
851216d
+   * |---------- Top of memory ----------|
851216d
+   * |                                   |
851216d
+   * |             available             |
851216d
+   * |                                   |
851216d
+   * |----------     768 MB    ----------|
851216d
+   * |                                   |
851216d
+   * |              reserved             |
851216d
+   * |                                   |
851216d
+   * |--- 768 MB - runtime min space  ---|
851216d
+   * |                                   |
851216d
+   * |             available             |
851216d
+   * |                                   |
851216d
+   * |----------      0 MB     ----------|
851216d
+   *
851216d
+   * Edge cases:
851216d
+   *
851216d
+   * - Total memory less than RUNTIME_MIN_SPACE: only claim up to HEAP_MAX_SIZE.
851216d
+   *   (enforced elsewhere)
851216d
+   *
851216d
+   * - Total memory between RUNTIME_MIN_SPACE and 768MB:
851216d
+   *
851216d
+   * |---------- Top of memory ----------|
851216d
+   * |                                   |
851216d
+   * |              reserved             |
851216d
+   * |                                   |
851216d
+   * |----  top - runtime min space  ----|
851216d
+   * |                                   |
851216d
+   * |             available             |
851216d
+   * |                                   |
851216d
+   * |----------      0 MB     ----------|
851216d
+   *
851216d
+   * This by itself would not leave us with RUNTIME_MIN_SPACE of free bytes: if
851216d
+   * rmo_top < 768MB, we will almost certainly have FW claims in the reserved
851216d
+   * region. We try to address that elsewhere: grub_ieee1275_mm_add_region will
851216d
+   * not call us if the resulting free space would be less than RUNTIME_MIN_SPACE.
851216d
+   */
851216d
+  linux_rmo_save = grub_min (RMO_ADDR_MAX, rmo_top) - RUNTIME_MIN_SPACE;
851216d
+  if (rmo_top > RUNTIME_MIN_SPACE)
851216d
+    {
851216d
+      if (rmo_top <= RMO_ADDR_MAX)
851216d
+        {
851216d
+          if (addr > linux_rmo_save)
851216d
+            {
851216d
+              grub_dprintf ("ieee1275", "rejecting region in RUNTIME_MIN_SPACE reservation (%llx)\n",
851216d
+                            addr);
851216d
+              return 0;
851216d
+            }
851216d
+          else if (addr + len > linux_rmo_save)
851216d
+            {
851216d
+              grub_dprintf ("ieee1275", "capping region: (%llx -> %llx) -> (%llx -> %llx)\n",
851216d
+                            addr, addr + len, addr, rmo_top - RUNTIME_MIN_SPACE);
851216d
+              len = linux_rmo_save - addr;
851216d
+            }
851216d
+        }
851216d
+      else
851216d
+        {
851216d
+          /*
851216d
+           * we order these cases to prefer higher addresses and avoid some
851216d
+           * splitting issues
851216d
+           */
851216d
+          if (addr < RMO_ADDR_MAX && (addr + len) > RMO_ADDR_MAX)
851216d
+            {
851216d
+              grub_dprintf ("ieee1275",
851216d
+                            "adjusting region for RUNTIME_MIN_SPACE: (%llx -> %llx) -> (%llx -> %llx)\n",
851216d
+                            addr, addr + len, RMO_ADDR_MAX, addr + len);
851216d
+              len = (addr + len) - RMO_ADDR_MAX;
851216d
+              addr = RMO_ADDR_MAX;
851216d
+            }
851216d
+          else if ((addr < linux_rmo_save) && ((addr + len) > linux_rmo_save))
851216d
+            {
851216d
+              grub_dprintf ("ieee1275", "capping region: (%llx -> %llx) -> (%llx -> %llx)\n",
851216d
+                            addr, addr + len, addr, linux_rmo_save);
851216d
+              len = linux_rmo_save - addr;
851216d
+            }
851216d
+          else if (addr >= linux_rmo_save && (addr + len) <= RMO_ADDR_MAX)
851216d
+            {
851216d
+              grub_dprintf ("ieee1275", "rejecting region in RUNTIME_MIN_SPACE reservation (%llx)\n",
851216d
+                            addr);
851216d
+              return 0;
851216d
+            }
851216d
+        }
851216d
+    }
851216d
+  if (flags & GRUB_MM_ADD_REGION_CONSECUTIVE && len < total)
851216d
+    return 0;
851216d
+
851216d
+  if (len > total)
851216d
+    len = total;
851216d
+
851216d
   if (len)
851216d
     {
851216d
       grub_err_t err;
851216d
@@ -192,15 +339,95 @@ heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
       if (err)
851216d
 	return err;
851216d
       grub_mm_init_region ((void *) (grub_addr_t) addr, len);
851216d
+      total -= len;
851216d
     }
851216d
 
851216d
-  *total += len;
851216d
-  if (*total >= HEAP_MAX_SIZE)
851216d
+  *(grub_uint32_t *) data = total;
851216d
+
851216d
+  if (total == 0)
851216d
     return 1;
851216d
 
851216d
   return 0;
851216d
 }
851216d
 
851216d
+static int
851216d
+heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
+	   void *data)
851216d
+{
851216d
+  return regions_claim (addr, len, type, GRUB_MM_ADD_REGION_NONE, data);
851216d
+}
851216d
+
851216d
+static int
851216d
+region_claim (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
851216d
+	   void *data)
851216d
+{
851216d
+  return regions_claim (addr, len, type, GRUB_MM_ADD_REGION_CONSECUTIVE, data);
851216d
+}
851216d
+
851216d
+static grub_err_t
851216d
+grub_ieee1275_mm_add_region (grub_size_t size, unsigned int flags)
851216d
+{
851216d
+  grub_uint32_t free_memory = 0;
851216d
+  grub_uint32_t avail = 0;
851216d
+  grub_uint32_t total;
851216d
+
851216d
+  grub_dprintf ("ieee1275", "mm requested region of size %x, flags %x\n",
851216d
+               size, flags);
851216d
+
851216d
+  /*
851216d
+   * Update free memory each time, which is a bit inefficient but guards us
851216d
+   * against a situation where some OF driver goes out to firmware for
851216d
+   * memory and we don't realise.
851216d
+   */
851216d
+  grub_machine_mmap_iterate (count_free, &free_memory);
851216d
+
851216d
+  /* Ensure we leave enough space to boot. */
851216d
+  if (free_memory <= RUNTIME_MIN_SPACE + size)
851216d
+    {
851216d
+      grub_dprintf ("ieee1275", "Cannot satisfy allocation and retain minimum runtime space\n");
851216d
+      return GRUB_ERR_OUT_OF_MEMORY;
851216d
+    }
851216d
+
851216d
+  if (free_memory > RUNTIME_MIN_SPACE)
851216d
+      avail = free_memory - RUNTIME_MIN_SPACE;
851216d
+
851216d
+  grub_dprintf ("ieee1275", "free = 0x%x available = 0x%x\n", free_memory, avail);
851216d
+
851216d
+  if (flags & GRUB_MM_ADD_REGION_CONSECUTIVE)
851216d
+    {
851216d
+      /* first try rounding up hard for the sake of speed */
851216d
+      total = grub_max (ALIGN_UP (size, 1024 * 1024) + 1024 * 1024, 32 * 1024 * 1024);
851216d
+      total = grub_min (avail, total);
851216d
+
851216d
+      grub_dprintf ("ieee1275", "looking for %x bytes of memory (%x requested)\n", total, size);
851216d
+
851216d
+      grub_machine_mmap_iterate (region_claim, &total);
851216d
+      grub_dprintf ("ieee1275", "get memory from fw %s\n", total == 0 ? "succeeded" : "failed");
851216d
+
851216d
+      if (total != 0)
851216d
+        {
851216d
+          total = grub_min (avail, size);
851216d
+
851216d
+          grub_dprintf ("ieee1275", "fallback for %x bytes of memory (%x requested)\n", total, size);
851216d
+
851216d
+          grub_machine_mmap_iterate (region_claim, &total);
851216d
+          grub_dprintf ("ieee1275", "fallback from fw %s\n", total == 0 ? "succeeded" : "failed");
851216d
+        }
851216d
+    }
851216d
+  else
851216d
+    {
851216d
+      /* provide padding for a grub_mm_header_t and region */
851216d
+      total = grub_min (avail, size);
851216d
+      grub_machine_mmap_iterate (heap_init, &total);
851216d
+      grub_dprintf ("ieee1275", "get noncontig memory from fw %s\n", total == 0 ? "succeeded" : "failed");
851216d
+    }
851216d
+
851216d
+  if (total == 0)
851216d
+    return GRUB_ERR_NONE;
851216d
+  else
851216d
+    return GRUB_ERR_OUT_OF_MEMORY;
851216d
+}
851216d
+
851216d
 /*
851216d
  * How much memory does OF believe it has? (regardless of whether
851216d
  * it's accessible or not)
851216d
@@ -356,17 +583,24 @@ grub_ieee1275_ibm_cas (void)
851216d
 static void
851216d
 grub_claim_heap (void)
851216d
 {
851216d
-  unsigned long total = 0;
851216d
+  grub_err_t err;
851216d
+  grub_uint32_t total = HEAP_MAX_SIZE;
851216d
+
851216d
+  err = grub_ieee1275_total_mem (&rmo_top);
851216d
+
851216d
+  /*
851216d
+   * If we cannot size the available memory, we can't be sure we're leaving
851216d
+   * space for the kernel, initrd and things Linux loads early in boot. So only
851216d
+   * allow further allocations from firmware on success
851216d
+   */
851216d
+  if (err == GRUB_ERR_NONE)
851216d
+    grub_mm_add_region_fn = grub_ieee1275_mm_add_region;
851216d
 
851216d
 #if defined(__powerpc__)
851216d
   if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CAN_TRY_CAS_FOR_MORE_MEMORY))
851216d
     {
851216d
-      grub_uint64_t rma_size;
851216d
-      grub_err_t err;
851216d
-
851216d
-      err = grub_ieee1275_total_mem (&rma_size);
851216d
       /* if we have an error, don't call CAS, just hope for the best */
851216d
-      if (err == GRUB_ERR_NONE && rma_size < (512 * 1024 * 1024))
851216d
+      if (err == GRUB_ERR_NONE && rmo_top < (512 * 1024 * 1024))
851216d
 	grub_ieee1275_ibm_cas ();
851216d
     }
851216d
 #endif
851216d
diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
851216d
index 7b2455a8fe..7edc5b7e2b 100644
851216d
--- a/docs/grub-dev.texi
851216d
+++ b/docs/grub-dev.texi
851216d
@@ -1047,7 +1047,10 @@ space is limited to 4GiB. GRUB allocates pages from EFI for its heap, at most
851216d
 1.6 GiB.
851216d
 
851216d
 On i386-ieee1275 and powerpc-ieee1275 GRUB uses same stack as IEEE1275.
851216d
-It allocates at most 32MiB for its heap.
851216d
+
851216d
+On i386-ieee1275 and powerpc-ieee1275, GRUB will allocate 32MiB for its heap on
851216d
+startup. It may allocate more at runtime, as long as at least 128MiB remain free
851216d
+in OpenFirmware.
851216d
 
851216d
 On sparc64-ieee1275 stack is 256KiB and heap is 2MiB.
851216d
 
851216d
@@ -1075,7 +1078,7 @@ In short:
851216d
 @item i386-qemu               @tab 60 KiB  @tab < 4 GiB
851216d
 @item *-efi                   @tab ?       @tab < 1.6 GiB
851216d
 @item i386-ieee1275           @tab ?       @tab < 32 MiB
851216d
-@item powerpc-ieee1275        @tab ?       @tab < 32 MiB
851216d
+@item powerpc-ieee1275        @tab ?       @tab available memory - 128MiB
851216d
 @item sparc64-ieee1275        @tab 256KiB  @tab 2 MiB
851216d
 @item arm-uboot               @tab 256KiB  @tab 2 MiB
851216d
 @item mips(el)-qemu_mips      @tab 2MiB    @tab 253 MiB