| // 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/fit/defer.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 "lib/syslog/cpp/macros.h" |
| #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_sound.h" |
| #include "src/virtualization/bin/vmm/controller/virtio_vsock.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"; |
| #else |
| #error Unknown architecture. |
| #endif |
| |
| #ifdef USE_LEGACY_IN_PROCESS_VSOCK |
| #define IN_PROCESS_VSOCK 1 |
| #else |
| #define IN_PROCESS_VSOCK 0 |
| #endif |
| |
| 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; |
| } |
| |
| DevMem dev_mem; |
| GuestImpl guest_controller; |
| fuchsia::component::RealmSyncPtr realm; |
| |
| context->svc()->Connect(realm.NewRequest()); |
| |
| std::optional guest_opt = std::make_optional<Guest>(); |
| Guest& guest = guest_opt.value(); |
| |
| FX_CHECK(cfg.has_guest_memory()) << "A guest memory value must be provided"; |
| status = guest.Init(cfg.guest_memory()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| std::vector<PlatformDevice*> platform_devices; |
| |
| // Setup interrupt controller. |
| InterruptController interrupt_controller(&guest); |
| #if __aarch64__ |
| status = interrupt_controller.Init(cfg.cpus(), cfg.interrupts()); |
| #elif __x86_64__ |
| status = interrupt_controller.Init(); |
| #else |
| #error Unknown architecture. |
| #endif |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to create interrupt controller"; |
| return status; |
| } |
| platform_devices.push_back(&interrupt_controller); |
| |
| // Setup UARTs. |
| Uart uart(guest_controller.SerialSocket()); |
| #if __aarch64__ |
| status = uart.Init(&guest); |
| #elif __x86_64__ |
| status = uart.Init( |
| &guest, [&interrupt_controller](uint32_t irq) { interrupt_controller.Interrupt(irq); }); |
| #else |
| #error Unknown architecture. |
| #endif |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to create UART"; |
| return status; |
| } |
| platform_devices.push_back(&uart); |
| |
| #if __aarch64__ |
| // Setup PL031 RTC. |
| Pl031 pl031; |
| status = pl031.Init(&guest); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to create PL031 RTC"; |
| 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_PLOGS(ERROR, status) << "Failed to create IO ports"; |
| return status; |
| } |
| #else |
| #error Unknown architecture. |
| #endif |
| |
| // Setup PCI. |
| PciBus bus(&guest, &interrupt_controller); |
| status = bus.Init(device_loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to create PCI bus"; |
| 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect balloon device"; |
| return status; |
| } |
| status = balloon.Start(guest.object(), realm, device_loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start balloon device"; |
| 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, block_device.format); |
| status = bus.Connect(block->pci_device(), device_loop.dispatcher(), true); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to connect block device"; |
| return status; |
| } |
| status = |
| block->Start(guest.object(), std::move(block_device.id), std::move(block_device.client), |
| realm, device_loop.dispatcher(), block_devices.size()); |
| |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start block device"; |
| 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect console device"; |
| return status; |
| } |
| status = console.Start(guest.object(), guest_controller.ConsoleSocket(), realm, |
| device_loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start console device"; |
| 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect keyboard device"; |
| return status; |
| } |
| status = input_keyboard.Start(guest.object(), realm, device_loop.dispatcher(), |
| "virtio_input_keyboard"); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start keyboard device"; |
| 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect mouse device"; |
| return status; |
| } |
| status = input_pointer.Start(guest.object(), realm, device_loop.dispatcher(), |
| "virtio_input_pointer"); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start mouse device"; |
| 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect GPU device"; |
| return status; |
| } |
| status = gpu.Start(guest.object(), std::move(keyboard_listener), std::move(pointer_listener), |
| realm, device_loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start GPU device"; |
| 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect RNG device"; |
| return status; |
| } |
| status = rng.Start(guest.object(), realm, device_loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start RNG device"; |
| return status; |
| } |
| } |
| |
| // Only one of these will be initialized. See fxb/97355 for information. |
| std::unique_ptr<VirtioVsock> legacy_vsock; |
| std::unique_ptr<controller::VirtioVsock> experimental_vsock; |
| |
| // In process vsock requires its own dispatcher. Will only be initialized if the legacy in |
| // process vsock is used. |
| std::unique_ptr<async::Loop> vsock_loop; |
| |
| if constexpr (IN_PROCESS_VSOCK) { |
| vsock_loop = std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread); |
| legacy_vsock = |
| std::make_unique<VirtioVsock>(context.get(), guest.phys_mem(), vsock_loop->dispatcher()); |
| if (cfg.virtio_vsock()) { |
| status = bus.Connect(legacy_vsock->pci_device(), vsock_loop->dispatcher(), false); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to connect Vsock device"; |
| return status; |
| } |
| status = vsock_loop->StartThread("vsock-handler"); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to create vsock async worker"; |
| return status; |
| } |
| } |
| } else { |
| experimental_vsock = std::make_unique<controller::VirtioVsock>(guest.phys_mem()); |
| if (cfg.virtio_vsock()) { |
| status = bus.Connect(experimental_vsock->pci_device(), device_loop.dispatcher(), true); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to connect vsock device"; |
| return status; |
| } |
| status = experimental_vsock->Start(guest.object(), realm, device_loop.dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start vsock device"; |
| return status; |
| } |
| } |
| } |
| FX_CHECK(!experimental_vsock != (!legacy_vsock && !vsock_loop)); |
| |
| // 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::ui::composition::Allocator> scenic_allocator = nullptr; |
| status = fdio_service_connect("/svc/fuchsia.ui.composition.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()->server), std::move(sysmem_allocator), |
| std::move(scenic_allocator), realm, 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), |
| realm, 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) { |
| FX_PLOGS(ERROR, status) << "Failed to connect Ethernet device"; |
| return status; |
| } |
| status = net->Start(guest.object(), net_device.mac_address, net_device.enable_bridge, realm, |
| device_loop.dispatcher(), net_devices.size()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Could not open Ethernet device"; |
| return status; |
| } |
| net_devices.push_back(std::move(net)); |
| } |
| |
| // Setup sound device. |
| VirtioSound sound(guest.phys_mem()); |
| if (cfg.virtio_sound()) { |
| status = bus.Connect(sound.pci_device(), device_loop.dispatcher(), true); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to connect sound device"; |
| return status; |
| } |
| status = sound.Start(guest.object(), realm, device_loop.dispatcher(), cfg.virtio_sound_input()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start sound device"; |
| return status; |
| } |
| } |
| |
| #if __x86_64__ |
| if (auto result = CreatePageTable(guest.phys_mem()); result.is_error()) { |
| FX_PLOGS(ERROR, result.status_value()) << "Failed to create page table"; |
| return result.status_value(); |
| } |
| |
| 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_PLOGS(ERROR, status) << "Failed to create ACPI table"; |
| 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; |
| } |
| } |
| |
| // Device memory has been finalized. Ensure that there's no overlap with the generated guest |
| // memory ranges. |
| dev_mem.Finalize(); |
| if (dev_mem.HasGuestMemoryOverlap(guest.memory_regions())) { |
| // Logs faulty guest ranges internally. |
| 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, guest.memory_regions(), |
| platform_devices, &entry, &boot_ptr); |
| break; |
| case fuchsia::virtualization::KernelType::LINUX: |
| status = setup_linux(&cfg, guest.phys_mem(), dev_mem, guest.memory_regions(), |
| platform_devices, &entry, &boot_ptr); |
| break; |
| default: |
| FX_LOGS(ERROR) << "Unknown kernel"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to load kernel"; |
| return status; |
| } |
| |
| // Destroy the guest before anything else we have on the stack; the guest has ownership of VCPU |
| // threads that may attempt to access various stack-allocated objects through the guest and its |
| // destructor joins those threads, avoiding stack-use-after-scope. |
| auto destroy_guest = fit::defer([&guest_opt]() { guest_opt.reset(); }); |
| |
| // Setup primary VCPU. |
| status = guest.StartVcpu(0 /* id */, entry, boot_ptr, &loop); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start VCPU-0"; |
| loop.Quit(); |
| } |
| |
| status = guest_controller.AddPublicService(context.get()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to add guest controller public service"; |
| loop.Quit(); |
| } |
| status = balloon.AddPublicService(context.get()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to add balloon public service"; |
| loop.Quit(); |
| } |
| status = gpu.AddPublicService(context.get()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to add GPU public service"; |
| 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_PLOGS(ERROR, status) << "Failed to create async worker"; |
| return status; |
| } |
| |
| status = loop.Run(); |
| |
| return status; |
| } |