blob: 4ac51c6e1702c4f90fe85cd1e2b7b75475c9f561 [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 "osboot.h"
#include <efi/protocol/graphics-output.h>
#include <efi/runtime-services.h>
#include <efi/zircon.h>
#include <cmdline.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <xefi.h>
#include <zircon/boot/image.h>
#include <zircon/pixelformat.h>
static efi_guid zircon_guid = ZIRCON_VENDOR_GUID;
static char16_t crashlog_name[] = ZIRCON_CRASHLOG_EFIVAR;
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;
}
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));
#else
#warning "add code for other arches here"
#endif
for (;;)
;
}
static int add_bootdata(void** ptr, size_t* avail,
zbi_header_t* bd, void* data) {
size_t len = ZBI_ALIGN(bd->length);
if ((sizeof(zbi_header_t) + len) > *avail) {
printf("boot: no room for bootdata type=%08x size=%08x\n",
bd->type, bd->length);
return -1;
}
bd->flags |= ZBI_FLAG_VERSION;
bd->reserved0 = 0;
bd->reserved1 = 0;
bd->magic = ZBI_ITEM_MAGIC;
bd->crc32 = ZBI_ITEM_NO_CRC32;
memcpy(*ptr, bd, sizeof(zbi_header_t));
memcpy((*ptr) + sizeof(zbi_header_t), data, len);
len += sizeof(zbi_header_t);
(*ptr) += len;
(*avail) -= len;
return 0;
}
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 != ZBI_TYPE_KERNEL_X64) ||
(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 != ZBI_TYPE_KERNEL_X64) ||
((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;
}
if (_entry) {
*_entry = entry;
}
if (_flen) {
*_flen = flen;
*_klen = klen;
}
return 0;
}
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;
}
}
//TODO: verify crc32 when present
unsigned identify_image(void* image, size_t sz) {
if (sz == 0) {
return IMAGE_EMPTY;
}
if (sz < sizeof(zbi_header_t)) {
printf("image is too small\n");
return IMAGE_INVALID;
}
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 IMAGE_INVALID;
}
image += sizeof(zbi_header_t);
unsigned n = 0;
unsigned r = 0;
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 IMAGE_INVALID;
}
if (ZBI_IS_KERNEL_BOOTITEM(bd->type)) {
if (n != 0) {
printf("image has kernel in middle\n");
return IMAGE_INVALID;
} else {
r = IMAGE_KERNEL;
}
}
if (bd->type == ZBI_TYPE_STORAGE_BOOTFS) {
if ((r == IMAGE_KERNEL) || (r == IMAGE_COMBO)) {
r = IMAGE_COMBO;
} else {
r = IMAGE_RAMDISK;
}
}
image += ZBI_ALIGN(bd->length) + sizeof(zbi_header_t);
sz -= ZBI_ALIGN(bd->length);
n++;
}
return r;
}
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;
}
// osboot ensures we have FRONT_BYTES ahead of the
// ramdisk to prepend our own bootdata items.
void* bptr = ramdisk - FRONT_BYTES;
size_t blen = FRONT_BYTES;
// We create a new container header of the same size
// as the one at the start of the ramdisk
zbi_header_t hdr = ZBI_CONTAINER_HEADER(hdr0->length + FRONT_BYTES);
memcpy(bptr, &hdr, sizeof(hdr));
bptr += sizeof(hdr);
// pass kernel commandline
hdr.type = ZBI_TYPE_CMDLINE;
hdr.length = csz;
hdr.extra = 0;
hdr.flags = ZBI_FLAG_VERSION;
if (add_bootdata(&bptr, &blen, &hdr, cmdline)) {
return -1;
}
// pass ACPI root pointer
uint64_t rsdp = find_acpi_root(img, sys);
if (rsdp != 0) {
hdr.type = ZBI_TYPE_ACPI_RSDP;
hdr.length = sizeof(rsdp);
if (add_bootdata(&bptr, &blen, &hdr, &rsdp)) {
return -1;
}
}
// pass SMBIOS entry point pointer
uint64_t smbios = find_smbios(img, sys);
if (smbios != 0) {
hdr.type = ZBI_TYPE_SMBIOS;
hdr.length = sizeof(smbios);
if (add_bootdata(&bptr, &blen, &hdr, &smbios)) {
return -1;
}
}
// pass EFI system table
uint64_t addr = (uintptr_t) sys;
hdr.type = ZBI_TYPE_EFI_SYSTEM_TABLE;
hdr.length = sizeof(sys);
if (add_bootdata(&bptr, &blen, &hdr, &addr)) {
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),
};
hdr.type = ZBI_TYPE_FRAMEBUFFER;
hdr.length = sizeof(fb);
if (add_bootdata(&bptr, &blen, &hdr, &fb)) {
return -1;
}
}
memcpy((void*)kernel_zone_base, image, isz);
// Obtain the system memory map
size_t msize, dsize;
for (int attempts = 0;;attempts++) {
efi_memory_descriptor* mmap = (efi_memory_descriptor*) (scratch + sizeof(uint64_t));
uint32_t dversion = 0;
size_t mkey = 0;
msize = sizeof(scratch) - sizeof(uint64_t);
dsize = 0;
efi_status r = sys->BootServices->GetMemoryMap(&msize, mmap, &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;
}
memcpy(scratch, &dsize, sizeof(uint64_t));
// install memory map
hdr.type = ZBI_TYPE_EFI_MEMORY_MAP;
hdr.length = msize + sizeof(uint64_t);
if (add_bootdata(&bptr, &blen, &hdr, scratch)) {
goto fail;
}
// obtain the last crashlog if we can
size_t sz = get_last_crashlog(sys, scratch, 4096);
if (sz > 0) {
hdr.type = ZBI_TYPE_CRASHLOG;
hdr.length = sz;
add_bootdata(&bptr, &blen, &hdr, scratch);
}
// fill the remaining gap between pre-data and ramdisk image
if ((blen < sizeof(hdr)) || (blen & 7)) {
goto fail;
}
hdr.type = ZBI_TYPE_DISCARD;
hdr.length = blen - sizeof(hdr);
hdr.flags = ZBI_FLAG_VERSION;
memcpy(bptr, &hdr, sizeof(hdr));
// jump to the kernel
start_zircon(entry, ramdisk - FRONT_BYTES);
fail:
return -1;
}
static char cmdline[CMDLINE_MAX];
int zedboot(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) + FRONT_BYTES;
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;
}
ramdisk += FRONT_BYTES;
*(zbi_header_t*)ramdisk = (zbi_header_t)ZBI_CONTAINER_HEADER(rlen);
memcpy(ramdisk + sizeof(zbi_header_t), image + roff, rlen);
rlen += sizeof(zbi_header_t);
printf("ramdisk @ %p\n", ramdisk);
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, rlen, cmdline, csz);
}
int boot_kernel(efi_handle img, efi_system_table* sys,
void* image, size_t sz,
void* ramdisk, size_t rsz) {
size_t csz = cmdline_to_string(cmdline, sizeof(cmdline));
zbi_header_t* bd = image;
if ((bd->type == ZBI_TYPE_CONTAINER) &&
(bd->extra == ZBI_CONTAINER_MAGIC)) {
return boot_zircon(img, sys, image, sz, ramdisk, rsz, cmdline, csz);
} else {
return -1;
}
}