grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH V2] i386: Using 64-bit boot protocol for 64-bit linux kernel


From: Wei Zhang
Subject: [PATCH V2] i386: Using 64-bit boot protocol for 64-bit linux kernel
Date: Sat, 23 Jul 2022 19:20:43 +0800

From: Wei Zhang <zhangweilst@gmail.com>

Currently GRUB boots linux with 32-bit protocol 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 have 
to be executed in the kernel.

Since linux has long ago 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.12), 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 | 93 ++++++++++++++++++++++++++++++++---
 include/grub/i386/linux.h     |  2 +-
 2 files changed, 86 insertions(+), 9 deletions(-)

diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c
index c5984d4b2..376b72aba 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,16 @@ 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 %d bytes will be allocated\n", 
LINUX_PGT_SIZE_64);
+        }
+      else
+       is_64bit = 0;
       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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]