blob: 65338f362bff83a90b4e86af6db4dad3affc76d1 [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 <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ios>
#include <vector>
#include <fbl/string_buffer.h>
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <trace-provider/provider.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/hypervisor.h>
#include "garnet/bin/guest/vmm/guest_config.h"
#include "garnet/bin/guest/vmm/guest_view.h"
#include "garnet/bin/guest/vmm/linux.h"
#include "garnet/bin/guest/vmm/zircon.h"
#include "garnet/lib/machina/address.h"
#include "garnet/lib/machina/framebuffer_scanout.h"
#include "garnet/lib/machina/guest.h"
#include "garnet/lib/machina/guest_controller_impl.h"
#include "garnet/lib/machina/hid_event_source.h"
#include "garnet/lib/machina/input_dispatcher.h"
#include "garnet/lib/machina/interrupt_controller.h"
#include "garnet/lib/machina/pci.h"
#include "garnet/lib/machina/uart.h"
#include "garnet/lib/machina/vcpu.h"
#include "garnet/lib/machina/virtio_balloon.h"
#include "garnet/lib/machina/virtio_block.h"
#include "garnet/lib/machina/virtio_console.h"
#include "garnet/lib/machina/virtio_gpu.h"
#include "garnet/lib/machina/virtio_input.h"
#include "garnet/lib/machina/virtio_net.h"
#include "garnet/lib/machina/virtio_vsock.h"
#include "garnet/public/lib/fxl/files/file.h"
#include "lib/app/cpp/startup_context.h"
#if __aarch64__
#include "garnet/lib/machina/arch/arm64/pl031.h"
static constexpr size_t kNumUarts = 1;
static constexpr uint64_t kUartBases[kNumUarts] = {
// TODO(abdulla): Considering parsing this from the MDI.
machina::kPl011PhysBase,
};
#elif __x86_64__
#include "garnet/lib/machina/arch/x86/acpi.h"
#include "garnet/lib/machina/arch/x86/io_port.h"
#include "garnet/lib/machina/arch/x86/page_table.h"
#include "garnet/lib/machina/arch/x86/tpm.h"
static constexpr char kDsdtPath[] = "/pkg/data/dsdt.aml";
static constexpr char kMcfgPath[] = "/pkg/data/mcfg.aml";
static constexpr size_t kNumUarts = 4;
static constexpr uint64_t kUartBases[kNumUarts] = {
machina::kI8250Base0,
machina::kI8250Base1,
machina::kI8250Base2,
machina::kI8250Base3,
};
#endif
static constexpr size_t kInputQueueDepth = 64;
static void balloon_stats_handler(machina::VirtioBalloon* balloon,
uint32_t threshold,
const virtio_balloon_stat_t* stats,
size_t len) {
for (size_t i = 0; i < len; ++i) {
if (stats[i].tag != VIRTIO_BALLOON_S_AVAIL) {
continue;
}
uint32_t current_pages = balloon->num_pages();
uint32_t available_pages =
static_cast<uint32_t>(stats[i].val / machina::VirtioBalloon::kPageSize);
uint32_t target_pages = current_pages + (available_pages - threshold);
if (current_pages == target_pages) {
return;
}
FXL_LOG(INFO) << "adjusting target pages " << std::hex << current_pages
<< " -> " << std::hex << target_pages;
zx_status_t status = balloon->UpdateNumPages(target_pages);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Error " << status << " updating balloon size";
}
return;
}
}
typedef struct balloon_task_args {
machina::VirtioBalloon* balloon;
const GuestConfig* cfg;
} balloon_task_args_t;
static int balloon_stats_task(void* ctx) {
fbl::unique_ptr<balloon_task_args_t> args(
static_cast<balloon_task_args_t*>(ctx));
machina::VirtioBalloon* balloon = args->balloon;
zx_duration_t interval = args->cfg->balloon_interval();
uint32_t threshold = args->cfg->balloon_pages_threshold();
while (true) {
zx_nanosleep(zx_deadline_after(interval));
args->balloon->RequestStats(
[balloon, threshold](const virtio_balloon_stat_t* stats, size_t len) {
balloon_stats_handler(balloon, threshold, stats, len);
});
}
return ZX_OK;
}
static zx_status_t poll_balloon_stats(machina::VirtioBalloon* balloon,
const GuestConfig* cfg) {
thrd_t thread;
auto args = new balloon_task_args_t{balloon, cfg};
int ret = thrd_create_with_name(&thread, balloon_stats_task, args,
"virtio-balloon");
if (ret != thrd_success) {
FXL_LOG(ERROR) << "Failed to create balloon thread " << ret;
delete args;
return ZX_ERR_INTERNAL;
}
ret = thrd_detach(thread);
if (ret != thrd_success) {
FXL_LOG(ERROR) << "Failed to detach balloon thread " << ret;
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
static zx_status_t setup_zircon_framebuffer(
machina::VirtioGpu* gpu, fbl::unique_ptr<machina::GpuScanout>* scanout) {
// Try software framebuffer.
zx_status_t status = machina::FramebufferScanout::Create(scanout);
if (status != ZX_OK) {
return status;
}
return gpu->AddScanout(scanout->get());
}
static zx_status_t setup_scenic_framebuffer(
fuchsia::sys::StartupContext* startup_context, machina::VirtioGpu* gpu,
machina::InputDispatcher* input_dispatcher,
machina::GuestControllerImpl* guest_controller,
fbl::unique_ptr<machina::GpuScanout>* scanout) {
fbl::unique_ptr<ScenicScanout> scenic_scanout;
zx_status_t status =
ScenicScanout::Create(startup_context, input_dispatcher, &scenic_scanout);
if (status != ZX_OK) {
return status;
}
guest_controller->set_view_provider(scenic_scanout.get());
*scanout = std::move(scenic_scanout);
return gpu->AddScanout(scanout->get());
}
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;
}
}
return parser.ParseArgcArgv(argc, argv);
}
int main(int argc, char** argv) {
async::Loop loop(&kAsyncLoopConfigMakeDefault);
trace::TraceProvider trace_provider(loop.async());
std::unique_ptr<fuchsia::sys::StartupContext> startup_context =
fuchsia::sys::StartupContext::CreateFromStartupInfo();
GuestConfig cfg;
zx_status_t status =
read_guest_cfg("/guest/data/guest.cfg", argc, argv, &cfg);
if (status != ZX_OK) {
return status;
}
machina::Guest guest;
status = guest.Init(cfg.memory());
if (status != ZX_OK) {
return status;
}
// Instantiate the controller service.
machina::GuestControllerImpl guest_controller(startup_context.get(),
guest.phys_mem());
#if __x86_64__
status = machina::create_page_table(guest.phys_mem());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create page table";
return status;
}
machina::AcpiConfig acpi_cfg = {
.dsdt_path = kDsdtPath,
.mcfg_path = kMcfgPath,
.io_apic_addr = machina::kIoApicPhysBase,
.num_cpus = cfg.num_cpus(),
};
status = machina::create_acpi_table(acpi_cfg, guest.phys_mem());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create ACPI table";
return status;
}
#endif // __x86_64__
// Setup kernel.
uintptr_t guest_ip = 0;
uintptr_t boot_ptr = 0;
switch (cfg.kernel()) {
case Kernel::ZIRCON:
status = setup_zircon(cfg, guest.phys_mem(), &guest_ip, &boot_ptr);
break;
case Kernel::LINUX:
status = setup_linux(cfg, guest.phys_mem(), &guest_ip, &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();
return status;
}
// Setup UARTs.
machina::Uart uart[kNumUarts];
for (size_t i = 0; i < kNumUarts; i++) {
status = uart[i].Init(&guest, kUartBases[i]);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create UART at " << std::hex
<< kUartBases[i];
return status;
}
}
// Setup interrupt controller.
machina::InterruptController interrupt_controller;
#if __aarch64__
status = interrupt_controller.Init(&guest, cfg.gic_version());
#elif __x86_64__
status = interrupt_controller.Init(&guest);
#endif
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create interrupt controller";
return status;
}
auto initialize_vcpu = [boot_ptr, &interrupt_controller](
machina::Guest* guest, uintptr_t guest_ip,
uint64_t id, machina::Vcpu* vcpu) {
zx_status_t status = vcpu->Create(guest, guest_ip, id);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create VCPU";
return status;
}
// Register VCPU with ID 0.
status = interrupt_controller.RegisterVcpu(id, vcpu);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to register VCPU with interrupt controller";
return status;
}
// Setup initial VCPU state.
zx_vcpu_state_t vcpu_state = {};
#if __aarch64__
vcpu_state.x[0] = boot_ptr;
#elif __x86_64__
vcpu_state.rsi = boot_ptr;
#endif
// Begin VCPU execution.
return vcpu->Start(&vcpu_state);
};
guest.RegisterVcpuFactory(initialize_vcpu);
#if __aarch64__
machina::Pl031 pl031;
status = pl031.Init(&guest);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create PL031 RTC";
return status;
}
#elif __x86_64__
// Setup IO ports.
machina::IoPort io_port;
status = io_port.Init(&guest);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create IO ports";
return status;
}
// Setup TPM
machina::Tpm tpm;
status = tpm.Init(&guest);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create TPM";
return status;
}
#endif
// Setup PCI.
machina::PciBus bus(&guest, &interrupt_controller);
status = bus.Init();
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create PCI bus";
return status;
}
// Setup balloon device.
machina::VirtioBalloon balloon(guest.phys_mem());
balloon.set_deflate_on_demand(cfg.balloon_demand_page());
status = bus.Connect(balloon.pci_device());
if (status != ZX_OK) {
return status;
}
if (cfg.balloon_interval() > 0) {
poll_balloon_stats(&balloon, &cfg);
}
// Setup block device.
std::vector<fbl::unique_ptr<machina::VirtioBlock>> block_devices;
for (const auto& block_spec : cfg.block_devices()) {
fbl::unique_ptr<machina::BlockDispatcher> dispatcher;
if (!block_spec.path.empty()) {
status = machina::BlockDispatcher::CreateFromPath(
block_spec.path.c_str(), block_spec.mode, block_spec.data_plane,
guest.phys_mem(), &dispatcher);
} else if (!block_spec.guid.empty()) {
status = machina::BlockDispatcher::CreateFromGuid(
block_spec.guid, cfg.block_wait() ? ZX_TIME_INFINITE : 0,
block_spec.mode, block_spec.data_plane, guest.phys_mem(),
&dispatcher);
} else {
FXL_LOG(ERROR) << "Block spec missing path or GUID attributes";
return ZX_ERR_INVALID_ARGS;
}
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create block dispatcher " << status;
return status;
}
if (block_spec.volatile_writes) {
status = machina::BlockDispatcher::CreateVolatileWrapper(
fbl::move(dispatcher), &dispatcher);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create volatile block dispatcher";
return status;
}
}
auto block = fbl::make_unique<machina::VirtioBlock>(guest.phys_mem());
status = block->SetDispatcher(fbl::move(dispatcher));
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to set block dispatcher " << status;
return status;
}
status = block->Start();
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start block device " << status;
return status;
}
status = bus.Connect(block->pci_device());
if (status != ZX_OK) {
return status;
}
block_devices.push_back(fbl::move(block));
}
// Setup console
machina::VirtioConsole console(guest.phys_mem(), guest.device_async(),
guest_controller.TakeSocket());
status = console.Start();
if (status != ZX_OK) {
return status;
}
status = bus.Connect(console.pci_device());
if (status != ZX_OK) {
return status;
}
machina::InputDispatcher input_dispatcher(kInputQueueDepth);
machina::HidEventSource hid_event_source(&input_dispatcher);
machina::VirtioKeyboard keyboard(input_dispatcher.Keyboard(),
guest.phys_mem(), "machina-keyboard",
"serial-number");
machina::VirtioRelativePointer mouse(input_dispatcher.Mouse(),
guest.phys_mem(), "machina-mouse",
"serial-number");
machina::VirtioAbsolutePointer touch(
input_dispatcher.Touch(), guest.phys_mem(), "machina-touch",
"serial-number", kGuestViewDisplayWidth, kGuestViewDisplayHeight);
machina::VirtioGpu gpu(guest.phys_mem(), guest.device_async());
fbl::unique_ptr<machina::GpuScanout> gpu_scanout;
if (cfg.display() != GuestDisplay::NONE) {
// Setup keyboard device.
status = keyboard.Start();
if (status != ZX_OK) {
return status;
}
status = bus.Connect(keyboard.pci_device());
if (status != ZX_OK) {
return status;
}
// Setup mouse device.
status = mouse.Start();
if (status != ZX_OK) {
return status;
}
status = bus.Connect(mouse.pci_device());
if (status != ZX_OK) {
return status;
}
// Setup touch device. Note that this device is used for all pointer events
// when using a scenic framebuffer because the pointer positions are
// absolute even when using a mouse.
status = touch.Start();
if (status != ZX_OK) {
return status;
}
status = bus.Connect(touch.pci_device());
if (status != ZX_OK) {
return status;
}
if (cfg.display() == GuestDisplay::FRAMEBUFFER) {
// Setup GPU device.
status = setup_zircon_framebuffer(&gpu, &gpu_scanout);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to acquire framebuffer " << status;
return status;
}
// When displaying to the framebuffer, we should read input events
// directly from the input devics.
status = hid_event_source.Start();
} else {
// Expose a view that can be composited by mozart. Input events will be
// injected by the view events.
status = setup_scenic_framebuffer(startup_context.get(), &gpu,
&input_dispatcher, &guest_controller,
&gpu_scanout);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create scenic view " << status;
return status;
}
}
if (status == ZX_OK) {
status = gpu.Init();
if (status != ZX_OK) {
return status;
}
status = bus.Connect(gpu.pci_device());
if (status != ZX_OK) {
return status;
}
}
}
// Setup net device.
machina::VirtioNet net(guest.phys_mem(), guest.device_async());
status = net.Start("/dev/class/ethernet/000");
if (status == ZX_OK) {
// If we started the net device, then connect to the PCI bus.
status = bus.Connect(net.pci_device());
if (status != ZX_OK) {
return status;
}
} else {
FXL_LOG(INFO) << "Could not open Ethernet device";
}
// Setup vsock device.
machina::VirtioVsock vsock(startup_context.get(), guest.phys_mem(),
guest.device_async());
status = bus.Connect(vsock.pci_device());
if (status != ZX_OK) {
return status;
}
// GPU back-ends can take some time to initialize. Wait for them to be
// created before starting the VCPU so that we can ensure we have the
// framebuffer allocated before software attempts to interface with it.
auto start_task = [&loop, &guest, guest_ip] {
zx_status_t status = guest.StartVcpu(guest_ip, 0 /* id */);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start VCPU-0 " << status;
loop.Quit();
}
};
if (gpu_scanout) {
gpu_scanout->WhenReady(
[&loop, &start_task] { async::PostTask(loop.async(), start_task); });
} else {
start_task();
}
loop.Run();
return guest.Join();
}