blob: ec941c38c1cb6409fa58ec801016b1b15c62376d [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.
#include <fuchsia/hardware/pci/cpp/banjo.h>
#include <lib/device-protocol/pci.h>
#include <lib/mmio/mmio.h>
#include <lib/zx/bti.h>
#include <lib/zx/object.h>
#include <lib/zx/vmo.h>
#include <zircon/errors.h>
#include <zircon/syscalls/pci.h>
#include <zxtest/zxtest.h>
#include "fuchsia/hardware/pci/c/banjo.h"
#include "src/devices/pci/testing/pci_protocol_fake.h"
#include "zircon/system/public/zircon/syscalls.h"
class FakePciProtocolTests : public zxtest::Test {
protected:
void SetUp() final {
fake_pci_.Reset();
pci_ = ddk::PciProtocolClient(&fake_pci_.get_protocol());
}
pci::FakePciProtocol& fake_pci() { return fake_pci_; }
ddk::PciProtocolClient& pci() { return pci_; }
private:
pci::FakePciProtocol fake_pci_;
ddk::PciProtocolClient pci_;
};
TEST_F(FakePciProtocolTests, CreateBar) {
zx::vmo vmo;
size_t size = 8193;
ASSERT_OK(zx::vmo::create(size, 0, &vmo));
fake_pci().CreateBar(0, size, true);
pci_bar_t bar;
pci().GetBar(0, &bar);
EXPECT_EQ(size, bar.size);
}
TEST_F(FakePciProtocolTests, ResetDevice) {
uint32_t reset_cnt = 0;
ASSERT_EQ(reset_cnt++, fake_pci().GetResetCount());
ASSERT_OK(pci().ResetDevice());
ASSERT_EQ(reset_cnt++, fake_pci().GetResetCount());
ASSERT_OK(pci().ResetDevice());
ASSERT_EQ(reset_cnt++, fake_pci().GetResetCount());
}
TEST_F(FakePciProtocolTests, GetBti) {
zx::bti bti{};
ASSERT_OK(pci().GetBti(0, &bti));
zx_info_bti_t info;
// Verify it's a BTI at least.
ASSERT_OK(bti.get_info(ZX_INFO_BTI, &info, sizeof(info), /*actual_count=*/nullptr,
/*avail_count=*/nullptr));
}
TEST_F(FakePciProtocolTests, SetBusMastering) {
// If enable has never been called there should be no value.
ASSERT_FALSE(fake_pci().GetBusMasterEnabled().has_value());
ASSERT_OK(pci().SetBusMastering(true));
ASSERT_TRUE(fake_pci().GetBusMasterEnabled().value());
ASSERT_OK(pci().SetBusMastering(false));
ASSERT_FALSE(fake_pci().GetBusMasterEnabled().value());
}
TEST_F(FakePciProtocolTests, GetDeviceInfo) {
pci_device_info_t actual{};
pci_device_info_t zeroed{};
ASSERT_OK(pci().GetDeviceInfo(&actual));
ASSERT_EQ(0, memcmp(&zeroed, &actual, sizeof(zeroed)));
pci_device_info_t expected = {
.vendor_id = 0x1,
.device_id = 0x2,
.base_class = 0x3,
.sub_class = 0x4,
.program_interface = 0x5,
.revision_id = 0x6,
.bus_id = 0x7,
.dev_id = 0x8,
.func_id = 0x9,
};
fake_pci().SetDeviceInfo(expected);
ASSERT_OK(pci().GetDeviceInfo(&actual));
ASSERT_EQ(0, memcmp(&expected, &actual, sizeof(expected)));
// Did we update the config header to match the device structure?
uint8_t val8;
uint16_t val16;
ASSERT_OK(pci().ReadConfig16(PCI_CONFIG_VENDOR_ID, &val16));
ASSERT_EQ(expected.vendor_id, val16);
ASSERT_OK(pci().ReadConfig16(PCI_CONFIG_DEVICE_ID, &val16));
ASSERT_EQ(expected.device_id, val16);
ASSERT_OK(pci().ReadConfig8(PCI_CONFIG_REVISION_ID, &val8));
ASSERT_EQ(expected.revision_id, val8);
ASSERT_OK(pci().ReadConfig8(PCI_CONFIG_CLASS_CODE_BASE, &val8));
ASSERT_EQ(expected.base_class, val8);
ASSERT_OK(pci().ReadConfig8(PCI_CONFIG_CLASS_CODE_SUB, &val8));
ASSERT_EQ(expected.sub_class, val8);
ASSERT_OK(pci().ReadConfig8(PCI_CONFIG_CLASS_CODE_INTR, &val8));
ASSERT_EQ(expected.program_interface, val8);
}
TEST_F(FakePciProtocolTests, GetInterruptModes) {
pci_interrupt_modes_t modes{};
pci().GetInterruptModes(&modes);
ASSERT_EQ(0, modes.has_legacy);
ASSERT_EQ(0, modes.msi_count);
ASSERT_EQ(0, modes.msix_count);
fake_pci().AddLegacyInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(1, modes.has_legacy);
// MSI supports interrupt configuration via powers of two, so ensure that we
// round down if not enough have been added.
fake_pci().AddMsiInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(1, modes.msi_count);
fake_pci().AddMsiInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(2, modes.msi_count);
fake_pci().AddMsiInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(2, modes.msi_count);
fake_pci().AddMsiInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(4, modes.msi_count);
// MSI-X doesn't care about alignment, so any value should work.
fake_pci().AddMsixInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(1, modes.msix_count);
fake_pci().AddMsixInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(2, modes.msix_count);
fake_pci().AddMsixInterrupt();
pci().GetInterruptModes(&modes);
ASSERT_EQ(3, modes.msix_count);
}
TEST_F(FakePciProtocolTests, SetInterruptMode) {
fake_pci().AddLegacyInterrupt();
fake_pci().AddMsiInterrupt();
fake_pci().AddMsiInterrupt();
fake_pci().AddMsiInterrupt();
fake_pci().AddMsiInterrupt();
fake_pci().AddMsixInterrupt();
fake_pci().AddMsixInterrupt();
pci_interrupt_mode_t mode = PCI_INTERRUPT_MODE_LEGACY;
ASSERT_OK(pci().SetInterruptMode(mode, 1));
ASSERT_EQ(1, fake_pci().GetIrqCount());
ASSERT_EQ(mode, fake_pci().GetIrqMode());
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().SetInterruptMode(mode, 2));
mode = PCI_INTERRUPT_MODE_MSI;
ASSERT_OK(pci().SetInterruptMode(mode, 1));
ASSERT_EQ(1, fake_pci().GetIrqCount());
ASSERT_EQ(mode, fake_pci().GetIrqMode());
ASSERT_OK(pci().SetInterruptMode(mode, 2));
ASSERT_EQ(2, fake_pci().GetIrqCount());
ASSERT_EQ(mode, fake_pci().GetIrqMode());
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().SetInterruptMode(mode, 3));
ASSERT_EQ(2, fake_pci().GetIrqCount());
ASSERT_EQ(mode, fake_pci().GetIrqMode());
ASSERT_OK(pci().SetInterruptMode(mode, 4));
ASSERT_EQ(4, fake_pci().GetIrqCount());
ASSERT_EQ(mode, fake_pci().GetIrqMode());
}
namespace {
// When interrupts are added to the fake a borrowed copy of the interrupt is
// returned for comparison by tests later. Its koid should match the koid of the
// duplicated handle returned by MapInterrupt.
template <typename T>
bool MatchKoids(const zx::object<T>& first, const zx::object<T>& second) {
zx_info_handle_basic finfo{}, sinfo{};
ZX_ASSERT(first.get_info(ZX_INFO_HANDLE_BASIC, &finfo, sizeof(finfo), nullptr, nullptr) == ZX_OK);
ZX_ASSERT(second.get_info(ZX_INFO_HANDLE_BASIC, &sinfo, sizeof(sinfo), nullptr, nullptr) ==
ZX_OK);
return finfo.koid == sinfo.koid;
}
} // namespace
TEST_F(FakePciProtocolTests, MapInterrupt) {
// One notable difference between this fake and the real PCI protocol is that
// it is an error to call SetInterruptMode and switch modes if an existing MSI is
// mapped still. In the fake though, it's fine to do so. Switching IRQ modes
// is not something drivers do in practice, so it's fine if they encounter
// ZX_ERR_BAD_STATE at runtime if documentation details it.
zx::interrupt& legacy = fake_pci().AddLegacyInterrupt();
zx::interrupt& msi0 = fake_pci().AddMsiInterrupt();
zx::interrupt& msi1 = fake_pci().AddMsiInterrupt();
zx::interrupt& msix0 = fake_pci().AddMsixInterrupt();
zx::interrupt& msix1 = fake_pci().AddMsixInterrupt();
zx::interrupt& msix2 = fake_pci().AddMsixInterrupt();
zx::interrupt interrupt{};
uint32_t irq_cnt = 1;
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY, irq_cnt));
ASSERT_OK(pci().MapInterrupt(0, &interrupt));
ASSERT_TRUE(MatchKoids(legacy, interrupt));
ASSERT_FALSE(MatchKoids(msi0, interrupt));
ASSERT_FALSE(MatchKoids(msi1, interrupt));
ASSERT_FALSE(MatchKoids(msix0, interrupt));
ASSERT_FALSE(MatchKoids(msix1, interrupt));
ASSERT_FALSE(MatchKoids(msix2, interrupt));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().MapInterrupt(irq_cnt, &interrupt));
interrupt.reset();
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY_NOACK, irq_cnt));
ASSERT_OK(pci().MapInterrupt(0, &interrupt));
ASSERT_TRUE(MatchKoids(legacy, interrupt));
ASSERT_FALSE(MatchKoids(msi0, interrupt));
ASSERT_FALSE(MatchKoids(msi1, interrupt));
ASSERT_FALSE(MatchKoids(msix0, interrupt));
ASSERT_FALSE(MatchKoids(msix1, interrupt));
ASSERT_FALSE(MatchKoids(msix2, interrupt));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().MapInterrupt(irq_cnt, &interrupt));
interrupt.reset();
irq_cnt = 2;
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_MSI, irq_cnt));
ASSERT_OK(pci().MapInterrupt(0, &interrupt));
ASSERT_FALSE(MatchKoids(legacy, interrupt));
ASSERT_TRUE(MatchKoids(msi0, interrupt));
ASSERT_FALSE(MatchKoids(msi1, interrupt));
ASSERT_FALSE(MatchKoids(msix0, interrupt));
ASSERT_FALSE(MatchKoids(msix1, interrupt));
ASSERT_FALSE(MatchKoids(msix2, interrupt));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().MapInterrupt(irq_cnt, &interrupt));
interrupt.reset();
ASSERT_OK(pci().MapInterrupt(1, &interrupt));
ASSERT_FALSE(MatchKoids(legacy, interrupt));
ASSERT_FALSE(MatchKoids(msi0, interrupt));
ASSERT_TRUE(MatchKoids(msi1, interrupt));
ASSERT_FALSE(MatchKoids(msix0, interrupt));
ASSERT_FALSE(MatchKoids(msix1, interrupt));
ASSERT_FALSE(MatchKoids(msix2, interrupt));
interrupt.reset();
irq_cnt = 3;
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_MSI_X, irq_cnt));
ASSERT_OK(pci().MapInterrupt(0, &interrupt));
ASSERT_FALSE(MatchKoids(legacy, interrupt));
ASSERT_FALSE(MatchKoids(msi0, interrupt));
ASSERT_FALSE(MatchKoids(msi1, interrupt));
ASSERT_TRUE(MatchKoids(msix0, interrupt));
ASSERT_FALSE(MatchKoids(msix1, interrupt));
ASSERT_FALSE(MatchKoids(msix2, interrupt));
interrupt.reset();
ASSERT_OK(pci().MapInterrupt(1, &interrupt));
ASSERT_FALSE(MatchKoids(legacy, interrupt));
ASSERT_FALSE(MatchKoids(msi0, interrupt));
ASSERT_FALSE(MatchKoids(msi1, interrupt));
ASSERT_FALSE(MatchKoids(msix0, interrupt));
ASSERT_TRUE(MatchKoids(msix1, interrupt));
ASSERT_FALSE(MatchKoids(msix2, interrupt));
interrupt.reset();
ASSERT_OK(pci().MapInterrupt(2, &interrupt));
ASSERT_FALSE(MatchKoids(legacy, interrupt));
ASSERT_FALSE(MatchKoids(msi0, interrupt));
ASSERT_FALSE(MatchKoids(msi1, interrupt));
ASSERT_FALSE(MatchKoids(msix0, interrupt));
ASSERT_FALSE(MatchKoids(msix1, interrupt));
ASSERT_TRUE(MatchKoids(msix2, interrupt));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().MapInterrupt(irq_cnt, &interrupt));
}
TEST_F(FakePciProtocolTests, VerifyAllocatedMsis) {
fake_pci().AddLegacyInterrupt();
fake_pci().AddMsiInterrupt();
fake_pci().AddMsiInterrupt();
fake_pci().AddMsixInterrupt();
zx::interrupt zero, one;
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_MSI, 2));
ASSERT_OK(pci().MapInterrupt(0, &zero));
ASSERT_OK(pci().MapInterrupt(1, &one));
// Changing to other IRQ modes should be blocked because IRQ handles are outstanding.
ASSERT_EQ(ZX_ERR_BAD_STATE, pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY, 1));
ASSERT_EQ(ZX_ERR_BAD_STATE, pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY_NOACK, 1));
ASSERT_EQ(ZX_ERR_BAD_STATE, pci().SetInterruptMode(PCI_INTERRUPT_MODE_MSI_X, 1));
zero.reset();
one.reset();
// Now transitioning should work.
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY, 1));
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_MSI_X, 1));
// Verify MSI-X works the same.
ASSERT_OK(pci().MapInterrupt(0, &zero));
ASSERT_EQ(ZX_ERR_BAD_STATE, pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY, 1));
zero.reset();
ASSERT_OK(pci().SetInterruptMode(PCI_INTERRUPT_MODE_LEGACY, 1));
}
TEST_F(FakePciProtocolTests, ConfigRW) {
auto config = fake_pci().GetConfigVmo();
// Verify the header space range. Reads can read the header [0, 63], but
// writes cannot. All IO must fit within the config space [0, 255].
uint8_t val8;
ASSERT_DEATH([&]() { pci().WriteConfig8(0, 0xFF); });
ASSERT_NO_DEATH([&]() { pci().ReadConfig8(0, &val8); });
ASSERT_DEATH([&]() { pci().WriteConfig8(PCI_CONFIG_HEADER_SIZE - 1, 0xFF); });
ASSERT_NO_DEATH([&]() { pci().ReadConfig8(PCI_CONFIG_HEADER_SIZE - 1, &val8); });
// The ensures we also verify that offset + read/write size is within bounds.
uint32_t val32;
ASSERT_DEATH([&]() { pci().WriteConfig32(PCI_BASE_CONFIG_SIZE - 2, 0xFF); });
ASSERT_DEATH([&]() { pci().ReadConfig32(PCI_BASE_CONFIG_SIZE - 2, &val32); });
for (uint16_t off = PCI_CONFIG_HEADER_SIZE; off < PCI_BASE_CONFIG_SIZE; off++) {
uint8_t val8;
pci().WriteConfig8(off, off);
pci().ReadConfig8(off, &val8);
ASSERT_EQ(off, val8);
ASSERT_OK(config->read(&val8, off, sizeof(val8)));
ASSERT_EQ(off, val8);
}
for (uint16_t off = PCI_CONFIG_HEADER_SIZE; off < PCI_BASE_CONFIG_SIZE - 1; off++) {
uint16_t val16;
pci().WriteConfig16(off, off);
pci().ReadConfig16(off, &val16);
ASSERT_EQ(off, val16);
ASSERT_OK(config->read(&val16, off, sizeof(val16)));
ASSERT_EQ(off, val16);
}
for (uint16_t off = PCI_CONFIG_HEADER_SIZE; off < PCI_BASE_CONFIG_SIZE - 3; off++) {
uint32_t val32;
pci().WriteConfig32(off, off);
pci().ReadConfig32(off, &val32);
ASSERT_EQ(off, val32);
ASSERT_OK(config->read(&val32, off, sizeof(val32)));
ASSERT_EQ(off, val32);
}
}
TEST_F(FakePciProtocolTests, GetBar) {
pci_bar_t bar{};
ASSERT_EQ(ZX_ERR_NOT_FOUND, pci().GetBar(0, &bar));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, pci().GetBar(6, &bar));
uint32_t bar_id = 3;
size_t size = 256;
ASSERT_NO_DEATH([&]() { fake_pci().CreateBar(bar_id, size, true); });
// Verify that the VMO we got back via the protocol method matches the setup
// and that the other fields are correct.
ASSERT_OK(pci().GetBar(bar_id, &bar));
zx::vmo proto(bar.result.vmo);
zx::vmo& borrowed = fake_pci().GetBar(bar_id);
ASSERT_TRUE(MatchKoids(borrowed, proto));
ASSERT_EQ(bar_id, bar.bar_id);
ASSERT_EQ(size, bar.size);
}
TEST_F(FakePciProtocolTests, BarTypes) {
size_t page_size = zx_system_get_page_size();
fake_pci().CreateBar(0, page_size, true);
fake_pci().CreateBar(1, page_size, false);
pci_bar_t bar;
ASSERT_OK(pci().GetBar(0, &bar));
ASSERT_EQ(bar.type, ZX_PCI_BAR_TYPE_MMIO);
ASSERT_OK(pci().GetBar(1, &bar));
ASSERT_EQ(bar.type, ZX_PCI_BAR_TYPE_PIO);
}
TEST_F(FakePciProtocolTests, MapMmio) {
const uint32_t bar_id = 0;
const uint64_t bar_size = 256;
fake_pci().CreateBar(bar_id, bar_size, true);
zx::vmo& borrowed = fake_pci().GetBar(bar_id);
// Ensure that our fake implementation / backend for the BAR methods still works with
// the MapMmio helper method added to device-protocol.
ddk::Pci dp_pci(fake_pci().get_protocol());
std::optional<fdf::MmioBuffer> mmio = std::nullopt;
ASSERT_OK(dp_pci.MapMmio(bar_id, ZX_CACHE_POLICY_UNCACHED_DEVICE, &mmio));
ASSERT_TRUE(MatchKoids(borrowed, *mmio->get_vmo()));
}
TEST_F(FakePciProtocolTests, Capabilities) {
// Try invalid capabilities.
ASSERT_DEATH([&]() { fake_pci().AddCapability(0, PCI_CONFIG_HEADER_SIZE, 16); });
ASSERT_DEATH([&]() {
fake_pci().AddCapability(PCI_CAPABILITY_ID_FLATTENING_PORTAL_BRIDGE + 1, PCI_CONFIG_HEADER_SIZE,
16);
});
// Try invalid locations.
ASSERT_DEATH([&]() { fake_pci().AddVendorCapability(PCI_CONFIG_HEADER_SIZE - 16, 32); });
ASSERT_DEATH([&]() { fake_pci().AddVendorCapability(PCI_BASE_CONFIG_SIZE - 16, 32); });
// Overlap tests.
ASSERT_NO_DEATH([&]() { fake_pci().AddVendorCapability(0xB0, 16); });
ASSERT_DEATH([&]() { fake_pci().AddVendorCapability(0xB0 + 8, 16); });
ASSERT_DEATH([&]() { fake_pci().AddVendorCapability(0xB0 - 8, 16); });
ASSERT_DEATH([&]() { fake_pci().AddVendorCapability(0xB0, 32); });
}
TEST_F(FakePciProtocolTests, PciGetFirstAndNextCapability) {
auto config = fake_pci().GetConfigVmo();
// The first capability should set up the capabilities pointer.
fake_pci().AddVendorCapability(0x50, 6);
uint8_t offset1 = 0;
ASSERT_OK(pci().GetFirstCapability(PCI_CAPABILITY_ID_VENDOR, &offset1));
uint8_t val;
config->read(&val, PCI_CONFIG_CAPABILITIES_PTR, sizeof(val));
ASSERT_EQ(0x50, val);
config->read(&val, offset1, sizeof(val));
ASSERT_EQ(PCI_CAPABILITY_ID_VENDOR, val);
config->read(&val, offset1 + 2, sizeof(val));
ASSERT_EQ(6, val);
// After adding the new capability we need to check that the previous next pointer was set up.
fake_pci().AddVendorCapability(0x60, 8);
config->read(&val, 0x51, sizeof(val));
ASSERT_EQ(val, 0x60);
// Can we find sequential capabilities, or different IDs?
uint8_t offset2 = 0;
ASSERT_OK(pci().GetNextCapability(PCI_CAPABILITY_ID_VENDOR, offset1, &offset2));
ASSERT_EQ(0x60, offset2);
fake_pci().AddPciExpressCapability(0x70);
fake_pci().AddVendorCapability(0xB0, 16);
ASSERT_OK(pci().GetFirstCapability(PCI_CAPABILITY_ID_PCI_EXPRESS, &offset1));
ASSERT_EQ(0x70, offset1);
ASSERT_OK(pci().GetNextCapability(PCI_CAPABILITY_ID_VENDOR, offset2, &offset1));
ASSERT_EQ(0xB0, offset1);
}