grub-devel
[Top][All Lists]
Advanced

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

Re: [PATCH] FreeBSD 64-bit kernel support


From: phcoder
Subject: Re: [PATCH] FreeBSD 64-bit kernel support
Date: Mon, 13 Apr 2009 03:27:49 +0200
User-agent: Thunderbird 2.0.0.21 (X11/20090318)

Bean kindly allowed me to mess with this patch. So here comes an improved version. I moved helpers out of the kernels. Because of how FreeBSD expects the initial virtual memory mapping only first GB of physical memory is accessible so it was required to use of trampoline technique.
Bean wrote:
Hi,

This patch allows you to load amd64 freebsd kernel directly, here is an example:

set root=(hd0,1,a)
freebsd /boot/kernel/kernel
freebsd_loadenv /boot/device.hints
set FreeBSD.vfs.root.mountfrom=ufs:/dev/ad0s1a
boot

Test successfully on FreeBSD 7.1 amd64.



------------------------------------------------------------------------

_______________________________________________
Grub-devel mailing list
address@hidden
http://lists.gnu.org/mailman/listinfo/grub-devel


--

Regards
Vladimir 'phcoder' Serbinenko
diff --git a/DISTLIST b/DISTLIST
index bec628e..eefa982 100644
--- a/DISTLIST
+++ b/DISTLIST
@@ -400,6 +400,7 @@ loader/multiboot_loader.c
 loader/efi/appleloader.c
 loader/efi/chainloader.c
 loader/i386/bsd.c
+loader/i386/bsd_helper.S
 loader/i386/linux.c
 loader/i386/multiboot.c
 loader/i386/multiboot_elfxx.c
diff --git a/conf/i386-pc.rmk b/conf/i386-pc.rmk
index 7dfb854..311551a 100644
--- a/conf/i386-pc.rmk
+++ b/conf/i386-pc.rmk
@@ -295,9 +295,10 @@ aout_mod_CFLAGS = $(COMMON_CFLAGS)
 aout_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For bsd.mod
-bsd_mod_SOURCES = loader/i386/bsd.c
+bsd_mod_SOURCES = loader/i386/bsd.c loader/i386/bsd_helper.S
 bsd_mod_CFLAGS = $(COMMON_CFLAGS)
 bsd_mod_LDFLAGS = $(COMMON_LDFLAGS)
+bsd_mod_ASFLAGS = $(COMMON_ASFLAGS)
 
 # For usb.mod
 usb_mod_SOURCES = bus/usb/usb.c bus/usb/usbtrans.c bus/usb/usbhub.c
diff --git a/include/grub/i386/bsd.h b/include/grub/i386/bsd.h
index f50f18e..3706f4d 100644
--- a/include/grub/i386/bsd.h
+++ b/include/grub/i386/bsd.h
@@ -80,9 +80,12 @@
 #define FREEBSD_MODINFOMD_SHDR         0x0009  /* section header table */
 #define FREEBSD_MODINFOMD_NOCOPY       0x8000  /* don't copy this metadata to 
the kernel */
 
+#define FREEBSD_MODINFOMD_SMAP         0x1001
+
 #define FREEBSD_MODINFOMD_DEPLIST      (0x4001 | FREEBSD_MODINFOMD_NOCOPY)  /* 
depends on */
 
 #define FREEBSD_MODTYPE_KERNEL         "elf kernel"
+#define FREEBSD_MODTYPE_KERNEL64       "elf64 kernel"
 #define FREEBSD_MODTYPE_MODULE         "elf module"
 #define FREEBSD_MODTYPE_RAW            "raw"
 
@@ -222,4 +225,11 @@ struct grub_netbsd_btinfo_bootdisk
   int partition;
 };
 
+void grub_unix_real_boot (grub_addr_t entry, ...)
+     __attribute__ ((cdecl,noreturn));
+
+extern grub_uint8_t grub_bsd64_trampoline_start, grub_bsd64_trampoline_end;
+extern grub_uint32_t grub_bsd64_trampoline_selfjump;
+extern grub_uint32_t grub_bsd64_trampoline_gdt;
+
 #endif /* ! GRUB_BSD_CPU_HEADER */
