psu-ng: Add in handling of specific MFR faults

Add in a function to determine what the various bits in statusMFR may be
indicating for a fault, based on the type of power supply (device driver
bound).

Add in PS_Kill, 12Vcs, and 12V CS faults for IBM power supply types.

Add in creating error logs for PS_Kill, 12Vcs, and 12V CS faults. The
12Vcs and 12V CS faults can essentially be treated the same as VOUT_UV
faults (same error type, same call out).

Tested:
Verified no PS_Kill, 12Vcs, or 12V CS fault on normal Rainier 2S4U

Simulated PS_Kill fault:
MFR fault: STATUS_WORD = 0x1840 STATUS_MFR_SPECIFIC = 0x10

Simulated 12Vcs fault:
PGOOD fault: STATUS_WORD = 0x1840, STATUS_MFR_SPECIFIC = 0x40
MFR fault: STATUS_WORD = 0x1840 STATUS_MFR_SPECIFIC = 0x40

Simulated 12V CS fault/warning:
MFR fault: STATUS_WORD = 0x1000 STATUS_MFR_SPECIFIC = 0x80

Change-Id: Ie89a58836ecec86dfa2e124eb6ab03e9dccce929
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 908f04f..a48441d 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -189,6 +189,28 @@
     }
 }
 
+void PowerSupply::determineMFRFault()
+{
+    if (bindPath.string().find("ibm-cffps") != std::string::npos)
+    {
+        // IBM MFR_SPECIFIC[4] is PS_Kill fault
+        if (statusMFR & 0x10)
+        {
+            psKillFault = true;
+        }
+        // IBM MFR_SPECIFIC[6] is 12Vcs fault.
+        if (statusMFR & 0x40)
+        {
+            ps12VcsFault = true;
+        }
+        // IBM MFR_SPECIFIC[7] is 12V Current-Share fault.
+        if (statusMFR & 0x80)
+        {
+            psCS12VFault = true;
+        }
+    }
+}
+
 void PowerSupply::analyze()
 {
     using namespace phosphor::pmbus;
@@ -360,6 +382,7 @@
                     }
 
                     mfrFault = true;
+                    determineMFRFault();
                 }
 
                 if (statusWord & status_word::VIN_UV_FAULT)
@@ -395,6 +418,9 @@
                                          .c_str());
                     pgoodFault = 0;
                 }
+                psKillFault = false;
+                ps12VcsFault = false;
+                psCS12VFault = false;
             }
         }
         catch (const ReadFailure& e)
@@ -451,6 +477,9 @@
         fanFault = false;
         tempFault = false;
         pgoodFault = 0;
+        psKillFault = false;
+        ps12VcsFault = false;
+        psCS12VFault = false;
         readFail = 0;
 
         try
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index ec523ae..c995376 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -302,6 +302,30 @@
     }
 
     /**
+     * @brief Return true if there is a PS_Kill fault.
+     */
+    bool hasPSKillFault() const
+    {
+        return psKillFault;
+    }
+
+    /**
+     * @brief Returns true if there is a 12Vcs (standy power) fault.
+     */
+    bool hasPS12VcsFault() const
+    {
+        return ps12VcsFault;
+    }
+
+    /**
+     * @brief Returns true if there is a 12V current-share fault.
+     */
+    bool hasPSCS12VFault() const
+    {
+        return psCS12VFault;
+    }
+
+    /**
      * @brief Returns the device path
      *
      * This can be used for error call outs.
@@ -426,10 +450,35 @@
      */
     int pgoodFault = 0;
 
+    /**
+     * @brief Power Supply Kill fault.
+     */
+    bool psKillFault = false;
+
+    /**
+     * @brief Power Supply 12Vcs fault (standby power).
+     */
+    bool ps12VcsFault = false;
+
+    /**
+     * @brief Power Supply Current-Share fault in 12V domain.
+     */
+    bool psCS12VFault = false;
+
     /** @brief Count of the number of read failures. */
     size_t readFail = 0;
 
     /**
+     * @brief Determine possible manufacturer-specific faults from bits in
+     * STATUS_MFR.
+     *
+     * The bits in the STATUS_MFR_SPECIFIC command response have "Manufacturer
+     * Defined" meanings. Determine which faults, if any, are present based on
+     * the power supply (device driver) type.
+     */
+    void determineMFRFault();
+
+    /**
      * @brief D-Bus path to use for this power supply's inventory status.
      **/
     std::string inventoryPath;
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index 6fa824a..ac0901b 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -429,7 +429,7 @@
         for (auto& psu : psus)
         {
             additionalData.clear();
-            // TODO: Fault priorities #918
+
             if (!psu->isFaultLogged() && !psu->isPresent())
             {
                 std::map<std::string, std::string> requiredPSUsData;
@@ -494,6 +494,13 @@
                                 additionalData);
                     psu->setFaultLogged();
                 }
