| // 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 <cmdline.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <xefi.h> |
| |
| #include <zircon/boot/bootdata.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) { |
| // 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)); |
| for (;;) |
| ; |
| } |
| |
| static bool with_extra = false; |
| static bootextra_t default_extra = { |
| .reserved0 = 0, |
| .reserved1 = 0, |
| .magic = BOOTITEM_MAGIC, |
| .crc32 = BOOTITEM_NO_CRC32, |
| }; |
| |
| static int add_bootdata(void** ptr, size_t* avail, |
| bootdata_t* bd, void* data) { |
| if (with_extra) { |
| size_t len = BOOTDATA_ALIGN(bd->length); |
| if ((sizeof(bootdata_t) + sizeof(bootextra_t) + len) > *avail) { |
| printf("boot: no room for bootdata type=%08x size=%08x\n", |
| bd->type, bd->length); |
| return -1; |
| } |
| bd->flags |= BOOTDATA_FLAG_EXTRA; |
| memcpy(*ptr, bd, sizeof(bootdata_t)); |
| memcpy((*ptr) + sizeof(bootdata_t), &default_extra, sizeof(bootextra_t)); |
| memcpy((*ptr) + sizeof(bootdata_t) + sizeof(bootextra_t), data, len); |
| len += sizeof(bootdata_t) + sizeof(bootextra_t); |
| (*ptr) += len; |
| (*avail) -= len; |
| } else { |
| size_t len = BOOTDATA_ALIGN(bd->length); |
| if ((sizeof(bootdata_t) + len) > *avail) { |
| printf("boot: no room for bootdata type=%08x size=%08x\n", |
| bd->type, bd->length); |
| return -1; |
| } |
| memcpy(*ptr, bd, sizeof(bootdata_t)); |
| memcpy((*ptr) + sizeof(bootdata_t), data, len); |
| len += sizeof(bootdata_t); |
| (*ptr) += len; |
| (*avail) -= len; |
| } |
| return 0; |
| } |
| |
| static int header_check(void* image, size_t sz, uint64_t* _entry, |
| size_t* _hsz, size_t* _flen, size_t* _klen) { |
| bootdata_t* bd = image; |
| size_t hsz, flen, klen; |
| uint64_t entry; |
| |
| if (bd->flags & BOOTDATA_FLAG_EXTRA) { |
| hsz = sizeof(bootdata_t) + sizeof(bootextra_t); |
| zircon_kernel2_t* kernel2 = image; |
| if ((sz < sizeof(zircon_kernel2_t)) || |
| (kernel2->hdr_kernel.type != BOOTDATA_KERNEL) || |
| ((kernel2->hdr_kernel.flags & BOOTDATA_FLAG_EXTRA) == 0)) { |
| printf("boot: invalid zircon kernel header\n"); |
| return -1; |
| } |
| flen = BOOTDATA_ALIGN(kernel2->hdr_file.length); |
| klen = BOOTDATA_ALIGN(kernel2->hdr_kernel.length); |
| entry = kernel2->data_kernel.entry64; |
| } else { |
| hsz = sizeof(bootdata_t); |
| zircon_kernel_t* kernel = image; |
| if ((sz < sizeof(zircon_kernel_t)) || |
| (kernel->hdr_kernel.type != BOOTDATA_KERNEL)) { |
| printf("boot: invalid zircon kernel header\n"); |
| return -1; |
| } |
| flen = BOOTDATA_ALIGN(kernel->hdr_file.length); |
| klen = BOOTDATA_ALIGN(kernel->hdr_kernel.length); |
| entry = kernel->data_kernel.entry64; |
| } |
| |
| if (flen > (sz - hsz)) { |
| printf("boot: invalid zircon kernel header (bad flen)\n"); |
| return -1; |
| } |
| |
| if (klen > (sz - (hsz * 2))) { |
| printf("boot: invalid zircon kernel header (bad klen)\n"); |
| return -1; |
| } |
| if (_entry) { |
| *_entry = entry; |
| } |
| if (_hsz) { |
| *_hsz = hsz; |
| *_flen = flen; |
| *_klen = klen; |
| } |
| |
| return 0; |
| } |
| |
| 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, NULL)) { |
| return -1; |
| } |
| if ((ramdisk == NULL) || (rsz < (sizeof(bootdata_t) + sizeof(bootextra_t)))) { |
| printf("boot: ramdisk missing or too small\n"); |
| return -1; |
| } |
| |
| bootdata_t* hdr0 = ramdisk; |
| if ((hdr0->type != BOOTDATA_CONTAINER) || |
| (hdr0->extra != BOOTDATA_MAGIC)) { |
| printf("boot: ramdisk has invalid bootdata header\n"); |
| return -1; |
| } |
| |
| // If the ramdisk container header is a new/large header, |
| // generate all our prepended headers in the same style... |
| size_t hsz = sizeof(bootdata_t); |
| if (hdr0->flags & BOOTDATA_FLAG_EXTRA) { |
| with_extra = true; |
| hsz += sizeof(bootextra_t); |
| } |
| |
| if ((hdr0->length > (rsz - hsz))) { |
| 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. |
| bootdata_t hdr; |
| 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 |
| hdr.type = BOOTDATA_CONTAINER; |
| hdr.length = hdr0->length + FRONT_BYTES; |
| hdr.extra = BOOTDATA_MAGIC; |
| hdr.flags = with_extra ? BOOTDATA_FLAG_EXTRA : 0; |
| memcpy(bptr, &hdr, sizeof(hdr)); |
| bptr += sizeof(hdr); |
| if (with_extra) { |
| memcpy(bptr, &default_extra, sizeof(default_extra)); |
| bptr += sizeof(default_extra); |
| } |
| |
| // pass kernel commandline |
| hdr.type = BOOTDATA_CMDLINE; |
| hdr.length = csz; |
| hdr.extra = 0; |
| hdr.flags = 0; |
| if (add_bootdata(&bptr, &blen, &hdr, cmdline)) { |
| return -1; |
| } |
| |
| // pass ACPI root pointer |
| uint64_t rsdp = find_acpi_root(img, sys); |
| hdr.type = BOOTDATA_ACPI_RSDP; |
| hdr.length = sizeof(rsdp); |
| if (add_bootdata(&bptr, &blen, &hdr, &rsdp)) { |
| return -1; |
| } |
| |
| // pass EFI system table |
| uint64_t addr = (uintptr_t) sys; |
| hdr.type = BOOTDATA_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) { |
| bootdata_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 = BOOTDATA_FRAMEBUFFER; |
| hdr.length = sizeof(fb); |
| if (add_bootdata(&bptr, &blen, &hdr, &fb)) { |
| return -1; |
| } |
| } |
| |
| // Allocate at 1M and copy kernel down there |
| efi_physical_addr mem = 0x100000; |
| unsigned pages = BYTES_TO_PAGES(isz); |
| //TODO: sort out why pages + 1? Inherited from deprecated_load() |
| if (bs->AllocatePages(AllocateAddress, EfiLoaderData, pages + 1, &mem)) { |
| printf("boot: cannot obtain memory @ %p\n", (void*) mem); |
| goto fail; |
| } |
| memcpy((void*)mem, 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 = BOOTDATA_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 = BOOTDATA_LAST_CRASHLOG; |
| hdr.length = sz; |
| add_bootdata(&bptr, &blen, &hdr, scratch); |
| } |
| |
| // fill the remaining gap between pre-data and ramdisk image |
| if ((blen < hsz) || (blen & 7)) { |
| goto fail; |
| } |
| hdr.type = BOOTDATA_IGNORE; |
| hdr.length = blen - hsz; |
| memcpy(bptr, &hdr, sizeof(hdr)); |
| if (with_extra) { |
| memcpy(bptr + sizeof(hdr), &default_extra, sizeof(default_extra)); |
| } |
| |
| // jump to the kernel |
| start_zircon(entry, ramdisk - FRONT_BYTES); |
| |
| fail: |
| bs->FreePages(mem, pages); |
| return -1; |
| } |
| |
| static char cmdline[CMDLINE_MAX]; |
| |
| int mxboot(efi_handle img, efi_system_table* sys, |
| void* image, size_t sz) { |
| |
| size_t hsz, flen, klen; |
| if (header_check(image, sz, NULL, &hsz, &flen, &klen)) { |
| return -1; |
| } |
| |
| // ramdisk portion is file - headers - kernel len |
| uint32_t rlen = flen - hsz - klen; |
| uint32_t roff = (hsz * 2) + klen; |
| if (rlen == 0) { |
| printf("mxboot: no ramdisk?!\n"); |
| return -1; |
| } |
| |
| // allocate space for the ramdisk |
| efi_boot_services* bs = sys->BootServices; |
| size_t rsz = rlen + hsz + 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("mxboot: cannot allocate ramdisk buffer\n"); |
| return -1; |
| } |
| |
| ramdisk += FRONT_BYTES; |
| bootdata_t* hdr = ramdisk; |
| hdr->type = BOOTDATA_CONTAINER; |
| hdr->length = rlen; |
| hdr->extra = BOOTDATA_MAGIC; |
| hdr->flags = 0; |
| if (hsz != sizeof(bootdata_t)) { |
| hdr->flags |= BOOTDATA_FLAG_EXTRA; |
| memcpy(hdr + 1, &default_extra, sizeof(default_extra)); |
| } |
| memcpy(ramdisk + hsz, image + roff, rlen); |
| rlen += hsz; |
| |
| printf("ramdisk @ %p\n", ramdisk); |
| |
| size_t csz = cmdline_to_string(cmdline, sizeof(cmdline)); |
| |
| // shrink original image header to include only the kernel |
| if (hsz == sizeof(bootdata_t)) { |
| zircon_kernel_t* kernel = image; |
| kernel->hdr_file.length = hsz + klen; |
| } else { |
| zircon_kernel2_t* kernel2 = image; |
| kernel2->hdr_file.length = hsz + 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)); |
| |
| bootdata_t* bd = image; |
| if ((bd->type == BOOTDATA_CONTAINER) && |
| (bd->extra == BOOTDATA_MAGIC)) { |
| return boot_zircon(img, sys, image, sz, ramdisk, rsz, cmdline, csz); |
| } else { |
| return -1; |
| } |
| } |