diff --git a/include/grub/i386/loader.h b/include/grub/i386/loader.h
index afd3eb9..72a44d0 100644
--- a/include/grub/i386/loader.h
+++ b/include/grub/i386/loader.h
@@ -32,7 +32,4 @@ extern grub_size_t EXPORT_VAR(grub_os_area_size);
 
 grub_err_t EXPORT_FUNC(grub_linux16_boot) (void);
 
-void EXPORT_FUNC(grub_unix_real_boot) (grub_addr_t entry, ...)
-     __attribute__ ((cdecl,noreturn));
-
 #endif /* ! GRUB_LOADER_CPU_HEADER */
diff --git a/kern/i386/loader.S b/kern/i386/loader.S
index bbd2187..3e9c713 100644
--- a/kern/i386/loader.S
+++ b/kern/i386/loader.S
@@ -118,25 +118,3 @@ linux_setup_seg:
        .word   0
        .code32
 
-/*
- * Use cdecl calling convention for *BSD kernels.
- */
-
-FUNCTION(grub_unix_real_boot)
-
-        call    EXT_C(grub_dl_unload_all)
-
-       /* Interrupts should be disabled.  */
-        cli
-
-       /* Discard `grub_unix_real_boot' return address.  */
-        popl    %eax
-
-        /* Fetch `entry' address ...  */
-        popl   %eax
-
-        /*
-         * ... and put our return address in its place. The kernel will
-         * ignore it, but it expects %esp to point to it.
-         */
-        call   *%eax
diff --git a/loader/i386/bsd.c b/loader/i386/bsd.c
index 355cb3f..8c59c1b 100644
--- a/loader/i386/bsd.c
+++ b/loader/i386/bsd.c
@@ -33,17 +33,19 @@
 #include <grub/command.h>
 
 #define ALIGN_DWORD(a) ALIGN_UP (a, 4)
+#define ALIGN_QWORD(a) ALIGN_UP (a, 8)
+#define ALIGN_VAR(a)   ((is_64bit) ? (ALIGN_QWORD(a)) : (ALIGN_DWORD(a)))
 #define ALIGN_PAGE(a)  ALIGN_UP (a, 4096)
 
 #define MOD_BUF_ALLOC_UNIT     4096
 
 static int kernel_type;
 static grub_dl_t my_mod;
-static grub_addr_t entry, kern_start, kern_end;
+static grub_addr_t entry, entry_hi, kern_start, kern_end;
 static grub_uint32_t bootflags;
 static char *mod_buf;
-static grub_uint32_t mod_buf_len, mod_buf_max;
-static int is_elf_kernel;
+static grub_uint32_t mod_buf_len, mod_buf_max, kern_end_mdofs;
+static int is_elf_kernel, is_64bit;
 
 static const char freebsd_opts[] = "DhaCcdgmnpqrsv";
 static const grub_uint32_t freebsd_flags[] =
@@ -135,11 +137,58 @@ grub_freebsd_add_meta (grub_uint32_t type, void *data, 
grub_uint32_t len)
   if (len)
     grub_memcpy (mod_buf + mod_buf_len, data, len);
 
-  mod_buf_len = ALIGN_DWORD (mod_buf_len + len);
+  mod_buf_len = ALIGN_VAR (mod_buf_len + len);
 
   return GRUB_ERR_NONE;
 }
 
