// 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 #include #include #include #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); }