ed1787d
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
ed1787d
From: Patrick Steinhardt <ps@pks.im>
ed1787d
Date: Thu, 21 Apr 2022 15:24:18 +1000
ed1787d
Subject: [PATCH] mm: Allow dynamically requesting additional memory regions
ed1787d
ed1787d
Currently, all platforms will set up their heap on initialization of the
ed1787d
platform code. While this works mostly fine, it poses some limitations
ed1787d
on memory management on us. Most notably, allocating big chunks of
ed1787d
memory in the gigabyte range would require us to pre-request this many
ed1787d
bytes from the firmware and add it to the heap from the beginning on
ed1787d
some platforms like EFI. As this isn't needed for most configurations,
ed1787d
it is inefficient and may even negatively impact some usecases when,
ed1787d
e.g., chainloading. Nonetheless, allocating big chunks of memory is
ed1787d
required sometimes, where one example is the upcoming support for the
ed1787d
Argon2 key derival function in LUKS2.
ed1787d
ed1787d
In order to avoid pre-allocating big chunks of memory, this commit
ed1787d
implements a runtime mechanism to add more pages to the system. When
ed1787d
a given allocation cannot be currently satisfied, we'll call a given
ed1787d
callback set up by the platform's own memory management subsystem,
ed1787d
asking it to add a memory area with at least "n" bytes. If this
ed1787d
succeeds, we retry searching for a valid memory region, which should
ed1787d
now succeed.
ed1787d
ed1787d
If this fails, we try asking for "n" bytes, possibly spread across
ed1787d
multiple regions, in hopes that region merging means that we end up
ed1787d
with enough memory for things to work out.
ed1787d
ed1787d
Signed-off-by: Patrick Steinhardt <ps@pks.im>
ed1787d
Signed-off-by: Daniel Axtens <dja@axtens.net>
ed1787d
Tested-by: Stefan Berger <stefanb@linux.ibm.com>
ed1787d
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
ed1787d
Tested-by: Patrick Steinhardt <ps@pks.im>
ed1787d
(cherry picked from commit 887f98f0db43e33fba4ec1f85e42fae1185700bc)
ed1787d
---
ed1787d
 grub-core/kern/mm.c | 30 ++++++++++++++++++++++++++++++
ed1787d
 include/grub/mm.h   | 18 ++++++++++++++++++
ed1787d
 2 files changed, 48 insertions(+)
ed1787d
ed1787d
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
ed1787d
index 1825dc8289..f2e27f263b 100644
ed1787d
--- a/grub-core/kern/mm.c
ed1787d
+++ b/grub-core/kern/mm.c
ed1787d
@@ -28,6 +28,9 @@
ed1787d
   - multiple regions may be used as free space. They may not be
ed1787d
   contiguous.
ed1787d
 
ed1787d
+  - if existing regions are insufficient to satisfy an allocation, a new
ed1787d
+  region can be requested from firmware.
ed1787d
+
ed1787d
   Regions are managed by a singly linked list, and the meta information is
ed1787d
   stored in the beginning of each region. Space after the meta information
ed1787d
   is used to allocate memory.
ed1787d
@@ -81,6 +84,7 @@
ed1787d
 
ed1787d
 
ed1787d
 grub_mm_region_t grub_mm_base;
ed1787d
+grub_mm_add_region_func_t grub_mm_add_region_fn;
ed1787d
 
ed1787d
 /* Get a header from the pointer PTR, and set *P and *R to a pointer
ed1787d
    to the header and a pointer to its region, respectively. PTR must
ed1787d
@@ -444,6 +448,32 @@ grub_memalign (grub_size_t align, grub_size_t size)
ed1787d
       count++;
ed1787d
       goto again;
ed1787d
 
ed1787d
+    case 1:
ed1787d
+      /* Request additional pages, contiguous */
ed1787d
+      count++;
ed1787d
+
ed1787d
+      if (grub_mm_add_region_fn != NULL &&
ed1787d
+          grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_CONSECUTIVE) == GRUB_ERR_NONE)
ed1787d
+	goto again;
ed1787d
+
ed1787d
+      /* fallthrough  */
ed1787d
+
ed1787d
+    case 2:
ed1787d
+      /* Request additional pages, anything at all */
ed1787d
+      count++;
ed1787d
+
ed1787d
+      if (grub_mm_add_region_fn != NULL)
ed1787d
+        {
ed1787d
+          /*
ed1787d
+           * Try again even if this fails, in case it was able to partially
ed1787d
+           * satisfy the request
ed1787d
+           */
ed1787d
+          grub_mm_add_region_fn (size, GRUB_MM_ADD_REGION_NONE);
ed1787d
+          goto again;
ed1787d
+        }
ed1787d
+
ed1787d
+      /* fallthrough */
ed1787d
+
ed1787d
     default:
ed1787d
       break;
ed1787d
     }
ed1787d
diff --git a/include/grub/mm.h b/include/grub/mm.h
ed1787d
index d81623d226..7c6f925ffd 100644
ed1787d
--- a/include/grub/mm.h
ed1787d
+++ b/include/grub/mm.h
ed1787d
@@ -20,6 +20,7 @@
ed1787d
 #ifndef GRUB_MM_H
ed1787d
 #define GRUB_MM_H	1
ed1787d
 
ed1787d
+#include <grub/err.h>
ed1787d
 #include <grub/types.h>
ed1787d
 #include <grub/symbol.h>
ed1787d
 #include <grub/err.h>
ed1787d
@@ -29,6 +30,23 @@
ed1787d
 # define NULL	((void *) 0)
ed1787d
 #endif
ed1787d
 
ed1787d
+#define GRUB_MM_ADD_REGION_NONE        0
ed1787d
+#define GRUB_MM_ADD_REGION_CONSECUTIVE (1 << 0)
ed1787d
+
ed1787d
+/*
ed1787d
+ * Function used to request memory regions of `grub_size_t` bytes. The second
ed1787d
+ * parameter is a bitfield of `GRUB_MM_ADD_REGION` flags.
ed1787d
+ */
ed1787d
+typedef grub_err_t (*grub_mm_add_region_func_t) (grub_size_t, unsigned int);
ed1787d
+
ed1787d
+/*
ed1787d
+ * Set this function pointer to enable adding memory-regions at runtime in case
ed1787d
+ * a memory allocation cannot be satisfied with existing regions.
ed1787d
+ */
ed1787d
+#ifndef GRUB_MACHINE_EMU
ed1787d
+extern grub_mm_add_region_func_t EXPORT_VAR(grub_mm_add_region_fn);
ed1787d
+#endif
ed1787d
+
ed1787d
 void grub_mm_init_region (void *addr, grub_size_t size);
ed1787d
 void *EXPORT_FUNC(grub_calloc) (grub_size_t nmemb, grub_size_t size);
ed1787d
 void *EXPORT_FUNC(grub_malloc) (grub_size_t size);