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.

-- 
Bean
diff --git a/include/grub/i386/bsd.h b/include/grub/i386/bsd.h
index f50f18e..f784d6a 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"
 
diff --git a/include/grub/i386/loader.h b/include/grub/i386/loader.h
index afd3eb9..4d5a913 100644
--- a/include/grub/i386/loader.h
+++ b/include/grub/i386/loader.h
@@ -35,4 +35,7 @@ grub_err_t EXPORT_FUNC(grub_linux16_boot) (void);
 void EXPORT_FUNC(grub_unix_real_boot) (grub_addr_t entry, ...)
      __attribute__ ((cdecl,noreturn));
 
+void EXPORT_FUNC(grub_bsd64_boot) (grub_addr_t entry, ...)
+     __attribute__ ((cdecl,noreturn));
+
 #endif /* ! GRUB_LOADER_CPU_HEADER */
diff --git a/kern/i386/bsd64.S b/kern/i386/bsd64.S
new file mode 100644
index 0000000..7a49d0b
--- /dev/null
+++ b/kern/i386/bsd64.S
@@ -0,0 +1,93 @@
+/*
+ *  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
+
+	.p2align	2
+
+bsd64_gdt:
+	.long   0
+	.long   0
+	.long   0x00000000
+	.long   0x00209800
+	.long   0x00000000
+	.long   0x00008000
+bsd64_gdtend:
+
+bsd64_gdtdesc:
+	.word   bsd64_gdtend - bsd64_gdt
+	.long	bsd64_gdt
+
+
+FUNCTION(grub_bsd64_boot)
+
+        call    EXT_C(grub_dl_unload_all)
+
+	/* 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	bsd64_gdtdesc
+	ljmp	$0x8, $ABS(bsd64_longmode)
+
+	.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
+
+	.code32
diff --git a/kern/i386/pc/startup.S b/kern/i386/pc/startup.S
index 8e8b661..e0e2caf 100644
--- a/kern/i386/pc/startup.S
+++ b/kern/i386/pc/startup.S
@@ -650,6 +650,8 @@ FUNCTION(grub_chainloader_real_boot)
 
 #include "../loader.S"
 
+#include "../bsd64.S"
+
 /*
  *   int grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap)
  *
diff --git a/loader/i386/bsd.c b/loader/i386/bsd.c
index 355cb3f..7a4b474 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,7 +309,7 @@ grub_freebsd_list_modules (void)
 	  }
 	}
 
-      pos = ALIGN_DWORD (pos + size);
+      pos = ALIGN_VAR (pos + size);
     }
 }
 
@@ -291,6 +359,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 +369,61 @@ grub_freebsd_boot (void)
       bi.bi_modulep = kern_end;
 
       kern_end = ALIGN_PAGE (kern_end + mod_buf_len);
+
+      if (is_64bit)
+	kern_end += 4096 * 3;
+
+      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;
+
+#define PG_V		0x001
+#define PG_RW		0x002
+#define PG_U		0x004
+#define PG_PS		0x080
+
+      pt4 = (grub_uint64_t *) (kern_end - 12288);
+      pt3 = (grub_uint64_t *) (kern_end - 8192);
+      pt2 = (grub_uint64_t *) (kern_end - 4096);
+
+      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 more than adequate.
+       */
+      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;
+	}
+
+      grub_bsd64_boot (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 +598,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 +630,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");
 }
_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to