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);
+}
diff --git a/pmbus.hpp b/pmbus.hpp
index 43d7dd8..1867e6b 100644
--- a/pmbus.hpp
+++ b/pmbus.hpp
@@ -171,6 +171,8 @@
                              Type type) = 0;
     virtual void findHwmonDir() = 0;
     virtual const fs::path& path() const = 0;
+    virtual std::string insertPageNum(const std::string& templateName,
+                                      size_t page) = 0;
 };
 
 /**
@@ -352,8 +354,8 @@
      *
      * @return string - the new string with the page number in it
      */
-    static std::string insertPageNum(const std::string& templateName,
-                                     size_t page);
+    std::string insertPageNum(const std::string& templateName,
+                              size_t page) override;
 
     /**
      * Finds the path relative to basePath to the hwmon directory