blob: 3baea136f999369c04dfa24c347bf253042a3116 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "../config.h"
#include "../device.h"
#include "fake_bus.h"
#include "fake_pciroot.h"
#include "fake_upstream_node.h"
#include <ddktl/protocol/pciroot.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <zircon/limits.h>
#include <zxtest/zxtest.h>
namespace pci {
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_; }
protected:
PciDeviceTests()
: upstream_(UpstreamNode::Type::ROOT, 0) {}
void SetUp() {
ASSERT_EQ(ZX_OK, FakePciroot::Create(0, 1, &pciroot_));
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) {
fbl::RefPtr<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().get_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, BasicCapabilityTest) {
// This is the configuration space dump of a virtio-input device. It should
// contain an MSIX capability along with 5 Vendor capabilities.
uint8_t virtio_input[] = {
0xf4, 0x1a, 0x52, 0x10, 0x07, 0x01, 0x10, 0x00, 0x01, 0x00, 0x00, 0x09,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xbf, 0xfe,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x00, 0xfe,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x1a, 0x00, 0x11,
0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0a, 0x01, 0x00, 0x00, 0x09, 0x00, 0x10, 0x01, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x09, 0x40, 0x10, 0x03,
0x04, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
0x09, 0x50, 0x10, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x09, 0x60, 0x14, 0x02, 0x04, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x09, 0x70, 0x14, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x84, 0x01, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00};
static_assert(sizeof(virtio_input) == 256);
fbl::RefPtr<Config> cfg;
// Copy the config dump into a device entry in the ecam.
memcpy(pciroot_proto().ecam().get(default_bdf()).config, virtio_input, sizeof(virtio_input));
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().get_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().begin();
EXPECT_EQ(static_cast<Capability::Id>(cap_iter->id()), Capability::Id::kMsiX);
ASSERT_TRUE(cap_iter != dev.capabilities().end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
ASSERT_TRUE(cap_iter != dev.capabilities().end());
EXPECT_EQ(static_cast<Capability::Id>((++cap_iter)->id()), Capability::Id::kVendor);
EXPECT_TRUE(++cap_iter == dev.capabilities().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;
fbl::RefPtr<Config> cfg;
ASSERT_OK(MmioConfig::Create(default_bdf(), &pciroot_proto().ecam().get_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().device_list().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().
fbl::RefPtr<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().get_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().device_list().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().
fbl::RefPtr<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().get_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().device_list().is_empty());
}
} // namespace pci