blob: 9b70dda1a88ce38db954fca1f584af5b186b9315 [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 <dirent.h>
#include <fcntl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/virtualization/cpp/fidl.h>
#include <inttypes.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/namespace.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/trace-provider/provider.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/hypervisor.h>
#include <atomic>
#include <unordered_map>
#include <vector>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/virtualization/bin/vmm/controller/virtio_balloon.h"
#include "src/virtualization/bin/vmm/controller/virtio_block.h"
#include "src/virtualization/bin/vmm/controller/virtio_console.h"
#include "src/virtualization/bin/vmm/controller/virtio_gpu.h"
#include "src/virtualization/bin/vmm/controller/virtio_input.h"
#include "src/virtualization/bin/vmm/controller/virtio_magma.h"
#include "src/virtualization/bin/vmm/controller/virtio_net.h"
#include "src/virtualization/bin/vmm/controller/virtio_rng.h"
#include "src/virtualization/bin/vmm/controller/virtio_wl.h"
#include "src/virtualization/bin/vmm/guest.h"
#include "src/virtualization/bin/vmm/guest_impl.h"
#include "src/virtualization/bin/vmm/interrupt_controller.h"
#include "src/virtualization/bin/vmm/linux.h"
#include "src/virtualization/bin/vmm/pci.h"
#include "src/virtualization/bin/vmm/platform_device.h"
#include "src/virtualization/bin/vmm/uart.h"
#include "src/virtualization/bin/vmm/vcpu.h"
#include "src/virtualization/bin/vmm/virtio_vsock.h"
#include "src/virtualization/bin/vmm/zircon.h"
#if __aarch64__
#include "src/virtualization/bin/vmm/arch/arm64/pl031.h"
#elif __x86_64__
#include "src/virtualization/bin/vmm/arch/x64/acpi.h"
#include "src/virtualization/bin/vmm/arch/x64/io_port.h"
#include "src/virtualization/bin/vmm/arch/x64/page_table.h"
static constexpr char kDsdtPath[] = "/pkg/data/dsdt.aml";
static constexpr char kMcfgPath[] = "/pkg/data/mcfg.aml";
#endif
// For devices that can have their addresses anywhere we run a dynamic
// allocator that starts fairly high in the guest physical address space.
static constexpr zx_gpaddr_t kFirstDynamicDeviceAddr = 0xc00000000;
static zx_gpaddr_t AllocDeviceAddr(size_t device_size) {
static zx_gpaddr_t next_device_addr = kFirstDynamicDeviceAddr;
zx_gpaddr_t ret = next_device_addr;
next_device_addr += device_size;
return ret;
}
int main(int argc, char** argv) {
syslog::SetTags({"vmm"});
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
async::Loop device_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
trace::TraceProviderWithFdio trace_provider(loop.dispatcher());
std::unique_ptr<sys::ComponentContext> context =
sys::ComponentContext::CreateAndServeOutgoingDirectory();
fuchsia::virtualization::GuestConfig cfg;
fuchsia::virtualization::GuestConfigProviderSyncPtr cfg_provider;
context->svc()->Connect(cfg_provider.NewRequest());
zx_status_t status = cfg_provider->Get(&cfg);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "No launch info provided";
return status;
}
GuestImpl guest_controller;
fuchsia::sys::LauncherPtr launcher;
context->svc()->Connect(launcher.NewRequest());
DevMem dev_mem;
for (const fuchsia::virtualization::MemorySpec& spec : cfg.memory()) {
// Avoid a collision between static and dynamic address assignment.
if (spec.base + spec.size > kFirstDynamicDeviceAddr) {
FX_LOGS(ERROR) << "Requested memory should be less than " << kFirstDynamicDeviceAddr;
return ZX_ERR_INVALID_ARGS;
}
// Add device memory range.
if (spec.policy == fuchsia::virtualization::MemoryPolicy::HOST_DEVICE &&
!dev_mem.AddRange(spec.base, spec.size)) {
FX_LOGS(ERROR) << "Failed to add device memory at 0x" << std::hex << spec.base;
return ZX_ERR_INTERNAL;
}
}
Guest guest;
status = guest.Init(cfg.memory());
if (status != ZX_OK) {
return status;
}
std::vector<PlatformDevice*> platform_devices;
// Setup UARTs.
Uart uart(guest_controller.SerialSocket());
status = uart.Init(&guest);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create UART at " << status;
return status;
}
platform_devices.push_back(&uart);
// Setup interrupt controller.
InterruptController interrupt_controller(&guest);
#if __aarch64__
status = interrupt_controller.Init(cfg.cpus(), cfg.interrupts());
#elif __x86_64__
status = interrupt_controller.Init();
#endif
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create interrupt controller " << status;
return status;
}
platform_devices.push_back(&interrupt_controller);
#if __aarch64__
// Setup PL031 RTC.
Pl031 pl031;
status = pl031.Init(&guest);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create PL031 RTC " << status;
return status;
}
platform_devices.push_back(&pl031);
#elif __x86_64__
// Setup IO ports.
IoPort io_port;
status = io_port.Init(&guest);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create IO ports " << status;
return status;
}
#endif
// Setup PCI.
PciBus bus(&guest, &interrupt_controller);
status = bus.Init(device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create PCI bus " << status;
return status;
}
platform_devices.push_back(&bus);
// Setup balloon device.
VirtioBalloon balloon(guest.phys_mem());
if (cfg.virtio_balloon()) {
status = bus.Connect(balloon.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = balloon.Start(guest.object(), launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to start balloon device " << status;
return status;
}
}
// Create a new VirtioBlock device for each device requested.
std::vector<std::unique_ptr<VirtioBlock>> block_devices;
for (auto& block_device : *cfg.mutable_block_devices()) {
auto block = std::make_unique<VirtioBlock>(guest.phys_mem(), block_device.mode);
status = bus.Connect(block->pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = block->Start(guest.object(), std::move(block_device.id), block_device.format,
std::move(block_device.file), launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to start block device " << status;
return status;
}
block_devices.push_back(std::move(block));
}
// Setup console device.
VirtioConsole console(guest.phys_mem());
if (cfg.virtio_console()) {
status = bus.Connect(console.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = console.Start(guest.object(), guest_controller.SerialSocket(), launcher.get(),
device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to start console device " << status;
return status;
}
}
VirtioGpu gpu(guest.phys_mem());
VirtioInput input_keyboard(guest.phys_mem(), VirtioInput::Keyboard);
VirtioInput input_pointer(guest.phys_mem(), VirtioInput::Pointer);
if (cfg.virtio_gpu()) {
// Setup keyboard device.
status = bus.Connect(input_keyboard.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = input_keyboard.Start(guest.object(), launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
return status;
}
fidl::InterfaceHandle<fuchsia::virtualization::hardware::KeyboardListener> keyboard_listener;
input_keyboard.Connect(keyboard_listener.NewRequest());
// Setup pointer device.
status = bus.Connect(input_pointer.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = input_pointer.Start(guest.object(), launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
return status;
}
fidl::InterfaceHandle<fuchsia::virtualization::hardware::PointerListener> pointer_listener;
input_pointer.Connect(pointer_listener.NewRequest());
// Setup GPU device.
status = bus.Connect(gpu.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = gpu.Start(guest.object(), std::move(keyboard_listener), std::move(pointer_listener),
launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
return status;
}
}
// Setup RNG device.
VirtioRng rng(guest.phys_mem());
if (cfg.virtio_rng()) {
status = bus.Connect(rng.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = rng.Start(guest.object(), launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to start RNG device " << status;
return status;
}
}
// Setup vsock device. Vsock uses its own dispatcher as a temporary measure
// until it is moved out of process.
async::Loop vsock_loop{&kAsyncLoopConfigNoAttachToCurrentThread};
VirtioVsock vsock(context.get(), guest.phys_mem(), vsock_loop.dispatcher());
if (cfg.virtio_vsock()) {
status = bus.Connect(vsock.pci_device(), vsock_loop.dispatcher(), false);
if (status != ZX_OK) {
return status;
}
status = vsock_loop.StartThread("vsock-handler");
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create vsock async worker " << status;
return status;
}
}
// Setup wayland device.
VirtioWl wl(guest.phys_mem());
if (cfg.has_wayland_device()) {
size_t wl_dev_mem_size = cfg.wayland_device().memory;
zx_gpaddr_t wl_dev_mem_offset = AllocDeviceAddr(wl_dev_mem_size);
if (!dev_mem.AddRange(wl_dev_mem_offset, wl_dev_mem_size)) {
FX_LOGS(INFO) << "Could not reserve device memory range for wayland device";
return status;
}
zx::vmar wl_vmar;
status = guest.CreateSubVmar(wl_dev_mem_offset, wl_dev_mem_size, &wl_vmar);
if (status != ZX_OK) {
FX_LOGS(INFO) << "Could not create VMAR for wayland device";
return status;
}
status = bus.Connect(wl.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
FX_LOGS(INFO) << "Could not connect wayland device";
return status;
}
fidl::InterfaceHandle<fuchsia::sysmem::Allocator> sysmem_allocator = nullptr;
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
sysmem_allocator.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGS(INFO) << "Could not connect to sysmem allocator service";
return status;
}
fidl::InterfaceHandle<fuchsia::scenic::allocation::Allocator> scenic_allocator = nullptr;
status = fdio_service_connect("/svc/fuchsia.scenic.allocation.Allocator",
scenic_allocator.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGS(INFO) << "Could not connect to scenic allocator service";
return status;
}
status =
wl.Start(guest.object(), std::move(wl_vmar),
std::move(cfg.mutable_wayland_device()->dispatcher), std::move(sysmem_allocator),
std::move(scenic_allocator), launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(INFO) << "Could not start wayland device";
return status;
}
}
// Setup magma device.
VirtioMagma magma(guest.phys_mem());
if (cfg.has_magma_device()) {
// TODO(fxbug.dev/12619): simplify vmm launch configs
size_t magma_dev_mem_size = cfg.magma_device().memory;
zx_gpaddr_t magma_dev_mem_offset = AllocDeviceAddr(magma_dev_mem_size);
if (!dev_mem.AddRange(magma_dev_mem_offset, magma_dev_mem_size)) {
FX_PLOGS(INFO, status) << "Could not reserve device memory range for magma device";
return status;
}
zx::vmar magma_vmar;
status = guest.CreateSubVmar(magma_dev_mem_offset, magma_dev_mem_size, &magma_vmar);
if (status != ZX_OK) {
FX_PLOGS(INFO, status) << "Could not create VMAR for magma device";
return status;
}
status = bus.Connect(magma.pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
FX_PLOGS(INFO, status) << "Could not connect magma device";
return status;
}
fidl::InterfaceHandle<fuchsia::virtualization::hardware::VirtioWaylandImporter>
wayland_importer_handle = nullptr;
if (cfg.has_wayland_device()) {
status = wl.GetImporter(wayland_importer_handle.NewRequest());
if (status != ZX_OK) {
FX_PLOGS(INFO, status) << "Could not get wayland importer";
return status;
}
}
status = magma.Start(guest.object(), std::move(magma_vmar), std::move(wayland_importer_handle),
launcher.get(), device_loop.dispatcher());
if (status == ZX_ERR_NOT_FOUND) {
FX_LOGS(INFO) << "Magma device not supported by host";
} else if (status != ZX_OK) {
FX_PLOGS(INFO, status) << "Could not start magma device";
return status;
}
}
// Setup net device.
// We setup networking last, as this can cause a temporary loss of network
// access as we configure the bridge. If networking is lost while loading
// packages for devices, the VMM will fail.
std::vector<std::unique_ptr<VirtioNet>> net_devices;
// NOTE(abdulla): We use mutable_net_devices() here so that we force default
// initialization of the field if it has not been previously set, thus
// avoiding an assertion failure.
for (auto& net_device : *cfg.mutable_net_devices()) {
auto net = std::make_unique<VirtioNet>(guest.phys_mem());
status = bus.Connect(net->pci_device(), device_loop.dispatcher(), true);
if (status != ZX_OK) {
return status;
}
status = net->Start(guest.object(), net_device.mac_address, net_device.enable_bridge,
launcher.get(), device_loop.dispatcher());
if (status != ZX_OK) {
FX_LOGS(INFO) << "Could not open Ethernet device " << status;
return status;
}
net_devices.push_back(std::move(net));
}
#if __x86_64__
status = create_page_table(guest.phys_mem());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create page table " << status;
return status;
}
AcpiConfig acpi_cfg = {
.dsdt_path = kDsdtPath,
.mcfg_path = kMcfgPath,
.io_apic_addr = IoApic::kPhysBase,
.cpus = cfg.cpus(),
};
status = create_acpi_table(acpi_cfg, guest.phys_mem());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create ACPI table " << status;
return status;
}
#endif // __x86_64__
// Add any trap ranges as device memory.
for (const IoMapping& mapping : guest.mappings()) {
if ((mapping.kind() == ZX_GUEST_TRAP_MEM || mapping.kind() == ZX_GUEST_TRAP_BELL) &&
!dev_mem.AddRange(mapping.base(), mapping.size())) {
FX_LOGS(ERROR) << "Failed to add trap range as device memory";
return ZX_ERR_INTERNAL;
}
}
// Setup kernel.
uintptr_t entry = 0;
uintptr_t boot_ptr = 0;
switch (cfg.kernel_type()) {
case fuchsia::virtualization::KernelType::ZIRCON:
status = setup_zircon(&cfg, guest.phys_mem(), dev_mem, platform_devices, &entry, &boot_ptr);
break;
case fuchsia::virtualization::KernelType::LINUX:
status = setup_linux(&cfg, guest.phys_mem(), dev_mem, platform_devices, &entry, &boot_ptr);
break;
default:
FX_LOGS(ERROR) << "Unknown kernel";
return ZX_ERR_INVALID_ARGS;
}
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to load kernel " << status;
return status;
}
// Setup primary VCPU.
status = guest.StartVcpu(0 /* id */, entry, boot_ptr, &loop);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to start VCPU-0 " << status;
loop.Quit();
}
status = guest_controller.AddPublicService(context.get());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to add public service " << status;
loop.Quit();
}
status = balloon.AddPublicService(context.get());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to add public service " << status;
loop.Quit();
}
status = gpu.AddPublicService(context.get());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to add public service " << status;
loop.Quit();
}
// Start the dispatch thread for communicating with the out of process
// devices.
status = device_loop.StartThread("device-worker");
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create async worker " << status;
return status;
}
return loop.Run();
}