blob: a2292420eb76fc3f77c5afc8654b000354c9d387 [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/pci.h"
#include <endian.h>
#include <lib/async/default.h>
#include <gtest/gtest.h>
#include "src/virtualization/bin/vmm/bits.h"
namespace fpci = fuchsia_hardware_pci;
namespace {
constexpr uint16_t kPciConfigAddrPortBase = 0;
constexpr uint16_t kPciConfigDataPortBase = 4;
// A simple 4-byte PCI capability containing the id/next fields,
// and 2 bytes of data.
struct SimplePciCap {
uint8_t id;
uint8_t next;
uint8_t data[2];
};
// A publicly configurable PciDevice.
class TestPciDevice : public PciDevice {
public:
TestPciDevice() : PciDevice({.name = "Test PCI device"}) {}
// Add a bar of the given size and type. Return the index.
size_t AddBar(size_t size, TrapType type) {
return PciDevice::AddBar(PciBar(this, size, type, nullptr)).value();
}
// Add the given POD type as a capability.
template <typename T>
zx_status_t AddCapability(const T& capability) {
return PciDevice::AddCapability(capability);
}
// `PciDevice` implementation.
bool HasPendingInterrupt() const override { return false; }
};
constexpr uint64_t pci_type1_addr(uint8_t bus, uint8_t device, uint8_t function, uint8_t reg) {
return 0x80000000 | (bus << 16) | (device << 11) | (function << 8) | pci_type1_register(reg);
}
// Test we can read multiple fields in 1 32-bit word.
TEST(PciDeviceTest, ReadConfigRegister) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
PciDevice* device = bus.root_complex();
// Access Vendor/Device ID as a single 32bit read.
IoValue value = {};
value.access_size = 4;
EXPECT_EQ(device->ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kVendorId), &value), ZX_OK);
EXPECT_EQ(value.u32, PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_Q35 << 16));
}
// Verify we can read portions of a 32 bit word, one byte at a time.
TEST(PciDeviceTest, ReadConfigRegisterBytewise) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
PciDevice* device = bus.root_complex();
uint32_t expected_device_vendor = PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_Q35 << 16);
for (int i = 0; i < 4; ++i) {
uint16_t reg = static_cast<uint16_t>(fidl::ToUnderlying(fpci::wire::Config::kVendorId) + i);
IoValue value = {};
value.access_size = 1;
EXPECT_EQ(device->ReadConfig(reg, &value), ZX_OK);
EXPECT_EQ(value.u32, bits_shift(expected_device_vendor, i * 8 + 7, i * 8));
}
}
// Test unaligned reads are reported as errors.
TEST(PciDeviceTest, ReadConfigRegisterUnaligned) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
PciDevice* device = bus.root_complex();
// Attempt to read 16-bits with an offset of 1.
IoValue value = IoValue::FromU16(0);
EXPECT_EQ(device->ReadConfig(/*reg=*/0x1, &value), ZX_ERR_IO);
// Attempt to write 32-bits with an offset of 2.
EXPECT_EQ(device->WriteConfig(/*reg=*/0x2, IoValue::FromU32(0)), ZX_ERR_IO);
}
// PCI devices BAR sizes must be a power of 2 and must not support setting any
// bits in the BAR that are not size aligned. Software often relies on this to
// read the bar size by writing all 1's to the register and reading back the
// value.
//
// This tests that we properly mask the lowest bits so software can compute the
// BAR size.
TEST(PciDeviceTest, ReadBarSize) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
// Add a PCI device with a single BAR.
TestPciDevice test_device{};
test_device.AddBar(/*size=*/0x10, TrapType::MMIO_SYNC);
bus.Connect(&test_device, async_get_default_dispatcher());
// Set all bits in the BAR register. The device will ignore writes to the
// LSBs which we can read out to determine the size.
IoValue value;
value.access_size = 4;
value.u32 = UINT32_MAX;
EXPECT_EQ(
test_device.WriteConfig(fidl::ToUnderlying(fpci::wire::Config::kBaseAddresses) + 0, value),
ZX_OK);
EXPECT_EQ(
test_device.WriteConfig(fidl::ToUnderlying(fpci::wire::Config::kBaseAddresses) + 4, value),
ZX_OK);
// PCI Express Base Specification, Rev. 4.0 Version 1.0, Section 7.5.1.2.1:
// Base Address Registers
//
// Software saves the original value of the Base Address register, writes a
// value of all 1's to the register, then reads it back. Size calculation can
// be done from the 32 bit value read by first clearing encoding information
// bits (bits 1:0 for I/O, bits 3:0 for memory), inverting all 32 bits
// (logical NOT), then incrementing by 1. The resultant 32-bit value is the
// memory/I/O range size decoded by the register. Note that the upper 16 bits
// of the result is ignored if the Base Address register is for I/O and bits
// 31:16 returned zero upon read. The original value in the Base Address
// register is restored before re-enabling decode in the Command register of
// the Function.
//
// 64-bit (memory) Base Address registers can be handled the same, except that
// the second 32 bit register is considered an extension of the first (i.e.,
// bits 63:32). Software writes a value of all 1's to both registers, reads
// them back, and combines the result into a 64-bit value. Size calculation is
// done on the 64-bit value.
// Read out BAR and compute size.
value.access_size = 4;
value.u32 = 0;
EXPECT_EQ(
test_device.ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kBaseAddresses) + 0, &value),
ZX_OK);
uint64_t reg = value.u32;
EXPECT_EQ(
test_device.ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kBaseAddresses) + 4, &value),
ZX_OK);
reg |= static_cast<uint64_t>(value.u32) << 32;
EXPECT_EQ(reg & ~kPciBarMmioAddrMask, kPciBarMmioType64Bit | kPciBarMmioAccessSpace);
const PciBar* bar = test_device.bar(0);
EXPECT_TRUE(bar != nullptr);
EXPECT_EQ(~(reg & kPciBarMmioAddrMask) + 1, bar->size());
}
// The 8-bit interrupt line register should be read/write.
TEST(PciDeviceTest, ReadWriteInterruptLine) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
PciDevice* device = bus.root_complex();
// Write/read the 8-bit value directly.
{
EXPECT_EQ(device->WriteConfig(fidl::ToUnderlying(fpci::wire::Config::kInterruptLine),
IoValue::FromU8(0xaa)),
ZX_OK);
IoValue value = IoValue::FromU8(0);
EXPECT_EQ(device->ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kInterruptLine), &value),
ZX_OK);
EXPECT_EQ(value.u8, 0xaa);
}
// Write/read 32-bits. The interrupt line register should be updated, but not
// the neighbouring registers.
{
IoValue original_value = IoValue::FromU32(0);
EXPECT_EQ(
device->ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kInterruptLine), &original_value),
ZX_OK);
EXPECT_EQ(device->WriteConfig(fidl::ToUnderlying(fpci::wire::Config::kInterruptLine),
IoValue::FromU32(0x55555555)),
ZX_OK);
IoValue value = IoValue::FromU32(0);
EXPECT_EQ(device->ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kInterruptLine), &value),
ZX_OK);
EXPECT_EQ(value.u32, clear_bits(original_value.u32, 8, 0) | 0x55);
}
}
// Verify stats & cap registers correctly show present capabilities and that
// capability data is readable.
TEST(PciDeviceTest, ReadCapability) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
TestPciDevice test_device{};
bus.Connect(&test_device, async_get_default_dispatcher());
// PCI Local Bus Spec 3.0 Table 6-2: Status Register Bits
//
// This optional read-only bit indicates whether or not this device
// implements the pointer for a New Capabilities linked list at offset 34h.
// A value of zero indicates that no New Capabilities linked list is
// available. A value of one indicates that the value read at offset 34h is
// a pointer in Configuration Space to a linked list of new capabilities.
// Refer to Section 6.7 for details on New Capabilities.
//
// Initially expect the bit to be 0, as we have no caps.
IoValue status = IoValue::FromU16(0);
EXPECT_EQ(test_device.ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kStatus), &status),
ZX_OK);
EXPECT_FALSE(status.u16 & static_cast<uint16_t>(fpci::wire::Status::kNewCaps));
// Create and install a simple capability.
EXPECT_EQ(test_device.AddCapability(SimplePciCap{.id = 0x9, .next = 0, .data = {0x11, 0x22}}),
ZX_OK);
// The PCI_STATUS_NEW_CAPS bit should now be set.
EXPECT_EQ(test_device.ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kStatus), &status),
ZX_OK);
EXPECT_TRUE(status.u16 & static_cast<uint16_t>(fpci::wire::Status::kNewCaps));
// Read the cap pointer from config space. Here just verify it points to
// some location beyond the pre-defined header.
IoValue cap_ptr = IoValue::FromU8(0);
EXPECT_EQ(
test_device.ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kCapabilitiesPtr), &cap_ptr),
ZX_OK);
EXPECT_LT(0x40u, cap_ptr.u8);
// Read the capability. This will be the Cap ID (9), next pointer (0), followed
// by data bytes (starting at index 2).
IoValue cap_value = IoValue::FromU32(0);
EXPECT_EQ(test_device.ReadConfig(cap_ptr.u8, &cap_value), ZX_OK);
EXPECT_EQ(be32toh(0x09'00'11'22), cap_value.u32);
}
// Build a list of capabilities with no data (only the required ID/next
// fields). Verify the next pointers are correctly wired up to traverse
// the linked list.
TEST(PciDeviceTest, ReadChainedCapability) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
TestPciDevice test_device{};
bus.Connect(&test_device, async_get_default_dispatcher());
// Build list of caps.
const int kNumCaps = 5;
for (uint8_t i = 0; i < kNumCaps; i++) {
EXPECT_EQ(test_device.AddCapability(SimplePciCap{.id = i}), ZX_OK);
}
IoValue cap_ptr = IoValue::FromU8(0);
EXPECT_EQ(
test_device.ReadConfig(fidl::ToUnderlying(fpci::wire::Config::kCapabilitiesPtr), &cap_ptr),
ZX_OK);
for (uint8_t i = 0; i < kNumCaps; ++i) {
IoValue cap_header = IoValue::FromU32(0);
// Read the current capability.
EXPECT_EQ(test_device.ReadConfig(cap_ptr.u8, &cap_header), ZX_OK);
// ID is the first byte.
EXPECT_EQ(i, cap_header.u32 & UINT8_MAX);
// Next pointer is the second byte.
cap_ptr.u8 = static_cast<uint8_t>(cap_header.u32 >> 8);
}
EXPECT_EQ(0u, cap_ptr.u8);
}
// 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.
//
// The device operates on relative port addresses so we'll use 0-3 instead of
// 0cf8-0xcfb
//
// Ex:
// -------------------------------------
// | port | valid access widths (bytes) |
// --------------------------------------|
// | 0 | 1, 2, 4 |
// | 1 | 1 |
// | 2 | 1, 2 |
// | 3 | 1 |
// -------------------------------------
TEST(PciBusTest, WriteConfigAddressPort) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
// 32 bit write.
IoValue value;
value.access_size = 4;
value.u32 = 0x12345678;
EXPECT_EQ(bus.WriteIoPort(kPciConfigAddrPortBase, value), ZX_OK);
EXPECT_EQ(bus.config_addr(), 0x12345678u);
// 16 bit write to bits 31..16. Other bits remain unchanged.
value.access_size = 2;
value.u16 = 0xFACE;
EXPECT_EQ(bus.WriteIoPort(kPciConfigAddrPortBase + 2, value), ZX_OK);
EXPECT_EQ(bus.config_addr(), 0xFACE5678u);
// 8 bit write to bits (15..8). Other bits remain unchanged.
value.access_size = 1;
value.u8 = 0x99;
EXPECT_EQ(bus.WriteIoPort(kPciConfigAddrPortBase + 1, value), ZX_OK);
EXPECT_EQ(bus.config_addr(), 0xFACE9978u);
}
// Test reading the PCI config address ports.
//
// See pci_bus_write_config_addr_port for more details.
TEST(PciBusTest, ReadConfigAddressPort) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
bus.set_config_addr(0x12345678);
// 32 bit read (bits 31..0).
IoValue value = {};
value.access_size = 4;
EXPECT_EQ(bus.ReadIoPort(kPciConfigAddrPortBase, &value), ZX_OK);
EXPECT_EQ(value.access_size, 4);
EXPECT_EQ(value.u32, 0x12345678u);
// 16 bit read (bits 31..16).
value.access_size = 2;
value.u16 = 0;
EXPECT_EQ(bus.ReadIoPort(kPciConfigAddrPortBase + 2, &value), ZX_OK);
EXPECT_EQ(value.access_size, 2);
EXPECT_EQ(value.u16, 0x1234u);
// 8 bit read (bits 15..8).
value.access_size = 1;
value.u8 = 0;
EXPECT_EQ(bus.ReadIoPort(kPciConfigAddrPortBase + 1, &value), ZX_OK);
EXPECT_EQ(value.access_size, 1);
EXPECT_EQ(value.u8, 0x56u);
}
// 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.
TEST(PciBusTest, ReadConfigDataPort) {
Guest guest;
PciBus bus(&guest, nullptr);
bus.Init(async_get_default_dispatcher());
IoValue value = {};
// 16-bit read.
bus.set_config_addr(pci_type1_addr(0, 0, 0, 0));
value.access_size = 2;
EXPECT_EQ(bus.ReadIoPort(kPciConfigDataPortBase, &value), ZX_OK);
EXPECT_EQ(value.access_size, 2);
EXPECT_EQ(value.u16, PCI_VENDOR_ID_INTEL);
// 32-bit read from same address. Result should now contain the Device ID
// in the upper 16 bits
value.access_size = 4;
value.u32 = 0;
EXPECT_EQ(bus.ReadIoPort(kPciConfigDataPortBase, &value), ZX_OK);
EXPECT_EQ(value.access_size, 4);
EXPECT_EQ(value.u32, PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_Q35 << 16));
// 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.
value.access_size = 2;
value.u16 = 0;
bus.set_config_addr(pci_type1_addr(0, 0, 0, fidl::ToUnderlying(fpci::wire::Config::kDeviceId)));
// Verify we're using a 4b aligned register address.
EXPECT_EQ(bus.config_addr() & bit_mask<uint32_t>(2), 0u);
// Add the register offset to the data port base address.
EXPECT_EQ(
bus.ReadIoPort(kPciConfigDataPortBase + (fidl::ToUnderlying(fpci::wire::Config::kDeviceId) &
bit_mask<uint32_t>(2)),
&value),
ZX_OK);
EXPECT_EQ(value.access_size, 2);
EXPECT_EQ(value.u16, PCI_DEVICE_ID_INTEL_Q35);
}
} // namespace