Pull allocator improvements from upstream

Signed-off-by: Robbie Harwood <rharwood@redhat.com>
This commit is contained in:
Robbie Harwood 2023-01-11 18:57:23 +00:00
parent 217ae25d88
commit 7be2bf00c3
19 changed files with 1484 additions and 107 deletions

View file

@ -1,105 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Julian Andres Klode <julian.klode@canonical.com>
Date: Tue, 6 Dec 2022 15:29:13 +0100
Subject: [PATCH] Allow internal grub allocations over 4GB
Previous commits introduced support for loading kernel and
initrd over 4GB if necessary, but only for the actual loading.
Grub also needs to load large initrds into memory as part of
the verifiers framework.
Increase the maximum allocation limit to the maximum usable
address, and at the same time, to preserve existing behavior,
define a 4G limit that behaves like the old one.
Signed-off-by: Julian Andres Klode <julian.klode@canonical.com>
(cherry picked from commit cf6516128ea03294156fc59a50ce90856bd3ebd2)
---
grub-core/loader/i386/efi/linux.c | 20 +++++++++++---------
include/grub/x86_64/efi/memory.h | 2 +-
2 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/grub-core/loader/i386/efi/linux.c b/grub-core/loader/i386/efi/linux.c
index 9854b0defa..0cc1c0d9a5 100644
--- a/grub-core/loader/i386/efi/linux.c
+++ b/grub-core/loader/i386/efi/linux.c
@@ -31,6 +31,8 @@
#include <grub/tpm.h>
#include <grub/safemath.h>
+#define GRUB_EFI_4G_ALLOCATION_ADDRESS 0x7fffffff
+
GRUB_MOD_LICENSE ("GPLv3+");
static grub_dl_t my_mod;
@@ -79,17 +81,17 @@ static struct allocation_choice max_addresses[] =
/* the kernel overrides this one with pref_address and
* GRUB_EFI_ALLOCATE_ADDRESS */
[KERNEL_PREF_ADDRESS] =
- { KERNEL_MEM, GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
+ { KERNEL_MEM, GRUB_EFI_4G_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
/* If the flag in params is set, this one gets changed to be above 4GB. */
[KERNEL_4G_LIMIT] =
- { KERNEL_MEM, GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
+ { KERNEL_MEM, GRUB_EFI_4G_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
/* this one is always below 4GB, which we still *prefer* even if the flag
* is set. */
[KERNEL_NO_LIMIT] =
- { KERNEL_MEM, GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
+ { KERNEL_MEM, GRUB_EFI_4G_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
/* this is for the initrd */
[INITRD_MAX_ADDRESS] =
- { INITRD_MEM, GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
+ { INITRD_MEM, GRUB_EFI_4G_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
{ NO_MEM, 0, 0 }
};
static struct allocation_choice saved_addresses[sizeof(max_addresses) / sizeof(max_addresses[0])];
@@ -190,7 +192,7 @@ grub_linuxefi_unload (void *data)
cmd_initrdefi->data = 0;
grub_free (context);
- max_addresses[INITRD_MAX_ADDRESS].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
+ max_addresses[INITRD_MAX_ADDRESS].addr = GRUB_EFI_4G_ALLOCATION_ADDRESS;
return GRUB_ERR_NONE;
}
@@ -506,13 +508,13 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
*/
save_addresses();
grub_dprintf ("linux", "lh->pref_address: %p\n", (void *)(grub_addr_t)lh->pref_address);
- if (lh->pref_address < (grub_uint64_t)GRUB_EFI_MAX_ALLOCATION_ADDRESS)
+ if (lh->pref_address < (grub_uint64_t)GRUB_EFI_4G_ALLOCATION_ADDRESS)
{
max_addresses[KERNEL_PREF_ADDRESS].addr = lh->pref_address;
max_addresses[KERNEL_PREF_ADDRESS].alloc_type = GRUB_EFI_ALLOCATE_ADDRESS;
}
- max_addresses[KERNEL_4G_LIMIT].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
- max_addresses[KERNEL_NO_LIMIT].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
+ max_addresses[KERNEL_4G_LIMIT].addr = GRUB_EFI_4G_ALLOCATION_ADDRESS;
+ max_addresses[KERNEL_NO_LIMIT].addr = GRUB_EFI_4G_ALLOCATION_ADDRESS;
kernel_size = lh->init_size;
grub_dprintf ("linux", "Trying to allocate kernel mem\n");
kernel_mem = kernel_alloc (KERNEL_MEM, kernel_size,
@@ -564,7 +566,7 @@ fail:
grub_dl_unref (my_mod);
- max_addresses[INITRD_MAX_ADDRESS].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
+ max_addresses[INITRD_MAX_ADDRESS].addr = GRUB_EFI_4G_ALLOCATION_ADDRESS;
if (lh)
kernel_free (cmdline, lh->cmdline_size + 1);
diff --git a/include/grub/x86_64/efi/memory.h b/include/grub/x86_64/efi/memory.h
index e81cfb3221..547e3f82f8 100644
--- a/include/grub/x86_64/efi/memory.h
+++ b/include/grub/x86_64/efi/memory.h
@@ -3,7 +3,7 @@
#if defined (__code_model_large__)
#define GRUB_EFI_MAX_USABLE_ADDRESS __UINTPTR_MAX__
-#define GRUB_EFI_MAX_ALLOCATION_ADDRESS 0x7fffffff
+#define GRUB_EFI_MAX_ALLOCATION_ADDRESS GRUB_EFI_MAX_USABLE_ADDRESS
#else
#define GRUB_EFI_MAX_USABLE_ADDRESS 0x7fffffff
#define GRUB_EFI_MAX_ALLOCATION_ADDRESS GRUB_EFI_MAX_USABLE_ADDRESS

View file

@ -0,0 +1,183 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 25 Nov 2021 02:22:46 +1100
Subject: [PATCH] mm: Clarify grub_real_malloc()
When iterating through the singly linked list of free blocks,
grub_real_malloc() uses p and q for the current and previous blocks
respectively. This isn't super clear, so swap to using prev and cur.
This makes another quirk more obvious. The comment at the top of
grub_real_malloc() might lead you to believe that the function will
allocate from *first if there is space in that block.
It actually doesn't do that, and it can't do that with the current
data structures. If we used up all of *first, we would need to change
the ->next of the previous block to point to *first->next, but we
can't do that because it's a singly linked list and we don't have
access to *first's previous block.
What grub_real_malloc() actually does is set *first to the initial
previous block, and *first->next is the block we try to allocate
from. That allows us to keep all the data structures consistent.
Document that.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit 246ad6a44c281bb13486ddea0a26bb661db73106)
---
grub-core/kern/mm.c | 76 +++++++++++++++++++++++++++++------------------------
1 file changed, 41 insertions(+), 35 deletions(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index d8c8377578..fb20e93acf 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -178,13 +178,20 @@ grub_mm_init_region (void *addr, grub_size_t size)
}
/* Allocate the number of units N with the alignment ALIGN from the ring
- buffer starting from *FIRST. ALIGN must be a power of two. Both N and
- ALIGN are in units of GRUB_MM_ALIGN. Return a non-NULL if successful,
- otherwise return NULL. */
+ * buffer given in *FIRST. ALIGN must be a power of two. Both N and
+ * ALIGN are in units of GRUB_MM_ALIGN. Return a non-NULL if successful,
+ * otherwise return NULL.
+ *
+ * Note: because in certain circumstances we need to adjust the ->next
+ * pointer of the previous block, we iterate over the singly linked
+ * list with the pair (prev, cur). *FIRST is our initial previous, and
+ * *FIRST->next is our initial current pointer. So we will actually
+ * allocate from *FIRST->next first and *FIRST itself last.
+ */
static void *
grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
{
- grub_mm_header_t p, q;
+ grub_mm_header_t cur, prev;
/* When everything is allocated side effect is that *first will have alloc
magic marked, meaning that there is no room in this region. */
@@ -192,24 +199,24 @@ grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
return 0;
/* Try to search free slot for allocation in this memory region. */
- for (q = *first, p = q->next; ; q = p, p = p->next)
+ for (prev = *first, cur = prev->next; ; prev = cur, cur = cur->next)
{
grub_off_t extra;
- extra = ((grub_addr_t) (p + 1) >> GRUB_MM_ALIGN_LOG2) & (align - 1);
+ extra = ((grub_addr_t) (cur + 1) >> GRUB_MM_ALIGN_LOG2) & (align - 1);
if (extra)
extra = align - extra;
- if (! p)
+ if (! cur)
grub_fatal ("null in the ring");
- if (p->magic != GRUB_MM_FREE_MAGIC)
- grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
+ if (cur->magic != GRUB_MM_FREE_MAGIC)
+ grub_fatal ("free magic is broken at %p: 0x%x", cur, cur->magic);
- if (p->size >= n + extra)
+ if (cur->size >= n + extra)
{
- extra += (p->size - extra - n) & (~(align - 1));
- if (extra == 0 && p->size == n)
+ extra += (cur->size - extra - n) & (~(align - 1));
+ if (extra == 0 && cur->size == n)
{
/* There is no special alignment requirement and memory block
is complete match.
@@ -222,9 +229,9 @@ grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
| alloc, size=n | |
+---------------+ v
*/
- q->next = p->next;
+ prev->next = cur->next;
}
- else if (align == 1 || p->size == n + extra)
+ else if (align == 1 || cur->size == n + extra)
{
/* There might be alignment requirement, when taking it into
account memory block fits in.
@@ -241,23 +248,22 @@ grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
| alloc, size=n | |
+---------------+ v
*/
-
- p->size -= n;
- p += p->size;
+ cur->size -= n;
+ cur += cur->size;
}
else if (extra == 0)
{
grub_mm_header_t r;
- r = p + extra + n;
+ r = cur + extra + n;
r->magic = GRUB_MM_FREE_MAGIC;
- r->size = p->size - extra - n;
- r->next = p->next;
- q->next = r;
+ r->size = cur->size - extra - n;
+ r->next = cur->next;
+ prev->next = r;
- if (q == p)
+ if (prev == cur)
{
- q = r;
+ prev = r;
r->next = r;
}
}
@@ -284,32 +290,32 @@ grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
*/
grub_mm_header_t r;
- r = p + extra + n;
+ r = cur + extra + n;
r->magic = GRUB_MM_FREE_MAGIC;
- r->size = p->size - extra - n;
- r->next = p;
+ r->size = cur->size - extra - n;
+ r->next = cur;
- p->size = extra;
- q->next = r;
- p += extra;
+ cur->size = extra;
+ prev->next = r;
+ cur += extra;
}
- p->magic = GRUB_MM_ALLOC_MAGIC;
- p->size = n;
+ cur->magic = GRUB_MM_ALLOC_MAGIC;
+ cur->size = n;
/* Mark find as a start marker for next allocation to fasten it.
This will have side effect of fragmenting memory as small
pieces before this will be un-used. */
/* So do it only for chunks under 64K. */
if (n < (0x8000 >> GRUB_MM_ALIGN_LOG2)
- || *first == p)
- *first = q;
+ || *first == cur)
+ *first = prev;
- return p + 1;
+ return cur + 1;
}
/* Search was completed without result. */
- if (p == *first)
+ if (cur == *first)
break;
}

View file

@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 25 Nov 2021 02:22:47 +1100
Subject: [PATCH] mm: grub_real_malloc(): Make small allocs comment match code
Small allocations move the region's *first pointer. The comment
says that this happens for allocations under 64K. The code says
it's for allocations under 32K. Commit 45bf8b3a7549 changed the
code intentionally: make the comment match.
Fixes: 45bf8b3a7549 (* grub-core/kern/mm.c (grub_real_malloc): Decrease cut-off of moving the)
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit a847895a8d000bdf27ad4d4326f883a0eed769ca)
---
grub-core/kern/mm.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index fb20e93acf..db7e0b2a5b 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -306,7 +306,7 @@ grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
/* Mark find as a start marker for next allocation to fasten it.
This will have side effect of fragmenting memory as small
pieces before this will be un-used. */
- /* So do it only for chunks under 64K. */
+ /* So do it only for chunks under 32K. */
if (n < (0x8000 >> GRUB_MM_ALIGN_LOG2)
|| *first == cur)
*first = prev;

View file

@ -0,0 +1,120 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 25 Nov 2021 02:22:48 +1100
Subject: [PATCH] mm: Document grub_free()
The grub_free() possesses a surprising number of quirks, and also
uses single-letter variable names confusingly to iterate through
the free list.
Document what's going on.
Use prev and cur to iterate over the free list.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit 1f8d0b01738e49767d662d6426af3570a64565f0)
---
grub-core/kern/mm.c | 63 ++++++++++++++++++++++++++++++++++-------------------
1 file changed, 41 insertions(+), 22 deletions(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index db7e0b2a5b..0351171cf9 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -446,54 +446,73 @@ grub_free (void *ptr)
}
else
{
- grub_mm_header_t q, s;
+ grub_mm_header_t cur, prev;
#if 0
- q = r->first;
+ cur = r->first;
do
{
grub_printf ("%s:%d: q=%p, q->size=0x%x, q->magic=0x%x\n",
- GRUB_FILE, __LINE__, q, q->size, q->magic);
- q = q->next;
+ GRUB_FILE, __LINE__, cur, cur->size, cur->magic);
+ cur = cur->next;
}
- while (q != r->first);
+ while (cur != r->first);
#endif
-
- for (s = r->first, q = s->next; q <= p || q->next >= p; s = q, q = s->next)
+ /* Iterate over all blocks in the free ring.
+ *
+ * The free ring is arranged from high addresses to low
+ * addresses, modulo wraparound.
+ *
+ * We are looking for a block with a higher address than p or
+ * whose next address is lower than p.
+ */
+ for (prev = r->first, cur = prev->next; cur <= p || cur->next >= p;
+ prev = cur, cur = prev->next)
{
- if (q->magic != GRUB_MM_FREE_MAGIC)
- grub_fatal ("free magic is broken at %p: 0x%x", q, q->magic);
+ if (cur->magic != GRUB_MM_FREE_MAGIC)
+ grub_fatal ("free magic is broken at %p: 0x%x", cur, cur->magic);
- if (q <= q->next && (q > p || q->next < p))
+ /* Deal with wrap-around */
+ if (cur <= cur->next && (cur > p || cur->next < p))
break;
}
+ /* mark p as free and insert it between cur and cur->next */
p->magic = GRUB_MM_FREE_MAGIC;
- p->next = q->next;
- q->next = p;
+ p->next = cur->next;
+ cur->next = p;
+ /*
+ * If the block we are freeing can be merged with the next
+ * free block, do that.
+ */
if (p->next + p->next->size == p)
{
p->magic = 0;
p->next->size += p->size;
- q->next = p->next;
+ cur->next = p->next;
p = p->next;
}
- r->first = q;
+ r->first = cur;
- if (q == p + p->size)
+ /* Likewise if can be merged with the preceeding free block */
+ if (cur == p + p->size)
{
- q->magic = 0;
- p->size += q->size;
- if (q == s)
- s = p;
- s->next = p;
- q = s;
+ cur->magic = 0;
+ p->size += cur->size;
+ if (cur == prev)
+ prev = p;
+ prev->next = p;
+ cur = prev;
}
- r->first = q;
+ /*
+ * Set r->first such that the just free()d block is tried first.
+ * (An allocation is tried from *first->next, and cur->next == p.)
+ */
+ r->first = cur;
}
}

View file

@ -0,0 +1,73 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 25 Nov 2021 02:22:49 +1100
Subject: [PATCH] mm: Document grub_mm_init_region()
The grub_mm_init_region() does some things that seem magical, especially
around region merging. Make it a bit clearer.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit 246d69b7ea619fc1e77dcc5960e37aea45a9808c)
---
grub-core/kern/mm.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index 0351171cf9..1cbf98c7ab 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -128,23 +128,52 @@ grub_mm_init_region (void *addr, grub_size_t size)
if (((grub_addr_t) addr + 0x1000) > ~(grub_addr_t) size)
size = ((grub_addr_t) -0x1000) - (grub_addr_t) addr;
+ /* Attempt to merge this region with every existing region */
for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
+ /*
+ * Is the new region immediately below an existing region? That
+ * is, is the address of the memory we're adding now (addr) + size
+ * of the memory we're adding (size) + the bytes we couldn't use
+ * at the start of the region we're considering (q->pre_size)
+ * equal to the address of q? In other words, does the memory
+ * looks like this?
+ *
+ * addr q
+ * |----size-----|-q->pre_size-|<q region>|
+ */
if ((grub_uint8_t *) addr + size + q->pre_size == (grub_uint8_t *) q)
{
+ /*
+ * Yes, we can merge the memory starting at addr into the
+ * existing region from below. Align up addr to GRUB_MM_ALIGN
+ * so that our new region has proper alignment.
+ */
r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
+ /* Copy the region data across */
*r = *q;
+ /* Consider all the new size as pre-size */
r->pre_size += size;
-
+
+ /*
+ * If we have enough pre-size to create a block, create a
+ * block with it. Mark it as allocated and pass it to
+ * grub_free (), which will sort out getting it into the free
+ * list.
+ */
if (r->pre_size >> GRUB_MM_ALIGN_LOG2)
{
h = (grub_mm_header_t) (r + 1);
+ /* block size is pre-size converted to cells */
h->size = (r->pre_size >> GRUB_MM_ALIGN_LOG2);
h->magic = GRUB_MM_ALLOC_MAGIC;
+ /* region size grows by block size converted back to bytes */
r->size += h->size << GRUB_MM_ALIGN_LOG2;
+ /* adjust pre_size to be accurate */
r->pre_size &= (GRUB_MM_ALIGN - 1);
*p = r;
grub_free (h + 1);
}
+ /* Replace the old region with the new region */
*p = r;
return;
}

