text: // 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
  