blob: d24d0ab98e9e9882f388d663d21f0836e98f8245 [file] [log] [blame]
// Copyright 2019 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 <lib/fake_ddk/fake_ddk.h>
#include <zircon/limits.h>
#include <ddktl/protocol/pciroot.h>
#include <fbl/ref_ptr.h>
#include <zxtest/zxtest.h>
#include "../../config.h"
#include "../../device.h"
#include "../fakes/fake_bus.h"
#include "../fakes/fake_pciroot.h"
#include "../fakes/fake_upstream_node.h"
#include "../fakes/test_device.h"
namespace pci {
// Creates a test device with a given device config using test defaults)
class PciDeviceTests : public zxtest::Test {
public:
FakePciroot& pciroot_proto() { return *pciroot_; }
ddk::PcirootProtocolClient& pciroot_client() { return *client_; }
FakeBus& bus() { return bus_; }
FakeUpstreamNode& upstream() { return upstream_; }
const pci_bdf_t default_bdf() { return default_bdf_; }
Device& CreateTestDevice(const uint8_t* cfg_buf, size_t cfg_size) {
// Copy the config dump into a device entry in the ecam.
memcpy(pciroot_proto().ecam().get(default_bdf()).config, cfg_buf, cfg_size);
// Create the config object for the device.
std::unique_ptr<Config> cfg;
EXPECT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().mmio(), 0, 1, &cfg));
// Create and initialize the fake device.
EXPECT_OK(Device::Create(fake_ddk::kFakeParent, std::move(cfg), &upstream(), &bus()));
return bus().get_device(default_bdf());
}
protected:
PciDeviceTests() : upstream_(UpstreamNode::Type::ROOT, 0) {}
void SetUp() {
pciroot_.reset(new FakePciroot(0, 1));
client_ = std::make_unique<ddk::PcirootProtocolClient>(pciroot_->proto());
}
void TearDown() {
upstream_.DisableDownstream();
upstream_.UnplugDownstream();
}
private:
std::unique_ptr<FakePciroot> pciroot_;
std::unique_ptr<ddk::PcirootProtocolClient> client_;
FakeBus bus_;
FakeUpstreamNode upstream_;
const pci_bdf_t default_bdf_ = {1, 2, 3};
};
TEST_F(PciDeviceTests, CreationTest) {
std::unique_ptr<Config> cfg;
// This test creates a device, goes through its init sequence, links it into
// the toplogy, and then has it linger. It will be cleaned up by TearDown()
// releasing all objects of upstream(). If creation succeeds here and no
// asserts happen following the test it means the fakes are built properly
// enough and the basic interface is fulfilled.
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().mmio(), 0, 1, &cfg));
ASSERT_OK(Device::Create(fake_ddk::kFakeParent, std::move(cfg), &upstream(), &bus()));
// Verify the created device's BDF.
auto& dev = bus().get_device(default_bdf());
ASSERT_EQ(default_bdf().bus_id, dev.bus_id());
ASSERT_EQ(default_bdf().device_id, dev.dev_id());
ASSERT_EQ(default_bdf().function_id, dev.func_id());
}
// Test a normal capability chain
TEST_F(PciDeviceTests, StdCapabilityTest) {
std::unique_ptr<Config> cfg;
// Copy the config dump into a device entry in the ecam.
memcpy(pciroot_proto().ecam().get(default_bdf()).config, kFakeVirtioInputDeviceConfig.data(),
kFakeVirtioInputDeviceConfig.max_size());
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().mmio(), 0, 1, &cfg));
ASSERT_OK(Device::Create(fake_ddk::kFakeParent, std::move(cfg), &upstream(), &bus()));
auto& dev = bus().get_device(default_bdf());
// Ensure our faked Keyboard exists.
ASSERT_EQ(0x1af4, dev.vendor_id());
ASSERT_EQ(0x1052, dev.device_id());
// Since this is a dump of an emulated device we know it has a single MSI-X
// capability followed by five Vendor capabilities.
auto cap_iter = dev.capabilities().list.begin();
EXPECT_EQ(static_cast<Capability::Id>(cap_iter->id()), Capability::Id::kMsiX);
ASSERT_TRUE(cap_iter != dev.capabilities().list.end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().list.end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().list.end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().list.end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().list.end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
EXPECT_TRUE(++cap_iter == dev.capabilities().list.end());
}
// Test an extended capability chain
TEST_F(PciDeviceTests, ExtendedCapabilityTest) {
auto& dev = CreateTestDevice(kFakeQuadroDeviceConfig.data(), kFakeQuadroDeviceConfig.max_size());
ASSERT_EQ(false, CURRENT_TEST_HAS_FAILURES());
// Since this is a dump of an emulated device we that it should have:
//
// Capabilities: [100] Virtual Channel
// Capabilities: [250] Latency Tolerance Reporting
// Capabilities: [258] L1 PM Substates
// Capabilities: [128] Power Budgeting
// Capabilities: [600] Vendor Specific Information
auto cap_iter = dev.capabilities().ext_list.begin();
ASSERT_TRUE(cap_iter.IsValid());
EXPECT_EQ(static_cast<ExtCapability::Id>(cap_iter->id()),
ExtCapability::Id::kVirtualChannelNoMFVC);
ASSERT_TRUE(cap_iter != dev.capabilities().ext_list.end());
EXPECT_EQ(static_cast<ExtCapability::Id>((++cap_iter)->id()),
ExtCapability::Id::kLatencyToleranceReporting);
ASSERT_TRUE(cap_iter != dev.capabilities().ext_list.end());
EXPECT_EQ(static_cast<ExtCapability::Id>((++cap_iter)->id()), ExtCapability::Id::kL1PMSubstates);
ASSERT_TRUE(cap_iter != dev.capabilities().ext_list.end());
EXPECT_EQ(static_cast<ExtCapability::Id>((++cap_iter)->id()), ExtCapability::Id::kPowerBudgeting);
ASSERT_TRUE(cap_iter != dev.capabilities().ext_list.end());
EXPECT_EQ(static_cast<ExtCapability::Id>((++cap_iter)->id()), ExtCapability::Id::kVendor);
EXPECT_TRUE(++cap_iter == dev.capabilities().ext_list.end());
}
// This test checks for proper handling of capability pointers that are
// invalid by pointing to inside the config header.
TEST_F(PciDeviceTests, InvalidPtrCapabilityTest) {
auto& raw_cfg = pciroot_proto().ecam().get(default_bdf()).config;
auto& fake_dev = pciroot_proto().ecam().get(default_bdf()).device;
// Two valid locations, followed by a third capability pointing at BAR 1.
const uint8_t kCap1 = 0x80;
const uint8_t kCap2 = 0x90;
const uint8_t kInvalidCap = 0x10;
// Point to 0x80 as the first capability.
fake_dev.set_vendor_id(0x8086)
.set_device_id(0x1234)
.set_capabilities_list(1)
.set_capabilities_ptr(kCap1);
raw_cfg[kCap1] = static_cast<uint8_t>(Capability::Id::kPciPowerManagement);
raw_cfg[kCap1 + 1] = kCap2;
raw_cfg[kCap2] = static_cast<uint8_t>(Capability::Id::kMsiX);
raw_cfg[kCap2 + 1] = kInvalidCap;
std::unique_ptr<Config> cfg;
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().mmio(), 0, 1, &cfg));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE,
Device::Create(fake_ddk::kFakeParent, std::move(cfg), &upstream(), &bus()));
// Ensure no device was added.
EXPECT_TRUE(bus().devices().is_empty());
}
// This test checks for proper handling (ZX_ERR_BAD_STATE) upon
// funding a pointer cycle while parsing capabilities.
TEST_F(PciDeviceTests, PtrCycleCapabilityTest) {
// Boilerplate to get a device corresponding to the default_bdf().
std::unique_ptr<Config> cfg;
auto& raw_cfg = pciroot_proto().ecam().get(default_bdf()).config;
auto& fake_dev = pciroot_proto().ecam().get(default_bdf()).device;
// Two valid locations, followed by a third capability pointing at BAR 1.
const uint8_t kCap1 = 0x80;
const uint8_t kCap2 = 0x90;
const uint8_t kCap3 = 0xA0;
// Create a Cycle of Cap1 -> Cap2 -> Cap3 -> Cap1
fake_dev.set_vendor_id(0x8086)
.set_device_id(0x1234)
.set_capabilities_list(1)
.set_capabilities_ptr(kCap1);
auto cap_id = static_cast<uint8_t>(Capability::Id::kVendor);
raw_cfg[kCap1] = cap_id;
raw_cfg[kCap1 + 1] = kCap2;
raw_cfg[kCap2] = cap_id;
raw_cfg[kCap2 + 1] = kCap3;
raw_cfg[kCap3] = cap_id;
raw_cfg[kCap3 + 1] = kCap1;
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().mmio(), 0, 1, &cfg));
EXPECT_EQ(ZX_ERR_BAD_STATE,
Device::Create(fake_ddk::kFakeParent, std::move(cfg), &upstream(), &bus()));
// Ensure no device was added.
EXPECT_TRUE(bus().devices().is_empty());
}
// Test that we properly bail out if we see multiple of a capability
// type that only one should exist of in a system.
TEST_F(PciDeviceTests, DuplicateFixedCapabilityTest) {
// Boilerplate to get a device corresponding to the default_bdf().
std::unique_ptr<Config> cfg;
auto& raw_cfg = pciroot_proto().ecam().get(default_bdf()).config;
auto& fake_dev = pciroot_proto().ecam().get(default_bdf()).device;
// Two valid locations, followed by a third capability pointing at BAR 1.
const uint8_t kCap1 = 0x80;
const uint8_t kCap2 = 0x90;
const uint8_t kCap3 = 0xA0;
// Create a device with three capabilities, two of which are kPciExpress
fake_dev.set_vendor_id(0x8086)
.set_device_id(0x1234)
.set_capabilities_list(1)
.set_capabilities_ptr(kCap1);
auto pcie_id = static_cast<uint8_t>(Capability::Id::kPciExpress);
auto null_id = static_cast<uint8_t>(Capability::Id::kNull);
raw_cfg[kCap1] = pcie_id;
raw_cfg[kCap1 + 1] = kCap2;
raw_cfg[kCap2] = null_id;
raw_cfg[kCap2 + 1] = kCap3;
raw_cfg[kCap3] = pcie_id;
raw_cfg[kCap3 + 1] = 0;
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().mmio(), 0, 1, &cfg));
EXPECT_EQ(ZX_ERR_BAD_STATE,
Device::Create(fake_ddk::kFakeParent, std::move(cfg), &upstream(), &bus()));
// Ensure no device was added.
EXPECT_TRUE(bus().devices().is_empty());
}
// Ensure we parse MSI capabilities properly in the Quadro device.
// lspci output: Capabilities: [68] MSI: Enable+ Count=1/4 Maskable- 64bit+
TEST_F(PciDeviceTests, MsiCapabilityTest) {
auto& dev = CreateTestDevice(kFakeQuadroDeviceConfig.data(), kFakeQuadroDeviceConfig.max_size());
ASSERT_EQ(false, CURRENT_TEST_HAS_FAILURES());
ASSERT_NE(nullptr, dev.capabilities().msi);
auto& msi = *dev.capabilities().msi;
EXPECT_EQ(0x68, msi.base());
EXPECT_EQ(static_cast<uint8_t>(Capability::Id::kMsi), msi.id());
EXPECT_EQ(true, msi.is_64bit());
EXPECT_EQ(4, msi.vectors_avail());
EXPECT_EQ(false, msi.supports_pvm());
MsiControlReg ctrl = {.value = dev.config()->Read(msi.ctrl())};
EXPECT_EQ(0, ctrl.enable());
}
// Ensure we parse MSIX capabilities properly in the Virtio-input device.
TEST_F(PciDeviceTests, MsixCapabilityTest) {
auto& dev = CreateTestDevice(kFakeVirtioInputDeviceConfig.data(),
kFakeVirtioInputDeviceConfig.max_size());
ASSERT_EQ(false, CURRENT_TEST_HAS_FAILURES());
ASSERT_NE(nullptr, dev.capabilities().msix);
auto& msix = *dev.capabilities().msix;
EXPECT_EQ(0x98, msix.base());
EXPECT_EQ(static_cast<uint8_t>(Capability::Id::kMsiX), msix.id());
EXPECT_EQ(1, msix.table_bar());
EXPECT_EQ(0, msix.table_offset());
EXPECT_EQ(2, msix.table_size());
EXPECT_EQ(1, msix.pba_bar());
EXPECT_EQ(0x800, msix.pba_offset());
MsixControlReg ctrl = {.value = dev.config()->Read(msix.ctrl())};
EXPECT_EQ(0, ctrl.enable());
}
} // namespace pci