grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 3/3] kern/efi/mm: introduce 'mmunlimited' for allocates on higher


From: Jeremy Szu
Subject: [PATCH 3/3] kern/efi/mm: introduce 'mmunlimited' for allocates on higher memories.
Date: Tue, 6 Dec 2022 15:35:21 +0800

In some recent EFI firwmare implmentation, the Loader-Data, Loader-Code
and Conventional memory regions (grub loader prefer to use) are located
under 1G (for example some HP platforms). Which causes there are only
~700MB on a 8G HP platform. (There are other 4G+ memories available above
0x100000000)

When verifier enabled (e.g. tpm), it allocates verified->buf to store
initrd for verification when grub_initrd_init().
Also, grub_relocator_alloc_chunk_align() allocates the other memory
region for loading initrd by kernel which needs to respect
initrd_addr_max from kernel protocol (but verifier).
Thus, grub_initrd_load() read the file to loadable address after
verification.
However, above steps consume double size of initrd.

In some cases, the OS put many modules in initrd (e.g. kernel drivers,
installation stuff, and for some reasons, not able to compress initrd)
which growth the initrd.

In a real world, some HP machines are not able to boot if initrd greater
than 120M~180M.

Therefore, introduce a new environment "mmunlimited" for some cutting
edge devices (which only works on newer kernel / firmware as well as all
64bits).

When enabling "mmunlimited", it will ignore some legacy memory
limitation as well as request the memory as much as possible.

  * grub-core/kern/efi/mm.c
    - Read the mmunlimited environment to adjust the maximum address
      when allocats the memory.
  * grub-core/loader/i386/linux.c
    - Parse kernel protocol 2.13 to check if kernel related binaries can
      be loaded above 4G.
    - Read the mmunlimited environment to unlimited memory allocations
      for kernel and initrd.
  * include/grub/i386/linux.h
    - Add two macro to get high_32 and low_32 bits from an address.
    - Add maximum address for 32/64 bits.
  * include/grub/x86_64/efi/memory.h
    - Add maximum usable address for 32/64 bits.
---
 grub-core/kern/efi/mm.c          |  34 +++++++---
 grub-core/loader/i386/linux.c    | 105 +++++++++++++++++++++++++++++--
 include/grub/i386/linux.h        |  17 +++++
 include/grub/x86_64/efi/memory.h |   8 ++-
 4 files changed, 149 insertions(+), 15 deletions(-)

diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c
index 5362247bc..4cec0dd5f 100644
--- a/grub-core/kern/efi/mm.c
+++ b/grub-core/kern/efi/mm.c
@@ -20,6 +20,7 @@
 #include <grub/misc.h>
 #include <grub/mm.h>
 #include <grub/efi/api.h>
+#include <grub/env.h>
 #include <grub/efi/efi.h>
 #include <grub/cpu/efi/memory.h>
 