View file

@ -0,0 +1,78 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 25 Nov 2021 02:22:45 +1100
Subject: [PATCH] mm: Document GRUB internal memory management structures
I spent more than a trivial quantity of time figuring out pre_size and
whether a memory region's size contains the header cell or not.
Document the meanings of all the properties. Hopefully now no-one else
has to figure it out!
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit a6c5c52ccffd2674d43db25fb4baa9c528526aa0)
---
include/grub/mm_private.h | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/include/grub/mm_private.h b/include/grub/mm_private.h
index c2c4cb1511..203533cc3d 100644
--- a/include/grub/mm_private.h
+++ b/include/grub/mm_private.h
@@ -21,15 +21,27 @@
#include <grub/mm.h>
+/* For context, see kern/mm.c */
+
/* Magic words. */
#define GRUB_MM_FREE_MAGIC 0x2d3c2808
#define GRUB_MM_ALLOC_MAGIC 0x6db08fa4
+/* A header describing a block of memory - either allocated or free */
typedef struct grub_mm_header
{
+ /*
+ * The 'next' free block in this region's circular free list.
+ * Only meaningful if the block is free.
+ */
struct grub_mm_header *next;
+ /* The block size, not in bytes but the number of cells of
+ * GRUB_MM_ALIGN bytes. Includes the header cell.
+ */
grub_size_t size;
+ /* either free or alloc magic, depending on the block type. */
grub_size_t magic;
+ /* pad to cell size: see the top of kern/mm.c. */
#if GRUB_CPU_SIZEOF_VOID_P == 4
char padding[4];
#elif GRUB_CPU_SIZEOF_VOID_P == 8
@@ -48,11 +60,27 @@ typedef struct grub_mm_header
#define GRUB_MM_ALIGN (1 << GRUB_MM_ALIGN_LOG2)
+/* A region from which we can make allocations. */
typedef struct grub_mm_region
{
+ /* The first free block in this region. */
struct grub_mm_header *first;
+
+ /*
+ * The next region in the linked list of regions. Regions are initially
+ * sorted in order of increasing size, but can grow, in which case the
+ * ordering may not be preserved.
+ */
struct grub_mm_region *next;
+
+ /*
+ * A grub_mm_region will always be aligned to cell size. The pre-size is
+ * the number of bytes we were given but had to skip in order to get that
+ * alignment.
+ */
grub_size_t pre_size;
+
+ /* How many bytes are in this region? (free and allocated) */
grub_size_t size;
}
*grub_mm_region_t;

View file

@ -0,0 +1,56 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 21 Apr 2022 15:24:14 +1000
Subject: [PATCH] mm: Assert that we preserve header vs region alignment
grub_mm_region_init() does:
h = (grub_mm_header_t) (r + 1);
where h is a grub_mm_header_t and r is a grub_mm_region_t.
Cells are supposed to be GRUB_MM_ALIGN aligned, but while grub_mm_dump
ensures this vs the region header, grub_mm_region_init() does not.
It's better to be explicit than implicit here: rather than changing
grub_mm_region_init() to ALIGN_UP(), require that the struct is
explicitly a multiple of the header size.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 1df8fe66c57087eb33bd6dc69f786ed124615aa7)
---
include/grub/mm_private.h | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/include/grub/mm_private.h b/include/grub/mm_private.h
index 203533cc3d..a688b92a83 100644
--- a/include/grub/mm_private.h
+++ b/include/grub/mm_private.h
@@ -20,6 +20,7 @@
#define GRUB_MM_PRIVATE_H 1
#include <grub/mm.h>
+#include <grub/misc.h>
/* For context, see kern/mm.c */
@@ -89,4 +90,17 @@ typedef struct grub_mm_region
extern grub_mm_region_t EXPORT_VAR (grub_mm_base);
#endif
+static inline void
+grub_mm_size_sanity_check (void) {
+ /* Ensure we preserve alignment when doing h = (grub_mm_header_t) (r + 1). */
+ COMPILE_TIME_ASSERT ((sizeof (struct grub_mm_region) %
+ sizeof (struct grub_mm_header)) == 0);
+
+ /*
+ * GRUB_MM_ALIGN is supposed to represent cell size, and a mm_header is
+ * supposed to be 1 cell.
+ */
+ COMPILE_TIME_ASSERT (sizeof (struct grub_mm_header) == GRUB_MM_ALIGN);
+}
+
#endif

