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 . + */ + +#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 #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"); }