Blame SOURCES/0315-ieee1275-support-runtime-memory-claiming.patch

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