psu-ng: Detect CML fault

If the STATUS_WORD has the CML (Communication, Memory, Logic) fault bit
on, bit 1 in lower byte of STATUS_WORD, then read STATUS_CML, and treat
the fault as another variety of a communication fault/error.

Change-Id: Iba368683734777874ba54ec845cbc94b00010b68
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 64e0f26..ca9c084 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -210,6 +210,21 @@
             {
                 statusInput = pmbusIntf->read(STATUS_INPUT, Type::Debug);
                 statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug);
+                statusCML = pmbusIntf->read(STATUS_CML, Type::Debug);
+                if (statusWord & status_word::CML_FAULT)
+                {
+                    if (!cmlFault)
+                    {
+                        log<level::INFO>(
+                            fmt::format("CML fault: STATUS_WORD = {:#04x}, "
+                                        "STATUS_CML = {:#02x}",
+                                        statusWord, statusCML)
+                                .c_str());
+                    }
+                    faultFound = true;
+                    cmlFault = true;
+                }
+
                 if (statusWord & status_word::INPUT_FAULT_WARN)
                 {
                     if (!inputFault)
@@ -260,6 +275,7 @@
             else
             {
                 faultFound = false;
+                cmlFault = false;
                 inputFault = false;
                 mfrFault = false;
                 vinUVFault = false;
@@ -313,6 +329,7 @@
         mfrFault = false;
         statusMFR = 0;
         vinUVFault = false;
+        cmlFault = false;
         readFail = 0;
 
         try
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 86ce57c..d322043 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -161,6 +161,14 @@
     }
 
     /**
+     * @brief Returns the last read value from STATUS_CML.
+     */
+    uint64_t getStatusCML() const
+    {
+        return statusCML;
+    }
+
+    /**
      * @brief Returns true if a fault was found.
      */
     bool isFaulted() const
@@ -252,7 +260,7 @@
      */
     bool hasCommFault() const
     {
-        return readFail >= LOG_LIMIT;
+        return ((readFail >= LOG_LIMIT) || (cmlFault));
     }
 
     /**
@@ -277,12 +285,18 @@
     /** @brief Will be updated to the latest/lastvalue read from STATUS_MFR.*/
     uint64_t statusMFR = 0;
 
+    /** @brief Will be updated to the latest/last value read from STATUS_CML.*/
+    uint64_t statusCML = 0;
+
     /** @brief True if a fault has already been found and not cleared */
     bool faultFound = false;
 
     /** @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. */
+    bool cmlFault = false;
+
     /** @brief True if bit 5 of STATUS_WORD high byte is on. */
     bool inputFault = false;
 
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index 4cfa801..c63207f 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -411,6 +411,8 @@
 
                 if (psu->hasCommFault())
                 {
+                    additionalData["STATUS_CML"] =
+                        fmt::format("{:#02x}", psu->getStatusCML());
                     /* Attempts to communicate with the power supply have
                      * reached there limit. Create an error. */
                     additionalData["CALLOUT_DEVICE_PATH"] =
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 7411c34..ef87a15 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -141,6 +141,7 @@
     EXPECT_EQ(psu.hasInputFault(), false);
     EXPECT_EQ(psu.hasMFRFault(), false);
     EXPECT_EQ(psu.hasVINUVFault(), false);
+    EXPECT_EQ(psu.hasCommFault(), false);
 
     PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
     // In order to get the various faults tested, the power supply needs to
@@ -151,11 +152,11 @@
 
     EXPECT_EQ(psu2.isPresent(), false);
 
-    // STATUS_WORD 0x0000 is powered on, no faults (0x0000).
     MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu2.getPMBus());
     // Presence change from missing to present will trigger in1_input read in
     // an attempt to get CLEAR_FAULTS called.
     EXPECT_CALL(mockPMBus, read(READ_VIN, _)).Times(1).WillOnce(Return(206000));
+    // STATUS_WORD 0x0000 is powered on, no faults.
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(0x0000));
@@ -165,26 +166,30 @@
     EXPECT_EQ(psu2.hasInputFault(), false);
     EXPECT_EQ(psu2.hasMFRFault(), false);
     EXPECT_EQ(psu2.hasVINUVFault(), false);
