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