View file

@ -0,0 +1,203 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 21 Apr 2022 15:24:15 +1000
Subject: [PATCH] mm: When adding a region, merge with region after as well as
before
On x86_64-efi (at least) regions seem to be added from top down. The mm
code will merge a new region with an existing region that comes
immediately before the new region. This allows larger allocations to be
satisfied that would otherwise be the case.
On powerpc-ieee1275, however, regions are added from bottom up. So if
we add 3x 32MB regions, we can still only satisfy a 32MB allocation,
rather than the 96MB allocation we might otherwise be able to satisfy.
* Define 'post_size' as being bytes lost to the end of an allocation
due to being given weird sizes from firmware that are not multiples
of GRUB_MM_ALIGN.
* Allow merging of regions immediately _after_ existing regions, not
just before. As with the other approach, we create an allocated
block to represent the new space and the pass it to grub_free() to
get the metadata right.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Tested-by: Stefan Berger <stefanb@linux.ibm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 052e6068be622ff53f1238b449c300dbd0a8abcd)
---
grub-core/kern/mm.c | 128 +++++++++++++++++++++++++++++-----------------
include/grub/mm_private.h | 9 ++++
2 files changed, 91 insertions(+), 46 deletions(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index 1cbf98c7ab..7be33e23bf 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -130,53 +130,88 @@ grub_mm_init_region (void *addr, grub_size_t size)
/* Attempt to merge this region with every existing region */
for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
- /*
- * Is the new region immediately below an existing region? That
- * is, is the address of the memory we're adding now (addr) + size
- * of the memory we're adding (size) + the bytes we couldn't use
- * at the start of the region we're considering (q->pre_size)
- * equal to the address of q? In other words, does the memory
- * looks like this?
- *
- * addr q
- * |----size-----|-q->pre_size-|<q region>|
- */
- if ((grub_uint8_t *) addr + size + q->pre_size == (grub_uint8_t *) q)
- {
- /*
- * Yes, we can merge the memory starting at addr into the
- * existing region from below. Align up addr to GRUB_MM_ALIGN
- * so that our new region has proper alignment.
- */
- r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
- /* Copy the region data across */
- *r = *q;
- /* Consider all the new size as pre-size */
- r->pre_size += size;
+ {
+ /*
+ * Is the new region immediately below an existing region? That
+ * is, is the address of the memory we're adding now (addr) + size
+ * of the memory we're adding (size) + the bytes we couldn't use
+ * at the start of the region we're considering (q->pre_size)
+ * equal to the address of q? In other words, does the memory
+ * looks like this?
+ *
+ * addr q
+ * |----size-----|-q->pre_size-|<q region>|
+ */
+ if ((grub_uint8_t *) addr + size + q->pre_size == (grub_uint8_t *) q)
+ {
+ /*
+ * Yes, we can merge the memory starting at addr into the
+ * existing region from below. Align up addr to GRUB_MM_ALIGN
+ * so that our new region has proper alignment.
+ */
+ r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
+ /* Copy the region data across */
+ *r = *q;
+ /* Consider all the new size as pre-size */
+ r->pre_size += size;
- /*
- * If we have enough pre-size to create a block, create a
- * block with it. Mark it as allocated and pass it to
- * grub_free (), which will sort out getting it into the free
- * list.
- */
- if (r->pre_size >> GRUB_MM_ALIGN_LOG2)
- {
- h = (grub_mm_header_t) (r + 1);
- /* block size is pre-size converted to cells */
- h->size = (r->pre_size >> GRUB_MM_ALIGN_LOG2);
- h->magic = GRUB_MM_ALLOC_MAGIC;
- /* region size grows by block size converted back to bytes */
- r->size += h->size << GRUB_MM_ALIGN_LOG2;
- /* adjust pre_size to be accurate */
- r->pre_size &= (GRUB_MM_ALIGN - 1);
- *p = r;
- grub_free (h + 1);
- }
- /* Replace the old region with the new region */
- *p = r;
- return;
- }
+ /*
+ * If we have enough pre-size to create a block, create a
+ * block with it. Mark it as allocated and pass it to
+ * grub_free (), which will sort out getting it into the free
+ * list.
+ */
+ if (r->pre_size >> GRUB_MM_ALIGN_LOG2)
+ {
+ h = (grub_mm_header_t) (r + 1);
+ /* block size is pre-size converted to cells */
+ h->size = (r->pre_size >> GRUB_MM_ALIGN_LOG2);
+ h->magic = GRUB_MM_ALLOC_MAGIC;
+ /* region size grows by block size converted back to bytes */
+ r->size += h->size << GRUB_MM_ALIGN_LOG2;
+ /* adjust pre_size to be accurate */
+ r->pre_size &= (GRUB_MM_ALIGN - 1);
+ *p = r;
+ grub_free (h + 1);
+ }
+ /* Replace the old region with the new region */
+ *p = r;
+ return;
+ }
+
+ /*
+ * Is the new region immediately above an existing region? That
+ * is:
+ * q addr
+ * |<q region>|-q->post_size-|----size-----|
+ */
+ if ((grub_uint8_t *) q + sizeof (*q) + q->size + q->post_size ==
+ (grub_uint8_t *) addr)
+ {
+ /*
+ * Yes! Follow a similar pattern to above, but simpler.
+ * Our header starts at address - post_size, which should align us
+ * to a cell boundary.
+ *
+ * Cast to (void *) first to avoid the following build error:
+ * kern/mm.c: In function grub_mm_init_region:
+ * kern/mm.c:211:15: error: cast increases required alignment of target type [-Werror=cast-align]
+ * 211 | h = (grub_mm_header_t) ((grub_uint8_t *) addr - q->post_size);
+ * | ^
+ * It is safe to do that because proper alignment is enforced in grub_mm_size_sanity_check().
+ */
+ h = (grub_mm_header_t)(void *) ((grub_uint8_t *) addr - q->post_size);
+ /* our size is the allocated size plus post_size, in cells */
+ h->size = (size + q->post_size) >> GRUB_MM_ALIGN_LOG2;
+ h->magic = GRUB_MM_ALLOC_MAGIC;
+ /* region size grows by block size converted back to bytes */
+ q->size += h->size << GRUB_MM_ALIGN_LOG2;
+ /* adjust new post_size to be accurate */
+ q->post_size = (q->post_size + size) & (GRUB_MM_ALIGN - 1);
+ grub_free (h + 1);
+ return;
+ }
+ }
/* Allocate a region from the head. */
r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
@@ -195,6 +230,7 @@ grub_mm_init_region (void *addr, grub_size_t size)
r->first = h;
r->pre_size = (grub_addr_t) r - (grub_addr_t) addr;
r->size = (h->size << GRUB_MM_ALIGN_LOG2);
+ r->post_size = size - r->size;
/* Find where to insert this region. Put a smaller one before bigger ones,
to prevent fragmentation. */
diff --git a/include/grub/mm_private.h b/include/grub/mm_private.h
index a688b92a83..96c2d816be 100644
--- a/include/grub/mm_private.h
+++ b/include/grub/mm_private.h
@@ -81,8 +81,17 @@ typedef struct grub_mm_region
*/
grub_size_t pre_size;
+ /*
+ * Likewise, the post-size is the number of bytes we wasted at the end
+ * of the allocation because it wasn't a multiple of GRUB_MM_ALIGN
+ */
+ grub_size_t post_size;
+
/* How many bytes are in this region? (free and allocated) */
grub_size_t size;
+
+ /* pad to a multiple of cell size */
+ char padding[3 * GRUB_CPU_SIZEOF_VOID_P];
}
*grub_mm_region_t;