+    EXPECT_EQ(psu2.hasCommFault(), false);
 
     // STATUS_WORD input fault/warn
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(status_word::INPUT_FAULT_WARN));
     // Due to the fault bit on in STATUS_WORD, there will also be a read of
-    // STATUS_INPUT and STATUS_MFR, so there should be 3 reads total expected.
+    // STATUS_INPUT, STATUS_MFR, and STATUS_CML, so there should be 4 reads
+    // total expected.
     // STATUS_INPUT fault bits ... on.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0x38));
     // STATUS_MFR don't care
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0));
-
+    // STATUS_CML don't care
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0));
     psu2.analyze();
     EXPECT_EQ(psu2.isPresent(), true);
     EXPECT_EQ(psu2.isFaulted(), true);
     EXPECT_EQ(psu2.hasInputFault(), true);
     EXPECT_EQ(psu2.hasMFRFault(), false);
     EXPECT_EQ(psu2.hasVINUVFault(), false);
+    EXPECT_EQ(psu2.hasCommFault(), false);
 
     // STATUS_WORD INPUT/UV fault.
     // First need it to return good status, then the fault
@@ -199,7 +204,8 @@
         .WillOnce(Return(0x38));
     // STATUS_MFR don't care
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0));
-
+    // STATUS_CML don't care
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0));
     psu2.analyze();
     psu2.analyze();
     EXPECT_EQ(psu2.isPresent(), true);
@@ -207,6 +213,7 @@
     EXPECT_EQ(psu2.hasInputFault(), true);
     EXPECT_EQ(psu2.hasMFRFault(), false);
     EXPECT_EQ(psu2.hasVINUVFault(), true);
+    EXPECT_EQ(psu2.hasCommFault(), false);
 
     // STATUS_WORD MFR fault.
     // First need it to return good status, then the fault
@@ -220,7 +227,8 @@
         .WillOnce(Return(0x00));
     // STATUS_MFR bits on.
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0xFF));
-
+    // STATUS_CML don't care
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0));
     psu2.analyze();
     psu2.analyze();
     EXPECT_EQ(psu2.isPresent(), true);
@@ -228,6 +236,7 @@
     EXPECT_EQ(psu2.hasInputFault(), false);
     EXPECT_EQ(psu2.hasMFRFault(), true);
     EXPECT_EQ(psu2.hasVINUVFault(), false);
+    EXPECT_EQ(psu2.hasCommFault(), false);
 
     // Ignore Temperature fault.
     // First STATUS_WORD with no bits set, then with temperature fault.
@@ -235,14 +244,16 @@
         .Times(2)
         .WillOnce(Return(0x0000))
         .WillOnce(Return(status_word::TEMPERATURE_FAULT_WARN));
-    // If STATUS_WORD bits set, should read STATUS_MFR_SPECIFIC and STATUS_INPUT
+    // If the STATUS_WORD has bits on, STATUS_MFR_SPECIFIC, STATUS_INPUT, and
+    // STATUS_CML will also be read.
     // STATUS_INPUT fault bits ... don't care.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0x00));
     // STATUS_MFR don't care
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0));
-
+    // STATUS_CML don't care
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0));
     psu2.analyze();
     psu2.analyze();
     EXPECT_EQ(psu2.isPresent(), true);
@@ -250,6 +261,31 @@
     EXPECT_EQ(psu2.hasInputFault(), false);
     EXPECT_EQ(psu2.hasMFRFault(), false);
     EXPECT_EQ(psu2.hasVINUVFault(), false);
