| // 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 "garnet/lib/machina/guest.h" |
| |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <fuchsia/sysinfo/c/fidl.h> |
| #include <lib/fdio/util.h> |
| #include <lib/zx/channel.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/hypervisor.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/threads.h> |
| |
| #include "garnet/lib/machina/sysinfo.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/strings/string_printf.h" |
| |
| // Number of threads reading from the async device port. |
| static constexpr size_t kNumAsyncWorkers = 2; |
| |
| static constexpr uint32_t trap_kind(machina::TrapType type) { |
| switch (type) { |
| case machina::TrapType::MMIO_SYNC: |
| return ZX_GUEST_TRAP_MEM; |
| case machina::TrapType::MMIO_BELL: |
| return ZX_GUEST_TRAP_BELL; |
| case machina::TrapType::PIO_SYNC: |
| return ZX_GUEST_TRAP_IO; |
| default: |
| ZX_PANIC("Unhandled TrapType %d\n", static_cast<int>(type)); |
| return 0; |
| } |
| } |
| |
| static zx_status_t get_hypervisor_resource( |
| const fuchsia::sysinfo::DeviceSyncPtr& sysinfo, zx::resource* resource) { |
| zx_status_t fidl_status; |
| zx_status_t status = sysinfo->GetHypervisorResource(&fidl_status, resource); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return fidl_status; |
| } |
| |
| static constexpr uint32_t cache_policy(MemoryPolicy policy) { |
| switch (policy) { |
| case MemoryPolicy::HOST_DEVICE: |
| return ZX_CACHE_POLICY_UNCACHED_DEVICE; |
| default: |
| return ZX_CACHE_POLICY_CACHED; |
| } |
| } |
| |
| namespace machina { |
| |
| zx_status_t Guest::Init(const std::vector<MemorySpec>& memory) { |
| fuchsia::sysinfo::DeviceSyncPtr sysinfo = get_sysinfo(); |
| zx::resource hypervisor_resource; |
| zx_status_t status = get_hypervisor_resource(sysinfo, &hypervisor_resource); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to get hypervisor resource " << status; |
| return status; |
| } |
| status = zx::guest::create(hypervisor_resource, 0, &guest_, &vmar_); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create guest " << status; |
| return status; |
| } |
| |
| zx::resource root_resource; |
| for (const MemorySpec& spec : memory) { |
| zx::vmo vmo; |
| switch (spec.policy) { |
| case MemoryPolicy::GUEST_CACHED: |
| status = zx::vmo::create(spec.len, ZX_VMO_NON_RESIZABLE, &vmo); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create VMO " << status; |
| return status; |
| } |
| break; |
| case MemoryPolicy::HOST_CACHED: |
| case MemoryPolicy::HOST_DEVICE: |
| if (!root_resource) { |
| status = get_root_resource(sysinfo, &root_resource); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to get root resource " << status; |
| return status; |
| } |
| } |
| status = zx::vmo::create_physical(root_resource, 0, spec.len, &vmo); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create physical VMO " << status; |
| return status; |
| } |
| status = vmo.set_cache_policy(cache_policy(spec.policy)); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to set cache policy on VMO " << status; |
| return status; |
| } |
| break; |
| default: |
| FXL_LOG(ERROR) << "Unknown memory policy " |
| << static_cast<uint32_t>(spec.policy); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_gpaddr_t addr; |
| status = vmar_.map(spec.addr, vmo, 0, spec.len, |
| ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE | |
| ZX_VM_SPECIFIC | ZX_VM_FLAG_REQUIRE_NON_RESIZABLE, |
| &addr); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to map guest physical memory " << status; |
| return status; |
| } |
| if (!phys_mem_.vmo()) { |
| status = phys_mem_.Init(std::move(vmo)); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to initialize guest physical memory " |
| << status; |
| return status; |
| } |
| } |
| } |
| |
| for (size_t i = 0; i < kNumAsyncWorkers; ++i) { |
| auto name = fxl::StringPrintf("io-handler-%zu", i); |
| status = device_loop_.StartThread(name.c_str()); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create async worker " << status; |
| return status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Guest::CreateMapping(TrapType type, uint64_t addr, size_t size, |
| uint64_t offset, IoHandler* handler) { |
| uint32_t kind = trap_kind(type); |
| mappings_.emplace_front(kind, addr, size, offset, handler); |
| zx_status_t status = mappings_.front().SetTrap(this); |
| if (status != ZX_OK) { |
| mappings_.pop_front(); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Guest::CreateSubVmar(uint64_t addr, size_t size, zx::vmar* vmar) { |
| uintptr_t guest_addr; |
| return vmar_.allocate( |
| addr, size, ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | ZX_VM_SPECIFIC, |
| vmar, &guest_addr); |
| } |
| |
| zx_status_t Guest::StartVcpu(uint64_t id, zx_gpaddr_t entry, |
| zx_gpaddr_t boot_ptr) { |
| if (id >= kMaxVcpus) { |
| FXL_LOG(ERROR) << "Failed to start VCPU-" << id << ", up to " << kMaxVcpus |
| << " VCPUs are supported"; |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| std::lock_guard<std::shared_mutex> lock(mutex_); |
| if (vcpus_[0] == nullptr && id != 0) { |
| FXL_LOG(ERROR) << "VCPU-0 must be started before other VCPUs"; |
| return ZX_ERR_BAD_STATE; |
| } |
| if (vcpus_[id] != nullptr) { |
| // The guest might make multiple requests to start a particular VCPU. On |
| // x86, the guest should send two START_UP IPIs but we initialize the VCPU |
| // on the first. So, we ignore subsequent requests. |
| return ZX_OK; |
| } |
| vcpus_[id] = std::make_unique<Vcpu>(id, this, entry, boot_ptr); |
| vcpus_[id]->Start(); |
| return ZX_OK; |
| } |
| |
| zx_status_t Guest::Interrupt(uint64_t mask, uint8_t vector) { |
| std::shared_lock<std::shared_mutex> lock(mutex_); |
| for (size_t id = 0; id != kMaxVcpus; ++id) { |
| if (!(mask & (1ul << id)) || !vcpus_[id]) { |
| continue; |
| } |
| zx_status_t status = vcpus_[id]->Interrupt(vector); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Guest::Join() { |
| std::shared_lock<std::shared_mutex> lock(mutex_); |
| // We assume that the VCPU-0 thread will be started first, and that no |
| // additional VCPUs will be brought up after it terminates. |
| zx_status_t status = vcpus_[0]->Join(); |
| |
| // Once the initial VCPU has terminated, wait for any additional VCPUs. |
| for (size_t id = 1; id != kMaxVcpus; ++id) { |
| if (vcpus_[id] != nullptr) { |
| zx_status_t vcpu_status = vcpus_[id]->Join(); |
| if (vcpu_status != ZX_OK) { |
| status = vcpu_status; |
| } |
| } |
| } |
| |
| return status; |
| } |
| |
| } // namespace machina |