e30274a
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
e30274a
From: Peter Jones <pjones@redhat.com>
e30274a
Date: Wed, 12 Sep 2018 16:12:27 -0400
e30274a
Subject: [PATCH] x86-efi: Allow initrd+params+cmdline allocations above 4GB.
e30274a
e30274a
This enables everything except the kernel itself to be above 4GB.
e30274a
Putting the kernel up there still doesn't work, because of the way
e30274a
params->code32_start is used.
e30274a
e30274a
Signed-off-by: Peter Jones <pjones@redhat.com>
e30274a
---
e30274a
 grub-core/loader/i386/efi/linux.c | 67 +++++++++++++++++++++++++++++++++++----
e30274a
 include/grub/i386/linux.h         |  6 +++-
e30274a
 2 files changed, 65 insertions(+), 8 deletions(-)
e30274a
e30274a
diff --git a/grub-core/loader/i386/efi/linux.c b/grub-core/loader/i386/efi/linux.c
a3bfe35
index 1811f4b3d56..65d1b5cc034 100644
e30274a
--- a/grub-core/loader/i386/efi/linux.c
e30274a
+++ b/grub-core/loader/i386/efi/linux.c
e30274a
@@ -53,13 +53,22 @@ struct allocation_choice {
e30274a
     grub_efi_allocate_type_t alloc_type;
e30274a
 };
e30274a
 
e30274a
-static struct allocation_choice max_addresses[] =
e30274a
+static struct allocation_choice max_addresses[4] =
e30274a
   {
e30274a
+    /* the kernel overrides this one with pref_address and
e30274a
+     * GRUB_EFI_ALLOCATE_ADDRESS */
e30274a
     { GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
e30274a
+    /* this one is always below 4GB, which we still *prefer* even if the flag
e30274a
+     * is set. */
e30274a
     { GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
e30274a
+    /* If the flag in params is set, this one gets changed to be above 4GB. */
e30274a
     { GRUB_EFI_MAX_ALLOCATION_ADDRESS, GRUB_EFI_ALLOCATE_MAX_ADDRESS },
e30274a
     { 0, 0 }
e30274a
   };
e30274a
+static struct allocation_choice saved_addresses[4];
e30274a
+
e30274a
+#define save_addresses() grub_memcpy(saved_addresses, max_addresses, sizeof(max_addresses))
e30274a
+#define restore_addresses() grub_memcpy(max_addresses, saved_addresses, sizeof(max_addresses))
e30274a
 
e30274a
 static inline void
e30274a
 kernel_free(void *addr, grub_efi_uintn_t size)
e30274a
@@ -81,6 +90,11 @@ kernel_alloc(grub_efi_uintn_t size, const char * const errmsg)
e30274a
       grub_uint64_t max = max_addresses[i].addr;
e30274a
       grub_efi_uintn_t pages;
e30274a
 
e30274a
+      /*
e30274a
+       * When we're *not* loading the kernel, or >4GB allocations aren't
e30274a
+       * supported, these entries are basically all the same, so don't re-try
e30274a
+       * the same parameters.
e30274a
+       */
e30274a
       if (max == prev_max)
e30274a
 	continue;
e30274a
 
a3bfe35
@@ -169,6 +183,9 @@ read(grub_file_t file, grub_uint8_t *bufp, grub_size_t len)
e30274a
   return bufpos;
e30274a
 }
e30274a
 
e30274a
+#define LOW_U32(val) ((grub_uint32_t)(((grub_addr_t)(val)) & 0xffffffffull))
e30274a
+#define HIGH_U32(val) ((grub_uint32_t)(((grub_addr_t)(val) >> 32) & 0xffffffffull))
e30274a
+
e30274a
 static grub_err_t
e30274a
 grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
e30274a
                  int argc, char *argv[])
a3bfe35
@@ -209,8 +226,12 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
e30274a
     goto fail;
e30274a
   grub_dprintf ("linux", "initrd_mem = %p\n", initrd_mem);
e30274a
 
e30274a
-  params->ramdisk_size = size;
e30274a
-  params->ramdisk_image = initrd_mem;
e30274a
+  params->ramdisk_size = LOW_U32(size);
e30274a
+  params->ramdisk_image = LOW_U32(initrd_mem);
e30274a
+#if defined(__x86_64__)
e30274a
+  params->ext_ramdisk_size = HIGH_U32(size);
e30274a
+  params->ext_ramdisk_image = HIGH_U32(initrd_mem);
e30274a
+#endif
e30274a
 
e30274a
   ptr = initrd_mem;
e30274a
 
a3bfe35
@@ -345,6 +366,18 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
e30274a
     }
e30274a
 #endif
e30274a
 
