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