blob: 832c1d4f87a46d56cffd0b8b2f283524f99cbf47 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "trampoline.h"
#include <inttypes.h>
#include <libzbi/zbi.h>
#include <string.h>
#include <zircon/boot/e820.h>
#define BOOT_LOADER_NAME_ENV "multiboot.boot_loader_name="
static size_t zbi_size(const zbi_header_t* zbi) {
return sizeof(*zbi) + zbi->length;
}
// Convert the multiboot memory information to ZBI_TYPE_E820_TABLE format.
static void add_memory_info(void* zbi, size_t capacity,
const multiboot_info_t* info) {
if ((info->flags & MB_INFO_MMAP) &&
info->mmap_addr != 0 &&
info->mmap_length >= sizeof(memory_map_t)) {
size_t nranges = 0;
for (memory_map_t* mmap = (void*)info->mmap_addr;
(uintptr_t)mmap < info->mmap_addr + info->mmap_length;
mmap = (void*)((uintptr_t)mmap + 4 + mmap->size)) {
++nranges;
}
e820entry_t* ranges;
void* payload;
zbi_result_t result = zbi_create_section(
zbi, capacity, nranges * sizeof(ranges[0]),
ZBI_TYPE_E820_TABLE, 0, 0, &payload);
if (result != ZBI_RESULT_OK) {
panic("zbi_create_section(%p, %#"PRIxPTR", %#zx) failed: %d",
zbi, capacity, nranges * sizeof(ranges[0]), (int)result);
}
ranges = payload;
for (memory_map_t* mmap = (void*)info->mmap_addr;
(uintptr_t)mmap < info->mmap_addr + info->mmap_length;
mmap = (void*)((uintptr_t)mmap + 4 + mmap->size)) {
*ranges++ = (e820entry_t){
.addr = (((uint64_t)mmap->base_addr_high << 32) |
mmap->base_addr_low),
.size = (((uint64_t)mmap->length_high << 32) |
mmap->length_low),
// MB_MMAP_TYPE_* matches E820_* values.
.type = mmap->type,
};
}
} else {
const e820entry_t ranges[] = {
{
.addr = 0,
.size = info->mem_lower << 10,
.type = E820_RAM,
},
{
.addr = 1 << 20,
.size = ((uint64_t)info->mem_upper << 10) - (1 << 20),
.type = E820_RAM,
},
};
zbi_result_t result = zbi_append_section(
zbi, capacity, sizeof(ranges), ZBI_TYPE_E820_TABLE, 0, 0, ranges);
if (result != ZBI_RESULT_OK) {
panic("zbi_append_section(%p, %#"PRIxPTR", %#zx) failed: %d",
zbi, capacity, sizeof(ranges), (int)result);
}
}
}
static void add_cmdline(void* zbi, size_t capacity,
const multiboot_info_t* info) {
// Boot loader command line.
if (info->flags & MB_INFO_CMD_LINE) {
const char* cmdline = (void*)info->cmdline;
size_t len = strlen(cmdline) + 1;
zbi_result_t result = zbi_append_section(
zbi, capacity, len, ZBI_TYPE_CMDLINE, 0, 0, cmdline);
if (result != ZBI_RESULT_OK) {
panic("zbi_append_section(%p, %#"PRIxPTR", %zu) failed: %d",
zbi, capacity, len, (int)result);
}
}
// Boot loader name.
if (info->flags & MB_INFO_BOOT_LOADER) {
const char* name = (void*)info->boot_loader_name;
size_t len = strlen(name) + 1;
void *payload;
zbi_result_t result = zbi_create_section(
zbi, capacity, sizeof(BOOT_LOADER_NAME_ENV) - 1 + len,
ZBI_TYPE_CMDLINE, 0, 0, &payload);
if (result != ZBI_RESULT_OK) {
panic("zbi_create_section(%p, %#"PRIxPTR", %zu) failed: %d",
zbi, capacity, sizeof(BOOT_LOADER_NAME_ENV) - 1 + len,
(int)result);
}
for (char *p = (memcpy(payload, BOOT_LOADER_NAME_ENV,
sizeof(BOOT_LOADER_NAME_ENV) - 1) +
sizeof(BOOT_LOADER_NAME_ENV) - 1);
len > 0;
++p, ++name, --len) {
*p = (*name == ' ' || *name == '\t' ||
*name == '\n' || *name == '\r') ? '+' : *name;
}
}
}
static void add_zbi_items(void* zbi, size_t capacity,
const multiboot_info_t* info) {
add_memory_info(zbi, capacity, info);
add_cmdline(zbi, capacity, info);
}
static zbi_result_t find_kernel_item(zbi_header_t* hdr, void* payload,
void* cookie) {
if (hdr->type == ZBI_TYPE_KERNEL_X64) {
*(const zbi_header_t**)cookie = hdr;
return ZBI_RESULT_INCOMPLETE_KERNEL;
}
return ZBI_RESULT_OK;
}
noreturn void multiboot_main(uint32_t magic, multiboot_info_t* info) {
if (magic != MULTIBOOT_BOOTLOADER_MAGIC) {
panic("bad multiboot magic from bootloader %#"PRIx32" != %#"PRIx32"\n",
magic, (uint32_t)MULTIBOOT_BOOTLOADER_MAGIC);
}
uintptr_t upper_memory_limit = 0;
if (info->flags & MB_INFO_MEM_SIZE) {
upper_memory_limit = info->mem_upper << 10;
if (info->mem_upper > (UINT32_MAX >> 10)) {
upper_memory_limit = -4096u;
}
} else if ((info->flags & MB_INFO_MMAP) &&
info->mmap_addr != 0 &&
info->mmap_length >= sizeof(memory_map_t)) {
for (memory_map_t* mmap = (void*)info->mmap_addr;
(uintptr_t)mmap < info->mmap_addr + info->mmap_length;
mmap = (void*)((uintptr_t)mmap + 4 + mmap->size)) {
if (mmap->type == MB_MMAP_TYPE_AVAILABLE) {
const uint64_t addr = (((uint64_t)mmap->base_addr_high << 32) |
mmap->base_addr_low);
const uint64_t len = (((uint64_t)mmap->length_high << 32) |
mmap->length_low);
const uint64_t end = addr + len;
if (addr <= (uint64_t)(uintptr_t)PHYS_LOAD_ADDRESS &&
end > (uint64_t)(uintptr_t)PHYS_LOAD_ADDRESS) {
if (end > UINT32_MAX) {
upper_memory_limit = -4096u;
} else {
upper_memory_limit = end;
}
break;
}
}
}
if (upper_memory_limit == 0) {
panic("multiboot memory map doesn't cover %#" PRIxPTR,
(uintptr_t)PHYS_LOAD_ADDRESS);
}
} else {
panic("multiboot memory information missing");
}
if (!(info->flags & MB_INFO_MODS)) {
panic("missing multiboot modules");
}
if (info->mods_count != 1) {
panic("cannot handle multiboot mods_count %"PRIu32" != 1\n",
info->mods_count);
}
module_t* const mod = (void*)info->mods_addr;
zbi_header_t* zbi = (void*)mod->mod_start;
size_t zbi_len = mod->mod_end - mod->mod_start;
if (zbi == NULL || zbi_len < sizeof(*zbi)) {
panic("insufficient multiboot module [%#"PRIx32",%#"PRIx32")"
" for ZBI header", mod->mod_start, mod->mod_end);
}
// TODO(mcgrathr): Sanity check disabled for now because Depthcharge as of
// https://chromium.googlesource.com/chromiumos/platform/depthcharge/+/firmware-eve-9584.B
// prepends items and adjusts the ZBI container header, but fails to update
// the Multiboot module_t header to match.
if (zbi_len < sizeof(*zbi) + zbi->length && 0) {
panic("insufficient multiboot module [%#"PRIx32",%#"PRIx32")"
" for ZBI length %#"PRIx32,
mod->mod_start, mod->mod_end, sizeof(*zbi) + zbi->length);
}
// Depthcharge prepends items to the ZBI, so the kernel is not necessarily
// first in the image seen here even though that is a requirement of the
// protocol with actual ZBI bootloaders. Hence this can't use
// zbi_check_complete.
zbi_header_t* bad_hdr;
zbi_result_t result = zbi_check(zbi, &bad_hdr);
if (result != ZBI_RESULT_OK) {
panic("ZBI failed check: %d at offset %#zx",
(int)result, (size_t)((uint8_t*)bad_hdr - (uint8_t*)zbi));
}
// Find the kernel item.
const zbi_header_t* kernel_item_header = NULL;
result = zbi_for_each(zbi, &find_kernel_item, &kernel_item_header);
if (result != ZBI_RESULT_INCOMPLETE_KERNEL) {
panic("ZBI missing kernel");
}
// This is the kernel item's payload, but it expects the whole
// zircon_kernel_t (i.e. starting with the container header) to be loaded
// at PHYS_LOAD_ADDRESS.
const zbi_kernel_t* kernel_header = (const void*)(kernel_item_header + 1);
// The kernel will sit at PHYS_LOAD_ADDRESS, where the code now
// running sits. The space until kernel_memory_end is reserved
// and can't be used for anything else.
const size_t kernel_load_size =
offsetof(zircon_kernel_t, data_kernel) + kernel_item_header->length;
uint8_t* const kernel_load_end = PHYS_LOAD_ADDRESS + kernel_load_size;
uint8_t* const kernel_memory_end =
kernel_load_end + ZBI_ALIGN(kernel_header->reserve_memory_size);
if (upper_memory_limit < (uintptr_t)kernel_memory_end) {
panic("upper memory limit %#"PRIxPTR" < kernel end %p",
upper_memory_limit, kernel_memory_end);
}
// Now we can append other items to the ZBI.
const size_t capacity = upper_memory_limit - (uintptr_t)zbi;
add_zbi_items(zbi, capacity, info);
// Use discarded ZBI space to hold the trampoline.
void* trampoline;
result = zbi_create_section(zbi, capacity, sizeof(struct trampoline),
ZBI_TYPE_DISCARD, 0, 0, &trampoline);
if (result != ZBI_RESULT_OK) {
panic("zbi_create_section(%p, %#"PRIxPTR", %#zx) failed: %d",
zbi, capacity, sizeof(struct trampoline), (int)result);
}
uint8_t* const zbi_end = (uint8_t*)zbi + zbi_size(zbi);
uintptr_t free_memory = (uintptr_t)kernel_memory_end;
if ((uint8_t*)zbi < kernel_memory_end) {
// The ZBI overlaps where the kernel needs to sit. Copy it further up.
zbi_header_t* new_zbi = (void*)zbi_end;
if ((uint8_t*)zbi_end < kernel_memory_end) {
new_zbi = (void*)kernel_memory_end;
}
// It needs to be page-aligned.
new_zbi = (void*)(((uintptr_t)new_zbi + 4096 - 1) & -4096);
memmove(new_zbi, zbi, zbi_size(zbi));
free_memory = (uintptr_t)new_zbi + zbi_size(zbi);
zbi = new_zbi;
}
// Set up page tables in free memory.
enable_64bit_paging(free_memory, upper_memory_limit);
// Copy the kernel into place and enter its code in 64-bit mode.
boot_zbi(zbi, kernel_item_header, trampoline);
}