Blame SOURCES/0299-mm-When-adding-a-region-merge-with-region-after-as-w.patch

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