| // 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 "garnet/bin/guest/vmm/zircon.h" |
| |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <libzbi/zbi.h> |
| #include <zircon/assert.h> |
| #include <zircon/boot/driver-config.h> |
| #include <zircon/boot/e820.h> |
| #include <zircon/boot/image.h> |
| |
| #include "garnet/bin/guest/vmm/dev_mem.h" |
| #include "garnet/bin/guest/vmm/guest.h" |
| #include "garnet/bin/guest/vmm/guest_config.h" |
| |
| #if __aarch64__ |
| // This address works for direct-mapping of host memory. This address is chosen |
| // to ensure that we do not collide with the mapping of the host kernel. |
| static constexpr uintptr_t kKernelOffset = 0x2080000; |
| |
| static constexpr zbi_platform_id_t kPlatformId = { |
| .vid = 3, // PDEV_VID_GOOGLE |
| .pid = 2, // PDEV_PID_MACHINA |
| .board_name = "machina", |
| }; |
| |
| static constexpr dcfg_arm_psci_driver_t kPsciDriver = { |
| .use_hvc = false, |
| }; |
| |
| static constexpr dcfg_arm_generic_timer_driver_t kTimerDriver = { |
| .irq_virt = 27, |
| }; |
| #elif __x86_64__ |
| static constexpr uintptr_t kKernelOffset = 0x100000; |
| |
| #include "garnet/bin/guest/vmm/arch/x64/acpi.h" |
| #include "garnet/bin/guest/vmm/arch/x64/e820.h" |
| #endif |
| |
| static constexpr uintptr_t kRamdiskOffset = 0x4000000; |
| |
| static inline bool is_within(uintptr_t x, uintptr_t addr, uintptr_t size) { |
| return x >= addr && x < addr + size; |
| } |
| |
| zx_status_t read_unified_zbi(const std::string& zbi_path, |
| const uintptr_t kernel_off, |
| const uintptr_t zbi_off, const PhysMem& phys_mem, |
| uintptr_t* guest_ip) { |
| fbl::unique_fd fd(open(zbi_path.c_str(), O_RDONLY)); |
| if (!fd) { |
| FXL_LOG(ERROR) << "Failed to open kernel image " << zbi_path; |
| return ZX_ERR_IO; |
| } |
| struct stat stat; |
| ssize_t ret = fstat(fd.get(), &stat); |
| if (ret < 0) { |
| FXL_LOG(ERROR) << "Failed to stat kernel image " << zbi_path; |
| return ZX_ERR_IO; |
| } |
| |
| if (ZBI_ALIGN(kernel_off) != kernel_off) { |
| FXL_LOG(ERROR) << "Kernel offset has invalid alignment"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // First read just the kernel header. |
| ret = read(fd.get(), phys_mem.as<void>(kernel_off, sizeof(zircon_kernel_t)), |
| sizeof(zircon_kernel_t)); |
| if (ret != sizeof(zircon_kernel_t)) { |
| FXL_LOG(ERROR) << "Failed to read kernel header"; |
| return ZX_ERR_IO; |
| } |
| |
| // Check that the kernel ZBI is the correct type. |
| auto kernel_hdr = phys_mem.as<zircon_kernel_t>(kernel_off); |
| if (!ZBI_IS_KERNEL_BOOTITEM(kernel_hdr->hdr_kernel.type)) { |
| FXL_LOG(ERROR) << "Invalid Zircon container"; |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| // Check that the total size of the ZBI matches the file size. |
| const uint32_t file_len = sizeof(zbi_header_t) + kernel_hdr->hdr_file.length; |
| if (stat.st_size != file_len) { |
| FXL_LOG(ERROR) << "ZBI length does not match file size"; |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // Check that the kernel's total memory reservation fits into guest physical |
| // memory. |
| const uint32_t reserved_size = offsetof(zircon_kernel_t, data_kernel) + |
| kernel_hdr->hdr_kernel.length + |
| kernel_hdr->data_kernel.reserve_memory_size; |
| if (kernel_off + reserved_size > phys_mem.size()) { |
| FXL_LOG(ERROR) |
| << "Zircon kernel memory reservation exceeds guest physical memory"; |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // Check that the kernel's total memory reservation does not overlap the |
| // ramdisk. |
| if (is_within(zbi_off, kernel_off, reserved_size)) { |
| FXL_LOG(ERROR) |
| << "Kernel reservation memory reservation overlaps RAM disk location"; |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // Read the kernel payload. |
| const uint32_t kernel_payload_len = |
| kernel_hdr->hdr_kernel.length - sizeof(zbi_kernel_t); |
| ret = read(fd.get(), |
| phys_mem.as<void>(kernel_off + offsetof(zircon_kernel_t, contents), |
| kernel_payload_len), |
| kernel_payload_len); |
| if (ret != kernel_payload_len) { |
| FXL_LOG(ERROR) << "Failed to read kernel payload"; |
| return ZX_ERR_IO; |
| } |
| |
| // Update the kernel ZBI container header and check that it is valid. |
| kernel_hdr->hdr_file = |
| ZBI_CONTAINER_HEADER(kernel_hdr->hdr_kernel.length + |
| static_cast<uint32_t>(sizeof(zbi_header_t))); |
| zbi_result_t res = zbi_check(kernel_hdr, nullptr); |
| if (res != ZBI_RESULT_OK) { |
| FXL_LOG(ERROR) << "Invalid kernel ZBI " << res; |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Create a separate data ZBI. |
| |
| if (ZBI_ALIGN(zbi_off) != zbi_off) { |
| FXL_LOG(ERROR) << "ZBI offset has invalid alignment"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| auto container_hdr = phys_mem.as<zbi_header_t>(zbi_off); |
| *container_hdr = ZBI_CONTAINER_HEADER(0); |
| |
| // Read additional items from the kernel ZBI container to the data ZBI. |
| const uint32_t kernel_end = |
| offsetof(zircon_kernel_t, data_kernel) + kernel_hdr->hdr_kernel.length; |
| if (file_len > kernel_end) { |
| const uint32_t items_len = file_len - kernel_end; |
| const uintptr_t data_off = |
| zbi_off + sizeof(zbi_header_t) + container_hdr->length; |
| ret = read(fd.get(), phys_mem.as<void>(data_off, items_len), items_len); |
| container_hdr->length += ZBI_ALIGN(items_len); |
| } |
| |
| // On arm64, the kernel is relocatable so the entry point must be offset by |
| // kKernelOffset. On x64, the entry point is absolute. |
| #if __aarch64__ |
| *guest_ip = kernel_hdr->data_kernel.entry + kKernelOffset; |
| #elif __x86_64__ |
| *guest_ip = kernel_hdr->data_kernel.entry; |
| #endif |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t build_data_zbi(const GuestConfig& cfg, |
| const PhysMem& phys_mem, |
| const DevMem& dev_mem, |
| const std::vector<PlatformDevice*>& devices, |
| uintptr_t zbi_off) { |
| auto container_hdr = phys_mem.as<zbi_header_t>(zbi_off); |
| const size_t zbi_max = phys_mem.size() - zbi_off; |
| |
| // Command line. |
| zbi_result_t res; |
| res = zbi_append_section(container_hdr, zbi_max, cfg.cmdline().size() + 1, |
| ZBI_TYPE_CMDLINE, 0, 0, cfg.cmdline().c_str()); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // Any platform devices |
| for (auto device : devices) { |
| zx_status_t status = device->ConfigureZbi(container_hdr, zbi_max); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| #if __aarch64__ |
| // CPU config. |
| uint8_t cpu_buffer[sizeof(zbi_cpu_config_t) + sizeof(zbi_cpu_cluster_t)] = {}; |
| auto cpu_config = reinterpret_cast<zbi_cpu_config_t*>(cpu_buffer); |
| cpu_config->cluster_count = 1; |
| cpu_config->clusters[0].cpu_count = cfg.cpus(); |
| res = zbi_append_section(container_hdr, zbi_max, sizeof(cpu_buffer), |
| ZBI_TYPE_CPU_CONFIG, 0, 0, cpu_buffer); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| // Memory config. |
| std::vector<zbi_mem_range_t> mem_config; |
| auto yield = [&mem_config](auto range) { |
| mem_config.emplace_back(zbi_mem_range_t{ |
| .paddr = range.addr, |
| .length = range.size, |
| .type = ZBI_MEM_RANGE_RAM, |
| }); |
| }; |
| for (const MemorySpec& spec : cfg.memory()) { |
| dev_mem.YieldInverseRange(spec.addr, spec.len, yield); |
| } |
| |
| // Zircon only supports a limited number of peripheral ranges so for any |
| // dev_mem ranges that are not in the RAM range we will build a single |
| // peripheral range to cover all of them. |
| zbi_mem_range_t periph_range = { |
| .paddr = 0, .length = 0, .type = ZBI_MEM_RANGE_PERIPHERAL}; |
| for (const auto& range : dev_mem) { |
| if (range.addr < phys_mem.size()) { |
| mem_config.emplace_back(zbi_mem_range_t{ |
| .paddr = range.addr, |
| .length = range.size, |
| .type = ZBI_MEM_RANGE_PERIPHERAL, |
| }); |
| } else { |
| if (periph_range.length == 0) { |
| periph_range.paddr = range.addr; |
| } |
| periph_range.length = range.addr + range.size - periph_range.paddr; |
| } |
| } |
| if (periph_range.length != 0) { |
| mem_config.emplace_back(std::move(periph_range)); |
| } |
| res = zbi_append_section(container_hdr, zbi_max, |
| sizeof(zbi_mem_range_t) * mem_config.size(), |
| ZBI_TYPE_MEM_CONFIG, 0, 0, &mem_config[0]); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| // Platform ID. |
| res = zbi_append_section(container_hdr, zbi_max, sizeof(kPlatformId), |
| ZBI_TYPE_PLATFORM_ID, 0, 0, &kPlatformId); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| // PSCI driver. |
| res = zbi_append_section(container_hdr, zbi_max, sizeof(kPsciDriver), |
| ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_PSCI, 0, |
| &kPsciDriver); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| // Timer driver. |
| res = zbi_append_section(container_hdr, zbi_max, sizeof(kTimerDriver), |
| ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GENERIC_TIMER, 0, |
| &kTimerDriver); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| #elif __x86_64__ |
| // ACPI root table pointer. |
| res = zbi_append_section(container_hdr, zbi_max, sizeof(uint64_t), |
| ZBI_TYPE_ACPI_RSDP, 0, 0, &kAcpiOffset); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| // E820 memory map. |
| E820Map e820_map(phys_mem.size(), dev_mem); |
| for (const auto& range : dev_mem) { |
| e820_map.AddReservedRegion(range.addr, range.size); |
| } |
| const size_t e820_size = e820_map.size() * sizeof(e820entry_t); |
| void* e820_addr = nullptr; |
| res = zbi_create_section(container_hdr, zbi_max, e820_size, |
| ZBI_TYPE_E820_TABLE, 0, 0, &e820_addr); |
| if (res != ZBI_RESULT_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| e820_map.copy(static_cast<e820entry_t*>(e820_addr)); |
| #endif |
| |
| res = zbi_check(container_hdr, nullptr); |
| if (res != ZBI_RESULT_OK) { |
| FXL_LOG(ERROR) << "Invalid Zircon container: " << res; |
| return ZX_ERR_INTERNAL; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t setup_zircon(const GuestConfig& cfg, const PhysMem& phys_mem, |
| const DevMem& dev_mem, |
| const std::vector<PlatformDevice*>& devices, |
| uintptr_t* guest_ip, uintptr_t* boot_ptr) { |
| zx_status_t status = read_unified_zbi(cfg.kernel_path(), kKernelOffset, |
| kRamdiskOffset, phys_mem, guest_ip); |
| |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = build_data_zbi(cfg, phys_mem, dev_mem, devices, kRamdiskOffset); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *boot_ptr = kRamdiskOffset; |
| return ZX_OK; |
| } |