| // 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 <fcntl.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <hypervisor/guest.h> |
| #include <zircon/assert.h> |
| #include <zircon/boot/bootdata.h> |
| |
| #include "zircon.h" |
| |
| static const uintptr_t kKernelOffset = 0x100000; |
| static const uintptr_t kBootdataOffset = 0x800000; |
| |
| static bool container_is_valid(const bootdata_t* container) { |
| return container->type == BOOTDATA_CONTAINER && |
| container->length > sizeof(bootdata_t) && |
| container->extra == BOOTDATA_MAGIC && |
| container->flags == 0; |
| } |
| |
| static zx_status_t load_zircon(const int fd, const uintptr_t addr, const size_t size, |
| const uintptr_t first_page, uintptr_t* guest_ip, |
| uintptr_t* end_off) { |
| zircon_kernel_t* header = reinterpret_cast<zircon_kernel_t*>(addr + kKernelOffset); |
| // Move the first page to where zircon would like it to be |
| memmove(header, reinterpret_cast<void*>(first_page), PAGE_SIZE); |
| |
| if (!container_is_valid(&header->hdr_file)) { |
| fprintf(stderr, "Invalid Zircon container\n"); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| if (header->hdr_kernel.type != BOOTDATA_KERNEL) { |
| fprintf(stderr, "Invalid Zircon kernel header\n"); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| if (header->data_kernel.entry64 >= size) { |
| fprintf(stderr, "Kernel entry point is outside of guest physical memory\n"); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| // We already read a page, now we need the rest... |
| // The rest is the length in the header, minus what we already read, but accounting for |
| // the bootdata_kernel_t portion of zircon_kernel_t that's included in the header length. |
| uintptr_t data_off = kKernelOffset + PAGE_SIZE; |
| size_t data_len = header->hdr_kernel.length - |
| (PAGE_SIZE - sizeof(zircon_kernel_t) + sizeof(bootdata_kernel_t)); |
| |
| ssize_t ret = read(fd, reinterpret_cast<void*>(addr + data_off), data_len); |
| if (ret < 0 || (size_t)ret != data_len) { |
| fprintf(stderr, "Failed to read Zircon kernel data\n"); |
| return ZX_ERR_IO; |
| } |
| |
| *guest_ip = header->data_kernel.entry64; |
| *end_off = header->hdr_file.length + sizeof(bootdata_t); |
| return ZX_OK; |
| } |
| |
| static zx_status_t load_cmdline(const char* cmdline, const uintptr_t addr, |
| const uintptr_t bootdata_off) { |
| bootdata_t* bootdata_hdr = (bootdata_t*)(addr + bootdata_off); |
| uintptr_t data_off = bootdata_off + sizeof(bootdata_t) + BOOTDATA_ALIGN(bootdata_hdr->length); |
| |
| bootdata_t* cmdline_hdr = (bootdata_t*)(addr + data_off); |
| cmdline_hdr->type = BOOTDATA_CMDLINE; |
| size_t cmdline_len = strlen(cmdline) + 1; |
| if (cmdline_len > UINT32_MAX) { |
| fprintf(stderr, "Command line length is outside of 32-bit range\n"); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| cmdline_hdr->length = cmdline_len & UINT32_MAX; |
| memcpy(cmdline_hdr + 1, cmdline, cmdline_len); |
| |
| bootdata_hdr->length += cmdline_hdr->length + static_cast<uint32_t>(sizeof(bootdata_t)); |
| return ZX_OK; |
| } |
| |
| static zx_status_t load_bootfs(const int fd, const uintptr_t addr, const uintptr_t bootdata_off) { |
| bootdata_t ramdisk_hdr; |
| ssize_t ret = read(fd, &ramdisk_hdr, sizeof(bootdata_t)); |
| if (ret != sizeof(bootdata_t)) { |
| fprintf(stderr, "Failed to read BOOTFS image header\n"); |
| return ZX_ERR_IO; |
| } |
| |
| if (!container_is_valid(&ramdisk_hdr)) { |
| fprintf(stderr, "Invalid BOOTFS container\n"); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| bootdata_t* bootdata_hdr = (bootdata_t*)(addr + bootdata_off); |
| uintptr_t data_off = bootdata_off + sizeof(bootdata_t) + BOOTDATA_ALIGN(bootdata_hdr->length); |
| ret = read(fd, (void*)(addr + data_off), ramdisk_hdr.length); |
| if (ret < 0 || (size_t)ret != ramdisk_hdr.length) { |
| fprintf(stderr, "Failed to read BOOTFS image data\n"); |
| return ZX_ERR_IO; |
| } |
| |
| bootdata_hdr->length += ramdisk_hdr.length + static_cast<uint32_t>(sizeof(bootdata_t)); |
| return ZX_OK; |
| } |
| |
| static zx_status_t create_bootdata(uintptr_t addr, size_t size, uintptr_t acpi_off, |
| uintptr_t bootdata_off) { |
| if (BOOTDATA_ALIGN(bootdata_off) != bootdata_off) |
| return ZX_ERR_INVALID_ARGS; |
| |
| const size_t e820_size = guest_e820_size(size); |
| const size_t bootdata_len = sizeof(bootdata_t) + BOOTDATA_ALIGN(sizeof(uint64_t)) + |
| sizeof(bootdata_t) + BOOTDATA_ALIGN(e820_size); |
| if (bootdata_off + bootdata_len > size) |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| if (bootdata_len > UINT32_MAX) |
| return ZX_ERR_OUT_OF_RANGE; |
| |
| // Bootdata container. |
| bootdata_t* header = (bootdata_t*)(addr + bootdata_off); |
| header->type = BOOTDATA_CONTAINER; |
| header->extra = BOOTDATA_MAGIC; |
| header->length = static_cast<uint32_t>(bootdata_len); |
| |
| // ACPI root table pointer. |
| bootdata_off += sizeof(bootdata_t); |
| bootdata_t* bootdata = (bootdata_t*)(addr + bootdata_off); |
| bootdata->type = BOOTDATA_ACPI_RSDP; |
| bootdata->length = sizeof(uint64_t); |
| |
| bootdata_off += sizeof(bootdata_t); |
| uint64_t* acpi_rsdp = (uint64_t*)(addr + bootdata_off); |
| *acpi_rsdp = acpi_off; |
| |
| // E820 memory map. |
| bootdata_off += BOOTDATA_ALIGN(sizeof(uint64_t)); |
| bootdata = (bootdata_t*)(addr + bootdata_off); |
| bootdata->type = BOOTDATA_E820_TABLE; |
| bootdata->length = static_cast<uint32_t>(e820_size); |
| |
| bootdata_off += sizeof(bootdata_t); |
| return guest_create_e820(addr, size, bootdata_off); |
| } |
| |
| static bool is_zircon(const uintptr_t first_page) { |
| zircon_kernel_t* header = (zircon_kernel_t*)first_page; |
| return container_is_valid(&header->hdr_file); |
| } |
| |
| zx_status_t setup_zircon(const uintptr_t addr, const size_t size, const uintptr_t first_page, |
| const uintptr_t acpi_off, const int fd, const char* bootdata_path, |
| const char* cmdline, uintptr_t* guest_ip, uintptr_t* bootdata_offset) { |
| if (!is_zircon(first_page)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t status = create_bootdata(addr, size, acpi_off, kBootdataOffset); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failed to create bootdata\n"); |
| return status; |
| } |
| |
| uintptr_t zircon_end_off; |
| status = load_zircon(fd, addr, size, first_page, guest_ip, &zircon_end_off); |
| if (status != ZX_OK) |
| return status; |
| ZX_ASSERT(zircon_end_off <= kBootdataOffset); |
| |
| // If we have a command line, load it. |
| if (cmdline != NULL) { |
| status = load_cmdline(cmdline, addr, kBootdataOffset); |
| if (status != ZX_OK) |
| return status; |
| } |
| |
| // If we have been provided a BOOTFS image, load it. |
| if (bootdata_path) { |
| int boot_fd = open(bootdata_path, O_RDONLY); |
| if (boot_fd < 0) { |
| fprintf(stderr, "Failed to open BOOTFS image \"%s\"\n", bootdata_path); |
| return ZX_ERR_IO; |
| } |
| |
| status = load_bootfs(boot_fd, addr, kBootdataOffset); |
| close(boot_fd); |
| if (status != ZX_OK) |
| return status; |
| } |
| *bootdata_offset = kBootdataOffset; |
| return ZX_OK; |
| } |