+                else if (psu->hasPSKillFault())
+                {
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
+                        additionalData);
+                    psu->setFaultLogged();
+                }
                 else if (psu->hasVoutOVFault())
                 {
                     // Include STATUS_VOUT for Vout faults.
@@ -521,7 +528,8 @@
 
                     psu->setFaultLogged();
                 }
-                else if (psu->hasVoutUVFault())
+                else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
+                         psu->hasPSCS12VFault())
                 {
                     // Include STATUS_VOUT for Vout faults.
                     additionalData["STATUS_VOUT"] =
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 222d9c3..a762ecb 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -159,6 +159,9 @@
         EXPECT_EQ(psu->hasFanFault(), false);
         EXPECT_EQ(psu->hasTempFault(), false);
         EXPECT_EQ(psu->hasPgoodFault(), false);
+        EXPECT_EQ(psu->hasPSKillFault(), false);
+        EXPECT_EQ(psu->hasPS12VcsFault(), false);
+        EXPECT_EQ(psu->hasPSCS12VFault(), false);
     }
     catch (...)
     {
@@ -209,6 +212,9 @@
         EXPECT_EQ(psu.hasFanFault(), false);
         EXPECT_EQ(psu.hasTempFault(), false);
         EXPECT_EQ(psu.hasPgoodFault(), false);
+        EXPECT_EQ(psu.hasPSKillFault(), false);
+        EXPECT_EQ(psu.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu.hasPSCS12VFault(), false);
     }
 
     PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
@@ -219,7 +225,6 @@
     // Always return 1 to indicate present.
     // Each analyze() call will trigger a read of the presence GPIO.
     EXPECT_CALL(*mockPresenceGPIO2, read()).WillRepeatedly(Return(1));
-    // Not present until analyze() does the GPIO read.
     EXPECT_EQ(psu2.isPresent(), false);
 
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu2.getPMBus());
@@ -255,6 +260,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
 
         // Update expectations for STATUS_WORD input fault/warn
         // STATUS_INPUT fault bits ... on.
@@ -274,6 +282,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // STATUS_WORD INPUT/UV fault.
@@ -301,6 +312,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // STATUS_WORD MFR fault.
@@ -327,6 +341,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), true);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), true);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), true);
     }
 
     // Temperature fault.
@@ -353,6 +370,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), true);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // CML fault
@@ -379,6 +399,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // VOUT_OV_FAULT fault
@@ -407,6 +430,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // IOUT_OC_FAULT fault
@@ -433,6 +459,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // VOUT_UV_FAULT
@@ -459,6 +488,9 @@
         EXPECT_EQ(psu2.hasFanFault(), false);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // Fan fault
@@ -484,6 +516,9 @@
         EXPECT_EQ(psu2.hasFanFault(), true);
         EXPECT_EQ(psu2.hasTempFault(), false);
         EXPECT_EQ(psu2.hasPgoodFault(), false);
+        EXPECT_EQ(psu2.hasPSKillFault(), false);
+        EXPECT_EQ(psu2.hasPS12VcsFault(), false);
+        EXPECT_EQ(psu2.hasPSCS12VFault(), false);
     }
 
     // PGOOD/OFF fault. Deglitched, needs to reach DEGLITCH_LIMIT.
@@ -614,6 +649,9 @@
     EXPECT_EQ(psu.hasFanFault(), false);
     EXPECT_EQ(psu.hasTempFault(), false);
     EXPECT_EQ(psu.hasPgoodFault(), false);
+    EXPECT_EQ(psu.hasPSKillFault(), false);
+    EXPECT_EQ(psu.hasPS12VcsFault(), false);
+    EXPECT_EQ(psu.hasPSCS12VFault(), false);
 
     // STATUS_WORD with fault bits galore!
     expectations.statusWordValue = 0xFFFF;
@@ -655,6 +693,9 @@
     psu.analyze();
     // DEGLITCH_LIMIT reached for pgoodFault
     EXPECT_EQ(psu.hasPgoodFault(), true);
