diff options
Diffstat (limited to 'grub-core/loader/i386')
-rw-r--r-- | grub-core/loader/i386/bsd.c | 2191 | ||||
-rw-r--r-- | grub-core/loader/i386/bsd32.c | 6 | ||||
-rw-r--r-- | grub-core/loader/i386/bsd64.c | 6 | ||||
-rw-r--r-- | grub-core/loader/i386/bsdXX.c | 679 | ||||
-rw-r--r-- | grub-core/loader/i386/bsd_pagetable.c | 92 | ||||
-rw-r--r-- | grub-core/loader/i386/coreboot/chainloader.c | 517 | ||||
-rw-r--r-- | grub-core/loader/i386/linux.c | 1141 | ||||
-rw-r--r-- | grub-core/loader/i386/multiboot_mbi.c | 756 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/chainloader.c | 310 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/freedos.c | 190 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/linux.c | 494 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/ntldr.c | 162 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/plan9.c | 607 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/pxechainloader.c | 168 | ||||
-rw-r--r-- | grub-core/loader/i386/pc/truecrypt.c | 233 | ||||
-rw-r--r-- | grub-core/loader/i386/xen.c | 986 | ||||
-rw-r--r-- | grub-core/loader/i386/xen_file.c | 117 | ||||
-rw-r--r-- | grub-core/loader/i386/xen_file32.c | 7 | ||||
-rw-r--r-- | grub-core/loader/i386/xen_file64.c | 7 | ||||
-rw-r--r-- | grub-core/loader/i386/xen_fileXX.c | 395 | ||||
-rw-r--r-- | grub-core/loader/i386/xnu.c | 1166 |
21 files changed, 10230 insertions, 0 deletions
diff --git a/grub-core/loader/i386/bsd.c b/grub-core/loader/i386/bsd.c new file mode 100644 index 0000000..5f3290c --- /dev/null +++ b/grub-core/loader/i386/bsd.c @@ -0,0 +1,2191 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/i386/bsd.h> +#include <grub/i386/cpuid.h> +#include <grub/memory.h> +#include <grub/i386/memory.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/elfload.h> +#include <grub/env.h> +#include <grub/misc.h> +#include <grub/aout.h> +#include <grub/command.h> +#include <grub/extcmd.h> +#include <grub/i18n.h> +#include <grub/ns8250.h> +#include <grub/bsdlabel.h> +#include <grub/crypto.h> +#include <grub/safemath.h> +#include <grub/verify.h> +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/machine/int.h> +#endif + +GRUB_MOD_LICENSE ("GPLv3+"); + +#include <grub/video.h> +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/machine/biosnum.h> +#endif +#ifdef GRUB_MACHINE_EFI +#include <grub/efi/efi.h> +#define NETBSD_DEFAULT_VIDEO_MODE "800x600" +#else +#define NETBSD_DEFAULT_VIDEO_MODE "text" +#include <grub/i386/pc/vbe.h> +#endif +#include <grub/video.h> + +#include <grub/disk.h> +#include <grub/device.h> +#include <grub/partition.h> +#include <grub/relocator.h> +#include <grub/i386/relocator.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) + +static int kernel_type = KERNEL_TYPE_NONE; +static grub_dl_t my_mod; +static grub_addr_t entry, entry_hi, kern_start, kern_end; +static void *kern_chunk_src; +static grub_uint32_t bootflags; +static int is_elf_kernel, is_64bit; +static grub_uint32_t openbsd_root; +static struct grub_relocator *relocator = NULL; +static struct grub_openbsd_ramdisk_descriptor openbsd_ramdisk; + +struct bsd_tag +{ + struct bsd_tag *next; + grub_size_t len; + grub_uint32_t type; + union { + grub_uint8_t a; + grub_uint16_t b; + grub_uint32_t c; + } data[0]; +}; + +static struct bsd_tag *tags, *tags_last; + +struct netbsd_module +{ + struct netbsd_module *next; + struct grub_netbsd_btinfo_module mod; +}; + +static struct netbsd_module *netbsd_mods, *netbsd_mods_last; + +static const struct grub_arg_option freebsd_opts[] = + { + {"dual", 'D', 0, N_("Display output on all consoles."), 0, 0}, + {"serial", 'h', 0, N_("Use serial console."), 0, 0}, + {"askname", 'a', 0, N_("Ask for file name to reboot from."), 0, 0}, + {"cdrom", 'C', 0, N_("Use CD-ROM as root."), 0, 0}, + {"config", 'c', 0, N_("Invoke user configuration routing."), 0, 0}, + {"kdb", 'd', 0, N_("Enter in KDB on boot."), 0, 0}, + {"gdb", 'g', 0, N_("Use GDB remote debugger instead of DDB."), 0, 0}, + {"mute", 'm', 0, N_("Disable all boot output."), 0, 0}, + {"nointr", 'n', 0, "", 0, 0}, + {"pause", 'p', 0, N_("Wait for keypress after every line of output."), 0, 0}, + {"quiet", 'q', 0, "", 0, 0}, + {"dfltroot", 'r', 0, N_("Use compiled-in root device."), 0, 0}, + {"single", 's', 0, N_("Boot into single mode."), 0, 0}, + {"verbose", 'v', 0, N_("Boot with verbose messages."), 0, 0}, + {0, 0, 0, 0, 0, 0} + }; + +static const grub_uint32_t freebsd_flags[] = +{ + FREEBSD_RB_DUAL, FREEBSD_RB_SERIAL, FREEBSD_RB_ASKNAME, + FREEBSD_RB_CDROM, FREEBSD_RB_CONFIG, FREEBSD_RB_KDB, + FREEBSD_RB_GDB, FREEBSD_RB_MUTE, FREEBSD_RB_NOINTR, + FREEBSD_RB_PAUSE, FREEBSD_RB_QUIET, FREEBSD_RB_DFLTROOT, + FREEBSD_RB_SINGLE, FREEBSD_RB_VERBOSE, 0 +}; + +static const struct grub_arg_option openbsd_opts[] = + { + {"askname", 'a', 0, N_("Ask for file name to reboot from."), 0, 0}, + {"halt", 'b', 0, N_("Don't reboot, just halt."), 0, 0}, + {"config", 'c', 0, N_("Change configured devices."), 0, 0}, + {"single", 's', 0, N_("Boot into single mode."), 0, 0}, + {"kdb", 'd', 0, N_("Enter in KDB on boot."), 0, 0}, + {"root", 'r', 0, N_("Set root device."), "wdXY", ARG_TYPE_STRING}, + {"serial", 'h', GRUB_ARG_OPTION_OPTIONAL, + N_("Use serial console."), + /* TRANSLATORS: "com" is static and not to be translated. It refers to + serial ports e.g. com1. + */ + N_("comUNIT[,SPEED]"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static const grub_uint32_t openbsd_flags[] = +{ + OPENBSD_RB_ASKNAME, OPENBSD_RB_HALT, OPENBSD_RB_CONFIG, + OPENBSD_RB_SINGLE, OPENBSD_RB_KDB, 0 +}; + +#define OPENBSD_ROOT_ARG (ARRAY_SIZE (openbsd_flags) - 1) +#define OPENBSD_SERIAL_ARG (ARRAY_SIZE (openbsd_flags)) + +static const struct grub_arg_option netbsd_opts[] = + { + {"no-smp", '1', 0, N_("Disable SMP."), 0, 0}, + {"no-acpi", '2', 0, N_("Disable ACPI."), 0, 0}, + {"askname", 'a', 0, N_("Ask for file name to reboot from."), 0, 0}, + {"halt", 'b', 0, N_("Don't reboot, just halt."), 0, 0}, + {"config", 'c', 0, N_("Change configured devices."), 0, 0}, + {"kdb", 'd', 0, N_("Enter in KDB on boot."), 0, 0}, + {"miniroot", 'm', 0, "", 0, 0}, + {"quiet", 'q', 0, N_("Don't display boot diagnostic messages."), 0, 0}, + {"single", 's', 0, N_("Boot into single mode."), 0, 0}, + {"verbose", 'v', 0, N_("Boot with verbose messages."), 0, 0}, + {"debug", 'x', 0, N_("Boot with debug messages."), 0, 0}, + {"silent", 'z', 0, N_("Suppress normal output (warnings remain)."), 0, 0}, + {"root", 'r', 0, N_("Set root device."), N_("DEVICE"), ARG_TYPE_STRING}, + {"serial", 'h', GRUB_ARG_OPTION_OPTIONAL, + N_("Use serial console."), + /* TRANSLATORS: "com" is static and not to be translated. It refers to + serial ports e.g. com1. + */ + N_("[ADDR|comUNIT][,SPEED]"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static const grub_uint32_t netbsd_flags[] = +{ + NETBSD_AB_NOSMP, NETBSD_AB_NOACPI, NETBSD_RB_ASKNAME, + NETBSD_RB_HALT, NETBSD_RB_USERCONFIG, NETBSD_RB_KDB, + NETBSD_RB_MINIROOT, NETBSD_AB_QUIET, NETBSD_RB_SINGLE, + NETBSD_AB_VERBOSE, NETBSD_AB_DEBUG, NETBSD_AB_SILENT, 0 +}; + +#define NETBSD_ROOT_ARG (ARRAY_SIZE (netbsd_flags) - 1) +#define NETBSD_SERIAL_ARG (ARRAY_SIZE (netbsd_flags)) + +static void +grub_bsd_get_device (grub_uint32_t * biosdev, + grub_uint32_t * unit, + grub_uint32_t * slice, grub_uint32_t * part) +{ + grub_device_t dev; + +#ifdef GRUB_MACHINE_PCBIOS + *biosdev = grub_get_root_biosnumber () & 0xff; +#else + *biosdev = 0xff; +#endif + *unit = (*biosdev & 0x7f); + *slice = 0xff; + *part = 0xff; + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + if (dev->disk->partition->parent) + { + *part = dev->disk->partition->number; + *slice = dev->disk->partition->parent->number + 1; + } + else + *slice = dev->disk->partition->number + 1; + } + if (dev) + grub_device_close (dev); +} + +static grub_err_t +grub_bsd_add_meta_ptr (grub_uint32_t type, void **ptr, grub_uint32_t len) +{ + struct bsd_tag *newtag; + + newtag = grub_malloc (len + sizeof (struct bsd_tag)); + if (!newtag) + return grub_errno; + newtag->len = len; + newtag->type = type; + newtag->next = NULL; + *ptr = newtag->data; + + if (kernel_type == KERNEL_TYPE_FREEBSD + && type == (FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_SMAP)) + { + struct bsd_tag *p; + for (p = tags; + p && p->type != (FREEBSD_MODINFO_METADATA + | FREEBSD_MODINFOMD_KERNEND); + p = p->next); + + if (p) + { + newtag->next = p->next; + p->next = newtag; + if (newtag->next == NULL) + tags_last = newtag; + return GRUB_ERR_NONE; + } + } + + if (tags_last) + tags_last->next = newtag; + else + tags = newtag; + tags_last = newtag; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_bsd_add_meta (grub_uint32_t type, const void *data, grub_uint32_t len) +{ + grub_err_t err; + void *ptr; + + err = grub_bsd_add_meta_ptr (type, &ptr, len); + if (err) + return err; + if (len) + grub_memcpy (ptr, data, len); + return GRUB_ERR_NONE; +} + + +struct grub_e820_mmap +{ + grub_uint64_t addr; + grub_uint64_t size; + grub_uint32_t type; +} GRUB_PACKED; +#define GRUB_E820_RAM 1 +#define GRUB_E820_RESERVED 2 +#define GRUB_E820_ACPI 3 +#define GRUB_E820_NVS 4 +#define GRUB_E820_BADRAM 5 +#define GRUB_E820_COREBOOT_TABLES 0x10 + +/* Context for generate_e820_mmap. */ +struct generate_e820_mmap_ctx +{ + int count; + struct grub_e820_mmap *mmap; + struct grub_e820_mmap prev, cur; +}; + +/* Helper for generate_e820_mmap. */ +static int +generate_e820_mmap_iter (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type, void *data) +{ + struct generate_e820_mmap_ctx *ctx = data; + + ctx->cur.addr = addr; + ctx->cur.size = size; + + if (type == GRUB_MEMORY_COREBOOT_TABLES + && addr == 0) + /* Nowadays the tables at 0 don't contain anything important but + *BSD needs the memory at 0 for own needs. + */ + type = GRUB_E820_RAM; + + ctx->cur.type = type; + + /* Merge regions if possible. */ + if (ctx->count && ctx->cur.type == ctx->prev.type + && ctx->cur.addr == ctx->prev.addr + ctx->prev.size) + { + ctx->prev.size += ctx->cur.size; + if (ctx->mmap) + ctx->mmap[-1] = ctx->prev; + } + else + { + if (ctx->mmap) + *ctx->mmap++ = ctx->cur; + ctx->prev = ctx->cur; + ctx->count++; + } + + if (kernel_type == KERNEL_TYPE_OPENBSD && ctx->prev.addr < 0x100000 + && ctx->prev.addr + ctx->prev.size > 0x100000) + { + ctx->cur.addr = 0x100000; + ctx->cur.size = ctx->prev.addr + ctx->prev.size - 0x100000; + ctx->cur.type = ctx->prev.type; + ctx->prev.size = 0x100000 - ctx->prev.addr; + if (ctx->mmap) + { + ctx->mmap[-1] = ctx->prev; + ctx->mmap[0] = ctx->cur; + ctx->mmap++; + } + ctx->prev = ctx->cur; + ctx->count++; + } + + return 0; +} + +static void +generate_e820_mmap (grub_size_t *len, grub_size_t *cnt, void *buf) +{ + struct generate_e820_mmap_ctx ctx = { + .count = 0, + .mmap = buf + }; + + grub_mmap_iterate (generate_e820_mmap_iter, &ctx); + + if (len) + *len = ctx.count * sizeof (struct grub_e820_mmap); + *cnt = ctx.count; + + return; +} + +static grub_err_t +grub_bsd_add_mmap (void) +{ + grub_size_t len, cnt; + void *buf = NULL, *buf0; + + generate_e820_mmap (&len, &cnt, buf); + + if (kernel_type == KERNEL_TYPE_NETBSD) + len += sizeof (grub_uint32_t); + + if (kernel_type == KERNEL_TYPE_OPENBSD) + len += sizeof (struct grub_e820_mmap); + + buf = grub_malloc (len); + if (!buf) + return grub_errno; + + buf0 = buf; + if (kernel_type == KERNEL_TYPE_NETBSD) + { + *(grub_uint32_t *) buf = cnt; + buf = ((grub_uint32_t *) buf + 1); + } + + generate_e820_mmap (NULL, &cnt, buf); + + if (kernel_type == KERNEL_TYPE_OPENBSD) + grub_memset ((grub_uint8_t *) buf + len - sizeof (struct grub_e820_mmap), 0, + sizeof (struct grub_e820_mmap)); + + grub_dprintf ("bsd", "%u entries in smap\n", (unsigned) cnt); + if (kernel_type == KERNEL_TYPE_NETBSD) + grub_bsd_add_meta (NETBSD_BTINFO_MEMMAP, buf0, len); + else if (kernel_type == KERNEL_TYPE_OPENBSD) + grub_bsd_add_meta (OPENBSD_BOOTARG_MMAP, buf0, len); + else + grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_SMAP, buf0, len); + + grub_free (buf0); + + return grub_errno; +} + +grub_err_t +grub_freebsd_add_meta_module (const char *filename, const char *type, + int argc, char **argv, + grub_addr_t addr, grub_uint32_t size) +{ + const char *name; + grub_err_t err; + + name = grub_strrchr (filename, '/'); + if (name) + name++; + else + name = filename; + if (grub_strcmp (type, "/boot/zfs/zpool.cache") == 0) + name = "/boot/zfs/zpool.cache"; + + if (grub_bsd_add_meta (FREEBSD_MODINFO_NAME, name, grub_strlen (name) + 1)) + return grub_errno; + + if (is_64bit) + { + grub_uint64_t addr64 = addr, size64 = size; + if (grub_bsd_add_meta (FREEBSD_MODINFO_TYPE, type, grub_strlen (type) + 1) + || grub_bsd_add_meta (FREEBSD_MODINFO_ADDR, &addr64, sizeof (addr64)) + || grub_bsd_add_meta (FREEBSD_MODINFO_SIZE, &size64, sizeof (size64))) + return grub_errno; + } + else + { + if (grub_bsd_add_meta (FREEBSD_MODINFO_TYPE, type, grub_strlen (type) + 1) + || grub_bsd_add_meta (FREEBSD_MODINFO_ADDR, &addr, sizeof (addr)) + || grub_bsd_add_meta (FREEBSD_MODINFO_SIZE, &size, sizeof (size))) + return grub_errno; + } + + if (argc) + { + int i, n; + + n = 0; + for (i = 0; i < argc; i++) + { + n += grub_strlen (argv[i]) + 1; + } + + if (n) + { + void *cmdline; + char *p; + + if (grub_bsd_add_meta_ptr (FREEBSD_MODINFO_ARGS, &cmdline, n)) + return grub_errno; + + p = cmdline; + for (i = 0; i < argc; i++) + { + grub_strcpy (p, argv[i]); + p += grub_strlen (argv[i]); + *(p++) = ' '; + } + *p = 0; + err = grub_verify_string (cmdline, GRUB_VERIFY_MODULE_CMDLINE); + if (err) + return err; + } + } + + return GRUB_ERR_NONE; +} + +static void +grub_freebsd_list_modules (void) +{ + struct bsd_tag *tag; + + grub_printf (" %-18s %-18s%14s%14s\n", _("name"), _("type"), _("addr"), + _("size")); + + for (tag = tags; tag; tag = tag->next) + { + switch (tag->type) + { + case FREEBSD_MODINFO_NAME: + case FREEBSD_MODINFO_TYPE: + grub_printf (" %-18s", (char *) tag->data); + break; + case FREEBSD_MODINFO_ADDR: + { + grub_uint32_t addr; + + addr = *((grub_uint32_t *) tag->data); + grub_printf (" 0x%08x", addr); + break; + } + case FREEBSD_MODINFO_SIZE: + { + grub_uint32_t len; + + len = *((grub_uint32_t *) tag->data); + grub_printf (" 0x%08x\n", len); + } + } + } +} + +static grub_err_t +grub_netbsd_add_meta_module (char *filename, grub_uint32_t type, + grub_addr_t addr, grub_uint32_t size) +{ + char *name; + struct netbsd_module *mod; + name = grub_strrchr (filename, '/'); + + if (name) + name++; + else + name = filename; + + mod = grub_zalloc (sizeof (*mod)); + if (!mod) + return grub_errno; + + grub_strncpy (mod->mod.name, name, sizeof (mod->mod.name) - 1); + mod->mod.addr = addr; + mod->mod.type = type; + mod->mod.size = size; + + if (netbsd_mods_last) + netbsd_mods_last->next = mod; + else + netbsd_mods = mod; + netbsd_mods_last = mod; + + return GRUB_ERR_NONE; +} + +static void +grub_netbsd_list_modules (void) +{ + struct netbsd_module *mod; + + grub_printf (" %-18s%14s%14s%14s\n", _("name"), _("type"), _("addr"), + _("size")); + + for (mod = netbsd_mods; mod; mod = mod->next) + grub_printf (" %-18s 0x%08x 0x%08x 0x%08x", mod->mod.name, + mod->mod.type, mod->mod.addr, mod->mod.size); +} + +/* This function would be here but it's under different license. */ +#include "bsd_pagetable.c" + +static grub_uint32_t freebsd_bootdev, freebsd_biosdev; +static grub_uint64_t freebsd_zfsguid; + +static void +freebsd_get_zfs (void) +{ + grub_device_t dev; + grub_fs_t fs; + char *uuid; + grub_err_t err; + + dev = grub_device_open (0); + if (!dev) + return; + fs = grub_fs_probe (dev); + if (!fs) + return; + if (!fs->fs_uuid || grub_strcmp (fs->name, "zfs") != 0) + return; + err = fs->fs_uuid (dev, &uuid); + if (err) + return; + if (!uuid) + return; + freebsd_zfsguid = grub_strtoull (uuid, 0, 16); + grub_free (uuid); +} + +static grub_err_t +grub_freebsd_boot (void) +{ + struct grub_freebsd_bootinfo bi; + grub_uint8_t *p, *p0; + grub_addr_t p_target; + grub_size_t p_size = 0; + grub_err_t err; + grub_size_t tag_buf_len = 0; + + struct grub_env_var *var; + + grub_memset (&bi, 0, sizeof (bi)); + bi.version = FREEBSD_BOOTINFO_VERSION; + bi.length = sizeof (bi); + + bi.boot_device = freebsd_biosdev; + + p_size = 0; + FOR_SORTED_ENV (var) + if ((grub_memcmp (var->name, "kFreeBSD.", sizeof("kFreeBSD.") - 1) == 0) && (var->name[sizeof("kFreeBSD.") - 1])) + { + p_size += grub_strlen (&var->name[sizeof("kFreeBSD.") - 1]); + p_size++; + p_size += grub_strlen (var->value) + 1; + } + + if (p_size) + p_size = ALIGN_PAGE (kern_end + p_size + 1) - kern_end; + + if (is_elf_kernel) + { + struct bsd_tag *tag; + + err = grub_bsd_add_mmap (); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_END, 0, 0); + if (err) + return err; + + tag_buf_len = 0; + for (tag = tags; tag; tag = tag->next) + tag_buf_len = ALIGN_VAR (tag_buf_len + + sizeof (struct freebsd_tag_header) + + tag->len); + p_size = ALIGN_PAGE (kern_end + p_size + tag_buf_len) - kern_end; + } + + if (is_64bit) + p_size += 4096 * 3; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + kern_end, p_size); + if (err) + return err; + p = get_virtual_current_address (ch); + } + p_target = kern_end; + p0 = p; + kern_end += p_size; + + FOR_SORTED_ENV (var) + if ((grub_memcmp (var->name, "kFreeBSD.", sizeof("kFreeBSD.") - 1) == 0) && (var->name[sizeof("kFreeBSD.") - 1])) + { + grub_strcpy ((char *) p, &var->name[sizeof("kFreeBSD.") - 1]); + p += grub_strlen ((char *) p); + *(p++) = '='; + grub_strcpy ((char *) p, var->value); + p += grub_strlen ((char *) p) + 1; + } + + if (p != p0) + { + *(p++) = 0; + + bi.environment = p_target; + } + + if (is_elf_kernel) + { + grub_uint8_t *p_tag = p; + struct bsd_tag *tag; + + for (tag = tags; tag; tag = tag->next) + { + struct freebsd_tag_header *head + = (struct freebsd_tag_header *) p_tag; + head->type = tag->type; + head->len = tag->len; + p_tag += sizeof (struct freebsd_tag_header); + switch (tag->type) + { + case FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_HOWTO: + if (is_64bit) + *(grub_uint64_t *) p_tag = bootflags; + else + *(grub_uint32_t *) p_tag = bootflags; + break; + + case FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_ENVP: + if (is_64bit) + *(grub_uint64_t *) p_tag = bi.environment; + else + *(grub_uint32_t *) p_tag = bi.environment; + break; + + case FREEBSD_MODINFO_METADATA | FREEBSD_MODINFOMD_KERNEND: + if (is_64bit) + *(grub_uint64_t *) p_tag = kern_end; + else + *(grub_uint32_t *) p_tag = kern_end; + break; + + default: + grub_memcpy (p_tag, tag->data, tag->len); + break; + } + p_tag += tag->len; + p_tag = ALIGN_VAR (p_tag - p) + p; + } + + bi.tags = (p - p0) + p_target; + + p = (ALIGN_PAGE ((p_tag - p0) + p_target) - p_target) + p0; + } + + bi.kern_end = kern_end; + + grub_video_set_mode ("text", 0, 0); + + if (is_64bit) + { + struct grub_relocator64_state state; + grub_uint8_t *pagetable; + grub_uint32_t *stack; + grub_addr_t stack_target; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + 0x10000, 0x90000, + 3 * sizeof (grub_uint32_t) + + sizeof (bi), 4, + GRUB_RELOCATOR_PREFERENCE_NONE, + 0); + if (err) + return err; + stack = get_virtual_current_address (ch); + stack_target = get_physical_target_address (ch); + } + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + pagetable = p; + fill_bsd64_pagetable (pagetable, (pagetable - p0) + p_target); + + state.cr3 = (pagetable - p0) + p_target; + state.rsp = stack_target; + state.rip = (((grub_uint64_t) entry_hi) << 32) | entry; + + stack[0] = entry; + stack[1] = bi.tags; + stack[2] = kern_end; + return grub_relocator64_boot (relocator, state, 0, 0x40000000); + } + else + { + struct grub_relocator32_state state; + grub_uint32_t *stack; + grub_addr_t stack_target; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + 0x10000, 0x90000, + 9 * sizeof (grub_uint32_t) + + sizeof (bi), 4, + GRUB_RELOCATOR_PREFERENCE_NONE, + 0); + if (err) + return err; + stack = get_virtual_current_address (ch); + stack_target = get_physical_target_address (ch); + } + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + grub_memcpy (&stack[9], &bi, sizeof (bi)); + state.eip = entry; + state.esp = stack_target; + state.ebp = stack_target; + stack[0] = entry; /* "Return" address. */ + stack[1] = bootflags | FREEBSD_RB_BOOTINFO; + stack[2] = freebsd_bootdev; + stack[3] = freebsd_zfsguid ? 4 : 0; + stack[4] = freebsd_zfsguid; + stack[5] = freebsd_zfsguid >> 32; + stack[6] = stack_target + 9 * sizeof (grub_uint32_t); + stack[7] = bi.tags; + stack[8] = kern_end; + return grub_relocator32_boot (relocator, state, 0); + } + + /* Not reached. */ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_openbsd_boot (void) +{ + grub_uint32_t *stack; + struct grub_relocator32_state state; + void *curarg, *buf0, *arg0; + grub_addr_t buf_target; + grub_err_t err; + grub_size_t tag_buf_len; + + err = grub_bsd_add_mmap (); + if (err) + return err; + +#ifdef GRUB_MACHINE_PCBIOS + { + struct grub_bios_int_registers regs; + + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + + regs.ebx = 0; + regs.ecx = 0; + regs.eax = 0xb101; + regs.es = 0; + regs.edi = 0; + regs.edx = 0; + + grub_bios_interrupt (0x1a, ®s); + if (regs.edx == 0x20494350) + { + struct grub_openbsd_bootarg_pcibios pcibios; + + pcibios.characteristics = regs.eax & 0xff; + pcibios.revision = regs.ebx & 0xffff; + pcibios.pm_entry = regs.edi; + pcibios.last_bus = regs.ecx & 0xff; + + grub_bsd_add_meta (OPENBSD_BOOTARG_PCIBIOS, &pcibios, + sizeof (pcibios)); + } + } +#endif + + { + struct bsd_tag *tag; + tag_buf_len = 0; + for (tag = tags; tag; tag = tag->next) + tag_buf_len = ALIGN_VAR (tag_buf_len + + sizeof (struct grub_openbsd_bootargs) + + tag->len); + } + + buf_target = GRUB_BSD_TEMP_BUFFER - 9 * sizeof (grub_uint32_t); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, buf_target, + tag_buf_len + + sizeof (struct grub_openbsd_bootargs) + + 9 * sizeof (grub_uint32_t)); + if (err) + return err; + buf0 = get_virtual_current_address (ch); + } + + stack = (grub_uint32_t *) buf0; + arg0 = curarg = stack + 9; + + { + struct bsd_tag *tag; + struct grub_openbsd_bootargs *head; + + for (tag = tags; tag; tag = tag->next) + { + head = curarg; + head->ba_type = tag->type; + head->ba_size = tag->len + sizeof (*head); + curarg = head + 1; + grub_memcpy (curarg, tag->data, tag->len); + curarg = (grub_uint8_t *) curarg + tag->len; + head->ba_next = (grub_uint8_t *) curarg - (grub_uint8_t *) buf0 + + buf_target; + } + head = curarg; + head->ba_type = OPENBSD_BOOTARG_END; + head->ba_size = 0; + head->ba_next = 0; + } + + grub_video_set_mode ("text", 0, 0); + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + state.eip = entry; + state.ebp = state.esp + = ((grub_uint8_t *) stack - (grub_uint8_t *) buf0) + buf_target; + stack[0] = entry; + stack[1] = bootflags; + stack[2] = openbsd_root; + stack[3] = OPENBSD_BOOTARG_APIVER; + stack[4] = 0; + stack[5] = grub_mmap_get_upper () >> 10; + stack[6] = grub_mmap_get_lower () >> 10; + stack[7] = (grub_uint8_t *) curarg - (grub_uint8_t *) arg0; + stack[8] = ((grub_uint8_t *) arg0 - (grub_uint8_t *) buf0) + buf_target; + + return grub_relocator32_boot (relocator, state, 0); +} + +static grub_err_t +grub_netbsd_setup_video (void) +{ + struct grub_video_mode_info mode_info; + void *framebuffer; + const char *modevar; + struct grub_netbsd_btinfo_framebuf params; + grub_err_t err; + grub_video_driver_id_t driv_id; + + modevar = grub_env_get ("gfxpayload"); + + /* Now all graphical modes are acceptable. + May change in future if we have modes without framebuffer. */ + if (modevar && *modevar != 0) + { + char *tmp; + tmp = grub_xasprintf ("%s;" NETBSD_DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; + err = grub_video_set_mode (tmp, 0, 0); + grub_free (tmp); + } + else + err = grub_video_set_mode (NETBSD_DEFAULT_VIDEO_MODE, 0, 0); + + if (err) + return err; + + driv_id = grub_video_get_driver_id (); + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return GRUB_ERR_NONE; + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + + if (err) + return err; + + params.width = mode_info.width; + params.height = mode_info.height; + params.bpp = mode_info.bpp; + params.pitch = mode_info.pitch; + params.flags = 0; + + params.fbaddr = (grub_addr_t) framebuffer; + + params.red_mask_size = mode_info.red_mask_size; + params.red_field_pos = mode_info.red_field_pos; + params.green_mask_size = mode_info.green_mask_size; + params.green_field_pos = mode_info.green_field_pos; + params.blue_mask_size = mode_info.blue_mask_size; + params.blue_field_pos = mode_info.blue_field_pos; + +#ifdef GRUB_MACHINE_PCBIOS + /* VESA packed modes may come with zeroed mask sizes, which need + to be set here according to DAC Palette width. If we don't, + this results in Linux displaying a black screen. */ + if (mode_info.bpp <= 8 && driv_id == GRUB_VIDEO_DRIVER_VBE) + { + struct grub_vbe_info_block controller_info; + int status; + int width = 8; + + status = grub_vbe_bios_get_controller_info (&controller_info); + + if (status == GRUB_VBE_STATUS_OK && + (controller_info.capabilities & GRUB_VBE_CAPABILITY_DACWIDTH)) + status = grub_vbe_bios_set_dac_palette_width (&width); + + if (status != GRUB_VBE_STATUS_OK) + /* 6 is default after mode reset. */ + width = 6; + + params.red_mask_size = params.green_mask_size + = params.blue_mask_size = width; + } +#endif + + err = grub_bsd_add_meta (NETBSD_BTINFO_FRAMEBUF, ¶ms, sizeof (params)); + return err; +} + +static grub_err_t +grub_netbsd_add_modules (void) +{ + struct netbsd_module *mod; + unsigned modcnt = 0; + struct grub_netbsd_btinfo_modules *mods; + unsigned i; + grub_err_t err; + grub_size_t sz; + + for (mod = netbsd_mods; mod; mod = mod->next) + modcnt++; + + if (grub_mul (modcnt, sizeof (mods->mods[0]), &sz) || + grub_add (sz, sizeof (*mods), &sz)) + return GRUB_ERR_OUT_OF_RANGE; + + mods = grub_malloc (sz); + if (!mods) + return grub_errno; + + mods->num = modcnt; + mods->last_addr = kern_end; + for (mod = netbsd_mods, i = 0; mod; i++, mod = mod->next) + mods->mods[i] = mod->mod; + + err = grub_bsd_add_meta (NETBSD_BTINFO_MODULES, mods, + sizeof (*mods) + sizeof (mods->mods[0]) * modcnt); + grub_free (mods); + return err; +} + +/* + * Adds NetBSD bootinfo bootdisk and bootwedge. The partition identified + * in these bootinfo fields is the root device. + */ +static void +grub_netbsd_add_boot_disk_and_wedge (void) +{ + grub_device_t dev; + grub_disk_t disk; + grub_partition_t part; + grub_uint32_t biosdev; + grub_uint32_t partmapsector; + union { + grub_uint64_t raw[GRUB_DISK_SECTOR_SIZE / 8]; + struct grub_partition_bsd_disk_label label; + } buf; + + if (GRUB_MD_MD5->mdlen > GRUB_CRYPTO_MAX_MDLEN) + { + grub_error (GRUB_ERR_BUG, "mdlen too long"); + return; + } + + dev = grub_device_open (0); + if (! (dev && dev->disk && dev->disk->partition)) + goto fail; + + disk = dev->disk; + part = disk->partition; + + if (disk->dev && disk->dev->id == GRUB_DISK_DEVICE_BIOSDISK_ID) + biosdev = (grub_uint32_t) disk->id & 0xff; + else + biosdev = 0xff; + + /* Absolute sector of the partition map describing this partition. */ + partmapsector = grub_partition_get_start (part->parent) + part->offset; + + disk->partition = part->parent; + if (grub_disk_read (disk, part->offset, 0, GRUB_DISK_SECTOR_SIZE, buf.raw) + != GRUB_ERR_NONE) + goto fail; + disk->partition = part; + + /* Fill bootwedge. */ + { + struct grub_netbsd_btinfo_bootwedge biw; + grub_uint8_t hash[GRUB_CRYPTO_MAX_MDLEN]; + + grub_memset (&biw, 0, sizeof (biw)); + biw.biosdev = biosdev; + biw.startblk = grub_partition_get_start (part); + biw.nblks = part->len; + biw.matchblk = partmapsector; + biw.matchnblks = 1; + + grub_crypto_hash (GRUB_MD_MD5, hash, + buf.raw, GRUB_DISK_SECTOR_SIZE); + grub_memcpy (biw.matchhash, hash, 16); + + grub_bsd_add_meta (NETBSD_BTINFO_BOOTWEDGE, &biw, sizeof (biw)); + } + + /* Fill bootdisk. */ + { + struct grub_netbsd_btinfo_bootdisk bid; + + grub_memset (&bid, 0, sizeof (bid)); + /* Check for a NetBSD disk label. */ + if (part->partmap != NULL && + (grub_strcmp (part->partmap->name, "netbsd") == 0 || + (part->parent == NULL && grub_strcmp (part->partmap->name, "bsd") == 0))) + { + bid.labelsector = partmapsector; + bid.label.type = buf.label.type; + bid.label.checksum = buf.label.checksum; + grub_memcpy (bid.label.packname, buf.label.packname, 16); + } + else + { + bid.labelsector = -1; + } + bid.biosdev = biosdev; + bid.partition = part->number; + + grub_bsd_add_meta (NETBSD_BTINFO_BOOTDISK, &bid, sizeof (bid)); + } + +fail: + if (dev) + grub_device_close (dev); +} + +static grub_err_t +grub_netbsd_boot (void) +{ + struct grub_netbsd_bootinfo *bootinfo; + void *curarg, *arg0; + grub_addr_t arg_target, stack_target; + grub_uint32_t *stack; + grub_err_t err; + struct grub_relocator32_state state; + grub_size_t tag_buf_len = 0; + int tag_count = 0; + + err = grub_bsd_add_mmap (); + if (err) + return err; + + err = grub_netbsd_setup_video (); + if (err) + { + grub_print_error (); + grub_puts_ (N_("Booting in blind mode")); + grub_errno = GRUB_ERR_NONE; + } + + err = grub_netbsd_add_modules (); + if (err) + return err; + +#ifdef GRUB_MACHINE_EFI + err = grub_bsd_add_meta (NETBSD_BTINFO_EFI, + &grub_efi_system_table, + sizeof (grub_efi_system_table)); + if (err) + return err; +#endif + + { + struct bsd_tag *tag; + tag_buf_len = 0; + for (tag = tags; tag; tag = tag->next) + { + tag_buf_len = ALIGN_VAR (tag_buf_len + + sizeof (struct grub_netbsd_btinfo_common) + + tag->len); + tag_count++; + } + } + + arg_target = kern_end; + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + arg_target, tag_buf_len + + sizeof (struct grub_netbsd_bootinfo) + + tag_count * sizeof (grub_uint32_t)); + if (err) + return err; + curarg = get_virtual_current_address (ch); + } + + arg0 = curarg; + bootinfo = (void *) ((grub_uint8_t *) arg0 + tag_buf_len); + + { + struct bsd_tag *tag; + unsigned i; + + bootinfo->bi_count = tag_count; + for (tag = tags, i = 0; tag; i++, tag = tag->next) + { + struct grub_netbsd_btinfo_common *head = curarg; + bootinfo->bi_data[i] = ((grub_uint8_t *) curarg - (grub_uint8_t *) arg0) + + arg_target; + head->type = tag->type; + head->len = tag->len + sizeof (*head); + curarg = head + 1; + grub_memcpy (curarg, tag->data, tag->len); + curarg = (grub_uint8_t *) curarg + tag->len; + } + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, 0x10000, 0x90000, + 7 * sizeof (grub_uint32_t), 4, + GRUB_RELOCATOR_PREFERENCE_NONE, + 0); + if (err) + return err; + stack = get_virtual_current_address (ch); + stack_target = get_physical_target_address (ch); + } + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + state.eip = entry; + state.esp = stack_target; + state.ebp = stack_target; + stack[0] = entry; + stack[1] = bootflags; + stack[2] = 0; + stack[3] = ((grub_uint8_t *) bootinfo - (grub_uint8_t *) arg0) + arg_target; + stack[4] = 0; + stack[5] = grub_mmap_get_upper () >> 10; + stack[6] = grub_mmap_get_lower () >> 10; + + return grub_relocator32_boot (relocator, state, 0); +} + +static grub_err_t +grub_bsd_unload (void) +{ + struct bsd_tag *tag, *next; + for (tag = tags; tag; tag = next) + { + next = tag->next; + grub_free (tag); + } + tags = NULL; + tags_last = NULL; + + kernel_type = KERNEL_TYPE_NONE; + grub_dl_unref (my_mod); + + grub_relocator_unload (relocator); + relocator = NULL; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_bsd_load_aout (grub_file_t file, const char *filename) +{ + grub_addr_t load_addr, load_end; + int ofs, align_page; + union grub_aout_header ah; + grub_err_t err; + grub_size_t bss_size; + + if ((grub_file_seek (file, 0)) == (grub_off_t) - 1) + return grub_errno; + + if (grub_file_read (file, &ah, sizeof (ah)) != sizeof (ah)) + { + if (!grub_errno) + grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file %s"), + filename); + return grub_errno; + } + + if (grub_aout_get_type (&ah) != AOUT_TYPE_AOUT32) + return grub_error (GRUB_ERR_BAD_OS, "invalid a.out header"); + + entry = ah.aout32.a_entry & 0xFFFFFF; + + if (AOUT_GETMAGIC (ah.aout32) == AOUT32_ZMAGIC) + { + load_addr = entry; + ofs = 0x1000; + align_page = 0; + } + else + { + load_addr = entry & 0xF00000; + ofs = sizeof (struct grub_aout32_header); + align_page = 1; + } + + if (load_addr < 0x100000) + return grub_error (GRUB_ERR_BAD_OS, "load address below 1M"); + + kern_start = load_addr; + load_end = kern_end = load_addr + ah.aout32.a_text + ah.aout32.a_data; + if (align_page) + kern_end = ALIGN_PAGE (kern_end); + + if (ah.aout32.a_bss) + { + kern_end += ah.aout32.a_bss; + if (align_page) + kern_end = ALIGN_PAGE (kern_end); + + bss_size = kern_end - load_end; + } + else + bss_size = 0; + + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + kern_start, kern_end - kern_start); + if (err) + return err; + kern_chunk_src = get_virtual_current_address (ch); + } + + return grub_aout_load (file, ofs, kern_chunk_src, + ah.aout32.a_text + ah.aout32.a_data, + bss_size); +} + +static grub_err_t +grub_bsd_load_elf (grub_elf_t elf, const char *filename) +{ + grub_err_t err; + + kern_end = 0; + kern_start = ~0; + + if (grub_elf_is_elf32 (elf)) + { + grub_relocator_chunk_t ch; + Elf32_Phdr *phdr; + + entry = elf->ehdr.ehdr32.e_entry & 0xFFFFFFF; + + FOR_ELF32_PHDRS (elf, phdr) + { + Elf32_Addr paddr; + + if (phdr->p_type != PT_LOAD + && phdr->p_type != PT_DYNAMIC) + continue; + + paddr = phdr->p_paddr & 0xFFFFFFF; + + if (paddr < kern_start) + kern_start = paddr; + + if (paddr + phdr->p_memsz > kern_end) + kern_end = paddr + phdr->p_memsz; + } + + if (grub_errno) + return grub_errno; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + kern_start, kern_end - kern_start); + if (err) + return err; + + kern_chunk_src = get_virtual_current_address (ch); + + err = grub_elf32_load (elf, filename, (grub_uint8_t *) kern_chunk_src - kern_start, GRUB_ELF_LOAD_FLAGS_LOAD_PT_DYNAMIC | GRUB_ELF_LOAD_FLAGS_28BITS, 0, 0); + if (err) + return err; + if (kernel_type != KERNEL_TYPE_OPENBSD) + return GRUB_ERR_NONE; + return grub_openbsd_find_ramdisk32 (elf->file, filename, kern_start, + kern_chunk_src, &openbsd_ramdisk); + } + else if (grub_elf_is_elf64 (elf)) + { + Elf64_Phdr *phdr; + + is_64bit = 1; + + if (! grub_cpuid_has_longmode) + return grub_error (GRUB_ERR_BAD_OS, "your CPU does not implement AMD64 architecture"); + + /* FreeBSD has 64-bit entry point. */ + if (kernel_type == KERNEL_TYPE_FREEBSD) + { + entry = elf->ehdr.ehdr64.e_entry & 0xffffffff; + entry_hi = (elf->ehdr.ehdr64.e_entry >> 32) & 0xffffffff; + } + else + { + entry = elf->ehdr.ehdr64.e_entry & 0x0fffffff; + entry_hi = 0; + } + + FOR_ELF64_PHDRS (elf, phdr) + { + Elf64_Addr paddr; + + if (phdr->p_type != PT_LOAD + && phdr->p_type != PT_DYNAMIC) + continue; + + paddr = phdr->p_paddr & 0xFFFFFFF; + + if (paddr < kern_start) + kern_start = paddr; + + if (paddr + phdr->p_memsz > kern_end) + kern_end = paddr + phdr->p_memsz; + } + + if (grub_errno) + return grub_errno; + + grub_dprintf ("bsd", "kern_start = %lx, kern_end = %lx\n", + (unsigned long) kern_start, (unsigned long) kern_end); + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, kern_start, + kern_end - kern_start); + if (err) + return err; + kern_chunk_src = get_virtual_current_address (ch); + } + + err = grub_elf64_load (elf, filename, + (grub_uint8_t *) kern_chunk_src - kern_start, GRUB_ELF_LOAD_FLAGS_LOAD_PT_DYNAMIC | GRUB_ELF_LOAD_FLAGS_28BITS, 0, 0); + if (err) + return err; + if (kernel_type != KERNEL_TYPE_OPENBSD) + return GRUB_ERR_NONE; + return grub_openbsd_find_ramdisk64 (elf->file, filename, kern_start, + kern_chunk_src, &openbsd_ramdisk); + } + else + return grub_error (GRUB_ERR_BAD_OS, N_("invalid arch-dependent ELF magic")); +} + +static grub_err_t +grub_bsd_load (int argc, char *argv[]) +{ + grub_file_t file; + grub_elf_t elf; + + grub_dl_ref (my_mod); + + grub_loader_unset (); + + grub_memset (&openbsd_ramdisk, 0, sizeof (openbsd_ramdisk)); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_BSD_KERNEL); + if (!file) + goto fail; + + relocator = grub_relocator_new (); + if (!relocator) + { + grub_file_close (file); + goto fail; + } + + elf = grub_elf_file (file, argv[0]); + if (elf) + { + is_elf_kernel = 1; + grub_bsd_load_elf (elf, argv[0]); + grub_elf_close (elf); + } + else + { + is_elf_kernel = 0; + grub_errno = 0; + grub_bsd_load_aout (file, argv[0]); + grub_file_close (file); + } + + kern_end = ALIGN_PAGE (kern_end); + +fail: + + if (grub_errno != GRUB_ERR_NONE) + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_uint32_t +grub_bsd_parse_flags (const struct grub_arg_list *state, + const grub_uint32_t * flags) +{ + grub_uint32_t result = 0; + unsigned i; + + for (i = 0; flags[i]; i++) + if (state[i].set) + result |= flags[i]; + + return result; +} + +static grub_err_t +grub_cmd_freebsd (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + kernel_type = KERNEL_TYPE_FREEBSD; + bootflags = grub_bsd_parse_flags (ctxt->state, freebsd_flags); + + if (grub_bsd_load (argc, argv) == GRUB_ERR_NONE) + { + grub_uint32_t unit, slice, part; + + kern_end = ALIGN_PAGE (kern_end); + if (is_elf_kernel) + { + grub_err_t err; + grub_uint64_t data = 0; + grub_file_t file; + int len = is_64bit ? 8 : 4; + + err = grub_freebsd_add_meta_module (argv[0], is_64bit + ? FREEBSD_MODTYPE_KERNEL64 + : FREEBSD_MODTYPE_KERNEL, + argc - 1, argv + 1, + kern_start, + kern_end - kern_start); + if (err) + return err; + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_BSD_KERNEL); + if (! file) + return grub_errno; + + if (is_64bit) + err = grub_freebsd_load_elf_meta64 (relocator, file, argv[0], + &kern_end); + else + err = grub_freebsd_load_elf_meta32 (relocator, file, argv[0], + &kern_end); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_HOWTO, &data, 4); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_ENVP, &data, len); + if (err) + return err; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_KERNEND, &data, len); + if (err) + return err; + } + grub_bsd_get_device (&freebsd_biosdev, &unit, &slice, &part); + freebsd_zfsguid = 0; + if (!is_64bit) + freebsd_get_zfs (); + grub_print_error (); + freebsd_bootdev = (FREEBSD_B_DEVMAGIC + ((slice + 1) << FREEBSD_B_SLICESHIFT) + + (unit << FREEBSD_B_UNITSHIFT) + (part << FREEBSD_B_PARTSHIFT)); + + grub_loader_set (grub_freebsd_boot, grub_bsd_unload, 0); + } + + return grub_errno; +} + +static const char *types[] = { + [0] = "wd", + [2] = "fd", + [4] = "sd", + [6] = "cd", + [14] = "vnd", + [17] = "rd" +}; + +static grub_err_t +grub_cmd_openbsd (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + grub_uint32_t bootdev; + + kernel_type = KERNEL_TYPE_OPENBSD; + bootflags = grub_bsd_parse_flags (ctxt->state, openbsd_flags); + + if (ctxt->state[OPENBSD_ROOT_ARG].set && ctxt->state[OPENBSD_ROOT_ARG].arg != NULL) + { + const char *arg = ctxt->state[OPENBSD_ROOT_ARG].arg; + unsigned type, unit, part; + for (type = 0; type < ARRAY_SIZE (types); type++) + if (types[type] + && grub_strncmp (arg, types[type], + grub_strlen (types[type])) == 0) + { + arg += grub_strlen (types[type]); + break; + } + if (type == ARRAY_SIZE (types)) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "unknown disk type name"); + + unit = grub_strtoul (arg, &arg, 10); + if (! (*arg >= 'a' && *arg <= 'z')) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only device specifications of form " + "<type><number><lowercase letter> are supported"); + + part = *arg - 'a'; + + bootdev = (OPENBSD_B_DEVMAGIC | (type << OPENBSD_B_TYPESHIFT) + | (unit << OPENBSD_B_UNITSHIFT) + | (part << OPENBSD_B_PARTSHIFT)); + } + else + bootdev = 0; + + if (ctxt->state[OPENBSD_SERIAL_ARG].set) + { + struct grub_openbsd_bootarg_console serial; + const char *ptr; + unsigned port = 0; + unsigned speed = 9600; + + grub_memset (&serial, 0, sizeof (serial)); + + if (ctxt->state[OPENBSD_SERIAL_ARG].arg) + { + ptr = ctxt->state[OPENBSD_SERIAL_ARG].arg; + if (grub_memcmp (ptr, "com", sizeof ("com") - 1) != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only com0-com3 are supported"); + ptr += sizeof ("com") - 1; + port = grub_strtoul (ptr, &ptr, 0); + if (port >= 4) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only com0-com3 are supported"); + if (*ptr == ',') + { + ptr++; + speed = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + } + } + + serial.device = (GRUB_OPENBSD_COM_MAJOR << 8) | port; + serial.speed = speed; + serial.addr = grub_ns8250_hw_get_port (port); + + grub_bsd_add_meta (OPENBSD_BOOTARG_CONSOLE, &serial, sizeof (serial)); + bootflags |= OPENBSD_RB_SERCONS; + } + else + { + struct grub_openbsd_bootarg_console serial; + + grub_memset (&serial, 0, sizeof (serial)); + serial.device = (GRUB_OPENBSD_VGA_MAJOR << 8); + serial.addr = 0xffffffff; + grub_bsd_add_meta (OPENBSD_BOOTARG_CONSOLE, &serial, sizeof (serial)); + bootflags &= ~OPENBSD_RB_SERCONS; + } + + if (grub_bsd_load (argc, argv) == GRUB_ERR_NONE) + { + grub_loader_set (grub_openbsd_boot, grub_bsd_unload, 0); + openbsd_root = bootdev; + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_netbsd (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + grub_err_t err; + kernel_type = KERNEL_TYPE_NETBSD; + bootflags = grub_bsd_parse_flags (ctxt->state, netbsd_flags); + + if (grub_bsd_load (argc, argv) == GRUB_ERR_NONE) + { + if (is_elf_kernel) + { + grub_file_t file; + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_BSD_KERNEL); + if (! file) + return grub_errno; + + if (is_64bit) + err = grub_netbsd_load_elf_meta64 (relocator, file, argv[0], &kern_end); + else + err = grub_netbsd_load_elf_meta32 (relocator, file, argv[0], &kern_end); + if (err) + return err; + } + + { + char bootpath[GRUB_NETBSD_MAX_BOOTPATH_LEN]; + char *name; + name = grub_strrchr (argv[0], '/'); + if (name) + name++; + else + name = argv[0]; + grub_memset (bootpath, 0, sizeof (bootpath)); + grub_strncpy (bootpath, name, sizeof (bootpath) - 1); + grub_bsd_add_meta (NETBSD_BTINFO_BOOTPATH, bootpath, sizeof (bootpath)); + } + + if (ctxt->state[NETBSD_ROOT_ARG].set) + { + char root[GRUB_NETBSD_MAX_ROOTDEVICE_LEN]; + grub_memset (root, 0, sizeof (root)); + grub_strncpy (root, ctxt->state[NETBSD_ROOT_ARG].arg, + sizeof (root) - 1); + grub_bsd_add_meta (NETBSD_BTINFO_ROOTDEVICE, root, sizeof (root)); + } + if (ctxt->state[NETBSD_SERIAL_ARG].set) + { + struct grub_netbsd_btinfo_serial serial; + const char *ptr; + + grub_memset (&serial, 0, sizeof (serial)); + grub_strcpy (serial.devname, "com"); + + serial.addr = grub_ns8250_hw_get_port (0); + serial.speed = 9600; + + if (ctxt->state[NETBSD_SERIAL_ARG].arg) + { + ptr = ctxt->state[NETBSD_SERIAL_ARG].arg; + if (grub_memcmp (ptr, "com", sizeof ("com") - 1) == 0) + { + ptr += sizeof ("com") - 1; + serial.addr + = grub_ns8250_hw_get_port (grub_strtoul (ptr, &ptr, 0)); + } + else + serial.addr = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + + if (*ptr == ',') + { + ptr++; + serial.speed = grub_strtoul (ptr, &ptr, 0); + if (grub_errno) + return grub_errno; + } + } + + grub_bsd_add_meta (NETBSD_BTINFO_CONSOLE, &serial, sizeof (serial)); + } + else + { + struct grub_netbsd_btinfo_serial cons; + + grub_memset (&cons, 0, sizeof (cons)); + grub_strcpy (cons.devname, "pc"); + + grub_bsd_add_meta (NETBSD_BTINFO_CONSOLE, &cons, sizeof (cons)); + } + + grub_netbsd_add_boot_disk_and_wedge (); + + grub_loader_set (grub_netbsd_boot, grub_bsd_unload, 0); + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_freebsd_loadenv (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + char *buf = 0, *curr, *next; + int len; + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + + if (kernel_type != KERNEL_TYPE_FREEBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only FreeBSD supports environment"); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_FREEBSD_ENV); + if ((!file) || (!file->size)) + goto fail; + + len = file->size; + buf = grub_malloc (len + 1); + if (!buf) + goto fail; + + if (grub_file_read (file, buf, len) != len) + goto fail; + + buf[len] = 0; + + next = buf; + while (next) + { + char *p; + + curr = next; + next = grub_strchr (curr, '\n'); + if (next) + { + + p = next - 1; + while (p > curr) + { + if ((*p != '\r') && (*p != ' ') && (*p != '\t')) + break; + p--; + } + + if ((p > curr) && (*p == '"')) + p--; + + *(p + 1) = 0; + next++; + } + + if (*curr == '#') + continue; + + p = grub_strchr (curr, '='); + if (!p) + continue; + + *(p++) = 0; + + if (*curr) + { + char *name; + + if (*p == '"') + p++; + + name = grub_xasprintf ("kFreeBSD.%s", curr); + if (!name) + goto fail; + if (grub_env_set (name, p)) + { + grub_free (name); + goto fail; + } + grub_free (name); + } + } + +fail: + grub_free (buf); + + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_err_t +grub_cmd_freebsd_module (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + int modargc; + char **modargv; + const char *type; + grub_err_t err; + void *src; + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + + if (kernel_type != KERNEL_TYPE_FREEBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no FreeBSD loaded"); + + if (!is_elf_kernel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only ELF kernel supports module"); + + /* List the current modules if no parameter. */ + if (!argc) + { + grub_freebsd_list_modules (); + return 0; + } + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_FREEBSD_MODULE); + if ((!file) || (!file->size)) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, kern_end, + file->size); + if (err) + goto fail; + src = get_virtual_current_address (ch); + } + + + grub_file_read (file, src, file->size); + if (grub_errno) + goto fail; + + modargc = argc - 1; + modargv = argv + 1; + + if (modargc && (! grub_memcmp (modargv[0], "type=", 5))) + { + type = &modargv[0][5]; + modargc--; + modargv++; + } + else + type = FREEBSD_MODTYPE_RAW; + + err = grub_freebsd_add_meta_module (argv[0], type, modargc, modargv, + kern_end, file->size); + if (err) + goto fail; + + kern_end = ALIGN_PAGE (kern_end + file->size); + +fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_err_t +grub_netbsd_module_load (char *filename, grub_uint32_t type) +{ + grub_file_t file = 0; + void *src; + grub_err_t err; + + file = grub_file_open (filename, GRUB_FILE_TYPE_NETBSD_MODULE); + if ((!file) || (!file->size)) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, kern_end, + file->size); + if (err) + goto fail; + + src = get_virtual_current_address (ch); + } + + grub_file_read (file, src, file->size); + if (grub_errno) + goto fail; + + err = grub_netbsd_add_meta_module (filename, type, kern_end, file->size); + + if (err) + goto fail; + + kern_end = ALIGN_PAGE (kern_end + file->size); + +fail: + if (file) + grub_file_close (file); + + return grub_errno; +} + +static grub_err_t +grub_cmd_netbsd_module (grub_command_t cmd, + int argc, char *argv[]) +{ + grub_uint32_t type; + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + + if (kernel_type != KERNEL_TYPE_NETBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no NetBSD loaded"); + + if (!is_elf_kernel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only ELF kernel supports module"); + + /* List the current modules if no parameter. */ + if (!argc) + { + grub_netbsd_list_modules (); + return 0; + } + + if (grub_strcmp (cmd->name, "knetbsd_module_elf") == 0) + type = GRUB_NETBSD_MODULE_ELF; + else + type = GRUB_NETBSD_MODULE_RAW; + + return grub_netbsd_module_load (argv[0], type); +} + +static grub_err_t +grub_cmd_freebsd_module_elf (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + + if (kernel_type != KERNEL_TYPE_FREEBSD) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only FreeBSD supports module"); + + if (! is_elf_kernel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "only ELF kernel supports module"); + + /* List the current modules if no parameter. */ + if (! argc) + { + grub_freebsd_list_modules (); + return 0; + } + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_FREEBSD_MODULE_ELF); + if (!file) + return grub_errno; + if (!file->size) + { + grub_file_close (file); + return grub_errno; + } + + if (is_64bit) + err = grub_freebsd_load_elfmodule_obj64 (relocator, file, + argc, argv, &kern_end); + else + err = grub_freebsd_load_elfmodule32 (relocator, file, + argc, argv, &kern_end); + grub_file_close (file); + + return err; +} + +static grub_err_t +grub_cmd_openbsd_ramdisk (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + grub_size_t size; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + if (! grub_loader_is_loaded ()) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + + if (kernel_type != KERNEL_TYPE_OPENBSD) + return grub_error (GRUB_ERR_BAD_OS, "no kOpenBSD loaded"); + + if (!openbsd_ramdisk.max_size) + return grub_error (GRUB_ERR_BAD_OS, "your kOpenBSD doesn't support ramdisk"); + + file = grub_file_open (args[0], GRUB_FILE_TYPE_OPENBSD_RAMDISK); + if (! file) + return grub_errno; + + size = grub_file_size (file); + + if (size > openbsd_ramdisk.max_size) + { + grub_file_close (file); + return grub_error (GRUB_ERR_BAD_OS, "your kOpenBSD supports ramdisk only" + " up to %" PRIuGRUB_SIZE " bytes, however you supplied" + " a %" PRIuGRUB_SIZE " bytes one", + openbsd_ramdisk.max_size, size); + } + + if (grub_file_read (file, openbsd_ramdisk.target, size) + != (grub_ssize_t) (size)) + { + grub_file_close (file); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]); + return grub_errno; + } + grub_memset (openbsd_ramdisk.target + size, 0, + openbsd_ramdisk.max_size - size); + *openbsd_ramdisk.size = ALIGN_UP (size, 512); + + return GRUB_ERR_NONE; +} + +static grub_extcmd_t cmd_freebsd, cmd_openbsd, cmd_netbsd; +static grub_command_t cmd_freebsd_loadenv, cmd_freebsd_module; +static grub_command_t cmd_netbsd_module, cmd_freebsd_module_elf; +static grub_command_t cmd_netbsd_module_elf, cmd_openbsd_ramdisk; + +GRUB_MOD_INIT (bsd) +{ + /* Net and OpenBSD kernels are often compressed. */ + grub_dl_load ("gzio"); + + cmd_freebsd = grub_register_extcmd ("kfreebsd", grub_cmd_freebsd, 0, + N_("FILE"), N_("Load kernel of FreeBSD."), + freebsd_opts); + cmd_openbsd = grub_register_extcmd ("kopenbsd", grub_cmd_openbsd, 0, + N_("FILE"), N_("Load kernel of OpenBSD."), + openbsd_opts); + cmd_netbsd = grub_register_extcmd ("knetbsd", grub_cmd_netbsd, 0, + N_("FILE"), N_("Load kernel of NetBSD."), + netbsd_opts); + cmd_freebsd_loadenv = + grub_register_command ("kfreebsd_loadenv", grub_cmd_freebsd_loadenv, + 0, N_("Load FreeBSD env.")); + cmd_freebsd_module = + grub_register_command ("kfreebsd_module", grub_cmd_freebsd_module, + 0, N_("Load FreeBSD kernel module.")); + cmd_netbsd_module = + grub_register_command ("knetbsd_module", grub_cmd_netbsd_module, + 0, N_("Load NetBSD kernel module.")); + cmd_netbsd_module_elf = + grub_register_command ("knetbsd_module_elf", grub_cmd_netbsd_module, + 0, N_("Load NetBSD kernel module (ELF).")); + cmd_freebsd_module_elf = + grub_register_command ("kfreebsd_module_elf", grub_cmd_freebsd_module_elf, + 0, N_("Load FreeBSD kernel module (ELF).")); + + cmd_openbsd_ramdisk = grub_register_command ("kopenbsd_ramdisk", + grub_cmd_openbsd_ramdisk, 0, + /* TRANSLATORS: ramdisk isn't identifier, + it can be translated. */ + N_("Load kOpenBSD ramdisk.")); + + my_mod = mod; +} + +GRUB_MOD_FINI (bsd) +{ + grub_unregister_extcmd (cmd_freebsd); + grub_unregister_extcmd (cmd_openbsd); + grub_unregister_extcmd (cmd_netbsd); + + grub_unregister_command (cmd_freebsd_loadenv); + grub_unregister_command (cmd_freebsd_module); + grub_unregister_command (cmd_netbsd_module); + grub_unregister_command (cmd_freebsd_module_elf); + grub_unregister_command (cmd_netbsd_module_elf); + grub_unregister_command (cmd_openbsd_ramdisk); + + grub_bsd_unload (); +} diff --git a/grub-core/loader/i386/bsd32.c b/grub-core/loader/i386/bsd32.c new file mode 100644 index 0000000..26704c4 --- /dev/null +++ b/grub-core/loader/i386/bsd32.c @@ -0,0 +1,6 @@ +#define SUFFIX(x) x ## 32 +#define GRUB_TARGET_WORDSIZE 32 +#define OBJSYM 0 +#include <grub/types.h> +typedef grub_uint32_t grub_freebsd_addr_t; +#include "bsdXX.c" diff --git a/grub-core/loader/i386/bsd64.c b/grub-core/loader/i386/bsd64.c new file mode 100644 index 0000000..f8aad1c --- /dev/null +++ b/grub-core/loader/i386/bsd64.c @@ -0,0 +1,6 @@ +#define SUFFIX(x) x ## 64 +#define GRUB_TARGET_WORDSIZE 64 +#define OBJSYM 1 +#include <grub/types.h> +typedef grub_uint64_t grub_freebsd_addr_t; +#include "bsdXX.c" diff --git a/grub-core/loader/i386/bsdXX.c b/grub-core/loader/i386/bsdXX.c new file mode 100644 index 0000000..a8d8bf7 --- /dev/null +++ b/grub-core/loader/i386/bsdXX.c @@ -0,0 +1,679 @@ +#include <grub/loader.h> +#include <grub/i386/bsd.h> +#include <grub/mm.h> +#include <grub/elf.h> +#include <grub/misc.h> +#include <grub/i386/relocator.h> +#include <grub/i18n.h> + +#define ALIGN_PAGE(a) ALIGN_UP (a, 4096) + +static inline grub_err_t +load (grub_file_t file, const char *filename, void *where, grub_off_t off, grub_size_t size) +{ + if (grub_file_seek (file, off) == (grub_off_t) -1) + return grub_errno; + if (grub_file_read (file, where, size) != (grub_ssize_t) size) + { + if (grub_errno) + return grub_errno; + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + } + return GRUB_ERR_NONE; +} + +static inline grub_err_t +read_headers (grub_file_t file, const char *filename, Elf_Ehdr *e, char **shdr) +{ + if (grub_file_seek (file, 0) == (grub_off_t) -1) + return grub_errno; + + if (grub_file_read (file, (char *) e, sizeof (*e)) != sizeof (*e)) + { + if (grub_errno) + return grub_errno; + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + } + + if (e->e_ident[EI_MAG0] != ELFMAG0 + || e->e_ident[EI_MAG1] != ELFMAG1 + || e->e_ident[EI_MAG2] != ELFMAG2 + || e->e_ident[EI_MAG3] != ELFMAG3 + || e->e_ident[EI_VERSION] != EV_CURRENT + || e->e_version != EV_CURRENT) + return grub_error (GRUB_ERR_BAD_OS, N_("invalid arch-independent ELF magic")); + + if (e->e_ident[EI_CLASS] != SUFFIX (ELFCLASS)) + return grub_error (GRUB_ERR_BAD_OS, N_("invalid arch-dependent ELF magic")); + + *shdr = grub_calloc (e->e_shnum, e->e_shentsize); + if (! *shdr) + return grub_errno; + + if (grub_file_seek (file, e->e_shoff) == (grub_off_t) -1) + return grub_errno; + + if (grub_file_read (file, *shdr, (grub_uint32_t) e->e_shnum * e->e_shentsize) + != (grub_ssize_t) ((grub_uint32_t) e->e_shnum * e->e_shentsize)) + { + if (grub_errno) + return grub_errno; + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + } + + return GRUB_ERR_NONE; +} + +/* On i386 FreeBSD uses "elf module" approarch for 32-bit variant + and "elf obj module" for 64-bit variant. However it may differ on other + platforms. So I keep both versions. */ +#if OBJSYM +grub_err_t +SUFFIX (grub_freebsd_load_elfmodule_obj) (struct grub_relocator *relocator, + grub_file_t file, int argc, + char *argv[], grub_addr_t *kern_end) +{ + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = 0; + grub_addr_t curload, module; + grub_err_t err; + grub_size_t chunk_size = 0; + void *chunk_src; + + curload = module = ALIGN_PAGE (*kern_end); + + err = read_headers (file, argv[0], &e, &shdr); + if (err) + goto out; + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (s->sh_addralign) + chunk_size = ALIGN_UP (chunk_size + *kern_end, s->sh_addralign) + - *kern_end; + + chunk_size += s->sh_size; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + module, chunk_size); + if (err) + goto out; + chunk_src = get_virtual_current_address (ch); + } + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (s->sh_addralign) + curload = ALIGN_UP (curload, s->sh_addralign); + s->sh_addr = curload; + + grub_dprintf ("bsd", "loading section to %x, size %d, align %d\n", + (unsigned) curload, (int) s->sh_size, + (int) s->sh_addralign); + + switch (s->sh_type) + { + default: + case SHT_PROGBITS: + err = load (file, argv[0], (grub_uint8_t *) chunk_src + curload - *kern_end, + s->sh_offset, s->sh_size); + if (err) + goto out; + break; + case SHT_NOBITS: + grub_memset ((grub_uint8_t *) chunk_src + curload - *kern_end, 0, + s->sh_size); + break; + } + curload += s->sh_size; + } + + *kern_end = ALIGN_PAGE (curload); + + err = grub_freebsd_add_meta_module (argv[0], FREEBSD_MODTYPE_ELF_MODULE_OBJ, + argc - 1, argv + 1, module, + curload - module); + if (! err) + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA + | FREEBSD_MODINFOMD_ELFHDR, + &e, sizeof (e)); + if (! err) + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA + | FREEBSD_MODINFOMD_SHDR, + shdr, e.e_shnum * e.e_shentsize); + +out: + grub_free (shdr); + return err; +} + +#else + +grub_err_t +SUFFIX (grub_freebsd_load_elfmodule) (struct grub_relocator *relocator, + grub_file_t file, int argc, char *argv[], + grub_addr_t *kern_end) +{ + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = 0; + grub_addr_t curload, module; + grub_err_t err; + grub_size_t chunk_size = 0; + void *chunk_src; + + curload = module = ALIGN_PAGE (*kern_end); + + err = read_headers (file, argv[0], &e, &shdr); + if (err) + goto out; + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (! (s->sh_flags & SHF_ALLOC)) + continue; + if (chunk_size < s->sh_addr + s->sh_size) + chunk_size = s->sh_addr + s->sh_size; + } + + if (chunk_size < sizeof (e)) + chunk_size = sizeof (e); + chunk_size += (grub_uint32_t) e.e_phnum * e.e_phentsize; + chunk_size += (grub_uint32_t) e.e_shnum * e.e_shentsize; + + { + grub_relocator_chunk_t ch; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + module, chunk_size); + if (err) + goto out; + + chunk_src = get_virtual_current_address (ch); + } + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + if (s->sh_size == 0) + continue; + + if (! (s->sh_flags & SHF_ALLOC)) + continue; + + grub_dprintf ("bsd", "loading section to %x, size %d, align %d\n", + (unsigned) curload, (int) s->sh_size, + (int) s->sh_addralign); + + switch (s->sh_type) + { + default: + case SHT_PROGBITS: + err = load (file, argv[0], + (grub_uint8_t *) chunk_src + module + + s->sh_addr - *kern_end, + s->sh_offset, s->sh_size); + if (err) + goto out; + break; + case SHT_NOBITS: + grub_memset ((grub_uint8_t *) chunk_src + module + + s->sh_addr - *kern_end, 0, s->sh_size); + break; + } + if (curload < module + s->sh_addr + s->sh_size) + curload = module + s->sh_addr + s->sh_size; + } + + load (file, argv[0], (grub_uint8_t *) chunk_src + module - *kern_end, 0, sizeof (e)); + if (curload < module + sizeof (e)) + curload = module + sizeof (e); + + load (file, argv[0], (grub_uint8_t *) chunk_src + curload - *kern_end, e.e_shoff, + (grub_uint32_t) e.e_shnum * e.e_shentsize); + e.e_shoff = curload - module; + curload += (grub_uint32_t) e.e_shnum * e.e_shentsize; + + load (file, argv[0], (grub_uint8_t *) chunk_src + curload - *kern_end, e.e_phoff, + (grub_uint32_t) e.e_phnum * e.e_phentsize); + e.e_phoff = curload - module; + curload += (grub_uint32_t) e.e_phnum * e.e_phentsize; + + *kern_end = curload; + + grub_freebsd_add_meta_module (argv[0], FREEBSD_MODTYPE_ELF_MODULE, + argc - 1, argv + 1, module, + curload - module); +out: + grub_free (shdr); + if (err) + return err; + return SUFFIX (grub_freebsd_load_elf_meta) (relocator, file, argv[0], kern_end); +} + +#endif + +grub_err_t +SUFFIX (grub_freebsd_load_elf_meta) (struct grub_relocator *relocator, + grub_file_t file, + const char *filename, + grub_addr_t *kern_end) +{ + grub_err_t err; + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = 0; + unsigned symoff, stroff, symsize, strsize; + grub_freebsd_addr_t symstart, symend, symentsize, dynamic; + Elf_Sym *sym; + void *sym_chunk; + grub_uint8_t *curload; + grub_freebsd_addr_t symtarget; + const char *str; + unsigned i; + grub_size_t chunk_size; + + err = read_headers (file, filename, &e, &shdr); + if (err) + goto out; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_ELFHDR, &e, + sizeof (e)); + if (err) + goto out; + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + if (s->sh_type == SHT_SYMTAB) + break; + if (s >= (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize)) + { + err = grub_error (GRUB_ERR_BAD_OS, N_("no symbol table")); + goto out; + } + symoff = s->sh_offset; + symsize = s->sh_size; + symentsize = s->sh_entsize; + s = (Elf_Shdr *) (shdr + e.e_shentsize * s->sh_link); + stroff = s->sh_offset; + strsize = s->sh_size; + + chunk_size = ALIGN_UP (symsize + strsize, sizeof (grub_freebsd_addr_t)) + + 2 * sizeof (grub_freebsd_addr_t); + + symtarget = ALIGN_UP (*kern_end, sizeof (grub_freebsd_addr_t)); + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + symtarget, chunk_size); + if (err) + goto out; + sym_chunk = get_virtual_current_address (ch); + } + + symstart = symtarget; + symend = symstart + chunk_size; + + curload = sym_chunk; + *((grub_freebsd_addr_t *) curload) = symsize; + curload += sizeof (grub_freebsd_addr_t); + + if (grub_file_seek (file, symoff) == (grub_off_t) -1) + { + err = grub_errno; + goto out; + } + sym = (Elf_Sym *) curload; + if (grub_file_read (file, curload, symsize) != (grub_ssize_t) symsize) + { + if (! grub_errno) + err = grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + else + err = grub_errno; + goto out; + } + curload += symsize; + + *((grub_freebsd_addr_t *) curload) = strsize; + curload += sizeof (grub_freebsd_addr_t); + if (grub_file_seek (file, stroff) == (grub_off_t) -1) + { + err = grub_errno; + goto out; + } + str = (char *) curload; + if (grub_file_read (file, curload, strsize) != (grub_ssize_t) strsize) + { + if (! grub_errno) + err = grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + else + err = grub_errno; + goto out; + } + + for (i = 0; + i * symentsize < symsize; + i++, sym = (Elf_Sym *) ((char *) sym + symentsize)) + { + const char *name = str + sym->st_name; + if (grub_strcmp (name, "_DYNAMIC") == 0) + break; + } + + if (i * symentsize < symsize) + { + dynamic = sym->st_value; + grub_dprintf ("bsd", "dynamic = %llx\n", (unsigned long long) dynamic); + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_DYNAMIC, &dynamic, + sizeof (dynamic)); + if (err) + goto out; + } + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_SSYM, &symstart, + sizeof (symstart)); + if (err) + goto out; + + err = grub_bsd_add_meta (FREEBSD_MODINFO_METADATA | + FREEBSD_MODINFOMD_ESYM, &symend, + sizeof (symend)); +out: + grub_free (shdr); + if (err) + return err; + + *kern_end = ALIGN_PAGE (symend); + + return GRUB_ERR_NONE; +} + +grub_err_t +SUFFIX (grub_netbsd_load_elf_meta) (struct grub_relocator *relocator, + grub_file_t file, const char *filename, + grub_addr_t *kern_end) +{ + grub_err_t err; + Elf_Ehdr e; + Elf_Shdr *s, *symsh, *strsh; + char *shdr = NULL; + unsigned symsize, strsize; + void *sym_chunk; + grub_uint8_t *curload; + grub_size_t chunk_size; + Elf_Ehdr *e2; + struct grub_netbsd_btinfo_symtab symtab; + grub_addr_t symtarget; + + err = read_headers (file, filename, &e, &shdr); + if (err) + { + grub_free (shdr); + return grub_errno; + } + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + if (s->sh_type == SHT_SYMTAB) + break; + if (s >= (Elf_Shdr *) ((char *) shdr + + e.e_shnum * e.e_shentsize)) + { + grub_free (shdr); + return GRUB_ERR_NONE; + } + symsize = s->sh_size; + symsh = s; + s = (Elf_Shdr *) (shdr + e.e_shentsize * s->sh_link); + strsize = s->sh_size; + strsh = s; + + chunk_size = ALIGN_UP (symsize, sizeof (grub_freebsd_addr_t)) + + ALIGN_UP (strsize, sizeof (grub_freebsd_addr_t)) + + sizeof (e) + (grub_uint32_t) e.e_shnum * e.e_shentsize; + + symtarget = ALIGN_UP (*kern_end, sizeof (grub_freebsd_addr_t)); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + symtarget, chunk_size); + if (err) + goto out; + sym_chunk = get_virtual_current_address (ch); + } + + symtab.nsyms = 1; + symtab.ssyms = symtarget; + symtab.esyms = symtarget + chunk_size; + + curload = sym_chunk; + + e2 = (Elf_Ehdr *) curload; + grub_memcpy (curload, &e, sizeof (e)); + e2->e_phoff = 0; + e2->e_phnum = 0; + e2->e_phentsize = 0; + e2->e_shstrndx = 0; + e2->e_shoff = sizeof (e); + + curload += sizeof (e); + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + { + Elf_Shdr *s2; + s2 = (Elf_Shdr *) curload; + grub_memcpy (curload, s, e.e_shentsize); + if (s == symsh) + s2->sh_offset = sizeof (e) + (grub_uint32_t) e.e_shnum * e.e_shentsize; + else if (s == strsh) + s2->sh_offset = ALIGN_UP (symsize, sizeof (grub_freebsd_addr_t)) + + sizeof (e) + (grub_uint32_t) e.e_shnum * e.e_shentsize; + else + s2->sh_offset = 0; + s2->sh_addr = s2->sh_offset; + curload += e.e_shentsize; + } + + if (grub_file_seek (file, symsh->sh_offset) == (grub_off_t) -1) + { + err = grub_errno; + goto out; + } + if (grub_file_read (file, curload, symsize) != (grub_ssize_t) symsize) + { + if (! grub_errno) + err = grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + else + err = grub_errno; + goto out; + } + curload += ALIGN_UP (symsize, sizeof (grub_freebsd_addr_t)); + + if (grub_file_seek (file, strsh->sh_offset) == (grub_off_t) -1) + { + err = grub_errno; + goto out; + } + if (grub_file_read (file, curload, strsize) != (grub_ssize_t) strsize) + { + if (! grub_errno) + err = grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + else + err = grub_errno; + goto out; + } + + err = grub_bsd_add_meta (NETBSD_BTINFO_SYMTAB, + &symtab, + sizeof (symtab)); +out: + grub_free (shdr); + if (err) + return err; + + *kern_end = ALIGN_PAGE (symtarget + chunk_size); + + return GRUB_ERR_NONE; +} + +grub_err_t +SUFFIX(grub_openbsd_find_ramdisk) (grub_file_t file, + const char *filename, + grub_addr_t kern_start, + void *kern_chunk_src, + struct grub_openbsd_ramdisk_descriptor *desc) +{ + unsigned symoff, stroff, symsize, strsize, symentsize; + + { + grub_err_t err; + Elf_Ehdr e; + Elf_Shdr *s; + char *shdr = NULL; + + err = read_headers (file, filename, &e, &shdr); + if (err) + { + grub_free (shdr); + return err; + } + + for (s = (Elf_Shdr *) shdr; s < (Elf_Shdr *) (shdr + + e.e_shnum * e.e_shentsize); + s = (Elf_Shdr *) ((char *) s + e.e_shentsize)) + if (s->sh_type == SHT_SYMTAB) + break; + if (s >= (Elf_Shdr *) ((char *) shdr + e.e_shnum * e.e_shentsize)) + { + grub_free (shdr); + return GRUB_ERR_NONE; + } + + symsize = s->sh_size; + symentsize = s->sh_entsize; + symoff = s->sh_offset; + + s = (Elf_Shdr *) (shdr + e.e_shentsize * s->sh_link); + stroff = s->sh_offset; + strsize = s->sh_size; + grub_free (shdr); + } + { + Elf_Sym *syms, *sym, *imagesym = NULL, *sizesym = NULL; + unsigned i; + char *strs; + + syms = grub_malloc (symsize); + if (!syms) + return grub_errno; + + if (grub_file_seek (file, symoff) == (grub_off_t) -1) + { + grub_free (syms); + return grub_errno; + } + if (grub_file_read (file, syms, symsize) != (grub_ssize_t) symsize) + { + grub_free (syms); + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + return grub_errno; + } + + strs = grub_malloc (strsize); + if (!strs) + { + grub_free (syms); + return grub_errno; + } + + if (grub_file_seek (file, stroff) == (grub_off_t) -1) + { + grub_free (syms); + grub_free (strs); + return grub_errno; + } + if (grub_file_read (file, strs, strsize) != (grub_ssize_t) strsize) + { + grub_free (syms); + grub_free (strs); + if (! grub_errno) + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + return grub_errno; + } + + for (i = 0, sym = syms; i * symentsize < symsize; + i++, sym = (Elf_Sym *) ((char *) sym + symentsize)) + { + if (ELF_ST_TYPE (sym->st_info) != STT_OBJECT) + continue; + if (!sym->st_name) + continue; + if (grub_strcmp (strs + sym->st_name, "rd_root_image") == 0) + imagesym = sym; + if (grub_strcmp (strs + sym->st_name, "rd_root_size") == 0) + sizesym = sym; + if (imagesym && sizesym) + break; + } + if (!imagesym || !sizesym) + { + grub_free (syms); + grub_free (strs); + return GRUB_ERR_NONE; + } + if (sizeof (*desc->size) != sizesym->st_size) + { + grub_free (syms); + grub_free (strs); + return grub_error (GRUB_ERR_BAD_OS, "unexpected size of rd_root_size"); + } + desc->max_size = imagesym->st_size; + desc->target = (imagesym->st_value & 0xFFFFFF) - kern_start + + (grub_uint8_t *) kern_chunk_src; + desc->size = (grub_uint32_t *) ((sizesym->st_value & 0xFFFFFF) - kern_start + + (grub_uint8_t *) kern_chunk_src); + grub_free (syms); + grub_free (strs); + + return GRUB_ERR_NONE; + } +} diff --git a/grub-core/loader/i386/bsd_pagetable.c b/grub-core/loader/i386/bsd_pagetable.c new file mode 100644 index 0000000..9ec5abf --- /dev/null +++ b/grub-core/loader/i386/bsd_pagetable.c @@ -0,0 +1,92 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * 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/>. + */ + +/* Based on the code from FreeBSD originally distributed under the + following terms: */ + +/*- + * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +static void +fill_bsd64_pagetable (grub_uint8_t *src, grub_addr_t target) +{ + grub_uint64_t *pt2, *pt3, *pt4; + grub_addr_t pt2t, pt3t; + int i; + +#define PG_V 0x001 +#define PG_RW 0x002 +#define PG_U 0x004 +#define PG_PS 0x080 + + pt4 = (grub_uint64_t *) src; + pt3 = (grub_uint64_t *) (src + 4096); + pt2 = (grub_uint64_t *) (src + 8192); + + pt3t = target + 4096; + pt2t = target + 8192; + + grub_memset (src, 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) pt3t; + 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) pt2t; + 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; + } +} diff --git a/grub-core/loader/i386/coreboot/chainloader.c b/grub-core/loader/i386/coreboot/chainloader.c new file mode 100644 index 0000000..0a19ebb --- /dev/null +++ b/grub-core/loader/i386/coreboot/chainloader.c @@ -0,0 +1,517 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2011 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/>. + */ + +#include <grub/loader.h> +#include <grub/memory.h> +#include <grub/i386/memory.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/elfload.h> +#include <grub/video.h> +#include <grub/relocator.h> +#include <grub/i386/relocator.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/cbfs_core.h> +#include <grub/lib/LzmaDec.h> +#include <grub/efi/pe32.h> +#include <grub/i386/cpuid.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_addr_t entry; +static struct grub_relocator *relocator = NULL; + +static grub_err_t +grub_chain_boot (void) +{ + struct grub_relocator32_state state; + + grub_video_set_mode ("text", 0, 0); + + state.eip = entry; + return grub_relocator32_boot (relocator, state, 0); +} + +static grub_err_t +grub_chain_unload (void) +{ + grub_relocator_unload (relocator); + relocator = NULL; + + return GRUB_ERR_NONE; +} + +static grub_err_t +load_elf (grub_file_t file, const char *filename) +{ + grub_elf_t elf; + Elf32_Phdr *phdr; + grub_err_t err; + + elf = grub_elf_file (file, filename); + if (!elf) + return grub_errno; + + if (!grub_elf_is_elf32 (elf)) + return grub_error (GRUB_ERR_BAD_OS, "only ELF32 can be coreboot payload"); + + entry = elf->ehdr.ehdr32.e_entry; + + FOR_ELF32_PHDRS(elf, phdr) + { + grub_uint8_t *load_addr; + grub_relocator_chunk_t ch; + + if (phdr->p_type != PT_LOAD) + continue; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + phdr->p_paddr, phdr->p_memsz); + if (err) + { + elf->file = 0; + grub_elf_close (elf); + return err; + } + + load_addr = get_virtual_current_address (ch); + + if (grub_file_seek (elf->file, phdr->p_offset) == (grub_off_t) -1) + { + elf->file = 0; + grub_elf_close (elf); + return grub_errno; + } + + if (phdr->p_filesz) + { + grub_ssize_t read; + read = grub_file_read (elf->file, load_addr, phdr->p_filesz); + if (read != (grub_ssize_t) phdr->p_filesz) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, + N_("premature end of file %s"), + filename); + elf->file = 0; + grub_elf_close (elf); + return grub_errno; + } + } + + if (phdr->p_filesz < phdr->p_memsz) + grub_memset ((load_addr + phdr->p_filesz), + 0, phdr->p_memsz - phdr->p_filesz); + } + + elf->file = 0; + grub_elf_close (elf); + return GRUB_ERR_NONE; +} + +static void *SzAlloc(void *p __attribute__ ((unused)), size_t size) { return grub_malloc (size); } +static void SzFree(void *p __attribute__ ((unused)), void *address) { grub_free (address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + + +static grub_err_t +load_segment (grub_file_t file, const char *filename, + void *load_addr, grub_uint32_t comp, + grub_size_t *size, grub_size_t max_size) +{ + switch (comp) + { + case grub_cpu_to_be32_compile_time (CBFS_COMPRESS_NONE): + if (grub_file_read (file, load_addr, *size) + != (grub_ssize_t) *size) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, + N_("premature end of file %s"), + filename); + return grub_errno; + } + return GRUB_ERR_NONE; + case grub_cpu_to_be32_compile_time (CBFS_COMPRESS_LZMA): + { + grub_uint8_t *buf; + grub_size_t outsize, insize; + SRes res; + SizeT src_len, dst_len; + ELzmaStatus status; + if (*size < 13) + return grub_error (GRUB_ERR_BAD_OS, "invalid compressed chunk"); + buf = grub_malloc (*size); + if (!buf) + return grub_errno; + if (grub_file_read (file, buf, *size) + != (grub_ssize_t) *size) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, + N_("premature end of file %s"), + filename); + grub_free (buf); + return grub_errno; + } + outsize = grub_get_unaligned64 (buf + 5); + if (outsize > max_size) + { + grub_free (buf); + return grub_error (GRUB_ERR_BAD_OS, "invalid compressed chunk"); + } + insize = *size - 13; + + src_len = insize; + dst_len = outsize; + res = LzmaDecode (load_addr, &dst_len, buf + 13, &src_len, + buf, 5, LZMA_FINISH_END, &status, &g_Alloc); + /* ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc)*/ + grub_free (buf); + grub_dprintf ("chain", "%x, %x, %x, %x\n", + insize, src_len, outsize, dst_len); + if (res != SZ_OK + || src_len != insize || dst_len != outsize) + return grub_error (GRUB_ERR_BAD_OS, "decompression failure %d", res); + *size = outsize; + } + return GRUB_ERR_NONE; + default: + return grub_error (GRUB_ERR_BAD_OS, "unsupported compression %d", + grub_be_to_cpu32 (comp)); + } +} + +static grub_err_t +load_tianocore (grub_file_t file) +{ + grub_uint16_t header_length; + grub_uint32_t section_head; + grub_uint8_t mz[2], pe[4]; + struct grub_pe32_coff_header coff_head; + struct file_header + { + grub_uint8_t unused[18]; + grub_uint8_t type; + grub_uint8_t unused2; + grub_uint8_t size[3]; + grub_uint8_t unused3; + } file_head; + grub_relocator_chunk_t ch; + + if (grub_file_seek (file, 48) == (grub_off_t) -1 + || grub_file_read (file, &header_length, sizeof (header_length)) + != sizeof (header_length) + || grub_file_seek (file, header_length) == (grub_off_t) -1) + goto fail; + + while (1) + { + grub_off_t off; + if (grub_file_read (file, &file_head, sizeof (file_head)) + != sizeof (file_head)) + goto fail; + if (file_head.type != 0xf0) + break; + off = grub_get_unaligned32 (file_head.size) & 0xffffff; + if (off < sizeof (file_head)) + goto fail; + if (grub_file_seek (file, grub_file_tell (file) + off + - sizeof (file_head)) == (grub_off_t) -1) + goto fail; + } + + if (file_head.type != 0x03) + goto fail; + + while (1) + { + if (grub_file_read (file, §ion_head, sizeof (section_head)) + != sizeof (section_head)) + goto fail; + if ((section_head >> 24) != 0x19) + break; + + if ((section_head & 0xffffff) < sizeof (section_head)) + goto fail; + + if (grub_file_seek (file, grub_file_tell (file) + + (section_head & 0xffffff) + - sizeof (section_head)) == (grub_off_t) -1) + goto fail; + } + + if ((section_head >> 24) != 0x10) + goto fail; + + grub_off_t exe_start = grub_file_tell (file); + + if (grub_file_read (file, &mz, sizeof (mz)) != sizeof (mz)) + goto fail; + if (mz[0] != 'M' || mz[1] != 'Z') + goto fail; + + if (grub_file_seek (file, grub_file_tell (file) + 0x3a) == (grub_off_t) -1) + goto fail; + + if (grub_file_read (file, §ion_head, sizeof (section_head)) + != sizeof (section_head)) + goto fail; + if (section_head < 0x40) + goto fail; + + if (grub_file_seek (file, grub_file_tell (file) + + section_head - 0x40) == (grub_off_t) -1) + goto fail; + + if (grub_file_read (file, &pe, sizeof (pe)) + != sizeof (pe)) + goto fail; + + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != '\0' || pe[3] != '\0') + goto fail; + + if (grub_file_read (file, &coff_head, sizeof (coff_head)) + != sizeof (coff_head)) + goto fail; + + grub_uint32_t loadaddr; + + switch (coff_head.machine) + { + case GRUB_PE32_MACHINE_I386: + { + struct grub_pe32_optional_header oh; + if (grub_file_read (file, &oh, sizeof (oh)) + != sizeof (oh)) + goto fail; + if (oh.magic != GRUB_PE32_PE32_MAGIC) + goto fail; + loadaddr = oh.image_base - exe_start; + entry = oh.image_base + oh.entry_addr; + break; + } + case GRUB_PE32_MACHINE_X86_64: + { + struct grub_pe64_optional_header oh; + if (! grub_cpuid_has_longmode) + { + grub_error (GRUB_ERR_BAD_OS, "your CPU does not implement AMD64 architecture"); + goto fail; + } + + if (grub_file_read (file, &oh, sizeof (oh)) + != sizeof (oh)) + goto fail; + if (oh.magic != GRUB_PE32_PE64_MAGIC) + goto fail; + loadaddr = oh.image_base - exe_start; + entry = oh.image_base + oh.entry_addr; + break; + } + default: + goto fail; + } + if (grub_file_seek (file, 0) == (grub_off_t) -1) + goto fail; + + grub_size_t fz = grub_file_size (file); + + if (grub_relocator_alloc_chunk_addr (relocator, &ch, + loadaddr, fz)) + goto fail; + + if (grub_file_read (file, get_virtual_current_address (ch), fz) + != (grub_ssize_t) fz) + goto fail; + + return GRUB_ERR_NONE; + + fail: + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, "fv volume is invalid"); + return grub_errno; +} + +static grub_err_t +load_chewed (grub_file_t file, const char *filename) +{ + grub_size_t i; + for (i = 0;; i++) + { + struct cbfs_payload_segment segment; + grub_err_t err; + + if (grub_file_seek (file, sizeof (segment) * i) == (grub_off_t) -1 + || grub_file_read (file, &segment, sizeof (segment)) + != sizeof (segment)) + { + if (!grub_errno) + return grub_error (GRUB_ERR_BAD_OS, + "payload is too short"); + return grub_errno; + } + + switch (segment.type) + { + case PAYLOAD_SEGMENT_PARAMS: + break; + + case PAYLOAD_SEGMENT_ENTRY: + entry = grub_be_to_cpu64 (segment.load_addr); + return GRUB_ERR_NONE; + + case PAYLOAD_SEGMENT_BSS: + segment.len = 0; + segment.offset = 0; + segment.len = 0; + /* Fallthrough. */ + case PAYLOAD_SEGMENT_CODE: + case PAYLOAD_SEGMENT_DATA: + { + grub_uint32_t target = grub_be_to_cpu64 (segment.load_addr); + grub_uint32_t memsize = grub_be_to_cpu32 (segment.mem_len); + grub_uint32_t filesize = grub_be_to_cpu32 (segment.len); + grub_uint8_t *load_addr; + grub_relocator_chunk_t ch; + + if (memsize < filesize) + memsize = filesize; + + grub_dprintf ("chain", "%x+%x\n", target, memsize); + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + target, memsize); + if (err) + return err; + + load_addr = get_virtual_current_address (ch); + + if (filesize) + { + if (grub_file_seek (file, grub_be_to_cpu32 (segment.offset)) + == (grub_off_t) -1) + return grub_errno; + + err = load_segment (file, filename, load_addr, + segment.compression, &filesize, memsize); + if (err) + return err; + } + + if (filesize < memsize) + grub_memset ((load_addr + filesize), + 0, memsize - filesize); + } + } + } +} + +static grub_err_t +grub_cmd_chain (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_err_t err; + grub_file_t file; + grub_uint32_t head; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_loader_unset (); + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_COREBOOT_CHAINLOADER); + if (!file) + return grub_errno; + + relocator = grub_relocator_new (); + if (!relocator) + { + grub_file_close (file); + return grub_errno; + } + + if (grub_file_read (file, &head, sizeof (head)) != sizeof (head) + || grub_file_seek (file, 0) == (grub_off_t) -1) + { + grub_file_close (file); + grub_relocator_unload (relocator); + relocator = 0; + if (!grub_errno) + return grub_error (GRUB_ERR_BAD_OS, + "payload is too short"); + return grub_errno; + } + + switch (head) + { + case ELFMAG0 | (ELFMAG1 << 8) | (ELFMAG2 << 16) | (ELFMAG3 << 24): + err = load_elf (file, argv[0]); + break; + case PAYLOAD_SEGMENT_CODE: + case PAYLOAD_SEGMENT_DATA: + case PAYLOAD_SEGMENT_PARAMS: + case PAYLOAD_SEGMENT_BSS: + case PAYLOAD_SEGMENT_ENTRY: + err = load_chewed (file, argv[0]); + break; + + default: + if (grub_file_seek (file, 40) == (grub_off_t) -1 + || grub_file_read (file, &head, sizeof (head)) != sizeof (head) + || grub_file_seek (file, 0) == (grub_off_t) -1 + || head != 0x4856465f) + err = grub_error (GRUB_ERR_BAD_OS, "unrecognised payload type"); + else + err = load_tianocore (file); + break; + } + grub_file_close (file); + if (err) + { + grub_relocator_unload (relocator); + relocator = 0; + return err; + } + + grub_loader_set (grub_chain_boot, grub_chain_unload, 0); + return GRUB_ERR_NONE; +} + +static grub_command_t cmd_chain; + +GRUB_MOD_INIT (chain) +{ + cmd_chain = grub_register_command ("chainloader", grub_cmd_chain, + N_("FILE"), + /* TRANSLATORS: "payload" is a term used + by coreboot and must be translated in + sync with coreboot. If unsure, + let it untranslated. */ + N_("Load another coreboot payload")); +} + +GRUB_MOD_FINI (chain) +{ + grub_unregister_command (cmd_chain); + grub_chain_unload (); +} diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c new file mode 100644 index 0000000..9f74a96 --- /dev/null +++ b/grub-core/loader/i386/linux.c @@ -0,0 +1,1141 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/memory.h> +#include <grub/normal.h> +#include <grub/file.h> +#include <grub/disk.h> +#include <grub/err.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/term.h> +#include <grub/cpu/linux.h> +#include <grub/video.h> +#include <grub/video_fb.h> +#include <grub/command.h> +#include <grub/i386/relocator.h> +#include <grub/i18n.h> +#include <grub/lib/cmdline.h> +#include <grub/linux.h> +#include <grub/machine/kernel.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/i386/pc/vesa_modes_table.h> +#endif + +#ifdef GRUB_MACHINE_EFI +#include <grub/efi/efi.h> +#include <grub/efi/sb.h> +#define HAS_VGA_TEXT 0 +#define DEFAULT_VIDEO_MODE "auto" +#define ACCEPTS_PURE_TEXT 0 +#elif defined (GRUB_MACHINE_IEEE1275) +#include <grub/ieee1275/ieee1275.h> +#define HAS_VGA_TEXT 0 +#define DEFAULT_VIDEO_MODE "text" +#define ACCEPTS_PURE_TEXT 1 +#else +#include <grub/i386/pc/vbe.h> +#include <grub/i386/pc/console.h> +#define HAS_VGA_TEXT 1 +#define DEFAULT_VIDEO_MODE "text" +#define ACCEPTS_PURE_TEXT 1 +#endif + +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 void *initrd_mem; +static grub_addr_t initrd_mem_target; +static grub_size_t prot_init_space; +static struct grub_relocator *relocator = NULL; +static void *efi_mmap_buf; +static grub_size_t maximal_cmdline_size; +static struct linux_kernel_params linux_params; +static char *linux_cmdline; +#ifdef GRUB_MACHINE_EFI +static grub_efi_uintn_t efi_mmap_size; +#else +static const grub_size_t efi_mmap_size = 0; +#endif + +/* FIXME */ +#if 0 +struct idt_descriptor +{ + grub_uint16_t limit; + void *base; +} GRUB_PACKED; + +static struct idt_descriptor idt_desc = + { + 0, + 0 + }; +#endif + +static inline grub_size_t +page_align (grub_size_t size) +{ + return (size + (1 << 12) - 1) & (~((1 << 12) - 1)); +} + +/* Helper for find_mmap_size. */ +static int +count_hook (grub_uint64_t addr __attribute__ ((unused)), + grub_uint64_t size __attribute__ ((unused)), + grub_memory_type_t type __attribute__ ((unused)), void *data) +{ + grub_size_t *count = data; + + (*count)++; + return 0; +} + +/* Find the optimal number of pages for the memory map. */ +static grub_size_t +find_mmap_size (void) +{ + grub_size_t count = 0, mmap_size; + + grub_mmap_iterate (count_hook, &count); + + mmap_size = count * sizeof (struct grub_e820_mmap); + + /* Increase the size a bit for safety, because GRUB allocates more on + later. */ + mmap_size += (1 << 12); + + return page_align (mmap_size); +} + +static void +free_pages (void) +{ + grub_relocator_unload (relocator); + relocator = NULL; + prot_mode_mem = initrd_mem = 0; + prot_mode_target = initrd_mem_target = 0; +} + +/* Allocate pages for the real mode code and the protected mode code + for linux as well as a memory map buffer. */ +static grub_err_t +allocate_pages (grub_size_t prot_size, grub_size_t *align, + grub_size_t min_align, int relocatable, + grub_uint64_t preferred_address) +{ + grub_err_t err; + + if (prot_size == 0) + prot_size = 1; + + prot_size = page_align (prot_size); + + /* Initialize the memory pointers with NULL for convenience. */ + free_pages (); + + relocator = grub_relocator_new (); + if (!relocator) + { + err = grub_errno; + goto fail; + } + + /* FIXME: Should request low memory from the heap when this feature is + implemented. */ + + { + grub_relocator_chunk_t ch; + if (relocatable) + { + err = grub_relocator_alloc_chunk_align (relocator, &ch, + preferred_address, + preferred_address, + prot_size, 1, + GRUB_RELOCATOR_PREFERENCE_LOW, + 1); + for (; err && *align + 1 > min_align; (*align)--) + { + grub_errno = GRUB_ERR_NONE; + err = grub_relocator_alloc_chunk_align (relocator, &ch, 0x1000000, + UP_TO_TOP32 (prot_size), + prot_size, 1 << *align, + GRUB_RELOCATOR_PREFERENCE_LOW, + 1); + } + if (err) + goto fail; + } + else + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + preferred_address, + prot_size); + if (err) + goto fail; + prot_mode_mem = get_virtual_current_address (ch); + prot_mode_target = get_physical_target_address (ch); + } + + grub_dprintf ("linux", "prot_mode_mem = %p, prot_mode_target = %lx, prot_size = %x\n", + prot_mode_mem, (unsigned long) prot_mode_target, + (unsigned) prot_size); + return GRUB_ERR_NONE; + + fail: + free_pages (); + return err; +} + +static grub_err_t +grub_e820_add_region (struct grub_e820_mmap *e820_map, int *e820_num, + grub_uint64_t start, grub_uint64_t size, + grub_uint32_t type) +{ + int n = *e820_num; + + if ((n > 0) && (e820_map[n - 1].addr + e820_map[n - 1].size == start) && + (e820_map[n - 1].type == type)) + e820_map[n - 1].size += size; + else + { + e820_map[n].addr = start; + e820_map[n].size = size; + e820_map[n].type = type; + (*e820_num)++; + } + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linux_setup_video (struct linux_kernel_params *params) +{ + struct grub_video_mode_info mode_info; + void *framebuffer; + grub_err_t err; + grub_video_driver_id_t driver_id; + const char *gfxlfbvar = grub_env_get ("gfxpayloadforcelfb"); + + driver_id = grub_video_get_driver_id (); + + if (driver_id == GRUB_VIDEO_DRIVER_NONE) + return 1; + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + + if (err) + { + grub_errno = GRUB_ERR_NONE; + return 1; + } + + params->lfb_width = mode_info.width; + params->lfb_height = mode_info.height; + params->lfb_depth = mode_info.bpp; + params->lfb_line_len = mode_info.pitch; + + params->lfb_base = (grub_size_t) framebuffer; + +#if defined (GRUB_MACHINE_EFI) && defined (__x86_64__) + params->ext_lfb_base = (grub_size_t) (((grub_uint64_t)(grub_size_t) framebuffer) >> 32); + params->capabilities |= VIDEO_CAPABILITY_64BIT_BASE; +#endif + + params->lfb_size = ALIGN_UP (params->lfb_line_len * params->lfb_height, 65536); + + params->red_mask_size = mode_info.red_mask_size; + params->red_field_pos = mode_info.red_field_pos; + params->green_mask_size = mode_info.green_mask_size; + params->green_field_pos = mode_info.green_field_pos; + params->blue_mask_size = mode_info.blue_mask_size; + params->blue_field_pos = mode_info.blue_field_pos; + params->reserved_mask_size = mode_info.reserved_mask_size; + params->reserved_field_pos = mode_info.reserved_field_pos; + + if (gfxlfbvar && (gfxlfbvar[0] == '1' || gfxlfbvar[0] == 'y')) + params->have_vga = GRUB_VIDEO_LINUX_TYPE_SIMPLE; + else + { + switch (driver_id) + { + case GRUB_VIDEO_DRIVER_VBE: + params->lfb_size >>= 16; + params->have_vga = GRUB_VIDEO_LINUX_TYPE_VESA; + break; + + case GRUB_VIDEO_DRIVER_EFI_UGA: + case GRUB_VIDEO_DRIVER_EFI_GOP: + params->have_vga = GRUB_VIDEO_LINUX_TYPE_EFIFB; + break; + + /* FIXME: check if better id is available. */ + case GRUB_VIDEO_DRIVER_SM712: + case GRUB_VIDEO_DRIVER_SIS315PRO: + case GRUB_VIDEO_DRIVER_VGA: + case GRUB_VIDEO_DRIVER_CIRRUS: + case GRUB_VIDEO_DRIVER_BOCHS: + case GRUB_VIDEO_DRIVER_RADEON_FULOONG2E: + case GRUB_VIDEO_DRIVER_RADEON_YEELOONG3A: + case GRUB_VIDEO_DRIVER_IEEE1275: + case GRUB_VIDEO_DRIVER_COREBOOT: + /* Make gcc happy. */ + case GRUB_VIDEO_DRIVER_XEN: + case GRUB_VIDEO_DRIVER_SDL: + case GRUB_VIDEO_DRIVER_NONE: + case GRUB_VIDEO_ADAPTER_CAPTURE: + params->have_vga = GRUB_VIDEO_LINUX_TYPE_SIMPLE; + break; + } + } + +#ifdef GRUB_MACHINE_PCBIOS + /* VESA packed modes may come with zeroed mask sizes, which need + to be set here according to DAC Palette width. If we don't, + this results in Linux displaying a black screen. */ + if (driver_id == GRUB_VIDEO_DRIVER_VBE && mode_info.bpp <= 8) + { + struct grub_vbe_info_block controller_info; + int status; + int width = 8; + + status = grub_vbe_bios_get_controller_info (&controller_info); + + if (status == GRUB_VBE_STATUS_OK && + (controller_info.capabilities & GRUB_VBE_CAPABILITY_DACWIDTH)) + status = grub_vbe_bios_set_dac_palette_width (&width); + + if (status != GRUB_VBE_STATUS_OK) + /* 6 is default after mode reset. */ + width = 6; + + params->red_mask_size = params->green_mask_size + = params->blue_mask_size = width; + params->reserved_mask_size = 0; + } +#endif + + return GRUB_ERR_NONE; +} + +/* Context for grub_linux_boot. */ +struct grub_linux_boot_ctx +{ + grub_addr_t real_mode_target; + grub_size_t real_size; + struct linux_kernel_params *params; + int e820_num; +}; + +/* Helper for grub_linux_boot. */ +static int +grub_linux_boot_mmap_find (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type, void *data) +{ + struct grub_linux_boot_ctx *ctx = data; + + /* We must put real mode code in the traditional space. */ + if (type != GRUB_MEMORY_AVAILABLE || addr > 0x90000) + return 0; + + if (addr + size < 0x10000) + return 0; + + if (addr < 0x10000) + { + size += addr - 0x10000; + addr = 0x10000; + } + + if (addr + size > 0x90000) + size = 0x90000 - addr; + + if (ctx->real_size + efi_mmap_size > size) + return 0; + + grub_dprintf ("linux", "addr = %lx, size = %x, need_size = %x\n", + (unsigned long) addr, + (unsigned) size, + (unsigned) (ctx->real_size + efi_mmap_size)); + ctx->real_mode_target = ((addr + size) - (ctx->real_size + efi_mmap_size)); + return 1; +} + +/* GRUB types conveniently match E820 types. */ +static int +grub_linux_boot_mmap_fill (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type, void *data) +{ + struct grub_linux_boot_ctx *ctx = data; + + if (grub_e820_add_region (ctx->params->e820_map, &ctx->e820_num, + addr, size, type)) + return 1; + + return 0; +} + +static grub_err_t +grub_linux_boot (void) +{ + grub_err_t err = 0; + const char *modevar; + char *tmp; + struct grub_relocator32_state state; + void *real_mode_mem; + struct grub_linux_boot_ctx ctx = { + .real_mode_target = 0 + }; + grub_size_t mmap_size; + grub_size_t cl_offset; + +#ifdef GRUB_MACHINE_IEEE1275 + { + const char *bootpath; + grub_ssize_t len; + + bootpath = grub_env_get ("root"); + if (bootpath) + grub_ieee1275_set_property (grub_ieee1275_chosen, + "bootpath", bootpath, + grub_strlen (bootpath) + 1, + &len); + linux_params.ofw_signature = GRUB_LINUX_OFW_SIGNATURE; + linux_params.ofw_num_items = 1; + linux_params.ofw_cif_handler = (grub_uint32_t) grub_ieee1275_entry_fn; + linux_params.ofw_idt = 0; + } +#endif + + modevar = grub_env_get ("gfxpayload"); + + /* Now all graphical modes are acceptable. + May change in future if we have modes without framebuffer. */ + if (modevar && *modevar != 0) + { + tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; +#if ACCEPTS_PURE_TEXT + err = grub_video_set_mode (tmp, 0, 0); +#else + err = grub_video_set_mode (tmp, GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0); +#endif + grub_free (tmp); + } + else /* We can't go back to text mode from coreboot fb. */ +#ifdef GRUB_MACHINE_COREBOOT + if (grub_video_get_driver_id () == GRUB_VIDEO_DRIVER_COREBOOT) + err = GRUB_ERR_NONE; + else +#endif + { +#if ACCEPTS_PURE_TEXT + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, 0, 0); +#else + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, + GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0); +#endif + } + + if (err) + { + grub_print_error (); + grub_puts_ (N_("Booting in blind mode")); + grub_errno = GRUB_ERR_NONE; + } + + if (grub_linux_setup_video (&linux_params)) + { +#if defined (GRUB_MACHINE_PCBIOS) || defined (GRUB_MACHINE_COREBOOT) || defined (GRUB_MACHINE_QEMU) + linux_params.have_vga = GRUB_VIDEO_LINUX_TYPE_TEXT; + linux_params.video_mode = 0x3; +#else + linux_params.have_vga = 0; + linux_params.video_mode = 0; + linux_params.video_width = 0; + linux_params.video_height = 0; +#endif + } + + +#ifndef GRUB_MACHINE_IEEE1275 + if (linux_params.have_vga == GRUB_VIDEO_LINUX_TYPE_TEXT) +#endif + { + grub_term_output_t term; + int found = 0; + FOR_ACTIVE_TERM_OUTPUTS(term) + if (grub_strcmp (term->name, "vga_text") == 0 + || grub_strcmp (term->name, "console") == 0 + || grub_strcmp (term->name, "ofconsole") == 0) + { + struct grub_term_coordinate pos = grub_term_getxy (term); + linux_params.video_cursor_x = pos.x; + linux_params.video_cursor_y = pos.y; + linux_params.video_width = grub_term_width (term); + linux_params.video_height = grub_term_height (term); + found = 1; + break; + } + if (!found) + { + linux_params.video_cursor_x = 0; + linux_params.video_cursor_y = 0; + linux_params.video_width = 80; + linux_params.video_height = 25; + } + } + +#ifdef GRUB_KERNEL_USE_RSDP_ADDR + linux_params.acpi_rsdp_addr = grub_le_to_cpu64 (grub_rsdp_addr); +#endif + + mmap_size = find_mmap_size (); + /* Make sure that each size is aligned to a page boundary. */ + cl_offset = ALIGN_UP (mmap_size + sizeof (linux_params), 4096); + if (cl_offset < ((grub_size_t) linux_params.setup_sects << GRUB_DISK_SECTOR_BITS)) + cl_offset = ALIGN_UP ((grub_size_t) (linux_params.setup_sects + << GRUB_DISK_SECTOR_BITS), 4096); + ctx.real_size = ALIGN_UP (cl_offset + maximal_cmdline_size, 4096); + +#ifdef GRUB_MACHINE_EFI + efi_mmap_size = grub_efi_find_mmap_size (); + if (efi_mmap_size == 0) + return grub_errno; +#endif + + grub_dprintf ("linux", "real_size = %x, mmap_size = %x\n", + (unsigned) ctx.real_size, (unsigned) mmap_size); + +#ifdef GRUB_MACHINE_EFI + grub_efi_mmap_iterate (grub_linux_boot_mmap_find, &ctx, 1); + if (! ctx.real_mode_target) + grub_efi_mmap_iterate (grub_linux_boot_mmap_find, &ctx, 0); +#else + grub_mmap_iterate (grub_linux_boot_mmap_find, &ctx); +#endif + grub_dprintf ("linux", "real_mode_target = %lx, real_size = %x, efi_mmap_size = %x\n", + (unsigned long) ctx.real_mode_target, + (unsigned) ctx.real_size, + (unsigned) efi_mmap_size); + + if (! ctx.real_mode_target) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate real mode pages"); + + { + grub_relocator_chunk_t ch; + grub_size_t sz; + + if (grub_add (ctx.real_size, efi_mmap_size, &sz)) + return GRUB_ERR_OUT_OF_RANGE; + + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + ctx.real_mode_target, sz); + if (err) + return err; + real_mode_mem = get_virtual_current_address (ch); + } + efi_mmap_buf = (grub_uint8_t *) real_mode_mem + ctx.real_size; + + grub_dprintf ("linux", "real_mode_mem = %p\n", + real_mode_mem); + + ctx.params = real_mode_mem; + + *ctx.params = linux_params; + ctx.params->cmd_line_ptr = ctx.real_mode_target + cl_offset; + grub_memcpy ((char *) ctx.params + cl_offset, linux_cmdline, + maximal_cmdline_size); + + grub_dprintf ("linux", "code32_start = %x\n", + (unsigned) ctx.params->code32_start); + + ctx.e820_num = 0; + if (grub_mmap_iterate (grub_linux_boot_mmap_fill, &ctx)) + return grub_errno; + ctx.params->mmap_size = ctx.e820_num; + +#ifdef GRUB_MACHINE_EFI + { + grub_efi_uintn_t efi_desc_size; + grub_size_t efi_mmap_target; + grub_efi_uint32_t efi_desc_version; + + ctx.params->secure_boot = grub_efi_get_secureboot (); + + err = grub_efi_finish_boot_services (&efi_mmap_size, efi_mmap_buf, NULL, + &efi_desc_size, &efi_desc_version); + if (err) + return err; + + /* Note that no boot services are available from here. */ + efi_mmap_target = ctx.real_mode_target + + ((grub_uint8_t *) efi_mmap_buf - (grub_uint8_t *) real_mode_mem); + /* Pass EFI parameters. */ + if (grub_le_to_cpu16 (ctx.params->version) >= 0x0208) + { + ctx.params->v0208.efi_mem_desc_size = efi_desc_size; + ctx.params->v0208.efi_mem_desc_version = efi_desc_version; + ctx.params->v0208.efi_mmap = efi_mmap_target; + ctx.params->v0208.efi_mmap_size = efi_mmap_size; + +#ifdef __x86_64__ + ctx.params->v0208.efi_mmap_hi = (efi_mmap_target >> 32); +#endif + } + else if (grub_le_to_cpu16 (ctx.params->version) >= 0x0206) + { + ctx.params->v0206.efi_mem_desc_size = efi_desc_size; + ctx.params->v0206.efi_mem_desc_version = efi_desc_version; + ctx.params->v0206.efi_mmap = efi_mmap_target; + ctx.params->v0206.efi_mmap_size = efi_mmap_size; + } + else if (grub_le_to_cpu16 (ctx.params->version) >= 0x0204) + { + ctx.params->v0204.efi_mem_desc_size = efi_desc_size; + ctx.params->v0204.efi_mem_desc_version = efi_desc_version; + ctx.params->v0204.efi_mmap = efi_mmap_target; + ctx.params->v0204.efi_mmap_size = efi_mmap_size; + } + } +#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); +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_dl_unref (my_mod); + loaded = 0; + grub_free (linux_cmdline); + linux_cmdline = 0; + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + struct linux_i386_kernel_header lh; + grub_uint8_t setup_sects; + grub_size_t real_size, prot_size, prot_file_size; + grub_ssize_t len; + int i; + grub_size_t align, min_align; + int relocatable; + grub_uint64_t preferred_address = GRUB_LINUX_BZIMAGE_ADDR; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL); + if (! file) + goto fail; + + if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + + if (lh.boot_flag != grub_cpu_to_le16_compile_time (0xaa55)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); + goto fail; + } + + if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) + { + grub_error (GRUB_ERR_BAD_OS, "too many setup sectors"); + goto fail; + } + + /* FIXME: 2.03 is not always good enough (Linux 2.4 can be 2.03 and + still not support 32-bit boot. */ + if (lh.header != grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE) + || grub_le_to_cpu16 (lh.version) < 0x0203) + { + grub_error (GRUB_ERR_BAD_OS, "version too old for 32-bit boot" +#ifdef GRUB_MACHINE_PCBIOS + " (try with `linux16')" +#endif + ); + goto fail; + } + + if (! (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL)) + { + grub_error (GRUB_ERR_BAD_OS, "zImage doesn't support 32-bit boot" +#ifdef GRUB_MACHINE_PCBIOS + " (try with `linux16')" +#endif + ); + goto fail; + } + + if (grub_le_to_cpu16 (lh.version) >= 0x0206) + maximal_cmdline_size = grub_le_to_cpu32 (lh.cmdline_size) + 1; + else + maximal_cmdline_size = 256; + + if (maximal_cmdline_size < 128) + maximal_cmdline_size = 128; + + setup_sects = lh.setup_sects; + + /* If SETUP_SECTS is not set, set it to the default (4). */ + if (! setup_sects) + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + real_size = setup_sects << GRUB_DISK_SECTOR_BITS; + prot_file_size = grub_file_size (file) - real_size - GRUB_DISK_SECTOR_SIZE; + + if (grub_le_to_cpu16 (lh.version) >= 0x205 + && lh.kernel_alignment != 0 + && ((lh.kernel_alignment - 1) & lh.kernel_alignment) == 0) + { + for (align = 0; align < 32; align++) + if (grub_le_to_cpu32 (lh.kernel_alignment) & (1 << align)) + break; + relocatable = lh.relocatable; + } + else + { + align = 0; + relocatable = 0; + } + + if (grub_le_to_cpu16 (lh.version) >= 0x020a) + { + min_align = lh.min_alignment; + prot_size = grub_le_to_cpu32 (lh.init_size); + prot_init_space = page_align (prot_size); + if (relocatable) + preferred_address = grub_le_to_cpu64 (lh.pref_address); + } + else + { + min_align = align; + prot_size = prot_file_size; + /* Usually, the compression ratio is about 50%. */ + prot_init_space = page_align (prot_size) * 3; + } + + if (allocate_pages (prot_size, &align, + min_align, relocatable, + preferred_address)) + goto fail; + + grub_memset (&linux_params, 0, sizeof (linux_params)); + + /* + * The Linux 32-bit boot protocol defines the setup header end + * to be at 0x202 + the byte value at 0x201. + */ + len = 0x202 + *((char *) &lh.jump + 1); + + /* Verify the struct is big enough so we do not write past the end. */ + if (len > (char *) &linux_params.edd_mbr_sig_buffer - (char *) &linux_params) { + grub_error (GRUB_ERR_BAD_OS, "Linux setup header too big"); + goto fail; + } + + grub_memcpy (&linux_params.setup_sects, &lh.setup_sects, len - 0x1F1); + + /* We've already read lh so there is no need to read it second time. */ + len -= sizeof(lh); + + if ((len > 0) && + (grub_file_read (file, (char *) &linux_params + sizeof (lh), len) != len)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + + linux_params.code32_start = prot_mode_target + lh.code32_start - GRUB_LINUX_BZIMAGE_ADDR; + linux_params.kernel_alignment = (1 << align); + linux_params.ps_mouse = linux_params.padding11 = 0; + linux_params.type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE; + + /* These two are used (instead of cmd_line_ptr) by older versions of Linux, + and otherwise ignored. */ + linux_params.cl_magic = GRUB_LINUX_CL_MAGIC; + linux_params.cl_offset = 0x1000; + + linux_params.ramdisk_image = 0; + linux_params.ramdisk_size = 0; + + linux_params.heap_end_ptr = GRUB_LINUX_HEAP_END_OFFSET; + linux_params.loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP; + + /* These are not needed to be precise, because Linux uses these values + only to raise an error when the decompression code cannot find good + space. */ + linux_params.ext_mem = ((32 * 0x100000) >> 10); + linux_params.alt_mem = ((32 * 0x100000) >> 10); + + /* Ignored by Linux. */ + linux_params.video_page = 0; + + /* Only used when `video_mode == 0x7', otherwise ignored. */ + linux_params.video_ega_bx = 0; + + linux_params.font_size = 16; /* XXX */ + +#ifdef GRUB_MACHINE_EFI +#ifdef __x86_64__ + if (grub_le_to_cpu16 (linux_params.version) < 0x0208 && + ((grub_addr_t) grub_efi_system_table >> 32) != 0) + return grub_error(GRUB_ERR_BAD_OS, + "kernel does not support 64-bit addressing"); +#endif + + if (grub_le_to_cpu16 (linux_params.version) >= 0x0208) + { + linux_params.v0208.efi_signature = GRUB_LINUX_EFI_SIGNATURE; + linux_params.v0208.efi_system_table = (grub_uint32_t) (grub_addr_t) grub_efi_system_table; +#ifdef __x86_64__ + linux_params.v0208.efi_system_table_hi = (grub_uint32_t) ((grub_uint64_t) grub_efi_system_table >> 32); +#endif + } + else if (grub_le_to_cpu16 (linux_params.version) >= 0x0206) + { + linux_params.v0206.efi_signature = GRUB_LINUX_EFI_SIGNATURE; + linux_params.v0206.efi_system_table = (grub_uint32_t) (grub_addr_t) grub_efi_system_table; + } + else if (grub_le_to_cpu16 (linux_params.version) >= 0x0204) + { + linux_params.v0204.efi_signature = GRUB_LINUX_EFI_SIGNATURE_0204; + linux_params.v0204.efi_system_table = (grub_uint32_t) (grub_addr_t) grub_efi_system_table; + } +#endif + + /* The other parameters are filled when booting. */ + + grub_file_seek (file, real_size + GRUB_DISK_SECTOR_SIZE); + + grub_dprintf ("linux", "bzImage, setup=0x%x, size=0x%x\n", + (unsigned) real_size, (unsigned) prot_size); + + /* Look for memory size and video mode specified on the command line. */ + linux_mem_size = 0; + for (i = 1; i < argc; i++) +#ifdef GRUB_MACHINE_PCBIOS + if (grub_memcmp (argv[i], "vga=", 4) == 0) + { + /* Video mode selection support. */ + char *val = argv[i] + 4; + unsigned vid_mode = GRUB_LINUX_VID_MODE_NORMAL; + struct grub_vesa_mode_table_entry *linux_mode; + grub_err_t err; + char *buf; + + grub_dl_load ("vbe"); + + if (grub_strcmp (val, "normal") == 0) + vid_mode = GRUB_LINUX_VID_MODE_NORMAL; + else if (grub_strcmp (val, "ext") == 0) + vid_mode = GRUB_LINUX_VID_MODE_EXTENDED; + else if (grub_strcmp (val, "ask") == 0) + { + grub_puts_ (N_("Legacy `ask' parameter no longer supported.")); + + /* We usually would never do this in a loader, but "vga=ask" means user + requested interaction, so it can't hurt to request keyboard input. */ + grub_wait_after_message (); + + goto fail; + } + else + vid_mode = (grub_uint16_t) grub_strtoul (val, 0, 0); + + switch (vid_mode) + { + case 0: + case GRUB_LINUX_VID_MODE_NORMAL: + grub_env_set ("gfxpayload", "text"); + grub_printf_ (N_("%s is deprecated. " + "Use set gfxpayload=%s before " + "linux command instead.\n"), + argv[i], "text"); + break; + + case 1: + case GRUB_LINUX_VID_MODE_EXTENDED: + /* FIXME: support 80x50 text. */ + grub_env_set ("gfxpayload", "text"); + grub_printf_ (N_("%s is deprecated. " + "Use set gfxpayload=%s before " + "linux command instead.\n"), + argv[i], "text"); + break; + default: + /* Ignore invalid values. */ + if (vid_mode < GRUB_VESA_MODE_TABLE_START || + vid_mode > GRUB_VESA_MODE_TABLE_END) + { + grub_env_set ("gfxpayload", "text"); + /* TRANSLATORS: "x" has to be entered in, like an identifier, + so please don't use better Unicode codepoints. */ + grub_printf_ (N_("%s is deprecated. VGA mode %d isn't recognized. " + "Use set gfxpayload=WIDTHxHEIGHT[xDEPTH] " + "before linux command instead.\n"), + argv[i], vid_mode); + break; + } + + linux_mode = &grub_vesa_mode_table[vid_mode + - GRUB_VESA_MODE_TABLE_START]; + + buf = grub_xasprintf ("%ux%ux%u,%ux%u", + linux_mode->width, linux_mode->height, + linux_mode->depth, + linux_mode->width, linux_mode->height); + if (! buf) + goto fail; + + grub_printf_ (N_("%s is deprecated. " + "Use set gfxpayload=%s before " + "linux command instead.\n"), + argv[i], buf); + err = grub_env_set ("gfxpayload", buf); + grub_free (buf); + if (err) + goto fail; + } + } + else +#endif /* GRUB_MACHINE_PCBIOS */ + if (grub_memcmp (argv[i], "mem=", 4) == 0) + { + const char *val = argv[i] + 4; + + linux_mem_size = grub_strtoul (val, &val, 0); + + if (grub_errno) + { + grub_errno = GRUB_ERR_NONE; + linux_mem_size = 0; + } + else + { + int shift = 0; + + switch (grub_tolower (val[0])) + { + case 'g': + shift += 10; + /* FALLTHROUGH */ + case 'm': + shift += 10; + /* FALLTHROUGH */ + case 'k': + shift += 10; + /* FALLTHROUGH */ + default: + break; + } + + /* Check an overflow. */ + if (linux_mem_size > (~0UL >> shift)) + linux_mem_size = 0; + else + linux_mem_size <<= shift; + } + } + else if (grub_memcmp (argv[i], "quiet", sizeof ("quiet") - 1) == 0) + { + linux_params.loadflags |= GRUB_LINUX_FLAG_QUIET; + } + + /* Create kernel command line. */ + linux_cmdline = grub_zalloc (maximal_cmdline_size + 1); + if (!linux_cmdline) + goto fail; + grub_memcpy (linux_cmdline, LINUX_IMAGE, sizeof (LINUX_IMAGE)); + { + grub_err_t err; + err = grub_create_loader_cmdline (argc, argv, + linux_cmdline + + sizeof (LINUX_IMAGE) - 1, + maximal_cmdline_size + - (sizeof (LINUX_IMAGE) - 1), + GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + goto fail; + } + + len = prot_file_size; + if (grub_file_read (file, prot_mode_mem, len) != len && !grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + + if (grub_errno == GRUB_ERR_NONE) + { + grub_loader_set (grub_linux_boot, grub_linux_unload, + 0 /* set noreturn=0 in order to avoid grub_console_fini() */); + loaded = 1; + } + + fail: + + if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_size_t size = 0, aligned_size = 0; + grub_addr_t addr_min, addr_max; + grub_addr_t addr; + grub_err_t err; + struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 }; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + if (! loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); + goto fail; + } + + if (grub_initrd_init (argc, argv, &initrd_ctx)) + goto fail; + + size = grub_get_initrd_size (&initrd_ctx); + aligned_size = ALIGN_UP (size, 4096); + + /* Get the highest address available for the initrd. */ + if (grub_le_to_cpu16 (linux_params.version) >= 0x0203) + { + addr_max = grub_cpu_to_le32 (linux_params.initrd_addr_max); + + /* XXX in reality, Linux specifies a bogus value, so + it is necessary to make sure that ADDR_MAX does not exceed + 0x3fffffff. */ + if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS) + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + } + else + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + + if (linux_mem_size != 0 && linux_mem_size < addr_max) + addr_max = linux_mem_size; + + /* Linux 2.3.xx has a bug in the memory range check, so avoid + the last page. + Linux 2.2.xx has a bug in the memory range check, which is + worse than that of Linux 2.3.xx, so avoid the last 64kb. */ + addr_max -= 0x10000; + + addr_min = (grub_addr_t) prot_mode_target + prot_init_space; + + /* Put the initrd as high as possible, 4KiB aligned. */ + addr = (addr_max - aligned_size) & ~0xFFF; + + if (addr < addr_min) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "the initrd is too big"); + goto fail; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align (relocator, &ch, + addr_min, addr, aligned_size, + 0x1000, + GRUB_RELOCATOR_PREFERENCE_HIGH, + 1); + if (err) + return err; + initrd_mem = get_virtual_current_address (ch); + initrd_mem_target = get_physical_target_address (ch); + } + + if (grub_initrd_load (&initrd_ctx, argv, initrd_mem)) + goto fail; + + grub_dprintf ("linux", "Initrd, addr=0x%x, size=0x%x\n", + (unsigned) addr, (unsigned) size); + + linux_params.ramdisk_image = initrd_mem_target; + linux_params.ramdisk_size = size; + linux_params.root_dev = 0x0100; /* XXX */ + + fail: + grub_initrd_close (&initrd_ctx); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux) +{ + cmd_linux = grub_register_command ("linux", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/i386/multiboot_mbi.c b/grub-core/loader/i386/multiboot_mbi.c new file mode 100644 index 0000000..a67d9d0 --- /dev/null +++ b/grub-core/loader/i386/multiboot_mbi.c @@ -0,0 +1,756 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,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 + * 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/>. + */ + +#include <grub/memory.h> +#ifdef GRUB_MACHINE_PCBIOS +#include <grub/machine/biosnum.h> +#include <grub/machine/apm.h> +#include <grub/machine/memory.h> +#endif +#include <grub/multiboot.h> +#include <grub/cpu/relocator.h> +#include <grub/disk.h> +#include <grub/device.h> +#include <grub/partition.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/env.h> +#include <grub/relocator.h> +#include <grub/video.h> +#include <grub/file.h> +#include <grub/net.h> +#include <grub/i18n.h> +#include <grub/lib/cmdline.h> + +#ifdef GRUB_MACHINE_EFI +#include <grub/efi/efi.h> +#endif + +/* The bits in the required part of flags field we don't support. */ +#define UNSUPPORTED_FLAGS 0x0000fff8 + +struct module +{ + struct module *next; + grub_addr_t start; + grub_size_t size; + char *cmdline; + int cmdline_size; +}; + +static struct module *modules, *modules_last; +static grub_size_t cmdline_size; +static grub_size_t total_modcmd; +static unsigned modcnt; +static char *cmdline = NULL; +static grub_uint32_t bootdev; +static int bootdev_set; +static grub_size_t elf_sec_num, elf_sec_entsize; +static unsigned elf_sec_shstrndx; +static void *elf_sections; +grub_multiboot_quirks_t grub_multiboot_quirks; + +static grub_err_t +load_kernel (grub_file_t file, const char *filename, + char *buffer, struct multiboot_header *header) +{ + grub_err_t err; + mbi_load_data_t mld; + + mld.file = file; + mld.filename = filename; + mld.buffer = buffer; + mld.mbi_ver = 1; + mld.relocatable = 0; + mld.avoid_efi_boot_services = 0; + + if (grub_multiboot_quirks & GRUB_MULTIBOOT_QUIRK_BAD_KLUDGE) + { + err = grub_multiboot_load_elf (&mld); + if (err == GRUB_ERR_NONE) { + return GRUB_ERR_NONE; + } + if (err == GRUB_ERR_UNKNOWN_OS && (header->flags & MULTIBOOT_AOUT_KLUDGE)) + grub_errno = err = GRUB_ERR_NONE; + } + if (header->flags & MULTIBOOT_AOUT_KLUDGE) + { + int offset = ((char *) header - buffer - + (header->header_addr - header->load_addr)); + int load_size = ((header->load_end_addr == 0) ? file->size - offset : + header->load_end_addr - header->load_addr); + grub_size_t code_size; + void *source; + grub_relocator_chunk_t ch; + + if (header->bss_end_addr) + code_size = (header->bss_end_addr - header->load_addr); + else + code_size = load_size; + + err = grub_relocator_alloc_chunk_addr (grub_multiboot_relocator, + &ch, header->load_addr, + code_size); + if (err) + { + grub_dprintf ("multiboot_loader", "Error loading aout kludge\n"); + return err; + } + source = get_virtual_current_address (ch); + + if ((grub_file_seek (file, offset)) == (grub_off_t) -1) + { + return grub_errno; + } + + grub_file_read (file, source, load_size); + if (grub_errno) + return grub_errno; + + if (header->bss_end_addr) + grub_memset ((grub_uint8_t *) source + load_size, 0, + header->bss_end_addr - header->load_addr - load_size); + + grub_multiboot_payload_eip = header->entry_addr; + return GRUB_ERR_NONE; + } + + return grub_multiboot_load_elf (&mld); +} + +static struct multiboot_header * +find_header (char *buffer, grub_ssize_t len) +{ + struct multiboot_header *header; + + /* Look for the multiboot header in the buffer. The header should + be at least 12 bytes and aligned on a 4-byte boundary. */ + for (header = (struct multiboot_header *) buffer; + ((char *) header <= buffer + len - 12); + header = (struct multiboot_header *) ((char *) header + MULTIBOOT_HEADER_ALIGN)) + { + if (header->magic == MULTIBOOT_HEADER_MAGIC + && !(header->magic + header->flags + header->checksum)) + return header; + } + return NULL; +} + +grub_err_t +grub_multiboot_load (grub_file_t file, const char *filename) +{ + char *buffer; + grub_ssize_t len; + struct multiboot_header *header; + grub_err_t err; + + buffer = grub_malloc (MULTIBOOT_SEARCH); + if (!buffer) + return grub_errno; + + len = grub_file_read (file, buffer, MULTIBOOT_SEARCH); + if (len < 32) + { + grub_free (buffer); + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + return grub_errno; + } + + header = find_header (buffer, len); + + if (header == 0) + { + grub_free (buffer); + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no multiboot header found"); + } + + if (header->flags & UNSUPPORTED_FLAGS) + { + grub_free (buffer); + return grub_error (GRUB_ERR_UNKNOWN_OS, + "unsupported flag: 0x%x", header->flags); + } + + err = load_kernel (file, filename, buffer, header); + if (err) + { + grub_free (buffer); + return err; + } + + if (header->flags & MULTIBOOT_VIDEO_MODE) + { + switch (header->mode_type) + { + case 1: + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + GRUB_MULTIBOOT_CONSOLE_EGA_TEXT + | GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + 0, 0, 0, 0); + break; + case 0: + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + GRUB_MULTIBOOT_CONSOLE_EGA_TEXT + | GRUB_MULTIBOOT_CONSOLE_FRAMEBUFFER, + header->width, header->height, + header->depth, 0); + break; + default: + err = grub_error (GRUB_ERR_BAD_OS, + "unsupported graphical mode type %d", + header->mode_type); + break; + } + } + else + err = grub_multiboot_set_console (GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + GRUB_MULTIBOOT_CONSOLE_EGA_TEXT, + 0, 0, 0, 0); + return err; +} + +#if GRUB_MACHINE_HAS_VBE || GRUB_MACHINE_HAS_VGA_TEXT +#include <grub/i386/pc/vbe.h> +#endif + +static grub_size_t +grub_multiboot_get_mbi_size (void) +{ + grub_size_t ret; + struct grub_net_network_level_interface *net; + + ret = sizeof (struct multiboot_info) + ALIGN_UP (cmdline_size, 4) + + modcnt * sizeof (struct multiboot_mod_list) + total_modcmd + + ALIGN_UP (sizeof(PACKAGE_STRING), 4) + + grub_multiboot_get_mmap_count () * sizeof (struct multiboot_mmap_entry) + + elf_sec_entsize * elf_sec_num + + 256 * sizeof (struct multiboot_color) +#if GRUB_MACHINE_HAS_VBE || GRUB_MACHINE_HAS_VGA_TEXT + + sizeof (struct grub_vbe_info_block) + + sizeof (struct grub_vbe_mode_info_block) +#endif + + ALIGN_UP (sizeof (struct multiboot_apm_info), 4); + + FOR_NET_NETWORK_LEVEL_INTERFACES(net) + if (net->dhcp_ack) + { + ret += net->dhcp_acklen; + break; + } + + return ret; +} + +/* Helper for grub_fill_multiboot_mmap. */ +static int +grub_fill_multiboot_mmap_iter (grub_uint64_t addr, grub_uint64_t size, + grub_memory_type_t type, void *data) +{ + struct multiboot_mmap_entry **mmap_entry = data; + + (*mmap_entry)->addr = addr; + (*mmap_entry)->len = size; + (*mmap_entry)->type = type; + (*mmap_entry)->size = sizeof (struct multiboot_mmap_entry) - sizeof ((*mmap_entry)->size); + (*mmap_entry)++; + + return 0; +} + +/* Fill previously allocated Multiboot mmap. */ +static void +grub_fill_multiboot_mmap (struct multiboot_mmap_entry *first_entry) +{ + struct multiboot_mmap_entry *mmap_entry = (struct multiboot_mmap_entry *) first_entry; + + grub_mmap_iterate (grub_fill_multiboot_mmap_iter, &mmap_entry); +} + +#if GRUB_MACHINE_HAS_VBE || GRUB_MACHINE_HAS_VGA_TEXT + +static grub_err_t +fill_vbe_info (struct multiboot_info *mbi, grub_uint8_t *ptrorig, + grub_uint32_t ptrdest, int fill_generic) +{ + grub_uint32_t vbe_mode; + struct grub_vbe_mode_info_block *mode_info; +#if GRUB_MACHINE_HAS_VBE + grub_vbe_status_t status; + void *scratch = (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR; + + status = grub_vbe_bios_get_controller_info (scratch); + if (status != GRUB_VBE_STATUS_OK) + return grub_error (GRUB_ERR_IO, "Can't get controller info."); + + mbi->vbe_control_info = ptrdest; + grub_memcpy (ptrorig, scratch, sizeof (struct grub_vbe_info_block)); + ptrorig += sizeof (struct grub_vbe_info_block); + ptrdest += sizeof (struct grub_vbe_info_block); +#else + mbi->vbe_control_info = 0; +#endif + +#if GRUB_MACHINE_HAS_VBE + status = grub_vbe_bios_get_mode (scratch); + vbe_mode = *(grub_uint32_t *) scratch; + if (status != GRUB_VBE_STATUS_OK) + return grub_error (GRUB_ERR_IO, "can't get VBE mode"); +#else + vbe_mode = 3; +#endif + mbi->vbe_mode = vbe_mode; + + mode_info = (struct grub_vbe_mode_info_block *) ptrorig; + mbi->vbe_mode_info = ptrdest; + /* get_mode_info isn't available for mode 3. */ + if (vbe_mode == 3) + { + grub_memset (mode_info, 0, sizeof (struct grub_vbe_mode_info_block)); + mode_info->memory_model = GRUB_VBE_MEMORY_MODEL_TEXT; + mode_info->x_resolution = 80; + mode_info->y_resolution = 25; + } + else + { +#if GRUB_MACHINE_HAS_VBE + status = grub_vbe_bios_get_mode_info (vbe_mode, scratch); + if (status != GRUB_VBE_STATUS_OK) + return grub_error (GRUB_ERR_IO, "can't get mode info"); + grub_memcpy (mode_info, scratch, + sizeof (struct grub_vbe_mode_info_block)); +#endif + } + ptrorig += sizeof (struct grub_vbe_mode_info_block); + ptrdest += sizeof (struct grub_vbe_mode_info_block); + +#if GRUB_MACHINE_HAS_VBE + grub_vbe_bios_get_pm_interface (&mbi->vbe_interface_seg, + &mbi->vbe_interface_off, + &mbi->vbe_interface_len); +#endif + + mbi->flags |= MULTIBOOT_INFO_VBE_INFO; + + if (fill_generic && mode_info->memory_model == GRUB_VBE_MEMORY_MODEL_TEXT) + { + mbi->framebuffer_addr = 0xb8000; + + mbi->framebuffer_pitch = 2 * mode_info->x_resolution; + mbi->framebuffer_width = mode_info->x_resolution; + mbi->framebuffer_height = mode_info->y_resolution; + + mbi->framebuffer_bpp = 16; + + mbi->framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT; + + mbi->flags |= MULTIBOOT_INFO_FRAMEBUFFER_INFO; + } + + return GRUB_ERR_NONE; +} +#endif + +static grub_err_t +retrieve_video_parameters (struct multiboot_info *mbi, + grub_uint8_t *ptrorig, grub_uint32_t ptrdest) +{ + grub_err_t err; + struct grub_video_mode_info mode_info; + void *framebuffer; + grub_video_driver_id_t driv_id; + struct grub_video_palette_data palette[256]; + + err = grub_multiboot_set_video_mode (); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + + grub_video_get_palette (0, ARRAY_SIZE (palette), palette); + + driv_id = grub_video_get_driver_id (); +#if GRUB_MACHINE_HAS_VGA_TEXT + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return fill_vbe_info (mbi, ptrorig, ptrdest, 1); +#else + if (driv_id == GRUB_VIDEO_DRIVER_NONE) + return GRUB_ERR_NONE; +#endif + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + if (err) + return err; + + mbi->framebuffer_addr = (grub_addr_t) framebuffer; + mbi->framebuffer_pitch = mode_info.pitch; + + mbi->framebuffer_width = mode_info.width; + mbi->framebuffer_height = mode_info.height; + + mbi->framebuffer_bpp = mode_info.bpp; + + if (mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_INDEX_COLOR) + { + struct multiboot_color *mb_palette; + unsigned i; + mbi->framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED; + mbi->framebuffer_palette_addr = ptrdest; + mbi->framebuffer_palette_num_colors = mode_info.number_of_colors; + if (mbi->framebuffer_palette_num_colors > ARRAY_SIZE (palette)) + mbi->framebuffer_palette_num_colors = ARRAY_SIZE (palette); + mb_palette = (struct multiboot_color *) ptrorig; + for (i = 0; i < mbi->framebuffer_palette_num_colors; i++) + { + mb_palette[i].red = palette[i].r; + mb_palette[i].green = palette[i].g; + mb_palette[i].blue = palette[i].b; + } + ptrorig += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + ptrdest += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + } + else + { + mbi->framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_RGB; + mbi->framebuffer_red_field_position = mode_info.red_field_pos; + mbi->framebuffer_red_mask_size = mode_info.red_mask_size; + mbi->framebuffer_green_field_position = mode_info.green_field_pos; + mbi->framebuffer_green_mask_size = mode_info.green_mask_size; + mbi->framebuffer_blue_field_position = mode_info.blue_field_pos; + mbi->framebuffer_blue_mask_size = mode_info.blue_mask_size; + } + + mbi->flags |= MULTIBOOT_INFO_FRAMEBUFFER_INFO; + +#if GRUB_MACHINE_HAS_VBE + if (driv_id == GRUB_VIDEO_DRIVER_VBE) + return fill_vbe_info (mbi, ptrorig, ptrdest, 0); +#endif + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_multiboot_make_mbi (grub_uint32_t *target) +{ + struct multiboot_info *mbi; + struct multiboot_mod_list *modlist; + unsigned i; + struct module *cur; + grub_size_t mmap_size; + grub_uint8_t *ptrorig; + grub_addr_t ptrdest; + + grub_err_t err; + grub_size_t bufsize; + grub_relocator_chunk_t ch; + + bufsize = grub_multiboot_get_mbi_size (); + + err = grub_relocator_alloc_chunk_align_safe (grub_multiboot_relocator, &ch, + 0x10000, 0xa0000, bufsize, 4, + GRUB_RELOCATOR_PREFERENCE_NONE, 0); + if (err) + return err; + ptrorig = get_virtual_current_address (ch); + ptrdest = get_physical_target_address (ch); + + *target = ptrdest; + + mbi = (struct multiboot_info *) ptrorig; + ptrorig += sizeof (*mbi); + ptrdest += sizeof (*mbi); + grub_memset (mbi, 0, sizeof (*mbi)); + + grub_memcpy (ptrorig, cmdline, cmdline_size); + mbi->flags |= MULTIBOOT_INFO_CMDLINE; + mbi->cmdline = ptrdest; + ptrorig += ALIGN_UP (cmdline_size, 4); + ptrdest += ALIGN_UP (cmdline_size, 4); + + grub_memcpy (ptrorig, PACKAGE_STRING, sizeof(PACKAGE_STRING)); + mbi->flags |= MULTIBOOT_INFO_BOOT_LOADER_NAME; + mbi->boot_loader_name = ptrdest; + ptrorig += ALIGN_UP (sizeof(PACKAGE_STRING), 4); + ptrdest += ALIGN_UP (sizeof(PACKAGE_STRING), 4); + +#ifdef GRUB_MACHINE_PCBIOS + { + struct grub_apm_info info; + if (grub_apm_get_info (&info)) + { + struct multiboot_apm_info *mbinfo = (void *) ptrorig; + + mbinfo->cseg = info.cseg; + mbinfo->offset = info.offset; + mbinfo->cseg_16 = info.cseg_16; + mbinfo->dseg = info.dseg; + mbinfo->flags = info.flags; + mbinfo->cseg_len = info.cseg_len; + mbinfo->dseg_len = info.dseg_len; + mbinfo->cseg_16_len = info.cseg_16_len; + mbinfo->version = info.version; + + ptrorig += ALIGN_UP (sizeof (struct multiboot_apm_info), 4); + ptrdest += ALIGN_UP (sizeof (struct multiboot_apm_info), 4); + } + } +#endif + + if (modcnt) + { + mbi->flags |= MULTIBOOT_INFO_MODS; + mbi->mods_addr = ptrdest; + mbi->mods_count = modcnt; + modlist = (struct multiboot_mod_list *) ptrorig; + ptrorig += modcnt * sizeof (struct multiboot_mod_list); + ptrdest += modcnt * sizeof (struct multiboot_mod_list); + + for (i = 0, cur = modules; i < modcnt; i++, cur = cur->next) + { + modlist[i].mod_start = cur->start; + modlist[i].mod_end = modlist[i].mod_start + cur->size; + modlist[i].cmdline = ptrdest; + grub_memcpy (ptrorig, cur->cmdline, cur->cmdline_size); + ptrorig += ALIGN_UP (cur->cmdline_size, 4); + ptrdest += ALIGN_UP (cur->cmdline_size, 4); + } + } + else + { + mbi->mods_addr = 0; + mbi->mods_count = 0; + } + + mmap_size = grub_multiboot_get_mmap_count () + * sizeof (struct multiboot_mmap_entry); + grub_fill_multiboot_mmap ((struct multiboot_mmap_entry *) ptrorig); + mbi->mmap_length = mmap_size; + mbi->mmap_addr = ptrdest; + mbi->flags |= MULTIBOOT_INFO_MEM_MAP; + ptrorig += mmap_size; + ptrdest += mmap_size; + + /* Convert from bytes to kilobytes. */ + mbi->mem_lower = grub_mmap_get_lower () / 1024; + mbi->mem_upper = grub_mmap_get_upper () / 1024; + mbi->flags |= MULTIBOOT_INFO_MEMORY; + + if (bootdev_set) + { + mbi->boot_device = bootdev; + mbi->flags |= MULTIBOOT_INFO_BOOTDEV; + } + + { + struct grub_net_network_level_interface *net; + FOR_NET_NETWORK_LEVEL_INTERFACES(net) + if (net->dhcp_ack) + { + grub_memcpy (ptrorig, net->dhcp_ack, net->dhcp_acklen); + mbi->drives_addr = ptrdest; + mbi->drives_length = net->dhcp_acklen; + ptrorig += net->dhcp_acklen; + ptrdest += net->dhcp_acklen; + break; + } + } + + if (elf_sec_num) + { + mbi->u.elf_sec.addr = ptrdest; + grub_memcpy (ptrorig, elf_sections, elf_sec_entsize * elf_sec_num); + mbi->u.elf_sec.num = elf_sec_num; + mbi->u.elf_sec.size = elf_sec_entsize; + mbi->u.elf_sec.shndx = elf_sec_shstrndx; + + mbi->flags |= MULTIBOOT_INFO_ELF_SHDR; + + ptrorig += elf_sec_entsize * elf_sec_num; + ptrdest += elf_sec_entsize * elf_sec_num; + } + + err = retrieve_video_parameters (mbi, ptrorig, ptrdest); + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + + if ((mbi->flags & MULTIBOOT_INFO_FRAMEBUFFER_INFO) + && mbi->framebuffer_type == MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED) + { + ptrorig += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + ptrdest += mbi->framebuffer_palette_num_colors + * sizeof (struct multiboot_color); + } + +#if GRUB_MACHINE_HAS_VBE + ptrorig += sizeof (struct grub_vbe_info_block); + ptrdest += sizeof (struct grub_vbe_info_block); + ptrorig += sizeof (struct grub_vbe_mode_info_block); + ptrdest += sizeof (struct grub_vbe_mode_info_block); +#endif + +#ifdef GRUB_MACHINE_EFI + err = grub_efi_finish_boot_services (NULL, NULL, NULL, NULL, NULL); + if (err) + return err; +#endif + + return GRUB_ERR_NONE; +} + +void +grub_multiboot_add_elfsyms (grub_size_t num, grub_size_t entsize, + unsigned shndx, void *data) +{ + elf_sec_num = num; + elf_sec_shstrndx = shndx; + elf_sec_entsize = entsize; + elf_sections = data; +} + +void +grub_multiboot_free_mbi (void) +{ + struct module *cur, *next; + + cmdline_size = 0; + total_modcmd = 0; + modcnt = 0; + grub_free (cmdline); + cmdline = NULL; + bootdev_set = 0; + + for (cur = modules; cur; cur = next) + { + next = cur->next; + grub_free (cur->cmdline); + grub_free (cur); + } + modules = NULL; + modules_last = NULL; + + grub_free (elf_sections); + elf_sections = NULL; + elf_sec_entsize = 0; + elf_sec_num = 0; +} + +grub_err_t +grub_multiboot_init_mbi (int argc, char *argv[]) +{ + grub_ssize_t len = 0; + + grub_multiboot_free_mbi (); + + len = grub_loader_cmdline_size (argc, argv); + + cmdline = grub_malloc (len); + if (! cmdline) + return grub_errno; + cmdline_size = len; + + return grub_create_loader_cmdline (argc, argv, cmdline, + cmdline_size, GRUB_VERIFY_KERNEL_CMDLINE); +} + +grub_err_t +grub_multiboot_add_module (grub_addr_t start, grub_size_t size, + int argc, char *argv[]) +{ + struct module *newmod; + grub_size_t len = 0; + grub_err_t err; + + newmod = grub_malloc (sizeof (*newmod)); + if (!newmod) + return grub_errno; + newmod->start = start; + newmod->size = size; + newmod->next = 0; + + len = grub_loader_cmdline_size (argc, argv); + + newmod->cmdline = grub_malloc (len); + if (! newmod->cmdline) + { + grub_free (newmod); + return grub_errno; + } + newmod->cmdline_size = len; + total_modcmd += ALIGN_UP (len, 4); + + err = grub_create_loader_cmdline (argc, argv, newmod->cmdline, + newmod->cmdline_size, GRUB_VERIFY_MODULE_CMDLINE); + if (err) + { + grub_free (newmod); + return grub_errno; + } + + if (modules_last) + modules_last->next = newmod; + else + modules = newmod; + modules_last = newmod; + + modcnt++; + + return GRUB_ERR_NONE; +} + +void +grub_multiboot_set_bootdev (void) +{ + grub_uint32_t biosdev, slice = ~0, part = ~0; + grub_device_t dev; + +#ifdef GRUB_MACHINE_PCBIOS + biosdev = grub_get_root_biosnumber (); +#else + biosdev = 0xffffffff; +#endif + + if (biosdev == 0xffffffff) + return; + + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + if (dev->disk->partition->parent) + { + part = dev->disk->partition->number; + slice = dev->disk->partition->parent->number; + } + else + slice = dev->disk->partition->number; + } + if (dev) + grub_device_close (dev); + + bootdev = ((biosdev & 0xff) << 24) | ((slice & 0xff) << 16) + | ((part & 0xff) << 8) | 0xff; + bootdev_set = 1; +} diff --git a/grub-core/loader/i386/pc/chainloader.c b/grub-core/loader/i386/pc/chainloader.c new file mode 100644 index 0000000..976fea7 --- /dev/null +++ b/grub-core/loader/i386/pc/chainloader.c @@ -0,0 +1,310 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/machine/chainloader.h> +#include <grub/machine/biosdisk.h> +#include <grub/machine/memory.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/memory.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/msdos_partition.h> +#include <grub/machine/biosnum.h> +#include <grub/cpu/floppy.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/fat.h> +#include <grub/ntfs.h> +#include <grub/i386/relocator.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static int boot_drive; +static grub_addr_t boot_part_addr; +static struct grub_relocator *rel; + +typedef enum + { + GRUB_CHAINLOADER_FORCE = 0x1, + GRUB_CHAINLOADER_BPB = 0x2, + } grub_chainloader_flags_t; + +static grub_err_t +grub_chainloader_boot (void) +{ + struct grub_relocator16_state state = { + .edx = boot_drive, + .esi = boot_part_addr, + .ds = 0, + .es = 0, + .fs = 0, + .gs = 0, + .ss = 0, + .cs = 0, + .sp = GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR, + .ip = GRUB_MEMORY_MACHINE_BOOT_LOADER_ADDR, + .a20 = 0 + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator16_boot (rel, state); +} + +static grub_err_t +grub_chainloader_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +void +grub_chainloader_patch_bpb (void *bs, grub_device_t dev, grub_uint8_t dl) +{ + grub_uint32_t part_start = 0, heads = 0, sectors = 0; + if (dev && dev->disk) + { + part_start = grub_partition_get_start (dev->disk->partition); + if (dev->disk->data) + { + heads = ((struct grub_biosdisk_data *)(dev->disk->data))->heads; + sectors = ((struct grub_biosdisk_data *)(dev->disk->data))->sectors; + } + } + if (grub_memcmp ((char *) &((struct grub_ntfs_bpb *) bs)->oem_name, + "NTFS", 4) == 0) + { + struct grub_ntfs_bpb *bpb = (struct grub_ntfs_bpb *) bs; + bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start); + bpb->bios_drive = dl; + return; + } + + do + { + struct grub_fat_bpb *bpb = (struct grub_fat_bpb *) bs; + if (grub_strncmp((const char *) bpb->version_specific.fat12_or_fat16.fstype, "FAT12", 5) + && grub_strncmp((const char *) bpb->version_specific.fat12_or_fat16.fstype, "FAT16", 5) + && grub_strncmp((const char *) bpb->version_specific.fat32.fstype, "FAT32", 5)) + break; + + if (grub_le_to_cpu16 (bpb->bytes_per_sector) < 512 + || (grub_le_to_cpu16 (bpb->bytes_per_sector) + & (grub_le_to_cpu16 (bpb->bytes_per_sector) - 1))) + break; + + if (bpb->sectors_per_cluster == 0 + || (bpb->sectors_per_cluster & (bpb->sectors_per_cluster - 1))) + break; + + if (bpb->num_reserved_sectors == 0) + break; + if (bpb->num_total_sectors_16 == 0 && bpb->num_total_sectors_32 == 0) + break; + + if (bpb->num_fats == 0) + break; + + if (bpb->sectors_per_fat_16) + { + bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start); + bpb->version_specific.fat12_or_fat16.num_ph_drive = dl; + if (sectors) + bpb->sectors_per_track = grub_cpu_to_le16 (sectors); + if (heads) + bpb->num_heads = grub_cpu_to_le16 (heads); + return; + } + if (bpb->version_specific.fat32.sectors_per_fat_32) + { + bpb->num_hidden_sectors = grub_cpu_to_le32 (part_start); + bpb->version_specific.fat32.num_ph_drive = dl; + if (sectors) + bpb->sectors_per_track = grub_cpu_to_le16 (sectors); + if (heads) + bpb->num_heads = grub_cpu_to_le16 (heads); + return; + } + break; + } + while (0); +} + +static void +grub_chainloader_cmd (const char *filename, grub_chainloader_flags_t flags) +{ + grub_file_t file = 0; + grub_uint16_t signature; + grub_device_t dev; + int drive = -1; + grub_addr_t part_addr = 0; + grub_uint8_t *bs, *ptable; + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + grub_dl_ref (my_mod); + + file = grub_file_open (filename, GRUB_FILE_TYPE_PCCHAINLOADER + | GRUB_FILE_TYPE_NO_DECOMPRESS); + if (! file) + goto fail; + + { + grub_relocator_chunk_t ch; + grub_err_t err; + + err = grub_relocator_alloc_chunk_addr (rel, &ch, 0x7C00, + GRUB_DISK_SECTOR_SIZE); + if (err) + goto fail; + bs = get_virtual_current_address (ch); + err = grub_relocator_alloc_chunk_addr (rel, &ch, + GRUB_MEMORY_MACHINE_PART_TABLE_ADDR, + 64); + if (err) + goto fail; + ptable = get_virtual_current_address (ch); + } + + /* Read the first block. */ + if (grub_file_read (file, bs, GRUB_DISK_SECTOR_SIZE) + != GRUB_DISK_SECTOR_SIZE) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + + goto fail; + } + + /* Check the signature. */ + signature = *((grub_uint16_t *) (bs + GRUB_DISK_SECTOR_SIZE - 2)); + if (signature != grub_le_to_cpu16 (0xaa55) + && ! (flags & GRUB_CHAINLOADER_FORCE)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid signature"); + goto fail; + } + + grub_file_close (file); + + /* Obtain the partition table from the root device. */ + drive = grub_get_root_biosnumber (); + dev = grub_device_open (0); + if (dev && dev->disk && dev->disk->partition) + { + grub_disk_t disk = dev->disk; + + if (disk) + { + grub_partition_t p = disk->partition; + + if (p && grub_strcmp (p->partmap->name, "msdos") == 0) + { + disk->partition = p->parent; + grub_disk_read (disk, p->offset, 446, 64, ptable); + part_addr = (GRUB_MEMORY_MACHINE_PART_TABLE_ADDR + + (p->index << 4)); + disk->partition = p; + } + } + } + + if (flags & GRUB_CHAINLOADER_BPB) + grub_chainloader_patch_bpb ((void *) 0x7C00, dev, drive); + + if (dev) + grub_device_close (dev); + + /* Ignore errors. Perhaps it's not fatal. */ + grub_errno = GRUB_ERR_NONE; + + boot_drive = drive; + boot_part_addr = part_addr; + + grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 1); + return; + + fail: + + if (file) + grub_file_close (file); + + grub_dl_unref (my_mod); +} + +static grub_err_t +grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_chainloader_flags_t flags = 0; + + while (argc > 0) + { + if (grub_strcmp (argv[0], "--force") == 0) + { + flags |= GRUB_CHAINLOADER_FORCE; + argc--; + argv++; + continue; + } + if (grub_strcmp (argv[0], "--bpb") == 0) + { + flags |= GRUB_CHAINLOADER_BPB; + argc--; + argv++; + continue; + } + break; + } + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_chainloader_cmd (argv[0], flags); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(chainloader) +{ + cmd = grub_register_command ("chainloader", grub_cmd_chainloader, + N_("[--force|--bpb] FILE"), + N_("Load another boot loader.")); + my_mod = mod; +} + +GRUB_MOD_FINI(chainloader) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/pc/freedos.c b/grub-core/loader/i386/pc/freedos.c new file mode 100644 index 0000000..aac6c97 --- /dev/null +++ b/grub-core/loader/i386/pc/freedos.c @@ -0,0 +1,190 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/machine/biosnum.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/machine/chainloader.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t ebx = 0xffffffff; + +#define GRUB_FREEDOS_SEGMENT 0x60 +#define GRUB_FREEDOS_ADDR (GRUB_FREEDOS_SEGMENT << 4) +#define GRUB_FREEDOS_STACK_SEGMENT 0x1fe0 +#define GRUB_FREEDOS_STACK_BPB_POINTER 0x7c00 +#define GRUB_FREEDOS_BPB_ADDR ((GRUB_FREEDOS_STACK_SEGMENT << 4) \ + + GRUB_FREEDOS_STACK_BPB_POINTER) + +/* FreeDOS boot.asm passes register sp as exactly this. Importantly, + it must point below the BPB (to avoid overwriting any of it). */ +#define GRUB_FREEDOS_STACK_POINTER (GRUB_FREEDOS_STACK_BPB_POINTER \ + - 0x60) + +/* In this, the additional 8192 bytes are the stack reservation; the + remaining parts trivially give the maximum allowed size. */ +#define GRUB_FREEDOS_MAX_SIZE ((GRUB_FREEDOS_STACK_SEGMENT << 4) \ + + GRUB_FREEDOS_STACK_POINTER \ + - GRUB_FREEDOS_ADDR \ + - 8192) + +static grub_err_t +grub_freedos_boot (void) +{ + struct grub_relocator16_state state = { + .cs = GRUB_FREEDOS_SEGMENT, + .ip = 0, + + .ds = GRUB_FREEDOS_STACK_SEGMENT, + .es = 0, + .fs = 0, + .gs = 0, + .ss = GRUB_FREEDOS_STACK_SEGMENT, + .sp = GRUB_FREEDOS_STACK_POINTER, + .ebp = GRUB_FREEDOS_STACK_BPB_POINTER, + .ebx = ebx, + .edx = ebx, + .a20 = 1 + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator16_boot (rel, state); +} + +static grub_err_t +grub_freedos_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_freedos (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + void *bs, *kernelsys; + grub_size_t kernelsyssize; + grub_device_t dev; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_dl_ref (my_mod); + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_FREEDOS); + if (! file) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_FREEDOS_BPB_ADDR, + GRUB_DISK_SECTOR_SIZE); + if (err) + goto fail; + bs = get_virtual_current_address (ch); + } + + ebx = grub_get_root_biosnumber (); + dev = grub_device_open (0); + + if (dev && dev->disk) + { + err = grub_disk_read (dev->disk, 0, 0, GRUB_DISK_SECTOR_SIZE, bs); + if (err) + { + grub_device_close (dev); + goto fail; + } + grub_chainloader_patch_bpb (bs, dev, ebx); + } + + if (dev) + grub_device_close (dev); + + kernelsyssize = grub_file_size (file); + + if (kernelsyssize > GRUB_FREEDOS_MAX_SIZE) + { + grub_error (GRUB_ERR_BAD_OS, + N_("the size of `%s' is too large"), argv[0]); + goto fail; + } + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_FREEDOS_ADDR, + kernelsyssize); + if (err) + goto fail; + kernelsys = get_virtual_current_address (ch); + } + + if (grub_file_read (file, kernelsys, kernelsyssize) + != (grub_ssize_t) kernelsyssize) + goto fail; + + grub_loader_set (grub_freedos_boot, grub_freedos_unload, 1); + return GRUB_ERR_NONE; + + fail: + + if (file) + grub_file_close (file); + + grub_freedos_unload (); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(freedos) +{ + cmd = grub_register_command ("freedos", grub_cmd_freedos, + 0, N_("Load FreeDOS kernel.sys.")); + my_mod = mod; +} + +GRUB_MOD_FINI(freedos) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/pc/linux.c b/grub-core/loader/i386/pc/linux.c new file mode 100644 index 0000000..2a29952 --- /dev/null +++ b/grub-core/loader/i386/pc/linux.c @@ -0,0 +1,494 @@ +/* linux.c - boot Linux zImage or bzImage */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/memory.h> +#include <grub/dl.h> +#include <grub/cpu/linux.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/video.h> +#include <grub/i386/floppy.h> +#include <grub/lib/cmdline.h> +#include <grub/linux.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_LINUX_CL_OFFSET 0x9000 + +static grub_dl_t my_mod; + +static grub_size_t linux_mem_size; +static int loaded; +static struct grub_relocator *relocator = NULL; +static grub_addr_t grub_linux_real_target; +static char *grub_linux_real_chunk; +static grub_size_t grub_linux16_prot_size; +static grub_size_t maximal_cmdline_size; + +static grub_err_t +grub_linux16_boot (void) +{ + grub_uint16_t segment; + struct grub_relocator16_state state; + + segment = grub_linux_real_target >> 4; + state.gs = state.fs = state.es = state.ds = state.ss = segment; + state.sp = GRUB_LINUX_SETUP_STACK; + state.cs = segment + 0x20; + state.ip = 0; + state.a20 = 1; + + grub_video_set_mode ("text", 0, 0); + + grub_stop_floppy (); + + return grub_relocator16_boot (relocator, state); +} + +static grub_err_t +grub_linux_unload (void) +{ + grub_dl_unref (my_mod); + loaded = 0; + grub_relocator_unload (relocator); + relocator = NULL; + return GRUB_ERR_NONE; +} + +static int +target_hook (grub_uint64_t addr, grub_uint64_t size, grub_memory_type_t type, + void *data) +{ + grub_uint64_t *result = data; + grub_uint64_t candidate; + + if (type != GRUB_MEMORY_AVAILABLE) + return 0; + if (addr >= 0xa0000) + return 0; + if (addr + size >= 0xa0000) + size = 0xa0000 - addr; + + /* Put the real mode part at as a high location as possible. */ + candidate = addr + size - (GRUB_LINUX_CL_OFFSET + maximal_cmdline_size); + /* But it must not exceed the traditional area. */ + if (candidate > GRUB_LINUX_OLD_REAL_MODE_ADDR) + candidate = GRUB_LINUX_OLD_REAL_MODE_ADDR; + if (candidate < addr) + return 0; + + if (candidate > *result || *result == (grub_uint64_t) -1) + *result = candidate; + return 0; +} + +static grub_addr_t +grub_find_real_target (void) +{ + grub_uint64_t result = (grub_uint64_t) -1; + + grub_mmap_iterate (target_hook, &result); + return result; +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + struct linux_i386_kernel_header lh; + grub_uint8_t setup_sects; + grub_size_t real_size; + grub_ssize_t len; + int i; + char *grub_linux_prot_chunk; + int grub_linux_is_bzimage; + grub_addr_t grub_linux_prot_target; + grub_err_t err; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL); + if (! file) + goto fail; + + if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + + if (lh.boot_flag != grub_cpu_to_le16_compile_time (0xaa55)) + { + grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); + goto fail; + } + + if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) + { + grub_error (GRUB_ERR_BAD_OS, "too many setup sectors"); + goto fail; + } + + grub_linux_is_bzimage = 0; + setup_sects = lh.setup_sects; + linux_mem_size = 0; + + maximal_cmdline_size = 256; + + if (lh.header == grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE) + && grub_le_to_cpu16 (lh.version) >= 0x0200) + { + grub_linux_is_bzimage = (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL); + lh.type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE; + + if (grub_le_to_cpu16 (lh.version) >= 0x0206) + maximal_cmdline_size = grub_le_to_cpu32 (lh.cmdline_size) + 1; + + grub_linux_real_target = grub_find_real_target (); + if (grub_linux_real_target == (grub_addr_t)-1) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, + "no appropriate low memory found"); + goto fail; + } + + if (grub_le_to_cpu16 (lh.version) >= 0x0201) + { + lh.heap_end_ptr = grub_cpu_to_le16_compile_time (GRUB_LINUX_HEAP_END_OFFSET); + lh.loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP; + } + + if (grub_le_to_cpu16 (lh.version) >= 0x0202) + lh.cmd_line_ptr = grub_linux_real_target + GRUB_LINUX_CL_OFFSET; + else + { + lh.cl_magic = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_MAGIC); + lh.cl_offset = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_OFFSET); + lh.setup_move_size = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_OFFSET + + maximal_cmdline_size); + } + } + else + { + /* Your kernel is quite old... */ + lh.cl_magic = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_MAGIC); + lh.cl_offset = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_OFFSET); + + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + grub_linux_real_target = GRUB_LINUX_OLD_REAL_MODE_ADDR; + } + + /* If SETUP_SECTS is not set, set it to the default (4). */ + if (! setup_sects) + setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS; + + real_size = setup_sects << GRUB_DISK_SECTOR_BITS; + if (grub_sub (grub_file_size (file), real_size, &grub_linux16_prot_size) || + grub_sub (grub_linux16_prot_size, GRUB_DISK_SECTOR_SIZE, &grub_linux16_prot_size)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected")); + goto fail; + } + + if (! grub_linux_is_bzimage + && GRUB_LINUX_ZIMAGE_ADDR + grub_linux16_prot_size + > grub_linux_real_target) + { + grub_error (GRUB_ERR_BAD_OS, "too big zImage (0x%" PRIxGRUB_SIZE + " > 0x%" PRIxGRUB_ADDR "), use bzImage instead", + GRUB_LINUX_ZIMAGE_ADDR + grub_linux16_prot_size, + grub_linux_real_target); + goto fail; + } + + grub_dprintf ("linux", "[Linux-%s, setup=0x%x, size=0x%x]\n", + grub_linux_is_bzimage ? "bzImage" : "zImage", + (unsigned) real_size, + (unsigned) grub_linux16_prot_size); + + for (i = 1; i < argc; i++) + if (grub_memcmp (argv[i], "vga=", 4) == 0) + { + /* Video mode selection support. */ + grub_uint16_t vid_mode; + char *val = argv[i] + 4; + + if (grub_strcmp (val, "normal") == 0) + vid_mode = GRUB_LINUX_VID_MODE_NORMAL; + else if (grub_strcmp (val, "ext") == 0) + vid_mode = GRUB_LINUX_VID_MODE_EXTENDED; + else if (grub_strcmp (val, "ask") == 0) + vid_mode = GRUB_LINUX_VID_MODE_ASK; + else + vid_mode = (grub_uint16_t) grub_strtoul (val, 0, 0); + + if (grub_errno) + goto fail; + + lh.vid_mode = grub_cpu_to_le16 (vid_mode); + } + else if (grub_memcmp (argv[i], "mem=", 4) == 0) + { + const char *val = argv[i] + 4; + + linux_mem_size = grub_strtoul (val, &val, 0); + + if (grub_errno) + { + grub_errno = GRUB_ERR_NONE; + linux_mem_size = 0; + } + else + { + int shift = 0; + + switch (grub_tolower (val[0])) + { + case 'g': + shift += 10; + /* Fallthrough. */ + case 'm': + shift += 10; + /* Fallthrough. */ + case 'k': + shift += 10; + /* Fallthrough. */ + default: + break; + } + + /* Check an overflow. */ + if (linux_mem_size > (~0UL >> shift)) + linux_mem_size = 0; + else + linux_mem_size <<= shift; + } + } + + relocator = grub_relocator_new (); + if (!relocator) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + grub_linux_real_target, + GRUB_LINUX_CL_OFFSET + + maximal_cmdline_size); + if (err) + return err; + grub_linux_real_chunk = get_virtual_current_address (ch); + } + + /* Put the real mode code at the temporary address. */ + grub_memmove (grub_linux_real_chunk, &lh, sizeof (lh)); + + len = real_size + GRUB_DISK_SECTOR_SIZE - sizeof (lh); + if (grub_file_read (file, grub_linux_real_chunk + sizeof (lh), len) != len) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + + if (lh.header != grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE) + || grub_le_to_cpu16 (lh.version) < 0x0200) + /* Clear the heap space. */ + grub_memset (grub_linux_real_chunk + + ((setup_sects + 1) << GRUB_DISK_SECTOR_BITS), + 0, + ((GRUB_LINUX_MAX_SETUP_SECTS - setup_sects - 1) + << GRUB_DISK_SECTOR_BITS)); + + /* Create kernel command line. */ + grub_memcpy ((char *)grub_linux_real_chunk + GRUB_LINUX_CL_OFFSET, + LINUX_IMAGE, sizeof (LINUX_IMAGE)); + err = grub_create_loader_cmdline (argc, argv, + (char *)grub_linux_real_chunk + + GRUB_LINUX_CL_OFFSET + sizeof (LINUX_IMAGE) - 1, + maximal_cmdline_size + - (sizeof (LINUX_IMAGE) - 1), + GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + goto fail; + + if (grub_linux_is_bzimage) + grub_linux_prot_target = GRUB_LINUX_BZIMAGE_ADDR; + else + grub_linux_prot_target = GRUB_LINUX_ZIMAGE_ADDR; + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (relocator, &ch, + grub_linux_prot_target, + grub_linux16_prot_size); + if (err) + return err; + grub_linux_prot_chunk = get_virtual_current_address (ch); + } + + len = grub_linux16_prot_size; + if (grub_file_read (file, grub_linux_prot_chunk, len) != len && !grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + + if (grub_errno == GRUB_ERR_NONE) + { + grub_loader_set (grub_linux16_boot, grub_linux_unload, 0); + loaded = 1; + } + + fail: + + if (file) + grub_file_close (file); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + grub_relocator_unload (relocator); + } + + return grub_errno; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_size_t size = 0; + grub_addr_t addr_max, addr_min; + struct linux_i386_kernel_header *lh; + grub_uint8_t *initrd_chunk; + grub_addr_t initrd_addr; + grub_err_t err; + struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 }; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + if (!loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); + goto fail; + } + + lh = (struct linux_i386_kernel_header *) grub_linux_real_chunk; + + if (!(lh->header == grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE) + && grub_le_to_cpu16 (lh->version) >= 0x0200)) + { + grub_error (GRUB_ERR_BAD_OS, "the kernel is too old for initrd"); + goto fail; + } + + /* Get the highest address available for the initrd. */ + if (grub_le_to_cpu16 (lh->version) >= 0x0203) + { + addr_max = grub_cpu_to_le32 (lh->initrd_addr_max); + + /* XXX in reality, Linux specifies a bogus value, so + it is necessary to make sure that ADDR_MAX does not exceed + 0x3fffffff. */ + if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS) + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + } + else + addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS; + + if (linux_mem_size != 0 && linux_mem_size < addr_max) + addr_max = linux_mem_size; + + /* Linux 2.3.xx has a bug in the memory range check, so avoid + the last page. + Linux 2.2.xx has a bug in the memory range check, which is + worse than that of Linux 2.3.xx, so avoid the last 64kb. */ + addr_max -= 0x10000; + + addr_min = GRUB_LINUX_BZIMAGE_ADDR + grub_linux16_prot_size; + + if (grub_initrd_init (argc, argv, &initrd_ctx)) + goto fail; + + size = grub_get_initrd_size (&initrd_ctx); + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_align_safe (relocator, &ch, addr_min, addr_max, size, + 0x1000, GRUB_RELOCATOR_PREFERENCE_HIGH, 0); + if (err) + return err; + initrd_chunk = get_virtual_current_address (ch); + initrd_addr = get_physical_target_address (ch); + } + + if (grub_initrd_load (&initrd_ctx, argv, initrd_chunk)) + goto fail; + + lh->ramdisk_image = initrd_addr; + lh->ramdisk_size = size; + + fail: + grub_initrd_close (&initrd_ctx); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linux16) +{ + cmd_linux = + grub_register_command ("linux16", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = + grub_register_command ("initrd16", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linux16) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/i386/pc/ntldr.c b/grub-core/loader/i386/pc/ntldr.c new file mode 100644 index 0000000..f0d7414 --- /dev/null +++ b/grub-core/loader/i386/pc/ntldr.c @@ -0,0 +1,162 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/machine/biosnum.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/machine/chainloader.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t edx = 0xffffffff; + +#define GRUB_NTLDR_SEGMENT 0x2000 + +static grub_err_t +grub_ntldr_boot (void) +{ + struct grub_relocator16_state state = { + .cs = GRUB_NTLDR_SEGMENT, + .ip = 0, + .ds = 0, + .es = 0, + .fs = 0, + .gs = 0, + .ss = 0, + .sp = 0x7c00, + .edx = edx, + .a20 = 1 + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator16_boot (rel, state); +} + +static grub_err_t +grub_ntldr_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_ntldr (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + void *bs, *ntldr; + grub_size_t ntldrsize; + grub_device_t dev; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_dl_ref (my_mod); + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_NTLDR); + if (! file) + goto fail; + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, 0x7C00, + GRUB_DISK_SECTOR_SIZE); + if (err) + goto fail; + bs = get_virtual_current_address (ch); + } + + edx = grub_get_root_biosnumber (); + dev = grub_device_open (0); + + if (dev && dev->disk) + { + err = grub_disk_read (dev->disk, 0, 0, GRUB_DISK_SECTOR_SIZE, bs); + if (err) + { + grub_device_close (dev); + goto fail; + } + grub_chainloader_patch_bpb (bs, dev, edx); + } + + if (dev) + grub_device_close (dev); + + ntldrsize = grub_file_size (file); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_NTLDR_SEGMENT << 4, + ntldrsize); + if (err) + goto fail; + ntldr = get_virtual_current_address (ch); + } + + if (grub_file_read (file, ntldr, ntldrsize) + != (grub_ssize_t) ntldrsize) + goto fail; + + grub_loader_set (grub_ntldr_boot, grub_ntldr_unload, 1); + return GRUB_ERR_NONE; + + fail: + + if (file) + grub_file_close (file); + + grub_ntldr_unload (); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(ntldr) +{ + cmd = grub_register_command ("ntldr", grub_cmd_ntldr, + 0, N_("Load NTLDR or BootMGR.")); + my_mod = mod; +} + +GRUB_MOD_FINI(ntldr) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/pc/plan9.c b/grub-core/loader/i386/pc/plan9.c new file mode 100644 index 0000000..3755015 --- /dev/null +++ b/grub-core/loader/i386/pc/plan9.c @@ -0,0 +1,607 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/msdos_partition.h> +#include <grub/scsi.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/extcmd.h> +#include <grub/verify.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t eip = 0xffffffff; + +#define GRUB_PLAN9_TARGET 0x100000 +#define GRUB_PLAN9_ALIGN 4096 +#define GRUB_PLAN9_CONFIG_ADDR 0x001200 +#define GRUB_PLAN9_CONFIG_PATH_SIZE 0x000040 +#define GRUB_PLAN9_CONFIG_MAGIC "ZORT 0\r\n" + +static const struct grub_arg_option options[] = + { + {"map", 'm', GRUB_ARG_OPTION_REPEATABLE, + /* TRANSLATORS: it's about guessing which GRUB disk + is which Plan9 disk. If your language has no + word "mapping" you can use another word which + means that the GRUBDEVICE and PLAN9DEVICE are + actually the same device, just named differently + in OS and GRUB. */ + N_("Override guessed mapping of Plan9 devices."), + N_("GRUBDEVICE=PLAN9DEVICE"), + ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +struct grub_plan9_header +{ + grub_uint32_t magic; +#define GRUB_PLAN9_MAGIC 0x1eb + grub_uint32_t text_size; + grub_uint32_t data_size; + grub_uint32_t bss_size; + grub_uint32_t sectiona; + grub_uint32_t entry_addr; + grub_uint32_t zero; + grub_uint32_t sectionb; +}; + +static grub_err_t +grub_plan9_boot (void) +{ + struct grub_relocator32_state state = { + .eax = 0, + .eip = eip, + .ebx = 0, + .ecx = 0, + .edx = 0, + .edi = 0, + .esp = 0, + .ebp = 0, + .esi = 0 + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator32_boot (rel, state, 0); +} + +static grub_err_t +grub_plan9_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +/* Context for grub_cmd_plan9. */ +struct grub_cmd_plan9_ctx +{ + grub_extcmd_context_t ctxt; + grub_file_t file; + char *pmap; + grub_size_t pmapalloc; + grub_size_t pmapptr; + int noslash; + int prefixescnt[5]; + char *bootdisk, *bootpart; +}; + +static const char prefixes[5][10] = { + "dos", "plan9", "ntfs", "linux", "linuxswap" +}; + +#include <grub/err.h> + +static inline grub_err_t +grub_extend_alloc (grub_size_t sz, grub_size_t *allocated, char **ptr) +{ + void *n; + if (sz < *allocated) + return GRUB_ERR_NONE; + + *allocated = 2 * sz; + n = grub_realloc (*ptr, *allocated); + if (!n) + return grub_errno; + *ptr = n; + return GRUB_ERR_NONE; +} + +/* Helper for grub_cmd_plan9. */ +static int +fill_partition (grub_disk_t disk, const grub_partition_t partition, void *data) +{ + struct grub_cmd_plan9_ctx *fill_ctx = data; + int file_disk = 0; + int pstart, pend; + + if (!fill_ctx->noslash) + { + if (grub_extend_alloc (fill_ctx->pmapptr + 1, &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + return 1; + fill_ctx->pmap[fill_ctx->pmapptr++] = '/'; + } + fill_ctx->noslash = 0; + + file_disk = fill_ctx->file->device->disk + && disk->id == fill_ctx->file->device->disk->id + && disk->dev->id == fill_ctx->file->device->disk->dev->id; + + pstart = fill_ctx->pmapptr; + if (grub_strcmp (partition->partmap->name, "plan") == 0) + { + unsigned ptr = partition->index + sizeof ("part ") - 1; + grub_err_t err; + disk->partition = partition->parent; + do + { + if (grub_extend_alloc (fill_ctx->pmapptr + 1, &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + return 1; + err = grub_disk_read (disk, 1, ptr, 1, + fill_ctx->pmap + fill_ctx->pmapptr); + if (err) + { + disk->partition = 0; + return err; + } + ptr++; + fill_ctx->pmapptr++; + } + while (grub_isalpha (fill_ctx->pmap[fill_ctx->pmapptr - 1]) + || grub_isdigit (fill_ctx->pmap[fill_ctx->pmapptr - 1])); + fill_ctx->pmapptr--; + } + else + { + char name[50]; + int c = 0; + if (grub_strcmp (partition->partmap->name, "msdos") == 0) + { + switch (partition->msdostype) + { + case GRUB_PC_PARTITION_TYPE_PLAN9: + c = 1; + break; + case GRUB_PC_PARTITION_TYPE_NTFS: + c = 2; + break; + case GRUB_PC_PARTITION_TYPE_MINIX: + case GRUB_PC_PARTITION_TYPE_LINUX_MINIX: + case GRUB_PC_PARTITION_TYPE_EXT2FS: + c = 3; + break; + case GRUB_PC_PARTITION_TYPE_LINUX_SWAP: + c = 4; + break; + } + } + + if (fill_ctx->prefixescnt[c] == 0) + grub_strcpy (name, prefixes[c]); + else + grub_snprintf (name, sizeof (name), "%s.%d", prefixes[c], + fill_ctx->prefixescnt[c]); + fill_ctx->prefixescnt[c]++; + if (grub_extend_alloc (fill_ctx->pmapptr + grub_strlen (name) + 1, + &fill_ctx->pmapalloc, &fill_ctx->pmap)) + return 1; + grub_strcpy (fill_ctx->pmap + fill_ctx->pmapptr, name); + fill_ctx->pmapptr += grub_strlen (name); + } + pend = fill_ctx->pmapptr; + if (grub_extend_alloc (fill_ctx->pmapptr + 2 + 25 + 5 + 25, + &fill_ctx->pmapalloc, &fill_ctx->pmap)) + return 1; + fill_ctx->pmap[fill_ctx->pmapptr++] = ' '; + grub_snprintf (fill_ctx->pmap + fill_ctx->pmapptr, 25 + 5 + 25, + "%" PRIuGRUB_UINT64_T " %" PRIuGRUB_UINT64_T, + grub_partition_get_start (partition), + grub_partition_get_start (partition) + + grub_partition_get_len (partition)); + if (file_disk && grub_partition_get_start (partition) + == grub_partition_get_start (fill_ctx->file->device->disk->partition) + && grub_partition_get_len (partition) + == grub_partition_get_len (fill_ctx->file->device->disk->partition)) + { + grub_free (fill_ctx->bootpart); + fill_ctx->bootpart = grub_strndup (fill_ctx->pmap + pstart, + pend - pstart); + } + + fill_ctx->pmapptr += grub_strlen (fill_ctx->pmap + fill_ctx->pmapptr); + return 0; +} + +/* Helper for grub_cmd_plan9. */ +static int +fill_disk (const char *name, void *data) +{ + struct grub_cmd_plan9_ctx *fill_ctx = data; + grub_device_t dev; + char *plan9name = NULL; + unsigned i; + int file_disk = 0; + + dev = grub_device_open (name); + if (!dev) + { + grub_print_error (); + return 0; + } + if (!dev->disk) + { + grub_device_close (dev); + return 0; + } + file_disk = fill_ctx->file->device->disk + && dev->disk->id == fill_ctx->file->device->disk->id + && dev->disk->dev->id == fill_ctx->file->device->disk->dev->id; + for (i = 0; + fill_ctx->ctxt->state[0].args && fill_ctx->ctxt->state[0].args[i]; i++) + if (grub_strncmp (name, fill_ctx->ctxt->state[0].args[i], + grub_strlen (name)) == 0 + && fill_ctx->ctxt->state[0].args[i][grub_strlen (name)] == '=') + break; + if (fill_ctx->ctxt->state[0].args && fill_ctx->ctxt->state[0].args[i]) + plan9name = grub_strdup (fill_ctx->ctxt->state[0].args[i] + + grub_strlen (name) + 1); + else + switch (dev->disk->dev->id) + { + case GRUB_DISK_DEVICE_BIOSDISK_ID: + if (dev->disk->id & 0x80) + plan9name = grub_xasprintf ("sdB%u", + (unsigned) (dev->disk->id & 0x7f)); + else + plan9name = grub_xasprintf ("fd%u", + (unsigned) (dev->disk->id & 0x7f)); + break; + /* Shouldn't happen as Plan9 doesn't work on these platforms. */ + case GRUB_DISK_DEVICE_OFDISK_ID: + case GRUB_DISK_DEVICE_EFIDISK_ID: + + /* Plan9 doesn't see those. */ + default: + + /* Not sure how to handle those. */ + case GRUB_DISK_DEVICE_NAND_ID: + if (!file_disk) + { + grub_device_close (dev); + return 0; + } + + /* if it's the disk the kernel is loaded from we need to name + it nevertheless. */ + plan9name = grub_strdup ("sdZ0"); + break; + + case GRUB_DISK_DEVICE_ATA_ID: + { + unsigned unit; + if (grub_strlen (dev->disk->name) < sizeof ("ata0") - 1) + unit = 0; + else + unit = grub_strtoul (dev->disk->name + sizeof ("ata0") - 1, 0, 0); + plan9name = grub_xasprintf ("sd%c%d", 'C' + unit / 2, unit % 2); + } + break; + case GRUB_DISK_DEVICE_SCSI_ID: + if (((dev->disk->id >> GRUB_SCSI_ID_SUBSYSTEM_SHIFT) & 0xff) + == GRUB_SCSI_SUBSYSTEM_PATA) + { + unsigned unit; + if (grub_strlen (dev->disk->name) < sizeof ("ata0") - 1) + unit = 0; + else + unit = grub_strtoul (dev->disk->name + sizeof ("ata0") - 1, + 0, 0); + plan9name = grub_xasprintf ("sd%c%d", 'C' + unit / 2, unit % 2); + break; + } + + /* FIXME: how does Plan9 number controllers? + We probably need save the SCSI devices and sort them */ + plan9name + = grub_xasprintf ("sd0%u", (unsigned) + ((dev->disk->id >> GRUB_SCSI_ID_BUS_SHIFT) + & 0xf)); + break; + } + if (!plan9name) + { + grub_print_error (); + grub_device_close (dev); + return 0; + } + if (grub_extend_alloc (fill_ctx->pmapptr + grub_strlen (plan9name) + + sizeof ("part="), &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + { + grub_free (plan9name); + grub_device_close (dev); + return 1; + } + grub_strcpy (fill_ctx->pmap + fill_ctx->pmapptr, plan9name); + fill_ctx->pmapptr += grub_strlen (plan9name); + if (!file_disk) + grub_free (plan9name); + else + { + grub_free (fill_ctx->bootdisk); + fill_ctx->bootdisk = plan9name; + } + grub_strcpy (fill_ctx->pmap + fill_ctx->pmapptr, "part="); + fill_ctx->pmapptr += sizeof ("part=") - 1; + + fill_ctx->noslash = 1; + grub_memset (fill_ctx->prefixescnt, 0, sizeof (fill_ctx->prefixescnt)); + if (grub_partition_iterate (dev->disk, fill_partition, fill_ctx)) + { + grub_device_close (dev); + return 1; + } + if (grub_extend_alloc (fill_ctx->pmapptr + 1, &fill_ctx->pmapalloc, + &fill_ctx->pmap)) + { + grub_device_close (dev); + return 1; + } + fill_ctx->pmap[fill_ctx->pmapptr++] = '\n'; + + grub_device_close (dev); + return 0; +} + +static grub_err_t +grub_cmd_plan9 (grub_extcmd_context_t ctxt, int argc, char *argv[]) +{ + struct grub_cmd_plan9_ctx fill_ctx = { + .ctxt = ctxt, + .file = 0, + .pmap = NULL, + .pmapalloc = 256, + .pmapptr = 0, + .noslash = 1, + .bootdisk = NULL, + .bootpart = NULL + }; + void *mem; + grub_size_t memsize, padsize; + struct grub_plan9_header hdr; + char *config, *configptr; + grub_size_t configsize; + char *bootpath = NULL; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_dl_ref (my_mod); + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + fill_ctx.file = grub_file_open (argv[0], GRUB_FILE_TYPE_PLAN9_KERNEL); + if (! fill_ctx.file) + goto fail; + + fill_ctx.pmap = grub_malloc (fill_ctx.pmapalloc); + if (!fill_ctx.pmap) + goto fail; + + if (grub_disk_dev_iterate (fill_disk, &fill_ctx)) + goto fail; + + if (grub_extend_alloc (fill_ctx.pmapptr + 1, &fill_ctx.pmapalloc, + &fill_ctx.pmap)) + goto fail; + fill_ctx.pmap[fill_ctx.pmapptr] = 0; + + { + char *file_name = grub_strchr (argv[0], ')'); + if (file_name) + file_name++; + else + file_name = argv[0]; + if (*file_name) + file_name++; + + if (fill_ctx.bootpart) + bootpath = grub_xasprintf ("%s!%s!%s", fill_ctx.bootdisk, + fill_ctx.bootpart, file_name); + else + bootpath = grub_xasprintf ("%s!%s", fill_ctx.bootdisk, file_name); + grub_free (fill_ctx.bootdisk); + grub_free (fill_ctx.bootpart); + } + if (!bootpath) + goto fail; + + if (grub_file_read (fill_ctx.file, &hdr, + sizeof (hdr)) != (grub_ssize_t) sizeof (hdr)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + + if (grub_be_to_cpu32 (hdr.magic) != GRUB_PLAN9_MAGIC + || hdr.zero) + { + grub_error (GRUB_ERR_BAD_OS, "unsupported Plan9"); + goto fail; + } + + memsize = ALIGN_UP (grub_be_to_cpu32 (hdr.text_size) + sizeof (hdr), + GRUB_PLAN9_ALIGN); + memsize += ALIGN_UP (grub_be_to_cpu32 (hdr.data_size), GRUB_PLAN9_ALIGN); + memsize += ALIGN_UP(grub_be_to_cpu32 (hdr.bss_size), GRUB_PLAN9_ALIGN); + eip = grub_be_to_cpu32 (hdr.entry_addr) & 0xfffffff; + + /* path */ + configsize = GRUB_PLAN9_CONFIG_PATH_SIZE; + /* magic */ + configsize += sizeof (GRUB_PLAN9_CONFIG_MAGIC) - 1; + { + int i; + for (i = 1; i < argc; i++) + configsize += grub_strlen (argv[i]) + 1; + } + configsize += (sizeof ("bootfile=") - 1) + grub_strlen (bootpath) + 1; + configsize += fill_ctx.pmapptr; + /* Terminating \0. */ + configsize++; + + { + grub_relocator_chunk_t ch; + grub_err_t err; + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_PLAN9_CONFIG_ADDR, + configsize); + if (err) + goto fail; + config = get_virtual_current_address (ch); + } + + grub_memset (config, 0, GRUB_PLAN9_CONFIG_PATH_SIZE); + grub_strncpy (config, bootpath, GRUB_PLAN9_CONFIG_PATH_SIZE - 1); + + configptr = config + GRUB_PLAN9_CONFIG_PATH_SIZE; + grub_memcpy (configptr, GRUB_PLAN9_CONFIG_MAGIC, + sizeof (GRUB_PLAN9_CONFIG_MAGIC) - 1); + configptr += sizeof (GRUB_PLAN9_CONFIG_MAGIC) - 1; + configptr = grub_stpcpy (configptr, "bootfile="); + configptr = grub_stpcpy (configptr, bootpath); + *configptr++ = '\n'; + char *cmdline = configptr; + { + int i; + for (i = 1; i < argc; i++) + { + configptr = grub_stpcpy (configptr, argv[i]); + *configptr++ = '\n'; + } + } + + { + grub_err_t err; + *configptr = '\0'; + err = grub_verify_string (cmdline, GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + goto fail; + } + + configptr = grub_stpcpy (configptr, fill_ctx.pmap); + + { + grub_relocator_chunk_t ch; + grub_err_t err; + + err = grub_relocator_alloc_chunk_addr (rel, &ch, GRUB_PLAN9_TARGET, + memsize); + if (err) + goto fail; + mem = get_virtual_current_address (ch); + } + + { + grub_uint8_t *ptr; + ptr = mem; + grub_memcpy (ptr, &hdr, sizeof (hdr)); + ptr += sizeof (hdr); + + if (grub_file_read (fill_ctx.file, ptr, grub_be_to_cpu32 (hdr.text_size)) + != (grub_ssize_t) grub_be_to_cpu32 (hdr.text_size)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + ptr += grub_be_to_cpu32 (hdr.text_size); + padsize = ALIGN_UP (grub_be_to_cpu32 (hdr.text_size) + sizeof (hdr), + GRUB_PLAN9_ALIGN) - grub_be_to_cpu32 (hdr.text_size) + - sizeof (hdr); + + grub_memset (ptr, 0, padsize); + ptr += padsize; + + if (grub_file_read (fill_ctx.file, ptr, grub_be_to_cpu32 (hdr.data_size)) + != (grub_ssize_t) grub_be_to_cpu32 (hdr.data_size)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + argv[0]); + goto fail; + } + ptr += grub_be_to_cpu32 (hdr.data_size); + padsize = ALIGN_UP (grub_be_to_cpu32 (hdr.data_size), GRUB_PLAN9_ALIGN) + - grub_be_to_cpu32 (hdr.data_size); + + grub_memset (ptr, 0, padsize); + ptr += padsize; + grub_memset (ptr, 0, ALIGN_UP(grub_be_to_cpu32 (hdr.bss_size), + GRUB_PLAN9_ALIGN)); + } + grub_loader_set (grub_plan9_boot, grub_plan9_unload, 1); + return GRUB_ERR_NONE; + + fail: + grub_free (fill_ctx.pmap); + + if (fill_ctx.file) + grub_file_close (fill_ctx.file); + + grub_plan9_unload (); + + return grub_errno; +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT(plan9) +{ + cmd = grub_register_extcmd ("plan9", grub_cmd_plan9, + GRUB_COMMAND_OPTIONS_AT_START, + N_("KERNEL ARGS"), N_("Load Plan9 kernel."), + options); + my_mod = mod; +} + +GRUB_MOD_FINI(plan9) +{ + grub_unregister_extcmd (cmd); +} diff --git a/grub-core/loader/i386/pc/pxechainloader.c b/grub-core/loader/i386/pc/pxechainloader.c new file mode 100644 index 0000000..acb0611 --- /dev/null +++ b/grub-core/loader/i386/pc/pxechainloader.c @@ -0,0 +1,168 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010,2012 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/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/machine/biosnum.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/machine/pxe.h> +#include <grub/net.h> + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t edx = 0xffffffff; +static char boot_file[128]; +static char server_name[64]; + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_err_t +grub_pxechain_boot (void) +{ + struct grub_relocator16_state state = { + .cs = 0, + .ip = 0x7c00, + .ds = 0, + .es = 0, + .fs = 0, + .gs = 0, + .ss = 0, + .sp = 0x7c00, + .edx = edx + }; + struct grub_net_bootp_packet *bp; + + bp = grub_pxe_get_cached (GRUB_PXENV_PACKET_TYPE_DHCP_ACK); + + grub_video_set_mode ("text", 0, 0); + + if (bp && boot_file[0]) + grub_memcpy (bp->boot_file, boot_file, sizeof (bp->boot_file)); + if (bp && server_name[0]) + grub_memcpy (bp->server_name, server_name, sizeof (bp->server_name)); + + return grub_relocator16_boot (rel, state); +} + +static grub_err_t +grub_pxechain_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_pxechain (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + void *image; + grub_size_t imagesize; + char *fname; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + grub_dl_ref (my_mod); + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_PXECHAINLOADER); + if (! file) + goto fail; + + if (file->device->net && file->device->net->name) + fname = file->device->net->name; + else + { + fname = argv[0]; + if (fname[0] == '(') + { + fname = grub_strchr (fname, ')'); + if (fname) + fname++; + else + fname = argv[0]; + } + } + + grub_memset (boot_file, 0, sizeof (boot_file)); + grub_strncpy (boot_file, fname, sizeof (boot_file)); + + grub_memset (server_name, 0, sizeof (server_name)); + if (file->device->net && file->device->net->server) + grub_strncpy (server_name, file->device->net->server, sizeof (server_name)); + + edx = grub_get_root_biosnumber (); + + imagesize = grub_file_size (file); + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, 0x7c00, imagesize); + if (err) + goto fail; + image = get_virtual_current_address (ch); + } + + if (grub_file_read (file, image, imagesize) != (grub_ssize_t) imagesize) + goto fail; + + grub_loader_set (grub_pxechain_boot, grub_pxechain_unload, + GRUB_LOADER_FLAG_NORETURN | GRUB_LOADER_FLAG_PXE_NOT_UNLOAD); + return GRUB_ERR_NONE; + + fail: + + if (file) + grub_file_close (file); + + grub_pxechain_unload (); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(pxechainloader) +{ + cmd = grub_register_command ("pxechainloader", grub_cmd_pxechain, + 0, N_("Load a PXE image.")); + my_mod = mod; +} + +GRUB_MOD_FINI(pxechainloader) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/pc/truecrypt.c b/grub-core/loader/i386/pc/truecrypt.c new file mode 100644 index 0000000..cbeeec7 --- /dev/null +++ b/grub-core/loader/i386/pc/truecrypt.c @@ -0,0 +1,233 @@ +/* chainloader.c - boot another boot loader */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2004,2007,2009,2010 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/>. + */ + +#include <grub/loader.h> +#include <grub/file.h> +#include <grub/err.h> +#include <grub/device.h> +#include <grub/disk.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/partition.h> +#include <grub/dl.h> +#include <grub/command.h> +#include <grub/machine/biosnum.h> +#include <grub/i18n.h> +#include <grub/video.h> +#include <grub/mm.h> +#include <grub/cpu/relocator.h> +#include <grub/deflate.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static struct grub_relocator *rel; +static grub_uint32_t edx = 0xffffffff; +static grub_uint16_t sp; +static grub_uint32_t destaddr; + +#define GRUB_TRUECRYPT_SEGMENT 0x2000 + +static grub_err_t +grub_truecrypt_boot (void) +{ + grub_uint16_t segment = destaddr >> 4; + struct grub_relocator16_state state = { + .cs = segment, + .ds = segment, + .es = segment, + .fs = segment, + .gs = segment, + .ss = segment, + .ip = 0x100, + .sp = sp, + .edx = edx, + .a20 = 1 + }; + grub_video_set_mode ("text", 0, 0); + + return grub_relocator16_boot (rel, state); +} + +static grub_err_t +grub_truecrypt_unload (void) +{ + grub_relocator_unload (rel); + rel = NULL; + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +/* Information on protocol supplied by Attila Lendvai. */ +#define MAGIC "\0CD001\1EL TORITO SPECIFICATION" + +static grub_err_t +grub_cmd_truecrypt (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + grub_err_t err; + void *truecrypt; + grub_size_t truecryptsize; + const grub_size_t truecryptmemsize = 42 * 1024; + grub_uint8_t dh; + grub_uint32_t catalog, rba; + grub_uint8_t buf[128]; + char *compressed = NULL; + char *uncompressed = NULL; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + rel = NULL; + + grub_dl_ref (my_mod); + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_TRUECRYPT); + if (! file) + goto fail; + + if (grub_file_seek (file, 17 * 2048) == (grub_size_t) -1) + goto fail; + + if (grub_file_read (file, buf, sizeof (buf)) + != sizeof (buf)) + goto fail; + + if (grub_memcmp (buf, MAGIC, sizeof (MAGIC)) != 0) + { + grub_error (GRUB_ERR_BAD_OS, "invalid eltorito signature"); + goto fail; + } + + catalog = grub_get_unaligned32 (buf + 0x47); + + if (grub_file_seek (file, catalog * 2048) == (grub_size_t)-1) + goto fail; + + if (grub_file_read (file, buf, sizeof (buf)) + != sizeof (buf)) + goto fail; + + if (buf[0] != 1 || buf[1] != 0 || buf[0x1e] != 0x55 + || buf[0x1f] != 0xaa || buf[0x20] != 0x88 + || buf[0x26] != 1 || buf[0x27] != 0) + { + grub_error (GRUB_ERR_BAD_OS, "invalid eltorito catalog"); + goto fail; + } + + rba = grub_get_unaligned32 (buf + 0x28); + + if (grub_file_seek (file, rba * 2048 + 0x1b7) == (grub_size_t) -1) + goto fail; + + if (grub_file_read (file, &dh, 1) + != 1) + goto fail; + + if (grub_file_seek (file, rba * 2048 + 512 + 2048) == (grub_size_t) -1) + goto fail; + + compressed = grub_malloc (57 * 512); + if (!compressed) + goto fail; + + if (grub_file_read (file, compressed, 57 * 512) + != 57 * 512) + goto fail; + + uncompressed = grub_malloc (truecryptmemsize); + if (!uncompressed) + goto fail; + + /* It's actually gzip but our gzip decompressor isn't able to handle + trailing garbage, hence let's use raw decompressor. */ + truecryptsize = grub_deflate_decompress (compressed + 10, 57 * 512 - 10, + 0, uncompressed, truecryptmemsize); + if ((grub_ssize_t) truecryptsize < 0) + goto fail; + + if (truecryptmemsize <= truecryptsize + 0x100) + { + grub_error (GRUB_ERR_BAD_OS, "file is too big"); + goto fail; + } + + rel = grub_relocator_new (); + if (!rel) + goto fail; + + edx = (dh << 8) | grub_get_root_biosnumber (); + + destaddr = ALIGN_DOWN (grub_min (0x90000, grub_mmap_get_lower ()) + - truecryptmemsize, 64 * 1024); + + { + grub_relocator_chunk_t ch; + err = grub_relocator_alloc_chunk_addr (rel, &ch, destaddr, + truecryptmemsize); + if (err) + goto fail; + truecrypt = get_virtual_current_address (ch); + } + + grub_memset (truecrypt, 0, 0x100); + grub_memcpy ((char *) truecrypt + 0x100, uncompressed, truecryptsize); + + grub_memset ((char *) truecrypt + truecryptsize + 0x100, + 0, truecryptmemsize - truecryptsize - 0x100); + sp = truecryptmemsize - 4; + + grub_loader_set (grub_truecrypt_boot, grub_truecrypt_unload, 1); + + grub_free (uncompressed); + grub_free (compressed); + + return GRUB_ERR_NONE; + + fail: + + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, "bad truecrypt ISO"); + + if (file) + grub_file_close (file); + + grub_truecrypt_unload (); + + grub_free (uncompressed); + grub_free (compressed); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(truecrypt) +{ + cmd = grub_register_command ("truecrypt", grub_cmd_truecrypt, + 0, N_("Load Truecrypt ISO.")); + my_mod = mod; +} + +GRUB_MOD_FINI(truecrypt) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/loader/i386/xen.c b/grub-core/loader/i386/xen.c new file mode 100644 index 0000000..cd24874 --- /dev/null +++ b/grub-core/loader/i386/xen.c @@ -0,0 +1,986 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013 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/>. + */ + +#include <grub/loader.h> +#include <grub/memory.h> +#include <grub/normal.h> +#include <grub/file.h> +#include <grub/disk.h> +#include <grub/err.h> +#include <grub/misc.h> +#include <grub/types.h> +#include <grub/dl.h> +#include <grub/mm.h> +#include <grub/term.h> +#include <grub/cpu/linux.h> +#include <grub/video.h> +#include <grub/video_fb.h> +#include <grub/command.h> +#include <grub/xen/relocator.h> +#include <grub/i18n.h> +#include <grub/elf.h> +#include <grub/elfload.h> +#include <grub/lib/cmdline.h> +#include <grub/xen.h> +#include <grub/xen_file.h> +#include <grub/linux.h> +#include <grub/i386/memory.h> +#include <grub/verify.h> +#include <grub/safemath.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#ifdef __x86_64__ +#define NUMBER_OF_LEVELS 4 +#define INTERMEDIATE_OR (GRUB_PAGE_PRESENT | GRUB_PAGE_RW | GRUB_PAGE_USER) +#define VIRT_MASK 0x0000ffffffffffffULL +#else +#define NUMBER_OF_LEVELS 3 +#define INTERMEDIATE_OR (GRUB_PAGE_PRESENT | GRUB_PAGE_RW) +#define VIRT_MASK 0x00000000ffffffffULL +#define HYPERVISOR_PUD_ADDRESS 0xc0000000ULL +#endif + +struct grub_xen_mapping_lvl { + grub_uint64_t virt_start; + grub_uint64_t virt_end; + grub_uint64_t pfn_start; + grub_uint64_t n_pt_pages; +}; + +struct grub_xen_mapping { + grub_uint64_t *where; + struct grub_xen_mapping_lvl area; + struct grub_xen_mapping_lvl lvls[NUMBER_OF_LEVELS]; +}; + +struct xen_loader_state { + struct grub_relocator *relocator; + struct grub_relocator_xen_state state; + struct start_info next_start; + struct grub_xen_file_info xen_inf; + grub_xen_mfn_t *virt_mfn_list; + struct start_info *virt_start_info; + grub_xen_mfn_t console_pfn; + grub_uint64_t max_addr; + grub_uint64_t pgtbl_end; + struct xen_multiboot_mod_list *module_info_page; + grub_uint64_t modules_target_start; + grub_size_t n_modules; + struct grub_xen_mapping *map_reloc; + struct grub_xen_mapping mappings[XEN_MAX_MAPPINGS]; + int n_mappings; + int loaded; +}; + +static struct xen_loader_state xen_state; + +static grub_dl_t my_mod; + +#define PAGE_SIZE (1UL << PAGE_SHIFT) +#define MAX_MODULES (PAGE_SIZE / sizeof (struct xen_multiboot_mod_list)) +#define STACK_SIZE 1048576 +#define ADDITIONAL_SIZE (1 << 19) +#define ALIGN_SIZE (1 << 22) +#define LOG_POINTERS_PER_PAGE 9 +#define POINTERS_PER_PAGE (1 << LOG_POINTERS_PER_PAGE) + +static grub_uint64_t +page2offset (grub_uint64_t page) +{ + return page << PAGE_SHIFT; +} + +static grub_err_t +get_pgtable_size (grub_uint64_t from, grub_uint64_t to, grub_uint64_t pfn) +{ + struct grub_xen_mapping *map, *map_cmp; + grub_uint64_t mask, bits; + int i, m; + + if (xen_state.n_mappings == XEN_MAX_MAPPINGS) + return grub_error (GRUB_ERR_BUG, "too many mapped areas"); + + grub_dprintf ("xen", "get_pgtable_size %d from=%llx, to=%llx, pfn=%llx\n", + xen_state.n_mappings, (unsigned long long) from, + (unsigned long long) to, (unsigned long long) pfn); + + map = xen_state.mappings + xen_state.n_mappings; + grub_memset (map, 0, sizeof (*map)); + + map->area.virt_start = from & VIRT_MASK; + map->area.virt_end = (to - 1) & VIRT_MASK; + map->area.n_pt_pages = 0; + + for (i = NUMBER_OF_LEVELS - 1; i >= 0; i--) + { + map->lvls[i].pfn_start = pfn + map->area.n_pt_pages; + if (i == NUMBER_OF_LEVELS - 1) + { + if (xen_state.n_mappings == 0) + { + map->lvls[i].virt_start = 0; + map->lvls[i].virt_end = VIRT_MASK; + map->lvls[i].n_pt_pages = 1; + map->area.n_pt_pages++; + } + continue; + } + + bits = PAGE_SHIFT + (i + 1) * LOG_POINTERS_PER_PAGE; + mask = (1ULL << bits) - 1; + map->lvls[i].virt_start = map->area.virt_start & ~mask; + map->lvls[i].virt_end = map->area.virt_end | mask; +#ifdef __i386__ + /* PAE wants last root directory present. */ + if (i == 1 && to <= HYPERVISOR_PUD_ADDRESS && xen_state.n_mappings == 0) + map->lvls[i].virt_end = VIRT_MASK; +#endif + for (m = 0; m < xen_state.n_mappings; m++) + { + map_cmp = xen_state.mappings + m; + if (map_cmp->lvls[i].virt_start == map_cmp->lvls[i].virt_end) + continue; + if (map->lvls[i].virt_start >= map_cmp->lvls[i].virt_start && + map->lvls[i].virt_end <= map_cmp->lvls[i].virt_end) + { + map->lvls[i].virt_start = 0; + map->lvls[i].virt_end = 0; + break; + } + if (map->lvls[i].virt_start >= map_cmp->lvls[i].virt_start && + map->lvls[i].virt_start <= map_cmp->lvls[i].virt_end) + map->lvls[i].virt_start = map_cmp->lvls[i].virt_end + 1; + if (map->lvls[i].virt_end >= map_cmp->lvls[i].virt_start && + map->lvls[i].virt_end <= map_cmp->lvls[i].virt_end) + map->lvls[i].virt_end = map_cmp->lvls[i].virt_start - 1; + } + if (map->lvls[i].virt_start < map->lvls[i].virt_end) + map->lvls[i].n_pt_pages = + ((map->lvls[i].virt_end - map->lvls[i].virt_start) >> bits) + 1; + map->area.n_pt_pages += map->lvls[i].n_pt_pages; + grub_dprintf ("xen", "get_pgtable_size level %d: virt %llx-%llx %d pts\n", + i, (unsigned long long) map->lvls[i].virt_start, + (unsigned long long) map->lvls[i].virt_end, + (int) map->lvls[i].n_pt_pages); + } + + grub_dprintf ("xen", "get_pgtable_size return: %d page tables\n", + (int) map->area.n_pt_pages); + + xen_state.state.paging_start[xen_state.n_mappings] = pfn; + xen_state.state.paging_size[xen_state.n_mappings] = map->area.n_pt_pages; + + return GRUB_ERR_NONE; +} + +static grub_uint64_t * +get_pg_table_virt (int mapping, int level) +{ + grub_uint64_t pfn; + struct grub_xen_mapping *map; + + map = xen_state.mappings + mapping; + pfn = map->lvls[level].pfn_start - map->lvls[NUMBER_OF_LEVELS - 1].pfn_start; + return map->where + pfn * POINTERS_PER_PAGE; +} + +static grub_uint64_t +get_pg_table_prot (int level, grub_uint64_t pfn) +{ + int m; + grub_uint64_t pfn_s, pfn_e; + + if (level > 0) + return INTERMEDIATE_OR; + for (m = 0; m < xen_state.n_mappings; m++) + { + pfn_s = xen_state.mappings[m].lvls[NUMBER_OF_LEVELS - 1].pfn_start; + pfn_e = xen_state.mappings[m].area.n_pt_pages + pfn_s; + if (pfn >= pfn_s && pfn < pfn_e) + return GRUB_PAGE_PRESENT | GRUB_PAGE_USER; + } + return GRUB_PAGE_PRESENT | GRUB_PAGE_RW | GRUB_PAGE_USER; +} + +static void +generate_page_table (grub_xen_mfn_t *mfn_list) +{ + int l, m1, m2; + long p, p_s, p_e; + grub_uint64_t start, end, pfn; + grub_uint64_t *pg; + struct grub_xen_mapping_lvl *lvl; + + for (m1 = 0; m1 < xen_state.n_mappings; m1++) + grub_memset (xen_state.mappings[m1].where, 0, + xen_state.mappings[m1].area.n_pt_pages * PAGE_SIZE); + + for (l = NUMBER_OF_LEVELS - 1; l >= 0; l--) + { + for (m1 = 0; m1 < xen_state.n_mappings; m1++) + { + start = xen_state.mappings[m1].lvls[l].virt_start; + end = xen_state.mappings[m1].lvls[l].virt_end; + pg = get_pg_table_virt(m1, l); + for (m2 = 0; m2 < xen_state.n_mappings; m2++) + { + lvl = (l > 0) ? xen_state.mappings[m2].lvls + l - 1 + : &xen_state.mappings[m2].area; + if (l > 0 && lvl->n_pt_pages == 0) + continue; + if (lvl->virt_start >= end || lvl->virt_end <= start) + continue; + p_s = (grub_max (start, lvl->virt_start) - start) >> + (PAGE_SHIFT + l * LOG_POINTERS_PER_PAGE); + p_e = (grub_min (end, lvl->virt_end) - start) >> + (PAGE_SHIFT + l * LOG_POINTERS_PER_PAGE); + pfn = ((grub_max (start, lvl->virt_start) - lvl->virt_start) >> + (PAGE_SHIFT + l * LOG_POINTERS_PER_PAGE)) + lvl->pfn_start; + grub_dprintf ("xen", "write page table entries level %d pg %p " + "mapping %d/%d index %lx-%lx pfn %llx\n", + l, pg, m1, m2, p_s, p_e, (unsigned long long) pfn); + for (p = p_s; p <= p_e; p++) + { + pg[p] = page2offset (mfn_list[pfn]) | + get_pg_table_prot (l, pfn); + pfn++; + } + } + } + } +} + +static grub_err_t +set_mfns (grub_xen_mfn_t pfn) +{ + grub_xen_mfn_t i, t; + grub_xen_mfn_t cn_pfn = -1, st_pfn = -1; + struct mmu_update m2p_updates[4]; + + + for (i = 0; i < grub_xen_start_page_addr->nr_pages; i++) + { + if (xen_state.virt_mfn_list[i] == + grub_xen_start_page_addr->console.domU.mfn) + cn_pfn = i; + if (xen_state.virt_mfn_list[i] == grub_xen_start_page_addr->store_mfn) + st_pfn = i; + } + if (cn_pfn == (grub_xen_mfn_t)-1) + return grub_error (GRUB_ERR_BUG, "no console"); + if (st_pfn == (grub_xen_mfn_t)-1) + return grub_error (GRUB_ERR_BUG, "no store"); + t = xen_state.virt_mfn_list[pfn]; + xen_state.virt_mfn_list[pfn] = xen_state.virt_mfn_list[cn_pfn]; + xen_state.virt_mfn_list[cn_pfn] = t; + t = xen_state.virt_mfn_list[pfn + 1]; + xen_state.virt_mfn_list[pfn + 1] = xen_state.virt_mfn_list[st_pfn]; + xen_state.virt_mfn_list[st_pfn] = t; + + m2p_updates[0].ptr = + page2offset (xen_state.virt_mfn_list[pfn]) | MMU_MACHPHYS_UPDATE; + m2p_updates[0].val = pfn; + m2p_updates[1].ptr = + page2offset (xen_state.virt_mfn_list[pfn + 1]) | MMU_MACHPHYS_UPDATE; + m2p_updates[1].val = pfn + 1; + m2p_updates[2].ptr = + page2offset (xen_state.virt_mfn_list[cn_pfn]) | MMU_MACHPHYS_UPDATE; + m2p_updates[2].val = cn_pfn; + m2p_updates[3].ptr = + page2offset (xen_state.virt_mfn_list[st_pfn]) | MMU_MACHPHYS_UPDATE; + m2p_updates[3].val = st_pfn; + + grub_xen_mmu_update (m2p_updates, 4, NULL, DOMID_SELF); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_xen_p2m_alloc (void) +{ + grub_relocator_chunk_t ch; + grub_size_t p2msize, p2malloc; + grub_err_t err; + struct grub_xen_mapping *map; + + if (xen_state.virt_mfn_list) + return GRUB_ERR_NONE; + + map = xen_state.mappings + xen_state.n_mappings; + p2msize = ALIGN_UP (sizeof (grub_xen_mfn_t) * + grub_xen_start_page_addr->nr_pages, PAGE_SIZE); + if (xen_state.xen_inf.has_p2m_base) + { + err = get_pgtable_size (xen_state.xen_inf.p2m_base, + xen_state.xen_inf.p2m_base + p2msize, + (xen_state.max_addr + p2msize) >> PAGE_SHIFT); + if (err) + return err; + + map->area.pfn_start = xen_state.max_addr >> PAGE_SHIFT; + p2malloc = p2msize + page2offset (map->area.n_pt_pages); + xen_state.n_mappings++; + xen_state.next_start.mfn_list = xen_state.xen_inf.p2m_base; + xen_state.next_start.first_p2m_pfn = map->area.pfn_start; + xen_state.next_start.nr_p2m_frames = p2malloc >> PAGE_SHIFT; + } + else + { + xen_state.next_start.mfn_list = + xen_state.max_addr + xen_state.xen_inf.virt_base; + p2malloc = p2msize; + } + + xen_state.state.mfn_list = xen_state.max_addr; + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, p2malloc); + if (err) + return err; + xen_state.virt_mfn_list = get_virtual_current_address (ch); + if (xen_state.xen_inf.has_p2m_base) + map->where = (grub_uint64_t *) xen_state.virt_mfn_list + + p2msize / sizeof (grub_uint64_t); + grub_memcpy (xen_state.virt_mfn_list, + (void *) grub_xen_start_page_addr->mfn_list, p2msize); + xen_state.max_addr += p2malloc; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_xen_special_alloc (void) +{ + grub_relocator_chunk_t ch; + grub_err_t err; + + if (xen_state.virt_start_info) + return GRUB_ERR_NONE; + + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, + sizeof (xen_state.next_start)); + if (err) + return err; + xen_state.state.start_info = xen_state.max_addr + xen_state.xen_inf.virt_base; + xen_state.virt_start_info = get_virtual_current_address (ch); + xen_state.max_addr = + ALIGN_UP (xen_state.max_addr + sizeof (xen_state.next_start), PAGE_SIZE); + xen_state.console_pfn = xen_state.max_addr >> PAGE_SHIFT; + xen_state.max_addr += 2 * PAGE_SIZE; + + xen_state.next_start.nr_pages = grub_xen_start_page_addr->nr_pages; + grub_memcpy (xen_state.next_start.magic, grub_xen_start_page_addr->magic, + sizeof (xen_state.next_start.magic)); + xen_state.next_start.store_mfn = grub_xen_start_page_addr->store_mfn; + xen_state.next_start.store_evtchn = grub_xen_start_page_addr->store_evtchn; + xen_state.next_start.console.domU = grub_xen_start_page_addr->console.domU; + xen_state.next_start.shared_info = grub_xen_start_page_addr->shared_info; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_xen_pt_alloc (void) +{ + grub_relocator_chunk_t ch; + grub_err_t err; + grub_uint64_t nr_info_pages; + grub_uint64_t nr_need_pages; + grub_uint64_t try_virt_end; + struct grub_xen_mapping *map; + + if (xen_state.pgtbl_end) + return GRUB_ERR_NONE; + + map = xen_state.mappings + xen_state.n_mappings; + xen_state.map_reloc = map + 1; + + xen_state.next_start.pt_base = + xen_state.max_addr + xen_state.xen_inf.virt_base; + nr_info_pages = xen_state.max_addr >> PAGE_SHIFT; + nr_need_pages = nr_info_pages; + + while (1) + { + try_virt_end = ALIGN_UP (xen_state.xen_inf.virt_base + + page2offset (nr_need_pages) + + ADDITIONAL_SIZE + STACK_SIZE, ALIGN_SIZE); + + err = get_pgtable_size (xen_state.xen_inf.virt_base, try_virt_end, + nr_info_pages); + if (err) + return err; + xen_state.n_mappings++; + + /* Map the relocator page either at virtual 0 or after end of area. */ + nr_need_pages = nr_info_pages + map->area.n_pt_pages; + if (xen_state.xen_inf.virt_base) + err = get_pgtable_size (0, PAGE_SIZE, nr_need_pages); + else + err = get_pgtable_size (try_virt_end, try_virt_end + PAGE_SIZE, + nr_need_pages); + if (err) + return err; + nr_need_pages += xen_state.map_reloc->area.n_pt_pages; + + if (xen_state.xen_inf.virt_base + page2offset (nr_need_pages) <= + try_virt_end) + break; + + xen_state.n_mappings--; + } + + xen_state.n_mappings++; + nr_need_pages = map->area.n_pt_pages + xen_state.map_reloc->area.n_pt_pages; + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, + page2offset (nr_need_pages)); + if (err) + return err; + + map->where = get_virtual_current_address (ch); + map->area.pfn_start = 0; + xen_state.max_addr += page2offset (nr_need_pages); + xen_state.state.stack = + xen_state.max_addr + STACK_SIZE + xen_state.xen_inf.virt_base; + xen_state.next_start.nr_pt_frames = nr_need_pages; + xen_state.max_addr = try_virt_end - xen_state.xen_inf.virt_base; + xen_state.pgtbl_end = xen_state.max_addr >> PAGE_SHIFT; + xen_state.map_reloc->where = (grub_uint64_t *) ((char *) map->where + + page2offset (map->area.n_pt_pages)); + + return GRUB_ERR_NONE; +} + +/* Allocate all not yet allocated areas mapped by initial page tables. */ +static grub_err_t +grub_xen_alloc_boot_data (void) +{ + grub_err_t err; + + if (!xen_state.xen_inf.has_p2m_base) + { + err = grub_xen_p2m_alloc (); + if (err) + return err; + } + err = grub_xen_special_alloc (); + if (err) + return err; + err = grub_xen_pt_alloc (); + if (err) + return err; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_xen_boot (void) +{ + grub_err_t err; + grub_uint64_t nr_pages; + struct gnttab_set_version gnttab_setver; + grub_size_t i; + + if (grub_xen_n_allocated_shared_pages) + return grub_error (GRUB_ERR_BUG, "active grants"); + + err = grub_xen_alloc_boot_data (); + if (err) + return err; + if (xen_state.xen_inf.has_p2m_base) + { + err = grub_xen_p2m_alloc (); + if (err) + return err; + } + + err = set_mfns (xen_state.console_pfn); + if (err) + return err; + + nr_pages = xen_state.max_addr >> PAGE_SHIFT; + + grub_dprintf ("xen", "bootstrap domain %llx+%llx\n", + (unsigned long long) xen_state.xen_inf.virt_base, + (unsigned long long) page2offset (nr_pages)); + + xen_state.map_reloc->area.pfn_start = nr_pages; + generate_page_table (xen_state.virt_mfn_list); + + xen_state.state.entry_point = xen_state.xen_inf.entry_point; + + *xen_state.virt_start_info = xen_state.next_start; + + grub_memset (&gnttab_setver, 0, sizeof (gnttab_setver)); + + gnttab_setver.version = 1; + grub_xen_grant_table_op (GNTTABOP_set_version, &gnttab_setver, 1); + + for (i = 0; i < ARRAY_SIZE (grub_xen_shared_info->evtchn_pending); i++) + grub_xen_shared_info->evtchn_pending[i] = 0; + + return grub_relocator_xen_boot (xen_state.relocator, xen_state.state, nr_pages, + xen_state.xen_inf.virt_base < + PAGE_SIZE ? page2offset (nr_pages) : 0, + xen_state.pgtbl_end - 1, + page2offset (xen_state.pgtbl_end - 1) + + xen_state.xen_inf.virt_base); +} + +static void +grub_xen_reset (void) +{ + grub_relocator_unload (xen_state.relocator); + + grub_memset (&xen_state, 0, sizeof (xen_state)); +} + +static grub_err_t +grub_xen_unload (void) +{ + grub_xen_reset (); + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +#define HYPERCALL_INTERFACE_SIZE 32 + +#ifdef __x86_64__ +static grub_uint8_t template[] = + { + 0x51, /* push %rcx */ + 0x41, 0x53, /* push %r11 */ + 0x48, 0xc7, 0xc0, 0xbb, 0xaa, 0x00, 0x00, /* mov $0xaabb,%rax */ + 0x0f, 0x05, /* syscall */ + 0x41, 0x5b, /* pop %r11 */ + 0x59, /* pop %rcx */ + 0xc3 /* ret */ + }; + +static grub_uint8_t template_iret[] = + { + 0x51, /* push %rcx */ + 0x41, 0x53, /* push %r11 */ + 0x50, /* push %rax */ + 0x48, 0xc7, 0xc0, 0x17, 0x00, 0x00, 0x00, /* mov $0x17,%rax */ + 0x0f, 0x05 /* syscall */ + }; +#define CALLNO_OFFSET 6 +#else + +static grub_uint8_t template[] = + { + 0xb8, 0xbb, 0xaa, 0x00, 0x00, /* mov imm32, %eax */ + 0xcd, 0x82, /* int $0x82 */ + 0xc3 /* ret */ + }; + +static grub_uint8_t template_iret[] = + { + 0x50, /* push %eax */ + 0xb8, 0x17, 0x00, 0x00, 0x00, /* mov $0x17,%eax */ + 0xcd, 0x82, /* int $0x82 */ + }; +#define CALLNO_OFFSET 1 + +#endif + + +static void +set_hypercall_interface (grub_uint8_t *tgt, unsigned callno) +{ + if (callno == 0x17) + { + grub_memcpy (tgt, template_iret, ARRAY_SIZE (template_iret)); + grub_memset (tgt + ARRAY_SIZE (template_iret), 0xcc, + HYPERCALL_INTERFACE_SIZE - ARRAY_SIZE (template_iret)); + return; + } + grub_memcpy (tgt, template, ARRAY_SIZE (template)); + grub_memset (tgt + ARRAY_SIZE (template), 0xcc, + HYPERCALL_INTERFACE_SIZE - ARRAY_SIZE (template)); + tgt[CALLNO_OFFSET] = callno & 0xff; + tgt[CALLNO_OFFSET + 1] = callno >> 8; +} + +#ifdef __x86_64__ +#define grub_elfXX_load grub_elf64_load +#else +#define grub_elfXX_load grub_elf32_load +#endif + +static grub_err_t +grub_cmd_xen (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file; + grub_elf_t elf; + grub_err_t err; + void *kern_chunk_src; + grub_relocator_chunk_t ch; + grub_addr_t kern_start; + grub_addr_t kern_end; + grub_size_t sz; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + /* Call grub_loader_unset early to avoid it being called by grub_loader_set */ + grub_loader_unset (); + + grub_xen_reset (); + + err = grub_create_loader_cmdline (argc - 1, argv + 1, + (char *) xen_state.next_start.cmd_line, + sizeof (xen_state.next_start.cmd_line) - 1, + GRUB_VERIFY_KERNEL_CMDLINE); + if (err) + return err; + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL); + if (!file) + return grub_errno; + + elf = grub_xen_file (file); + if (!elf) + goto fail; + + err = grub_xen_get_info (elf, &xen_state.xen_inf); + if (err) + goto fail; +#ifdef __x86_64__ + if (xen_state.xen_inf.arch != GRUB_XEN_FILE_X86_64) +#else + if (xen_state.xen_inf.arch != GRUB_XEN_FILE_I386_PAE + && xen_state.xen_inf.arch != GRUB_XEN_FILE_I386_PAE_BIMODE) +#endif + { + grub_error (GRUB_ERR_BAD_OS, "incompatible architecture: %d", + xen_state.xen_inf.arch); + goto fail; + } + + if (xen_state.xen_inf.virt_base & (PAGE_SIZE - 1)) + { + grub_error (GRUB_ERR_BAD_OS, "unaligned virt_base"); + goto fail; + } + grub_dprintf ("xen", "virt_base = %llx, entry = %llx\n", + (unsigned long long) xen_state.xen_inf.virt_base, + (unsigned long long) xen_state.xen_inf.entry_point); + + xen_state.relocator = grub_relocator_new (); + if (!xen_state.relocator) + goto fail; + + kern_start = xen_state.xen_inf.kern_start - xen_state.xen_inf.paddr_offset; + kern_end = xen_state.xen_inf.kern_end - xen_state.xen_inf.paddr_offset; + + if (xen_state.xen_inf.has_hypercall_page) + { + grub_dprintf ("xen", "hypercall page at 0x%llx\n", + (unsigned long long) xen_state.xen_inf.hypercall_page); + kern_start = grub_min (kern_start, xen_state.xen_inf.hypercall_page - + xen_state.xen_inf.virt_base); + kern_end = grub_max (kern_end, xen_state.xen_inf.hypercall_page - + xen_state.xen_inf.virt_base + PAGE_SIZE); + } + + xen_state.max_addr = ALIGN_UP (kern_end, PAGE_SIZE); + + + if (grub_sub (kern_end, kern_start, &sz)) + { + err = GRUB_ERR_OUT_OF_RANGE; + goto fail; + } + + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, kern_start, sz); + if (err) + goto fail; + kern_chunk_src = get_virtual_current_address (ch); + + grub_dprintf ("xen", "paddr_offset = 0x%llx\n", + (unsigned long long) xen_state.xen_inf.paddr_offset); + grub_dprintf ("xen", "kern_start = 0x%llx, kern_end = 0x%llx\n", + (unsigned long long) xen_state.xen_inf.kern_start, + (unsigned long long) xen_state.xen_inf.kern_end); + + err = grub_elfXX_load (elf, argv[0], + (grub_uint8_t *) kern_chunk_src - kern_start + - xen_state.xen_inf.paddr_offset, 0, 0, 0); + + if (xen_state.xen_inf.has_hypercall_page) + { + unsigned i; + for (i = 0; i < PAGE_SIZE / HYPERCALL_INTERFACE_SIZE; i++) + set_hypercall_interface ((grub_uint8_t *) kern_chunk_src + + i * HYPERCALL_INTERFACE_SIZE + + xen_state.xen_inf.hypercall_page - + xen_state.xen_inf.virt_base - kern_start, i); + } + + if (err) + goto fail; + + grub_dl_ref (my_mod); + xen_state.loaded = 1; + + grub_loader_set (grub_xen_boot, grub_xen_unload, 0); + + goto fail; + +fail: + /* grub_errno might be clobbered by further calls, save the error reason. */ + err = grub_errno; + + if (elf) + grub_elf_close (elf); + else if (file) + grub_file_close (file); + + if (err != GRUB_ERR_NONE) + grub_xen_reset (); + + return err; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_size_t size = 0; + grub_err_t err; + struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 }; + grub_relocator_chunk_t ch; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + if (!xen_state.loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + goto fail; + } + + if (xen_state.next_start.mod_start || xen_state.next_start.mod_len) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("initrd already loaded")); + goto fail; + } + + if (xen_state.xen_inf.unmapped_initrd) + { + err = grub_xen_alloc_boot_data (); + if (err) + goto fail; + } + + if (grub_initrd_init (argc, argv, &initrd_ctx)) + goto fail; + + size = grub_get_initrd_size (&initrd_ctx); + + if (size) + { + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, size); + if (err) + goto fail; + + if (grub_initrd_load (&initrd_ctx, argv, + get_virtual_current_address (ch))) + goto fail; + } + + xen_state.next_start.mod_len = size; + + if (xen_state.xen_inf.unmapped_initrd) + { + xen_state.next_start.flags |= SIF_MOD_START_PFN; + xen_state.next_start.mod_start = xen_state.max_addr >> PAGE_SHIFT; + } + else + xen_state.next_start.mod_start = + xen_state.max_addr + xen_state.xen_inf.virt_base; + + grub_dprintf ("xen", "Initrd, addr=0x%x, size=0x%x\n", + (unsigned) (xen_state.max_addr + xen_state.xen_inf.virt_base), + (unsigned) size); + + xen_state.max_addr = ALIGN_UP (xen_state.max_addr + size, PAGE_SIZE); + +fail: + grub_initrd_close (&initrd_ctx); + + return grub_errno; +} + +static grub_err_t +grub_cmd_module (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_size_t size = 0; + grub_err_t err; + grub_relocator_chunk_t ch; + grub_size_t cmdline_len; + int nounzip = 0; + grub_file_t file; + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + if (grub_strcmp (argv[0], "--nounzip") == 0) + { + argv++; + argc--; + nounzip = 1; + } + + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + if (!xen_state.loaded) + { + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("you need to load the kernel first")); + } + + if ((xen_state.next_start.mod_start || xen_state.next_start.mod_len) && + !xen_state.module_info_page) + { + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("initrd already loaded")); + } + + /* Leave one space for terminator. */ + if (xen_state.n_modules >= MAX_MODULES - 1) + { + return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many modules"); + } + + if (!xen_state.module_info_page) + { + xen_state.xen_inf.unmapped_initrd = 0; + xen_state.n_modules = 0; + xen_state.max_addr = ALIGN_UP (xen_state.max_addr, PAGE_SIZE); + xen_state.modules_target_start = xen_state.max_addr; + xen_state.next_start.mod_start = + xen_state.max_addr + xen_state.xen_inf.virt_base; + xen_state.next_start.flags |= SIF_MULTIBOOT_MOD; + + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, MAX_MODULES + * + sizeof (xen_state.module_info_page + [0])); + if (err) + return err; + xen_state.module_info_page = get_virtual_current_address (ch); + grub_memset (xen_state.module_info_page, 0, MAX_MODULES + * sizeof (xen_state.module_info_page[0])); + xen_state.max_addr += + MAX_MODULES * sizeof (xen_state.module_info_page[0]); + } + + xen_state.max_addr = ALIGN_UP (xen_state.max_addr, PAGE_SIZE); + + file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_INITRD | + (nounzip ? GRUB_FILE_TYPE_NO_DECOMPRESS : GRUB_FILE_TYPE_NONE)); + if (!file) + return grub_errno; + size = grub_file_size (file); + + cmdline_len = grub_loader_cmdline_size (argc - 1, argv + 1); + + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, cmdline_len); + if (err) + goto fail; + + err = grub_create_loader_cmdline (argc - 1, argv + 1, + get_virtual_current_address (ch), cmdline_len, + GRUB_VERIFY_MODULE_CMDLINE); + if (err) + goto fail; + + xen_state.module_info_page[xen_state.n_modules].cmdline = + xen_state.max_addr - xen_state.modules_target_start; + xen_state.max_addr = ALIGN_UP (xen_state.max_addr + cmdline_len, PAGE_SIZE); + + if (size) + { + err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, + xen_state.max_addr, size); + if (err) + goto fail; + if (grub_file_read (file, get_virtual_current_address (ch), size) + != (grub_ssize_t) size) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, + N_("premature end of file %s"), argv[0]); + goto fail; + } + } + xen_state.next_start.mod_len = + xen_state.max_addr + size - xen_state.modules_target_start; + xen_state.module_info_page[xen_state.n_modules].mod_start = + xen_state.max_addr - xen_state.modules_target_start; + xen_state.module_info_page[xen_state.n_modules].mod_end = + xen_state.max_addr + size - xen_state.modules_target_start; + + xen_state.n_modules++; + grub_dprintf ("xen", "module, addr=0x%x, size=0x%x\n", + (unsigned) xen_state.max_addr, (unsigned) size); + xen_state.max_addr = ALIGN_UP (xen_state.max_addr + size, PAGE_SIZE); + + +fail: + grub_file_close (file); + + return grub_errno; +} + +static grub_command_t cmd_xen, cmd_initrd, cmd_module, cmd_multiboot; + +GRUB_MOD_INIT (xen) +{ + cmd_xen = grub_register_command ("linux", grub_cmd_xen, + 0, N_("Load Linux.")); + cmd_multiboot = grub_register_command ("multiboot", grub_cmd_xen, + 0, N_("Load Linux.")); + cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, + 0, N_("Load initrd.")); + cmd_module = grub_register_command ("module", grub_cmd_module, + 0, N_("Load module.")); + my_mod = mod; +} + +GRUB_MOD_FINI (xen) +{ + grub_unregister_command (cmd_xen); + grub_unregister_command (cmd_initrd); + grub_unregister_command (cmd_multiboot); + grub_unregister_command (cmd_module); +} diff --git a/grub-core/loader/i386/xen_file.c b/grub-core/loader/i386/xen_file.c new file mode 100644 index 0000000..9af5d66 --- /dev/null +++ b/grub-core/loader/i386/xen_file.c @@ -0,0 +1,117 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013 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/>. + */ + +#include <grub/xen_file.h> +#include <grub/i386/linux.h> +#include <grub/misc.h> + +#define XZ_MAGIC "\3757zXZ\0" + +grub_elf_t +grub_xen_file (grub_file_t file) +{ + grub_elf_t elf; + struct linux_i386_kernel_header lh; + grub_file_t off_file; + grub_uint32_t payload_offset, payload_length; + grub_uint8_t magic[6]; + + elf = grub_elf_file (file, file->name); + if (elf) + return elf; + grub_errno = GRUB_ERR_NONE; + + if (grub_file_seek (file, 0) == (grub_off_t) -1) + goto fail; + + if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + goto fail; + + if (lh.boot_flag != grub_cpu_to_le16_compile_time (0xaa55) + || lh.header != grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE) + || grub_le_to_cpu16 (lh.version) < 0x0208) + { + grub_error (GRUB_ERR_BAD_OS, "version too old for xen boot"); + return NULL; + } + + payload_length = lh.payload_length; + payload_offset = (lh.setup_sects + 1) * 512 + + lh.payload_offset; + + if (payload_length < sizeof (magic)) + { + grub_error (GRUB_ERR_BAD_OS, "payload too short"); + return NULL; + } + + grub_dprintf ("xen", "found bzimage payload 0x%llx-0x%llx\n", + (unsigned long long) payload_offset, + (unsigned long long) lh.payload_length); + + grub_file_seek (file, payload_offset); + + if (grub_file_read (file, &magic, sizeof (magic)) != sizeof (magic)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + file->name); + goto fail; + } + + /* Kernel suffixes xz payload with their uncompressed size. + Trim it. */ + if (grub_memcmp (magic, XZ_MAGIC, sizeof (XZ_MAGIC) - 1) == 0) + payload_length -= 4; + off_file = grub_file_offset_open (file, GRUB_FILE_TYPE_LINUX_KERNEL, payload_offset, + payload_length); + if (!off_file) + goto fail; + + elf = grub_elf_file (off_file, file->name); + if (elf) + return elf; + grub_file_offset_close (off_file); + +fail: + grub_error (GRUB_ERR_BAD_OS, "not xen image"); + return NULL; +} + +grub_err_t +grub_xen_get_info (grub_elf_t elf, struct grub_xen_file_info * xi) +{ + grub_memset (xi, 0, sizeof (*xi)); + + if (grub_elf_is_elf64 (elf) + && elf->ehdr.ehdr64.e_machine + == grub_cpu_to_le16_compile_time (EM_X86_64) + && elf->ehdr.ehdr64.e_ident[EI_DATA] == ELFDATA2LSB) + { + xi->arch = GRUB_XEN_FILE_X86_64; + return grub_xen_get_info64 (elf, xi); + } + if (grub_elf_is_elf32 (elf) + && elf->ehdr.ehdr32.e_machine == grub_cpu_to_le16_compile_time (EM_386) + && elf->ehdr.ehdr32.e_ident[EI_DATA] == ELFDATA2LSB) + { + xi->arch = GRUB_XEN_FILE_I386; + return grub_xen_get_info32 (elf, xi); + } + return grub_error (GRUB_ERR_BAD_OS, "unknown ELF type"); +} diff --git a/grub-core/loader/i386/xen_file32.c b/grub-core/loader/i386/xen_file32.c new file mode 100644 index 0000000..340d445 --- /dev/null +++ b/grub-core/loader/i386/xen_file32.c @@ -0,0 +1,7 @@ +#define GRUB_TARGET_WORDSIZE 32 +#define XX 32 +#define grub_le_to_cpu_addr grub_le_to_cpu32 +#define ehdrXX ehdr32 +#define grub_xen_get_infoXX grub_xen_get_info32 +#define FOR_ELF_PHDRS FOR_ELF32_PHDRS +#include "xen_fileXX.c" diff --git a/grub-core/loader/i386/xen_file64.c b/grub-core/loader/i386/xen_file64.c new file mode 100644 index 0000000..c410493 --- /dev/null +++ b/grub-core/loader/i386/xen_file64.c @@ -0,0 +1,7 @@ +#define GRUB_TARGET_WORDSIZE 64 +#define XX 64 +#define grub_le_to_cpu_addr grub_le_to_cpu64 +#define ehdrXX ehdr64 +#define grub_xen_get_infoXX grub_xen_get_info64 +#define FOR_ELF_PHDRS FOR_ELF64_PHDRS +#include "xen_fileXX.c" diff --git a/grub-core/loader/i386/xen_fileXX.c b/grub-core/loader/i386/xen_fileXX.c new file mode 100644 index 0000000..27afcaa --- /dev/null +++ b/grub-core/loader/i386/xen_fileXX.c @@ -0,0 +1,395 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013 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/>. + */ + +#include <grub/xen_file.h> +#include <grub/misc.h> +#include <xen/elfnote.h> + +static grub_err_t +parse_xen_guest (grub_elf_t elf, struct grub_xen_file_info *xi, + grub_off_t off, grub_size_t sz) +{ + char *buf; + const char *ptr; + int has_paddr = 0; + + grub_errno = GRUB_ERR_NONE; + if (grub_file_seek (elf->file, off) == (grub_off_t) -1) + return grub_errno; + buf = grub_malloc (sz); + if (!buf) + return grub_errno; + + if (grub_file_read (elf->file, buf, sz) != (grub_ssize_t) sz) + { + if (grub_errno) + goto out; + grub_free (buf); + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + elf->file->name); + } + xi->has_xen_guest = 1; + for (ptr = buf; ptr && ptr - buf < (grub_ssize_t) sz; + ptr = grub_strchr (ptr, ','), (ptr ? ptr++ : 0)) + { + if (grub_strncmp (ptr, "PAE=no,", sizeof ("PAE=no,") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386; + continue; + } + + if (grub_strncmp (ptr, "PAE=yes,", sizeof ("PAE=yes,") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386_PAE; + continue; + } + + if (grub_strncmp (ptr, "PAE=yes[extended-cr3],", + sizeof ("PAE=yes[extended-cr3],") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386_PAE; + xi->extended_cr3 = 1; + continue; + } + + if (grub_strncmp (ptr, "PAE=bimodal,", sizeof ("PAE=bimodal,") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386_PAE_BIMODE; + continue; + } + + if (grub_strncmp (ptr, "PAE=bimodal[extended-cr3],", + sizeof ("PAE=bimodal[extended-cr3],") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386_PAE_BIMODE; + xi->extended_cr3 = 1; + continue; + } + + if (grub_strncmp (ptr, "PAE=yes,bimodal,", sizeof ("PAE=yes,bimodal,") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386_PAE_BIMODE; + continue; + } + + if (grub_strncmp (ptr, "PAE=yes[extended-cr3],bimodal,", + sizeof ("PAE=yes[extended-cr3],bimodal,") - 1) == 0) + { + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + continue; + xi->arch = GRUB_XEN_FILE_I386_PAE_BIMODE; + xi->extended_cr3 = 1; + continue; + } + + if (grub_strncmp (ptr, "VIRT_BASE=", sizeof ("VIRT_BASE=") - 1) == 0) + { + xi->virt_base = grub_strtoull (ptr + sizeof ("VIRT_BASE=") - 1, &ptr, 16); + if (grub_errno) + goto out; + continue; + } + if (grub_strncmp (ptr, "VIRT_ENTRY=", sizeof ("VIRT_ENTRY=") - 1) == 0) + { + xi->entry_point = grub_strtoull (ptr + sizeof ("VIRT_ENTRY=") - 1, &ptr, 16); + if (grub_errno) + goto out; + continue; + } + if (grub_strncmp (ptr, "HYPERCALL_PAGE=", sizeof ("HYPERCALL_PAGE=") - 1) == 0) + { + xi->hypercall_page = grub_strtoull (ptr + sizeof ("HYPERCALL_PAGE=") - 1, &ptr, 16); + xi->has_hypercall_page = 1; + if (grub_errno) + goto out; + continue; + } + if (grub_strncmp (ptr, "ELF_PADDR_OFFSET=", sizeof ("ELF_PADDR_OFFSET=") - 1) == 0) + { + xi->paddr_offset = grub_strtoull (ptr + sizeof ("ELF_PADDR_OFFSET=") - 1, &ptr, 16); + has_paddr = 1; + if (grub_errno) + goto out; + continue; + } + } + if (xi->has_hypercall_page) + xi->hypercall_page = (xi->hypercall_page << 12) + xi->virt_base; + if (!has_paddr) + xi->paddr_offset = xi->virt_base; + +out: + grub_free (buf); + + return grub_errno; +} + +#pragma GCC diagnostic ignored "-Wcast-align" + +static grub_err_t +parse_note (grub_elf_t elf, struct grub_xen_file_info *xi, + grub_off_t off, grub_size_t sz) +{ + grub_uint32_t *buf; + grub_uint32_t *ptr; + if (grub_file_seek (elf->file, off) == (grub_off_t) -1) + return grub_errno; + buf = grub_malloc (sz); + if (!buf) + return grub_errno; + + if (grub_file_read (elf->file, buf, sz) != (grub_ssize_t) sz) + { + if (grub_errno) + return grub_errno; + return grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + elf->file->name); + } + for (ptr = buf; ptr - buf < (grub_ssize_t) (sz / sizeof (grub_uint32_t));) + { + Elf_Nhdr *nh = (Elf_Nhdr *) ptr; + char *name; + grub_uint32_t *desc; + grub_uint32_t namesz, descsz; + ptr += sizeof (*nh) / sizeof (grub_uint32_t); + name = (char *) ptr; + namesz = grub_le_to_cpu32 (nh->n_namesz); + descsz = grub_le_to_cpu32 (nh->n_descsz); + ptr += (namesz + 3) / 4; + desc = ptr; + ptr += (grub_le_to_cpu32 (nh->n_descsz) + 3) / 4; + if ((namesz < 3) || grub_memcmp (name, "Xen", namesz == 3 ? 3 : 4) != 0) + continue; + xi->has_note = 1; + switch (nh->n_type) + { + case XEN_ELFNOTE_ENTRY: + xi->entry_point = grub_le_to_cpu_addr (*(Elf_Addr *) desc); + break; + case XEN_ELFNOTE_HYPERCALL_PAGE: + xi->hypercall_page = grub_le_to_cpu_addr (*(Elf_Addr *) desc); + xi->has_hypercall_page = 1; + break; + case XEN_ELFNOTE_VIRT_BASE: + xi->virt_base = grub_le_to_cpu_addr (*(Elf_Addr *) desc); + break; + case XEN_ELFNOTE_PADDR_OFFSET: + xi->paddr_offset = grub_le_to_cpu_addr (*(Elf_Addr *) desc); + break; + case XEN_ELFNOTE_XEN_VERSION: + grub_dprintf ("xen", "xenversion = `%s'\n", (char *) desc); + break; + case XEN_ELFNOTE_GUEST_OS: + grub_dprintf ("xen", "name = `%s'\n", (char *) desc); + break; + case XEN_ELFNOTE_GUEST_VERSION: + grub_dprintf ("xen", "version = `%s'\n", (char *) desc); + break; + case XEN_ELFNOTE_LOADER: + if (descsz < 7 + || grub_memcmp (desc, "generic", descsz == 7 ? 7 : 8) != 0) + return grub_error (GRUB_ERR_BAD_OS, "invalid loader"); + break; + /* PAE */ + case XEN_ELFNOTE_PAE_MODE: + grub_dprintf ("xen", "pae = `%s', %d, %d\n", (char *) desc, + xi->arch, descsz); + if (xi->arch != GRUB_XEN_FILE_I386 + && xi->arch != GRUB_XEN_FILE_I386_PAE + && xi->arch != GRUB_XEN_FILE_I386_PAE_BIMODE) + break; + if (descsz >= 3 && grub_memcmp (desc, "yes", + descsz == 3 ? 3 : 4) == 0) + { + xi->extended_cr3 = 1; + xi->arch = GRUB_XEN_FILE_I386_PAE; + } + if (descsz >= 7 && grub_memcmp (desc, "bimodal", + descsz == 7 ? 7 : 8) == 0) + { + xi->extended_cr3 = 1; + xi->arch = GRUB_XEN_FILE_I386_PAE_BIMODE; + } + if (descsz >= 11 && grub_memcmp (desc, "yes,bimodal", + descsz == 11 ? 11 : 12) == 0) + { + xi->extended_cr3 = 1; + xi->arch = GRUB_XEN_FILE_I386_PAE_BIMODE; + } + if (descsz >= 2 && grub_memcmp (desc, "no", + descsz == 2 ? 2 : 3) == 0) + xi->arch = GRUB_XEN_FILE_I386; + break; + case XEN_ELFNOTE_INIT_P2M: + xi->p2m_base = grub_le_to_cpu_addr (*(Elf_Addr *) desc); + xi->has_p2m_base = 1; + break; + case XEN_ELFNOTE_MOD_START_PFN: + xi->unmapped_initrd = !!grub_le_to_cpu32(*(grub_uint32_t *) desc); + break; + default: + grub_dprintf ("xen", "unknown note type %d\n", nh->n_type); + break; + } + } + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xen_get_infoXX (grub_elf_t elf, struct grub_xen_file_info *xi) +{ + Elf_Shdr *s, *s0; + grub_size_t shnum = elf->ehdr.ehdrXX.e_shnum; + grub_size_t shentsize = elf->ehdr.ehdrXX.e_shentsize; + grub_size_t shsize = shnum * shentsize; + grub_off_t stroff; + grub_err_t err; + Elf_Phdr *phdr; + + xi->kern_end = 0; + xi->kern_start = ~0; + xi->entry_point = elf->ehdr.ehdrXX.e_entry; + + /* FIXME: check note. */ + FOR_ELF_PHDRS (elf, phdr) + { + Elf_Addr paddr; + + if (phdr->p_type == PT_NOTE) + { + err = parse_note (elf, xi, phdr->p_offset, phdr->p_filesz); + if (err) + return err; + } + + if (phdr->p_type != PT_LOAD) + continue; + + paddr = phdr->p_paddr; + + if (paddr < xi->kern_start) + xi->kern_start = paddr; + + if (paddr + phdr->p_memsz > xi->kern_end) + xi->kern_end = paddr + phdr->p_memsz; + } + + if (xi->has_note) + return GRUB_ERR_NONE; + + if (!shnum || !shentsize) + return grub_error (GRUB_ERR_BAD_OS, "no XEN note"); + + s0 = grub_malloc (shsize); + if (!s0) + return grub_errno; + + if (grub_file_seek (elf->file, elf->ehdr.ehdrXX.e_shoff) == (grub_off_t) -1) + { + err = grub_errno; + goto cleanup; + } + + if (grub_file_read (elf->file, s0, shsize) != (grub_ssize_t) shsize) + { + if (grub_errno) + err = grub_errno; + else + err = grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + elf->file->name); + goto cleanup; + } + + s = (Elf_Shdr *) ((char *) s0 + elf->ehdr.ehdrXX.e_shstrndx * shentsize); + stroff = s->sh_offset; + + for (s = s0; s < (Elf_Shdr *) ((char *) s0 + shnum * shentsize); + s = (Elf_Shdr *) ((char *) s + shentsize)) + { + if (s->sh_type == SHT_NOTE) + { + err = parse_note (elf, xi, s->sh_offset, s->sh_size); + if (err) + goto cleanup; + } + } + + if (xi->has_note) + { + err = GRUB_ERR_NONE; + goto cleanup; + } + + for (s = s0; s < (Elf_Shdr *) ((char *) s0 + shnum * shentsize); + s = (Elf_Shdr *) ((char *) s + shentsize)) + { + char name[sizeof("__xen_guest")]; + grub_memset (name, 0, sizeof (name)); + if (grub_file_seek (elf->file, stroff + s->sh_name) == (grub_off_t) -1) + { + err = grub_errno; + goto cleanup; + } + + if (grub_file_read (elf->file, name, sizeof (name)) != (grub_ssize_t) sizeof (name)) + { + if (grub_errno) + { + err = grub_errno; + goto cleanup; + } + continue; + } + if (grub_memcmp (name, "__xen_guest", + sizeof("__xen_guest")) != 0) + continue; + err = parse_xen_guest (elf, xi, s->sh_offset, s->sh_size); + goto cleanup; + } + err = grub_error (GRUB_ERR_BAD_OS, "no XEN note found"); + +cleanup: + grub_free (s0); + return err; +} diff --git a/grub-core/loader/i386/xnu.c b/grub-core/loader/i386/xnu.c new file mode 100644 index 0000000..a700936 --- /dev/null +++ b/grub-core/loader/i386/xnu.c @@ -0,0 +1,1166 @@ +/* + * 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/>. + */ + +#include <grub/env.h> +#include <grub/file.h> +#include <grub/disk.h> +#include <grub/xnu.h> +#include <grub/cpu/xnu.h> +#include <grub/mm.h> +#include <grub/loader.h> +#include <grub/autoefi.h> +#include <grub/i386/tsc.h> +#include <grub/i386/cpuid.h> +#include <grub/efi/api.h> +#include <grub/i386/pit.h> +#include <grub/misc.h> +#include <grub/charset.h> +#include <grub/term.h> +#include <grub/command.h> +#include <grub/i18n.h> +#include <grub/bitmap_scale.h> +#include <grub/cpu/io.h> +#include <grub/random.h> + +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +#define DEFAULT_VIDEO_MODE "auto" + +char grub_xnu_cmdline[1024]; +grub_uint32_t grub_xnu_entry_point, grub_xnu_arg1, grub_xnu_stack; + +/* Aliases set for some tables. */ +struct tbl_alias +{ + grub_efi_guid_t guid; + const char *name; +}; + +static struct tbl_alias table_aliases[] = + { + {GRUB_EFI_ACPI_20_TABLE_GUID, "ACPI_20"}, + {GRUB_EFI_ACPI_TABLE_GUID, "ACPI"}, + }; + +struct grub_xnu_devprop_device_descriptor +{ + struct grub_xnu_devprop_device_descriptor *next; + struct grub_xnu_devprop_device_descriptor **prev; + struct property_descriptor *properties; + struct grub_efi_device_path *path; + int pathlen; +}; + +static int +utf16_strlen (grub_uint16_t *in) +{ + int i; + for (i = 0; in[i]; i++); + return i; +} + +/* Read frequency from a string in MHz and return it in Hz. */ +static grub_uint64_t +readfrequency (const char *str) +{ + grub_uint64_t num = 0; + int mul = 1000000; + int found = 0; + + while (*str) + { + unsigned long digit; + + digit = grub_tolower (*str) - '0'; + if (digit > 9) + break; + + found = 1; + + num = num * 10 + digit; + str++; + } + num *= 1000000; + if (*str == '.') + { + str++; + while (*str) + { + unsigned long digit; + + digit = grub_tolower (*str) - '0'; + if (digit > 9) + break; + + found = 1; + + mul /= 10; + num = num + mul * digit; + str++; + } + } + if (! found) + return 0; + + return num; +} + +/* Thanks to Kabyl for precious information about Intel architecture. */ +static grub_uint64_t +guessfsb (void) +{ + const grub_uint64_t sane_value = 100000000; + grub_uint32_t manufacturer[3], max_cpuid, capabilities, msrlow; + grub_uint32_t a, b, d, divisor; + + if (! grub_cpu_is_cpuid_supported ()) + return sane_value; + + grub_cpuid (0, max_cpuid, manufacturer[0], manufacturer[2], manufacturer[1]); + + /* Only Intel for now is done. */ + if (grub_memcmp (manufacturer, "GenuineIntel", 12) != 0) + return sane_value; + + /* Check Speedstep. */ + if (max_cpuid < 1) + return sane_value; + + grub_cpuid (1, a, b, capabilities, d); + + if (! (capabilities & (1 << 7))) + return sane_value; + + /* Read the multiplier. */ + asm volatile ("movl $0x198, %%ecx\n" + "rdmsr" + : "=d" (msrlow) + : + : "%ecx", "%eax"); + + grub_uint64_t v; + grub_uint32_t r; + + /* (2000ULL << 32) / grub_tsc_rate */ + /* Assumption: TSC frequency is over 2 MHz. */ + v = 0xffffffff / grub_tsc_rate; + v *= 2000; + /* v is at most 2000 off from (2000ULL << 32) / grub_tsc_rate. + Since grub_tsc_rate < 2^32/2^11=2^21, so no overflow. + */ + r = (2000ULL << 32) - v * grub_tsc_rate; + v += r / grub_tsc_rate; + + divisor = ((msrlow >> 7) & 0x3e) | ((msrlow >> 14) & 1); + if (divisor == 0) + return sane_value; + return grub_divmod64 (v, divisor, 0); +} + +struct property_descriptor +{ + struct property_descriptor *next; + struct property_descriptor **prev; + grub_uint8_t *name; + grub_uint16_t *name16; + int name16len; + int length; + void *data; +}; + +static struct grub_xnu_devprop_device_descriptor *devices = 0; + +grub_err_t +grub_xnu_devprop_remove_property (struct grub_xnu_devprop_device_descriptor *dev, + char *name) +{ + struct property_descriptor *prop; + prop = grub_named_list_find (GRUB_AS_NAMED_LIST (dev->properties), name); + if (!prop) + return GRUB_ERR_NONE; + + grub_free (prop->name); + grub_free (prop->name16); + grub_free (prop->data); + + grub_list_remove (GRUB_AS_LIST (prop)); + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_devprop_remove_device (struct grub_xnu_devprop_device_descriptor *dev) +{ + void *t; + struct property_descriptor *prop; + + grub_list_remove (GRUB_AS_LIST (dev)); + + for (prop = dev->properties; prop; ) + { + grub_free (prop->name); + grub_free (prop->name16); + grub_free (prop->data); + t = prop; + prop = prop->next; + grub_free (t); + } + + grub_free (dev->path); + grub_free (dev); + + return GRUB_ERR_NONE; +} + +struct grub_xnu_devprop_device_descriptor * +grub_xnu_devprop_add_device (struct grub_efi_device_path *path, int length) +{ + struct grub_xnu_devprop_device_descriptor *ret; + + ret = grub_zalloc (sizeof (*ret)); + if (!ret) + return 0; + + ret->path = grub_malloc (length); + if (!ret->path) + { + grub_free (ret); + return 0; + } + ret->pathlen = length; + grub_memcpy (ret->path, path, length); + + grub_list_push (GRUB_AS_LIST_P (&devices), GRUB_AS_LIST (ret)); + + return ret; +} + +static grub_err_t +grub_xnu_devprop_add_property (struct grub_xnu_devprop_device_descriptor *dev, + grub_uint8_t *utf8, grub_uint16_t *utf16, + int utf16len, void *data, int datalen) +{ + struct property_descriptor *prop; + + prop = grub_malloc (sizeof (*prop)); + if (!prop) + return grub_errno; + + prop->data = grub_malloc (datalen); + if (!prop->data) + { + grub_free (prop); + return grub_errno; + } + grub_memcpy (prop->data, data, datalen); + + prop->name = utf8; + prop->name16 = utf16; + prop->name16len = utf16len; + prop->length = datalen; + + grub_list_push (GRUB_AS_LIST_P (&dev->properties), + GRUB_AS_LIST (prop)); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_devprop_add_property_utf8 (struct grub_xnu_devprop_device_descriptor *dev, + char *name, void *data, int datalen) +{ + grub_uint8_t *utf8; + grub_uint16_t *utf16; + int len, utf16len; + grub_err_t err; + + utf8 = (grub_uint8_t *) grub_strdup (name); + if (!utf8) + return grub_errno; + + len = grub_strlen (name); + utf16 = grub_calloc (len, sizeof (grub_uint16_t)); + if (!utf16) + { + grub_free (utf8); + return grub_errno; + } + + utf16len = grub_utf8_to_utf16 (utf16, len, utf8, len, NULL); + if (utf16len < 0) + { + grub_free (utf8); + grub_free (utf16); + return grub_errno; + } + + err = grub_xnu_devprop_add_property (dev, utf8, utf16, + utf16len, data, datalen); + if (err) + { + grub_free (utf8); + grub_free (utf16); + return err; + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_devprop_add_property_utf16 (struct grub_xnu_devprop_device_descriptor *dev, + grub_uint16_t *name, int namelen, + void *data, int datalen) +{ + grub_uint8_t *utf8; + grub_uint16_t *utf16; + grub_err_t err; + + utf16 = grub_calloc (namelen, sizeof (grub_uint16_t)); + if (!utf16) + return grub_errno; + grub_memcpy (utf16, name, sizeof (grub_uint16_t) * namelen); + + utf8 = grub_malloc (namelen * 4 + 1); + if (!utf8) + { + grub_free (utf16); + return grub_errno; + } + + *grub_utf16_to_utf8 ((grub_uint8_t *) utf8, name, namelen) = '\0'; + + err = grub_xnu_devprop_add_property (dev, utf8, utf16, + namelen, data, datalen); + if (err) + { + grub_free (utf8); + grub_free (utf16); + return err; + } + + return GRUB_ERR_NONE; +} + +void +grub_cpu_xnu_unload (void) +{ + struct grub_xnu_devprop_device_descriptor *dev1, *dev2; + + for (dev1 = devices; dev1; ) + { + dev2 = dev1->next; + grub_xnu_devprop_remove_device (dev1); + dev1 = dev2; + } +} + +static grub_err_t +grub_cpu_xnu_fill_devprop (void) +{ + struct grub_xnu_devtree_key *efikey; + int total_length = sizeof (struct grub_xnu_devprop_header); + struct grub_xnu_devtree_key *devprop; + struct grub_xnu_devprop_device_descriptor *device; + void *ptr; + struct grub_xnu_devprop_header *head; + void *t; + int numdevs = 0; + + /* The key "efi". */ + efikey = grub_xnu_create_key (&grub_xnu_devtree_root, "efi"); + if (! efikey) + return grub_errno; + + for (device = devices; device; device = device->next) + { + struct property_descriptor *propdesc; + total_length += sizeof (struct grub_xnu_devprop_device_header); + total_length += device->pathlen; + + for (propdesc = device->properties; propdesc; propdesc = propdesc->next) + { + total_length += sizeof (grub_uint32_t); + total_length += sizeof (grub_uint16_t) + * (propdesc->name16len + 1); + total_length += sizeof (grub_uint32_t); + total_length += propdesc->length; + } + numdevs++; + } + + devprop = grub_xnu_create_value (&(efikey->first_child), "device-properties"); + if (!devprop) + return grub_errno; + + devprop->data = grub_malloc (total_length); + devprop->datasize = total_length; + + ptr = devprop->data; + head = ptr; + ptr = head + 1; + head->length = total_length; + head->alwaysone = 1; + head->num_devices = numdevs; + for (device = devices; device; ) + { + struct grub_xnu_devprop_device_header *devhead; + struct property_descriptor *propdesc; + devhead = ptr; + devhead->num_values = 0; + ptr = devhead + 1; + + grub_memcpy (ptr, device->path, device->pathlen); + ptr = (char *) ptr + device->pathlen; + + for (propdesc = device->properties; propdesc; ) + { + grub_uint32_t *len; + grub_uint16_t *name; + void *data; + + len = ptr; + *len = 2 * propdesc->name16len + sizeof (grub_uint16_t) + + sizeof (grub_uint32_t); + ptr = len + 1; + + name = ptr; + grub_memcpy (name, propdesc->name16, 2 * propdesc->name16len); + name += propdesc->name16len; + + /* NUL terminator. */ + *name = 0; + ptr = name + 1; + + len = ptr; + *len = propdesc->length + sizeof (grub_uint32_t); + data = len + 1; + ptr = data; + grub_memcpy (ptr, propdesc->data, propdesc->length); + ptr = (char *) ptr + propdesc->length; + + grub_free (propdesc->name); + grub_free (propdesc->name16); + grub_free (propdesc->data); + t = propdesc; + propdesc = propdesc->next; + grub_free (t); + devhead->num_values++; + } + + devhead->length = (char *) ptr - (char *) devhead; + t = device; + device = device->next; + grub_free (t); + } + + devices = 0; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_devprop_load (grub_command_t cmd __attribute__ ((unused)), + int argc, char *args[]) +{ + grub_file_t file; + void *buf, *bufstart, *bufend; + struct grub_xnu_devprop_header *head; + grub_size_t size; + unsigned i, j; + + if (argc != 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + + file = grub_file_open (args[0], GRUB_FILE_XNU_DEVPROP); + if (! file) + return grub_errno; + size = grub_file_size (file); + buf = grub_malloc (size); + if (!buf) + { + grub_file_close (file); + return grub_errno; + } + if (grub_file_read (file, buf, size) != (grub_ssize_t) size) + { + grub_file_close (file); + return grub_errno; + } + grub_file_close (file); + + bufstart = buf; + bufend = (char *) buf + size; + head = buf; + buf = head + 1; + for (i = 0; i < grub_le_to_cpu32 (head->num_devices) && buf < bufend; i++) + { + struct grub_efi_device_path *dp, *dpstart; + struct grub_xnu_devprop_device_descriptor *dev; + struct grub_xnu_devprop_device_header *devhead; + + devhead = buf; + buf = devhead + 1; + dp = dpstart = buf; + + while (GRUB_EFI_DEVICE_PATH_VALID (dp) && buf < bufend) + { + buf = (char *) buf + GRUB_EFI_DEVICE_PATH_LENGTH (dp); + if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp)) + break; + dp = buf; + } + + dev = grub_xnu_devprop_add_device (dpstart, (char *) buf + - (char *) dpstart); + + for (j = 0; j < grub_le_to_cpu32 (devhead->num_values) && buf < bufend; + j++) + { + grub_uint32_t *namelen; + grub_uint32_t *datalen; + grub_uint16_t *utf16; + void *data; + grub_err_t err; + + namelen = buf; + buf = namelen + 1; + if (buf >= bufend) + break; + + utf16 = buf; + buf = (char *) buf + *namelen - sizeof (grub_uint32_t); + if (buf >= bufend) + break; + + datalen = buf; + buf = datalen + 1; + if (buf >= bufend) + break; + + data = buf; + buf = (char *) buf + *datalen - sizeof (grub_uint32_t); + if (buf >= bufend) + break; + err = grub_xnu_devprop_add_property_utf16 + (dev, utf16, (*namelen - sizeof (grub_uint32_t) + - sizeof (grub_uint16_t)) / sizeof (grub_uint16_t), + data, *datalen - sizeof (grub_uint32_t)); + if (err) + { + grub_free (bufstart); + return err; + } + } + } + + grub_free (bufstart); + return GRUB_ERR_NONE; +} + +/* Fill device tree. */ +/* FIXME: some entries may be platform-agnostic. Move them to loader/xnu.c. */ +static grub_err_t +grub_cpu_xnu_fill_devicetree (grub_uint64_t *fsbfreq_out) +{ + struct grub_xnu_devtree_key *efikey; + struct grub_xnu_devtree_key *chosenkey; + struct grub_xnu_devtree_key *cfgtablekey; + struct grub_xnu_devtree_key *curval; + struct grub_xnu_devtree_key *runtimesrvkey; + struct grub_xnu_devtree_key *platformkey; + unsigned i, j; + grub_err_t err; + + chosenkey = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen"); + if (! chosenkey) + return grub_errno; + + /* Random seed. */ + curval = grub_xnu_create_value (&(chosenkey->first_child), "random-seed"); + if (! curval) + return grub_errno; + curval->datasize = 64; + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + /* Our random is not peer-reviewed but xnu uses this seed only for + ASLR in kernel. */ + err = grub_crypto_get_random (curval->data, curval->datasize); + if (err) + return err; + + /* The value "model". */ + /* FIXME: may this value be sometimes different? */ + curval = grub_xnu_create_value (&grub_xnu_devtree_root, "model"); + if (! curval) + return grub_errno; + curval->datasize = sizeof ("ACPI"); + curval->data = grub_strdup ("ACPI"); + curval = grub_xnu_create_value (&grub_xnu_devtree_root, "compatible"); + if (! curval) + return grub_errno; + curval->datasize = sizeof ("ACPI"); + curval->data = grub_strdup ("ACPI"); + + /* The key "efi". */ + efikey = grub_xnu_create_key (&grub_xnu_devtree_root, "efi"); + if (! efikey) + return grub_errno; + + /* Information about firmware. */ + curval = grub_xnu_create_value (&(efikey->first_child), "firmware-revision"); + if (! curval) + return grub_errno; + curval->datasize = (SYSTEM_TABLE_SIZEOF (firmware_revision)); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + grub_memcpy (curval->data, (SYSTEM_TABLE_VAR(firmware_revision)), + curval->datasize); + + curval = grub_xnu_create_value (&(efikey->first_child), "firmware-vendor"); + if (! curval) + return grub_errno; + curval->datasize = + 2 * (utf16_strlen (SYSTEM_TABLE_PTR (firmware_vendor)) + 1); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + grub_memcpy (curval->data, SYSTEM_TABLE_PTR (firmware_vendor), + curval->datasize); + + curval = grub_xnu_create_value (&(efikey->first_child), "firmware-abi"); + if (! curval) + return grub_errno; + curval->datasize = sizeof ("EFI32"); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + if (SIZEOF_OF_UINTN == 4) + grub_memcpy (curval->data, "EFI32", curval->datasize); + else + grub_memcpy (curval->data, "EFI64", curval->datasize); + + /* The key "platform". */ + platformkey = grub_xnu_create_key (&(efikey->first_child), + "platform"); + if (! platformkey) + return grub_errno; + + /* Pass FSB frequency to the kernel. */ + curval = grub_xnu_create_value (&(platformkey->first_child), "FSBFrequency"); + if (! curval) + return grub_errno; + curval->datasize = sizeof (grub_uint64_t); + curval->data = grub_malloc (curval->datasize); + if (!curval->data) + return grub_errno; + + /* First see if user supplies the value. */ + const char *fsbvar = grub_env_get ("fsb"); + grub_uint64_t fsbfreq = 0; + if (fsbvar) + fsbfreq = readfrequency (fsbvar); + /* Try autodetect. */ + if (! fsbfreq) + fsbfreq = guessfsb (); + *((grub_uint64_t *) curval->data) = fsbfreq; + *fsbfreq_out = fsbfreq; + grub_dprintf ("xnu", "fsb autodetected as %llu\n", + (unsigned long long) *((grub_uint64_t *) curval->data)); + + cfgtablekey = grub_xnu_create_key (&(efikey->first_child), + "configuration-table"); + if (!cfgtablekey) + return grub_errno; + + /* Fill "configuration-table" key. */ + for (i = 0; i < SYSTEM_TABLE (num_table_entries); i++) + { + void *ptr; + struct grub_xnu_devtree_key *curkey; + grub_efi_packed_guid_t guid; + char guidbuf[64]; + + /* Retrieve current key. */ +#ifdef GRUB_MACHINE_EFI + { + ptr = (void *) + grub_efi_system_table->configuration_table[i].vendor_table; + guid = grub_efi_system_table->configuration_table[i].vendor_guid; + } +#else + if (SIZEOF_OF_UINTN == 4) + { + ptr = (void *) (grub_addr_t) ((grub_efiemu_configuration_table32_t *) + SYSTEM_TABLE_PTR (configuration_table))[i] + .vendor_table; + guid = + ((grub_efiemu_configuration_table32_t *) + SYSTEM_TABLE_PTR (configuration_table))[i].vendor_guid; + } + else + { + ptr = (void *) (grub_addr_t) ((grub_efiemu_configuration_table64_t *) + SYSTEM_TABLE_PTR (configuration_table))[i] + .vendor_table; + guid = + ((grub_efiemu_configuration_table64_t *) + SYSTEM_TABLE_PTR (configuration_table))[i].vendor_guid; + } +#endif + + /* The name of key for new table. */ + grub_snprintf (guidbuf, sizeof (guidbuf), "%08x-%04x-%04x-%02x%02x-", + guid.data1, guid.data2, guid.data3, guid.data4[0], + guid.data4[1]); + for (j = 2; j < 8; j++) + grub_snprintf (guidbuf + grub_strlen (guidbuf), + sizeof (guidbuf) - grub_strlen (guidbuf), + "%02x", guid.data4[j]); + /* For some reason GUID has to be in uppercase. */ + for (j = 0; guidbuf[j] ; j++) + if (guidbuf[j] >= 'a' && guidbuf[j] <= 'f') + guidbuf[j] += 'A' - 'a'; + curkey = grub_xnu_create_key (&(cfgtablekey->first_child), guidbuf); + if (! curkey) + return grub_errno; + + curval = grub_xnu_create_value (&(curkey->first_child), "guid"); + if (! curval) + return grub_errno; + curval->datasize = sizeof (guid); + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + grub_memcpy (curval->data, &guid, curval->datasize); + + /* The value "table". */ + curval = grub_xnu_create_value (&(curkey->first_child), "table"); + if (! curval) + return grub_errno; + curval->datasize = SIZEOF_OF_UINTN; + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + if (SIZEOF_OF_UINTN == 4) + *((grub_uint32_t *) curval->data) = (grub_addr_t) ptr; + else + *((grub_uint64_t *) curval->data) = (grub_addr_t) ptr; + + /* Create alias. */ + for (j = 0; j < ARRAY_SIZE(table_aliases); j++) + if (grub_memcmp (&table_aliases[j].guid, &guid, sizeof (guid)) == 0) + break; + if (j != ARRAY_SIZE(table_aliases)) + { + curval = grub_xnu_create_value (&(curkey->first_child), "alias"); + if (!curval) + return grub_errno; + curval->datasize = grub_strlen (table_aliases[j].name) + 1; + curval->data = grub_malloc (curval->datasize); + if (!curval->data) + return grub_errno; + grub_memcpy (curval->data, table_aliases[j].name, curval->datasize); + } + } + + /* Create and fill "runtime-services" key. */ + runtimesrvkey = grub_xnu_create_key (&(efikey->first_child), + "runtime-services"); + if (! runtimesrvkey) + return grub_errno; + curval = grub_xnu_create_value (&(runtimesrvkey->first_child), "table"); + if (! curval) + return grub_errno; + curval->datasize = SIZEOF_OF_UINTN; + curval->data = grub_malloc (curval->datasize); + if (! curval->data) + return grub_errno; + if (SIZEOF_OF_UINTN == 4) + *((grub_uint32_t *) curval->data) + = (grub_addr_t) SYSTEM_TABLE_PTR (runtime_services); + else + *((grub_uint64_t *) curval->data) + = (grub_addr_t) SYSTEM_TABLE_PTR (runtime_services); + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_xnu_boot_resume (void) +{ + struct grub_relocator32_state state; + + state.esp = grub_xnu_stack; + state.ebp = grub_xnu_stack; + state.eip = grub_xnu_entry_point; + state.eax = grub_xnu_arg1; + + return grub_relocator32_boot (grub_xnu_relocator, state, 0); +} + +/* Setup video for xnu. */ +static grub_err_t +grub_xnu_set_video (struct grub_xnu_boot_params_common *params) +{ + struct grub_video_mode_info mode_info; + char *tmp; + const char *modevar; + void *framebuffer; + grub_err_t err; + struct grub_video_bitmap *bitmap = NULL; + + modevar = grub_env_get ("gfxpayload"); + /* Consider only graphical 32-bit deep modes. */ + if (! modevar || *modevar == 0) + err = grub_video_set_mode (DEFAULT_VIDEO_MODE, + GRUB_VIDEO_MODE_TYPE_PURE_TEXT + | GRUB_VIDEO_MODE_TYPE_DEPTH_MASK, + 32 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS); + else + { + tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar); + if (! tmp) + return grub_errno; + err = grub_video_set_mode (tmp, + GRUB_VIDEO_MODE_TYPE_PURE_TEXT + | GRUB_VIDEO_MODE_TYPE_DEPTH_MASK, + 32 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS); + grub_free (tmp); + } + + if (err) + return err; + + err = grub_video_get_info (&mode_info); + if (err) + return err; + + if (grub_xnu_bitmap) + { + if (grub_xnu_bitmap_mode == GRUB_XNU_BITMAP_STRETCH) + err = grub_video_bitmap_create_scaled (&bitmap, + mode_info.width, + mode_info.height, + grub_xnu_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + else + bitmap = grub_xnu_bitmap; + } + + if (bitmap) + { + if (grub_xnu_bitmap_mode == GRUB_XNU_BITMAP_STRETCH) + err = grub_video_bitmap_create_scaled (&bitmap, + mode_info.width, + mode_info.height, + grub_xnu_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + else + bitmap = grub_xnu_bitmap; + } + + if (bitmap) + { + int x, y; + + x = mode_info.width - bitmap->mode_info.width; + x /= 2; + y = mode_info.height - bitmap->mode_info.height; + y /= 2; + err = grub_video_blit_bitmap (bitmap, + GRUB_VIDEO_BLIT_REPLACE, + x > 0 ? x : 0, + y > 0 ? y : 0, + x < 0 ? -x : 0, + y < 0 ? -y : 0, + min (bitmap->mode_info.width, + mode_info.width), + min (bitmap->mode_info.height, + mode_info.height)); + } + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + bitmap = 0; + } + + err = grub_video_get_info_and_fini (&mode_info, &framebuffer); + if (err) + return err; + + params->lfb_width = mode_info.width; + params->lfb_height = mode_info.height; + params->lfb_depth = mode_info.bpp; + params->lfb_line_len = mode_info.pitch; + + params->lfb_base = (grub_addr_t) framebuffer; + params->lfb_mode = bitmap ? GRUB_XNU_VIDEO_SPLASH + : GRUB_XNU_VIDEO_TEXT_IN_VIDEO; + + return GRUB_ERR_NONE; +} + +static int +total_ram_hook (grub_uint64_t addr __attribute__ ((unused)), grub_uint64_t size, + grub_memory_type_t type, + void *data) +{ + grub_uint64_t *result = data; + + if (type != GRUB_MEMORY_AVAILABLE) + return 0; + *result += size; + return 0; +} + +static grub_uint64_t +get_total_ram (void) +{ + grub_uint64_t result = 0; + + grub_mmap_iterate (total_ram_hook, &result); + return result; +} + +/* Boot xnu. */ +grub_err_t +grub_xnu_boot (void) +{ + union grub_xnu_boot_params_any *bootparams; + struct grub_xnu_boot_params_common *bootparams_common; + void *bp_in; + grub_addr_t bootparams_target; + grub_err_t err; + grub_efi_uintn_t memory_map_size = 0; + void *memory_map; + grub_addr_t memory_map_target; + grub_efi_uintn_t map_key = 0; + grub_efi_uintn_t descriptor_size = 0; + grub_efi_uint32_t descriptor_version = 0; + grub_uint64_t firstruntimepage, lastruntimepage; + grub_uint64_t curruntimepage; + grub_addr_t devtree_target; + grub_size_t devtreelen; + int i; + struct grub_relocator32_state state; + grub_uint64_t fsbfreq = 100000000; + int v2 = (grub_xnu_darwin_version >= 11); + grub_uint32_t efi_system_table = 0; + + err = grub_autoefi_prepare (); + if (err) + return err; + + err = grub_cpu_xnu_fill_devprop (); + if (err) + return err; + + err = grub_cpu_xnu_fill_devicetree (&fsbfreq); + if (err) + return err; + + err = grub_xnu_fill_devicetree (); + if (err) + return err; + + /* Page-align to avoid following parts to be inadvertently freed. */ + err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE); + if (err) + return err; + + /* Pass memory map to kernel. */ + memory_map_size = 0; + memory_map = 0; + map_key = 0; + descriptor_size = 0; + descriptor_version = 0; + + grub_dprintf ("xnu", "eip=%x, efi=%p\n", grub_xnu_entry_point, + grub_autoefi_system_table); + + const char *debug = grub_env_get ("debug"); + + if (debug && (grub_strword (debug, "all") || grub_strword (debug, "xnu"))) + { + grub_puts_ (N_("Press any key to launch xnu")); + grub_getkey (); + } + + /* Relocate the boot parameters to heap. */ + err = grub_xnu_heap_malloc (sizeof (*bootparams), + &bp_in, &bootparams_target); + if (err) + return err; + bootparams = bp_in; + + grub_memset (bootparams, 0, sizeof (*bootparams)); + if (v2) + { + bootparams_common = &bootparams->v2.common; + bootparams->v2.fsbfreq = fsbfreq; + bootparams->v2.ram_size = get_total_ram(); + } + else + bootparams_common = &bootparams->v1.common; + + /* Set video. */ + err = grub_xnu_set_video (bootparams_common); + if (err != GRUB_ERR_NONE) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + grub_puts_ (N_("Booting in blind mode")); + + bootparams_common->lfb_mode = 0; + bootparams_common->lfb_width = 0; + bootparams_common->lfb_height = 0; + bootparams_common->lfb_depth = 0; + bootparams_common->lfb_line_len = 0; + bootparams_common->lfb_base = 0; + } + + if (grub_autoefi_get_memory_map (&memory_map_size, memory_map, + &map_key, &descriptor_size, + &descriptor_version) < 0) + return grub_errno; + + /* We will do few allocations later. Reserve some space for possible + memory map growth. */ + memory_map_size += 20 * descriptor_size; + err = grub_xnu_heap_malloc (memory_map_size, + &memory_map, &memory_map_target); + if (err) + return err; + + err = grub_xnu_writetree_toheap (&devtree_target, &devtreelen); + if (err) + return err; + + grub_memcpy (bootparams_common->cmdline, grub_xnu_cmdline, + sizeof (bootparams_common->cmdline)); + + bootparams_common->devtree = devtree_target; + bootparams_common->devtreelen = devtreelen; + + err = grub_autoefi_finish_boot_services (&memory_map_size, memory_map, + &map_key, &descriptor_size, + &descriptor_version); + if (err) + return err; + + if (v2) + bootparams->v2.efi_system_table = (grub_addr_t) grub_autoefi_system_table; + else + bootparams->v1.efi_system_table = (grub_addr_t) grub_autoefi_system_table; + + firstruntimepage = (((grub_addr_t) grub_xnu_heap_target_start + + grub_xnu_heap_size + GRUB_XNU_PAGESIZE - 1) + / GRUB_XNU_PAGESIZE) + 20; + curruntimepage = firstruntimepage; + + for (i = 0; (unsigned) i < memory_map_size / descriptor_size; i++) + { + grub_efi_memory_descriptor_t *curdesc = (grub_efi_memory_descriptor_t *) + ((char *) memory_map + descriptor_size * i); + + curdesc->virtual_start = curdesc->physical_start; + + if (curdesc->type == GRUB_EFI_RUNTIME_SERVICES_DATA + || curdesc->type == GRUB_EFI_RUNTIME_SERVICES_CODE) + { + curdesc->virtual_start = curruntimepage << 12; + curruntimepage += curdesc->num_pages; + if (curdesc->physical_start + <= (grub_addr_t) grub_autoefi_system_table + && curdesc->physical_start + (curdesc->num_pages << 12) + > (grub_addr_t) grub_autoefi_system_table) + efi_system_table + = (grub_addr_t) grub_autoefi_system_table + - curdesc->physical_start + curdesc->virtual_start; + if (SIZEOF_OF_UINTN == 8 && grub_xnu_is_64bit) + curdesc->virtual_start |= 0xffffff8000000000ULL; + } + } + + lastruntimepage = curruntimepage; + + if (v2) + { + bootparams->v2.efi_uintnbits = SIZEOF_OF_UINTN * 8; + bootparams->v2.verminor = GRUB_XNU_BOOTARGSV2_VERMINOR; + bootparams->v2.vermajor = GRUB_XNU_BOOTARGSV2_VERMAJOR; + bootparams->v2.efi_system_table = efi_system_table; + } + else + { + bootparams->v1.efi_uintnbits = SIZEOF_OF_UINTN * 8; + bootparams->v1.verminor = GRUB_XNU_BOOTARGSV1_VERMINOR; + bootparams->v1.vermajor = GRUB_XNU_BOOTARGSV1_VERMAJOR; + bootparams->v1.efi_system_table = efi_system_table; + } + + bootparams_common->efi_runtime_first_page = firstruntimepage; + bootparams_common->efi_runtime_npages = lastruntimepage - firstruntimepage; + bootparams_common->efi_mem_desc_size = descriptor_size; + bootparams_common->efi_mem_desc_version = descriptor_version; + bootparams_common->efi_mmap = memory_map_target; + bootparams_common->efi_mmap_size = memory_map_size; + bootparams_common->heap_start = grub_xnu_heap_target_start; + bootparams_common->heap_size = curruntimepage * GRUB_XNU_PAGESIZE - grub_xnu_heap_target_start; + + /* Parameters for asm helper. */ + grub_xnu_stack = bootparams_common->heap_start + + bootparams_common->heap_size + GRUB_XNU_PAGESIZE; + grub_xnu_arg1 = bootparams_target; + + grub_autoefi_set_virtual_address_map (memory_map_size, descriptor_size, + descriptor_version, memory_map); + + state.eip = grub_xnu_entry_point; + state.eax = grub_xnu_arg1; + state.esp = grub_xnu_stack; + state.ebp = grub_xnu_stack; + + /* XNU uses only APIC. Disable PIC. */ + grub_outb (0xff, 0x21); + grub_outb (0xff, 0xa1); + + return grub_relocator32_boot (grub_xnu_relocator, state, 0); +} + +static grub_command_t cmd_devprop_load; + +void +grub_cpu_xnu_init (void) +{ + cmd_devprop_load = grub_register_command ("xnu_devprop_load", + grub_cmd_devprop_load, + /* TRANSLATORS: `device-properties' + is a variable name, + not a program. */ + 0, N_("Load `device-properties' dump.")); +} + +void +grub_cpu_xnu_fini (void) +{ + grub_unregister_command (cmd_devprop_load); +} |