blob: b5d107c7c58368e56a6773713ed18fd63c2dcbe4 [file] [log] [blame]
// Copyright 2017 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 <cmdline.h>
#include <inttypes.h>
#include <lib/zbi/zbi.h>
#include <stdio.h>
#include <string.h>
#include <xefi.h>
#include <zircon/boot/image.h>
#include <zircon/limits.h>
#include <zircon/pixelformat.h>
#include <efi/protocol/graphics-output.h>
#include <efi/runtime-services.h>
#include "acpi.h"
#include "osboot.h"
static efi_guid zircon_guid = ZIRCON_VENDOR_GUID;
static char16_t crashlog_name[] = ZIRCON_CRASHLOG_EFIVAR;
#if __x86_64__
const uint32_t MY_ARCH_KERNEL_TYPE = ZBI_TYPE_KERNEL_X64;
#elif __aarch64__
const uint32_t MY_ARCH_KERNEL_TYPE = ZBI_TYPE_KERNEL_ARM64;
#endif
static int add_staged_zbi_files(zbi_header_t* zbi, size_t capacity);
static size_t get_last_crashlog(efi_system_table* sys, void* ptr, size_t max) {
efi_runtime_services* rs = sys->RuntimeServices;
uint32_t attr = ZIRCON_CRASHLOG_EFIATTR;
size_t sz = max;
efi_status r = rs->GetVariable(crashlog_name, &zircon_guid, &attr, &sz, ptr);
if (r == EFI_SUCCESS) {
// Erase it
rs->SetVariable(crashlog_name, &zircon_guid, ZIRCON_CRASHLOG_EFIATTR, 0, NULL);
} else {
sz = 0;
}
return sz;
}
// Converts an EFI memory type to a zbi_mem_range_t type.
uint32_t to_mem_range_type(uint32_t efi_mem_type) {
switch (efi_mem_type) {
case EfiLoaderCode:
case EfiLoaderData:
case EfiBootServicesCode:
case EfiBootServicesData:
case EfiConventionalMemory:
return ZBI_MEM_RANGE_RAM;
}
return ZBI_MEM_RANGE_RESERVED;
}
static unsigned char scratch[32768];
static void start_zircon(uint64_t entry, void* bootdata) {
#if __x86_64__
// ebx = 0, ebp = 0, edi = 0, esi = bootdata
__asm__ __volatile__(
"movl $0, %%ebp \n"
"cli \n"
"jmp *%[entry] \n" ::[entry] "a"(entry),
[bootdata] "S"(bootdata), "b"(0), "D"(0));
#elif defined(__aarch64__)
__asm__(
"mov x0, %[zbi]\n" // Argument register.
"mov x29, xzr\n" // Clear FP.
"mov x30, xzr\n" // Clear LR.
"br %[entry]\n" ::[entry] "r"(entry),
[zbi] "r"(bootdata)
: "x0", "x29", "x30");
#else
#error "add code for other arches here"
#endif
__builtin_unreachable();
}
size_t image_getsize(void* image, size_t sz) {
if (sz < sizeof(zircon_kernel_t)) {
return 0;
}
zircon_kernel_t* kernel = image;
if ((kernel->hdr_file.type != ZBI_TYPE_CONTAINER) || (kernel->hdr_file.magic != ZBI_ITEM_MAGIC) ||
(kernel->hdr_kernel.type != MY_ARCH_KERNEL_TYPE) ||
(kernel->hdr_kernel.magic != ZBI_ITEM_MAGIC)) {
return 0;
}
return ZBI_ALIGN(kernel->hdr_file.length) + sizeof(zbi_header_t);
}
static int header_check(void* image, size_t sz, uint64_t* _entry, size_t* _flen, size_t* _klen) {
zbi_header_t* bd = image;
size_t flen, klen;
uint64_t entry;
if (!(bd->flags & ZBI_FLAG_VERSION)) {
printf("boot: v1 bootdata kernel no longer supported\n");
return -1;
}
zircon_kernel_t* kernel = image;
if ((sz < sizeof(zircon_kernel_t)) || (kernel->hdr_kernel.type != MY_ARCH_KERNEL_TYPE) ||
((kernel->hdr_kernel.flags & ZBI_FLAG_VERSION) == 0)) {
printf("boot: invalid zircon kernel header\n");
return -1;
}
flen = ZBI_ALIGN(kernel->hdr_file.length);
klen = ZBI_ALIGN(kernel->hdr_kernel.length);
entry = kernel->data_kernel.entry;
if (flen > (sz - sizeof(zbi_header_t))) {
printf("boot: invalid zircon kernel header (bad flen)\n");
return -1;
}
if (klen > (sz - (sizeof(zbi_header_t) * 2))) {
printf("boot: invalid zircon kernel header (bad klen)\n");
return -1;
}
// TODO(fxbug.dev/32255): Eventually the fixed-position case can be removed.
#if __x86_64__
const uint64_t kFixedLoadAddress = 0x100000;
const uint64_t image_len = (2 * sizeof(zbi_header_t)) + klen;
if (entry > kFixedLoadAddress && entry - kFixedLoadAddress < image_len) {
printf("detected fixed-position kernel: entry address %#" PRIx64 "\n", entry);
} else if (entry < kFixedLoadAddress && entry < image_len) {
printf("detected position-independent kernel: entry offset %#" PRIx64 "\n", entry);
entry += kernel_zone_base;
} else {
printf("boot: invalid entry address %#" PRIx64 "\n", entry);
return -1;
}
#elif __aarch64__
// arm64 kernels have always been position independent
printf("detected position-independent kernel: entry offset %#" PRIx64 "\n", entry);
entry += kernel_zone_base;
#endif
if (_entry) {
*_entry = entry;
}
if (_flen) {
*_flen = flen;
*_klen = klen;
}
return 0;
}
// TODO: verify crc32 when present
static int item_check(zbi_header_t* bd, size_t sz) {
if (sz > 0x7FFFFFFF) {
// disallow 2GB+ items to avoid wrap on align issues
return -1;
}
if ((bd->magic != ZBI_ITEM_MAGIC) || ((bd->flags & ZBI_FLAG_VERSION) == 0) ||
(ZBI_ALIGN(bd->length) > sz)) {
return -1;
} else {
return 0;
}
}
bool image_is_valid(void* image, size_t sz) {
if (sz < sizeof(zbi_header_t)) {
printf("image is too small\n");
return false;
}
zbi_header_t* bd = image;
sz -= sizeof(zbi_header_t);
if ((bd->type != ZBI_TYPE_CONTAINER) || item_check(bd, sz)) {
printf("image has invalid header\n");
return false;
}
image += sizeof(zbi_header_t);
enum {
kKernelAbsent,
kKernelFirst,
kKernelLater,
} kernel = kKernelAbsent;
bool bootfs = false;
bool empty = true;
while (sz > sizeof(zbi_header_t)) {
bd = image;
sz -= sizeof(zbi_header_t);
if (item_check(image, sz)) {
printf("image has invalid bootitem\n");
return false;
}
if (ZBI_IS_KERNEL_BOOTITEM(bd->type)) {
kernel = (empty && kernel == kKernelAbsent) ? kKernelFirst : kKernelLater;
} else if (bd->type == ZBI_TYPE_STORAGE_BOOTFS) {
bootfs = true;
}
empty = false;
image += ZBI_ALIGN(bd->length) + sizeof(zbi_header_t);
sz -= ZBI_ALIGN(bd->length);
}
if (empty) {
printf("empty ZBI\n");
}
switch (kernel) {
case kKernelAbsent:
printf("no kernel item found\n");
break;
case kKernelLater:
printf("kernel item out of order: must be first\n");
break;
case kKernelFirst:
if (bootfs) { // It's complete.
return true;
}
printf("missing BOOTFS\n");
break;
}
return false;
}
int boot_zircon(efi_handle img, efi_system_table* sys, void* image, size_t isz, void* ramdisk,
size_t rsz, void* cmdline, size_t csz) {
efi_boot_services* bs = sys->BootServices;
uint64_t entry;
if (header_check(image, isz, &entry, NULL, NULL)) {
return -1;
}
if ((ramdisk == NULL) || (rsz < sizeof(zbi_header_t))) {
printf("boot: ramdisk missing or too small\n");
return -1;
}
if (isz > kernel_zone_size) {
printf("boot: kernel image too large\n");
return -1;
}
zbi_header_t* hdr0 = ramdisk;
if ((hdr0->type != ZBI_TYPE_CONTAINER) || (hdr0->extra != ZBI_CONTAINER_MAGIC) ||
!(hdr0->flags & ZBI_FLAG_VERSION)) {
printf("boot: ramdisk has invalid bootdata header\n");
return -1;
}
if ((hdr0->length > (rsz - sizeof(zbi_header_t)))) {
printf("boot: ramdisk has invalid bootdata length\n");
return -1;
}
// pass kernel commandline
zbi_result_t result =
zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_CMDLINE, 0, 0, cmdline, csz);
if (result != ZBI_RESULT_OK) {
return -1;
}
// pass ACPI root pointer
acpi_rsdp_t* rsdp = load_acpi_rsdp(gSys->ConfigurationTable, gSys->NumberOfTableEntries);
if (rsdp != 0) {
result =
zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_ACPI_RSDP, 0, 0, &rsdp, sizeof(rsdp));
if (result != ZBI_RESULT_OK) {
return -1;
}
}
// pass SMBIOS entry point pointer
uint64_t smbios = find_smbios(img, sys);
if (smbios != 0) {
result =
zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_SMBIOS, 0, 0, &smbios, sizeof(smbios));
if (result != ZBI_RESULT_OK) {
return -1;
}
}
// pass EFI system table
uint64_t addr = (uintptr_t)sys;
result = zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_EFI_SYSTEM_TABLE, 0, 0, &addr,
sizeof(addr));
if (result != ZBI_RESULT_OK) {
return -1;
}
// pass framebuffer data
efi_graphics_output_protocol* gop = NULL;
bs->LocateProtocol(&GraphicsOutputProtocol, NULL, (void**)&gop);
if (gop) {
zbi_swfb_t fb = {
.base = gop->Mode->FrameBufferBase,
.width = gop->Mode->Info->HorizontalResolution,
.height = gop->Mode->Info->VerticalResolution,
.stride = gop->Mode->Info->PixelsPerScanLine,
.format = get_zx_pixel_format(gop),
};
result =
zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_FRAMEBUFFER, 0, 0, &fb, sizeof(fb));
if (result != ZBI_RESULT_OK) {
return -1;
}
}
add_staged_zbi_files(ramdisk, rsz);
printf("copying kernel image from %p to %p size %zu, entry at %p\n", image,
(void*)kernel_zone_base, isz, (void*)entry);
memcpy((void*)kernel_zone_base, image, isz);
// Obtain the system memory map
size_t msize, dsize;
for (int attempts = 0;; attempts++) {
uint32_t dversion = 0;
size_t mkey = 0;
msize = sizeof(scratch);
dsize = 0;
efi_status r = sys->BootServices->GetMemoryMap(&msize, (efi_memory_descriptor*)scratch, &mkey,
&dsize, &dversion);
if (r != EFI_SUCCESS) {
printf("boot: cannot GetMemoryMap()\n");
goto fail;
}
r = sys->BootServices->ExitBootServices(img, mkey);
if (r == EFI_SUCCESS) {
break;
}
if (r == EFI_INVALID_PARAMETER) {
if (attempts > 0) {
printf("boot: cannot ExitBootServices(): %s\n", xefi_strerror(r));
goto fail;
}
// Attempting to exit may cause us to have to re-grab the
// memory map, but if it happens more than once something's
// broken.
continue;
}
printf("boot: cannot ExitBootServices(): %s\n", xefi_strerror(r));
goto fail;
}
// Past this block, we can assume that sizeof(zbi_mem_range_t) <= dsize.
if (dsize < sizeof(efi_memory_descriptor)) {
printf("boot: bad descriptor size: %zu\n", dsize);
goto fail;
}
_Static_assert(sizeof(zbi_mem_range_t) <= sizeof(efi_memory_descriptor),
"Cannot assume that sizeof(zbi_mem_range_t) <= dsize");
// Convert the memory map in place to a range of zbi_mem_range_t, the
// preferred ZBI memory format. In-place conversion can safely be done
// one-by-one, given that zbi_mem_range_t is smaller than a descriptor.
const size_t num_ranges = msize / dsize;
zbi_mem_range_t* ranges = (zbi_mem_range_t*)scratch;
for (size_t i = 0; i < num_ranges; ++i) {
const efi_memory_descriptor* desc = (const efi_memory_descriptor*)&scratch[i * dsize];
const zbi_mem_range_t range = {
.paddr = desc->PhysicalStart,
.length = desc->NumberOfPages * ZX_PAGE_SIZE,
.type = to_mem_range_type(desc->Type),
};
memcpy(&ranges[i], &range, sizeof(range));
}
result = zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_MEM_CONFIG, 0, 0, ranges,
num_ranges * sizeof(zbi_mem_range_t));
if (result != ZBI_RESULT_OK) {
goto fail;
}
// obtain the last crashlog if we can
size_t sz = get_last_crashlog(sys, scratch, 4096);
if (sz > 0) {
zbi_create_entry_with_payload(ramdisk, rsz, ZBI_TYPE_CRASHLOG, 0, 0, scratch, sz);
}
// jump to the kernel
start_zircon(entry, ramdisk);
fail:
return -1;
}
static char cmdline[CMDLINE_MAX];
int zbi_boot(efi_handle img, efi_system_table* sys, void* image, size_t sz) {
size_t flen, klen;
if (header_check(image, sz, NULL, &flen, &klen)) {
return -1;
}
// ramdisk portion is file - headers - kernel len
uint32_t rlen = flen - sizeof(zbi_header_t) - klen;
uint32_t roff = (sizeof(zbi_header_t) * 2) + klen;
if (rlen == 0) {
printf("zedboot: no ramdisk?!\n");
return -1;
}
// allocate space for the ramdisk
efi_boot_services* bs = sys->BootServices;
size_t rsz = rlen + sizeof(zbi_header_t) + EXTRA_ZBI_ITEM_SPACE;
size_t pages = BYTES_TO_PAGES(rsz);
void* ramdisk = NULL;
efi_status r =
bs->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, (efi_physical_addr*)&ramdisk);
if (r) {
printf("zedboot: cannot allocate ramdisk buffer\n");
return -1;
}
// Set up the header.
*(zbi_header_t*)ramdisk = (zbi_header_t)ZBI_CONTAINER_HEADER(rlen);
// Copy in place the existing ramdisk and boot items.
memcpy(ramdisk + sizeof(zbi_header_t), image + roff, rlen);
rlen += sizeof(zbi_header_t);
printf("ramdisk @ %p\n", ramdisk);
zbi_result_t r2 = zbi_check(ramdisk, NULL);
printf("check result %d\n", r2);
size_t csz = cmdline_to_string(cmdline, sizeof(cmdline));
// shrink original image header to include only the kernel
zircon_kernel_t* kernel = image;
kernel->hdr_file.length = sizeof(zbi_header_t) + klen;
return boot_zircon(img, sys, image, roff, ramdisk, rsz, cmdline, csz);
}
// Buffer to keep staged ZBI files.
// We store them in their own ZBI container, so we lose a little bit of extra space, but makes
// copying to the final ZBI trivial.
//
// We have enough space for 3 SSH keys.
static uint8_t zbi_files[4096] __attribute__((aligned(ZBI_ALIGNMENT)));
static bool zbi_files_initialized = false;
int zircon_stage_zbi_file(const char* name, const uint8_t* data, size_t data_len) {
size_t name_len = strlen(name);
if (name_len > UINT8_MAX) {
printf("ZBI filename too long");
return -1;
}
// Payload = (name_length_byte + name + data), size must fit in a uint32_t.
size_t payload_length = 1 + name_len + data_len;
if (payload_length > UINT32_MAX || payload_length < data_len) {
printf("ZBI file data too large");
return -1;
}
if (!zbi_files_initialized) {
zbi_result_t result = zbi_init(zbi_files, sizeof(zbi_files));
if (result != ZBI_RESULT_OK) {
printf("Failed to initialize zbi_files: %d\n", result);
return -1;
}
zbi_files_initialized = true;
}
void* payload_as_void = NULL;
zbi_result_t result = zbi_create_entry(zbi_files, sizeof(zbi_files), ZBI_TYPE_BOOTLOADER_FILE, 0,
0, payload_length, &payload_as_void);
if (result != ZBI_RESULT_OK) {
printf("Failed to create ZBI file entry: %d\n", result);
return -1;
}
uint8_t* payload = payload_as_void;
payload[0] = name_len;
memcpy(&payload[1], name, name_len);
memcpy(&payload[1 + name_len], data, data_len);
return 0;
}
static int add_staged_zbi_files(zbi_header_t* zbi, size_t capacity) {
if (!zbi_files_initialized) {
return 0;
}
zbi_result_t result = zbi_extend(zbi, capacity, zbi_files);
if (result != ZBI_RESULT_OK) {
printf("Failed to add staged ZBI files: %d\n", result);
return -1;
}
printf("Added staged ZBI files with total ZBI size %u\n", ((zbi_header_t*)zbi_files)->length);
return 0;
}