psu-ng: Add in detection of PGOOD fault

If STATUS_WORD has bit 11 high (POWER_GOOD# is inactive) or bit 6 high
(unit is OFF), consider that a PGOOD fault. Lowest priority error to
check, log an error if a power supply has a PGOOD fault, call out the
power supply.

Tested:
    Simulated POWER_GOOD# / OFF and verified error logged.
    Verified normal operations on real hardware do not log error.
    Get power supply in bad state via i2cset commands:
     1. Write 0x19 to 0x02 (ON_OFF_CONFIG, OPERATION command only).
     2. Write 0x00 to 0x01 (Immediate off OPERATION command).
     3. Restart service, error logged for STATUS_WORD = 0x0800.
     4. Write 0x19 to 0x02, 0x00 to 0x01 while powered on.
     5. Error logged for STATUS_WORD=0x0840.

Change-Id: I24dd1db780510d39dc245e76977099f790a2e59e
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 2cef368..8183e37 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -276,6 +276,22 @@
                     tempFault = true;
                 }
 
+                if ((statusWord & status_word::POWER_GOOD_NEGATED) ||
+                    (statusWord & status_word::UNIT_IS_OFF))
+                {
+                    if (!pgoodFault)
+                    {
+                        log<level::ERR>(
+                            fmt::format("PGOOD fault: "
+                                        "STATUS_WORD = {:#04x}, "
+                                        "STATUS_MFR_SPECIFIC = {:#02x}",
+                                        statusWord, statusMFR)
+                                .c_str());
+                    }
+
+                    pgoodFault = true;
+                }
+
                 if (statusWord & status_word::MFR_SPECIFIC_FAULT)
                 {
                     if (!mfrFault)
@@ -314,6 +330,7 @@
                 vinUVFault = false;
                 voutOVFault = false;
                 tempFault = false;
+                pgoodFault = false;
             }
         }
         catch (const ReadFailure& e)
@@ -366,6 +383,7 @@
         cmlFault = false;
         voutOVFault = false;
         tempFault = false;
+        pgoodFault = false;
         readFail = 0;
 
         try
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index c1f7e59..ca73959 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -190,7 +190,7 @@
     bool isFaulted() const
     {
         return (hasCommFault() || vinUVFault || inputFault || voutOVFault ||
-                tempFault || mfrFault);
+                tempFault || pgoodFault || mfrFault);
     }
 
     /**
@@ -250,6 +250,15 @@
     }
 
     /**
+     * @brief Returns true if there is a PGood fault (PGOOD# inactive, or OFF
+     * bit on).
+     */
+    bool hasPgoodFault() const
+    {
+        return pgoodFault;
+    }
+
+    /**
      * @brief Returns the device path
      *
      * This can be used for error call outs.
@@ -349,6 +358,11 @@
     /** @brief True if bit 2 of STATUS_WORD low byte is on. */
     bool tempFault = false;
 
+    /** @brief True if bit 11 or 6 of STATUS_WORD is on. PGOOD# is inactive, or
+     * the unit is off.
+     */
+    bool pgoodFault = 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 b55d019..6d2815d 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -517,6 +517,18 @@
 
                     psu->setFaultLogged();
                 }
+                else if (psu->hasPgoodFault())
+                {
+                    /* POWER_GOOD# is not low, or OFF is on */
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
             }
         }
     }
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 7fb7d03..5d96fea 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -147,6 +147,7 @@
         EXPECT_EQ(psu->hasVINUVFault(), false);
         EXPECT_EQ(psu->hasVoutOVFault(), false);
         EXPECT_EQ(psu->hasTempFault(), false);
+        EXPECT_EQ(psu->hasPgoodFault(), false);
     }
     catch (...)
     {
@@ -193,6 +194,7 @@
         EXPECT_EQ(psu.hasCommFault(), false);
         EXPECT_EQ(psu.hasVoutOVFault(), false);
         EXPECT_EQ(psu.hasTempFault(), false);
+        EXPECT_EQ(psu.hasPgoodFault(), false);
     }
 
     PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