+    EXPECT_EQ(psu2.hasCommFault(), false);
+
+    // CML fault
+    // First STATUS_WORD wit no bits set, then with CML fault.
+    // STATUS_WORD with CML fault bit on.
+    EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
+        .Times(2)
+        .WillOnce(Return(0x0000))
+        .WillOnce(Return(status_word::CML_FAULT));
+    psu2.analyze();
+    // If the STATUS_WORD has bits on, STATUS_MFR_SPECIFIC, STATUS_INPUT, and
+    // STATUS_CML will also be read.
+    EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
+        .Times(1)
+        .WillOnce(Return(0x00));
+    EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0x00));
+    // Turn on STATUS_CML fault bit(s)
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0xFF));
+    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(), true);
 
     // Ignore fan fault
     // First STATUS_WORD with no bits set, then with fan fault.
@@ -257,13 +293,14 @@
         .Times(2)
         .WillOnce(Return(0x0000))
         .WillOnce(Return(status_word::FAN_FAULT));
-    // STATUS_WORD bits set causes read STATUS_MFR_SPECIFIC and STATUS_INPUT.
+    // If the STATUS_WORD has bits on, STATUS_MFR_SPECIFIC, STATUS_INPUT, and
+    // STATUS_CML will also be read.
     // Don't care if bits set or not.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0x00));
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0));
-
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0));
     psu2.analyze();
     psu2.analyze();
     EXPECT_EQ(psu2.isPresent(), true);
@@ -271,6 +308,7 @@
     EXPECT_EQ(psu2.hasInputFault(), false);
     EXPECT_EQ(psu2.hasMFRFault(), false);
     EXPECT_EQ(psu2.hasVINUVFault(), false);
+    EXPECT_EQ(psu2.hasCommFault(), false);
 
     // TODO: ReadFailure
 }
@@ -336,24 +374,28 @@
     EXPECT_EQ(psu.hasInputFault(), false);
     EXPECT_EQ(psu.hasMFRFault(), false);
     EXPECT_EQ(psu.hasVINUVFault(), false);
+    EXPECT_EQ(psu.hasCommFault(), false);
     // STATUS_WORD with fault bits galore!
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(0xFFFF));
-    // If STATUS_WORD has any fault bits on, STATUS_MFR_SPECIFIC and
-    // STATUS_INPUT will be read.
+    // If STATUS_WORD has any fault bits on, STATUS_MFR_SPECIFIC, STATUS_INPUT
+    // and STATUS_CML will be read.
     // STATUS_INPUT with fault bits on.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0xFF));
     // STATUS_MFR_SPEFIC with bits on.
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0xFF));
+    // STATUS_CML with bits on.
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0xFF));
     psu.analyze();
     EXPECT_EQ(psu.isPresent(), true);
     EXPECT_EQ(psu.isFaulted(), true);
     EXPECT_EQ(psu.hasInputFault(), true);
     EXPECT_EQ(psu.hasMFRFault(), true);
     EXPECT_EQ(psu.hasVINUVFault(), true);
+    EXPECT_EQ(psu.hasCommFault(), true);
     EXPECT_CALL(mockPMBus, read("in1_input", _))
         .Times(1)
         .WillOnce(Return(209000));
@@ -363,6 +405,7 @@
     EXPECT_EQ(psu.hasInputFault(), false);
     EXPECT_EQ(psu.hasMFRFault(), false);
     EXPECT_EQ(psu.hasVINUVFault(), false);
+    EXPECT_EQ(psu.hasCommFault(), false);
 
     // TODO: Faults clear on missing/present?
 }
@@ -445,13 +488,16 @@
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(0xFFFF));
-    // If STATUS_WORD has bit(s) on, STATUS_MFR_SPECIFIC and STATUS_INPUT read.
+    // Fault bit(s) on in STATUS_WORD causes read of STATUS_MFR_SPECIFIC,
+    // STATUS_INPUT, and STATUS_CML.
     // STATUS_INPUT with fault bits on.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0xFF));
     // STATUS_MFR_SPECIFIC with faults bits on.
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0xFF));
+    // STATUS_CML with faults bits on.
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0xFF));
     psu.analyze();
     EXPECT_EQ(psu.isFaulted(), true);
 }
