blob: d72b647618d851024eae76bf11e324ea490ecd0e [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 <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <atomic>
#include <unordered_map>
#include <vector>
#include <fuchsia/guest/vmm/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/util.h>
#include <lib/fxl/strings/string_printf.h>
#include <trace-provider/provider.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/hypervisor.h>
#include "garnet/bin/guest/vmm/controller/virtio_balloon.h"
#include "garnet/bin/guest/vmm/controller/virtio_block.h"
#include "garnet/bin/guest/vmm/controller/virtio_console.h"
#include "garnet/bin/guest/vmm/controller/virtio_gpu.h"
#include "garnet/bin/guest/vmm/controller/virtio_input.h"
#include "garnet/bin/guest/vmm/controller/virtio_net.h"
#include "garnet/bin/guest/vmm/controller/virtio_rng.h"
#include "garnet/bin/guest/vmm/controller/virtio_wl.h"
#include "garnet/bin/guest/vmm/guest.h"
#include "garnet/bin/guest/vmm/guest_config.h"
#include "garnet/bin/guest/vmm/instance_controller_impl.h"
#include "garnet/bin/guest/vmm/interrupt_controller.h"
#include "garnet/bin/guest/vmm/linux.h"
#include "garnet/bin/guest/vmm/pci.h"
#include "garnet/bin/guest/vmm/platform_device.h"
#include "garnet/bin/guest/vmm/uart.h"
#include "garnet/bin/guest/vmm/vcpu.h"
#include "garnet/bin/guest/vmm/virtio_net_legacy.h"
#include "garnet/bin/guest/vmm/virtio_vsock.h"
#include "garnet/bin/guest/vmm/zircon.h"
#include "garnet/public/lib/fxl/files/file.h"
#if __aarch64__
#include "garnet/bin/guest/vmm/arch/arm64/pl031.h"
#elif __x86_64__
#include "garnet/bin/guest/vmm/arch/x64/acpi.h"
#include "garnet/bin/guest/vmm/arch/x64/io_port.h"
#include "garnet/bin/guest/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_status_t read_guest_cfg(const char* cfg_path, int argc, char** argv,
GuestConfig* cfg) {
GuestConfigParser parser(cfg);
std::string cfg_str;
if (files::ReadFileToString(cfg_path, &cfg_str)) {
zx_status_t status = parser.ParseConfig(cfg_str);
if (status != ZX_OK) {
return status;
}
}
zx_status_t status = parser.ParseArgcArgv(argc, argv);
if (status != ZX_OK) {
return status;
}
parser.SetDefaults();
return ZX_OK;
}
static zx_gpaddr_t alloc_device_addr(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) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
trace::TraceProvider trace_provider(loop.dispatcher());
std::unique_ptr<component::StartupContext> context =
component::StartupContext::CreateFromStartupInfo();
fuchsia::guest::LaunchInfo launch_info;
fuchsia::guest::vmm::LaunchInfoProviderSyncPtr launch_info_provider;
context->ConnectToEnvironmentService(launch_info_provider.NewRequest());
zx_status_t status = launch_info_provider->GetLaunchInfo(&launch_info);
// NOTE: This isn't an error yet since only the guest_manager exposes the
// LaunchInfoProvider service. This will become an error once we invert the
// dependency between guest_runner and guest_manager.
if (status != ZX_OK) {
FXL_LOG(INFO) << "No launch info provided.";
}
InstanceControllerImpl instance_controller;
fuchsia::sys::LauncherPtr launcher;
context->environment()->GetLauncher(launcher.NewRequest());
GuestConfig cfg;
status = read_guest_cfg("/guest/data/guest.cfg", argc, argv, &cfg);
if (status != ZX_OK) {
return status;
}
DevMem dev_mem;
for (const MemorySpec& spec : cfg.memory()) {
// Avoid a collision between static and dynamic address assignment.
if (spec.base + spec.size > kFirstDynamicDeviceAddr) {
FXL_LOG(ERROR) << "Requested memory should be less than "
<< kFirstDynamicDeviceAddr;
return ZX_ERR_INVALID_ARGS;
}
// Add device memory range.
if (spec.policy == MemoryPolicy::HOST_DEVICE &&
!dev_mem.AddRange(spec.base, spec.size)) {
FXL_LOG(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(instance_controller.SerialSocket());
status = uart.Init(&guest);
if (status != ZX_OK) {
FXL_LOG(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) {
FXL_LOG(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) {
FXL_LOG(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) {
FXL_LOG(ERROR) << "Failed to create IO ports " << status;
return status;
}
#endif
// Setup PCI.
PciBus bus(&guest, &interrupt_controller);
status = bus.Init();
if (status != ZX_OK) {
FXL_LOG(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(), true);
if (status != ZX_OK) {
return status;
}
status = balloon.Start(guest.object(), launcher.get(),
guest.device_dispatcher());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start balloon device " << status;
return status;
}
}
// Setup block device.
//
// We first add the devices specified in the package config file, followed by
// the devices in the launch_info.
std::vector<fuchsia::guest::BlockDevice> block_infos;
for (size_t i = 0; i < cfg.block_devices().size(); i++) {
const auto& block_spec = cfg.block_devices()[i];
if (block_spec.path.empty()) {
FXL_LOG(ERROR) << "Block spec missing path attribute " << status;
return ZX_ERR_INVALID_ARGS;
}
uint32_t flags = ZX_FS_RIGHT_READABLE;
if (block_spec.mode == fuchsia::guest::BlockMode::READ_WRITE) {
flags |= ZX_FS_RIGHT_WRITABLE;
}
fidl::InterfaceHandle<fuchsia::io::File> file;
status = fdio_open(block_spec.path.c_str(), flags,
file.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to open " << block_spec.path << " " << status;
return status;
}
block_infos.push_back({
.id = fxl::StringPrintf("block-%zu", i),
.mode = block_spec.mode,
.format = block_spec.format,
.file = std::move(file),
});
}
if (launch_info.block_devices) {
block_infos.insert(
block_infos.end(),
std::make_move_iterator(launch_info.block_devices->begin()),
std::make_move_iterator(launch_info.block_devices->end()));
}
// Create a new VirtioBlock device for each device requested.
std::vector<std::unique_ptr<VirtioBlock>> block_devices;
for (auto& block_device : block_infos) {
auto block =
std::make_unique<VirtioBlock>(block_device.mode, guest.phys_mem());
status = bus.Connect(block->pci_device(), true);
if (status != ZX_OK) {
return status;
}
status = block->Start(guest.object(), std::move(block_device.id),
block_device.format, block_device.file.Bind(),
launcher.get(), guest.device_dispatcher());
if (status != ZX_OK) {
FXL_LOG(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(), true);
if (status != ZX_OK) {
return status;
}
status = console.Start(guest.object(), instance_controller.SerialSocket(),
launcher.get(), guest.device_dispatcher());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start console device " << status;
return status;
}
}
VirtioGpu gpu(guest.phys_mem());
VirtioInput input(guest.phys_mem());
if (cfg.virtio_gpu()) {
// Setup input device.
status = bus.Connect(input.pci_device(), true);
if (status != ZX_OK) {
return status;
}
fidl::InterfaceHandle<fuchsia::guest::device::ViewListener> view_listener;
status = input.Start(guest.object(), view_listener.NewRequest(),
launcher.get(), guest.device_dispatcher());
if (status != ZX_OK) {
return status;
}
// Setup GPU device.
status = bus.Connect(gpu.pci_device(), true);
if (status != ZX_OK) {
return status;
}
status = gpu.Start(guest.object(), std::move(view_listener), launcher.get(),
guest.device_dispatcher());
if (status != ZX_OK) {
return status;
}
}
// Setup net device.
VirtioNetLegacy legacy_net(guest.phys_mem(), guest.device_dispatcher());
VirtioNet net(guest.phys_mem());
if (cfg.virtio_net()) {
if (cfg.legacy_net()) {
status = bus.Connect(legacy_net.pci_device());
if (status != ZX_OK) {
return status;
}
status = legacy_net.Start("/dev/class/ethernet/000");
if (status != ZX_OK) {
FXL_LOG(INFO) << "Could not open Ethernet device";
return status;
}
} else {
status = bus.Connect(net.pci_device(), true);
if (status != ZX_OK) {
return status;
}
status =
net.Start(guest.object(), launcher.get(), guest.device_dispatcher());
if (status != ZX_OK) {
FXL_LOG(INFO) << "Could not open Ethernet device";
return status;
}
}
}
// Setup RNG device.
VirtioRng rng(guest.phys_mem());
if (cfg.virtio_rng()) {
status = bus.Connect(rng.pci_device(), true);
if (status != ZX_OK) {
return status;
}
status =
rng.Start(guest.object(), launcher.get(), guest.device_dispatcher());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start RNG device" << status;
return status;
}
}
// Setup vsock device.
VirtioVsock vsock(context.get(), guest.phys_mem(), guest.device_dispatcher());
if (cfg.virtio_vsock()) {
status = bus.Connect(vsock.pci_device());
if (status != ZX_OK) {
return status;
}
}
// Setup wayland device.
VirtioWl wl(guest.phys_mem());
if (launch_info.wayland_device) {
size_t wl_dev_mem_size = launch_info.wayland_device->memory;
zx_gpaddr_t wl_dev_mem_offset = alloc_device_addr(wl_dev_mem_size);
if (!dev_mem.AddRange(wl_dev_mem_offset, wl_dev_mem_size)) {
FXL_LOG(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) {
FXL_LOG(INFO) << "Could not create VMAR for wayland device";
return status;
}
status = bus.Connect(wl.pci_device(), true);
if (status != ZX_OK) {
FXL_LOG(INFO) << "Could not connect wayland device";
return status;
}
status = wl.Start(guest.object(), std::move(wl_vmar),
std::move(launch_info.wayland_device->dispatcher),
launcher.get(), guest.device_dispatcher());
if (status != ZX_OK) {
FXL_LOG(INFO) << "Could not start wayland device";
return status;
}
}
#if __x86_64__
status = create_page_table(guest.phys_mem());
if (status != ZX_OK) {
FXL_LOG(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) {
FXL_LOG(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())) {
FXL_LOG(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()) {
case Kernel::ZIRCON:
status = setup_zircon(cfg, guest.phys_mem(), dev_mem, platform_devices,
&entry, &boot_ptr);
break;
case Kernel::LINUX:
status = setup_linux(cfg, guest.phys_mem(), dev_mem, platform_devices,
&entry, &boot_ptr);
break;
default:
FXL_LOG(ERROR) << "Unknown kernel";
return ZX_ERR_INVALID_ARGS;
}
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to load kernel " << cfg.kernel_path() << " "
<< status;
return status;
}
// Setup primary VCPU.
status = guest.StartVcpu(0 /* id */, entry, boot_ptr);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start VCPU-0 " << status;
loop.Quit();
}
status = instance_controller.AddPublicService(context.get());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to add public service " << status;
loop.Quit();
}
status = balloon.AddPublicService(context.get());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to add public service " << status;
loop.Quit();
}
loop.Run();
return guest.Join();
}