blob: 27f892c63f78145f956b92d2ab0419c0746f0dc3 [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 "garnet/bin/guest/vmm/zircon.h"
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.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/image.h>
#include "garnet/bin/guest/vmm/kernel.h"
#include "garnet/lib/machina/guest.h"
#if __aarch64__
static constexpr uintptr_t kKernelOffset = 0;
static zbi_mem_range_t mem_config[] = {
{
.type = ZBI_MEM_RANGE_RAM,
.paddr = 0,
.length = 0x40000000, // Set to phys_mem.size().
},
{
.type = ZBI_MEM_RANGE_PERIPHERAL,
.paddr = 0xe8100000,
.length = 0x17f00000,
},
{
// Reserved for RTC.
.type = ZBI_MEM_RANGE_RESERVED,
.paddr = 0x09010000,
.length = 0x1000, // 4KB
},
{
// Reserved for MMIO.
.type = ZBI_MEM_RANGE_RESERVED,
.paddr = 0x06fe0000,
.length = 0x1000000, // 16MB
},
{
// Reserved for ECAM.
.type = ZBI_MEM_RANGE_RESERVED,
.paddr = 0x2e000000,
.length = 0x1000000, // 16MB
},
};
static constexpr zbi_platform_id_t kPlatformId = {
.vid = 3, // PDEV_VID_GOOGLE
.pid = 2, // PDEV_PID_MACHINA
.board_name = "machina",
};
static constexpr dcfg_simple_t kUartDriver = {
.mmio_phys = 0xfff32000,
.irq = 111,
};
static constexpr dcfg_arm_gicv2_driver_t kGicV2Driver = {
.mmio_phys = 0xe82b0000,
.gicd_offset = 0x1000,
.gicc_offset = 0x2000,
.gich_offset = 0x4000,
.gicv_offset = 0x6000,
.ipi_base = 12,
.optional = true,
.use_msi = true,
};
static constexpr dcfg_arm_gicv3_driver_t kGicV3Driver = {
.mmio_phys = 0xe82b0000,
.gicd_offset = 0x00000,
.gicr_offset = 0xa0000,
.gicr_stride = 0x20000,
.ipi_base = 12,
.optional = true,
};
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/lib/machina/arch/x86/acpi.h"
#include "garnet/lib/machina/arch/x86/e820.h"
#endif
static bool is_zbi(const zbi_header_t* header) {
return header->type == ZBI_TYPE_CONTAINER &&
header->length > sizeof(zbi_header_t) &&
header->extra == ZBI_CONTAINER_MAGIC &&
header->flags & ZBI_FLAG_VERSION && header->magic == ZBI_ITEM_MAGIC;
}
static zx_status_t load_bootfs(const int fd, const machina::PhysMem& phys_mem,
const uintptr_t zbi_off) {
zbi_header_t ramdisk_hdr;
ssize_t ret = read(fd, &ramdisk_hdr, sizeof(zbi_header_t));
if (ret != sizeof(zbi_header_t)) {
FXL_LOG(ERROR) << "Failed to read BOOTFS image header";
return ZX_ERR_IO;
}
if (!is_zbi(&ramdisk_hdr)) {
FXL_LOG(ERROR) << "Invalid BOOTFS image header";
return ZX_ERR_IO_DATA_INTEGRITY;
}
if (ramdisk_hdr.length > phys_mem.size() - zbi_off) {
FXL_LOG(ERROR) << "BOOTFS image is too large";
return ZX_ERR_OUT_OF_RANGE;
}
auto container_hdr = phys_mem.as<zbi_header_t>(zbi_off);
const uintptr_t data_off =
zbi_off + sizeof(zbi_header_t) + container_hdr->length;
ret = read(fd, phys_mem.as<void>(data_off, ramdisk_hdr.length),
ramdisk_hdr.length);
if (ret < 0 || (size_t)ret != ramdisk_hdr.length) {
FXL_LOG(ERROR) << "Failed to read BOOTFS image data";
return ZX_ERR_IO;
}
container_hdr->length += ZBI_ALIGN(ramdisk_hdr.length);
return ZX_OK;
}
static zx_status_t create_zbi(const GuestConfig& cfg,
const machina::PhysMem& phys_mem,
const uintptr_t kernel_off, uintptr_t zbi_off) {
if (ZBI_ALIGN(zbi_off) != zbi_off) {
FXL_LOG(ERROR) << "ZBI offset has invalid alignment";
return ZX_ERR_INVALID_ARGS;
}
// Create ZBI container.
const size_t zbi_max = phys_mem.size() - zbi_off;
auto container_hdr = phys_mem.as<zbi_header_t>(zbi_off);
*container_hdr = ZBI_CONTAINER_HEADER(0);
// TODO(PD-166): We should split reading the kernel ZBI item from reading the
// additional ZBI items in the kernel ZBI container. This is so that we can
// avoid the memcpy below.
auto kernel_hdr = phys_mem.as<zircon_kernel_t>(kernel_off);
const uint32_t file_len = sizeof(zbi_header_t) + kernel_hdr->hdr_file.length;
const uint32_t kernel_len =
offsetof(zircon_kernel_t, data_kernel) + kernel_hdr->hdr_kernel.length;
// Copy additional items from the kernel ZBI container to our ZBI container.
if (file_len > kernel_len) {
const uint32_t items_len = file_len - kernel_len;
const uintptr_t data_off =
zbi_off + sizeof(zbi_header_t) + container_hdr->length;
memcpy(phys_mem.as<void>(data_off, items_len),
phys_mem.as<void>(kernel_off + kernel_len, items_len), items_len);
container_hdr->length += ZBI_ALIGN(items_len);
}
// Update the kernel ZBI container header.
kernel_hdr->hdr_file = ZBI_CONTAINER_HEADER(static_cast<uint32_t>(kernel_len));
// 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;
}
#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.num_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.
mem_config[0].length = cfg.memory();
res = zbi_append_section(container_hdr, zbi_max, sizeof(mem_config),
ZBI_TYPE_MEM_CONFIG, 0, 0, mem_config);
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;
}
// UART driver.
res = zbi_append_section(container_hdr, zbi_max, sizeof(kUartDriver),
ZBI_TYPE_KERNEL_DRIVER, KDRV_PL011_UART, 0,
&kUartDriver);
if (res != ZBI_RESULT_OK) {
return ZX_ERR_INTERNAL;
}
// GICv2 driver.
res = zbi_append_section(container_hdr, zbi_max, sizeof(kGicV2Driver),
ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V2, 0,
&kGicV2Driver);
if (res != ZBI_RESULT_OK) {
return ZX_ERR_INTERNAL;
}
// GICv3 driver.
res = zbi_append_section(container_hdr, zbi_max, sizeof(kGicV3Driver),
ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V3, 0,
&kGicV3Driver);
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, &machina::kAcpiOffset);
if (res != ZBI_RESULT_OK) {
return ZX_ERR_INTERNAL;
}
// E820 memory map.
const size_t e820_size = machina::e820_size(phys_mem.size());
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;
}
machina::create_e820(e820_addr, phys_mem.size());
#endif
return ZX_OK;
}
static zx_status_t check_kernel(const machina::PhysMem& phys_mem,
const uintptr_t kernel_off,
uintptr_t* guest_ip) {
auto kernel_hdr = phys_mem.as<zircon_kernel_t>(kernel_off);
zbi_result_t res = zbi_check(kernel_hdr, nullptr);
if (res != ZBI_RESULT_OK ||
!ZBI_IS_KERNEL_BOOTITEM(kernel_hdr->hdr_kernel.type)) {
FXL_LOG(ERROR) << "Invalid Zircon container";
return ZX_ERR_IO_DATA_INTEGRITY;
}
if (kernel_off + offsetof(zircon_kernel_t, data_kernel) +
kernel_hdr->hdr_kernel.length +
kernel_hdr->data_kernel.reserve_memory_size >
phys_mem.size()) {
FXL_LOG(ERROR)
<< "Zircon kernel memory reservation exceeds guest physical memory";
return ZX_ERR_OUT_OF_RANGE;
}
// TODO(PD-166): Reject kernel if file doesn't match size in header.
*guest_ip = kernel_hdr->data_kernel.entry;
return ZX_OK;
}
zx_status_t setup_zircon(const GuestConfig& cfg,
const machina::PhysMem& phys_mem, uintptr_t* guest_ip,
uintptr_t* boot_ptr) {
// Read the kernel image.
zx_status_t status = load_kernel(cfg.kernel_path(), phys_mem, kKernelOffset);
if (status != ZX_OK) {
return status;
}
status = check_kernel(phys_mem, kKernelOffset, guest_ip);
if (status != ZX_OK) {
return status;
}
// Create the ZBI container.
status = create_zbi(cfg, phys_mem, kKernelOffset, kRamdiskOffset);
if (status != ZX_OK) {
return status;
}
// If we have been provided a BOOTFS image, load it.
if (!cfg.ramdisk_path().empty()) {
fbl::unique_fd boot_fd(open(cfg.ramdisk_path().c_str(), O_RDONLY));
if (!boot_fd) {
FXL_LOG(ERROR) << "Failed to open BOOTFS image " << cfg.ramdisk_path();
return ZX_ERR_IO;
}
status = load_bootfs(boot_fd.get(), phys_mem, kRamdiskOffset);
if (status != ZX_OK) {
return status;
}
}
*boot_ptr = kRamdiskOffset;
return ZX_OK;
}