blob: 5dbc9b1cfe546b35d122615046e35eab59403093 [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 <stdio.h>
#include <string.h>
#include <hw/pci.h>
#include <hypervisor/address.h>
#include <hypervisor/bits.h>
#include <hypervisor/block.h>
#include <hypervisor/decode.h>
#include <hypervisor/io_apic.h>
#include <hypervisor/io_port.h>
#include <hypervisor/pci.h>
#include <hypervisor/uart.h>
#include <hypervisor/vcpu.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <fbl/unique_ptr.h>
#include "acpi_priv.h"
// clang-format off
/* Local APIC register addresses. */
#define LOCAL_APIC_REGISTER_ID 0x0020
#define LOCAL_APIC_REGISTER_VERSION 0x0030
#define LOCAL_APIC_REGISTER_LDR 0x00d0
#define LOCAL_APIC_REGISTER_DFR 0x00e0
#define LOCAL_APIC_REGISTER_SVR 0x00f0
#define LOCAL_APIC_REGISTER_ISR_31_0 0x0100
#define LOCAL_APIC_REGISTER_ISR_255_224 0x0170
#define LOCAL_APIC_REGISTER_TMR_31_0 0x0180
#define LOCAL_APIC_REGISTER_TMR_255_224 0x01f0
#define LOCAL_APIC_REGISTER_IRR_31_0 0x0200
#define LOCAL_APIC_REGISTER_IRR_255_224 0x0270
#define LOCAL_APIC_REGISTER_ESR 0x0280
#define LOCAL_APIC_REGISTER_ICR_31_0 0x0300
#define LOCAL_APIC_REGISTER_ICR_63_32 0x0310
#define LOCAL_APIC_REGISTER_LVT_TIMER 0x0320
#define LOCAL_APIC_REGISTER_LVT_THERMAL 0x0330
#define LOCAL_APIC_REGISTER_LVT_PERFMON 0x0340
#define LOCAL_APIC_REGISTER_LVT_LINT0 0x0350
#define LOCAL_APIC_REGISTER_LVT_LINT1 0x0360
#define LOCAL_APIC_REGISTER_LVT_ERROR 0x0370
#define LOCAL_APIC_REGISTER_INITIAL_COUNT 0x0380
/* Interrupt vectors. */
#define X86_INT_GP_FAULT 13u
// clang-format on
static zx_status_t handle_local_apic(local_apic_t* local_apic, const zx_packet_guest_mem_t* mem,
instruction_t* inst) {
ZX_ASSERT(mem->addr >= LOCAL_APIC_PHYS_BASE);
zx_vaddr_t offset = mem->addr - LOCAL_APIC_PHYS_BASE;
// From Intel Volume 3, Section 10.4.1.: All 32-bit registers should be
// accessed using 128-bit aligned 32-bit loads or stores. Some processors
// may support loads and stores of less than 32 bits to some of the APIC
// registers. This is model specific behavior and is not guaranteed to work
// on all processors.
switch (offset) {
case LOCAL_APIC_REGISTER_VERSION: {
// From Intel Volume 3, Section 10.4.8.
//
// We choose 15H as it causes us to be seen as a modern APIC by Linux,
// and is the highest non-reserved value.
const uint32_t version = 0x15;
const uint32_t max_lvt_entry = 0x6; // LVT entries minus 1.
const uint32_t eoi_suppression = 0; // Disable support for EOI-broadcast suppression.
return inst_read32(inst, version | (max_lvt_entry << 16) | (eoi_suppression << 24));
}
case LOCAL_APIC_REGISTER_ESR:
// From Intel Volume 3, Section 10.5.3: Before attempt to read from the
// ESR, software should first write to it.
//
// Therefore, we ignore writes to the ESR.
if (inst->type == INST_MOV_WRITE)
return ZX_OK;
case LOCAL_APIC_REGISTER_ISR_31_0 ... LOCAL_APIC_REGISTER_ISR_255_224:
case LOCAL_APIC_REGISTER_TMR_31_0 ... LOCAL_APIC_REGISTER_TMR_255_224:
case LOCAL_APIC_REGISTER_IRR_31_0 ... LOCAL_APIC_REGISTER_IRR_255_224:
return inst_read32(inst, 0);
case LOCAL_APIC_REGISTER_ID: {
// The IO APIC implementation currently assumes these won't change.
if (inst->type == INST_MOV_WRITE && inst_val32(inst) != local_apic->regs->id.u32) {
fprintf(stderr, "Changing APIC IDs is not supported.\n");
return ZX_ERR_NOT_SUPPORTED;
}
uintptr_t addr = reinterpret_cast<uintptr_t>(local_apic->apic_addr) + offset;
return inst_rw32(inst, reinterpret_cast<uint32_t*>(addr));
}
case LOCAL_APIC_REGISTER_DFR:
case LOCAL_APIC_REGISTER_ICR_31_0 ... LOCAL_APIC_REGISTER_ICR_63_32:
case LOCAL_APIC_REGISTER_LDR:
case LOCAL_APIC_REGISTER_LVT_ERROR:
case LOCAL_APIC_REGISTER_LVT_LINT0:
case LOCAL_APIC_REGISTER_LVT_LINT1:
case LOCAL_APIC_REGISTER_LVT_PERFMON:
case LOCAL_APIC_REGISTER_LVT_THERMAL:
case LOCAL_APIC_REGISTER_LVT_TIMER:
case LOCAL_APIC_REGISTER_SVR: {
uintptr_t addr = reinterpret_cast<uintptr_t>(local_apic->apic_addr) + offset;
return inst_rw32(inst, reinterpret_cast<uint32_t*>(addr));
}
case LOCAL_APIC_REGISTER_INITIAL_COUNT: {
uint32_t initial_count;
zx_status_t status = inst_write32(inst, &initial_count);
if (status != ZX_OK)
return status;
return initial_count > 0 ? ZX_ERR_NOT_SUPPORTED : ZX_OK;
}
}
fprintf(stderr, "Unhandled local APIC address %#lx\n", offset);
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t unhandled_mem(const zx_packet_guest_mem_t* mem, const instruction_t* inst) {
fprintf(stderr, "Unhandled address %#lx\n", mem->addr);
if (inst->type == INST_MOV_READ)
*inst->reg = UINT64_MAX;
return ZX_OK;
}
static zx_status_t handle_mmio_read(vcpu_ctx_t* vcpu_ctx, zx_vaddr_t addr, uint8_t access_size,
zx_vcpu_io_t* io) {
switch (addr) {
case PCI_ECAM_PHYS_BASE ... PCI_ECAM_PHYS_TOP:
return pci_ecam_read(vcpu_ctx->guest_ctx->bus, addr, access_size, io);
default:
return ZX_ERR_NOT_FOUND;
}
}
static zx_status_t handle_mmio_write(vcpu_ctx_t* vcpu_ctx, zx_vaddr_t addr, zx_vcpu_io_t* io) {
switch (addr) {
case PCI_ECAM_PHYS_BASE ... PCI_ECAM_PHYS_TOP:
return pci_ecam_write(vcpu_ctx->guest_ctx->bus, addr, io);
default:
return ZX_ERR_NOT_FOUND;
}
}
static zx_status_t handle_mmio(vcpu_ctx_t* vcpu_ctx, const zx_packet_guest_mem_t* mem, const instruction_t* inst) {
zx_status_t status;
zx_vcpu_io_t mmio;
if (inst->type == INST_MOV_WRITE) {
switch (inst->mem) {
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;
mmio.access_size = inst->mem;
return handle_mmio_write(vcpu_ctx, mem->addr, &mmio);
}
if (inst->type == INST_MOV_READ) {
status = handle_mmio_read(vcpu_ctx, mem->addr, inst->mem, &mmio);
if (status != ZX_OK)
return status;
switch (inst->mem) {
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;
}
}
if (inst->type == INST_TEST) {
status = handle_mmio_read(vcpu_ctx, mem->addr, inst->mem, &mmio);
if (status != ZX_OK)
return status;
switch (inst->mem) {
case 1:
return inst_test8(inst, static_cast<uint8_t>(inst->imm), mmio.u8);
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
return ZX_ERR_INVALID_ARGS;
}
static zx_status_t handle_mem(vcpu_ctx_t* vcpu_ctx, const zx_packet_guest_mem_t* mem) {
zx_vcpu_state_t vcpu_state;
zx_status_t status = vcpu_ctx->read_state(vcpu_ctx, ZX_VCPU_STATE, &vcpu_state,
sizeof(vcpu_state));
if (status != ZX_OK)
return status;
instruction_t inst;
#if __aarch64__
status = ZX_ERR_NOT_SUPPORTED;
#elif __x86_64__
status = inst_decode(mem->inst_buf, mem->inst_len, &vcpu_state, &inst);
#else
#error Unsupported architecture
#endif
if (status != ZX_OK) {
fprintf(stderr, "Unsupported instruction:");
#if __x86_64__
for (uint8_t i = 0; i < mem->inst_len; i++)
fprintf(stderr, " %x", mem->inst_buf[i]);
#endif // __x86_64__
fprintf(stderr, "\n");
} else {
guest_ctx_t* guest_ctx = vcpu_ctx->guest_ctx;
switch (mem->addr) {
case LOCAL_APIC_PHYS_BASE ... LOCAL_APIC_PHYS_TOP:
status = handle_local_apic(&vcpu_ctx->local_apic, mem, &inst);
break;
case IO_APIC_PHYS_BASE ... IO_APIC_PHYS_TOP:
status = io_apic_handler(guest_ctx->io_apic, mem, &inst);
break;
default: {
status = handle_mmio(vcpu_ctx, mem, &inst);
if (status == ZX_ERR_NOT_FOUND)
status = unhandled_mem(mem, &inst);
break;
}
}
}
if (status != ZX_OK) {
return zx_vcpu_interrupt(vcpu_ctx->vcpu, X86_INT_GP_FAULT);
} else if (inst.type == INST_MOV_READ || inst.type == INST_TEST) {
// If there was an attempt to read or test memory, update the GPRs.
return vcpu_ctx->write_state(vcpu_ctx, ZX_VCPU_STATE, &vcpu_state,
sizeof(vcpu_state));
}
return status;
}
static zx_status_t handle_input(vcpu_ctx_t* vcpu_ctx, const zx_packet_guest_io_t* io) {
#if __x86_64__
zx_status_t status = ZX_OK;
zx_vcpu_io_t vcpu_io;
memset(&vcpu_io, 0, sizeof(vcpu_io));
switch (io->port) {
case I8042_COMMAND_PORT:
case I8042_DATA_PORT:
case PIC1_DATA_PORT:
case PM1_EVENT_PORT + PM1A_REGISTER_ENABLE:
case PM1_EVENT_PORT + PM1A_REGISTER_STATUS:
case RTC_DATA_PORT:
status = io_port_read(vcpu_ctx->guest_ctx->io_port, io->port, &vcpu_io);
break;
case UART_RECEIVE_PORT ... UART_SCR_SCRATCH_PORT:
status = uart_read(vcpu_ctx->guest_ctx->uart, io->port, &vcpu_io);
break;
case PCI_CONFIG_ADDRESS_PORT_BASE ... PCI_CONFIG_ADDRESS_PORT_TOP:
case PCI_CONFIG_DATA_PORT_BASE ... PCI_CONFIG_DATA_PORT_TOP:
status = pci_bus_read(vcpu_ctx->guest_ctx->bus, io->port, io->access_size, &vcpu_io);
break;
default: {
uint8_t bar;
uint16_t port_off;
pci_bus_t* bus = vcpu_ctx->guest_ctx->bus;
pci_device_t* pci_device = pci_mapped_device(bus, PCI_BAR_IO_TYPE_PIO, io->port, &bar,
&port_off);
if (pci_device) {
status = pci_device->ops->read_bar(pci_device, bar, port_off, io->access_size,
&vcpu_io);
break;
}
status = ZX_ERR_NOT_SUPPORTED;
}
}
if (status != ZX_OK) {
fprintf(stderr, "Unhandled port in %#x: %d\n", io->port, status);
return status;
}
if (vcpu_io.access_size != io->access_size) {
fprintf(stderr, "Unexpected size (%u != %u) for port in %#x\n", vcpu_io.access_size,
io->access_size, io->port);
return ZX_ERR_IO_DATA_INTEGRITY;
}
return vcpu_ctx->write_state(vcpu_ctx, ZX_VCPU_IO, &vcpu_io, sizeof(vcpu_io));
#else // __x86_64__
return ZX_ERR_NOT_SUPPORTED;
#endif // __x86_64__
}
static zx_status_t handle_output(vcpu_ctx_t* vcpu_ctx, const zx_packet_guest_io_t* io) {
#if __x86_64__
switch (io->port) {
case I8042_COMMAND_PORT:
case I8042_DATA_PORT:
case I8253_CHANNEL_0:
case I8253_CONTROL_PORT:
case PIC1_COMMAND_PORT ... PIC1_DATA_PORT:
case PIC2_COMMAND_PORT ... PIC2_DATA_PORT:
case PM1_EVENT_PORT + PM1A_REGISTER_ENABLE:
case PM1_EVENT_PORT + PM1A_REGISTER_STATUS:
case RTC_INDEX_PORT:
return io_port_write(vcpu_ctx->guest_ctx->io_port, io);
case PCI_CONFIG_ADDRESS_PORT_BASE ... PCI_CONFIG_ADDRESS_PORT_TOP:
case PCI_CONFIG_DATA_PORT_BASE ... PCI_CONFIG_DATA_PORT_TOP:
return pci_bus_write(vcpu_ctx->guest_ctx->bus, io);
case UART_INTERRUPT_ENABLE_PORT ... UART_SCR_SCRATCH_PORT:
return uart_write(vcpu_ctx->guest_ctx->uart, io);
}
fprintf(stderr, "Unhandled port out %#x\n", io->port);
return ZX_ERR_NOT_SUPPORTED;
#else // __x86_64__
return ZX_ERR_NOT_SUPPORTED;
#endif // __x86_64__
}
static zx_status_t handle_io(vcpu_ctx_t* vcpu_ctx, const zx_packet_guest_io_t* io) {
return io->input ? handle_input(vcpu_ctx, io) : handle_output(vcpu_ctx, io);
}
static zx_status_t vcpu_state_read(vcpu_ctx_t* vcpu_ctx, uint32_t kind, void* buffer,
uint32_t len) {
return zx_vcpu_read_state(vcpu_ctx->vcpu, kind, buffer, len);
}
static zx_status_t vcpu_state_write(vcpu_ctx_t* vcpu_ctx, uint32_t kind, const void* buffer,
uint32_t len) {
return zx_vcpu_write_state(vcpu_ctx->vcpu, kind, buffer, len);
}
void vcpu_init(vcpu_ctx_t* vcpu_ctx) {
memset(vcpu_ctx, 0, sizeof(*vcpu_ctx));
vcpu_ctx->read_state = vcpu_state_read;
vcpu_ctx->write_state = vcpu_state_write;
}
zx_status_t vcpu_loop(vcpu_ctx_t* vcpu_ctx) {
while (true) {
zx_port_packet_t packet;
zx_status_t status = zx_vcpu_resume(vcpu_ctx->vcpu, &packet);
if (status != ZX_OK) {
fprintf(stderr, "Failed to resume VCPU %d\n", status);
return status;
}
status = vcpu_packet_handler(vcpu_ctx, &packet);
if (status != ZX_OK) {
fprintf(stderr, "Failed to handle guest packet %d: %d\n", packet.type, status);
return status;
}
}
}
zx_status_t vcpu_packet_handler(vcpu_ctx_t* vcpu_ctx, zx_port_packet_t* packet) {
switch (packet->type) {
case ZX_PKT_TYPE_GUEST_MEM:
return handle_mem(vcpu_ctx, &packet->guest_mem);
case ZX_PKT_TYPE_GUEST_IO:
return handle_io(vcpu_ctx, &packet->guest_io);
default:
fprintf(stderr, "Unhandled guest packet %d\n", packet->type);
return ZX_ERR_NOT_SUPPORTED;
}
}
typedef struct device {
zx_handle_t port;
device_handler_fn_t handler;
void* ctx;
} device_t;
static int device_loop(void* ctx) {
fbl::unique_ptr<device_t> device(static_cast<device_t*>(ctx));
while (true) {
zx_port_packet_t packet;
zx_status_t status = zx_port_wait(device->port, ZX_TIME_INFINITE, &packet, 0);
if (status != ZX_OK) {
fprintf(stderr, "Failed to wait for device port %d\n", status);
break;
}
status = device->handler(&packet, device->ctx);
if (status != ZX_OK) {
fprintf(stderr, "Unable to handle packet for device %d\n", status);
break;
}
}
zx_handle_close(device->port);
return ZX_ERR_INTERNAL;
}
zx_status_t device_async(zx_handle_t guest, const trap_args_t* traps, size_t num_traps,
device_handler_fn_t handler, void* ctx) {
if (num_traps == 0)
return ZX_ERR_INVALID_ARGS;
int ret;
auto device = new device_t{ZX_HANDLE_INVALID, handler, ctx};
zx_status_t status = zx_port_create(0, &device->port);
if (status != ZX_OK) {
fprintf(stderr, "Failed to create device port %d\n", status);
goto mem_cleanup;
}
for (size_t i = 0; i < num_traps; ++i) {
const trap_args_t* trap = &traps[i];
status = zx_guest_set_trap(guest, trap->kind, trap->addr, trap->len, device->port,
trap->key);
if (status != ZX_OK) {
fprintf(stderr, "Failed to set trap for device port %d\n", status);
goto cleanup;
}
}
thrd_t thread;
ret = thrd_create(&thread, device_loop, device);
if (ret != thrd_success) {
fprintf(stderr, "Failed to create device thread %d\n", ret);
goto cleanup;
}
ret = thrd_detach(thread);
if (ret != thrd_success) {
fprintf(stderr, "Failed to detach device thread %d\n", ret);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
cleanup:
zx_handle_close(device->port);
mem_cleanup:
delete device;
return ZX_ERR_INTERNAL;
}