View file

@ -0,0 +1,71 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Thu, 21 Apr 2022 15:24:16 +1000
Subject: [PATCH] mm: Debug support for region operations
This is handy for debugging. Enable with "set debug=regions".
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 8afa5ef45b797ba5d8147ceee85ac2c59dcc7f09)
---
grub-core/kern/mm.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index 7be33e23bf..38bfb01df9 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -115,9 +115,8 @@ grub_mm_init_region (void *addr, grub_size_t size)
grub_mm_header_t h;
grub_mm_region_t r, *p, q;
-#if 0
- grub_printf ("Using memory for heap: start=%p, end=%p\n", addr, addr + (unsigned int) size);
-#endif
+ grub_dprintf ("regions", "Using memory for heap: start=%p, end=%p\n",
+ addr, (char *) addr + (unsigned int) size);
/* Exclude last 4K to avoid overflows. */
/* If addr + 0x1000 overflows then whole region is in excluded zone. */
@@ -142,8 +141,14 @@ grub_mm_init_region (void *addr, grub_size_t size)
* addr q
* |----size-----|-q->pre_size-|<q region>|
*/
+ grub_dprintf ("regions", "Can we extend into region above?"
+ " %p + %" PRIxGRUB_SIZE " + %" PRIxGRUB_SIZE " ?=? %p\n",
+ (grub_uint8_t *) addr, size, q->pre_size, (grub_uint8_t *) q);
if ((grub_uint8_t *) addr + size + q->pre_size == (grub_uint8_t *) q)
{
+ grub_dprintf ("regions", "Yes: extending a region: (%p -> %p) -> (%p -> %p)\n",
+ q, (grub_uint8_t *) q + sizeof (*q) + q->size,
+ addr, (grub_uint8_t *) q + sizeof (*q) + q->size);
/*
* Yes, we can merge the memory starting at addr into the
* existing region from below. Align up addr to GRUB_MM_ALIGN
@@ -185,9 +190,15 @@ grub_mm_init_region (void *addr, grub_size_t size)
* q addr
* |<q region>|-q->post_size-|----size-----|
*/
+ grub_dprintf ("regions", "Can we extend into region below?"
+ " %p + %" PRIxGRUB_SIZE " + %" PRIxGRUB_SIZE " + %" PRIxGRUB_SIZE " ?=? %p\n",
+ (grub_uint8_t *) q, sizeof(*q), q->size, q->post_size, (grub_uint8_t *) addr);
if ((grub_uint8_t *) q + sizeof (*q) + q->size + q->post_size ==
(grub_uint8_t *) addr)
{
+ grub_dprintf ("regions", "Yes: extending a region: (%p -> %p) -> (%p -> %p)\n",
+ q, (grub_uint8_t *) q + sizeof (*q) + q->size,
+ q, (grub_uint8_t *) addr + size);
/*
* Yes! Follow a similar pattern to above, but simpler.
* Our header starts at address - post_size, which should align us
@@ -213,6 +224,8 @@ grub_mm_init_region (void *addr, grub_size_t size)
}
}
+ grub_dprintf ("regions", "No: considering a new region at %p of size %" PRIxGRUB_SIZE "\n",
+ addr, size);
/* Allocate a region from the head. */
r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);

View file

@ -0,0 +1,80 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Steinhardt <ps@pks.im>
Date: Thu, 21 Apr 2022 15:24:17 +1000
Subject: [PATCH] mm: Drop unused unloading of modules on OOM
In grub_memalign(), there's a commented section which would allow for
unloading of unneeded modules in case where there is not enough free
memory available to satisfy a request. Given that this code is never
compiled in, let's remove it together with grub_dl_unload_unneeded().
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 139fd9b134a01e0b5fe0ebefafa7f48d1ffb6d60)
---
grub-core/kern/dl.c | 20 --------------------
grub-core/kern/mm.c | 8 --------
include/grub/dl.h | 1 -
3 files changed, 29 deletions(-)
diff --git a/grub-core/kern/dl.c b/grub-core/kern/dl.c
index d5de80186f..ab9101a5ad 100644
--- a/grub-core/kern/dl.c
+++ b/grub-core/kern/dl.c
@@ -998,23 +998,3 @@ grub_dl_unload (grub_dl_t mod)
grub_free (mod);
return 1;
}
-
-/* Unload unneeded modules. */
-void
-grub_dl_unload_unneeded (void)
-{
- /* Because grub_dl_remove modifies the list of modules, this
- implementation is tricky. */
- grub_dl_t p = grub_dl_head;
-
- while (p)
- {
- if (grub_dl_unload (p))
- {
- p = grub_dl_head;
- continue;
- }
-
- p = p->next;
- }
-}
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index 38bfb01df9..1825dc8289 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -444,14 +444,6 @@ grub_memalign (grub_size_t align, grub_size_t size)
count++;
goto again;
-#if 0
- case 1:
- /* Unload unneeded modules. */
- grub_dl_unload_unneeded ();
- count++;
- goto again;
-#endif
-
default:
break;
}
diff --git a/include/grub/dl.h b/include/grub/dl.h
index 45ac8e339f..6bc2560bf0 100644
--- a/include/grub/dl.h
+++ b/include/grub/dl.h
@@ -206,7 +206,6 @@ grub_dl_t EXPORT_FUNC(grub_dl_load) (const char *name);
grub_dl_t grub_dl_load_core (void *addr, grub_size_t size);
grub_dl_t EXPORT_FUNC(grub_dl_load_core_noinit) (void *addr, grub_size_t size);
int EXPORT_FUNC(grub_dl_unload) (grub_dl_t mod);
-extern void grub_dl_unload_unneeded (void);
extern int EXPORT_FUNC(grub_dl_ref) (grub_dl_t mod);
extern int EXPORT_FUNC(grub_dl_unref) (grub_dl_t mod);
extern int EXPORT_FUNC(grub_dl_ref_count) (grub_dl_t mod);

