blob: c0477ecc1a8712b011dbcecb544af4f0c81117df [file] [log] [blame]
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright 2017 Toomas Soome <tsoome@me.com>
*/
/*
* This module adds support for loading and booting illumos multiboot2
* kernel. This code is only built to support the illumos kernel, it does
* not support xen.
*/
#include <sys/cdefs.h>
#include <sys/stddef.h>
#include <sys/param.h>
#include <sys/exec.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/stdint.h>
#include <sys/multiboot2.h>
#include <stand.h>
#include <stdbool.h>
#include <machine/elf.h>
#include "libzfs.h"
#include "bootstrap.h"
#include <machine/metadata.h>
#include <machine/pc/bios.h>
#define SUPPORT_DHCP
#include <bootp.h>
#if !defined(EFI)
#include "../i386/libi386/libi386.h"
#include "../i386/btx/lib/btxv86.h"
#else
#include <efi.h>
#include <efilib.h>
#include "loader_efi.h"
static void (*trampoline)(uint32_t, struct relocator *, uint64_t);
#endif
#include "platform/acfreebsd.h"
#include "acconfig.h"
#define ACPI_SYSTEM_XFACE
#include "actypes.h"
#include "actbl.h"
extern ACPI_TABLE_RSDP *rsdp;
/* MB data heap pointer. */
static vm_offset_t last_addr;
static int multiboot2_loadfile(char *, u_int64_t, struct preloaded_file **);
static int multiboot2_exec(struct preloaded_file *);
struct file_format multiboot2 = { multiboot2_loadfile, multiboot2_exec };
static bool keep_bs = false;
static bool have_framebuffer = false;
static vm_offset_t load_addr;
static vm_offset_t entry_addr;
/*
* Validate tags in info request. This function is provided just to
* recognize the current tag list and only serves as a limited
* safe guard against possibly corrupt information.
*/
static bool
is_info_request_valid(multiboot_header_tag_information_request_t *rtag)
{
int i;
/*
* If the tag is optional and we do not support it, we do not
* have to do anything special, so we skip optional tags.
*/
if (rtag->mbh_flags & MULTIBOOT_HEADER_TAG_OPTIONAL)
return (true);
for (i = 0; i < (rtag->mbh_size - sizeof (*rtag)) /
sizeof (rtag->mbh_requests[0]); i++)
switch (rtag->mbh_requests[i]) {
case MULTIBOOT_TAG_TYPE_END:
case MULTIBOOT_TAG_TYPE_CMDLINE:
case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME:
case MULTIBOOT_TAG_TYPE_MODULE:
case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
case MULTIBOOT_TAG_TYPE_BOOTDEV:
case MULTIBOOT_TAG_TYPE_MMAP:
case MULTIBOOT_TAG_TYPE_FRAMEBUFFER:
case MULTIBOOT_TAG_TYPE_VBE:
case MULTIBOOT_TAG_TYPE_ELF_SECTIONS:
case MULTIBOOT_TAG_TYPE_APM:
case MULTIBOOT_TAG_TYPE_EFI32:
case MULTIBOOT_TAG_TYPE_EFI64:
case MULTIBOOT_TAG_TYPE_ACPI_OLD:
case MULTIBOOT_TAG_TYPE_ACPI_NEW:
case MULTIBOOT_TAG_TYPE_NETWORK:
case MULTIBOOT_TAG_TYPE_EFI_MMAP:
case MULTIBOOT_TAG_TYPE_EFI_BS:
case MULTIBOOT_TAG_TYPE_EFI32_IH:
case MULTIBOOT_TAG_TYPE_EFI64_IH:
case MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR:
break;
default:
printf("unsupported information tag: 0x%x\n",
rtag->mbh_requests[i]);
return (false);
}
return (true);
}
static int
multiboot2_loadfile(char *filename, u_int64_t dest,
struct preloaded_file **result)
{
int fd, error;
uint32_t i;
struct stat st;
caddr_t header_search;
multiboot2_header_t *header;
multiboot_header_tag_t *tag;
multiboot_header_tag_address_t *addr_tag = NULL;
multiboot_header_tag_entry_address_t *entry_tag = NULL;
struct preloaded_file *fp;
/* This allows to check other file formats from file_formats array. */
error = EFTYPE;
if (filename == NULL)
return (error);
/* is kernel already loaded? */
fp = file_findfile(NULL, NULL);
if (fp != NULL)
return (error);
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);
}
if (read(fd, header_search, MULTIBOOT_SEARCH) != MULTIBOOT_SEARCH)
goto out;
header = NULL;
for (i = 0; i <= (MULTIBOOT_SEARCH - sizeof (multiboot2_header_t));
i += MULTIBOOT_HEADER_ALIGN) {
header = (multiboot2_header_t *)(header_search + i);
/* Do we have match on magic? */
if (header->mb2_magic != MULTIBOOT2_HEADER_MAGIC) {
header = NULL;
continue;
}
/*
* Validate checksum, the sum of magic + architecture +
* header_length + checksum must equal 0.
*/
if (header->mb2_magic + header->mb2_architecture +
header->mb2_header_length + header->mb2_checksum != 0) {
header = NULL;
continue;
}
/*
* Finally, the entire header must fit within MULTIBOOT_SEARCH.
*/
if (i + header->mb2_header_length > MULTIBOOT_SEARCH) {
header = NULL;
continue;
}
break;
}
if (header == NULL)
goto out;
for (tag = header->mb2_tags; tag->mbh_type != MULTIBOOT_TAG_TYPE_END;
tag = (multiboot_header_tag_t *)((uintptr_t)tag +
roundup2(tag->mbh_size, MULTIBOOT_TAG_ALIGN))) {
switch (tag->mbh_type) {
case MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST:
if (is_info_request_valid((void*)tag) == false)
goto out;
break;
case MULTIBOOT_HEADER_TAG_ADDRESS:
addr_tag = (multiboot_header_tag_address_t *)tag;
break;
case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS:
entry_tag =
(multiboot_header_tag_entry_address_t *)tag;
break;
case MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS:
break;
case MULTIBOOT_HEADER_TAG_FRAMEBUFFER:
have_framebuffer = true;
break;
case MULTIBOOT_HEADER_TAG_MODULE_ALIGN:
/* we always align modules */
break;
case MULTIBOOT_HEADER_TAG_EFI_BS:
keep_bs = true;
break;
default:
if (!(tag->mbh_flags & MULTIBOOT_HEADER_TAG_OPTIONAL)) {
printf("unsupported tag: 0x%x\n",
tag->mbh_type);
goto out;
}
}
}
/*
* We must have addr_tag and entry_tag to load a 64-bit kernel.
* If these tags are missing, we either have a 32-bit kernel, or
* this is not our kernel at all.
*/
if (addr_tag != NULL && entry_tag != NULL) {
fp = file_alloc();
if (fp == NULL) {
error = ENOMEM;
goto out;
}
if (lseek(fd, 0, SEEK_SET) == -1) {
printf("lseek failed\n");
error = EIO;
file_discard(fp);
goto out;
}
if (fstat(fd, &st) < 0) {
printf("fstat failed\n");
error = EIO;
file_discard(fp);
goto out;
}
load_addr = addr_tag->mbh_load_addr;
entry_addr = entry_tag->mbh_entry_addr;
fp->f_addr = archsw.arch_loadaddr(LOAD_KERN, filename,
addr_tag->mbh_load_addr);
if (fp->f_addr == 0) {
error = ENOMEM;
file_discard(fp);
goto out;
}
fp->f_size = archsw.arch_readin(fd, fp->f_addr, st.st_size);
if (fp->f_size != st.st_size) {
printf("error reading: %s", strerror(errno));
file_discard(fp);
error = EIO;
goto out;
}
fp->f_name = strdup(filename);
fp->f_type = strdup("aout multiboot2 kernel");
if (fp->f_name == NULL || fp->f_type == NULL) {
error = ENOMEM;
file_discard(fp);
goto out;
}
fp->f_metadata = NULL;
error = 0;
} else {
#if defined(EFI)
/* 32-bit kernel is not yet supported for EFI */
printf("32-bit kernel is not supported by UEFI loader\n");
error = ENOTSUP;
goto out;
#endif
/* elf32_loadfile_raw will fill the attributes in fp. */
error = elf32_loadfile_raw(filename, dest, &fp, 2);
if (error != 0) {
printf("elf32_loadfile_raw failed: %d unable to "
"load multiboot2 kernel\n", error);
goto out;
}
entry_addr = fp->f_addr;
/*
* We want the load_addr to have some legal value,
* so we set it same as the entry_addr.
* The distinction is important with UEFI, but not
* with BIOS version, because BIOS version does not use
* staging area.
*/
load_addr = fp->f_addr;
}
setenv("kernelname", fp->f_name, 1);
#if defined(EFI)
efi_addsmapdata(fp);
#else
bios_addsmapdata(fp);
#endif
*result = fp;
out:
free(header_search);
close(fd);
return (error);
}
/*
* Search the command line for named property.
*
* Return codes:
* 0 The name is found, we return the data in value and len.
* ENOENT The name is not found.
* EINVAL The provided command line is badly formed.
*/
static int
find_property_value(const char *cmd, const char *name, const char **value,
size_t *len)
{
const char *namep, *valuep;
size_t name_len, value_len;
int quoted;
*value = NULL;
*len = 0;
if (cmd == NULL)
return (ENOENT);
while (*cmd != '\0') {
if (cmd[0] != '-' || cmd[1] != 'B') {
cmd++;
continue;
}
cmd += 2; /* Skip -B */
while (cmd[0] == ' ' || cmd[0] == '\t')
cmd++; /* Skip whitespaces. */
while (*cmd != '\0' && cmd[0] != ' ' && cmd[0] != '\t') {
namep = cmd;
valuep = strchr(cmd, '=');
if (valuep == NULL)
break;
name_len = valuep - namep;
valuep++;
value_len = 0;
quoted = 0;
for (; ; ++value_len) {
if (valuep[value_len] == '\0')
break;
/* Is this value quoted? */
if (value_len == 0 &&
(valuep[0] == '\'' || valuep[0] == '"')) {
quoted = valuep[0];
++value_len;
}
/*
* In the quote accept any character,
* but look for ending quote.
*/
if (quoted != 0) {
if (valuep[value_len] == quoted)
quoted = 0;
continue;
}
/* A comma or white space ends the value. */
if (valuep[value_len] == ',' ||
valuep[value_len] == ' ' ||
valuep[value_len] == '\t')
break;
}
if (quoted != 0) {
printf("Missing closing '%c' in \"%s\"\n",
quoted, valuep);
return (EINVAL);
}
if (value_len != 0) {
if (strncmp(namep, name, name_len) == 0) {
*value = valuep;
*len = value_len;
return (0);
}
}
cmd = valuep + value_len;
while (*cmd == ',')
cmd++;
}
}
return (ENOENT);
}
/*
* If command line has " -B ", insert property after "-B ", otherwise
* append to command line.
*/
static char *
insert_cmdline(const char *head, const char *prop)
{
const char *prop_opt = " -B ";
char *cmdline, *tail;
int len = 0;
tail = strstr(head, prop_opt);
if (tail != NULL) {
ptrdiff_t diff;
tail += strlen(prop_opt);
diff = tail - head;
if (diff >= INT_MAX)
return (NULL);
len = (int)diff;
}
if (tail == NULL)
asprintf(&cmdline, "%s%s%s", head, prop_opt, prop);
else
asprintf(&cmdline, "%.*s%s,%s", len, head, prop, tail);
return (cmdline);
}
/*
* Since we have no way to pass the environment to the mb1 kernel other than
* through arguments, we need to take care of console setup.
*
* If the console is in mirror mode, set the kernel console from $os_console.
* If it's unset, use first item from $console.
* If $console is "ttyX", also pass $ttyX-mode, since it may have been set by
* the user.
*
* In case of memory allocation errors, just return the original command line
* so we have a chance of booting.
*
* On success, cl will be freed and a new, allocated command line string is
* returned.
*
* For the mb2 kernel, we only set command line console if os_console is set.
* We can not overwrite console in the environment, as it can disrupt the
* loader console messages, and we do not want to deal with the os_console
* in the kernel.
*/
static char *
update_cmdline(char *cl, bool mb2)
{
char *os_console = getenv("os_console");
char *ttymode = NULL;
char mode[10];
char *tmp;
const char *prop;
size_t plen;
int rv;
if (mb2 == true && os_console == NULL)
return (cl);
if (os_console == NULL) {
tmp = strdup(getenv("console"));
os_console = strsep(&tmp, ", ");
} else {
os_console = strdup(os_console);
}
if (os_console == NULL)
return (cl);
if (mb2 == false && strncmp(os_console, "tty", 3) == 0) {
snprintf(mode, sizeof (mode), "%s-mode", os_console);
/*
* The ttyX-mode variable is set by our serial console
* driver for ttya-ttyd. However, since the os_console
* values are not verified, it is possible we get bogus
* name and no mode variable. If so, we do not set console
* property and let the kernel use defaults.
*/
if ((ttymode = getenv(mode)) == NULL)
return (cl);
}
rv = find_property_value(cl, "console", &prop, &plen);
if (rv != 0 && rv != ENOENT) {
free(os_console);
return (cl);
}
/* If console is set and this is MB2 boot, we are done. */
if (rv == 0 && mb2 == true) {
free(os_console);
return (cl);
}
/* If console is set, do we need to set tty mode? */
if (rv == 0) {
const char *ttyp = NULL;
size_t ttylen;
free(os_console);
os_console = NULL;
*mode = '\0';
if (strncmp(prop, "tty", 3) == 0 && plen == 4) {
strncpy(mode, prop, plen);
mode[plen] = '\0';
strncat(mode, "-mode", 5);
find_property_value(cl, mode, &ttyp, &ttylen);
}
if (*mode != '\0' && ttyp == NULL)
ttymode = getenv(mode);
else
return (cl);
}
/* Build updated command line. */
if (os_console != NULL) {
char *propstr;
asprintf(&propstr, "console=%s", os_console);
free(os_console);
if (propstr == NULL) {
return (cl);
}
tmp = insert_cmdline(cl, propstr);
free(propstr);
if (tmp == NULL)
return (cl);
free(cl);
cl = tmp;
}
if (ttymode != NULL) {
char *propstr;
asprintf(&propstr, "%s=\"%s\"", mode, ttymode);
if (propstr == NULL)
return (cl);
tmp = insert_cmdline(cl, propstr);
free(propstr);
if (tmp == NULL)
return (cl);
free(cl);
cl = tmp;
}
return (cl);
}
/*
* Build the kernel command line. Shared function between MB1 and MB2.
*
* In both cases, if fstype is set and is not zfs, we do not set up
* zfs-bootfs property. But we set kernel file name and options.
*
* For the MB1, we only can pass properties on command line, so
* we will set console, ttyX-mode (for serial console) and zfs-bootfs.
*
* For the MB2, we can pass properties in environment, but if os_console
* is set in environment, we need to add console property on the kernel
* command line.
*
* The console properties are managed in update_cmdline().
*/
int
mb_kernel_cmdline(struct preloaded_file *fp, struct devdesc *rootdev,
char **line)
{
const char *fs = getenv("fstype");
char *cmdline;
size_t len;
bool zfs_root = false;
bool mb2;
int rv;
/*
* 64-bit kernel has aout header, 32-bit kernel is elf, and the
* type strings are different. Lets just search for "multiboot2".
*/
if (strstr(fp->f_type, "multiboot2") == NULL)
mb2 = false;
else
mb2 = true;
if (rootdev->d_dev->dv_type == DEVT_ZFS)
zfs_root = true;
/* If we have fstype set in env, reset zfs_root if needed. */
if (fs != NULL && strcmp(fs, "zfs") != 0)
zfs_root = false;
/*
* If we have fstype set on the command line,
* reset zfs_root if needed.
*/
rv = find_property_value(fp->f_args, "fstype", &fs, &len);
if (rv != 0 && rv != ENOENT)
return (rv);
if (fs != NULL && strncmp(fs, "zfs", len) != 0)
zfs_root = false;
/* zfs_bootfs() will set the environment, it must be called. */
if (zfs_root == true)
fs = zfs_bootfs(rootdev);
if (fp->f_args == NULL)
cmdline = strdup(fp->f_name);
else
asprintf(&cmdline, "%s %s", fp->f_name, fp->f_args);
if (cmdline == NULL)
return (ENOMEM);
/* Append zfs-bootfs for MB1 command line. */
if (mb2 == false && zfs_root == true) {
char *tmp;
tmp = insert_cmdline(cmdline, fs);
free(cmdline);
if (tmp == NULL)
return (ENOMEM);
cmdline = tmp;
}
*line = update_cmdline(cmdline, mb2);
return (0);
}
/*
* Returns allocated virtual address from MB info area.
*/
static vm_offset_t
mb_malloc(size_t n)
{
vm_offset_t ptr = last_addr;
last_addr = roundup(last_addr + n, MULTIBOOT_TAG_ALIGN);
return (ptr);
}
/*
* Calculate size for module tag list.
*/
static size_t
module_size(struct preloaded_file *fp)
{
size_t len, size;
struct preloaded_file *mfp;
size = 0;
for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
len = strlen(mfp->f_name) + 1;
len += strlen(mfp->f_type) + 5 + 1; /* 5 is for "type=" */
if (mfp->f_args != NULL)
len += strlen(mfp->f_args) + 1;
size += sizeof (multiboot_tag_module_t) + len;
size = roundup(size, MULTIBOOT_TAG_ALIGN);
}
return (size);
}
#if defined (EFI)
/*
* Calculate size for UEFI memory map tag.
*/
static int
efimemmap_size(void)
{
UINTN size, cur_size, desc_size;
EFI_MEMORY_DESCRIPTOR *mmap;
EFI_STATUS ret;
size = EFI_PAGE_SIZE; /* Start with 4k. */
while (1) {
cur_size = size;
mmap = malloc(cur_size);
if (mmap == NULL)
return (0);
ret = BS->GetMemoryMap(&cur_size, mmap, NULL, &desc_size, NULL);
free(mmap);
if (ret == EFI_SUCCESS)
break;
if (ret == EFI_BUFFER_TOO_SMALL) {
if (size < cur_size)
size = cur_size;
size += (EFI_PAGE_SIZE);
} else
return (0);
}
/* EFI MMAP will grow when we allocate MBI, set some buffer. */
size += (3 << EFI_PAGE_SHIFT);
size = roundup(size, desc_size);
return (sizeof (multiboot_tag_efi_mmap_t) + size);
}
#endif
/*
* Calculate size for bios smap tag.
*/
static size_t
biossmap_size(struct preloaded_file *fp)
{
int num;
struct file_metadata *md;
md = file_findmetadata(fp, MODINFOMD_SMAP);
if (md == NULL)
return (0);
num = md->md_size / sizeof(struct bios_smap); /* number of entries */
return (sizeof (multiboot_tag_mmap_t) +
num * sizeof (multiboot_mmap_entry_t));
}
static size_t
mbi_size(struct preloaded_file *fp, char *cmdline)
{
size_t size;
size = sizeof (uint32_t) * 2; /* first 2 fields from MBI header */
size += sizeof (multiboot_tag_string_t) + strlen(cmdline) + 1;
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
size += sizeof (multiboot_tag_string_t) + strlen(bootprog_info) + 1;
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
#if !defined (EFI)
size += sizeof (multiboot_tag_basic_meminfo_t);
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
#endif
size += module_size(fp);
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
#if defined (EFI)
size += sizeof (multiboot_tag_efi64_t);
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
size += efimemmap_size();
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
if (have_framebuffer == true) {
size += sizeof (multiboot_tag_framebuffer_t);
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
}
#endif
size += biossmap_size(fp);
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
if (bootp_response != NULL) {
size += sizeof(multiboot_tag_network_t) + bootp_response_size;
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
}
if (rsdp != NULL) {
if (rsdp->Revision == 0) {
size += sizeof (multiboot_tag_old_acpi_t) +
sizeof(ACPI_RSDP_COMMON);
} else {
size += sizeof (multiboot_tag_new_acpi_t) +
rsdp->Length;
}
size = roundup2(size, MULTIBOOT_TAG_ALIGN);
}
size += sizeof(multiboot_tag_t);
return (size);
}
static int
multiboot2_exec(struct preloaded_file *fp)
{
struct preloaded_file *mfp;
multiboot2_info_header_t *mbi;
char *cmdline = NULL;
struct devdesc *rootdev;
struct file_metadata *md;
int i, error, num;
int rootfs = 0;
size_t size;
struct bios_smap *smap;
#if defined (EFI)
multiboot_tag_module_t *module, *mp;
EFI_MEMORY_DESCRIPTOR *map;
UINTN map_size, desc_size;
struct relocator *relocator;
struct chunk_head *head;
struct chunk *chunk;
vm_offset_t tmp;
efi_getdev((void **)(&rootdev), NULL, NULL);
#else
i386_getdev((void **)(&rootdev), NULL, NULL);
#endif
mbi = NULL;
error = EINVAL;
if (rootdev == NULL) {
printf("can't determine root device\n");
goto error;
}
/*
* Set the image command line.
*/
if (fp->f_args == NULL) {
cmdline = getenv("boot-args");
if (cmdline != NULL) {
fp->f_args = strdup(cmdline);
if (fp->f_args == NULL) {
error = ENOMEM;
goto error;
}
}
}
error = mb_kernel_cmdline(fp, rootdev, &cmdline);
if (error != 0)
goto error;
/* mb_kernel_cmdline() updates the environment. */
build_environment_module();
size = mbi_size(fp, cmdline); /* Get the size for MBI. */
/* Set up the base for mb_malloc. */
i = 0;
for (mfp = fp; mfp->f_next != NULL; mfp = mfp->f_next)
i++;
#if defined (EFI)
/* We need space for kernel + MBI + # modules */
num = (EFI_PAGE_SIZE - offsetof(struct relocator, rel_chunklist)) /
sizeof (struct chunk);
if (i + 2 >= num) {
printf("Too many modules, do not have space for relocator.\n");
error = ENOMEM;
goto error;
}
last_addr = efi_loadaddr(LOAD_MEM, &size, mfp->f_addr + mfp->f_size);
mbi = (multiboot2_info_header_t *)last_addr;
if (mbi == NULL) {
error = ENOMEM;
goto error;
}
last_addr = (vm_offset_t)mbi->mbi_tags;
#else
/* Start info block from the new page. */
last_addr = i386_loadaddr(LOAD_MEM, &size, mfp->f_addr + mfp->f_size);
/* Do we have space for multiboot info? */
if (last_addr + size >= memtop_copyin) {
error = ENOMEM;
goto error;
}
mbi = (multiboot2_info_header_t *)PTOV(last_addr);
last_addr = (vm_offset_t)mbi->mbi_tags;
#endif /* EFI */
{
multiboot_tag_string_t *tag;
i = sizeof (multiboot_tag_string_t) + strlen(cmdline) + 1;
tag = (multiboot_tag_string_t *) mb_malloc(i);
tag->mb_type = MULTIBOOT_TAG_TYPE_CMDLINE;
tag->mb_size = i;
memcpy(tag->mb_string, cmdline, strlen(cmdline) + 1);
free(cmdline);
cmdline = NULL;
}
{
multiboot_tag_string_t *tag;
i = sizeof (multiboot_tag_string_t) + strlen(bootprog_info) + 1;
tag = (multiboot_tag_string_t *) mb_malloc(i);
tag->mb_type = MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME;
tag->mb_size = i;
memcpy(tag->mb_string, bootprog_info,
strlen(bootprog_info) + 1);
}
#if !defined (EFI)
/* Only set in case of BIOS. */
{
multiboot_tag_basic_meminfo_t *tag;
tag = (multiboot_tag_basic_meminfo_t *)
mb_malloc(sizeof (*tag));
tag->mb_type = MULTIBOOT_TAG_TYPE_BASIC_MEMINFO;
tag->mb_size = sizeof (*tag);
tag->mb_mem_lower = bios_basemem / 1024;
tag->mb_mem_upper = bios_extmem / 1024;
}
#endif
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 (num == 0 || rootfs == 0) {
/* We need at least one module - rootfs. */
printf("No rootfs module provided, aborting\n");
error = EINVAL;
goto error;
}
/*
* Set the stage for physical memory layout:
* - We have kernel at load_addr.
* - Modules are aligned to page boundary.
* - MBI is aligned to page boundary.
* - Set the tmp to point to physical address of the first module.
* - tmp != mfp->f_addr only in case of EFI.
*/
#if defined (EFI)
tmp = roundup2(load_addr + fp->f_size + 1, MULTIBOOT_MOD_ALIGN);
module = (multiboot_tag_module_t *)last_addr;
#endif
for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
multiboot_tag_module_t *tag;
num = strlen(mfp->f_name) + 1;
num += strlen(mfp->f_type) + 5 + 1;
if (mfp->f_args != NULL) {
num += strlen(mfp->f_args) + 1;
}
cmdline = malloc(num);
if (cmdline == NULL) {
error = ENOMEM;
goto error;
}
if (mfp->f_args != NULL)
snprintf(cmdline, num, "%s type=%s %s",
mfp->f_name, mfp->f_type, mfp->f_args);
else
snprintf(cmdline, num, "%s type=%s",
mfp->f_name, mfp->f_type);
tag = (multiboot_tag_module_t *)mb_malloc(sizeof (*tag) + num);
tag->mb_type = MULTIBOOT_TAG_TYPE_MODULE;
tag->mb_size = sizeof (*tag) + num;
#if defined (EFI)
/*
* We can assign module addresses only after BS have been
* switched off.
*/
tag->mb_mod_start = 0;
tag->mb_mod_end = mfp->f_size;
#else
tag->mb_mod_start = mfp->f_addr;
tag->mb_mod_end = mfp->f_addr + mfp->f_size;
#endif
memcpy(tag->mb_cmdline, cmdline, num);
free(cmdline);
cmdline = NULL;
}
md = file_findmetadata(fp, MODINFOMD_SMAP);
if (md == NULL) {
printf("no memory smap\n");
error = EINVAL;
goto error;
}
smap = (struct bios_smap *)md->md_data;
num = md->md_size / sizeof(struct bios_smap); /* number of entries */
{
multiboot_tag_mmap_t *tag;
multiboot_mmap_entry_t *mmap_entry;
tag = (multiboot_tag_mmap_t *)
mb_malloc(sizeof (*tag) +
num * sizeof (multiboot_mmap_entry_t));
tag->mb_type = MULTIBOOT_TAG_TYPE_MMAP;
tag->mb_size = sizeof (*tag) +
num * sizeof (multiboot_mmap_entry_t);
tag->mb_entry_size = sizeof (multiboot_mmap_entry_t);
tag->mb_entry_version = 0;
mmap_entry = (multiboot_mmap_entry_t *)tag->mb_entries;
for (i = 0; i < num; i++) {
mmap_entry[i].mmap_addr = smap[i].base;
mmap_entry[i].mmap_len = smap[i].length;
mmap_entry[i].mmap_type = smap[i].type;
mmap_entry[i].mmap_reserved = 0;
}
}
if (bootp_response != NULL) {
multiboot_tag_network_t *tag;
tag = (multiboot_tag_network_t *)
mb_malloc(sizeof(*tag) + bootp_response_size);
tag->mb_type = MULTIBOOT_TAG_TYPE_NETWORK;
tag->mb_size = sizeof(*tag) + bootp_response_size;
memcpy(tag->mb_dhcpack, bootp_response, bootp_response_size);
}
if (rsdp != NULL) {
multiboot_tag_new_acpi_t *ntag;
multiboot_tag_old_acpi_t *otag;
uint32_t tsize;
if (rsdp->Revision == 0) {
tsize = sizeof (*otag) + sizeof (ACPI_RSDP_COMMON);
otag = (multiboot_tag_old_acpi_t *)mb_malloc(tsize);
otag->mb_type = MULTIBOOT_TAG_TYPE_ACPI_OLD;
otag->mb_size = tsize;
memcpy(otag->mb_rsdp, rsdp, sizeof (ACPI_RSDP_COMMON));
} else {
tsize = sizeof (*ntag) + rsdp->Length;
ntag = (multiboot_tag_new_acpi_t *)mb_malloc(tsize);
ntag->mb_type = MULTIBOOT_TAG_TYPE_ACPI_NEW;
ntag->mb_size = tsize;
memcpy(ntag->mb_rsdp, rsdp, rsdp->Length);
}
}
#if defined (EFI)
#ifdef __LP64__
{
multiboot_tag_efi64_t *tag;
tag = (multiboot_tag_efi64_t *)
mb_malloc(sizeof (*tag));
tag->mb_type = MULTIBOOT_TAG_TYPE_EFI64;
tag->mb_size = sizeof (*tag);
tag->mb_pointer = (uint64_t)(uintptr_t)ST;
}
#else
{
multiboot_tag_efi32_t *tag;
tag = (multiboot_tag_efi32_t *)
mb_malloc(sizeof (*tag));
tag->mb_type = MULTIBOOT_TAG_TYPE_EFI32;
tag->mb_size = sizeof (*tag);
tag->mb_pointer = (uint32_t)ST;
}
#endif /* __LP64__ */
if (have_framebuffer == true) {
multiboot_tag_framebuffer_t *tag;
int bpp;
struct efi_fb fb;
extern int efi_find_framebuffer(struct efi_fb *efifb);
if (efi_find_framebuffer(&fb) == 0) {
tag = (multiboot_tag_framebuffer_t *)
mb_malloc(sizeof (*tag));
/*
* We assume contiguous color bitmap, and use
* the msb for bits per pixel calculation.
*/
bpp = fls(fb.fb_mask_red | fb.fb_mask_green |
fb.fb_mask_blue | fb.fb_mask_reserved);
tag->framebuffer_common.mb_type =
MULTIBOOT_TAG_TYPE_FRAMEBUFFER;
tag->framebuffer_common.mb_size =
sizeof (multiboot_tag_framebuffer_t);
tag->framebuffer_common.framebuffer_addr = fb.fb_addr;
tag->framebuffer_common.framebuffer_width = fb.fb_width;
tag->framebuffer_common.framebuffer_height =
fb.fb_height;
tag->framebuffer_common.framebuffer_bpp = bpp;
/*
* Pitch is stride * bytes per pixel.
* Stride is pixels per scanline.
*/
tag->framebuffer_common.framebuffer_pitch =
fb.fb_stride * (bpp / 8);
tag->framebuffer_common.framebuffer_type =
MULTIBOOT_FRAMEBUFFER_TYPE_RGB;
tag->framebuffer_common.mb_reserved = 0;
/*
* The RGB or BGR color ordering.
*/
if (fb.fb_mask_red & 0x000000ff) {
tag->u.fb2.framebuffer_red_field_position = 0;
tag->u.fb2.framebuffer_blue_field_position = 16;
} else {
tag->u.fb2.framebuffer_red_field_position = 16;
tag->u.fb2.framebuffer_blue_field_position = 0;
}
tag->u.fb2.framebuffer_red_mask_size = 8;
tag->u.fb2.framebuffer_green_field_position = 8;
tag->u.fb2.framebuffer_green_mask_size = 8;
tag->u.fb2.framebuffer_blue_mask_size = 8;
}
}
/* Leave EFI memmap last as we will also switch off the BS. */
{
multiboot_tag_efi_mmap_t *tag;
UINTN key;
EFI_STATUS status;
tag = (multiboot_tag_efi_mmap_t *)
mb_malloc(sizeof (*tag));
map_size = 0;
status = BS->GetMemoryMap(&map_size,
(EFI_MEMORY_DESCRIPTOR *)tag->mb_efi_mmap, &key,
&desc_size, &tag->mb_descr_vers);
if (status != EFI_BUFFER_TOO_SMALL) {
error = EINVAL;
goto error;
}
status = BS->GetMemoryMap(&map_size,
(EFI_MEMORY_DESCRIPTOR *)tag->mb_efi_mmap, &key,
&desc_size, &tag->mb_descr_vers);
if (EFI_ERROR(status)) {
error = EINVAL;
goto error;
}
tag->mb_type = MULTIBOOT_TAG_TYPE_EFI_MMAP;
tag->mb_size = sizeof (*tag) + map_size;
tag->mb_descr_size = (uint32_t) desc_size;
/*
* Find relocater pages. We assume we have free pages
* below kernel load address.
* In this version we are using 5 pages:
* relocator data, trampoline, copy, memmove, stack.
*/
for (i = 0, map = (EFI_MEMORY_DESCRIPTOR *)tag->mb_efi_mmap;
i < map_size / desc_size;
i++, map = NextMemoryDescriptor(map, desc_size)) {
if (map->PhysicalStart == 0)
continue;
if (map->Type != EfiConventionalMemory)
continue;
if (map->PhysicalStart < load_addr &&
map->NumberOfPages > 5)
break;
}
if (map->PhysicalStart == 0)
panic("Could not find memory for relocater\n");
if (keep_bs == 0) {
status = BS->ExitBootServices(IH, key);
if (EFI_ERROR(status)) {
printf("Call to ExitBootServices failed\n");
error = EINVAL;
goto error;
}
}
last_addr += map_size;
last_addr = roundup2(last_addr, MULTIBOOT_TAG_ALIGN);
}
#endif
/*
* MB tag list end marker.
*/
{
multiboot_tag_t *tag = (multiboot_tag_t *)
mb_malloc(sizeof(*tag));
tag->mb_type = MULTIBOOT_TAG_TYPE_END;
tag->mb_size = sizeof(*tag);
}
mbi->mbi_total_size = last_addr - (vm_offset_t)mbi;
mbi->mbi_reserved = 0;
#if defined (EFI)
/* At this point we have load_addr pointing to kernel load
* address, module list in MBI having physical addresses,
* module list in fp having logical addresses and tmp pointing to
* physical address for MBI.
* Now we must move all pieces to place and start the kernel.
*/
relocator = (struct relocator *)(uintptr_t)map->PhysicalStart;
head = &relocator->rel_chunk_head;
STAILQ_INIT(head);
i = 0;
chunk = &relocator->rel_chunklist[i++];
chunk->chunk_vaddr = fp->f_addr;
chunk->chunk_paddr = load_addr;
chunk->chunk_size = fp->f_size;
STAILQ_INSERT_TAIL(head, chunk, chunk_next);
mp = module;
for (mfp = fp->f_next; mfp != NULL; mfp = mfp->f_next) {
chunk = &relocator->rel_chunklist[i++];
chunk->chunk_vaddr = mfp->f_addr;
/*
* fix the mb_mod_start and mb_mod_end.
*/
mp->mb_mod_start = efi_physaddr(module, tmp, map,
map_size / desc_size, desc_size, mp->mb_mod_end);
if (mp->mb_mod_start == 0)
panic("Could not find memory for module\n");
mp->mb_mod_end += mp->mb_mod_start;
chunk->chunk_paddr = mp->mb_mod_start;
chunk->chunk_size = mfp->f_size;
STAILQ_INSERT_TAIL(head, chunk, chunk_next);
mp = (multiboot_tag_module_t *)
roundup2((uintptr_t)mp + mp->mb_size,
MULTIBOOT_TAG_ALIGN);
}
chunk = &relocator->rel_chunklist[i++];
chunk->chunk_vaddr = (EFI_VIRTUAL_ADDRESS)(uintptr_t)mbi;
chunk->chunk_paddr = efi_physaddr(module, tmp, map,
map_size / desc_size, desc_size, mbi->mbi_total_size);
chunk->chunk_size = mbi->mbi_total_size;
STAILQ_INSERT_TAIL(head, chunk, chunk_next);
trampoline = (void *)(uintptr_t)relocator + EFI_PAGE_SIZE;
memmove(trampoline, multiboot_tramp, EFI_PAGE_SIZE);
relocator->rel_copy = (uintptr_t)trampoline + EFI_PAGE_SIZE;
memmove((void *)relocator->rel_copy, efi_copy_finish, EFI_PAGE_SIZE);
relocator->rel_memmove = (uintptr_t)relocator->rel_copy + EFI_PAGE_SIZE;
memmove((void *)relocator->rel_memmove, memmove, EFI_PAGE_SIZE);
relocator->rel_stack = relocator->rel_memmove + EFI_PAGE_SIZE - 8;
trampoline(MULTIBOOT2_BOOTLOADER_MAGIC, relocator, entry_addr);
#else
dev_cleanup();
__exec((void *)VTOP(multiboot_tramp), MULTIBOOT2_BOOTLOADER_MAGIC,
(void *)entry_addr, (void *)VTOP(mbi));
#endif
panic("exec returned");
error:
if (cmdline != NULL)
free(cmdline);
#if defined (EFI)
if (mbi != NULL)
efi_free_loadaddr((vm_offset_t)mbi, EFI_SIZE_TO_PAGES(size));
#endif
return (error);
}