psu-ng: Add code to detect VOUT_OV_FAULT
If the output voltage hits an overvoltage condition, the VOUT_OV_FAULT
bit (bit 5 of STATUS_WORD low byte) should turn on. The PMBus spec
indicates that VOUT fault/warning (bit 7 high byte) and VOUT_OV_FAULT
are associated with status in the STATUS_VOUT command response.
Check for VOUT_OV_FAULT after check for VIN_UV_FAULT, create error if
the fault is indicated.
Change-Id: Ia68b4f986393f0ba0184401deb29b4f5d25a2ed0
Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index ca9c084..2102e53 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -211,6 +211,8 @@
statusInput = pmbusIntf->read(STATUS_INPUT, Type::Debug);
statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug);
statusCML = pmbusIntf->read(STATUS_CML, Type::Debug);
+ auto status0Vout = pmbusIntf->insertPageNum(STATUS_VOUT, 0);
+ statusVout = pmbusIntf->read(status0Vout, Type::Debug);
if (statusWord & status_word::CML_FAULT)
{
if (!cmlFault)
@@ -241,6 +243,21 @@
inputFault = true;
}
+ if (statusWord & status_word::VOUT_OV_FAULT)
+ {
+ if (!voutOVFault)
+ {
+ log<level::INFO>(
+ fmt::format("INPUT fault: STATUS_WORD = {:#04x}, "
+ "STATUS_MFR_SPECIFIC = {:#02x}, "
+ "STATUS_VOUT = {:#02x}",
+ statusWord, statusMFR, statusVout)
+ .c_str());
+ }
+ faultFound = true;
+ voutOVFault = true;
+ }
+
if (statusWord & status_word::MFR_SPECIFIC_FAULT)
{
if (!mfrFault)
@@ -279,6 +296,7 @@
inputFault = false;
mfrFault = false;
vinUVFault = false;
+ voutOVFault = false;
}
}
catch (const ReadFailure& e)
@@ -330,6 +348,7 @@
statusMFR = 0;
vinUVFault = false;
cmlFault = false;
+ voutOVFault = false;
readFail = 0;
try
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index d322043..0cc80d9 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -169,6 +169,14 @@
}
/**
+ * @brief Returns the last read value from STATUS_VOUT.
+ */
+ uint64_t getStatusVout() const
+ {
+ return statusVout;
+ }
+
+ /**
* @brief Returns true if a fault was found.
*/
bool isFaulted() const
@@ -217,6 +225,14 @@
}
/**
+ * @brief Returns true if VOUT_OV_FAULT occurred.
+ */
+ bool hasVoutOVFault() const
+ {
+ return voutOVFault;
+ }
+
+ /**
* @brief Returns the device path
*
* This can be used for error call outs.
@@ -288,6 +304,9 @@
/** @brief Will be updated to the latest/last value read from STATUS_CML.*/
uint64_t statusCML = 0;
+ /** @brief Will be updated to the latest/last value read from STATUS_VOUT.*/
+ uint64_t statusVout = 0;
+
/** @brief True if a fault has already been found and not cleared */
bool faultFound = false;
@@ -306,6 +325,9 @@
/** @brief True if bit 3 of STATUS_WORD low byte is on. */
bool vinUVFault = false;
+ /** @brief True if bit 5 of STATUS_WORD low byte is on. */
+ bool voutOVFault = false;
+
/** @brief Count of the number of read failures. */
size_t readFail = 0;
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index c63207f..0d12dbc 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -443,6 +443,21 @@
additionalData);
psu->setFaultLogged();
}
+ else if (psu->hasVoutOVFault())
+ {
+ // Include STATUS_VOUT for Vout faults.
+ additionalData["STATUS_VOUT"] =
+ fmt::format("{:#02x}", psu->getStatusVout());
+
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
else if (psu->hasMFRFault())
{
/* This can represent a variety of faults that result in
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
index a6a384b..25f48c1 100644
--- a/phosphor-power-supply/test/mock.hpp
+++ b/phosphor-power-supply/test/mock.hpp
@@ -28,6 +28,8 @@
(override));
MOCK_METHOD(void, findHwmonDir, (), (override));
MOCK_METHOD(const fs::path&, path, (), (const, override));
+ MOCK_METHOD(std::string, insertPageNum,
+ (const std::string& templateName, size_t page), (override));
};
} // namespace pmbus
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 04c0fc7..5e3d6b4 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -26,7 +26,8 @@
void setPMBusExpectations(MockedPMBus& mockPMBus, uint16_t statusWordValue,
uint8_t statusInputValue = 0,
uint8_t statusMFRValue = 0,
- uint8_t statusCMLValue = 0)
+ uint8_t statusCMLValue = 0,
+ uint8_t statusVOUTValue = 0)
{
EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
.Times(1)
@@ -45,6 +46,13 @@
EXPECT_CALL(mockPMBus, read(STATUS_CML, _))
.Times(1)
.WillOnce(Return(statusCMLValue));
+ // Page will need to be set to 0 to read STATUS_VOUT.
+ EXPECT_CALL(mockPMBus, insertPageNum(STATUS_VOUT, 0))
+ .Times(1)
+ .WillOnce(Return("status0_vout"));
+ EXPECT_CALL(mockPMBus, read("status0_vout", _))
+ .Times(1)
+ .WillOnce(Return(statusVOUTValue));
}
}
@@ -126,6 +134,7 @@
EXPECT_EQ(psu->hasInputFault(), false);
EXPECT_EQ(psu->hasMFRFault(), false);
EXPECT_EQ(psu->hasVINUVFault(), false);
+ EXPECT_EQ(psu->hasVoutOVFault(), false);
}
catch (...)
{
@@ -169,6 +178,7 @@
EXPECT_EQ(psu.hasMFRFault(), false);
EXPECT_EQ(psu.hasVINUVFault(), false);
EXPECT_EQ(psu.hasCommFault(), false);
+ EXPECT_EQ(psu.hasVoutOVFault(), false);
PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
// In order to get the various faults tested, the power supply needs to
@@ -197,12 +207,13 @@
EXPECT_EQ(psu2.hasMFRFault(), false);
EXPECT_EQ(psu2.hasVINUVFault(), false);
EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
// STATUS_WORD input fault/warn
statusWordValue = (status_word::INPUT_FAULT_WARN);
// STATUS_INPUT fault bits ... on.
statusInputValue = 0x38;
- // STATUS_MFR and STATUS_CML don't care
+ // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
psu2.analyze();
EXPECT_EQ(psu2.isPresent(), true);
@@ -211,6 +222,7 @@
EXPECT_EQ(psu2.hasMFRFault(), false);
EXPECT_EQ(psu2.hasVINUVFault(), false);
EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
// STATUS_WORD INPUT/UV fault.
// First need it to return good status, then the fault
@@ -222,7 +234,7 @@
(status_word::INPUT_FAULT_WARN | status_word::VIN_UV_FAULT);
// STATUS_INPUT fault bits ... on.
statusInputValue = 0x38;
- // STATUS_MFR and STATUS_CML don't care
+ // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
psu2.analyze();
EXPECT_EQ(psu2.isPresent(), true);
@@ -231,6 +243,7 @@
EXPECT_EQ(psu2.hasMFRFault(), false);
EXPECT_EQ(psu2.hasVINUVFault(), true);
EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
// STATUS_WORD MFR fault.
// First need it to return good status, then the fault
@@ -243,7 +256,7 @@
statusInputValue = 0;
// STATUS_MFR bits on.
uint8_t statusMFRValue = 0xFF;
- // STATUS_CML don't care
+ // STATUS_CML and STATUS_VOUT don't care
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
statusMFRValue);
psu2.analyze();
@@ -253,6 +266,7 @@
EXPECT_EQ(psu2.hasMFRFault(), true);
EXPECT_EQ(psu2.hasVINUVFault(), false);
EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
// Ignore Temperature fault.
// First STATUS_WORD with no bits set, then with temperature fault.
@@ -261,7 +275,8 @@
psu2.analyze();
// STATUS_WORD with temperature fault bit on.
statusWordValue = (status_word::TEMPERATURE_FAULT_WARN);
- // STATUS_INPUT, STATUS_MFR, and STATUS_CML fault bits ... don't care.
+ // STATUS_INPUT, STATUS_MFR, STATUS_CML, and STATUS_VOUT fault bits ...
+ // don't care.
setPMBusExpectations(mockPMBus, statusWordValue);
psu2.analyze();
EXPECT_EQ(psu2.isPresent(), true);
@@ -270,6 +285,7 @@
EXPECT_EQ(psu2.hasMFRFault(), false);
EXPECT_EQ(psu2.hasVINUVFault(), false);
EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
// CML fault
// First STATUS_WORD wit no bits set, then with CML fault.
@@ -284,6 +300,7 @@
statusMFRValue = 0;
// Turn on STATUS_CML fault bit(s)
uint8_t statusCMLValue = 0xFF;
+ // STATUS_VOUT don't care
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
statusMFRValue, statusCMLValue);
psu2.analyze();
@@ -293,6 +310,34 @@
EXPECT_EQ(psu2.hasMFRFault(), false);
EXPECT_EQ(psu2.hasVINUVFault(), false);
EXPECT_EQ(psu2.hasCommFault(), true);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
+
+ // VOUT_OV_FAULT fault
+ // First STATUS_WORD with no bits set, then with VOUT/VOUT_OV fault.
+ statusWordValue = 0;
+ setPMBusExpectations(mockPMBus, statusWordValue);
+ psu2.analyze();
+ // STATUS_WORD with VOUT/VOUT_OV fault.
+ statusWordValue =
+ ((status_word::VOUT_FAULT) | (status_word::VOUT_OV_FAULT));
+ // STATUS_INPUT fault bits ... don't care.
+ statusInputValue = 0;
+ // STATUS_MFR don't care
+ statusMFRValue = 0;
+ // STATUS_CML don't care
+ statusCMLValue = 0;
+ // Turn on STATUS_VOUT fault bit(s)
+ uint8_t statusVOUTValue = 0xA0;
+ setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
+ statusMFRValue, statusCMLValue, statusVOUTValue);
+ psu2.analyze();
+ EXPECT_EQ(psu2.isPresent(), true);
+ EXPECT_EQ(psu2.isFaulted(), true);
+ EXPECT_EQ(psu2.hasInputFault(), false);
+ EXPECT_EQ(psu2.hasMFRFault(), false);
+ EXPECT_EQ(psu2.hasVINUVFault(), false);
+ EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), true);
// Ignore fan fault
// First STATUS_WORD with no bits set, then with fan
@@ -301,7 +346,8 @@
setPMBusExpectations(mockPMBus, statusWordValue);
psu2.analyze();
statusWordValue = (status_word::FAN_FAULT);
- // STATUS_INPUT, STATUS_MFR, and STATUS_CML: Don't care if bits set or not.
+ // STATUS_INPUT, STATUS_MFR, STATUS_CML, STATUS_VOUT: Don't care if
+ // bits set or not.
setPMBusExpectations(mockPMBus, statusWordValue);
psu2.analyze();
EXPECT_EQ(psu2.isPresent(), true);
@@ -310,6 +356,7 @@
EXPECT_EQ(psu2.hasMFRFault(), false);
EXPECT_EQ(psu2.hasVINUVFault(), false);
EXPECT_EQ(psu2.hasCommFault(), false);
+ EXPECT_EQ(psu2.hasVoutOVFault(), false);
// TODO: ReadFailure
}
@@ -381,6 +428,7 @@
EXPECT_EQ(psu.hasMFRFault(), false);
EXPECT_EQ(psu.hasVINUVFault(), false);
EXPECT_EQ(psu.hasCommFault(), false);
+ EXPECT_EQ(psu.hasVoutOVFault(), false);
// STATUS_WORD with fault bits galore!
statusWordValue = 0xFFFF;
// STATUS_INPUT with fault bits on.
@@ -389,8 +437,10 @@
uint8_t statusMFRValue = 0xFF;
// STATUS_CML with bits on.
uint8_t statusCMLValue = 0xFF;
+ // STATUS_VOUT with bits on.
+ uint8_t statusVOUTValue = 0xFF;
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
- statusMFRValue, statusCMLValue);
+ statusMFRValue, statusCMLValue, statusVOUTValue);
psu.analyze();
EXPECT_EQ(psu.isPresent(), true);
EXPECT_EQ(psu.isFaulted(), true);
@@ -398,6 +448,7 @@
EXPECT_EQ(psu.hasMFRFault(), true);
EXPECT_EQ(psu.hasVINUVFault(), true);
EXPECT_EQ(psu.hasCommFault(), true);
+ EXPECT_EQ(psu.hasVoutOVFault(), true);
EXPECT_CALL(mockPMBus, read("in1_input", _))
.Times(1)
.WillOnce(Return(209000));
@@ -408,6 +459,7 @@
EXPECT_EQ(psu.hasMFRFault(), false);
EXPECT_EQ(psu.hasVINUVFault(), false);
EXPECT_EQ(psu.hasCommFault(), false);
+ EXPECT_EQ(psu.hasVoutOVFault(), false);
// TODO: Faults clear on missing/present?
}
@@ -494,8 +546,10 @@
uint8_t statusMFRValue = 0xFF;
// STATUS_CML with faults bits on.
uint8_t statusCMLValue = 0xFF;
+ // STATUS_VOUT with fault bits on.
+ uint8_t statusVOUTValue = 0xFF;
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
- statusMFRValue, statusCMLValue);
+ statusMFRValue, statusCMLValue, statusVOUTValue);
psu.analyze();
EXPECT_EQ(psu.isFaulted(), true);
}
@@ -521,7 +575,7 @@
statusWordValue = (status_word::INPUT_FAULT_WARN);
// STATUS_INPUT with an input fault bit on.
uint8_t statusInputValue = 0x80;
- // STATUS_MFR and STATUS_CML don't care.
+ // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care.
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
psu.analyze();
EXPECT_EQ(psu.hasInputFault(), true);
@@ -556,7 +610,7 @@
uint8_t statusInputValue = 0;
// STATUS_MFR_SPEFIC with bit(s) on.
uint8_t statusMFRValue = 0xFF;
- // STATUS_CML don't care.
+ // STATUS_CML and STATUS_VOUT don't care.
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
statusMFRValue);
psu.analyze();
@@ -590,7 +644,7 @@
// Curious disagreement between PMBus Spec. Part II Figure 16 and 33. Go by
// Figure 16, and assume bits on in STATUS_INPUT.
uint8_t statusInputValue = 0x18;
- // STATUS_MFR and STATUS_CML don't care.
+ // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care.
setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
psu.analyze();
EXPECT_EQ(psu.hasVINUVFault(), true);
@@ -600,3 +654,41 @@
psu.analyze();
EXPECT_EQ(psu.hasVINUVFault(), false);
}
+
+TEST_F(PowerSupplyTests, HasVoutOVFault)
+{
+ auto bus = sdbusplus::bus::new_default();
+
+ PowerSupply psu{bus, PSUInventoryPath, 3, 0x69, PSUGPIOLineName};
+ MockedGPIOInterface* mockPresenceGPIO =
+ static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+ // Always return 1 to indicate present.
+ EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+ psu.analyze();
+ MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+ EXPECT_EQ(psu.hasVoutOVFault(), false);
+ // STATUS_WORD 0x0000 is powered on, no faults.
+ uint16_t statusWordValue = 0;
+ setPMBusExpectations(mockPMBus, statusWordValue);
+ psu.analyze();
+ EXPECT_EQ(psu.hasVoutOVFault(), false);
+ // Turn fault on.
+ statusWordValue = (status_word::VOUT_OV_FAULT);
+ // STATUS_INPUT don't care.
+ uint8_t statusInputValue = 0;
+ // STATUS_MFR don't care.
+ uint8_t statusMFRValue = 0;
+ // STATUS_CML don't care.
+ uint8_t statusCMLValue = 0;
+ // STATUS_VOUT fault bit(s)
+ uint8_t statusVOUTValue = 0x80;
+ setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
+ statusMFRValue, statusCMLValue, statusVOUTValue);
+ psu.analyze();
+ EXPECT_EQ(psu.hasVoutOVFault(), true);
+ // Back to no fault bits on in STATUS_WORD
+ statusWordValue = 0;
+ setPMBusExpectations(mockPMBus, statusWordValue);
+ psu.analyze();
+ EXPECT_EQ(psu.hasVoutOVFault(), false);
+}