View file

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

View file

@ -0,0 +1,103 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Steinhardt <ps@pks.im>
Date: Thu, 21 Apr 2022 15:24:19 +1000
Subject: [PATCH] kern/efi/mm: Always request a fixed number of pages on init
When initializing the EFI memory subsystem, we will by default request
a quarter of the available memory, bounded by a minimum/maximum value.
Given that we're about to extend the EFI memory system to dynamically
request additional pages from the firmware as required, this scaling of
requested memory based on available memory will not make a lot of sense
anymore.
Remove this logic as a preparatory patch such that we'll instead defer
to the runtime memory allocator. Note that ideally, we'd want to change
this after dynamic requesting of pages has been implemented for the EFI
platform. But because we'll need to split up initialization of the
memory subsystem and the request of pages from the firmware, we'd have
to duplicate quite some logic at first only to remove it afterwards
again. This seems quite pointless, so we instead have patches slightly
out of order.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 938c3760b8c0fca759140be48307179b50107ff6)
---
grub-core/kern/efi/mm.c | 35 +++--------------------------------
1 file changed, 3 insertions(+), 32 deletions(-)
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
index e460b072e6..782a1365a1 100644
--- a/grub-core/kern/efi/mm.c
+++ b/grub-core/kern/efi/mm.c
@@ -38,9 +38,8 @@
a multiplier of 4KB. */
#define MEMORY_MAP_SIZE 0x3000
-/* The minimum and maximum heap size for GRUB itself. */
-#define MIN_HEAP_SIZE 0x100000
-#define MAX_HEAP_SIZE (1600 * 0x100000)
+/* The default heap size for GRUB itself in bytes. */
+#define DEFAULT_HEAP_SIZE 0x100000
static void *finish_mmap_buf = 0;
static grub_efi_uintn_t finish_mmap_size = 0;
@@ -514,23 +513,6 @@ filter_memory_map (grub_efi_memory_descriptor_t *memory_map,
return filtered_desc;
}
-/* Return the total number of pages. */
-static grub_efi_uint64_t
-get_total_pages (grub_efi_memory_descriptor_t *memory_map,
- grub_efi_uintn_t desc_size,
- grub_efi_memory_descriptor_t *memory_map_end)
-{
- grub_efi_memory_descriptor_t *desc;
- grub_efi_uint64_t total = 0;
-
- for (desc = memory_map;
- desc < memory_map_end;
- desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size))
- total += desc->num_pages;
-
- return total;
-}
-
/* Add memory regions. */
static void
add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
@@ -694,8 +676,6 @@ grub_efi_mm_init (void)
grub_efi_memory_descriptor_t *filtered_memory_map_end;
grub_efi_uintn_t map_size;
grub_efi_uintn_t desc_size;
- grub_efi_uint64_t total_pages;
- grub_efi_uint64_t required_pages;
int mm_status;
grub_nx_init ();
@@ -737,22 +717,13 @@ grub_efi_mm_init (void)
filtered_memory_map_end = filter_memory_map (memory_map, filtered_memory_map,
desc_size, memory_map_end);
- /* By default, request a quarter of the available memory. */
- total_pages = get_total_pages (filtered_memory_map, desc_size,
- filtered_memory_map_end);
- required_pages = (total_pages >> 2);
- if (required_pages < BYTES_TO_PAGES (MIN_HEAP_SIZE))
- required_pages = BYTES_TO_PAGES (MIN_HEAP_SIZE);
- else if (required_pages > BYTES_TO_PAGES (MAX_HEAP_SIZE))
- required_pages = BYTES_TO_PAGES (MAX_HEAP_SIZE);
-
/* Sort the filtered descriptors, so that GRUB can allocate pages
from smaller regions. */
sort_memory_map (filtered_memory_map, desc_size, filtered_memory_map_end);
/* Allocate memory regions for GRUB's memory management. */
add_memory_regions (filtered_memory_map, desc_size,
- filtered_memory_map_end, required_pages);
+ filtered_memory_map_end, BYTES_TO_PAGES (DEFAULT_HEAP_SIZE));
#if 0
/* For debug. */

