/*
* GRUB -- GRand Unified Bootloader
- * Copyright (C) 2008 Free Software Foundation, Inc.
+ * Copyright (C) 2008, 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
#include <grub/cpu/bsd.h>
#include <grub/machine/init.h>
#include <grub/machine/memory.h>
+#include <grub/memory.h>
+#include <grub/machine/machine.h>
#include <grub/file.h>
#include <grub/err.h>
#include <grub/dl.h>
#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[] =
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));
+#define GRUB_E820_RAM 1
+#define GRUB_E820_RESERVED 2
+#define GRUB_E820_ACPI 3
+#define GRUB_E820_NVS 4
+#define GRUB_E820_EXEC_CODE 5
+
+static grub_err_t
+grub_freebsd_add_mmap (void)
+{
+ grub_size_t len = 0;
+ struct grub_e820_mmap *mmap_buf = 0;
+ struct grub_e820_mmap *mmap = 0;
+ int isfirstrun = 1;
+
+ 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)
+ {
+ /* FreeBSD assumes that first 64KiB are available.
+ Not always true but try to prevent panic somehow. */
+ if (isfirstrun && addr != 0)
+ {
+ if (mmap)
+ {
+ mmap->addr = 0;
+ mmap->size = (addr < 0x10000) ? addr : 0x10000;
+ mmap->type = GRUB_E820_RAM;
+ mmap++;
+ }
+ else
+ len += sizeof (struct grub_e820_mmap);
+ }
+ isfirstrun = 0;
+ if (mmap)
+ {
+ mmap->addr = addr;
+ mmap->size = size;
+ switch (type)
+ {
+ case GRUB_MACHINE_MEMORY_AVAILABLE:
+ mmap->type = GRUB_E820_RAM;
+ break;
+
+#ifdef GRUB_MACHINE_MEMORY_ACPI
+ case GRUB_MACHINE_MEMORY_ACPI:
+ mmap->type = GRUB_E820_ACPI;
+ break;
+#endif
+
+#ifdef GRUB_MACHINE_MEMORY_NVS
+ case GRUB_MACHINE_MEMORY_NVS:
+ mmap->type = GRUB_E820_NVS;
+ break;
+#endif
+
+ default:
+#ifdef GRUB_MACHINE_MEMORY_CODE
+ case GRUB_MACHINE_MEMORY_CODE:
+#endif
+#ifdef GRUB_MACHINE_MEMORY_RESERVED
+ case GRUB_MACHINE_MEMORY_RESERVED:
+#endif
+ mmap->type = GRUB_E820_RESERVED;
+ break;
+ }
+
+ /* Merge regions if possible. */
+ if (mmap != mmap_buf && mmap->type == mmap[-1].type &&
+ mmap->addr == mmap[-1].addr + mmap[-1].size)
+ mmap[-1].size += mmap->size;
+ else
+ mmap++;
+ }
+ else
+ len += sizeof (struct grub_e820_mmap);
+
+ return 0;
+ }
+
+ grub_mmap_iterate (hook);
+ mmap_buf = mmap = grub_malloc (len);
+ if (! mmap)
+ return grub_errno;
+
+ isfirstrun = 1;
+ grub_mmap_iterate (hook);
+
+ len = (mmap - mmap_buf) * sizeof (struct grub_e820_mmap);
+ int i;
+ for (i = 0; i < mmap - mmap_buf; i++)
+ grub_dprintf ("bsd", "smap %d, %d:%llx - %llx\n", i,
+ mmap_buf[i].type,
+ (unsigned long long) mmap_buf[i].addr,
+ (unsigned long long) mmap_buf[i].size);
+
+ grub_dprintf ("bsd", "%d entries in smap\n", mmap - mmap_buf);
+ 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)
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,
+ if (is_64bit)
+ {
+ grub_uint64_t addr64 = addr, size64 = size;
+ if ((grub_freebsd_add_meta (FREEBSD_MODINFO_TYPE, type,
grub_strlen (type) + 1)) ||
- (grub_freebsd_add_meta (FREEBSD_MODINFO_ADDR, &addr, sizeof (addr))) ||
- (grub_freebsd_add_meta (FREEBSD_MODINFO_SIZE, &size, sizeof (size))))
- return grub_errno;
+ (grub_freebsd_add_meta (FREEBSD_MODINFO_ADDR, &addr64,
+ sizeof (addr64))) ||
+ (grub_freebsd_add_meta (FREEBSD_MODINFO_SIZE, &size64,
+ sizeof (size64))))
+ return grub_errno;
+ }
+ else
+ {
+ if ((grub_freebsd_add_meta (FREEBSD_MODINFO_TYPE, type,
+ grub_strlen (type) + 1)) ||
+ (grub_freebsd_add_meta (FREEBSD_MODINFO_ADDR, &addr,
+ sizeof (addr))) ||
+ (grub_freebsd_add_meta (FREEBSD_MODINFO_SIZE, &size,
+ sizeof (size))))
+ return grub_errno;
+ }
if (argc)
{
}
}
+ 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;
}
}
}
- pos = ALIGN_DWORD (pos + size);
+ pos = ALIGN_VAR (pos + size);
}
}
+/* This function would be here but it's under different licence. */
+#include "bsd_pagetable.c"
+
+struct gdt_descriptor
+{
+ grub_uint16_t limit;
+ void *base;
+} __attribute__ ((packed));
+
static grub_err_t
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;
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)
+ {
+ grub_uint32_t *gdt;
+ grub_uint8_t *trampoline;
+ void (*launch_trampoline) (grub_addr_t entry, ...)
+ __attribute__ ((cdecl, regparm (0)));
+ grub_uint8_t *pagetable;
+
+ struct gdt_descriptor *gdtdesc;
+
+ pagetable = (grub_uint8_t *) (kern_end - 16384);
+ fill_bsd64_pagetable (pagetable);
+
+ /* Create GDT. */
+ gdt = (grub_uint32_t *) (kern_end - 4096);
+ gdt[0] = 0;
+ gdt[1] = 0;
+ gdt[2] = 0;
+ gdt[3] = 0x00209800;
+ gdt[4] = 0;
+ 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));
+
+ /* Copy trampoline. */
+ grub_memcpy (trampoline, &grub_bsd64_trampoline_start,
+ &grub_bsd64_trampoline_end - &grub_bsd64_trampoline_start);
+
+ /* Launch trampoline. */
+ launch_trampoline (entry, entry_hi, pagetable, 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;
return GRUB_ERR_NONE;
}
+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)
{
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");
}