summaryrefslogtreecommitdiff
path: root/usr/src/boot/i386/libi386/multiboot.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/boot/i386/libi386/multiboot.c')
-rw-r--r--usr/src/boot/i386/libi386/multiboot.c534
1 files changed, 534 insertions, 0 deletions
diff --git a/usr/src/boot/i386/libi386/multiboot.c b/usr/src/boot/i386/libi386/multiboot.c
new file mode 100644
index 0000000000..0474b62350
--- /dev/null
+++ b/usr/src/boot/i386/libi386/multiboot.c
@@ -0,0 +1,534 @@
+/*
+ * Copyright (c) 2014 Roger Pau Monné <royger@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.
+ */
+
+/*
+ * This multiboot implementation only implements a subset of the full
+ * multiboot specification in order to be able to boot Xen and a
+ * FreeBSD Dom0. Trying to use it to boot other multiboot compliant
+ * kernels will most surely fail.
+ *
+ * The full multiboot specification can be found here:
+ * http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/exec.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/stdint.h>
+#define _MACHINE_ELF_WANT_32BIT
+#include <machine/elf.h>
+#include <machine/metadata.h>
+#include <machine/pc/bios.h>
+#include <string.h>
+#include <stand.h>
+
+#include "bootstrap.h"
+#include <sys/multiboot.h>
+#include "vbe.h"
+#include "libzfs.h"
+#include "libi386.h"
+#include "../btx/lib/btxv86.h"
+
+#define SUPPORT_DHCP
+#include <bootp.h>
+
+#define MULTIBOOT_SUPPORTED_FLAGS \
+ (MULTIBOOT_AOUT_KLUDGE|MULTIBOOT_PAGE_ALIGN|MULTIBOOT_MEMORY_INFO)
+#define METADATA_FIXED_SIZE (PAGE_SIZE*4)
+#define METADATA_MODULE_SIZE PAGE_SIZE
+
+#define METADATA_RESV_SIZE(mod_num) \
+ roundup(METADATA_FIXED_SIZE + METADATA_MODULE_SIZE * mod_num, PAGE_SIZE)
+
+/* MB data heap pointer */
+static vm_offset_t last_addr;
+
+static int multiboot_loadfile(char *, u_int64_t, struct preloaded_file **);
+static int multiboot_exec(struct preloaded_file *);
+
+static int multiboot_obj_loadfile(char *, u_int64_t, struct preloaded_file **);
+static int multiboot_obj_exec(struct preloaded_file *fp);
+
+struct file_format multiboot = { multiboot_loadfile, multiboot_exec };
+struct file_format multiboot_obj =
+ { multiboot_obj_loadfile, multiboot_obj_exec };
+
+static int
+num_modules(struct preloaded_file *kfp)
+{
+ struct kernel_module *kmp;
+ int mod_num = 0;
+
+ for (kmp = kfp->f_modules; kmp != NULL; kmp = kmp->m_next)
+ mod_num++;
+
+ return (mod_num);
+}
+
+static int
+multiboot_loadfile(char *filename, u_int64_t dest,
+ struct preloaded_file **result)
+{
+ uint32_t *magic;
+ int i, error;
+ caddr_t header_search;
+ ssize_t search_size;
+ int fd;
+ struct multiboot_header *header;
+ struct preloaded_file *fp;
+
+ if (filename == NULL)
+ return (EFTYPE);
+
+ /* is kernel already loaded? */
+ fp = file_findfile(NULL, NULL);
+ if (fp != NULL) {
+ return (EFTYPE);
+ }
+
+ if ((fd = open(filename, O_RDONLY)) == -1)
+ return (errno);
+
+ /*
+ * Read MULTIBOOT_SEARCH size in order to search for the
+ * multiboot magic header.
+ */
+ header_search = malloc(MULTIBOOT_SEARCH);
+ if (header_search == NULL) {
+ close(fd);
+ return (ENOMEM);
+ }
+
+ search_size = read(fd, header_search, MULTIBOOT_SEARCH);
+ magic = (uint32_t *)header_search;
+
+ header = NULL;
+ for (i = 0; i < (search_size / sizeof(uint32_t)); i++) {
+ if (magic[i] == MULTIBOOT_HEADER_MAGIC) {
+ header = (struct multiboot_header *)&magic[i];
+ break;
+ }
+ }
+
+ if (header == NULL) {
+ error = EFTYPE;
+ goto out;
+ }
+
+ /* Valid multiboot header has been found, validate checksum */
+ if (header->magic + header->flags + header->checksum != 0) {
+ printf(
+ "Multiboot checksum failed, magic: 0x%x flags: 0x%x checksum: 0x%x\n",
+ header->magic, header->flags, header->checksum);
+ error = EFTYPE;
+ goto out;
+ }
+
+ if ((header->flags & ~MULTIBOOT_SUPPORTED_FLAGS) != 0) {
+ printf("Unsupported multiboot flags found: 0x%x\n",
+ header->flags);
+ error = EFTYPE;
+ goto out;
+ }
+ /* AOUT KLUDGE means we just load entire flat file as blob */
+ if (header->flags & MULTIBOOT_AOUT_KLUDGE) {
+ vm_offset_t laddr;
+ int got;
+
+ dest = header->load_addr;
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ printf("lseek failed\n");
+ error = EIO;
+ goto out;
+ }
+ laddr = dest;
+ for (;;) {
+ got = archsw.arch_readin(fd, laddr, 4096);
+ if (got == 0)
+ break;
+ if (got < 0) {
+ printf("error reading: %s", strerror(errno));
+ error = EIO;
+ goto out;
+ }
+ laddr += got;
+ }
+
+ fp = file_alloc();
+ if (fp == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ fp->f_name = strdup(filename);
+ fp->f_type = strdup("aout multiboot kernel");
+ fp->f_addr = header->entry_addr;
+ fp->f_size = laddr - dest;
+ if (fp->f_size == 0) {
+ file_discard(fp);
+ error = EIO;
+ goto out;
+ }
+ fp->f_metadata = NULL;
+ error = 0;
+ } else {
+ error = elf32_loadfile_raw(filename, dest, &fp, 1);
+ if (error != 0) {
+ printf("elf32_loadfile_raw failed: %d unable to "
+ "load multiboot kernel\n", error);
+ goto out;
+ }
+ }
+
+ setenv("kernelname", fp->f_name, 1);
+ bios_addsmapdata(fp);
+ *result = fp;
+out:
+ free(header_search);
+ close(fd);
+ return (error);
+}
+
+/*
+ * returns allocated virtual address from MB info area
+ */
+static vm_offset_t
+mb_malloc(size_t n)
+{
+ vm_offset_t ptr = last_addr;
+ if (ptr + n >= high_heap_base)
+ return (0);
+ last_addr = roundup(last_addr + n, MULTIBOOT_INFO_ALIGN);
+ return (ptr);
+}
+
+static int
+multiboot_exec(struct preloaded_file *fp)
+{
+ struct preloaded_file *mfp;
+ vm_offset_t entry;
+ struct file_metadata *md;
+ struct multiboot_info *mb_info = NULL;
+ struct multiboot_mod_list *mb_mod = NULL;
+ multiboot_memory_map_t *mmap;
+ struct bios_smap *smap;
+ struct devdesc *rootdev;
+ char *cmdline = NULL;
+ size_t len;
+ int error, num, i;
+ int rootfs = 0; /* flag for rootfs */
+ int xen = 0; /* flag for xen */
+ int kernel = 0; /* flag for kernel */
+
+ /* Set up base for mb_malloc. */
+ for (mfp = fp; mfp->f_next != NULL; mfp = mfp->f_next);
+
+ /* Start info block from new page. */
+ last_addr = roundup(mfp->f_addr + mfp->f_size, MULTIBOOT_MOD_ALIGN);
+
+ /* Allocate the multiboot struct and fill the basic details. */
+ mb_info = (struct multiboot_info *)PTOV(mb_malloc(sizeof (*mb_info)));
+
+ bzero(mb_info, sizeof(struct multiboot_info));
+ mb_info->flags = MULTIBOOT_INFO_MEMORY|MULTIBOOT_INFO_BOOT_LOADER_NAME;
+ mb_info->mem_lower = bios_basemem / 1024;
+ mb_info->mem_upper = bios_extmem / 1024;
+ mb_info->boot_loader_name = mb_malloc(strlen(bootprog_info) + 1);
+
+ i386_copyin(bootprog_info, mb_info->boot_loader_name,
+ strlen(bootprog_info) + 1);
+
+ i386_getdev((void **)(&rootdev), NULL, NULL);
+ if (rootdev == NULL) {
+ printf("can't determine root device\n");
+ error = EINVAL;
+ goto error;
+ }
+
+ /*
+ * Boot image command line. If args were not provided, we need to set
+ * args here, and that depends on image type...
+ * Fortunately we only have following options:
+ * 64 or 32 bit unix or xen. So we just check if f_name has unix.
+ */
+ /* Do we boot xen? */
+ if (strstr(fp->f_name, "unix") == NULL)
+ xen = 1;
+
+ entry = fp->f_addr;
+
+ num = 0;
+ for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
+ num++;
+ if (mfp->f_type != NULL && strcmp(mfp->f_type, "rootfs") == 0)
+ rootfs++;
+ if (mfp->f_type != NULL && strcmp(mfp->f_type, "kernel") == 0)
+ kernel++;
+ }
+
+ if (num == 0 || rootfs == 0) {
+ /* We need at least one module - rootfs. */
+ printf("No rootfs module provided, aborting\n");
+ error = EINVAL;
+ goto error;
+ }
+ if (xen == 1 && kernel == 0) {
+ printf("No kernel module provided for xen, aborting\n");
+ error = EINVAL;
+ goto error;
+ }
+ mb_mod = (struct multiboot_mod_list *) PTOV(last_addr);
+ last_addr += roundup(sizeof(*mb_mod) * num, MULTIBOOT_INFO_ALIGN);
+
+ bzero(mb_mod, sizeof(*mb_mod) * num);
+
+ num = 0;
+ for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
+ mb_mod[num].mod_start = mfp->f_addr;
+ mb_mod[num].mod_end = mfp->f_addr + mfp->f_size;
+
+ if (strcmp(mfp->f_type, "kernel") == 0) {
+ cmdline = NULL;
+ error = mb_kernel_cmdline(mfp, rootdev, &cmdline);
+ if (error != 0)
+ goto error;
+ } else {
+ len = strlen(mfp->f_name) + 1;
+ len += strlen(mfp->f_type) + 5 + 1;
+ if (mfp->f_args != NULL) {
+ len += strlen(mfp->f_args) + 1;
+ }
+ cmdline = malloc(len);
+ if (cmdline == NULL) {
+ error = ENOMEM;
+ goto error;
+ }
+
+ if (mfp->f_args != NULL)
+ snprintf(cmdline, len, "%s type=%s %s",
+ mfp->f_name, mfp->f_type, mfp->f_args);
+ else
+ snprintf(cmdline, len, "%s type=%s",
+ mfp->f_name, mfp->f_type);
+ }
+
+ mb_mod[num].cmdline = mb_malloc(strlen(cmdline)+1);
+ i386_copyin(cmdline, mb_mod[num].cmdline, strlen(cmdline)+1);
+ free(cmdline);
+ num++;
+ }
+
+ mb_info->mods_count = num;
+ mb_info->mods_addr = VTOP(mb_mod);
+ mb_info->flags |= MULTIBOOT_INFO_MODS;
+
+ md = file_findmetadata(fp, MODINFOMD_SMAP);
+ if (md == NULL) {
+ printf("no memory smap\n");
+ error = EINVAL;
+ goto error;
+ }
+
+ num = md->md_size / sizeof(struct bios_smap); /* number of entries */
+ mmap = (multiboot_memory_map_t *)PTOV(mb_malloc(sizeof(*mmap) * num));
+
+ mb_info->mmap_length = num * sizeof(*mmap);
+ smap = (struct bios_smap *)md->md_data;
+
+ for (i = 0; i < num; i++) {
+ mmap[i].size = sizeof(*smap);
+ mmap[i].addr = smap[i].base;
+ mmap[i].len = smap[i].length;
+ mmap[i].type = smap[i].type;
+ }
+ mb_info->mmap_addr = VTOP(mmap);
+ mb_info->flags |= MULTIBOOT_INFO_MEM_MAP;
+
+ if (strstr(getenv("loaddev"), "net") != NULL &&
+ bootp_response != NULL) {
+ mb_info->drives_length = bootp_response_size;
+ mb_info->drives_addr = mb_malloc(bootp_response_size);
+ i386_copyin(bootp_response, mb_info->drives_addr,
+ bootp_response_size);
+ mb_info->flags &= ~MULTIBOOT_INFO_DRIVE_INFO;
+ }
+ /*
+ * Set the image command line. Need to do this as last thing,
+ * as illumos kernel dboot_startkern will check cmdline
+ * address as last check to find first free address.
+ */
+ if (fp->f_args == NULL) {
+ if (xen)
+ cmdline = getenv("xen_cmdline");
+ else
+ cmdline = getenv("boot-args");
+ if (cmdline != NULL) {
+ fp->f_args = strdup(cmdline);
+ if (fp->f_args == NULL) {
+ error = ENOMEM;
+ goto error;
+ }
+ }
+ }
+
+ /*
+ * If the image is xen, we just use f_name + f_args for commandline
+ * for unix, we need to add zfs-bootfs.
+ */
+ if (xen) {
+ len = strlen(fp->f_name) + 1;
+ if (fp->f_args != NULL)
+ len += strlen(fp->f_args) + 1;
+
+ if (fp->f_args != NULL) {
+ if((cmdline = malloc(len)) == NULL) {
+ error = ENOMEM;
+ goto error;
+ }
+ snprintf(cmdline, len, "%s %s", fp->f_name, fp->f_args);
+ } else {
+ cmdline = strdup(fp->f_name);
+ if (cmdline == NULL) {
+ error = ENOMEM;
+ goto error;
+ }
+ }
+ } else {
+ cmdline = NULL;
+ if ((error = mb_kernel_cmdline(fp, rootdev, &cmdline)) != 0)
+ goto error;
+ }
+
+ mb_info->cmdline = mb_malloc(strlen(cmdline)+1);
+ i386_copyin(cmdline, mb_info->cmdline, strlen(cmdline)+1);
+ mb_info->flags |= MULTIBOOT_INFO_CMDLINE;
+ free(cmdline);
+ cmdline = NULL;
+
+ /* make sure we have text mode */
+ bios_set_text_mode(VGA_TEXT_MODE);
+
+ dev_cleanup();
+ __exec((void *)VTOP(multiboot_tramp), MULTIBOOT_BOOTLOADER_MAGIC,
+ (void *)entry, (void *)VTOP(mb_info));
+
+ panic("exec returned");
+
+error:
+ free(cmdline);
+ return (error);
+}
+
+static int
+multiboot_obj_loadfile(char *filename, u_int64_t dest,
+ struct preloaded_file **result)
+{
+ struct preloaded_file *mfp, *kfp, *rfp;
+ int error, mod_num;
+
+ /* See if there's a aout multiboot kernel loaded */
+ mfp = file_findfile(NULL, "aout multiboot kernel");
+ if (mfp != NULL) {
+ /* we have normal kernel loaded, add module */
+ rfp = file_loadraw(filename, "module", 0, NULL, 0);
+ if (rfp == NULL) {
+ printf(
+ "Unable to load %s as a multiboot payload module\n",
+ filename);
+ return (EINVAL);
+ }
+ rfp->f_size = roundup(rfp->f_size, PAGE_SIZE);
+ *result = rfp;
+ return (0);
+ }
+
+ /* See if there's a multiboot kernel loaded */
+ mfp = file_findfile(NULL, "elf multiboot kernel");
+ if (mfp == NULL) {
+ return (EFTYPE); /* this allows to check other methods */
+ }
+
+ /*
+ * We have a multiboot kernel loaded, see if there's a
+ * kernel loaded also.
+ */
+ kfp = file_findfile(NULL, "elf kernel");
+ if (kfp == NULL) {
+ /*
+ * No kernel loaded, this must be it. The kernel has to
+ * be loaded as a raw file, it will be processed by
+ * Xen and correctly loaded as an ELF file.
+ */
+ rfp = file_loadraw(filename, "elf kernel", 0, NULL, 0);
+ if (rfp == NULL) {
+ printf(
+ "Unable to load %s as a multiboot payload kernel\n",
+ filename);
+ return (EINVAL);
+ }
+
+ /* Load kernel metadata... */
+ setenv("kernelname", filename, 1);
+ error = elf64_load_modmetadata(rfp, rfp->f_addr + rfp->f_size);
+ if (error) {
+ printf("Unable to load kernel %s metadata error: %d\n",
+ rfp->f_name, error);
+ return (EINVAL);
+ }
+
+ /*
+ * Save space at the end of the kernel in order to place
+ * the metadata information. We do an approximation of the
+ * max metadata size, this is not optimal but it's probably
+ * the best we can do at this point. Once all modules are
+ * loaded and the size of the metadata is known this
+ * space will be recovered if not used.
+ */
+ mod_num = num_modules(rfp);
+ rfp->f_size = roundup(rfp->f_size, PAGE_SIZE);
+ rfp->f_size += METADATA_RESV_SIZE(mod_num);
+ *result = rfp;
+ } else {
+ /* The rest should be loaded as regular modules */
+ error = elf64_obj_loadfile(filename, dest, result);
+ if (error != 0) {
+ printf("Unable to load %s as an object file, error: %d",
+ filename, error);
+ return (error);
+ }
+ }
+
+ return (0);
+}
+
+static int
+multiboot_obj_exec(struct preloaded_file *fp __unused)
+{
+
+ return (EFTYPE);
+}