psu-ng: Add code to detect temperature fault

If the low byte of STATUS_WORD has bit 2 on/1, there is a temperature
fault or warning.

Tested:
    Verify no error logged for temperature fault during normal operation
    on real hardware.
    Verify temperature fault error logged when simulated
    over-temperature fault.

Change-Id: Ib5b0fe849699e72e517ea4d59a69bbb6de1e5283
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 73998f3..2cef368 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -213,6 +213,8 @@
                 statusCML = pmbusIntf->read(STATUS_CML, Type::Debug);
                 auto status0Vout = pmbusIntf->insertPageNum(STATUS_VOUT, 0);
                 statusVout = pmbusIntf->read(status0Vout, Type::Debug);
+                statusTemperature =
+                    pmbusIntf->read(STATUS_TEMPERATURE, Type::Debug);
                 if (statusWord & status_word::CML_FAULT)
                 {
                     if (!cmlFault)
@@ -257,6 +259,23 @@
                     voutOVFault = true;
                 }
 
+                if (statusWord & status_word::TEMPERATURE_FAULT_WARN)
+                {
+                    if (!tempFault)
+                    {
+                        log<level::ERR>(
+                            fmt::format("TEMPERATURE fault/warning: "
+                                        "STATUS_WORD = {:#04x}, "
+                                        "STATUS_MFR_SPECIFIC = {:#02x}, "
+                                        "STATUS_TEMPERATURE = {:#02x}",
+                                        statusWord, statusMFR,
+                                        statusTemperature)
+                                .c_str());
+                    }
+
+                    tempFault = true;
+                }
+
                 if (statusWord & status_word::MFR_SPECIFIC_FAULT)
                 {
                     if (!mfrFault)
@@ -294,6 +313,7 @@
                 mfrFault = false;
                 vinUVFault = false;
                 voutOVFault = false;
+                tempFault = false;
             }
         }
         catch (const ReadFailure& e)
@@ -345,6 +365,7 @@
         vinUVFault = false;
         cmlFault = false;
         voutOVFault = false;
+        tempFault = false;
         readFail = 0;
 
         try
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 8d072c7..c1f7e59 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -177,12 +177,20 @@
     }
 
     /**
+     * @brief Returns the last value read from STATUS_TEMPERATURE.
+     */
+    uint64_t getStatusTemperature() const
+    {
+        return statusTemperature;
+    }
+
+    /**
      * @brief Returns true if a fault was found.
      */
     bool isFaulted() const
     {
         return (hasCommFault() || vinUVFault || inputFault || voutOVFault ||
-                mfrFault);
+                tempFault || mfrFault);
     }
 
     /**
@@ -234,6 +242,14 @@
     }
 
     /**
+     * @brief Returns true if TEMPERATURE fault occurred.
+     */
+    bool hasTempFault() const
+    {
+        return tempFault;
+    }
+
+    /**
      * @brief Returns the device path
      *
      * This can be used for error call outs.
@@ -308,10 +324,14 @@
     /** @brief Will be updated to the latest/last value read from STATUS_VOUT.*/
     uint64_t statusVout = 0;
 
+    /** @brief Will be updated to the latest/last value read from
+     * STATUS_TEMPERATURE.*/
+    uint64_t statusTemperature = 0;
+
     /** @brief True if an error for a fault has already been logged. */
     bool faultLogged = false;
 
-    /** @brief True if bit 2 of STATUS_WORD low byte is on. */
+    /** @brief True if bit 1 of STATUS_WORD low byte is on. */
     bool cmlFault = false;
 
     /** @brief True if bit 5 of STATUS_WORD high byte is on. */
@@ -326,6 +346,9 @@
     /** @brief True if bit 5 of STATUS_WORD low byte is on. */
     bool voutOVFault = false;
 
+    /** @brief True if bit 2 of STATUS_WORD low byte is on. */
+    bool tempFault = 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 257fdfb..b55d019 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -482,6 +482,21 @@
 
                     psu->setFaultLogged();
                 }
