blob: 56a9561851db9969bfb74faa8f4fa5c916899dbc [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 "garnet/bin/guest/vmm/vcpu.h"
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <lib/fit/function.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/strings/string_printf.h>
#include <trace/event.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/hypervisor.h>
#include <zircon/syscalls/port.h>
#include <zx/thread.h>
#include "garnet/bin/guest/vmm/guest.h"
#include "garnet/bin/guest/vmm/io.h"
static thread_local Vcpu* thread_vcpu = nullptr;
#if __aarch64__
static zx_status_t HandleMemArm(const zx_packet_guest_mem_t& mem,
uint64_t trap_key, uint64_t* reg) {
TRACE_DURATION("machina", "mmio", "addr", mem.addr, "access_size",
mem.access_size);
IoValue mmio = {mem.access_size, {.u64 = mem.data}};
IoMapping* mapping = IoMapping::FromPortKey(trap_key);
if (!mem.read) {
return mapping->Write(mem.addr, mmio);
}
zx_status_t status = mapping->Read(mem.addr, &mmio);
if (status != ZX_OK) {
return status;
}
*reg = mmio.u64;
if (mem.sign_extend && *reg & (1ul << (mmio.access_size * CHAR_BIT - 1))) {
*reg |= UINT64_MAX << mmio.access_size;
}
return ZX_OK;
}
#elif __x86_64__
#include "garnet/bin/guest/vmm/arch/x64/decode.h"
static zx_status_t HandleMemX86(const zx_packet_guest_mem_t& mem,
uint64_t trap_key, const Instruction* inst) {
TRACE_DURATION("machina", "mmio", "addr", mem.addr, "access_size",
inst->access_size);
zx_status_t status;
IoValue mmio = {inst->access_size, {.u64 = 0}};
switch (inst->type) {
case INST_MOV_WRITE:
switch (inst->access_size) {
case 1:
status = inst_write8(inst, &mmio.u8);
break;
case 2:
status = inst_write16(inst, &mmio.u16);
break;
case 4:
status = inst_write32(inst, &mmio.u32);
break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
if (status != ZX_OK) {
return status;
}
return IoMapping::FromPortKey(trap_key)->Write(mem.addr, mmio);
case INST_MOV_READ:
status = IoMapping::FromPortKey(trap_key)->Read(mem.addr, &mmio);
if (status != ZX_OK) {
return status;
}
switch (inst->access_size) {
case 1:
return inst_read8(inst, mmio.u8);
case 2:
return inst_read16(inst, mmio.u16);
case 4:
return inst_read32(inst, mmio.u32);
default:
return ZX_ERR_NOT_SUPPORTED;
}
case INST_TEST:
status = IoMapping::FromPortKey(trap_key)->Read(mem.addr, &mmio);
if (status != ZX_OK) {
return status;
}
switch (inst->access_size) {
case 1:
return inst_test8(inst, static_cast<uint8_t>(inst->imm), mmio.u8);
default:
return ZX_ERR_NOT_SUPPORTED;
}
default:
return ZX_ERR_INVALID_ARGS;
}
}
#endif
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) {}
void Vcpu::Start() {
std::promise<void> barrier;
std::future<void> barrier_future = barrier.get_future();
future_ = std::async(std::launch::async, fit::bind_member(this, &Vcpu::Loop),
std::move(barrier));
barrier_future.wait();
}
zx_status_t Vcpu::Join() { return future_.get(); }
Vcpu* Vcpu::GetCurrent() {
FXL_DCHECK(thread_vcpu != nullptr) << "Thread does not have a VCPU";
return thread_vcpu;
}
zx_status_t Vcpu::Loop(std::promise<void> barrier) {
FXL_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) {
FXL_LOG(WARNING) << "Failed to set VCPU " << id_ << " thread name "
<< status;
}
}
// Create the VCPU.
{
std::lock_guard<std::shared_mutex> lock(mutex_);
zx_status_t status = zx::vcpu::create(guest_->object(), 0, entry_, &vcpu_);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create VCPU " << id_ << " " << status;
return status;
}
}
// Set the initial VCPU state.
std::shared_lock<std::shared_mutex> lock(mutex_);
{
zx_vcpu_state_t vcpu_state = {};
#if __aarch64__
vcpu_state.x[0] = boot_ptr_;
#elif __x86_64__
vcpu_state.rsi = boot_ptr_;
#endif
zx_status_t status =
vcpu_.write_state(ZX_VCPU_STATE, &vcpu_state, sizeof(vcpu_state));
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to set VCPU " << id_ << " state " << status;
return status;
}
}
// Unblock VCPU startup barrier.
barrier.set_value();
while (true) {
zx_port_packet_t packet;
zx_status_t status = vcpu_.resume(&packet);
if (status == ZX_ERR_STOP) {
return ZX_OK;
}
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to resume VCPU " << id_ << " " << status;
exit(status);
}
status = HandlePacketLocked(packet);
if (status == ZX_ERR_STOP) {
return ZX_OK;
}
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to handle packet " << packet.type << " "
<< status;
exit(status);
}
}
}
zx_status_t Vcpu::Interrupt(uint32_t vector) {
std::shared_lock<std::shared_mutex> lock(mutex_);
return vcpu_.interrupt(vector);
}
zx_status_t Vcpu::HandlePacketLocked(const zx_port_packet_t& packet) {
switch (packet.type) {
case ZX_PKT_TYPE_GUEST_MEM:
return HandleMemLocked(packet.guest_mem, packet.key);
#if __x86_64__
case ZX_PKT_TYPE_GUEST_IO:
return HandleIo(packet.guest_io, packet.key);
#endif // __x86_64__
case ZX_PKT_TYPE_GUEST_VCPU:
return HandleVcpu(packet.guest_vcpu, packet.key);
default:
FXL_LOG(ERROR) << "Unhandled guest packet " << packet.type;
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t Vcpu::HandleMemLocked(const zx_packet_guest_mem_t& mem,
uint64_t trap_key) {
zx_vcpu_state_t vcpu_state;
zx_status_t status;
#if __aarch64__
if (mem.read)
#endif
{
status = vcpu_.read_state(ZX_VCPU_STATE, &vcpu_state, sizeof(vcpu_state));
if (status != ZX_OK) {
return status;
}
}
bool do_write = false;
#if __aarch64__
do_write = mem.read;
status = HandleMemArm(mem, trap_key, &vcpu_state.x[mem.xt]);
#elif __x86_64__
Instruction inst;
status = inst_decode(mem.inst_buf, mem.inst_len, mem.default_operand_size,
&vcpu_state, &inst);
if (status != ZX_OK) {
std::string inst;
for (uint8_t i = 0; i < mem.inst_len; i++) {
fxl::StringAppendf(&inst, " %x", mem.inst_buf[i]);
}
FXL_LOG(ERROR) << "Unsupported instruction:" << inst;
} else {
status = HandleMemX86(mem, trap_key, &inst);
// If there was an attempt to read or test memory, update the GPRs.
do_write = inst.type == INST_MOV_READ || inst.type == INST_TEST;
}
#endif // __x86_64__
if (status == ZX_OK && do_write) {
return vcpu_.write_state(ZX_VCPU_STATE, &vcpu_state, sizeof(vcpu_state));
}
return status;
}
#if __x86_64__
zx_status_t Vcpu::HandleInput(const zx_packet_guest_io_t& io,
uint64_t trap_key) {
TRACE_DURATION("machina", "pio_in", "port", io.port, "access_size",
io.access_size);
IoValue value = {};
value.access_size = io.access_size;
zx_status_t status = IoMapping::FromPortKey(trap_key)->Read(io.port, &value);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to handle port in 0x" << std::hex << io.port
<< " " << std::dec << status;
return status;
}
zx_vcpu_io_t vcpu_io;
memset(&vcpu_io, 0, sizeof(vcpu_io));
vcpu_io.access_size = value.access_size;
vcpu_io.u32 = value.u32;
if (vcpu_io.access_size != io.access_size) {
FXL_LOG(ERROR) << "Unexpected size (" << vcpu_io.access_size
<< " != " << io.access_size << ") for port in 0x" << std::hex
<< io.port;
return ZX_ERR_IO_DATA_INTEGRITY;
}
return vcpu_.write_state(ZX_VCPU_IO, &vcpu_io, sizeof(vcpu_io));
}
zx_status_t Vcpu::HandleOutput(const zx_packet_guest_io_t& io,
uint64_t trap_key) {
TRACE_DURATION("machina", "pio_out", "port", io.port, "access_size",
io.access_size);
IoValue value;
value.access_size = io.access_size;
value.u32 = io.u32;
zx_status_t status = IoMapping::FromPortKey(trap_key)->Write(io.port, value);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to handle port out 0x" << std::hex << io.port
<< " " << std::dec << status;
}
return status;
}
zx_status_t Vcpu::HandleIo(const zx_packet_guest_io_t& io, uint64_t trap_key) {
return io.input ? HandleInput(io, trap_key) : HandleOutput(io, trap_key);
}
#endif // __x86_64__
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;
}
}