blob: 384a003f8a1e5389e0a74c2e05f09780609f65ce [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 "src/virtualization/bin/vmm/vcpu.h"
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/thread.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/hypervisor.h>
#include <zircon/syscalls/port.h>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/virtualization/bin/vmm/guest.h"
#include "src/virtualization/bin/vmm/io.h"
static thread_local Vcpu* thread_vcpu = nullptr;
Vcpu::Vcpu(uint64_t id, Guest* guest, zx_gpaddr_t entry, zx_gpaddr_t boot_ptr)
: id_(id), guest_(guest), entry_(entry), boot_ptr_(boot_ptr) {}
Vcpu::~Vcpu() {
if (thread_.has_value()) {
vcpu_.kick();
thread_.value().join();
}
}
zx_status_t Vcpu::Start() {
FX_DCHECK(!thread_.has_value()) << "VCPU already has a thread";
std::promise<zx_status_t> barrier;
std::future<zx_status_t> barrier_future = barrier.get_future();
thread_ = std::thread(fit::bind_member(this, &Vcpu::Loop), std::move(barrier));
barrier_future.wait();
return barrier_future.get();
}
Vcpu* Vcpu::GetCurrent() {
FX_DCHECK(thread_vcpu != nullptr) << "Thread does not have a VCPU";
return thread_vcpu;
}
zx_status_t Vcpu::Loop(std::promise<zx_status_t> barrier) {
FX_DCHECK(thread_vcpu == nullptr) << "Thread has multiple VCPUs";
// Set the thread state.
{
thread_vcpu = this;
auto name = fxl::StringPrintf("vcpu-%lu", id_);
zx_status_t status = zx::thread::self()->set_property(ZX_PROP_NAME, name.c_str(), name.size());
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to set VCPU " << id_ << " thread name";
}
}
// Create the VCPU.
{
zx_status_t status = zx::vcpu::create(guest_->object(), 0, entry_, &vcpu_);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to create VCPU " << id_;
barrier.set_value(status);
return status;
}
}
// Set the 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_;
#else
#error Unknown architecture.
#endif
zx_status_t status = vcpu_.write_state(ZX_VCPU_STATE, &vcpu_state, sizeof(vcpu_state));
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to set VCPU " << id_ << " state";
barrier.set_value(status);
return status;
}
}
// Unblock VCPU startup barrier.
barrier.set_value(ZX_OK);
zx_status_t status = ZX_OK;
// Invoke the stop callback if this function returns. This callback will ultimately result in
// the VMM being destroyed.
auto deferred = fit::defer([this, &status] {
if (status == ZX_ERR_CANCELED) {
this->guest_->Stop(fit::ok());
} else {
this->guest_->Stop(fit::error(::fuchsia::virtualization::GuestError::VCPU_RUNTIME_FAILURE));
}
});
while (true) {
zx_port_packet_t packet;
status = vcpu_.enter(&packet);
switch (status) {
case ZX_OK:
break;
case ZX_ERR_CANCELED:
FX_LOGS(INFO) << "Stopping VCPU " << id_;
return ZX_OK;
default:
FX_LOGS(ERROR) << "Fatal error attempting to enter VCPU " << id_ << ": "
<< zx_status_get_string(status) << ". Shutting down VM.";
return status;
}
status = HandlePacket(packet);
switch (status) {
case ZX_OK:
break;
case ZX_ERR_CANCELED:
// Gracefully shut down the entire VM.
FX_LOGS(INFO) << "Guest requested shutdown";
return ZX_OK;
default:
FX_LOGS(ERROR) << "Fatal error handling packet of type " << packet.type << ": "
<< zx_status_get_string(status) << ". Shutting down VM.";
return status;
}
}
}
zx_status_t Vcpu::Interrupt(uint32_t vector) { return vcpu_.interrupt(vector); }
zx_status_t Vcpu::HandleMem(const zx_packet_guest_mem_t& packet, uint64_t trap_key) {
IoMapping* device_mapping = IoMapping::FromPortKey(trap_key);
zx_status_t status = ArchHandleMem(packet, device_mapping);
if (status != ZX_OK) {
FX_LOGS(ERROR) << std::hex << "Device '" << device_mapping->handler()->Name()
<< "' returned status " << zx_status_get_string(status)
<< " while attempting to handle MMIO access at paddr 0x" << packet.addr
<< " (mapping offset 0x" << (packet.addr - device_mapping->base()) << ").";
return status;
}
return ZX_OK;
}
zx_status_t Vcpu::HandlePacket(const zx_port_packet_t& packet) {
switch (packet.type) {
case ZX_PKT_TYPE_GUEST_MEM:
return HandleMem(packet.guest_mem, packet.key);
#if __x86_64__
case ZX_PKT_TYPE_GUEST_IO:
return ArchHandleIo(packet.guest_io, packet.key);
#endif // __x86_64__
case ZX_PKT_TYPE_GUEST_VCPU:
return HandleVcpu(packet.guest_vcpu, packet.key);
default:
FX_LOGS(ERROR) << "Unhandled guest packet " << packet.type;
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t Vcpu::HandleVcpu(const zx_packet_guest_vcpu_t& packet, uint64_t trap_key) {
switch (packet.type) {
case ZX_PKT_GUEST_VCPU_INTERRUPT:
return guest_->Interrupt(packet.interrupt.mask, packet.interrupt.vector);
case ZX_PKT_GUEST_VCPU_STARTUP:
return guest_->StartVcpu(packet.startup.id, packet.startup.entry, boot_ptr_);
default:
return ZX_ERR_NOT_SUPPORTED;
}
}