blob: 65cc6712657eb4b044c375bbe29cede780f5b82f [file] [log] [blame]
// Copyright 2021 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.
#ifndef SRC_DEVICES_PCI_TESTING_PCI_PROTOCOL_FAKE_H_
#define SRC_DEVICES_PCI_TESTING_PCI_PROTOCOL_FAKE_H_
#include <fuchsia/hardware/pci/c/banjo.h>
#include <fuchsia/hardware/pci/cpp/banjo.h>
#include <lib/ddk/device.h>
#include <lib/fake-bti/bti.h>
#include <lib/zx/bti.h>
#include <lib/zx/interrupt.h>
#include <zircon/errors.h>
#include <zircon/hw/pci.h>
#include <zircon/status.h>
#include <array>
#include <optional>
#include <vector>
#include <fbl/algorithm.h>
// These are here to prevent a large dependency chain from including the userspace
// PCI driver's headers.
#define PCI_DEVICE_BAR_COUNT 6
#define MSI_MAX_VECTORS 32
#define PCI_CFG_HEADER_SIZE 64
#define kFakePciInternalError "Internal FakePciProtocol Error"
namespace pci {
class FakePciProtocol : public ddk::PciProtocol<FakePciProtocol> {
public:
FakePciProtocol() { reset(); }
struct FakeBar {
size_t size;
zx::vmo vmo;
};
struct FakeCapability {
bool operator<(const FakeCapability& r) const { return this->position < r.position; }
uint8_t id;
uint8_t position;
uint8_t size;
};
zx_status_t PciGetBar(uint32_t bar_id, pci_bar_t* out_res) {
if (!out_res) {
return ZX_ERR_INVALID_ARGS;
}
if (bar_id >= PCI_DEVICE_BAR_COUNT) {
return ZX_ERR_INVALID_ARGS;
}
if (bars_[bar_id].size == 0) {
return ZX_ERR_NOT_FOUND;
}
auto& bar = bars_[bar_id];
zx::vmo bar_vmo{};
zx_status_t status = bar.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &bar_vmo);
ZX_ASSERT_MSG(status == ZX_OK, kFakePciInternalError);
out_res->id = bar_id;
out_res->size = bar.size;
out_res->type = ZX_PCI_BAR_TYPE_MMIO;
out_res->u.handle = bar_vmo.release();
return ZX_OK;
}
zx_status_t PciAckInterrupt() {
return (irq_mode_ == PCI_IRQ_MODE_LEGACY) ? ZX_OK : ZX_ERR_BAD_STATE;
}
zx_status_t PciMapInterrupt(uint32_t which_irq, zx::interrupt* out_handle) {
if (!out_handle) {
return ZX_ERR_INVALID_ARGS;
}
switch (irq_mode_) {
case PCI_IRQ_MODE_LEGACY:
case PCI_IRQ_MODE_LEGACY_NOACK:
if (which_irq > 0) {
return ZX_ERR_INVALID_ARGS;
}
return legacy_interrupt_->duplicate(ZX_RIGHT_SAME_RIGHTS, out_handle);
case PCI_IRQ_MODE_MSI:
if (which_irq >= msi_interrupts_.size()) {
return ZX_ERR_INVALID_ARGS;
}
return msi_interrupts_[which_irq].duplicate(ZX_RIGHT_SAME_RIGHTS, out_handle);
case PCI_IRQ_MODE_MSI_X:
if (which_irq >= msix_interrupts_.size()) {
return ZX_ERR_INVALID_ARGS;
}
return msix_interrupts_[which_irq].duplicate(ZX_RIGHT_SAME_RIGHTS, out_handle);
}
return ZX_ERR_BAD_STATE;
}
zx_status_t PciConfigureIrqMode(uint32_t requested_irq_count, pci_irq_mode_t* out_irq_mode) {
ZX_ASSERT(requested_irq_count);
zx_status_t status;
if (msix_interrupts_.size() >= requested_irq_count) {
if ((status = PciSetIrqMode(PCI_IRQ_MODE_MSI_X, requested_irq_count)) == ZX_OK) {
if (out_irq_mode) {
*out_irq_mode = PCI_IRQ_MODE_MSI_X;
}
return ZX_OK;
}
}
if (msi_interrupts_.size() >= requested_irq_count) {
if ((status = PciSetIrqMode(PCI_IRQ_MODE_MSI, requested_irq_count)) == ZX_OK) {
if (out_irq_mode) {
*out_irq_mode = PCI_IRQ_MODE_MSI;
}
return ZX_OK;
}
}
if (legacy_interrupt_ && requested_irq_count == 1) {
if ((status = PciSetIrqMode(PCI_IRQ_MODE_LEGACY, requested_irq_count)) == ZX_OK) {
if (out_irq_mode) {
*out_irq_mode = PCI_IRQ_MODE_LEGACY;
}
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PciQueryIrqMode(pci_irq_mode_t mode, uint32_t* out_max_irqs) {
ZX_ASSERT(out_max_irqs);
ZX_ASSERT(mode < PCI_IRQ_MODE_COUNT);
switch (mode) {
case PCI_IRQ_MODE_LEGACY:
case PCI_IRQ_MODE_LEGACY_NOACK:
if (legacy_interrupt_) {
*out_max_irqs = 1;
return ZX_OK;
}
break;
case PCI_IRQ_MODE_MSI:
if (!msi_interrupts_.empty()) {
// MSI interrupts are only supported in powers of 2.
*out_max_irqs = (msi_interrupts_.size() <= 1)
? msi_interrupts_.size()
: fbl::round_down(msi_interrupts_.size(), 2u);
return ZX_OK;
}
break;
case PCI_IRQ_MODE_MSI_X:
if (!msix_interrupts_.empty()) {
*out_max_irqs = msix_interrupts_.size();
return ZX_OK;
break;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
// This allows us to mimic the kernel's handling of outstanding MsiDispatchers per MsiAllocation
// objects. A device's legacy interrupt is still a valid object if the interrupt mode is
// switched, albeit not a useful one.
bool AllMappedInterruptsFreed() {
zx_info_handle_count_t info;
for (auto& interrupts : {&msix_interrupts_, &msi_interrupts_}) {
for (auto& interrupt : *interrupts) {
zx_status_t status =
interrupt.get_info(ZX_INFO_HANDLE_COUNT, &info, sizeof(info), nullptr, nullptr);
ZX_ASSERT_MSG(status == ZX_OK, "%s status %d", kFakePciInternalError, status);
if (info.handle_count > 1) {
return false;
}
}
}
return true;
}
zx_status_t PciSetIrqMode(pci_irq_mode_t mode, uint32_t requested_irq_count) {
if (!AllMappedInterruptsFreed()) {
return ZX_ERR_BAD_STATE;
}
switch (mode) {
case PCI_IRQ_MODE_LEGACY:
case PCI_IRQ_MODE_LEGACY_NOACK:
if (requested_irq_count > 1) {
return ZX_ERR_INVALID_ARGS;
}
if (legacy_interrupt_) {
irq_mode_ = mode;
irq_cnt_ = 1;
}
return ZX_OK;
case PCI_IRQ_MODE_MSI:
if (msi_interrupts_.empty()) {
break;
}
if (!fbl::is_pow2(requested_irq_count) || requested_irq_count > MSI_MAX_VECTORS) {
return ZX_ERR_INVALID_ARGS;
}
if (msi_interrupts_.size() < requested_irq_count) {
return ZX_ERR_INVALID_ARGS;
}
irq_mode_ = PCI_IRQ_MODE_MSI;
irq_cnt_ = requested_irq_count;
return ZX_OK;
case PCI_IRQ_MODE_MSI_X:
if (msix_interrupts_.empty()) {
break;
}
if (msix_interrupts_.size() < requested_irq_count) {
return ZX_ERR_INVALID_ARGS;
}
irq_mode_ = PCI_IRQ_MODE_MSI_X;
irq_cnt_ = requested_irq_count;
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PciEnableBusMaster(bool enable) {
bus_master_en_ = enable;
return ZX_OK;
}
zx_status_t PciResetDevice() {
reset_cnt_++;
return ZX_OK;
}
zx_status_t PciGetDeviceInfo(pcie_device_info_t* out_info) {
ZX_ASSERT(out_info);
*out_info = info_;
return ZX_OK;
}
template <typename T>
zx_status_t ConfigRead(uint16_t offset, T* out_value) {
ZX_ASSERT_MSG(offset + sizeof(T) <= PCI_BASE_CONFIG_SIZE,
"FakePciProtocol: PciConfigRead reads must fit in the range [%#x, %#x] (offset "
"= %#x, io width = %#lx).",
0, PCI_BASE_CONFIG_SIZE - 1, offset, sizeof(T));
return config_.read(out_value, offset, sizeof(T));
}
constexpr zx_status_t PciConfigRead8(uint16_t offset, uint8_t* out_value) {
return ConfigRead(offset, out_value);
}
constexpr zx_status_t PciConfigRead16(uint16_t offset, uint16_t* out_value) {
return ConfigRead(offset, out_value);
}
constexpr zx_status_t PciConfigRead32(uint16_t offset, uint32_t* out_value) {
return ConfigRead(offset, out_value);
}
template <typename T>
zx_status_t ConfigWrite(uint16_t offset, T value) {
ZX_ASSERT_MSG(offset >= PCI_CFG_HEADER_SIZE && offset + sizeof(T) <= PCI_BASE_CONFIG_SIZE,
"FakePciProtocol: PciConfigWrite writes must fit in the range [%#x, %#x] (offset "
"= %#x, io width = %#lx).",
PCI_CFG_HEADER_SIZE, PCI_BASE_CONFIG_SIZE - 1, offset, sizeof(T));
return config_.write(&value, offset, sizeof(T));
}
zx_status_t PciConfigWrite8(uint16_t offset, uint8_t value) { return ConfigWrite(offset, value); }
zx_status_t PciConfigWrite16(uint16_t offset, uint16_t value) {
return ConfigWrite(offset, value);
}
zx_status_t PciConfigWrite32(uint16_t offset, uint32_t value) {
return ConfigWrite(offset, value);
}
zx_status_t CommonCapabilitySearch(uint8_t id, std::optional<uint8_t> offset,
uint8_t* out_offset) {
if (!out_offset) {
return ZX_ERR_INVALID_ARGS;
}
for (auto& cap : capabilities_) {
// Skip until we've caught up to last one found if one was provided.
if (offset && cap.position <= offset) {
continue;
}
if (cap.id == id) {
*out_offset = cap.position;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t PciGetFirstCapability(uint8_t id, uint8_t* out_offset) {
return CommonCapabilitySearch(id, std::nullopt, out_offset);
}
zx_status_t PciGetNextCapability(uint8_t id, uint8_t offset, uint8_t* out_offset) {
return CommonCapabilitySearch(id, offset, out_offset);
}
zx_status_t PciGetFirstExtendedCapability(uint16_t id, uint16_t* out_offset) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PciGetNextExtendedCapability(uint16_t id, uint16_t offset, uint16_t* out_offset) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PciGetBti(uint32_t index, zx::bti* out_bti) {
if (!out_bti) {
return ZX_ERR_INVALID_ARGS;
}
return bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, out_bti);
}
// Returns a |pci_protocol_t| suitable for use with the C api, or passing to a
// ddk::PciProtocolClient.
const pci_protocol_t& get_protocol() { return protocol_; }
// Support methods for tests
// Add an interrupt for the specified PCI interrupt mode. A borrowed copy of the zx::interrupt
// is returned for use in tests.
zx::unowned_interrupt AddLegacyInterrupt() { return AddInterrupt(PCI_IRQ_MODE_LEGACY); }
zx::unowned_interrupt AddMsiInterrupt() { return AddInterrupt(PCI_IRQ_MODE_MSI); }
zx::unowned_interrupt AddMsixInterrupt() { return AddInterrupt(PCI_IRQ_MODE_MSI_X); }
zx::unowned_interrupt AddInterrupt(pci_irq_mode_t mode) {
ZX_ASSERT_MSG(!(mode == PCI_IRQ_MODE_LEGACY && legacy_interrupt_),
"FakePciProtocol Error: Legacy interrupt mode only supports 1 interrupt.");
ZX_ASSERT_MSG(!(mode == PCI_IRQ_MODE_MSI && msi_interrupts_.size() == MSI_MAX_VECTORS),
"FakePciProtocol Error: MSI interrupt mode only supports up to %u interrupts.",
MSI_MAX_VECTORS);
zx::interrupt interrupt{};
zx_status_t status = zx::interrupt::create(*zx::unowned_resource(ZX_HANDLE_INVALID), 0,
ZX_INTERRUPT_VIRTUAL, &interrupt);
ZX_ASSERT_MSG(status == ZX_OK, kFakePciInternalError);
zx::unowned_interrupt borrow = interrupt.borrow();
switch (mode) {
case PCI_IRQ_MODE_LEGACY:
legacy_interrupt_ = std::move(interrupt);
break;
case PCI_IRQ_MODE_MSI:
msi_interrupts_.push_back(std::move(interrupt));
break;
case PCI_IRQ_MODE_MSI_X:
msix_interrupts_.push_back(std::move(interrupt));
break;
}
return borrow;
}
pcie_device_info_t SetDeviceInfo(pcie_device_info_t info) {
info_ = info;
config_.write(&info_.vendor_id, PCI_CFG_VENDOR_ID, sizeof(info_.vendor_id));
config_.write(&info_.device_id, PCI_CFG_DEVICE_ID, sizeof(info_.device_id));
config_.write(&info_.revision_id, PCI_CFG_REVISION_ID, sizeof(info_.revision_id));
config_.write(&info_.base_class, PCI_CFG_CLASS_CODE_BASE, sizeof(info_.base_class));
config_.write(&info_.sub_class, PCI_CFG_CLASS_CODE_SUB, sizeof(info_.sub_class));
config_.write(&info_.program_interface, PCI_CFG_CLASS_CODE_INTR,
sizeof(info_.program_interface));
return info;
}
void AddVendorCapability(uint8_t position, uint8_t size) {
ZX_ASSERT_MSG(
size > 2,
"FakePciProtocol Error: a vendor capability must be at least size 0x3 (size = %#x).", size);
AddCapability(PCI_CAP_ID_VENDOR, position, size);
// Vendor capabilities store a size at the byte following the next pointer.
config_.write(&size, position + 2, sizeof(size));
}
// No registers are configured, but most devices that check for this
// capability do so just to understand the configuration space they have
// available, not to actually attempt to modify this capability.
static constexpr uint8_t kPciExpressCapabilitySize = 0x3B;
void AddPciExpressCapability(uint8_t position) {
AddCapability(PCI_CAP_ID_PCI_EXPRESS, position, kPciExpressCapabilitySize);
}
// Capabilities are the hardest part to implement because if a device expects a capability
// at a given address in configuration space then it's possible they will want to write to it.
// Additionally, vendor capabilities are of a variable size which is read from the capability
// at runtime. To further complicate things, particular devices will have registers in their
// configuration space that the device may be expected to use but which are not exposed
// through any PCI base address register mechanism. This makes it risky to lay out a
// capability wherever we wish for fear it may overlap with one of these spaces. For this
// reason we do no validation of the capability's setup in configuration space besides writing
// the capability id and next pointer. The test author is responsible for setting up the
// layout of the capabilities as necessary to match their device, but we can provide helper
// methods to ensure they're doing it properly.
void AddCapability(uint8_t capability_id, uint8_t position, uint8_t size) {
ZX_ASSERT_MSG(
capability_id > 0 && capability_id <= PCI_CAP_ID_FLATTENING_PORTAL_BRIDGE,
"FakePciProtocol Error: capability_id must be non-zero and <= %#x (capability_id = %#x).",
PCI_CAP_ID_FLATTENING_PORTAL_BRIDGE, capability_id);
ZX_ASSERT_MSG(position >= PCI_CFG_HEADER_SIZE && position + size < PCI_BASE_CONFIG_SIZE,
"FakePciProtocolError: capability must fit the range [%#x, %#x] (capability = "
"[%#x, %#x]).",
PCI_CFG_HEADER_SIZE, PCI_BASE_CONFIG_SIZE - 1, position, position + size - 1);
// We need to update the next pointer of the previous capability, or the
// original header capabilities pointer if this is the first.
uint8_t next_ptr = PCI_CFG_CAPABILITIES_PTR;
if (!capabilities_.empty()) {
for (auto& cap : capabilities_) {
ZX_ASSERT_MSG(!(position <= cap.position && position + size > cap.position) &&
!(position >= cap.position && position < cap.position + cap.size),
"FakePciProtocol Error: New capability overlaps with a previous capability "
"[%#x, %#x] (new capability id = %#x @ [%#x, %#x]).",
cap.position, cap.position + cap.size - 1, capability_id, position,
position + size - 1);
}
next_ptr = capabilities_[capabilities_.size() - 1].position + 1;
}
config_.write(&capability_id, position, sizeof(capability_id));
config_.write(&position, next_ptr, sizeof(position));
capabilities_.push_back({.id = capability_id, .position = position, .size = size});
// Not fast, but not as error prone as doing it by hand on insertion with
// capability cyles being a possibility.
std::sort(capabilities_.begin(), capabilities_.end());
}
zx::unowned_vmo SetBar(uint32_t bar_id, size_t size, zx::vmo vmo) {
ZX_ASSERT_MSG(bar_id < PCI_DEVICE_BAR_COUNT,
"FakePciProtocol Error: valid BAR ids are [0, 5] (bar_id = %u)", bar_id);
uint64_t vmo_size = 0;
zx_status_t status = vmo.get_size(&vmo_size);
ZX_ASSERT_MSG(status == ZX_OK, kFakePciInternalError);
ZX_ASSERT_MSG(vmo_size >= size,
"FakePciProtocol Error: vmo is not large enough for BAR size (BAR size = %#lx, "
"vmo size = %#lx)",
size, vmo_size);
bars_[bar_id].size = size;
bars_[bar_id].vmo = std::move(vmo);
return bars_[bar_id].vmo.borrow();
}
zx::unowned_vmo CreateBar(uint32_t bar_id, size_t size) {
zx::vmo vmo;
zx_status_t status = zx::vmo::create(size, /*options=*/0, &vmo);
ZX_ASSERT_MSG(status == ZX_OK,
"FakePciProtocol Error: failed to create VMO for bar (bar_id = %u, size = %#zx, "
"status = %d)",
bar_id, size, status);
return SetBar(bar_id, size, std::move(vmo));
}
zx::unowned_vmo GetBar(uint32_t bar_id) {
ZX_ASSERT_MSG(bar_id < PCI_DEVICE_BAR_COUNT,
"FakePciProtocol Error: valid BAR ids are [0, 5] (bar_id = %u)", bar_id);
ZX_ASSERT_MSG(bars_[bar_id].size > 0, "FakePciProtocol Error: BAR %u has not been set.",
bar_id);
return bars_[bar_id].vmo.borrow();
}
zx::unowned_vmo GetConfigVmo() { return config_.borrow(); }
pci_irq_mode_t GetIrqMode() const { return irq_mode_; }
uint32_t GetIrqCount() const { return irq_cnt_; }
uint32_t GetResetCount() const { return reset_cnt_; }
// Returns the state of the device's Bus Mastering setting. Returned as an optional
// so that the caller can differentiate between off and "never set" states in driver
// testing.
std::optional<bool> GetBusMasterEnabled() const { return bus_master_en_; }
void reset() {
legacy_interrupt_ = std::nullopt;
msi_interrupts_.clear();
msix_interrupts_.clear();
irq_mode_ = PCI_IRQ_MODE_DISABLED;
irq_cnt_ = 0;
bars_ = {};
capabilities_.clear();
bus_master_en_ = std::nullopt;
reset_cnt_ = 0;
info_ = {};
zx_status_t status = zx::vmo::create(PCI_BASE_CONFIG_SIZE, /*options=*/0, &config_);
ZX_ASSERT(status == ZX_OK);
status = fake_bti_create(bti_.reset_and_get_address());
ZX_ASSERT(status == ZX_OK);
}
private:
// Interrupts
std::optional<zx::interrupt> legacy_interrupt_;
std::vector<zx::interrupt> msi_interrupts_;
std::vector<zx::interrupt> msix_interrupts_;
pci_irq_mode_t irq_mode_;
uint32_t irq_cnt_;
std::array<FakeBar, PCI_DEVICE_BAR_COUNT> bars_{};
std::vector<FakeCapability> capabilities_;
zx::bti bti_;
uint32_t reset_cnt_;
std::optional<bool> bus_master_en_;
pcie_device_info_t info_ = {};
zx::vmo config_;
pci_protocol_t protocol_ = {.ops = &pci_protocol_ops_, .ctx = this};
};
} // namespace pci
#endif // SRC_DEVICES_PCI_TESTING_PCI_PROTOCOL_FAKE_H_