pseq: Add pgood fault detection to Rail class
Add power good (pgood) fault detection to the Rail class in the
phosphor-power-sequencer application.
Implement the checking defined in the JSON configuration file:
* Check for fault bits set in STATUS_VOUT
* Check for a GPIO with the wrong value
* Check if the output voltage (READ_VOUT) is below the undervoltage
limit (VOUT_UV_FAULT_LIMIT)
If a pgood fault is detected, capture the relevant debug information
such as the rail name and STATUS_WORD value.
Tested:
* Added gtests for the new code
* Ran all gtests and verified they passed
Change-Id: I09c3ed6c504fe907a7854a4ac462a2bc4a8b806f
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/src/meson.build b/phosphor-power-sequencer/src/meson.build
index c1fa67c..65146ad 100644
--- a/phosphor-power-sequencer/src/meson.build
+++ b/phosphor-power-sequencer/src/meson.build
@@ -6,6 +6,7 @@
phosphor_power_sequencer_library = static_library(
'phosphor-power-sequencer',
'config_file_parser.cpp',
+ 'rail.cpp',
'services.cpp',
implicit_include_directories: false,
dependencies: [
diff --git a/phosphor-power-sequencer/src/power_sequencer_device.hpp b/phosphor-power-sequencer/src/power_sequencer_device.hpp
index e7ec274..26e7c6a 100644
--- a/phosphor-power-sequencer/src/power_sequencer_device.hpp
+++ b/phosphor-power-sequencer/src/power_sequencer_device.hpp
@@ -19,7 +19,6 @@
#include <cstdint>
#include <map>
-#include <stdexcept>
#include <string>
#include <vector>
@@ -122,20 +121,6 @@
virtual double getVoutUVFaultLimit(uint8_t page) = 0;
/**
- * Returns the value of the PMBus VOUT_OV_FAULT_LIMIT command for the
- * specified PMBus page.
- *
- * The returned value is in Volts.
- *
- * Throws an exception if the value could not be obtained or the device does
- * not support the VOUT_OV_FAULT_LIMIT command.
- *
- * @param page PMBus page
- * @return VOUT_OV_FAULT_LIMIT value in volts
- */
- virtual double getVoutOVFaultLimit(uint8_t page) = 0;
-
- /**
* Returns whether a pgood fault has occurred on one of the rails being
* monitored by this device.
*
diff --git a/phosphor-power-sequencer/src/rail.cpp b/phosphor-power-sequencer/src/rail.cpp
new file mode 100644
index 0000000..7355b9a
--- /dev/null
+++ b/phosphor-power-sequencer/src/rail.cpp
@@ -0,0 +1,284 @@
+/**
+ * Copyright © 2024 IBM Corporation
+ *
+ * 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 "rail.hpp"
+
+#include "pmbus.hpp"
+
+#include <exception>
+#include <format>
+
+namespace phosphor::power::sequencer
+{
+namespace status_vout = phosphor::pmbus::status_vout;
+
+bool Rail::isPresent(Services& services)
+{
+ // Initially assume rail is present
+ bool present{true};
+
+ // If presence data member contains an inventory path to check
+ if (presence)
+ {
+ const std::string& inventoryPath = *presence;
+ try
+ {
+ present = services.isPresent(inventoryPath);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to determine presence of rail {} using inventory path {}: {}",
+ name, inventoryPath, e.what())};
+ }
+ }
+
+ return present;
+}
+
+uint16_t Rail::getStatusWord(PowerSequencerDevice& device)
+{
+ uint16_t value{0};
+ try
+ {
+ verifyHasPage();
+ value = device.getStatusWord(*page);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{
+ std::format("Unable to read STATUS_WORD value for rail {}: {}",
+ name, e.what())};
+ }
+ return value;
+}
+
+uint8_t Rail::getStatusVout(PowerSequencerDevice& device)
+{
+ uint8_t value{0};
+ try
+ {
+ verifyHasPage();
+ value = device.getStatusVout(*page);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{
+ std::format("Unable to read STATUS_VOUT value for rail {}: {}",
+ name, e.what())};
+ }
+ return value;
+}
+
+double Rail::getReadVout(PowerSequencerDevice& device)
+{
+ double value{0.0};
+ try
+ {
+ verifyHasPage();
+ value = device.getReadVout(*page);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read READ_VOUT value for rail {}: {}", name, e.what())};
+ }
+ return value;
+}
+
+double Rail::getVoutUVFaultLimit(PowerSequencerDevice& device)
+{
+ double value{0.0};
+ try
+ {
+ verifyHasPage();
+ value = device.getVoutUVFaultLimit(*page);
+ }
+ catch (const std::exception& e)
+ {
+ throw std::runtime_error{std::format(
+ "Unable to read VOUT_UV_FAULT_LIMIT value for rail {}: {}", name,
+ e.what())};
+ }
+ return value;
+}
+
+bool Rail::hasPgoodFault(PowerSequencerDevice& device, Services& services,
+ const std::vector<int>& gpioValues,
+ std::map<std::string, std::string>& additionalData)
+{
+ // If rail is not present, return false and don't check anything else
+ if (!isPresent(services))
+ {
+ services.logInfoMsg(std::format("Rail {} is not present", name));
+ return false;
+ }
+
+ // Check if STATUS_VOUT indicates a pgood fault occurred
+ bool hasFault = hasPgoodFaultStatusVout(device, services, additionalData);
+
+ // Check if a GPIO value indicates a pgood fault occurred
+ if (!hasFault)
+ {
+ hasFault = hasPgoodFaultGPIO(services, gpioValues, additionalData);
+ }
+
+ // Check if output voltage is below UV limit indicating pgood fault occurred
+ if (!hasFault)
+ {
+ hasFault = hasPgoodFaultOutputVoltage(device, services, additionalData);
+ }
+
+ // If fault detected, store debug data in additional data map
+ if (hasFault)
+ {
+ services.logErrorMsg(
+ std::format("Pgood fault detected in rail {}", name));
+ storePgoodFaultDebugData(device, services, additionalData);
+ }
+
+ return hasFault;
+}
+
+void Rail::verifyHasPage()
+{
+ if (!page)
+ {
+ throw std::runtime_error{
+ std::format("No PAGE number defined for rail {}", name)};
+ }
+}
+
+bool Rail::hasPgoodFaultStatusVout(
+ PowerSequencerDevice& device, Services& services,
+ std::map<std::string, std::string>& additionalData)
+{
+ bool hasFault{false};
+
+ // If we are checking the value of STATUS_VOUT for the rail
+ if (checkStatusVout)
+ {
+ // Read STATUS_VOUT value from device
+ uint8_t statusVout = getStatusVout(device);
+
+ // Check if fault (non-warning) bits are set in value
+ if (statusVout & ~status_vout::WARNING_MASK)
+ {
+ hasFault = true;
+ services.logErrorMsg(std::format(
+ "Rail {} has fault bits set in STATUS_VOUT: {:#04x}", name,
+ statusVout));
+ additionalData.emplace("STATUS_VOUT",
+ std::format("{:#04x}", statusVout));
+ }
+ else if (statusVout != 0)
+ {
+ services.logInfoMsg(std::format(
+ "Rail {} has warning bits set in STATUS_VOUT: {:#04x}", name,
+ statusVout));
+ }
+ }
+
+ return hasFault;
+}
+
+bool Rail::hasPgoodFaultGPIO(Services& services,
+ const std::vector<int>& gpioValues,
+ std::map<std::string, std::string>& additionalData)
+{
+ bool hasFault{false};
+
+ // If a GPIO is defined for checking pgood status
+ if (gpio)
+ {
+ // Get GPIO value
+ unsigned int line = gpio->line;
+ bool activeLow = gpio->activeLow;
+ if (line >= gpioValues.size())
+ {
+ throw std::runtime_error{std::format(
+ "Invalid GPIO line offset {} for rail {}: Device only has {} GPIO values",
+ line, name, gpioValues.size())};
+ }
+ int value = gpioValues[line];
+
+ // Check if value indicates pgood signal is not active
+ if ((activeLow && (value == 1)) || (!activeLow && (value == 0)))
+ {
+ hasFault = true;
+ services.logErrorMsg(std::format(
+ "Rail {} pgood GPIO line offset {} has inactive value {}", name,
+ line, value));
+ additionalData.emplace("GPIO_LINE", std::format("{}", line));
+ additionalData.emplace("GPIO_VALUE", std::format("{}", value));
+ }
+ }
+
+ return hasFault;
+}
+
+bool Rail::hasPgoodFaultOutputVoltage(
+ PowerSequencerDevice& device, Services& services,
+ std::map<std::string, std::string>& additionalData)
+{
+ bool hasFault{false};
+
+ // If we are comparing output voltage to UV limit to check pgood status
+ if (compareVoltageToLimit)
+ {
+ // Read output voltage and UV fault limit values from device
+ double vout = getReadVout(device);
+ double uvLimit = getVoutUVFaultLimit(device);
+
+ // If output voltage is at or below UV fault limit
+ if (vout <= uvLimit)
+ {
+ hasFault = true;
+ services.logErrorMsg(std::format(
+ "Rail {} output voltage {}V is <= UV fault limit {}V", name,
+ vout, uvLimit));
+ additionalData.emplace("READ_VOUT", std::format("{}", vout));
+ additionalData.emplace("VOUT_UV_FAULT_LIMIT",
+ std::format("{}", uvLimit));
+ }
+ }
+
+ return hasFault;
+}
+
+void Rail::storePgoodFaultDebugData(
+ PowerSequencerDevice& device, Services& services,
+ std::map<std::string, std::string>& additionalData)
+{
+ additionalData.emplace("RAIL_NAME", name);
+ if (page)
+ {
+ try
+ {
+ uint16_t statusWord = getStatusWord(device);
+ services.logInfoMsg(
+ std::format("Rail {} STATUS_WORD: {:#06x}", name, statusWord));
+ additionalData.emplace("STATUS_WORD",
+ std::format("{:#06x}", statusWord));
+ }
+ catch (...)
+ {
+ // Ignore error; don't interrupt pgood fault handling
+ }
+ }
+}
+
+} // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/rail.hpp b/phosphor-power-sequencer/src/rail.hpp
index a0115e5..5a49e13 100644
--- a/phosphor-power-sequencer/src/rail.hpp
+++ b/phosphor-power-sequencer/src/rail.hpp
@@ -15,10 +15,15 @@
*/
#pragma once
+#include "power_sequencer_device.hpp"
+#include "services.hpp"
+
#include <cstdint>
+#include <map>
#include <optional>
#include <stdexcept>
#include <string>
+#include <vector>
namespace phosphor::power::sequencer
{
@@ -93,7 +98,7 @@
compareVoltageToLimit{compareVoltageToLimit}, gpio{gpio}
{
// If checking STATUS_VOUT or output voltage, verify PAGE was specified
- if ((checkStatusVout || compareVoltageToLimit) && !page.has_value())
+ if ((checkStatusVout || compareVoltageToLimit) && !page)
{
throw std::invalid_argument{"PMBus PAGE is required"};
}
@@ -172,8 +177,158 @@
return gpio;
}
+ /**
+ * Returns whether the rail is present.
+ *
+ * Returns true if no inventory path was specified for presence detection.
+ *
+ * @param services System services like hardware presence and the journal
+ * @return true if rail is present, false otherwise
+ */
+ bool isPresent(Services& services);
+
+ /**
+ * Returns the value of the PMBus STATUS_WORD command for the rail.
+ *
+ * Reads the value from the specified device. The returned value is in
+ * host-endian order.
+ *
+ * Throws an exception if the value could not be obtained.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @return STATUS_WORD value
+ */
+ uint16_t getStatusWord(PowerSequencerDevice& device);
+
+ /**
+ * Returns the value of the PMBus STATUS_VOUT command for the rail.
+ *
+ * Reads the value from the specified device.
+ *
+ * Throws an exception if the value could not be obtained.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @return STATUS_VOUT value
+ */
+ uint8_t getStatusVout(PowerSequencerDevice& device);
+
+ /**
+ * Returns the value of the PMBus READ_VOUT command for the rail.
+ *
+ * Reads the value from the specified device. The returned value is in
+ * volts.
+ *
+ * Throws an exception if the value could not be obtained.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @return READ_VOUT value in volts
+ */
+ double getReadVout(PowerSequencerDevice& device);
+
+ /**
+ * Returns the value of the PMBus VOUT_UV_FAULT_LIMIT command for the rail.
+ *
+ * Reads the value from the specified device. The returned value is in
+ * volts.
+ *
+ * Throws an exception if the value could not be obtained.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @return VOUT_UV_FAULT_LIMIT value in volts
+ */
+ double getVoutUVFaultLimit(PowerSequencerDevice& device);
+
+ /**
+ * Returns whether a pgood (power good) fault has occurred on the rail.
+ *
+ * Throws an exception if an error occurs while trying to obtain the rail
+ * status.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @param services System services like hardware presence and the journal
+ * @param gpioValues GPIO values obtained from the device (if any)
+ * @param additionalData Additional data to include in an error log if this
+ * method returns true
+ * @return true if a pgood fault was found on the rail, false otherwise
+ */
+ bool hasPgoodFault(PowerSequencerDevice& device, Services& services,
+ const std::vector<int>& gpioValues,
+ std::map<std::string, std::string>& additionalData);
+
private:
/**
+ * Verifies that a PMBus PAGE number is defined for the rail.
+ *
+ * Throws an exception if a PAGE number is not defined.
+ */
+ void verifyHasPage();
+
+ /**
+ * Returns whether the PMBus STATUS_VOUT command indicates a pgood fault
+ * has occurred on the rail.
+ *
+ * Throws an exception if an error occurs while trying to obtain the rail
+ * status.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @param services System services like hardware presence and the journal
+ * @param additionalData Additional data to include in an error log if this
+ * method returns true
+ * @return true if a pgood fault was found on the rail, false otherwise
+ */
+ bool hasPgoodFaultStatusVout(
+ PowerSequencerDevice& device, Services& services,
+ std::map<std::string, std::string>& additionalData);
+
+ /**
+ * Returns whether a GPIO value indicates a pgood fault has occurred on the
+ * rail.
+ *
+ * Throws an exception if an error occurs while trying to obtain the rail
+ * status.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @param gpioValues GPIO values obtained from the device (if any)
+ * @param additionalData Additional data to include in an error log if this
+ * method returns true
+ * @return true if a pgood fault was found on the rail, false otherwise
+ */
+ bool hasPgoodFaultGPIO(Services& services,
+ const std::vector<int>& gpioValues,
+ std::map<std::string, std::string>& additionalData);
+
+ /**
+ * Returns whether the output voltage is below the undervoltage limit
+ * indicating a pgood fault has occurred on the rail.
+ *
+ * Throws an exception if an error occurs while trying to obtain the rail
+ * status.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @param services System services like hardware presence and the journal
+ * @param additionalData Additional data to include in an error log if this
+ * method returns true
+ * @return true if a pgood fault was found on the rail, false otherwise
+ */
+ bool hasPgoodFaultOutputVoltage(
+ PowerSequencerDevice& device, Services& services,
+ std::map<std::string, std::string>& additionalData);
+
+ /**
+ * Store pgood fault debug data in the specified additional data map.
+ *
+ * Stores data that is relevant regardless of which method was used to
+ * detect the pgood fault.
+ *
+ * @param device Power sequencer device that enables and monitors the rail
+ * @param services System services like hardware presence and the journal
+ * @param additionalData Additional data to include in an error log
+ */
+ void storePgoodFaultDebugData(
+ PowerSequencerDevice& device, Services& services,
+ std::map<std::string, std::string>& additionalData);
+
+ /**
* Unique name for the rail.
*/
std::string name{};
diff --git a/phosphor-power-sequencer/test/mock_device.hpp b/phosphor-power-sequencer/test/mock_device.hpp
index 2d2e0ca..5bf09ea 100644
--- a/phosphor-power-sequencer/test/mock_device.hpp
+++ b/phosphor-power-sequencer/test/mock_device.hpp
@@ -44,10 +44,9 @@
MOCK_METHOD(uint8_t, getStatusVout, (uint8_t page), (override));
MOCK_METHOD(double, getReadVout, (uint8_t page), (override));
MOCK_METHOD(double, getVoutUVFaultLimit, (uint8_t page), (override));
- MOCK_METHOD(double, getVoutOVFaultLimit, (uint8_t page), (override));
MOCK_METHOD(bool, hasPgoodFault,
(const std::string& powerSupplyError, std::string& error,
- std::map<std::string, std::string>& additionalData),
+ (std::map<std::string, std::string> & additionalData)),
(override));
};
diff --git a/phosphor-power-sequencer/test/mock_services.hpp b/phosphor-power-sequencer/test/mock_services.hpp
index c0544f0..6c0d906 100644
--- a/phosphor-power-sequencer/test/mock_services.hpp
+++ b/phosphor-power-sequencer/test/mock_services.hpp
@@ -23,6 +23,8 @@
namespace phosphor::power::sequencer
{
+using MockPMBus = phosphor::pmbus::MockPMBus;
+
/**
* @class MockServices
*
diff --git a/phosphor-power-sequencer/test/rail_tests.cpp b/phosphor-power-sequencer/test/rail_tests.cpp
index 9b05b02..479767b 100644
--- a/phosphor-power-sequencer/test/rail_tests.cpp
+++ b/phosphor-power-sequencer/test/rail_tests.cpp
@@ -14,16 +14,24 @@
* limitations under the License.
*/
+#include "mock_device.hpp"
+#include "mock_services.hpp"
#include "rail.hpp"
#include <cstdint>
+#include <map>
#include <optional>
#include <string>
+#include <vector>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace phosphor::power::sequencer;
+using ::testing::Return;
+using ::testing::Throw;
+
TEST(GPIOTests, Initialization)
{
// Default initialization
@@ -325,3 +333,1046 @@
EXPECT_FALSE(rail.getGPIO().value().activeLow);
}
}
+
+TEST(RailTests, IsPresent)
+{
+ std::string name{"VDD2"};
+ std::optional<uint8_t> page{};
+ bool isPowerSupplyRail{false};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+
+ // Test where inventory path not specified; always returns true
+ {
+ std::optional<std::string> presence{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockServices services{};
+ EXPECT_CALL(services, isPresent).Times(0);
+
+ EXPECT_TRUE(rail.isPresent(services));
+ }
+
+ // Test where inventory path is not present
+ {
+ std::optional<std::string> presence{
+ "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockServices services{};
+ EXPECT_CALL(services, isPresent(*presence))
+ .Times(1)
+ .WillOnce(Return(false));
+
+ EXPECT_FALSE(rail.isPresent(services));
+ }
+
+ // Test where inventory path is present
+ {
+ std::optional<std::string> presence{
+ "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockServices services{};
+ EXPECT_CALL(services, isPresent(*presence))
+ .Times(1)
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(rail.isPresent(services));
+ }
+
+ // Test where exception occurs trying to get presence
+ {
+ std::optional<std::string> presence{
+ "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockServices services{};
+ EXPECT_CALL(services, isPresent(*presence))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"Invalid object path"}));
+
+ try
+ {
+ rail.isPresent(services);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(
+ e.what(),
+ "Unable to determine presence of rail VDD2 using "
+ "inventory path "
+ "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2: "
+ "Invalid object path");
+ }
+ }
+}
+
+TEST(RailTests, GetStatusWord)
+{
+ std::string name{"VDD2"};
+ std::optional<std::string> presence{};
+ bool isPowerSupplyRail{false};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+
+ // Test where page was not specified: Throws exception
+ {
+ std::optional<uint8_t> page{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusWord).Times(0);
+
+ try
+ {
+ rail.getStatusWord(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read STATUS_WORD value for rail VDD2: "
+ "No PAGE number defined for rail VDD2");
+ }
+ }
+
+ // Test where value read successfully
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
+
+ EXPECT_EQ(rail.getStatusWord(device), 0xbeef);
+ }
+
+ // Test where exception occurs trying to read value
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusWord(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ try
+ {
+ rail.getStatusWord(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read STATUS_WORD value for rail VDD2: "
+ "File does not exist");
+ }
+ }
+}
+
+TEST(RailTests, GetStatusVout)
+{
+ std::string name{"VDD2"};
+ std::optional<std::string> presence{};
+ bool isPowerSupplyRail{false};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+
+ // Test where page was not specified: Throws exception
+ {
+ std::optional<uint8_t> page{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout).Times(0);
+
+ try
+ {
+ rail.getStatusVout(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read STATUS_VOUT value for rail VDD2: "
+ "No PAGE number defined for rail VDD2");
+ }
+ }
+
+ // Test where value read successfully
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0xad));
+
+ EXPECT_EQ(rail.getStatusVout(device), 0xad);
+ }
+
+ // Test where exception occurs trying to read value
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ try
+ {
+ rail.getStatusVout(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read STATUS_VOUT value for rail VDD2: "
+ "File does not exist");
+ }
+ }
+}
+
+TEST(RailTests, GetReadVout)
+{
+ std::string name{"VDD2"};
+ std::optional<std::string> presence{};
+ bool isPowerSupplyRail{false};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+
+ // Test where page was not specified: Throws exception
+ {
+ std::optional<uint8_t> page{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getReadVout).Times(0);
+
+ try
+ {
+ rail.getReadVout(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read READ_VOUT value for rail VDD2: "
+ "No PAGE number defined for rail VDD2");
+ }
+ }
+
+ // Test where value read successfully
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.23));
+
+ EXPECT_EQ(rail.getReadVout(device), 1.23);
+ }
+
+ // Test where exception occurs trying to read value
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getReadVout(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ try
+ {
+ rail.getReadVout(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read READ_VOUT value for rail VDD2: "
+ "File does not exist");
+ }
+ }
+}
+
+TEST(RailTests, GetVoutUVFaultLimit)
+{
+ std::string name{"VDD2"};
+ std::optional<std::string> presence{};
+ bool isPowerSupplyRail{false};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+
+ // Test where page was not specified: Throws exception
+ {
+ std::optional<uint8_t> page{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getVoutUVFaultLimit).Times(0);
+
+ try
+ {
+ rail.getVoutUVFaultLimit(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(
+ e.what(),
+ "Unable to read VOUT_UV_FAULT_LIMIT value for rail VDD2: "
+ "No PAGE number defined for rail VDD2");
+ }
+ }
+
+ // Test where value read successfully
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getVoutUVFaultLimit(2))
+ .Times(1)
+ .WillOnce(Return(0.9));
+
+ EXPECT_EQ(rail.getVoutUVFaultLimit(device), 0.9);
+ }
+
+ // Test where exception occurs trying to read value
+ {
+ std::optional<uint8_t> page{2};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getVoutUVFaultLimit(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ try
+ {
+ rail.getVoutUVFaultLimit(device);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(
+ e.what(),
+ "Unable to read VOUT_UV_FAULT_LIMIT value for rail VDD2: "
+ "File does not exist");
+ }
+ }
+}
+
+TEST(RailTests, HasPgoodFault)
+{
+ std::string name{"VDD2"};
+ bool isPowerSupplyRail{false};
+
+ // Test where presence check defined: Rail is not present
+ {
+ std::optional<std::string> presence{
+ "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
+ std::optional<uint8_t> page{3};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+
+ MockServices services{};
+ EXPECT_CALL(services, isPresent(*presence))
+ .Times(1)
+ .WillOnce(Return(false));
+ EXPECT_CALL(services, logInfoMsg("Rail VDD2 is not present")).Times(1);
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where presence check defined: Rail is present
+ {
+ std::optional<std::string> presence{
+ "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu2"};
+ std::optional<uint8_t> page{};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+
+ MockServices services{};
+ EXPECT_CALL(services, isPresent(*presence))
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(services, logInfoMsg).Times(0);
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where no checks are specified
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where 1 check defined: STATUS_VOUT: No fault detected
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where 1 check defined: STATUS_VOUT: No fault detected, but warning
+ // bits set
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x6a));
+
+ MockServices services{};
+ EXPECT_CALL(
+ services,
+ logInfoMsg("Rail VDD2 has warning bits set in STATUS_VOUT: 0x6a"))
+ .Times(1);
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where 1 check defined: STATUS_VOUT: Fault detected
+ // STATUS_WORD captured in additional data
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x10));
+ EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
+
+ MockServices services{};
+ EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
+ .Times(1);
+ EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
+ .Times(1);
+ EXPECT_CALL(
+ services,
+ logErrorMsg("Rail VDD2 has fault bits set in STATUS_VOUT: 0x10"))
+ .Times(1);
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_TRUE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 3);
+ EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
+ EXPECT_EQ(additionalData["STATUS_VOUT"], "0x10");
+ EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
+ }
+
+ // Test where 1 check defined: STATUS_VOUT: Exception thrown
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{false};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ try
+ {
+ rail.hasPgoodFault(device, services, gpioValues, additionalData);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read STATUS_VOUT value for rail VDD2: "
+ "File does not exist");
+ }
+ }
+
+ // Test where 1 check defined: GPIO: No fault detected
+ // GPIO value is 1 and GPIO is active high
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ bool activeLow{false};
+ std::optional<GPIO> gpio{GPIO(3, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{1, 1, 1, 1, 1, 1};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where 1 check defined: GPIO: Fault detected
+ // GPIO value is 0 and GPIO is active high
+ // STATUS_WORD not captured since no PMBus page defined
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ bool activeLow{false};
+ std::optional<GPIO> gpio{GPIO(3, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+
+ MockServices services{};
+ EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
+ .Times(1);
+ EXPECT_CALL(
+ services,
+ logErrorMsg(
+ "Rail VDD2 pgood GPIO line offset 3 has inactive value 0"))
+ .Times(1);
+
+ std::vector<int> gpioValues{1, 1, 1, 0, 1, 1};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_TRUE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 3);
+ EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
+ EXPECT_EQ(additionalData["GPIO_LINE"], "3");
+ EXPECT_EQ(additionalData["GPIO_VALUE"], "0");
+ }
+
+ // Test where 1 check defined: GPIO: Exception thrown: Invalid line offset
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{false};
+ bool activeLow{false};
+ std::optional<GPIO> gpio{GPIO(6, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{1, 1, 1, 1, 1, 1};
+ std::map<std::string, std::string> additionalData{};
+ try
+ {
+ rail.hasPgoodFault(device, services, gpioValues, additionalData);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(), "Invalid GPIO line offset 6 for rail VDD2: "
+ "Device only has 6 GPIO values");
+ }
+ }
+
+ // Test where 1 check defined: READ_VOUT: No fault detected
+ // Output voltage > UV limit
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{true};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
+ EXPECT_CALL(device, getVoutUVFaultLimit(2))
+ .Times(1)
+ .WillOnce(Return(1.0));
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where 1 check defined: READ_VOUT: Fault detected
+ // Output voltage < UV limit
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{true};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
+ EXPECT_CALL(device, getVoutUVFaultLimit(2))
+ .Times(1)
+ .WillOnce(Return(1.2));
+ EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
+
+ MockServices services{};
+ EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
+ .Times(1);
+ EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
+ .Times(1);
+ EXPECT_CALL(
+ services,
+ logErrorMsg(
+ "Rail VDD2 output voltage 1.1V is <= UV fault limit 1.2V"))
+ .Times(1);
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_TRUE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 4);
+ EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
+ EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
+ EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.2");
+ EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
+ }
+
+ // Test where 1 check defined: READ_VOUT: Exception thrown
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{false};
+ bool compareVoltageToLimit{true};
+ std::optional<GPIO> gpio{};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getReadVout(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{};
+ std::map<std::string, std::string> additionalData{};
+ try
+ {
+ rail.hasPgoodFault(device, services, gpioValues, additionalData);
+ ADD_FAILURE() << "Should not have reached this line.";
+ }
+ catch (const std::exception& e)
+ {
+ EXPECT_STREQ(e.what(),
+ "Unable to read READ_VOUT value for rail VDD2: "
+ "File does not exist");
+ }
+ }
+
+ // Test where 3 checks defined: No fault detected
+ // GPIO value is 0 and GPIO is active low
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{true};
+ bool activeLow{true};
+ std::optional<GPIO> gpio{GPIO(3, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
+ EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
+ EXPECT_CALL(device, getVoutUVFaultLimit(2))
+ .Times(1)
+ .WillOnce(Return(0.9));
+
+ MockServices services{};
+
+ std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_FALSE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 0);
+ }
+
+ // Test where 3 checks defined: Fault detected via STATUS_VOUT
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{true};
+ bool activeLow{true};
+ std::optional<GPIO> gpio{GPIO(3, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x10));
+ EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
+
+ MockServices services{};
+ EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
+ .Times(1);
+ EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
+ .Times(1);
+ EXPECT_CALL(
+ services,
+ logErrorMsg("Rail VDD2 has fault bits set in STATUS_VOUT: 0x10"))
+ .Times(1);
+
+ std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_TRUE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 3);
+ EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
+ EXPECT_EQ(additionalData["STATUS_VOUT"], "0x10");
+ EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
+ }
+
+ // Test where 3 checks defined: Fault detected via GPIO
+ // GPIO value is 1 and GPIO is active low
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{true};
+ bool activeLow{true};
+ std::optional<GPIO> gpio{GPIO(3, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
+ EXPECT_CALL(device, getStatusWord(2)).Times(1).WillOnce(Return(0xbeef));
+
+ MockServices services{};
+ EXPECT_CALL(services, logInfoMsg("Rail VDD2 STATUS_WORD: 0xbeef"))
+ .Times(1);
+ EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
+ .Times(1);
+ EXPECT_CALL(
+ services,
+ logErrorMsg(
+ "Rail VDD2 pgood GPIO line offset 3 has inactive value 1"))
+ .Times(1);
+
+ std::vector<int> gpioValues{0, 0, 0, 1, 0, 0};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_TRUE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 4);
+ EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
+ EXPECT_EQ(additionalData["GPIO_LINE"], "3");
+ EXPECT_EQ(additionalData["GPIO_VALUE"], "1");
+ EXPECT_EQ(additionalData["STATUS_WORD"], "0xbeef");
+ }
+
+ // Test where 3 checks defined: Fault detected via READ_VOUT
+ // Output voltage == UV limit
+ // STATUS_WORD not captured because reading it caused an exception
+ {
+ std::optional<std::string> presence{};
+ std::optional<uint8_t> page{2};
+ bool checkStatusVout{true};
+ bool compareVoltageToLimit{true};
+ bool activeLow{true};
+ std::optional<GPIO> gpio{GPIO(3, activeLow)};
+ Rail rail{name,
+ presence,
+ page,
+ isPowerSupplyRail,
+ checkStatusVout,
+ compareVoltageToLimit,
+ gpio};
+
+ MockDevice device{};
+ EXPECT_CALL(device, getStatusVout(2)).Times(1).WillOnce(Return(0x00));
+ EXPECT_CALL(device, getReadVout(2)).Times(1).WillOnce(Return(1.1));
+ EXPECT_CALL(device, getVoutUVFaultLimit(2))
+ .Times(1)
+ .WillOnce(Return(1.1));
+ EXPECT_CALL(device, getStatusWord(2))
+ .Times(1)
+ .WillOnce(Throw(std::runtime_error{"File does not exist"}));
+
+ MockServices services{};
+ EXPECT_CALL(services, logErrorMsg("Pgood fault detected in rail VDD2"))
+ .Times(1);
+ EXPECT_CALL(
+ services,
+ logErrorMsg(
+ "Rail VDD2 output voltage 1.1V is <= UV fault limit 1.1V"))
+ .Times(1);
+
+ std::vector<int> gpioValues{0, 0, 0, 0, 0, 0};
+ std::map<std::string, std::string> additionalData{};
+ EXPECT_TRUE(
+ rail.hasPgoodFault(device, services, gpioValues, additionalData));
+ EXPECT_EQ(additionalData.size(), 3);
+ EXPECT_EQ(additionalData["RAIL_NAME"], "VDD2");
+ EXPECT_EQ(additionalData["READ_VOUT"], "1.1");
+ EXPECT_EQ(additionalData["VOUT_UV_FAULT_LIMIT"], "1.1");
+ }
+}