+                else if (psu->hasTempFault())
+                {
+                    // Include STATUS_TEMPERATURE for temperature faults.
+                    additionalData["STATUS_TEMPERATURE"] =
+                        fmt::format("{:#02x}", psu->getStatusTemperature());
+
+                    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/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index c131621..7fb7d03 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -29,6 +29,7 @@
     uint8_t statusMFRValue{0x00};
     uint8_t statusCMLValue{0x00};
     uint8_t statusVOUTValue{0x00};
+    uint8_t statusTempValue{0x00};
 };
 
 // Helper function to setup expectations for various STATUS_* commands
@@ -42,7 +43,8 @@
     if (expectations.statusWordValue != 0)
     {
         // If fault bits are on in STATUS_WORD, there will also be a read of
-        // STATUS_INPUT, STATUS_MFR, and STATUS_CML.
+        // STATUS_INPUT, STATUS_MFR, STATUS_CML, STATUS_VOUT (page 0), and
+        // STATUS_TEMPERATURE.
         EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
             .Times(1)
             .WillOnce(Return(expectations.statusInputValue));
@@ -59,6 +61,9 @@
         EXPECT_CALL(mockPMBus, read("status0_vout", _))
             .Times(1)
             .WillOnce(Return(expectations.statusVOUTValue));
+        EXPECT_CALL(mockPMBus, read(STATUS_TEMPERATURE, _))
+            .Times(1)
+            .WillOnce(Return(expectations.statusTempValue));
     }
 }
 
@@ -141,6 +146,7 @@
         EXPECT_EQ(psu->hasMFRFault(), false);
         EXPECT_EQ(psu->hasVINUVFault(), false);
         EXPECT_EQ(psu->hasVoutOVFault(), false);
+        EXPECT_EQ(psu->hasTempFault(), false);
     }
     catch (...)
     {
@@ -186,6 +192,7 @@
         EXPECT_EQ(psu.hasVINUVFault(), false);
         EXPECT_EQ(psu.hasCommFault(), false);
         EXPECT_EQ(psu.hasVoutOVFault(), false);
+        EXPECT_EQ(psu.hasTempFault(), false);
     }
 
     PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
@@ -218,8 +225,10 @@
         EXPECT_EQ(psu2.hasVINUVFault(), false);
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
 
         // Update expectations for STATUS_WORD input fault/warn
+        // STATUS_INPUT fault bits ... on.
         expectations.statusWordValue = (status_word::INPUT_FAULT_WARN);
         expectations.statusInputValue = 0x38;
         setPMBusExpectations(mockPMBus, expectations);
@@ -231,6 +240,7 @@
         EXPECT_EQ(psu2.hasVINUVFault(), false);
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
     }
 
     // STATUS_WORD INPUT/UV fault.
@@ -253,6 +263,7 @@
         EXPECT_EQ(psu2.hasVINUVFault(), true);
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
     }
 
     // STATUS_WORD MFR fault.
@@ -274,9 +285,10 @@
         EXPECT_EQ(psu2.hasVINUVFault(), false);
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
     }
 
-    // Ignore Temperature fault.
+    // Temperature fault.
     {
         // First STATUS_WORD with no bits set, then with temperature fault.
         PMBusExpectations expectations;
@@ -284,17 +296,18 @@
         psu2.analyze();
         // STATUS_WORD with temperature fault bit on.
         expectations.statusWordValue = (status_word::TEMPERATURE_FAULT_WARN);
-        // STATUS_INPUT, STATUS_MFR, STATUS_CML, and STATUS_VOUT fault bits ...
-        // don't care (defaults).
+        // STATUS_TEMPERATURE with fault bit(s) on.
+        expectations.statusTempValue = 0x10;
         setPMBusExpectations(mockPMBus, expectations);
         psu2.analyze();
         EXPECT_EQ(psu2.isPresent(), true);
-        EXPECT_EQ(psu2.isFaulted(), false);
+        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(), false);
+        EXPECT_EQ(psu2.hasTempFault(), true);
     }
 
     // CML fault
@@ -316,6 +329,7 @@
         EXPECT_EQ(psu2.hasVINUVFault(), false);
         EXPECT_EQ(psu2.hasCommFault(), true);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
     }
 
     // VOUT_OV_FAULT fault