+struct grub_e820_mmap
+{
+  grub_uint64_t addr;
+  grub_uint64_t size;
+  grub_uint32_t type;
+} __attribute__((packed));
+
+static grub_err_t
+grub_freebsd_add_mmap (void)
+{
+  grub_size_t len = 0;
+  struct grub_e820_mmap *mmap = 0;
+
+  auto int NESTED_FUNC_ATTR hook (grub_uint64_t, grub_uint64_t, grub_uint32_t);
+  int NESTED_FUNC_ATTR hook (grub_uint64_t addr, grub_uint64_t size,
+                            grub_uint32_t type)
+    {
+      if (mmap)
+       {
+         mmap->addr = addr;
+         mmap->size = size;
+         mmap->type = type;
+         mmap++;
+       }
+      else
+       len += sizeof (struct grub_e820_mmap);
+
+      return 0;
+    }
+
+  struct grub_e820_mmap *mmap_buf;
+
+  grub_machine_mmap_iterate (hook);
+  mmap_buf = mmap = grub_malloc (len);
+  if (! mmap)
+    return grub_errno;
+
+  grub_machine_mmap_iterate (hook);
+
+  grub_freebsd_add_meta (FREEBSD_MODINFO_METADATA |
+                        FREEBSD_MODINFOMD_SMAP, mmap_buf, len);
+
+  grub_free (mmap_buf);
+
+  return grub_errno;
+}
+
 static grub_err_t
 grub_freebsd_add_meta_module (int is_kern, int argc, char **argv,
                              grub_addr_t addr, grub_uint32_t size)
@@ -166,7 +215,9 @@ grub_freebsd_add_meta_module (int is_kern, int argc, char 
**argv,
       argv++;
     }
   else
-    type = (is_kern) ? FREEBSD_MODTYPE_KERNEL : FREEBSD_MODTYPE_RAW;
+    type = ((is_kern) ?
+           ((is_64bit) ? FREEBSD_MODTYPE_KERNEL64 : FREEBSD_MODTYPE_KERNEL)
+           : FREEBSD_MODTYPE_RAW);
 
   if ((grub_freebsd_add_meta (FREEBSD_MODINFO_TYPE, type,
                              grub_strlen (type) + 1)) ||
@@ -202,6 +253,23 @@ grub_freebsd_add_meta_module (int is_kern, int argc, char 
**argv,
        }
     }
 
+  if (is_kern)
+    {
+      int len = (is_64bit) ? 8 : 4;
+      grub_uint64_t data = 0;
+
+      if ((grub_freebsd_add_meta (FREEBSD_MODINFO_METADATA |
+                                 FREEBSD_MODINFOMD_HOWTO, &data, 4)) ||
+         (grub_freebsd_add_meta (FREEBSD_MODINFO_METADATA |
+                                 FREEBSD_MODINFOMD_ENVP, &data, len)) ||
+         (grub_freebsd_add_meta (FREEBSD_MODINFO_METADATA |
+                                 FREEBSD_MODINFOMD_KERNEND, &data, len)))
+       return grub_errno;
+      kern_end_mdofs = mod_buf_len - len;
+
+      return grub_freebsd_add_mmap ();
+    }
+
   return GRUB_ERR_NONE;
 }
 
@@ -241,10 +309,16 @@ grub_freebsd_list_modules (void)
          }
        }
 
-      pos = ALIGN_DWORD (pos + size);
+      pos = ALIGN_VAR (pos + size);
     }
 }
 