@@ -119,18 +120,24 @@ grub_efi_allocate_pages_real (grub_efi_physical_address_t 
address,
                              grub_efi_allocate_type_t alloctype,
                              grub_efi_memory_type_t memtype)
 {
+  grub_efi_physical_address_t max_usable_addr = GRUB_EFI_MAX_USABLE_ADDRESS;
   grub_efi_status_t status;
   grub_efi_boot_services_t *b;
 
+#if defined (__x86_64__)
+  if (grub_env_get_bool("mmunlimited", false))
+    max_usable_addr = GRUB_EFI_MAX_USABLE_ADDRESS_64;
+#endif
+
   /* Limit the memory access to less than 4GB for 32-bit platforms.  */
-  if (address > GRUB_EFI_MAX_USABLE_ADDRESS)
+  if (address > max_usable_addr)
     {
       char inv_addr[17], max_addr[17]; /* log16(2^64) = 16, plus NUL. */
 
       grub_snprintf (inv_addr, sizeof (inv_addr) - 1, "%" PRIxGRUB_UINT64_T,
                     address);
       grub_snprintf (max_addr, sizeof (max_addr) - 1, "%" PRIxGRUB_UINT64_T,
-                    (grub_efi_uint64_t) GRUB_EFI_MAX_USABLE_ADDRESS);
+                    (grub_efi_uint64_t) max_usable_addr);
       grub_error (GRUB_ERR_BAD_ARGUMENT,
                  N_("invalid memory address (0x%s > 0x%s)"), inv_addr, 
max_addr);
       return NULL;
@@ -148,7 +155,7 @@ grub_efi_allocate_pages_real (grub_efi_physical_address_t 
address,
     {
       /* Uggh, the address 0 was allocated... This is too annoying,
         so reallocate another one.  */
-      address = GRUB_EFI_MAX_USABLE_ADDRESS;
+      address = max_usable_addr;
       status = efi_call_4 (b->allocate_pages, alloctype, memtype, pages, 
&address);
       grub_efi_free_pages (0, pages);
       if (status != GRUB_EFI_SUCCESS)
@@ -166,7 +173,14 @@ grub_efi_allocate_pages_real (grub_efi_physical_address_t 
address,
 void *
 grub_efi_allocate_any_pages (grub_efi_uintn_t pages)
 {
-  return grub_efi_allocate_pages_real (GRUB_EFI_MAX_USABLE_ADDRESS,
+  grub_efi_physical_address_t max_usable_addr = GRUB_EFI_MAX_USABLE_ADDRESS;
+
+#if defined (__x86_64__)
+  if (grub_env_get_bool("mmunlimited", false))
+    max_usable_addr = GRUB_EFI_MAX_USABLE_ADDRESS_64;
+#endif
+
+  return grub_efi_allocate_pages_real (max_usable_addr,
                                       pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS,
                                       GRUB_EFI_LOADER_DATA);
 }
@@ -434,16 +448,22 @@ filter_memory_map (grub_efi_memory_descriptor_t 
*memory_map,
                   grub_efi_uintn_t desc_size,
                   grub_efi_memory_descriptor_t *memory_map_end)
 {
+  grub_efi_physical_address_t max_usable_addr = GRUB_EFI_MAX_USABLE_ADDRESS;
   grub_efi_memory_descriptor_t *desc;
   grub_efi_memory_descriptor_t *filtered_desc;
 
+#if defined (__x86_64__)
+  if (grub_env_get_bool("mmunlimited", false))
+    max_usable_addr = GRUB_EFI_MAX_USABLE_ADDRESS_64;
+#endif
+
   for (desc = memory_map, filtered_desc = filtered_memory_map;
        desc < memory_map_end;
        desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size))
     {
       if (desc->type == GRUB_EFI_CONVENTIONAL_MEMORY
 #if 1
-         && desc->physical_start <= GRUB_EFI_MAX_USABLE_ADDRESS
+         && desc->physical_start <= max_usable_addr
 #endif
          && desc->physical_start + PAGES_TO_BYTES (desc->num_pages) > 0x100000
          && desc->num_pages != 0)
@@ -461,9 +481,9 @@ filter_memory_map (grub_efi_memory_descriptor_t *memory_map,
 #if 1
          if (BYTES_TO_PAGES (filtered_desc->physical_start)
              + filtered_desc->num_pages
-             > BYTES_TO_PAGES_DOWN (GRUB_EFI_MAX_USABLE_ADDRESS))
+             > BYTES_TO_PAGES_DOWN (max_usable_addr))
            filtered_desc->num_pages
-             = (BYTES_TO_PAGES_DOWN (GRUB_EFI_MAX_USABLE_ADDRESS)
+             = (BYTES_TO_PAGES_DOWN (max_usable_addr)
                 - BYTES_TO_PAGES (filtered_desc->physical_start));
 #endif
 
diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c
index 06d482481..29bbda61c 100644
--- a/grub-core/loader/i386/linux.c
+++ b/grub-core/loader/i386/linux.c
@@ -655,6 +655,10 @@ grub_cmd_linux (grub_command_t cmd __attribute__ 
((unused)),
   int i;
   grub_size_t align, min_align;
   int relocatable;
+#if defined (GRUB_MACHINE_EFI)
+  int kernel_can_above_4g = 0;
+  grub_addr_t max_usable_addr = GRUB_LINUX_MAX_ADDR_32;
+#endif
   grub_uint64_t preferred_address = GRUB_LINUX_BZIMAGE_ADDR;
 
   grub_dl_ref (my_mod);
@@ -805,6 +809,8 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
 
   linux_params.ramdisk_image = 0;
   linux_params.ramdisk_size = 0;
+  linux_params.ext_ramdisk_image = 0;
+  linux_params.ext_ramdisk_size = 0;
 
   linux_params.heap_end_ptr = GRUB_LINUX_HEAP_END_OFFSET;
   linux_params.loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP;
@@ -825,6 +831,12 @@ grub_cmd_linux (grub_command_t cmd __attribute__ 
((unused)),
 
 #ifdef GRUB_MACHINE_EFI
 #ifdef __x86_64__
+  if (grub_le_to_cpu16 (linux_params.version) >= 0x020c)
+    {
+      if (linux_params.xloadflags & LINUX_XLF_CAN_BE_LOADED_ABOVE_4G)
+        kernel_can_above_4g = 1;
+    }
+
   if (grub_le_to_cpu16 (linux_params.version) < 0x0208 &&
       ((grub_addr_t) grub_efi_system_table >> 32) != 0)
     return grub_error(GRUB_ERR_BAD_OS,
@@ -1011,6 +1023,38 @@ grub_cmd_linux (grub_command_t cmd __attribute__ 
((unused)),
     grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
                argv[0]);
 
+#if defined (GRUB_MACHINE_EFI)
+  /* If user don't want to use memory over 4g. */
+  if (grub_env_get_bool("mmunlimited", false) == false) {
+      max_usable_addr = GRUB_LINUX_MAX_ADDR_32;
+  }
+  /* Else if user wants. */
+  else {
+    /* If kernel supports be allocated over 4g. */
+    if (kernel_can_above_4g == 1) {
+#ifdef __x86_64__
+      /* If machine is 64 bits. */
+      max_usable_addr = GRUB_LINUX_MAX_ADDR_64;
+#else
+      /* The target device doesn't support 4G. */
+      max_usable_addr = GRUB_LINUX_MAX_ADDR_32;
+#endif
+    }
+    /* Kernel doesn't support be loaded over 4G. */
+    else {
+      max_usable_addr = GRUB_LINUX_MAX_ADDR_32;
+    }
+  }
+  grub_dprintf ("linux", "max_usable_addr = 0x%" PRIxGRUB_ADDR "\n",
+                       max_usable_addr);
+  if (prot_mode_mem > (void*) (max_usable_addr - len)) {
+    grub_error (GRUB_ERR_BAD_OS,
+                "Doesn't support load kernel above 0x%lx.",
+                max_usable_addr);
+    goto fail;
+  }
+#endif
+
   if (grub_errno == GRUB_ERR_NONE)
     {
       grub_loader_set (grub_linux_boot, grub_linux_unload,
@@ -1041,6 +1085,7 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ 
((unused)),
   grub_addr_t addr;
   grub_err_t err;
   struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
+  int initrd_can_above_4g = 0;
 
   if (argc == 0)
     {
@@ -1060,6 +1105,15 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ 
((unused)),
   size = grub_get_initrd_size (&initrd_ctx);
   aligned_size = ALIGN_UP (size, 4096);
 
+#if defined (GRUB_MACHINE_EFI) && defined (__x86_64__)
+  /* Check if initrd can be loaded above 4G */
+  if (grub_le_to_cpu16 (linux_params.version) >= 0x020c)
+    {
+      if (linux_params.xloadflags & LINUX_XLF_CAN_BE_LOADED_ABOVE_4G)
+        initrd_can_above_4g = 1;
+    }
+#endif
+
   /* Get the highest address available for the initrd.  */
   if (grub_le_to_cpu16 (linux_params.version) >= 0x0203)
     {
@@ -1067,9 +1121,31 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ 
((unused)),
 
       /* XXX in reality, Linux specifies a bogus value, so
         it is necessary to make sure that ADDR_MAX does not exceed
-        0x3fffffff.  */
-      if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS)
-       addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
+        0x3fffffff except users prefer to unlimit it.  */
+      if (grub_env_get_bool("mmunlimited", false) == false)
+       {
+         if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS)
+           addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
+       }
+      /* If user don't want memory allocation be limited. */
+      else
+      {
+        /* Maximum memory shouldn't exceed 4G unless kerenl supports it. */
+        if (initrd_can_above_4g == 0) {
+             if (addr_max > GRUB_LINUX_MAX_ADDR_32)
+               addr_max = GRUB_LINUX_MAX_ADDR_32;
+        }
+        /* If initrd can be loaded above 4G, overwrite inird_addr_max. */
+        else {
+#ifdef __x86_64__
+          /* If machine is 64 bits. */
+          addr_max = GRUB_LINUX_MAX_ADDR_64;
+#else
+          /* The target device doesn't support 4G. */
+          addr_max = GRUB_LINUX_MAX_ADDR_32;
+#endif
+        }
+      }
     }
   else
     addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
@@ -1094,7 +1170,20 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ 
((unused)),
     }
 
   /* Put the initrd as high as possible, 4KiB aligned.  */
-  addr = (addr_max - aligned_size) & ~0xFFF;
+  addr = (addr_max - aligned_size) & (grub_addr_t) ~0xFFF;
+
+  grub_dprintf ("linux",
+                "Initrd at addr 0x%" PRIxGRUB_ADDR " which is expected in"
+                " ranger 0x%" PRIxGRUB_ADDR    " ~ 0x%" PRIxGRUB_ADDR "\n",
+                addr, addr_min, addr_max);
+
+  /* If kernel doesn't support memory be allocated over 4G. */
+  if (initrd_can_above_4g == 0 &&
+      addr > GRUB_LINUX_MAX_ADDR_32) {
+      grub_error (GRUB_ERR_OUT_OF_RANGE,
+                  "the addr is over 4G but unsupported.");
+      goto fail;
+  }
 
   grub_dprintf ("linux",
                 "Initrd at addr 0x%" PRIxGRUB_ADDR " which is expected in"
@@ -1126,9 +1215,13 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ 
((unused)),
   grub_dprintf ("linux", "Initrd, addr=0x%x, size=0x%x\n",
                (unsigned) addr, (unsigned) size);
 
-  linux_params.ramdisk_image = initrd_mem_target;
-  linux_params.ramdisk_size = size;
+  linux_params.ramdisk_image = GRUB_LINUX_ADDR_LOW_U32(initrd_mem_target);
+  linux_params.ramdisk_size = GRUB_LINUX_SIZE_LOW_U32(size);
   linux_params.root_dev = 0x0100; /* XXX */
+#ifdef __x86_64__
+  linux_params.ext_ramdisk_image = GRUB_LINUX_ADDR_HIGH_U32(initrd_mem_target);
+  linux_params.ext_ramdisk_size = GRUB_LINUX_SIZE_HIGH_U32(size);
+#endif
 
  fail:
   grub_initrd_close (&initrd_ctx);
diff --git a/include/grub/i386/linux.h b/include/grub/i386/linux.h
index a868d115c..b042751b7 100644
--- a/include/grub/i386/linux.h
+++ b/include/grub/i386/linux.h
@@ -48,16 +48,33 @@
 #define VIDEO_CAPABILITY_SKIP_QUIRKS   (1 << 0)
 #define VIDEO_CAPABILITY_64BIT_BASE    (1 << 1)        /* Frame buffer base is 
64-bit. */
 
+#define GRUB_LINUX_ADDR_LOW_U32(addr)  \
+       ((grub_uint32_t)(((grub_addr_t)addr) & 0xffffffffull))
+#ifdef __x86_64__
+#define GRUB_LINUX_ADDR_HIGH_U32(addr) \
+       ((grub_uint32_t)((((grub_addr_t)addr) >> 32) & 0xffffffffull))
+#endif
+
+#define GRUB_LINUX_SIZE_LOW_U32(addr)  \
+       ((grub_uint32_t)(((grub_size_t)size) & 0xffffffffull))
+#ifdef __x86_64__
+#define GRUB_LINUX_SIZE_HIGH_U32(addr) \
+       ((grub_uint32_t)((((grub_size_t)size) >> 32) & 0xffffffffull))
+#endif
+
 /* Maximum number of MBR signatures to store. */
 #define EDD_MBR_SIG_MAX                        16
 
 #ifdef __x86_64__
 
+#define GRUB_LINUX_MAX_ADDR_32     0xffffffff
+#define GRUB_LINUX_MAX_ADDR_64       0xffffffffffffull
 #define GRUB_LINUX_EFI_SIGNATURE       \
   ('4' << 24 | '6' << 16 | 'L' << 8 | 'E')
 
 #else
 
+#define GRUB_LINUX_MAX_ADDR_32     0xffffffff
 #define GRUB_LINUX_EFI_SIGNATURE       \
   ('2' << 24 | '3' << 16 | 'L' << 8 | 'E')
 
diff --git a/include/grub/x86_64/efi/memory.h b/include/grub/x86_64/efi/memory.h
index 46e9145a3..d8c1273b1 100644
--- a/include/grub/x86_64/efi/memory.h
+++ b/include/grub/x86_64/efi/memory.h
@@ -2,9 +2,13 @@
 #include <grub/efi/memory.h>
 
 #if defined (__code_model_large__)
-#define GRUB_EFI_MAX_USABLE_ADDRESS 0xffffffff
+#define GRUB_EFI_MAX_USABLE_ADDRESS_32 0xffffffff
+#define GRUB_EFI_MAX_USABLE_ADDRESS_64 __UINTPTR_MAX__
+#define GRUB_EFI_MAX_USABLE_ADDRESS GRUB_EFI_MAX_USABLE_ADDRESS_32
 #else
-#define GRUB_EFI_MAX_USABLE_ADDRESS 0x7fffffff
+#define GRUB_EFI_MAX_USABLE_ADDRESS_32 0x7fffffff
+#define GRUB_EFI_MAX_USABLE_ADDRESS_64 GRUB_EFI_MAX_USABLE_ADDRESS_32
+#define GRUB_EFI_MAX_USABLE_ADDRESS GRUB_EFI_MAX_USABLE_ADDRESS_32
 #endif
 
 #endif /* ! GRUB_MEMORY_CPU_HEADER */
-- 
2.38.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]