grub2/SOURCES/0299-mm-When-adding-a-regio...

204 lines
7.9 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;