View file

@ -0,0 +1,85 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Steinhardt <ps@pks.im>
Date: Thu, 21 Apr 2022 15:24:20 +1000
Subject: [PATCH] kern/efi/mm: Extract function to add memory regions
In preparation of support for runtime-allocating additional memory
region, this patch extracts the function to retrieve the EFI memory
map and add a subset of it to GRUB's own memory regions.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 96a7ea29e3cb61b6c2302e260e8e6a6117e17fa3)
[rharwood: backport around our nx]
---
grub-core/kern/efi/mm.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
index 782a1365a1..a1d3b51fe6 100644
--- a/grub-core/kern/efi/mm.c
+++ b/grub-core/kern/efi/mm.c
@@ -667,8 +667,8 @@ grub_nx_init (void)
}
}
-void
-grub_efi_mm_init (void)
+static grub_err_t
+grub_efi_mm_add_regions (grub_size_t required_bytes)
{
grub_efi_memory_descriptor_t *memory_map;
grub_efi_memory_descriptor_t *memory_map_end;
@@ -683,7 +683,7 @@ grub_efi_mm_init (void)
/* Prepare a memory region to store two memory maps. */
memory_map = grub_efi_allocate_any_pages (2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
if (! memory_map)
- grub_fatal ("cannot allocate memory");
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate memory for memory map");
/* Obtain descriptors for available memory. */
map_size = MEMORY_MAP_SIZE;
@@ -701,14 +701,14 @@ grub_efi_mm_init (void)
memory_map = grub_efi_allocate_any_pages (2 * BYTES_TO_PAGES (map_size));
if (! memory_map)
- grub_fatal ("cannot allocate memory");
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate memory for new memory map");
mm_status = grub_efi_get_memory_map (&map_size, memory_map, 0,
&desc_size, 0);
}
if (mm_status < 0)
- grub_fatal ("cannot get memory map");
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, "error fetching memory map from EFI");
memory_map_end = NEXT_MEMORY_DESCRIPTOR (memory_map, map_size);
@@ -723,7 +723,7 @@ grub_efi_mm_init (void)
/* Allocate memory regions for GRUB's memory management. */
add_memory_regions (filtered_memory_map, desc_size,
- filtered_memory_map_end, BYTES_TO_PAGES (DEFAULT_HEAP_SIZE));
+ filtered_memory_map_end, BYTES_TO_PAGES (required_bytes));
#if 0
/* For debug. */
@@ -741,6 +741,15 @@ grub_efi_mm_init (void)
/* Release the memory maps. */
grub_efi_free_pages ((grub_addr_t) memory_map,
2 * BYTES_TO_PAGES (MEMORY_MAP_SIZE));
+
+ return GRUB_ERR_NONE;
+}
+
+void
+grub_efi_mm_init (void)
+{
+ if (grub_efi_mm_add_regions (DEFAULT_HEAP_SIZE) != GRUB_ERR_NONE)
+ grub_fatal ("%s", grub_errmsg);
}
#if defined (__aarch64__) || defined (__arm__) || defined (__riscv)

