| /* |
| * SPDX-FileCopyrightText: Copyright OpenBMC Authors |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "MctpMockTestBase.hpp" |
| #include "MessagePackUnpackUtils.hpp" |
| #include "MockMctpRequester.hpp" |
| #include "NvidiaGpuMctpVdm.hpp" |
| #include "NvidiaPcieInterface.hpp" |
| #include "OcpMctpVdm.hpp" |
| #include "TestUtils.hpp" |
| |
| #include <sdbusplus/exception.hpp> |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <memory> |
| #include <span> |
| #include <string> |
| #include <system_error> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| namespace |
| { |
| |
| // QueryScalarGroupTelemetryV2 response: CommonResponse(11) + N*uint32_t |
| // Telemetry indices: [0]=GenInUse [1]=LanesInUse [2]=unused [3]=GenSupported |
| // [4]=MaxLanes |
| std::vector<uint8_t> buildScalarTelemetryResponse( |
| const std::vector<uint32_t>& values) |
| { |
| return test_utils::buildPcieScalarTelemetryResponse(values); |
| } |
| |
| class NvidiaPcieInterfaceTest : public MctpMockTestBase |
| { |
| protected: |
| static std::shared_ptr<NvidiaPcieInterface> createPcieInterface( |
| const std::string& name = "PCIE_IF", |
| uint8_t eid = test_utils::defaultEid) |
| { |
| const std::string path = "/test/pcie/" + name; |
| return std::make_shared<NvidiaPcieInterface>( |
| bus(), requester(), name, path, eid, objects(), |
| gpu::DeviceIdentification::DEVICE_PCIE); |
| } |
| |
| static std::string pciePath(const std::string& name) |
| { |
| return "/xyz/openbmc_project/inventory/" + name; |
| } |
| }; |
| |
| // Constructor — D-Bus interface creation |
| |
| TEST_F(NvidiaPcieInterfaceTest, ConstructorCreatesPcieDeviceInterface) |
| { |
| const std::string name = "pcie_ctor"; |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface(name); |
| const std::string path = pciePath(name); |
| |
| EXPECT_EQ( |
| getProperty<std::string>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "GenerationInUse"), |
| "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown"); |
| EXPECT_EQ(getProperty<size_t>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "LanesInUse"), |
| std::numeric_limits<size_t>::max()); |
| EXPECT_EQ( |
| getProperty<std::string>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "GenerationSupported"), |
| "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Unknown"); |
| EXPECT_EQ(getProperty<size_t>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "MaxLanes"), |
| static_cast<size_t>(0)); |
| } |
| |
| // Update — successful response updates D-Bus properties |
| |
| TEST_F(NvidiaPcieInterfaceTest, UpdateSuccessUpdatesGeneration) |
| { |
| // GenInUse=5(Gen5), LanesInUse=5(x16), unused=0, GenSupported=5, MaxLanes=5 |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith( |
| {}, buildScalarTelemetryResponse({5, 5, 0, 5, 5}))); |
| |
| const std::string name = "pcie_update_gen"; |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface(name); |
| pcieIf->update(); |
| const std::string path = pciePath(name); |
| |
| EXPECT_EQ(getProperty<std::string>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "GenerationInUse"), |
| "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen5"); |
| EXPECT_EQ(getProperty<size_t>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "LanesInUse"), |
| static_cast<size_t>(16)); |
| EXPECT_EQ(getProperty<std::string>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "GenerationSupported"), |
| "xyz.openbmc_project.Inventory.Item.PCIeSlot.Generations.Gen5"); |
| EXPECT_EQ(getProperty<size_t>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "MaxLanes"), |
| static_cast<size_t>(16)); |
| } |
| |
| // Update — request encoding verification |
| |
| TEST_F(NvidiaPcieInterfaceTest, UpdateVerifiesRequestEncoding) |
| { |
| // Copy the request bytes before completing the call: the reqMsg span is |
| // a view into caller-owned memory, valid only during the call. |
| std::vector<uint8_t> lastRequest; |
| const std::vector<uint8_t> response; |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce([&](uint8_t /*eid*/, std::span<const uint8_t> reqMsg, |
| auto callback) { |
| lastRequest.assign(reqMsg.begin(), reqMsg.end()); |
| callback(std::error_code{}, response); |
| }); |
| |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface("pcie_req_enc"); |
| pcieIf->update(); |
| |
| ASSERT_FALSE(lastRequest.empty()); |
| |
| UnpackBuffer unpack(lastRequest); |
| ocp::accelerator_management::MessageType ocpMsgType{}; |
| uint8_t instanceId = 0; |
| uint8_t msgType = 0; |
| const int rc = ocp::accelerator_management::unpackHeader( |
| unpack, gpu::nvidiaPciVendorId, ocpMsgType, instanceId, msgType); |
| EXPECT_EQ(rc, 0); |
| EXPECT_EQ(ocpMsgType, ocp::accelerator_management::MessageType::REQUEST); |
| EXPECT_EQ(msgType, static_cast<uint8_t>(gpu::MessageType::PCIE_LINK)); |
| |
| uint8_t command = 0; |
| unpack.unpack(command); |
| EXPECT_EQ(command, static_cast<uint8_t>( |
| gpu::PcieLinkCommands::QueryScalarGroupTelemetryV2)); |
| |
| EXPECT_EQ(unpack.getError(), 0); |
| } |
| |
| // Update — sends request |
| |
| TEST_F(NvidiaPcieInterfaceTest, UpdateSendsRequest) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(mock_mctp::respondWith({}, {})); |
| |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface("pcie_sends"); |
| pcieIf->update(); |
| } |
| |
| TEST_F(NvidiaPcieInterfaceTest, UpdateRequestContainsCorrectEid) |
| { |
| constexpr uint8_t testEid = 42; |
| EXPECT_CALL(mctpMock, sendRecvMsg(testEid, testing::_, testing::_)) |
| .WillOnce(mock_mctp::respondWith({}, {})); |
| |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface("pcie_eid_test", testEid); |
| pcieIf->update(); |
| } |
| |
| // Error handling |
| |
| TEST_F(NvidiaPcieInterfaceTest, UpdateMctpTransportErrorNoCrash) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith( |
| std::make_error_code(std::errc::timed_out), {})); |
| |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface("pcie_mctp_err"); |
| EXPECT_NO_THROW(pcieIf->update()); |
| } |
| |
| TEST_F(NvidiaPcieInterfaceTest, UpdateEmptyBufferNoCrash) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg).WillOnce(mock_mctp::respondWith({}, {})); |
| |
| const std::shared_ptr<NvidiaPcieInterface> pcieIf = |
| createPcieInterface("pcie_empty"); |
| EXPECT_NO_THROW(pcieIf->update()); |
| } |
| |
| // Destructor |
| |
| TEST_F(NvidiaPcieInterfaceTest, DestructorRemovesInterfaces) |
| { |
| const std::string name = "pcie_if_dtor"; |
| const std::string path = pciePath(name); |
| { |
| const std::shared_ptr<NvidiaPcieInterface> pcieInterface = |
| createPcieInterface(name); |
| ASSERT_NE(pcieInterface, nullptr); |
| EXPECT_NO_THROW(getProperty<size_t>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "LanesInUse")); |
| } |
| drainPendingAsync(); |
| |
| EXPECT_THROW(getProperty<size_t>( |
| path, "xyz.openbmc_project.Inventory.Item.PCIeDevice", |
| "LanesInUse"), |
| sdbusplus::exception_t); |
| } |
| |
| } // namespace |