| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "google3/host_commands.h" |
| |
| #include "firmware_spi_updater.hpp" |
| #include "host_command_mock.hpp" |
| #include "message_util.hpp" |
| |
| #include <xyz/openbmc_project/Control/Hoth/error.hpp> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include <cstring> |
| |
| using google::hoth::internal::FirmwareSpiUpdater; |
| using google::hoth::internal::HostCommandMock; |
| using google::hoth::internal::ResetMode; |
| using sdbusplus::error::xyz::openbmc_project::control::hoth::FirmwareFailure; |
| |
| using ::testing::_; |
| using ::testing::Return; |
| |
| class FirmwareSpiUpdaterTest : public ::testing::Test |
| { |
| protected: |
| // Host Command interface handle |
| HostCommandMock hostCmd; |
| |
| std::vector<uint8_t> hothGetRegionResponse = { |
| 0x03, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, // Header |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, |
| 0x01 // Data |
| }; |
| |
| std::vector<uint8_t> hothSpiEmptyResponse = { |
| 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Header |
| }; |
| |
| std::vector<uint8_t> hothSpiReadResponse = { |
| 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RspHeader |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Padding |
| 0x01, 0x01, 0x01, 0x01, 0x01 // Data |
| }; |
| |
| std::vector<uint8_t> hothSpiEnter4BResponse = { |
| 0x03, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xd2, 0x4e, 0x00, |
| 0xe8, 0xcf, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| |
| std::vector<uint8_t> hothSpiExit4BResponse = { |
| 0x03, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xd2, 0x4e, 0x00, |
| 0xe8, 0xcf, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| |
| std::vector<uint8_t> hothGetSpsPassthoughEnabledResponse = { |
| 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, // Header |
| 0x01, // Enabled |
| 0x00, 0x00, 0x00, // Padding |
| }; |
| |
| std::vector<uint8_t> hothGetSpsPassthoughDisabledResponse = { |
| 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, // Header |
| 0x00, // Enabled |
| 0x00, 0x00, 0x00, // Padding |
| }; |
| |
| std::vector<uint8_t> hothCommandFailedResponse = { |
| 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Header |
| }; |
| }; |
| |
| TEST_F(FirmwareSpiUpdaterTest, SuccessEmptyData) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Return(hothGetRegionResponse)) |
| .WillOnce(Return(hothSpiEnter4BResponse)); |
| FirmwareSpiUpdater updater(&hostCmd); |
| ASSERT_NO_THROW(updater.update({})); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SuccessEmptyData3BytesAddress) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Return(hothGetRegionResponse)) |
| .WillOnce(Return(hothSpiEnter4BResponse)); |
| FirmwareSpiUpdater updater(&hostCmd, 3, ResetMode::never); |
| ASSERT_NO_THROW(updater.update({})); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SuccessNonEmptyData) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Return(hothGetRegionResponse)) |
| .WillOnce(Return(hothSpiEnter4BResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiReadResponse)); |
| FirmwareSpiUpdater updater(&hostCmd); |
| std::vector<uint8_t> firmwareData(5, 0x01); |
| ASSERT_NO_THROW(updater.update(firmwareData)); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, InvalidGetRegionResponse) |
| { |
| FirmwareSpiUpdater updater(&hostCmd); |
| // Invalid GetRegionResponse in Hoth response |
| hothGetRegionResponse.pop_back(); |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Return(hothGetRegionResponse)); |
| EXPECT_THROW(updater.update({}), std::runtime_error); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, GetRegionResponseFlagOff) |
| { |
| FirmwareSpiUpdater updater(&hostCmd); |
| // Flag off |
| hothGetRegionResponse.back() = 0x00; |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Return(hothGetRegionResponse)); |
| EXPECT_THROW(updater.update({}), FirmwareFailure); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SuccessSpiWriteNonEmptyData) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Return(hothSpiEnter4BResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiReadResponse)); |
| FirmwareSpiUpdater updater(&hostCmd); |
| std::vector<uint8_t> firmwareData(5, 0x01); |
| ASSERT_NO_THROW(updater.spiWrite(0, firmwareData)); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SpiWriteSpsPassthroughDisabled) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_GET_SPS_PASSTHROUGH_STATUS); |
| return hothGetSpsPassthoughDisabledResponse; |
| }) |
| .WillOnce(Return(hothSpiEnter4BResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiReadResponse)); |
| FirmwareSpiUpdater updater(&hostCmd, /* addressSize = */ 4, ResetMode::needed); |
| std::vector<uint8_t> firmwareData(5, 0x01); |
| ASSERT_NO_THROW(updater.spiWrite(0, firmwareData)); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SpiWriteSpsPassthroughEnabled) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_GET_SPS_PASSTHROUGH_STATUS); |
| return hothGetSpsPassthoughEnabledResponse; |
| }) |
| .WillOnce([this](uint16_t cmd, auto&&, const void* requestPtr, size_t requestSize) { |
| ec_request_reset_target request = {}; |
| EXPECT_EQ(requestSize, sizeof(request)); |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_RESET_TARGET); |
| std::memcpy(&request, requestPtr, std::min(requestSize, sizeof(request))); |
| EXPECT_EQ(request.reset_option, EC_TARGET_RESET_OPTION_SET); |
| return hothSpiEmptyResponse; |
| }) |
| .WillOnce(Return(hothSpiEnter4BResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiReadResponse)) |
| .WillOnce([this](uint16_t cmd, auto&&, const void* requestPtr, size_t requestSize) { |
| ec_request_reset_target request = {}; |
| EXPECT_EQ(requestSize, sizeof(request)); |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_RESET_TARGET); |
| std::memcpy(&request, requestPtr, std::min(requestSize, sizeof(request))); |
| EXPECT_EQ(request.reset_option, EC_TARGET_RESET_OPTION_RELEASE); |
| return hothSpiEmptyResponse; |
| }); |
| FirmwareSpiUpdater updater(&hostCmd, /* addressSize = */ 4, ResetMode::needed); |
| std::vector<uint8_t> firmwareData(5, 0x01); |
| ASSERT_NO_THROW(updater.spiWrite(0, firmwareData)); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SpiWriteActivelyDisablePassthrough) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_GET_SPS_PASSTHROUGH_STATUS); |
| return hothGetSpsPassthoughEnabledResponse; |
| }) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_SPS_PASSTHROUGH_DISABLE); |
| return hothSpiEmptyResponse; |
| }) |
| .WillOnce(Return(hothSpiEnter4BResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiReadResponse)) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_SPS_PASSTHROUGH_ENABLE); |
| return hothSpiEmptyResponse; |
| }); |
| FirmwareSpiUpdater updater(&hostCmd, /* addressSize = */ 4, ResetMode::needed_active); |
| std::vector<uint8_t> firmwareData(5, 0x01); |
| ASSERT_NO_THROW(updater.spiWrite(0, firmwareData)); |
| } |
| |
| TEST_F(FirmwareSpiUpdaterTest, SpiWriteDisablePassthroughFailed) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_GET_SPS_PASSTHROUGH_STATUS); |
| return hothGetSpsPassthoughEnabledResponse; |
| }) |
| .WillOnce([this](uint16_t cmd, auto&&...){ |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_SPS_PASSTHROUGH_DISABLE); |
| return hothCommandFailedResponse; |
| }) |
| .WillOnce([this](uint16_t cmd, auto&&, const void* requestPtr, size_t requestSize) { |
| ec_request_reset_target request = {}; |
| EXPECT_EQ(requestSize, sizeof(request)); |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_RESET_TARGET); |
| std::memcpy(&request, requestPtr, std::min(requestSize, sizeof(request))); |
| EXPECT_EQ(request.reset_option, EC_TARGET_RESET_OPTION_SET); |
| return hothSpiEmptyResponse; |
| }) |
| .WillOnce(Return(hothSpiEnter4BResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiEmptyResponse)) |
| .WillOnce(Return(hothSpiReadResponse)) |
| .WillOnce([this](uint16_t cmd, auto&&, const void* requestPtr, size_t requestSize) { |
| ec_request_reset_target request = {}; |
| EXPECT_EQ(requestSize, sizeof(request)); |
| EXPECT_EQ(cmd, EC_CMD_BOARD_SPECIFIC_BASE | EC_PRV_CMD_HOTH_RESET_TARGET); |
| std::memcpy(&request, requestPtr, std::min(requestSize, sizeof(request))); |
| EXPECT_EQ(request.reset_option, EC_TARGET_RESET_OPTION_RELEASE); |
| return hothSpiEmptyResponse; |
| }); |
| FirmwareSpiUpdater updater(&hostCmd, /* addressSize = */ 4, ResetMode::needed_active); |
| std::vector<uint8_t> firmwareData(5, 0x01); |
| ASSERT_NO_THROW(updater.spiWrite(0, firmwareData)); |
| } |