View file

@ -0,0 +1,87 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Steinhardt <ps@pks.im>
Date: Thu, 21 Apr 2022 15:24:21 +1000
Subject: [PATCH] kern/efi/mm: Pass up errors from add_memory_regions()
The function add_memory_regions() is currently only called on system
initialization to allocate a fixed amount of pages. As such, it didn't
need to return any errors: in case it failed, we cannot proceed anyway.
This will change with the upcoming support for requesting more memory
from the firmware at runtime, where it doesn't make sense anymore to
fail hard.
Refactor the function to return an error to prepare for this. Note that
this does not change the behaviour when initializing the memory system
because grub_efi_mm_init() knows to call grub_fatal() in case
grub_efi_mm_add_regions() returns an error.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 15a015698921240adc1ac266a3b5bc5fcbd81521)
---
grub-core/kern/efi/mm.c | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
index a1d3b51fe6..e0ebc65dba 100644
--- a/grub-core/kern/efi/mm.c
+++ b/grub-core/kern/efi/mm.c
@@ -514,7 +514,7 @@ filter_memory_map (grub_efi_memory_descriptor_t *memory_map,
}
/* Add memory regions. */
-static void
+static grub_err_t
add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
grub_efi_uintn_t desc_size,
grub_efi_memory_descriptor_t *memory_map_end,
@@ -542,9 +542,9 @@ add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
GRUB_EFI_ALLOCATE_ADDRESS,
GRUB_EFI_LOADER_CODE);
if (! addr)
- grub_fatal ("cannot allocate conventional memory %p with %u pages",
- (void *) ((grub_addr_t) start),
- (unsigned) pages);
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ "Memory starting at %p (%u pages) marked as free, but EFI would not allocate",
+ (void *) ((grub_addr_t) start), (unsigned) pages);
grub_mm_init_region (addr, PAGES_TO_BYTES (pages));
@@ -554,7 +554,11 @@ add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
}
if (required_pages > 0)
- grub_fatal ("too little memory");
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY,
+ "could not allocate all requested memory: %" PRIuGRUB_UINT64_T " pages still required after iterating EFI memory map",
+ required_pages);
+
+ return GRUB_ERR_NONE;
}
void
@@ -676,6 +680,7 @@ grub_efi_mm_add_regions (grub_size_t required_bytes)
grub_efi_memory_descriptor_t *filtered_memory_map_end;
grub_efi_uintn_t map_size;
grub_efi_uintn_t desc_size;
+ grub_err_t err;
int mm_status;
grub_nx_init ();
@@ -722,8 +727,11 @@ grub_efi_mm_add_regions (grub_size_t required_bytes)
sort_memory_map (filtered_memory_map, desc_size, filtered_memory_map_end);
/* Allocate memory regions for GRUB's memory management. */
- add_memory_regions (filtered_memory_map, desc_size,
- filtered_memory_map_end, BYTES_TO_PAGES (required_bytes));
+ err = add_memory_regions (filtered_memory_map, desc_size,
+ filtered_memory_map_end,
+ BYTES_TO_PAGES (required_bytes));
+ if (err != GRUB_ERR_NONE)
+ return err;
#if 0
/* For debug. */

View file

