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