+struct gdt_descriptor
+{
+  grub_uint16_t limit;
+  void *base;
+} __attribute__ ((packed));
+
 static grub_err_t
 grub_freebsd_boot (void)
 {
@@ -291,6 +365,9 @@ grub_freebsd_boot (void)
 
   if (is_elf_kernel)
     {
+      grub_addr_t md_ofs;
+      int ofs;
+
       if (grub_freebsd_add_meta (FREEBSD_MODINFO_END, 0, 0))
        return grub_errno;
 
@@ -298,12 +375,98 @@ grub_freebsd_boot (void)
       bi.bi_modulep = kern_end;
 
       kern_end = ALIGN_PAGE (kern_end + mod_buf_len);
+
+      if (is_64bit)
+       kern_end += 4096 * 4;
+
+      md_ofs = bi.bi_modulep + kern_end_mdofs;
+      ofs = (is_64bit) ? 16 : 12;
+      *((grub_uint32_t *) md_ofs) = kern_end;
+      md_ofs -= ofs;
+      *((grub_uint32_t *) md_ofs) = bi.bi_envp;
+      md_ofs -= ofs;
+      *((grub_uint32_t *) md_ofs) = bootflags;
     }
 
   bi.bi_kernend = kern_end;
 
-  grub_unix_real_boot (entry, bootflags | FREEBSD_RB_BOOTINFO, bootdev,
-                      0, 0, 0, &bi, bi.bi_modulep, kern_end);
+  if (is_64bit)
+    {
+      int i;
+      grub_uint64_t *pt2, *pt3, *pt4;
+      grub_uint32_t *gdt;
+      grub_uint8_t *trampoline;
+      void (*launch_trampoline) (grub_addr_t entry, ...)
+       __attribute__ ((cdecl, regparm (0)));
+
+      struct gdt_descriptor *gdtdesc;
+
+#define PG_V           0x001
+#define PG_RW          0x002
+#define PG_U           0x004
+#define PG_PS          0x080
+
+      pt4 = (grub_uint64_t *) (kern_end - 16384);
+      pt3 = (grub_uint64_t *) (kern_end - 12288);
+      pt2 = (grub_uint64_t *) (kern_end - 8192);
+
+      grub_memset ((char *) pt4, 0, 4096 * 3);
+
+      /*
+       * This is kinda brutal, but every single 1GB VM memory segment points to
+       * the same first 1GB of physical memory.  But it is how BSD expects 
+       * it to be.
+       */
+      for (i = 0; i < 512; i++)
+       {
+         /* Each slot of the level 4 pages points to the same level 3 page */
+         pt4[i] = (grub_addr_t) &pt3[0];
+         pt4[i] |= PG_V | PG_RW | PG_U;
+
+         /* Each slot of the level 3 pages points to the same level 2 page */
+         pt3[i] = (grub_addr_t) &pt2[0];
+         pt3[i] |= PG_V | PG_RW | PG_U;
+
+         /* The level 2 page slots are mapped with 2MB pages for 1GB. */
+         pt2[i] = i * (2 * 1024 * 1024);
+         pt2[i] |= PG_V | PG_RW | PG_PS | PG_U;
+       }
+
+      /* Create GDT. */
+      gdt = (grub_uint32_t *) (kern_end - 4096);
+      gdt[0] = 0;
+      gdt[1] = 0;
+      gdt[2] = 0x00000000;
+      gdt[3] = 0x00209800;
+      gdt[4] = 0x00000000;
+      gdt[5] = 0x00008000;
+
+      /* Create GDT descriptor. */
+      gdtdesc = (struct gdt_descriptor *) (kern_end - 4096 + 24);
+      gdtdesc->limit = 24;
+      gdtdesc->base = gdt;
+
+      /* Prepare trampoline. */
+      trampoline = (grub_uint8_t *) (kern_end - 4096 + 24 
+                                    + sizeof (struct gdt_descriptor));
+      launch_trampoline = (void  __attribute__ ((cdecl, regparm (0))) 
+                          (*) (grub_addr_t entry, ...)) trampoline;
+      grub_bsd64_trampoline_gdt = (grub_uint32_t) gdtdesc;
+      grub_bsd64_trampoline_selfjump 
+       = (grub_uint32_t) (trampoline + 6
+                          + ((grub_uint8_t *) &grub_bsd64_trampoline_selfjump 
+                             - &grub_bsd64_trampoline_start));
+
+      /* Prepare trampoline. */
+      grub_memcpy (trampoline, &grub_bsd64_trampoline_start, 
+                  &grub_bsd64_trampoline_end - &grub_bsd64_trampoline_start);
+
+      /* Launch trampoline. */
+      launch_trampoline (entry, entry_hi, pt4, bi.bi_modulep, kern_end);
+    }
+  else
+    grub_unix_real_boot (entry, bootflags | FREEBSD_RB_BOOTINFO, bootdev,
+                        0, 0, 0, &bi, bi.bi_modulep, kern_end);
 
   /* Not reached.  */
   return GRUB_ERR_NONE;
@@ -478,6 +641,29 @@ grub_bsd_elf32_hook (Elf32_Phdr * phdr, grub_addr_t * addr)
 }
 
 static grub_err_t