@ -0,0 +1,75 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Steinhardt <ps@pks.im>
Date: Thu, 21 Apr 2022 15:24:22 +1000
Subject: [PATCH] kern/efi/mm: Implement runtime addition of pages
Adjust the interface of grub_efi_mm_add_regions() to take a set of
GRUB_MM_ADD_REGION_* flags, which most notably is currently only the
GRUB_MM_ADD_REGION_CONSECUTIVE flag. This allows us to set the function
up as callback for the memory subsystem and have it call out to us in
case there's not enough pages available in the current heap.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 1df2934822df4c1170dde069d97cfbf7a9572bba)
---
grub-core/kern/efi/mm.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
index e0ebc65dba..016ba6cf2f 100644
--- a/grub-core/kern/efi/mm.c
+++ b/grub-core/kern/efi/mm.c
@@ -518,7 +518,8 @@ static grub_err_t
add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
grub_efi_uintn_t desc_size,
grub_efi_memory_descriptor_t *memory_map_end,
- grub_efi_uint64_t required_pages)
+ grub_efi_uint64_t required_pages,
+ unsigned int flags)
{
grub_efi_memory_descriptor_t *desc;
@@ -532,6 +533,10 @@ add_memory_regions (grub_efi_memory_descriptor_t *memory_map,
start = desc->physical_start;
pages = desc->num_pages;
+
+ if (pages < required_pages && (flags & GRUB_MM_ADD_REGION_CONSECUTIVE))
+ continue;
+
if (pages > required_pages)
{
start += PAGES_TO_BYTES (pages - required_pages);
@@ -672,7 +677,7 @@ grub_nx_init (void)
}
static grub_err_t
-grub_efi_mm_add_regions (grub_size_t required_bytes)
+grub_efi_mm_add_regions (grub_size_t required_bytes, unsigned int flags)
{
grub_efi_memory_descriptor_t *memory_map;
grub_efi_memory_descriptor_t *memory_map_end;
@@ -729,7 +734,8 @@ grub_efi_mm_add_regions (grub_size_t required_bytes)
/* Allocate memory regions for GRUB's memory management. */
err = add_memory_regions (filtered_memory_map, desc_size,
filtered_memory_map_end,
- BYTES_TO_PAGES (required_bytes));
+ BYTES_TO_PAGES (required_bytes),
+ flags);
if (err != GRUB_ERR_NONE)
return err;
@@ -756,8 +762,9 @@ grub_efi_mm_add_regions (grub_size_t required_bytes)
void
grub_efi_mm_init (void)
{
- if (grub_efi_mm_add_regions (DEFAULT_HEAP_SIZE) != GRUB_ERR_NONE)
+ if (grub_efi_mm_add_regions (DEFAULT_HEAP_SIZE, GRUB_MM_ADD_REGION_NONE) != GRUB_ERR_NONE)
grub_fatal ("%s", grub_errmsg);
+ grub_mm_add_region_fn = grub_efi_mm_add_regions;
}
#if defined (__aarch64__) || defined (__arm__) || defined (__riscv)

View file

@ -0,0 +1,31 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Daniel Axtens <dja@axtens.net>
Date: Tue, 20 Sep 2022 00:30:30 +1000
Subject: [PATCH] efi: Increase default memory allocation to 32 MiB
We have multiple reports of things being slower with a 1 MiB initial static
allocation, and a report (more difficult to nail down) of a boot failure
as a result of the smaller initial allocation.
Make the initial memory allocation 32 MiB.
Signed-off-by: Daniel Axtens <dja@axtens.net>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
(cherry picked from commit 75e38e86e7d9202f050b093f20500d9ad4c6dad9)
---
grub-core/kern/efi/mm.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
index 016ba6cf2f..b27e966e1f 100644
--- a/grub-core/kern/efi/mm.c
+++ b/grub-core/kern/efi/mm.c
@@ -39,7 +39,7 @@
#define MEMORY_MAP_SIZE 0x3000
/* The default heap size for GRUB itself in bytes. */
-#define DEFAULT_HEAP_SIZE 0x100000
+#define DEFAULT_HEAP_SIZE 0x2000000
static void *finish_mmap_buf = 0;
static grub_efi_uintn_t finish_mmap_size = 0;

View file

@ -0,0 +1,57 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zhang Boyang <zhangboyang.id@gmail.com>
Date: Sat, 15 Oct 2022 22:15:11 +0800
Subject: [PATCH] mm: Try invalidate disk caches last when out of memory
Every heap grow will cause all disk caches invalidated which decreases
performance severely. This patch moves disk cache invalidation code to
the last of memory squeezing measures. So, disk caches are released only
when there are no other ways to get free memory.
Signed-off-by: Zhang Boyang <zhangboyang.id@gmail.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Reviewed-by: Patrick Steinhardt <ps@pks.im>
(cherry picked from commit 17975d10a80e2457e5237f87fa58a7943031983e)
---
grub-core/kern/mm.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/grub-core/kern/mm.c b/grub-core/kern/mm.c
index f2e27f263b..da1ac9427c 100644
--- a/grub-core/kern/mm.c
+++ b/grub-core/kern/mm.c
@@ -443,12 +443,6 @@ grub_memalign (grub_size_t align, grub_size_t size)
switch (count)
{
case 0:
- /* Invalidate disk caches. */
- grub_disk_cache_invalidate_all ();
- count++;
- goto again;
-
- case 1:
/* Request additional pages, contiguous */
count++;
@@ -458,7 +452,7 @@ grub_memalign (grub_size_t align, grub_size_t size)
/* fallthrough */
- case 2:
+ case 1:
/* Request additional pages, anything at all */
count++;
@@ -474,6 +468,12 @@ grub_memalign (grub_size_t align, grub_size_t size)
/* fallthrough */
+ case 2:
+ /* Invalidate disk caches. */
+ grub_disk_cache_invalidate_all ();
+ count++;
+ goto again;
+
default:
break;
}

View file

@ -299,4 +299,19 @@ Patch0298: 0298-font-Assign-null_font-to-glyphs-in-ascii_font_glyph.patch
Patch0299: 0299-normal-charset-Fix-an-integer-overflow-in-grub_unico.patch
Patch0300: 0300-font-Try-opening-fonts-from-the-bundled-memdisk.patch
Patch0301: 0301-Correction-in-vector-5-values.patch
Patch0302: 0302-Allow-internal-grub-allocations-over-4GB.patch
Patch0302: 0302-mm-Clarify-grub_real_malloc.patch
Patch0303: 0303-mm-grub_real_malloc-Make-small-allocs-comment-match-.patch
Patch0304: 0304-mm-Document-grub_free.patch
Patch0305: 0305-mm-Document-grub_mm_init_region.patch
Patch0306: 0306-mm-Document-GRUB-internal-memory-management-structur.patch
Patch0307: 0307-mm-Assert-that-we-preserve-header-vs-region-alignmen.patch
Patch0308: 0308-mm-When-adding-a-region-merge-with-region-after-as-w.patch
Patch0309: 0309-mm-Debug-support-for-region-operations.patch
Patch0310: 0310-mm-Drop-unused-unloading-of-modules-on-OOM.patch
Patch0311: 0311-mm-Allow-dynamically-requesting-additional-memory-re.patch
Patch0312: 0312-kern-efi-mm-Always-request-a-fixed-number-of-pages-o.patch
Patch0313: 0313-kern-efi-mm-Extract-function-to-add-memory-regions.patch
Patch0314: 0314-kern-efi-mm-Pass-up-errors-from-add_memory_regions.patch
Patch0315: 0315-kern-efi-mm-Implement-runtime-addition-of-pages.patch
Patch0316: 0316-efi-Increase-default-memory-allocation-to-32-MiB.patch
Patch0317: 0317-mm-Try-invalidate-disk-caches-last-when-out-of-memor.patch

View file

@ -17,7 +17,7 @@
Name: grub2
Epoch: 1
Version: 2.06
Release: 74%{?dist}
Release: 75%{?dist}
Summary: Bootloader with support for Linux, Multiboot and more
License: GPLv3+
URL: http://www.gnu.org/software/grub/
@ -544,6 +544,9 @@ mv ${EFI_HOME}/grub.cfg.stb ${EFI_HOME}/grub.cfg
%endif
%changelog
* Wed Jan 11 2023 Robbie Harwood <rharwood@redhat.com> - 2.06-75
- Pull allocator improvements from upstream
* Wed Jan 11 2023 Robbie Harwood <rharwood@redhat.com> - 2.06-74
- Apply more hardening to host binaries