e30274a
+#if defined(__x86_64__)
e30274a
+  if (lh->xloadflags & LINUX_XLF_CAN_BE_LOADED_ABOVE_4G)
e30274a
+    {
e30274a
+      grub_dprintf ("linux", "Loading kernel above 4GB is supported; enabling.\n");
e30274a
+      max_addresses[2].addr = GRUB_EFI_MAX_USABLE_ADDRESS;
e30274a
+    }
e30274a
+  else
e30274a
+    {
e30274a
+      grub_dprintf ("linux", "Loading kernel above 4GB is not supported\n");
e30274a
+    }
e30274a
+#endif
e30274a
+
e30274a
   params = kernel_alloc (sizeof(*params), "cannot allocate kernel parameters");
e30274a
   if (!params)
e30274a
     goto fail;
a3bfe35
@@ -378,21 +411,40 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
e30274a
 
e30274a
   grub_dprintf ("linux", "cmdline:%s\n", linux_cmdline);
e30274a
   grub_dprintf ("linux", "setting lh->cmd_line_ptr to 0x%08x\n",
e30274a
-		linux_cmdline);
e30274a
-  lh->cmd_line_ptr = linux_cmdline;
e30274a
+		LOW_U32(linux_cmdline));
e30274a
+  lh->cmd_line_ptr = LOW_U32(linux_cmdline);
e30274a
+#if defined(__x86_64__)
e30274a
+  if ((grub_efi_uintn_t)linux_cmdline > 0xffffffffull)
e30274a
+    {
e30274a
+      grub_dprintf ("linux", "setting params->ext_cmd_line_ptr to 0x%08x\n",
e30274a
+		    HIGH_U32(linux_cmdline));
e30274a
+      params->ext_cmd_line_ptr = HIGH_U32(linux_cmdline);
e30274a
+    }
e30274a
+#endif
e30274a
 
e30274a
   handover_offset = lh->handover_offset;
e30274a
   grub_dprintf("linux", "handover_offset: 0x%08x\n", handover_offset);
e30274a
 
e30274a
   start = (lh->setup_sects + 1) * 512;
e30274a
 
e30274a
+  /*
e30274a
+   * AFAICS >4GB for kernel *cannot* work because of params->code32_start being
e30274a
+   * 32-bit and getting called unconditionally in head_64.S from either entry
e30274a
+   * point.
e30274a
+   *
e30274a
+   * so nerf that out here...
e30274a
+   */
e30274a
+  save_addresses();
e30274a
   grub_dprintf ("linux", "lh->pref_address: %p\n", (void *)(grub_addr_t)lh->pref_address);
e30274a
   if (lh->pref_address < (grub_uint64_t)GRUB_EFI_MAX_ALLOCATION_ADDRESS)
e30274a
     {
e30274a
       max_addresses[0].addr = lh->pref_address;
e30274a
       max_addresses[0].alloc_type = GRUB_EFI_ALLOCATE_ADDRESS;
e30274a
     }
e30274a
+  max_addresses[1].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
e30274a
+  max_addresses[2].addr = GRUB_EFI_MAX_ALLOCATION_ADDRESS;
e30274a
   kernel_mem = kernel_alloc (lh->init_size, N_("can't allocate kernel"));
e30274a
+  restore_addresses();
e30274a
   if (!kernel_mem)
e30274a
     goto fail;
e30274a
   grub_dprintf("linux", "kernel_mem = %p\n", kernel_mem);
a3bfe35
@@ -401,8 +453,9 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
e30274a
 
e30274a
   loaded = 1;
e30274a
 
e30274a
-  grub_dprintf ("linux", "setting lh->code32_start to %p\n", kernel_mem);
e30274a
-  lh->code32_start = (grub_uint32_t)(grub_addr_t) kernel_mem;
a3bfe35
+  grub_dprintf ("linux", "setting lh->code32_start to 0x%08x\n",
e30274a
+		LOW_U32(kernel_mem));
e30274a
+  lh->code32_start = LOW_U32(kernel_mem);
e30274a
 
e30274a
   grub_memcpy (kernel_mem, (char *)kernel + start, filelen - start);
e30274a
 
e30274a
diff --git a/include/grub/i386/linux.h b/include/grub/i386/linux.h
e30274a
index 8474a857ed2..a4b37dcced5 100644
e30274a
--- a/include/grub/i386/linux.h
e30274a
+++ b/include/grub/i386/linux.h
e30274a
@@ -230,7 +230,11 @@ struct linux_kernel_params
e30274a
   grub_uint32_t ofw_cif_handler;	/* b8 */
e30274a
   grub_uint32_t ofw_idt;		/* bc */
e30274a
 
e30274a
-  grub_uint8_t padding7[0x1b8 - 0xc0];
e30274a
+  grub_uint32_t ext_ramdisk_image;	/* 0xc0 */
e30274a
+  grub_uint32_t ext_ramdisk_size;	/* 0xc4 */
e30274a
+  grub_uint32_t ext_cmd_line_ptr;	/* 0xc8 */
e30274a
+
e30274a
+  grub_uint8_t padding7[0x1b8 - 0xcc];
e30274a
 
e30274a
   union
e30274a
     {