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