@@ -329,6 +343,7 @@
             ((status_word::VOUT_FAULT) | (status_word::VOUT_OV_FAULT));
         // Turn on STATUS_VOUT fault bit(s)
         expectations.statusVOUTValue = 0xA0;
+        // STATUS_TEMPERATURE don't care (default)
         setPMBusExpectations(mockPMBus, expectations);
         psu2.analyze();
         EXPECT_EQ(psu2.isPresent(), true);
@@ -338,6 +353,7 @@
         EXPECT_EQ(psu2.hasVINUVFault(), false);
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), true);
+        EXPECT_EQ(psu2.hasTempFault(), false);
     }
 
     // Ignore fan fault
@@ -356,6 +372,7 @@
         EXPECT_EQ(psu2.hasVINUVFault(), false);
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
     }
     // TODO: ReadFailure
 }
@@ -428,6 +445,7 @@
     EXPECT_EQ(psu.hasVINUVFault(), false);
     EXPECT_EQ(psu.hasCommFault(), false);
     EXPECT_EQ(psu.hasVoutOVFault(), false);
+    EXPECT_EQ(psu.hasTempFault(), false);
 
     // STATUS_WORD with fault bits galore!
     expectations.statusWordValue = 0xFFFF;
@@ -439,6 +457,8 @@
     expectations.statusCMLValue = 0xFF;
     // STATUS_VOUT with bits on.
     expectations.statusVOUTValue = 0xFF;
+    // STATUS_TEMPERATURE with bits on.
+    expectations.statusTempValue = 0xFF;
     setPMBusExpectations(mockPMBus, expectations);
     psu.analyze();
     EXPECT_EQ(psu.isPresent(), true);
@@ -448,6 +468,7 @@
     EXPECT_EQ(psu.hasVINUVFault(), true);
     EXPECT_EQ(psu.hasCommFault(), true);
     EXPECT_EQ(psu.hasVoutOVFault(), true);
+    EXPECT_EQ(psu.hasTempFault(), true);
     EXPECT_CALL(mockPMBus, read("in1_input", _))
         .Times(1)
         .WillOnce(Return(209000));
@@ -459,6 +480,7 @@
     EXPECT_EQ(psu.hasVINUVFault(), false);
     EXPECT_EQ(psu.hasCommFault(), false);
     EXPECT_EQ(psu.hasVoutOVFault(), false);
+    EXPECT_EQ(psu.hasTempFault(), false);
 
     // TODO: Faults clear on missing/present?
 }
@@ -548,6 +570,8 @@
     expectations.statusCMLValue = 0xFF;
     // STATUS_VOUT with fault bits on.
     expectations.statusVOUTValue = 0xFF;
+    // STATUS_TEMPERATURE with fault bits on.
+    expectations.statusTempValue = 0xFF;
     setPMBusExpectations(mockPMBus, expectations);
     psu.analyze();
     EXPECT_EQ(psu.isFaulted(), true);
@@ -669,6 +693,7 @@
     expectations.statusWordValue = (status_word::VOUT_OV_FAULT);
     // STATUS_VOUT fault bit(s)
     expectations.statusVOUTValue = 0x80;
+    // STATUS_TEMPERATURE default.
     setPMBusExpectations(mockPMBus, expectations);
     psu.analyze();
     EXPECT_EQ(psu.hasVoutOVFault(), true);
@@ -678,3 +703,34 @@
     psu.analyze();
     EXPECT_EQ(psu.hasVoutOVFault(), false);
 }
+
+TEST_F(PowerSupplyTests, HasTempFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+
+    PowerSupply psu{bus, PSUInventoryPath, 3, 0x6a, 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.hasTempFault(), false);
+    // STATUS_WORD 0x0000 is powered on, no faults.
+    PMBusExpectations expectations;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasTempFault(), false);
+    // Turn fault on.
+    expectations.statusWordValue = (status_word::TEMPERATURE_FAULT_WARN);
+    // STATUS_TEMPERATURE fault bit on (OT Fault)
+    expectations.statusTempValue = 0x80;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasTempFault(), true);
+    // Back to no fault bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasTempFault(), false);
+}