Fill in PGOOD fault checking code

Fill in the code to check for a PGOOD fault.  This is where
the power sequencer device detects that one of its child devices
lost PGOOD.  A separate error log will be created for each input
that has a fault.  Each input will only have an error logged against
it once for the lifetime of the object.

Errors are detected by reading the real time status of the PGOOD input,
which is exposed as a GPIO by the device driver.

Ideally we would be able to use a summary bit in the status_word
register to see if there is an error before doing any GPIO reads,
but as the device drivers sends a clear faults every time we read
that register, and the GPI fault bits are edge triggered in the
mfr_status register that feeds status_word, we would never detect
any failures.  If this was ever fixed in the core PMBus device
driver code, we could add this functionality in and save some
CPU cycles.

Change-Id: I5000f2ebc2b22dcca946154afd2405b29734ccaf
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
diff --git a/power-sequencer/types.hpp b/power-sequencer/types.hpp
index 7eba8d6..b236cc9 100644
--- a/power-sequencer/types.hpp
+++ b/power-sequencer/types.hpp
@@ -12,12 +12,22 @@
 namespace ucd90160
 {
 
+constexpr auto gpiNumField = 0;
+constexpr auto pinIDField = 1;
+constexpr auto gpiNameField = 2;
+constexpr auto pollField = 3;
+
+using GPIConfig = std::tuple<size_t, size_t, std::string, bool>;
+
+using GPIConfigs = std::vector<GPIConfig>;
+
 using RailNames = std::vector<std::string>;
 
 constexpr auto pathField = 0;
 constexpr auto railNamesField = 1;
+constexpr auto gpiConfigField = 2;
 
-using DeviceDefinition = std::tuple<std::string, RailNames>;
+using DeviceDefinition = std::tuple<std::string, RailNames, GPIConfigs>;
 
 //Maps a device instance to its definition
 using DeviceMap = std::map<size_t, DeviceDefinition>;
diff --git a/power-sequencer/ucd90160.cpp b/power-sequencer/ucd90160.cpp
index 156c8f4..05db613 100644
--- a/power-sequencer/ucd90160.cpp
+++ b/power-sequencer/ucd90160.cpp
@@ -39,6 +39,7 @@
 constexpr auto NUM_PAGES = 16;
 
 namespace fs = std::experimental::filesystem;
+using namespace gpio;
 using namespace pmbus;
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
@@ -178,7 +179,84 @@
 
 bool UCD90160::checkPGOODFaults(bool polling)
 {
-    return false;
+    bool errorCreated = false;
+
+    //While PGOOD faults could show up in MFR_STATUS (and we could then
+    //check the summary bit in STATUS_WORD first), they are edge triggered,
+    //and as the device driver sends a clear faults command every time we
+    //do a read, we will never see them.  So, we'll have to just read the
+    //real time GPI status GPIO.
+
+    //Check only the GPIs configured on this system.
+    auto& gpiConfigs = std::get<ucd90160::gpiConfigField>(
+            deviceMap.find(getInstance())->second);
+
+    for (const auto& gpiConfig : gpiConfigs)
+    {
+        auto gpiNum = std::get<ucd90160::gpiNumField>(gpiConfig);
+        auto doPoll = std::get<ucd90160::pollField>(gpiConfig);
+
+        //Can skip this one if there is already an error on this input,
+        //or we are polling and these inputs don't need to be polled
+        //(because errors on them are fatal).
+        if (isPGOODFaultLogged(gpiNum) || (polling && !doPoll))
+        {
+            continue;
+        }
+
+        //The real time status is read via the pin ID
+        auto pinID = std::get<ucd90160::pinIDField>(gpiConfig);
+        auto gpio = gpios.find(pinID);
+        Value gpiStatus;
+
+        try
+        {
+            //The first time through, create the GPIO objects
+            if (gpio == gpios.end())
+            {
+                gpios.emplace(
+                        pinID,
+                        std::make_unique<GPIO>(
+                                gpioDevice, pinID, Direction::input));
+                gpio = gpios.find(pinID);
+            }
+
+            gpiStatus = gpio->second->read();
+        }
+        catch (std::exception& e)
+        {
+            if (!accessError)
+            {
+                log<level::ERR>(e.what());
+                accessError = true;
+            }
+            continue;
+        }
+
+        if (gpiStatus == Value::low)
+        {
+            auto& gpiName = std::get<ucd90160::gpiNameField>(gpiConfig);
+            auto status = (gpiStatus == Value::low) ? 0 : 1;
+
+            util::NamesValues nv;
+            nv.add("STATUS_WORD", readStatusWord());
+            nv.add("MFR_STATUS", readMFRStatus());
+            nv.add("INPUT_STATUS", status);
+
+            using metadata =  xyz::openbmc_project::Power::Fault::
+                    PowerSequencerPGOODFault;
+
+            report<PowerSequencerPGOODFault>(
+                    metadata::INPUT_NUM(gpiNum),
+                    metadata::INPUT_NAME(gpiName.c_str()),
+                    metadata::RAW_STATUS(nv.get().c_str()));
+
+            setPGOODFaultLogged(gpiNum);
+            errorCreated = true;
+        }
+    }
+
+    return errorCreated;
 }
 
 void UCD90160::createPowerFaultLog()
diff --git a/power-sequencer/ucd90160.hpp b/power-sequencer/ucd90160.hpp
index 09a3abf..a24443e 100644
--- a/power-sequencer/ucd90160.hpp
+++ b/power-sequencer/ucd90160.hpp
@@ -5,6 +5,7 @@
 #include <map>
 #include <vector>
 #include "device.hpp"
+#include "gpio.hpp"
 #include "pmbus.hpp"
 #include "types.hpp"
 
@@ -136,17 +137,57 @@
         }
 
         /**
+         * Says if we've already logged a PGOOD fault
+         *
+         * The policy is only 1 of the same errors will
+         * be logged for the duration of a class instance.
+         *
+         * @param[in] input - the input to check
+         *
+         * @return bool - if we've already logged a fault against
+         *                this input
+         */
+        inline bool isPGOODFaultLogged(uint32_t input) const
+        {
+            return std::find(pgoodErrors.begin(),
+                             pgoodErrors.end(),
+                             input) != pgoodErrors.end();
+        }
+
+        /**
+         * Saves that a PGOOD fault has been logged
+         *
+         * @param[in] input - the input the error was logged against
+         */
+        inline void setPGOODFaultLogged(uint32_t input)
+        {
+            pgoodErrors.push_back(input);
+        }
+
+        /**
          * List of pages that Vout errors have
          * already been logged against
          */
         std::vector<uint32_t> voutErrors;
 
         /**
+         * List of inputs that PGOOD errors have
+         * already been logged against
+         */
+        std::vector<uint32_t> pgoodErrors;
+
+        /**
          * The read/write interface to this hardware
          */
         pmbus::PMBus interface;
 
         /**
+         * A map of GPI pin IDs to the GPIO object
+         * used to access them
+         */
+        std::map<size_t, std::unique_ptr<gpio::GPIO>> gpios;
+
+        /**
          * Keeps track of device access errors to avoid repeatedly
          * logging errors for bad hardware
          */
diff --git a/power-sequencer/ucd90160_defs.cpp b/power-sequencer/ucd90160_defs.cpp
index 6124afc..fd3fd1d 100644
--- a/power-sequencer/ucd90160_defs.cpp
+++ b/power-sequencer/ucd90160_defs.cpp
@@ -48,6 +48,15 @@
                 "VDD-B"s,
                 "VCS-A"s,
                 "VCS-B"s
+            },
+
+            GPIConfigs{
+                GPIConfig{1, 8,  "PGOOD_5P0V"s, false},
+                GPIConfig{2, 9,  "MEM_GOOD0"s, false},
+                GPIConfig{3, 10, "MEM_GOOD1"s, false},
+                GPIConfig{4, 14, "GPU_PGOOD"s, true},
+                GPIConfig{5, 17, "GPU_TH_OVERT"s, true},
+                GPIConfig{6, 11, "SOFTWARE_PGOOD"s, false}
             }
         }
     }