[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] i386: Using 64-bit boot protocol for 64-bit linux kernel
From: |
Wei Zhang |
Subject: |
[PATCH] i386: Using 64-bit boot protocol for 64-bit linux kernel |
Date: |
Sat, 23 Jul 2022 18:25:42 +0800 |
Currently GRUB boots linux with 32-bit protocol even for 64 bit kernel. Thus if
both GRUB and linux kernel are in 64-bit, we'll have to go through 64-bit grub
-> 32-bit boot protocol -> 64-bit kernel transitions, and extra instructions
will be executed in the kernel.
Since linux has long supported 64-bit boot protocol, we can take advantage of
that to directly boot to 64-bit kernel.
To do this, first we determine whether the kernel is 64-bit by xloadflags
(since linux boot protocol 2.13), then we build the identity-mapped page table
required by the 64-bit kernel, and that's it. The memory needed by the page
table is allocated after the protected kernel image proper.
So if we're in 32-bit GRUB to boot a 64-bit kernel, the transition will happen
before handing over to the kernel, and if we're in 64-bit GRUB, we don't have
to go down to 32-bit and back to 64-bit. The 32-bit kernel boot process will
not be affected.
Tested on my 64-bit machine and QEMU.
Signed-off-by: Wei Zhang <zhangweilst@126.com>
---
grub-core/loader/i386/linux.c | 91 ++++++++++++++++++++++++++++++++---
include/grub/i386/linux.h | 2 +-
2 files changed, 84 insertions(+), 9 deletions(-)
diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c
index c5984d4b2..949dacea1 100644
--- a/grub-core/loader/i386/linux.c
+++ b/grub-core/loader/i386/linux.c
@@ -63,12 +63,24 @@ GRUB_MOD_LICENSE ("GPLv3+");
#define ACCEPTS_PURE_TEXT 1
#endif
+#define PG_P 0x001
+#define PG_RW 0x002
+#define PG_US 0x004
+#define PG_PS 0x080
+#define PG_G 0x100
+#define LINUX_KERNEL_64 0x0001
+#define LINUX_STARTUP_64 0x200
+#define LINUX_PGT_SIZE_64 (6 << 12)
+
static grub_dl_t my_mod;
static grub_size_t linux_mem_size;
static int loaded;
static void *prot_mode_mem;
static grub_addr_t prot_mode_target;
+static int is_64bit = 0;
+static grub_uint8_t *pgtable;
+static grub_addr_t pgtable_target;
static void *initrd_mem;
static grub_addr_t initrd_mem_target;
static grub_size_t prot_init_space;
@@ -199,6 +211,11 @@ allocate_pages (grub_size_t prot_size, grub_size_t *align,
goto fail;
prot_mode_mem = get_virtual_current_address (ch);
prot_mode_target = get_physical_target_address (ch);
+ if (is_64bit)
+ {
+ pgtable = (grub_uint8_t *)((grub_addr_t) prot_mode_mem + prot_size -
LINUX_PGT_SIZE_64);
+ pgtable_target = prot_mode_target + prot_size - LINUX_PGT_SIZE_64;
+ }
}
grub_dprintf ("linux", "prot_mode_mem = %p, prot_mode_target = %lx,
prot_size = %x\n",
@@ -398,13 +415,52 @@ grub_linux_boot_mmap_fill (grub_uint64_t addr,
grub_uint64_t size,
return 0;
}
+/* Fill linux identity map page table. */
+static void
+grub_fill_linux64_pgtable (void)
+{
+ grub_uint64_t *p4d, *pud, *pmd;
+ grub_addr_t pudt, pmdt;
+ int i;
+
+ p4d = (grub_uint64_t *) pgtable;
+ pud = (grub_uint64_t *) (pgtable + 0x1000);
+ pmd = (grub_uint64_t *) (pgtable + 0x2000);
+
+ pudt = pgtable_target + 0x1000;
+ pmdt = pgtable_target + 0x2000;
+
+ grub_memset (pgtable, 0, LINUX_PGT_SIZE_64);
+
+ /* First entry for the 3rd level page */
+ p4d[0] = (grub_addr_t) (pudt);
+ p4d[0] |= (PG_P | PG_RW | PG_US);
+
+ /* Map the whole 4G physical address */
+ for (i = 0; i < 4; i++)
+ {
+ pud[i] = (grub_addr_t) (pmdt + i * 0x1000);
+ pud[i] |= (PG_P | PG_RW | PG_US);
+ }
+
+ /* Identity map with 2MB pages */
+ for (i = 0; i < 2048; i++)
+ {
+ pmd[i] = (grub_addr_t) (i) * 0x200000;
+ pmd[i] |= (PG_P | PG_RW | PG_PS | PG_G);
+ }
+}
+
static grub_err_t
grub_linux_boot (void)
{
grub_err_t err = 0;
const char *modevar;
char *tmp;
- struct grub_relocator32_state state;
+ union {
+ struct grub_relocator32_state _32;
+ struct grub_relocator64_state _64;
+ } state;
void *real_mode_mem;
struct grub_linux_boot_ctx ctx = {
.real_mode_target = 0
@@ -624,13 +680,24 @@ grub_linux_boot (void)
}
#endif
- /* FIXME. */
- /* asm volatile ("lidt %0" : : "m" (idt_desc)); */
- state.ebp = state.edi = state.ebx = 0;
- state.esi = ctx.real_mode_target;
- state.esp = ctx.real_mode_target;
- state.eip = ctx.params->code32_start;
- return grub_relocator32_boot (relocator, state, 0);
+ if (is_64bit)
+ {
+ state._64.rsi = ctx.real_mode_target;
+ state._64.rip = ctx.params->code32_start + LINUX_STARTUP_64;
+ grub_fill_linux64_pgtable();
+ state._64.cr3 = (grub_addr_t) pgtable_target;
+ return grub_relocator64_boot (relocator, state._64, 0x1000, 0x9a000);
+ }
+ else
+ {
+ /* FIXME. */
+ /* asm volatile ("lidt %0" : : "m" (idt_desc)); */
+ state._32.ebp = state._32.edi = state._32.ebx = 0;
+ state._32.esi = ctx.real_mode_target;
+ state._32.esp = ctx.real_mode_target;
+ state._32.eip = ctx.params->code32_start;
+ return grub_relocator32_boot (relocator, state._32, 0);
+ }
}
static grub_err_t
@@ -748,6 +815,14 @@ grub_cmd_linux (grub_command_t cmd __attribute__
((unused)),
{
min_align = lh.min_alignment;
prot_size = grub_le_to_cpu32 (lh.init_size);
+ if (grub_le_to_cpu16 (lh.version) >= 0x020c
+ && (lh.xloadflags & LINUX_KERNEL_64))
+ {
+ is_64bit = 1;
+ prot_size += LINUX_PGT_SIZE_64;
+ grub_dprintf ("linux", "using 64-bit boot protocol, "
+ "extra bytes allocated: %d\n", LINUX_PGT_SIZE_64);
+ }
prot_init_space = page_align (prot_size);
if (relocatable)
preferred_address = grub_le_to_cpu64 (lh.pref_address);
diff --git a/include/grub/i386/linux.h b/include/grub/i386/linux.h
index 0fd6e1212..9b511eb21 100644
--- a/include/grub/i386/linux.h
+++ b/include/grub/i386/linux.h
@@ -138,7 +138,7 @@ struct linux_i386_kernel_header
grub_uint32_t kernel_alignment;
grub_uint8_t relocatable;
grub_uint8_t min_alignment;
- grub_uint8_t pad[2];
+ grub_uint16_t xloadflags;
grub_uint32_t cmdline_size;
grub_uint32_t hardware_subarch;
grub_uint64_t hardware_subarch_data;
--
2.34.1