+    EXPECT_EQ(psu.hasPSKillFault(), true);
+    EXPECT_EQ(psu.hasPS12VcsFault(), true);
+    EXPECT_EQ(psu.hasPSCS12VFault(), true);
 
     EXPECT_CALL(mockPMBus, read("in1_input", _))
         .Times(1)
@@ -672,6 +713,9 @@
     EXPECT_EQ(psu.hasFanFault(), false);
     EXPECT_EQ(psu.hasTempFault(), false);
     EXPECT_EQ(psu.hasPgoodFault(), false);
+    EXPECT_EQ(psu.hasPSKillFault(), false);
+    EXPECT_EQ(psu.hasPS12VcsFault(), false);
+    EXPECT_EQ(psu.hasPSCS12VFault(), false);
 
     // TODO: Faults clear on missing/present?
 }
@@ -1083,3 +1127,129 @@
     psu.analyze();
     EXPECT_EQ(psu.hasPgoodFault(), false);
 }
+
+TEST_F(PowerSupplyTests, HasPSKillFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+    PowerSupply psu{bus, PSUInventoryPath, 4, 0x6d, PSUGPIOLineName};
+    MockedGPIOInterface* mockPresenceGPIO =
+        static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSKillFault(), false);
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    // STATUS_WORD 0x0000 is powered on, no faults.
+    PMBusExpectations expectations;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSKillFault(), false);
+    // Next return STATUS_WORD with MFR fault bit on.
+    expectations.statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
+    // STATUS_MFR_SPEFIC with bit(s) on.
+    expectations.statusMFRValue = 0xFF;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSKillFault(), true);
+    // Back to no bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSKillFault(), false);
+    // Next return STATUS_WORD with MFR fault bit on.
+    expectations.statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
+    // STATUS_MFR_SPEFIC with bit 4 on.
+    expectations.statusMFRValue = 0x10;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSKillFault(), true);
+    // Back to no bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSKillFault(), false);
+}
+
+TEST_F(PowerSupplyTests, HasPS12VcsFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+    PowerSupply psu{bus, PSUInventoryPath, 5, 0x6e, PSUGPIOLineName};
+    MockedGPIOInterface* mockPresenceGPIO =
+        static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
+    EXPECT_EQ(psu.hasPS12VcsFault(), false);
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    // STATUS_WORD 0x0000 is powered on, no faults.
+    PMBusExpectations expectations;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPS12VcsFault(), false);
+    // Next return STATUS_WORD with MFR fault bit on.
+    expectations.statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
+    // STATUS_MFR_SPEFIC with bit(s) on.
+    expectations.statusMFRValue = 0xFF;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPS12VcsFault(), true);
+    // Back to no bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPS12VcsFault(), false);
+    // Next return STATUS_WORD with MFR fault bit on.
+    expectations.statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
+    // STATUS_MFR_SPEFIC with bit 6 on.
+    expectations.statusMFRValue = 0x40;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPS12VcsFault(), true);
+    // Back to no bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPS12VcsFault(), false);
+}
+
+TEST_F(PowerSupplyTests, HasPSCS12VFault)
+{
+    auto bus = sdbusplus::bus::new_default();
+    PowerSupply psu{bus, PSUInventoryPath, 6, 0x6f, PSUGPIOLineName};
+    MockedGPIOInterface* mockPresenceGPIO =
+        static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
+    // Always return 1 to indicate present.
+    EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSCS12VFault(), false);
+    MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
+    // STATUS_WORD 0x0000 is powered on, no faults.
+    PMBusExpectations expectations;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSCS12VFault(), false);
+    // Next return STATUS_WORD with MFR fault bit on.
+    expectations.statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
+    // STATUS_MFR_SPEFIC with bit(s) on.
+    expectations.statusMFRValue = 0xFF;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSCS12VFault(), true);
+    // Back to no bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSCS12VFault(), false);
+    // Next return STATUS_WORD with MFR fault bit on.
+    expectations.statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
+    // STATUS_MFR_SPEFIC with bit 7 on.
+    expectations.statusMFRValue = 0x80;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSCS12VFault(), true);
+    // Back to no bits on in STATUS_WORD
+    expectations.statusWordValue = 0;
+    setPMBusExpectations(mockPMBus, expectations);
+    psu.analyze();
+    EXPECT_EQ(psu.hasPSCS12VFault(), false);
+}