@@ -475,13 +521,16 @@
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(status_word::INPUT_FAULT_WARN));
-    // If STATUS_WORD has bit(s) on, STATUS_MFR_SPECIFIC and STATUS_INPUT read.
+    // Fault bit(s) on in STATUS_WORD causes read of STATUS_MFR_SPECIFIC,
+    // STATUS_INPUT, and STATUS_CML.
     // STATUS_INPUT with an input fault bit on.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0x80));
     // STATUS_MFR don't care.
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0x00));
+    // STATUS_CML don't care.
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0x00));
     psu.analyze();
     EXPECT_EQ(psu.hasInputFault(), true);
     // STATUS_WORD with no bits on.
@@ -514,13 +563,16 @@
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(status_word::MFR_SPECIFIC_FAULT));
-    // If STATUS_WORD has bits on, STATUS_MFR_SPECIFIC and STATUS_INPUT read.
+    // Fault bit(s) on in STATUS_WORD causes read of STATUS_MFR_SPECIFIC,
+    // STATUS_INPUT, and STATUS_CML.
     // STATUS_INPUT don't care
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0x00));
     // STATUS_MFR_SPEFIC with bit(s) on.
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0xFF));
+    // STATUS_CML don't care.
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0x00));
     psu.analyze();
     EXPECT_EQ(psu.hasMFRFault(), true);
     // Back to no bits on in STATUS_WORD
@@ -552,15 +604,17 @@
     EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
         .Times(1)
         .WillOnce(Return(status_word::VIN_UV_FAULT));
-    // Fault bits on in STATUS_WORD causes read of STATUS_MFR_SPECIFIC and
-    // STATUS_INPUT.
-    // Curious disagreement between PMBus Spec. Part II Figure 16 and 33.
-    // Go by Figure 16, and assume bits on in STATUS_INPUT.
+    // Fault bit(s) on in STATUS_WORD causes read of STATUS_MFR_SPECIFIC,
+    // STATUS_INPUT, and STATUS_CML.
+    // Curious disagreement between PMBus Spec. Part II Figure 16 and 33. Go by
+    // Figure 16, and assume bits on in STATUS_INPUT.
     EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
         .Times(1)
         .WillOnce(Return(0x18));
     // STATUS_MFR don't care.
     EXPECT_CALL(mockPMBus, read(STATUS_MFR, _)).Times(1).WillOnce(Return(0x00));
+    // STATUS_CML don't care.
+    EXPECT_CALL(mockPMBus, read(STATUS_CML, _)).Times(1).WillOnce(Return(0x00));
 
     psu.analyze();
     EXPECT_EQ(psu.hasVINUVFault(), true);
diff --git a/pmbus.hpp b/pmbus.hpp
index 4bdc23f..43d7dd8 100644
--- a/pmbus.hpp
+++ b/pmbus.hpp
@@ -60,6 +60,9 @@
 // overtemperature warning, undertemperature warning, undertemperature fault.
 constexpr auto STATUS_TEMPERATURE = "status0_temp";
 
+// Reports on the communication, memory, logic fault(s).
+constexpr auto STATUS_CML = "status0_cml";
+
 namespace status_word
 {
 constexpr auto VOUT_FAULT = 0x8000;
@@ -101,6 +104,9 @@
 // STATUS_WORD. Bit 2 of the low byte (STATUS_BYTE).
 constexpr auto TEMPERATURE_FAULT_WARN = 0x0004;
 
+// The bit mask representing the CML (Communication, Memory, and/or Logic) fault
+// bit of the STATUS_WORD. Bit 1 of the low byte (STATUS_BYTE).
+constexpr auto CML_FAULT = 0x0002;
 } // namespace status_word
 
 namespace status_vout