+grub_bsd_elf64_hook (Elf64_Phdr * phdr, grub_addr_t * addr)
+{
+  Elf64_Addr paddr;
+
+  paddr = phdr->p_paddr & 0xffffff;
+
+  if ((paddr < grub_os_area_addr)
+      || (paddr + phdr->p_memsz > grub_os_area_addr + grub_os_area_size))
+    return grub_error (GRUB_ERR_OUT_OF_RANGE, "Address 0x%x is out of range",
+                      paddr);
+
+  if ((!kern_start) || (paddr < kern_start))
+    kern_start = paddr;
+
+  if (paddr + phdr->p_memsz > kern_end)
+    kern_end = paddr + phdr->p_memsz;
+
+  *addr = paddr;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
 grub_bsd_load_elf (grub_elf_t elf)
 {
   kern_start = kern_end = 0;
@@ -487,6 +673,13 @@ grub_bsd_load_elf (grub_elf_t elf)
       entry = elf->ehdr.ehdr32.e_entry & 0xFFFFFF;
       return grub_elf32_load (elf, grub_bsd_elf32_hook, 0, 0);
     }
+  else if (grub_elf_is_elf64 (elf))
+    {
+      is_64bit = 1;
+      entry = elf->ehdr.ehdr64.e_entry & 0xffffffff;
+      entry_hi = (elf->ehdr.ehdr64.e_entry >> 32) & 0xffffffff;
+      return grub_elf64_load (elf, grub_bsd_elf64_hook, 0, 0);
+    }
   else
     return grub_error (GRUB_ERR_BAD_OS, "invalid elf");
 }
diff --git a/loader/i386/bsd_helper.S b/loader/i386/bsd_helper.S
new file mode 100644
index 0000000..2ed0f50
--- /dev/null
+++ b/loader/i386/bsd_helper.S
@@ -0,0 +1,114 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define MSR_EFER       0xc0000080
+#define EFER_LME       0x00000100
+#define CR4_PAE                0x00000020
+#define CR4_PSE                0x00000010
+#define CR0_PG         0x80000000
+
+#include <grub/symbol.h>
+       
+       .p2align        2
+       
+
+       .code32 
+
+VARIABLE(grub_bsd64_trampoline_start)
+
+       /* Discard `grub_unix_real_boot' return address.  */
+        popl    %eax
+
+        /* entry  */
+        popl   %edi
+
+        /* entry_hi  */
+        popl   %esi
+
+       cli
+
+       /* Turn on EFER.LME.  */
+       movl    $MSR_EFER, %ecx
+       rdmsr
+       orl     $EFER_LME, %eax
+        wrmsr
+
+       /* Turn on PAE.  */
+       movl    %cr4, %eax
+       orl     $(CR4_PAE | CR4_PSE), %eax
+       movl    %eax, %cr4
+
+       /* Set %cr3 for PT4.  */
+       popl    %eax
+       movl    %eax, %cr3
+
+       /* Push a dummy return address.  */
+       pushl   %eax
+
+       /* Turn on paging (implicitly sets EFER.LMA).  */
+       movl    %cr0, %eax
+       orl     $CR0_PG, %eax
+       movl    %eax, %cr0
+
+       /* Now we're in compatability mode. set %cs for long mode.  */
+       /* lgdt */
+       .byte 0x0f
+       .byte 0x01
+       .byte 0x15
+VARIABLE (grub_bsd64_trampoline_gdt)
+       .long 0x0
+       
+       /* ljmp */
+       .byte 0xea
+VARIABLE (grub_bsd64_trampoline_selfjump)
+       .long 0x0
+       .word 0x08
+
+       .code64
+
+bsd64_longmode:
+         /* We're still running V=P, jump to entry point.  */
+       movl    %esi, %eax
+       salq    $32, %rax
+       orq     %rdi, %rax
+       pushq   %rax
+       ret
+VARIABLE(grub_bsd64_trampoline_end)
+
+       .code32
+
+/*
+ * Use cdecl calling convention for *BSD kernels.
+ */
+
+FUNCTION(grub_unix_real_boot)
+
+       /* Interrupts should be disabled.  */
+        cli
+
+       /* Discard `grub_unix_real_boot' return address.  */
+        popl    %eax
+
+        /* Fetch `entry' address ...  */
+        popl   %eax
+
+        /*
+         * ... and put our return address in its place. The kernel will
+         * ignore it, but it expects %esp to point to it.
+         */
+        call   *%eax

reply via email to

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