blob: f77d296c76425976d7e0b04ceeba4822e64f23ac [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 "src/virtualization/bin/vmm/zircon.h"
#include <fuchsia/virtualization/cpp/fidl.h>
#include <lib/arch/zbi.h>
#include <lib/fdio/fd.h>
#include <lib/stdcompat/span.h>
#include <lib/zbi-format/board.h>
#include <lib/zbi-format/cpu.h>
#include <lib/zbi-format/driver-config.h>
#include <lib/zbi-format/memory.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zbitl/error-string.h>
#include <lib/zbitl/fd.h>
#include <lib/zbitl/image.h>
#include <lib/zircon-internal/e820.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <iterator>
#include <fbl/unique_fd.h>
#include "src/virtualization/bin/vmm/bits.h"
#include "src/virtualization/bin/vmm/dev_mem.h"
#include "src/virtualization/bin/vmm/guest.h"
#include "src/virtualization/bin/vmm/memory.h"
#include "src/virtualization/bin/vmm/zbi.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 zbi_dcfg_arm_psci_driver_t kPsciDriver = {
.use_hvc = false,
};
static constexpr zbi_dcfg_arm_generic_timer_driver_t kTimerDriver = {
.irq_virt = 27,
};
#elif __x86_64__
static constexpr uintptr_t kKernelOffset = 0x100000;
// If the kernel specifies a load address smaller than this cut off,
// we assume it is position-independent.
//
// TODO(https://fxbug.dev/42107320): Delete once the x86 kernel is position-independent.
constexpr uintptr_t kX86PositionIndependentLoadAddressCutOff = 0x100000;
#include "src/virtualization/bin/vmm/arch/x64/acpi.h"
#else
#error Unknown architecture.
#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(fbl::unique_fd zbi_fd, const uintptr_t kernel_zbi_off,
const uintptr_t data_zbi_off, const PhysMem& phys_mem,
uintptr_t* guest_ip) {
if (align(kernel_zbi_off, ZBI_ALIGNMENT) != kernel_zbi_off) {
FX_LOGS(ERROR) << "Kernel ZBI offset has invalid alignment";
return ZX_ERR_INVALID_ARGS;
}
if (align(data_zbi_off, ZBI_ALIGNMENT) != data_zbi_off) {
FX_LOGS(ERROR) << "Data ZBI offset has invalid alignment";
return ZX_ERR_INVALID_ARGS;
}
if (!zbi_fd) {
FX_LOGS(ERROR) << "Failed to open ZBI";
return ZX_ERR_IO;
}
// Read out the initial headers to check that the ZBI's total memory
// reservation fits into the guest's physical memory.
zbi_header_t kernel_item_header;
zbi_kernel_t kernel_payload_header;
{
if (auto ret = read(zbi_fd.get(), phys_mem.ptr(kernel_zbi_off, sizeof(arch::ZbiKernelImage)),
sizeof(arch::ZbiKernelImage));
ret != sizeof(arch::ZbiKernelImage)) {
FX_LOGS(ERROR) << "Failed to read initial ZBI headers: " << strerror(errno);
return ZX_ERR_IO;
}
if (auto ret = lseek(zbi_fd.get(), 0, SEEK_SET); ret) {
FX_LOGS(ERROR) << "Failed to seek back to beginning of ZBI: " << strerror(errno);
return ZX_ERR_IO;
}
// Dereference and copy for good measure, as we will soon be overwriting
// the kernel ZBI range.
kernel_item_header =
phys_mem.read<zbi_header_t>(kernel_zbi_off + offsetof(arch::ZbiKernelImage, hdr_kernel));
kernel_payload_header =
phys_mem.read<zbi_kernel_t>(kernel_zbi_off + offsetof(arch::ZbiKernelImage, data_kernel));
}
const uintptr_t reserved_size = offsetof(arch::ZbiKernelImage, data_kernel) +
kernel_item_header.length +
kernel_payload_header.reserve_memory_size;
if (kernel_zbi_off + reserved_size > phys_mem.size()) {
FX_LOGS(ERROR) << "Zircon kernel memory reservation exceeds guest physical memory";
return ZX_ERR_OUT_OF_RANGE;
}
// Check that the ZBI's total memory reservation does not overlap the
// ramdisk.
if (is_within(data_zbi_off, kernel_zbi_off, reserved_size)) {
FX_LOGS(ERROR) << "Kernel reservation memory reservation overlaps RAM disk location";
return ZX_ERR_OUT_OF_RANGE;
}
zbitl::View view(std::move(zbi_fd));
if (auto result = zbitl::CheckBootable(view); result.is_error()) {
FX_LOGS(ERROR) << "Unbootable ZBI: " << result.error_value();
return ZX_ERR_IO;
}
auto first = view.begin();
auto second = std::next(first);
size_t kernel_zbi_size = second.item_offset();
size_t data_zbi_size = view.size_bytes() - (second.item_offset() - first.item_offset());
auto kernel_zbi = phys_mem.span<std::byte>(kernel_zbi_off, kernel_zbi_size);
auto data_zbi = phys_mem.span<std::byte>(data_zbi_off, data_zbi_size);
// Now that we have performed basic data integrity checks and know that the
// kernel and data ZBI ranges do not overlap, copy.
if (auto result = view.Copy(kernel_zbi, first, second); result.is_error()) {
FX_LOGS(ERROR) << "Failed to create kernel ZBI: "
<< zbitl::ViewCopyErrorString(result.error_value());
view.ignore_error();
return ZX_ERR_INTERNAL;
}
if (auto result = view.Copy(data_zbi, second, view.end()); result.is_error()) {
FX_LOGS(ERROR) << zbitl::ViewCopyErrorString(result.error_value());
view.ignore_error();
return ZX_ERR_INTERNAL;
}
if (zx_status_t status = LogIfZbiError(view.take_error()); status != ZX_OK) {
return status;
}
*guest_ip = kernel_payload_header.entry + kKernelOffset;
// TODO(https://fxbug.dev/42107320): Transitionally, we assume the x86 entrypoint is
// absolute if it is greater than the fixed load address.
#if __x86_64__
if (kernel_payload_header.entry > kX86PositionIndependentLoadAddressCutOff) {
*guest_ip = kernel_payload_header.entry;
}
#endif
return ZX_OK;
}
static zx_status_t build_data_zbi(const fuchsia::virtualization::GuestConfig& cfg,
const PhysMem& phys_mem, const DevMem& dev_mem,
const std::vector<GuestMemoryRegion>& guest_mem,
const std::vector<PlatformDevice*>& devices, uintptr_t zbi_off) {
const size_t zbi_max = phys_mem.size() - zbi_off;
cpp20::span<std::byte> zbi{phys_mem.aligned_as<std::byte>(zbi_off), zbi_max};
zbitl::Image image(zbi);
// Command line.
const std::string cmdline = cfg.cmdline();
zbitl::ByteView cmdline_bytes = zbitl::AsBytes(cmdline.data(), cmdline.size() + 1);
zx_status_t status =
LogIfZbiError(image.Append(zbi_header_t{.type = ZBI_TYPE_CMDLINE}, cmdline_bytes),
"Failed to append command-line item");
if (status != ZX_OK) {
return status;
}
// Any platform devices
for (auto device : devices) {
status = device->ConfigureZbi(zbi);
if (status != ZX_OK) {
return status;
}
}
std::vector<zbi_mem_range_t> zbi_ranges = ZbiMemoryRanges(dev_mem, guest_mem);
status = LogIfZbiError(
image.Append(zbi_header_t{.type = ZBI_TYPE_MEM_CONFIG}, zbitl::AsBytes(zbi_ranges)),
"Failed to append memory configuration");
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();
status = LogIfZbiError(image.Append(zbi_header_t{.type = ZBI_TYPE_DEPRECATED_CPU_TOPOLOGY_V1},
zbitl::AsBytes(cpu_buffer)),
"Failed to append CPU configuration");
if (status != ZX_OK) {
return status;
}
// Platform ID.
status = LogIfZbiError(
image.Append(zbi_header_t{.type = ZBI_TYPE_PLATFORM_ID}, zbitl::AsBytes(kPlatformId)),
"Failed to append platform ID");
if (status != ZX_OK) {
return status;
}
// PSCI driver.
status = LogIfZbiError(image.Append(
zbi_header_t{
.type = ZBI_TYPE_KERNEL_DRIVER,
.extra = ZBI_KERNEL_DRIVER_ARM_PSCI,
},
zbitl::AsBytes(&kPsciDriver, sizeof(kPsciDriver))),
"Failed to append PSCI driver item");
if (status != ZX_OK) {
return status;
}
// Timer driver.
status = LogIfZbiError(image.Append(
zbi_header_t{
.type = ZBI_TYPE_KERNEL_DRIVER,
.extra = ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER,
},
zbitl::AsBytes(kTimerDriver)),
"Failed to append timer driver item");
if (status != ZX_OK) {
return status;
}
#elif __x86_64__
// ACPI root table pointer.
if (zx_status_t status = LogIfZbiError(
image.Append(zbi_header_t{.type = ZBI_TYPE_ACPI_RSDP}, zbitl::AsBytes(kAcpiOffset)),
"Failed to append root ACPI table pointer");
status != ZX_OK) {
return status;
}
#endif
return ZX_OK;
}
zx_status_t setup_zircon(fuchsia::virtualization::GuestConfig* cfg, const PhysMem& phys_mem,
const DevMem& dev_mem, const std::vector<GuestMemoryRegion>& guest_mem,
const std::vector<PlatformDevice*>& devices, uintptr_t* guest_ip,
uintptr_t* boot_ptr) {
fbl::unique_fd kernel_fd;
zx_status_t status = fdio_fd_create(cfg->mutable_kernel()->TakeChannel().release(),
kernel_fd.reset_and_get_address());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to open kernel image";
return status;
}
status =
read_unified_zbi(std::move(kernel_fd), kKernelOffset, kRamdiskOffset, phys_mem, guest_ip);
if (status != ZX_OK) {
return status;
}
status = build_data_zbi(*cfg, phys_mem, dev_mem, guest_mem, devices, kRamdiskOffset);
if (status != ZX_OK) {
return status;
}
*boot_ptr = kRamdiskOffset;
return ZX_OK;
}