@@ -226,6 +228,7 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
 
         // Update expectations for STATUS_WORD input fault/warn
         // STATUS_INPUT fault bits ... on.
@@ -241,6 +244,7 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
 
     // STATUS_WORD INPUT/UV fault.
@@ -264,6 +268,7 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
 
     // STATUS_WORD MFR fault.
@@ -286,6 +291,7 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
 
     // Temperature fault.
@@ -308,6 +314,7 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), true);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
 
     // CML fault
@@ -330,6 +337,7 @@
         EXPECT_EQ(psu2.hasCommFault(), true);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
 
     // VOUT_OV_FAULT fault
@@ -354,6 +362,7 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), true);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
 
     // Ignore fan fault
@@ -373,7 +382,34 @@
         EXPECT_EQ(psu2.hasCommFault(), false);
         EXPECT_EQ(psu2.hasVoutOVFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), false);
     }
+
+    {
+        // PGOOD/OFF fault.
+        // First STATUS_WORD with no bits set.
+        PMBusExpectations expectations;
+        setPMBusExpectations(mockPMBus, expectations);
+        psu2.analyze();
+        EXPECT_EQ(psu2.isFaulted(), false);
+        // POWER_GOOD# inactive, and OFF bit on.
+        expectations.statusWordValue =
+            ((status_word::POWER_GOOD_NEGATED) | (status_word::UNIT_IS_OFF));
+        // STATUS_INPUT, STATUS_MFR, STATUS_CML, STATUS_VOUT, and
+        // STATUS_TEMPERATURE: Don't care if bits set or not (defaults).
+        setPMBusExpectations(mockPMBus, expectations);
+        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(), false);
+        EXPECT_EQ(psu2.hasTempFault(), false);
+        EXPECT_EQ(psu2.hasPgoodFault(), true);
+    }
+
     // TODO: ReadFailure
 }
 
@@ -446,6 +482,7 @@
     EXPECT_EQ(psu.hasCommFault(), false);
     EXPECT_EQ(psu.hasVoutOVFault(), false);
     EXPECT_EQ(psu.hasTempFault(), false);
+    EXPECT_EQ(psu.hasPgoodFault(), false);
 
     // STATUS_WORD with fault bits galore!
     expectations.statusWordValue = 0xFFFF;
@@ -469,6 +506,7 @@
     EXPECT_EQ(psu.hasCommFault(), true);
     EXPECT_EQ(psu.hasVoutOVFault(), true);
     EXPECT_EQ(psu.hasTempFault(), true);
+    EXPECT_EQ(psu.hasPgoodFault(), true);
     EXPECT_CALL(mockPMBus, read("in1_input", _))
         .Times(1)
         .WillOnce(Return(209000));
@@ -481,6 +519,7 @@
     EXPECT_EQ(psu.hasCommFault(), false);
     EXPECT_EQ(psu.hasVoutOVFault(), false);
     EXPECT_EQ(psu.hasTempFault(), false);
+    EXPECT_EQ(psu.hasPgoodFault(), false);
 
     // TODO: Faults clear on missing/present?
 }
@@ -734,3 +773,42 @@
     psu.analyze();
     EXPECT_EQ(psu.hasTempFault(), false);
 }
+
+TEST_F(PowerSupplyTests, HasPgoodFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+
+    PowerSupply psu{bus, PSUInventoryPath, 3, 0x6b, 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.hasPgoodFault(), false);
+    // STATUS_WORD 0x0000 is powered on, no faults.
+    PMBusExpectations expectations;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPgoodFault(), false);
+    // Turn PGOOD# off (fault on).
+    expectations.statusWordValue = (status_word::POWER_GOOD_NEGATED);
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPgoodFault(), true);
+    // Back to no fault bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPgoodFault(), false);
+    // Turn OFF bit on
+    expectations.statusWordValue = (status_word::UNIT_IS_OFF);
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPgoodFault(), true);
+    // Back to no fault bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPgoodFault(), false);
+}