| // 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; |
| } |
| } |