blob: 94fb303c227b65319ac6ad33801243c71a30c6d2 [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 <hw/pci.h>
#include <hypervisor/address.h>
#include <hypervisor/bits.h>
#include <hypervisor/guest.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/syscalls.h>
#include <zircon/types.h>
#include <unittest/unittest.h>
#define PCI_TYPE1_ADDR(bus, device, function, reg) \
(0x80000000 | ((bus) << 16) | ((device) << 11) | ((function) << 8) | \
((reg)&PCI_TYPE1_REGISTER_MASK))
typedef struct test {
vcpu_ctx_t vcpu_ctx;
guest_ctx_t guest_ctx;
io_apic_t io_apic;
io_port_t io_port;
pci_bus_t bus;
zx_vcpu_io_t vcpu_io;
} test_t;
static zx_status_t vcpu_read_test_state(vcpu_ctx_t* vcpu_ctx, uint32_t kind, void* buffer,
uint32_t len) {
return ZX_ERR_INTERNAL;
}
static zx_status_t vcpu_write_test_state(vcpu_ctx_t* vcpu_ctx, uint32_t kind, const void* buffer,
uint32_t len) {
if (kind != ZX_VCPU_IO || len != sizeof(zx_vcpu_io_t))
return ZX_ERR_INVALID_ARGS;
test_t* test = (test_t*)vcpu_ctx;
auto io = static_cast<const zx_vcpu_io_t*>(buffer);
memcpy(&test->vcpu_io, io, sizeof(*io));
return ZX_OK;
}
static zx_status_t setup(test_t* test) {
memset(test, 0, sizeof(*test));
vcpu_init(&test->vcpu_ctx);
io_apic_init(&test->io_apic);
io_port_init(&test->io_port);
pci_bus_init(&test->bus, &test->io_apic);
test->guest_ctx.io_apic = &test->io_apic;
test->guest_ctx.io_port = &test->io_port;
test->guest_ctx.bus = &test->bus;
test->vcpu_ctx.guest_ctx = &test->guest_ctx;
// Redirect read/writes to the VCPU state to just access a field in the
// test structure.
test->vcpu_ctx.read_state = vcpu_read_test_state;
test->vcpu_ctx.write_state = vcpu_write_test_state;
return ZX_OK;
}
/* Test handling of an IO packet for an input instruction.
*
* Expected behavior is to read the value at the provided port address and
* write the result to RAX.
*/
static bool handle_input_packet(void) {
BEGIN_TEST;
test_t test;
zx_port_packet_t packet = {};
ASSERT_EQ(setup(&test), ZX_OK, "Failed to initialize test");
// Initialize the hosts register to an arbitrary non-zero value.
uart_t uart;
uart_init(&uart, &test.io_apic);
uart.line_control = 0xfe;
test.guest_ctx.uart = &uart;
// Send a guest packet to to read the UART line control port.
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = true;
packet.guest_io.port = UART_LINE_CONTROL_PORT;
packet.guest_io.access_size = 1;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
// Verify result value was written to RAX.
EXPECT_EQ(test.vcpu_io.u8, uart.line_control, "RAX was not populated with expected value");
END_TEST;
}
/* Test handling of an IO packet for an out instruction.
*
* Expected behavior is for the value to be saved into a host data structure.
*/
static bool handle_output_packet(void) {
BEGIN_TEST;
test_t test;
zx_packet_guest_io_t io = {};
ASSERT_EQ(setup(&test), ZX_OK, "Failed to initialize test");
uart_t uart;
uart_init(&uart, &test.io_apic);
test.guest_ctx.uart = &uart;
// Send a guest packet to to write the UART line control port.
io.input = false;
io.port = UART_LINE_CONTROL_PORT;
io.access_size = 1;
io.u8 = 0xaf;
EXPECT_EQ(uart_write(&uart, &io), ZX_OK, "Failed to handle UART IO packet");
// Verify packet value was saved to the host port state.
EXPECT_EQ(io.u8, uart.line_control, "UART was not populated with expected value");
END_TEST;
}
/* Test accesses to the PCI config address ports.
*
* Access to the 32-bit PCI config address port is provided by the IO ports
* 0xcf8 - 0xcfb. Accesses to each port must have the same alignment as the
* port address used.
*
* Ex:
* -------------------------------------
* | port | valid access widths (bytes) |
* --------------------------------------|
* | 0xcf8 | 1, 2, 4 |
* | 0xcf9 | 1 |
* | 0xcfa | 1, 2 |
* | 0xcfb | 1 |
* -------------------------------------
*/
static bool write_pci_config_addr_port(void) {
BEGIN_TEST;
test_t test;
zx_port_packet_t packet = {};
ASSERT_EQ(setup(&test), ZX_OK, "Failed to setup test");
// 32 bit write.
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = false;
packet.guest_io.port = PCI_CONFIG_ADDRESS_PORT_BASE;
packet.guest_io.access_size = 4;
packet.guest_io.u32 = 0x12345678;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.bus.config_addr, 0x12345678u, "Incorrect address read from PCI address port");
// 16 bit write to bits 31..16. Other bits remain unchanged.
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = false;
packet.guest_io.port = PCI_CONFIG_ADDRESS_PORT_BASE + 2;
packet.guest_io.access_size = 2;
packet.guest_io.u16 = 0xFACE;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.bus.config_addr, 0xFACE5678u, "Incorrect address read from PCI address port");
// 8 bit write to bits (15..8). Other bits remain unchanged.
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = false;
packet.guest_io.port = PCI_CONFIG_ADDRESS_PORT_BASE + 1;
packet.guest_io.access_size = 1;
packet.guest_io.u8 = 0x99;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.bus.config_addr, 0xFACE9978u, "Incorrect address read from PCI address port");
END_TEST;
}
/* Test reading the PCI config address ports.
*
* See write_pci_config_addr_port for more details.
*/
static bool read_pci_config_addr_port(void) {
BEGIN_TEST;
test_t test;
zx_port_packet_t packet = {};
ASSERT_EQ(setup(&test), ZX_OK, "Failed to setup test");
test.bus.config_addr = 0x12345678;
// 32 bit read (bits 31..0).
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = true;
packet.guest_io.port = PCI_CONFIG_ADDRESS_PORT_BASE;
packet.guest_io.access_size = 4;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.vcpu_io.access_size, 4, "Incorrect IO access_size");
EXPECT_EQ(test.vcpu_io.u32, 0x12345678u, "Incorrect address read from PCI address port");
// 16 bit read (bits 31..16).
test.vcpu_io.u16 = 0;
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = true;
packet.guest_io.port = PCI_CONFIG_ADDRESS_PORT_BASE + 2;
packet.guest_io.access_size = 2;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.vcpu_io.access_size, 2, "Incorrect IO access_size");
EXPECT_EQ(test.vcpu_io.u16, 0x1234u, "Incorrect address read from PCI address port");
// 8 bit read (bits 15..8).
test.vcpu_io.u8 = 0;
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = true;
packet.guest_io.port = PCI_CONFIG_ADDRESS_PORT_BASE + 1;
packet.guest_io.access_size = 1;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.vcpu_io.access_size, 1, "Incorrect IO access_size");
EXPECT_EQ(test.vcpu_io.u8, 0x56u, "Incorrect address read from PCI address port");
END_TEST;
}
/* The address written to the data port (0xcf8) is 4b aligned. The offset into
* the data port range 0xcfc-0xcff is added to the address to access partial
* words.
*/
static bool read_pci_config_data_port(void) {
BEGIN_TEST;
test_t test;
zx_port_packet_t packet = {};
ASSERT_EQ(setup(&test), ZX_OK, "Failed to setup test");
// 16-bit read.
test.bus.config_addr = PCI_TYPE1_ADDR(0, 0, 0, 0);
packet.type = ZX_PKT_TYPE_GUEST_IO;
packet.guest_io.input = true;
packet.guest_io.port = PCI_CONFIG_DATA_PORT_BASE;
packet.guest_io.access_size = 2;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.vcpu_io.access_size, 2, "Incorrect IO access_size");
EXPECT_EQ(test.vcpu_io.u16, PCI_VENDOR_ID_INTEL, "Incorrect value read from PCI data port");
// 32-bit read from same address. Result should now contain the Device ID
// in the upper 16 bits
packet.guest_io.access_size = 4;
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.vcpu_io.access_size, 4, "Incorrect IO access_size");
EXPECT_EQ(test.vcpu_io.u32, PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_Q35 << 16),
"Incorrect value read from PCI data port");
// 16-bit read of upper half-word.
//
// Device ID is 2b aligned and the PCI config address register can only hold
// a 4b aligned address. The offset into the word addressed by the PCI
// address port is added to the data port address.
test.vcpu_io.u32 = 0;
packet.guest_io.access_size = 2;
test.bus.config_addr = PCI_TYPE1_ADDR(0, 0, 0, PCI_CONFIG_DEVICE_ID);
// Verify we're using a 4b aligned register address.
EXPECT_EQ(test.bus.config_addr & bit_mask<uint32_t>(2), 0u);
// Add the register offset to the data port base address.
packet.guest_io.port =
PCI_CONFIG_DATA_PORT_BASE + (PCI_CONFIG_DEVICE_ID & bit_mask<uint32_t>(2));
EXPECT_EQ(vcpu_packet_handler(&test.vcpu_ctx, &packet), ZX_OK, "Failed to handle guest packet");
EXPECT_EQ(test.vcpu_io.access_size, 2, "Incorrect IO access_size");
EXPECT_EQ(test.vcpu_io.u16, PCI_DEVICE_ID_INTEL_Q35, "Incorrect value read from PCI data port");
END_TEST;
}
BEGIN_TEST_CASE(vcpu)
RUN_TEST(handle_input_packet);
RUN_TEST(handle_output_packet);
RUN_TEST(read_pci_config_addr_port)
RUN_TEST(write_pci_config_addr_port)
RUN_TEST(read_pci_config_data_port)
END_TEST_CASE(vcpu)