| /* |
| * SPDX-FileCopyrightText: Copyright OpenBMC Authors |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "MctpMockTestBase.hpp" |
| #include "MessagePackUnpackUtils.hpp" |
| #include "MockMctpRequester.hpp" |
| #include "NvidiaGpuMctpVdm.hpp" |
| #include "NvidiaGpuMemoryClockFrequency.hpp" |
| #include "OcpMctpVdm.hpp" |
| #include "TestUtils.hpp" |
| |
| #include <sdbusplus/asio/object_server.hpp> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <span> |
| #include <string> |
| #include <system_error> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| namespace |
| { |
| |
| const std::string dramIface = "xyz.openbmc_project.Inventory.Item.Dimm"; |
| |
| std::vector<uint8_t> buildClockFreqResponse(uint32_t mhz) |
| { |
| return test_utils::buildPlatformEnvSuccessResponse( |
| gpu::PlatformEnvironmentalCommands::GET_CURRENT_CLOCK_FREQUENCY, mhz); |
| } |
| |
| std::vector<uint8_t> buildErrorResponse() |
| { |
| return test_utils::buildPlatformEnvErrorResponse( |
| gpu::PlatformEnvironmentalCommands::GET_CURRENT_CLOCK_FREQUENCY, |
| static_cast<uint8_t>( |
| ocp::accelerator_management::CompletionCode::ERROR), |
| 0x1234); |
| } |
| |
| class NvidiaGpuMemoryClockFrequencyTest : public MctpMockTestBase |
| { |
| protected: |
| static std::shared_ptr<sdbusplus::asio::dbus_interface> createDramInterface( |
| const std::string& name) |
| { |
| const std::string path = |
| "/xyz/openbmc_project/inventory/" + name + "_DRAM_0"; |
| auto iface = objects().add_interface(path, dramIface); |
| iface->register_property("MemoryConfiguredSpeedInMhz", uint16_t{0}); |
| iface->initialize(); |
| return iface; |
| } |
| |
| static std::shared_ptr<NvidiaGpuMemoryClockFrequency> createMemFreq( |
| const std::string& name = "GPU_MEM", |
| uint8_t eid = test_utils::defaultEid) |
| { |
| auto iface = createDramInterface(name); |
| return std::make_shared<NvidiaGpuMemoryClockFrequency>( |
| requester(), name, eid, iface); |
| } |
| |
| static std::string dramPath(const std::string& name) |
| { |
| return "/xyz/openbmc_project/inventory/" + name + "_DRAM_0"; |
| } |
| }; |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, ConstructorDoesNotCrash) |
| { |
| auto metric = createMemFreq("mem_ctor"); |
| ASSERT_NE(metric, nullptr); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateSuccessSetsMemorySpeed) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith({}, buildClockFreqResponse(2619))); |
| |
| auto metric = createMemFreq("mem_succ"); |
| metric->update(); |
| |
| EXPECT_EQ(getProperty<uint16_t>(dramPath("mem_succ"), dramIface, |
| "MemoryConfiguredSpeedInMhz"), |
| 2619); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateTruncatesTo16Bits) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith({}, buildClockFreqResponse(65537))); |
| |
| auto metric = createMemFreq("mem_trunc"); |
| metric->update(); |
| |
| EXPECT_EQ(getProperty<uint16_t>(dramPath("mem_trunc"), dramIface, |
| "MemoryConfiguredSpeedInMhz"), |
| 1); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateSendsRequest) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .Times(testing::AtLeast(1)) |
| .WillRepeatedly(mock_mctp::respondWith({}, {})); |
| |
| auto metric = createMemFreq("mem_sends"); |
| metric->update(); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateRequestContainsCorrectEid) |
| { |
| constexpr uint8_t testEid = 42; |
| EXPECT_CALL(mctpMock, sendRecvMsg(testEid, testing::_, testing::_)) |
| .WillOnce(mock_mctp::respondWith({}, {})); |
| |
| auto metric = createMemFreq("mem_eid", testEid); |
| metric->update(); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, 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; |
| 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{}, std::span<const uint8_t>{}); |
| }); |
| |
| auto metric = createMemFreq("mem_enc"); |
| metric->update(); |
| |
| ASSERT_FALSE(lastRequest.empty()); |
| |
| UnpackBuffer unpack(lastRequest); |
| ocp::accelerator_management::MessageType ocpMsgType{}; |
| uint8_t instanceId = 0; |
| uint8_t msgType = 0; |
| EXPECT_EQ(ocp::accelerator_management::unpackHeader( |
| unpack, gpu::nvidiaPciVendorId, ocpMsgType, instanceId, |
| msgType), |
| 0); |
| |
| uint8_t command = 0; |
| uint8_t dataSize = 0; |
| uint8_t clockType = 0; |
| unpack.unpack(command); |
| unpack.unpack(dataSize); |
| unpack.unpack(clockType); |
| EXPECT_EQ( |
| command, |
| static_cast<uint8_t>( |
| gpu::PlatformEnvironmentalCommands::GET_CURRENT_CLOCK_FREQUENCY)); |
| EXPECT_EQ(clockType, static_cast<uint8_t>(gpu::ClockType::MEMORY_CLOCK)); |
| EXPECT_EQ(unpack.getError(), 0); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateDecodeErrorNoCrash) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith({}, buildErrorResponse())); |
| |
| auto metric = createMemFreq("mem_dec_err"); |
| EXPECT_NO_THROW(metric->update()); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateEmptyBufferNoCrash) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg).WillOnce(mock_mctp::respondWith({}, {})); |
| |
| auto metric = createMemFreq("mem_empty"); |
| EXPECT_NO_THROW(metric->update()); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateTinyBufferNoCrash) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith({}, {0x00, 0x01})); |
| |
| auto metric = createMemFreq("mem_tiny"); |
| EXPECT_NO_THROW(metric->update()); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, UpdateMctpTransportError) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith( |
| std::make_error_code(std::errc::timed_out), {})); |
| |
| auto metric = createMemFreq("mem_mctp_err_val"); |
| metric->update(); |
| |
| // A transport error must leave the speed at its default (0). |
| EXPECT_EQ(getProperty<uint16_t>(dramPath("mem_mctp_err_val"), dramIface, |
| "MemoryConfiguredSpeedInMhz"), |
| 0); |
| } |
| |
| TEST_F(NvidiaGpuMemoryClockFrequencyTest, |
| UpdateSuccessThenErrorKeepsPreviousValue) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith({}, buildClockFreqResponse(2619))) |
| .WillOnce(mock_mctp::respondWith({}, buildErrorResponse())); |
| |
| auto metric = createMemFreq("mem_keep"); |
| |
| metric->update(); |
| EXPECT_EQ(getProperty<uint16_t>(dramPath("mem_keep"), dramIface, |
| "MemoryConfiguredSpeedInMhz"), |
| 2619); |
| |
| // A subsequent error response must not overwrite the last good value. |
| metric->update(); |
| EXPECT_EQ(getProperty<uint16_t>(dramPath("mem_keep"), dramIface, |
| "MemoryConfiguredSpeedInMhz"), |
| 2619); |
| } |
| |
| } // namespace |