power-utils: Implement isReadyToUpdate

Check other PSUs' status word and status vout, return false when the
status has input/output fault.

Tested: Verify on Witherspoon that the update is not started and log
            Unable to update PSU when other PSU has input/ouput fault
        when the other PSU has input/output error;
        And the update continues when the other PSU has no errors.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: Ia2a4a23a43c18a417b8a85fbd5339f487984e689
diff --git a/pmbus.hpp b/pmbus.hpp
index 8f5d914..4635915 100644
--- a/pmbus.hpp
+++ b/pmbus.hpp
@@ -81,6 +81,16 @@
 
 } // namespace status_word
 
+namespace status_vout
+{
+// The IBM CFF power supply driver maps MFR's OV_FAULT and VAUX_FAULT to this
+// bit.
+constexpr auto OV_FAULT = 0x80;
+
+// The IBM CFF power supply driver maps MFR's UV_FAULT to this bit.
+constexpr auto UV_FAULT = 0x10;
+} // namespace status_vout
+
 namespace status_temperature
 {
 // Overtemperature Fault
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index deab95f..e26d1b0 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -17,6 +17,7 @@
 
 #include "updater.hpp"
 
+#include "pmbus.hpp"
 #include "types.hpp"
 #include "utility.hpp"
 
@@ -146,16 +147,85 @@
 
 bool Updater::isReadyToUpdate()
 {
+    using namespace phosphor::pmbus;
+
     // Pre-condition for updating PSU:
     // * Host is powered off
-    // * All other PSUs are having AC input
-    // * All other PSUs are having standby output
-    if (util::isPoweredOn(bus))
+    // * At least one other PSU is present
+    // * All other PSUs that are present are having AC input and DC standby
+    //   output
+
+    if (util::isPoweredOn(bus, true))
     {
+        log<level::WARNING>("Unable to update PSU when host is on");
         return false;
     }
-    // TODO
-    return true;
+
+    bool hasOtherPresent = false;
+    auto paths = util::getPSUInventoryPaths(bus);
+    for (const auto& p : paths)
+    {
+        if (p == psuInventoryPath)
+        {
+            // Skip check for itself
+            continue;
+        }
+
+        // Check PSU present
+        bool present = false;
+        try
+        {
+            auto service = util::getService(p, INVENTORY_IFACE, bus);
+            util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
+                              service, bus, present);
+        }
+        catch (const std::exception& e)
+        {
+            log<level::ERR>("Failed to get present property",
+                            entry("PSU=%s", p.c_str()));
+        }
+        if (!present)
+        {
+            log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str()));
+            continue;
+        }
+        hasOtherPresent = true;
+
+        // Typically the driver is still bound here, so it is possible to
+        // directly read the debugfs to get the status.
+        try
+        {
+            auto devPath = internal::getDevicePath(p);
+            PMBus pmbus(devPath);
+            uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
+            auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
+            uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
+            if ((statusWord & status_word::VOUT_FAULT) ||
+                (statusWord & status_word::INPUT_FAULT_WARN) ||
+                (statusWord & status_word::VIN_UV_FAULT) ||
+                // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
+                // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
+                // UV_FAULT in vout status.
+                (voutStatus & status_vout::UV_FAULT) ||
+                (voutStatus & status_vout::OV_FAULT))
+            {
+                log<level::WARNING>(
+                    "Unable to update PSU when other PSU has input/ouput fault",
+                    entry("PSU=%s", p.c_str()),
+                    entry("STATUS_WORD=0x%04x", statusWord),
+                    entry("VOUT_BYTE=0x%02x", voutStatus));
+                return false;
+            }
+        }
+        catch (const std::exception& ex)
+        {
+            // If error occurs on accessing the debugfs, it means something went
+            // wrong, e.g. PSU is not present, and it's not ready to update.
+            log<level::ERR>(ex.what());
+            return false;
+        }
+    }
+    return hasOtherPresent;
 }
 
 int Updater::doUpdate()
diff --git a/types.hpp b/types.hpp
index 90b5c6a..d334a25 100644
--- a/types.hpp
+++ b/types.hpp
@@ -9,6 +9,8 @@
 constexpr auto INVENTORY_MGR_IFACE = "xyz.openbmc_project.Inventory.Manager";
 constexpr auto ASSET_IFACE = "xyz.openbmc_project.Inventory.Decorator.Asset";
 constexpr auto VERSION_IFACE = "xyz.openbmc_project.Software.Version";
+constexpr auto PSU_INVENTORY_IFACE =
+    "xyz.openbmc_project.Inventory.Item.PowerSupply";
 
 constexpr auto ENDPOINTS_PROP = "endpoints";
 constexpr auto MESSAGE_PROP = "Message";
diff --git a/utility.cpp b/utility.cpp
index 680b89e..75ecb80 100644
--- a/utility.cpp
+++ b/utility.cpp
@@ -105,24 +105,38 @@
     return type;
 }
 
-bool isPoweredOn(sdbusplus::bus::bus& bus)
+bool isPoweredOn(sdbusplus::bus::bus& bus, bool defaultState)
 {
-    // When state = 1, system is powered on
-    int32_t state = 0;
+    int32_t state = defaultState;
 
     try
     {
+        // When state = 1, system is powered on
         auto service = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
         getProperty<int32_t>(POWER_IFACE, "state", POWER_OBJ_PATH, service, bus,
                              state);
     }
     catch (std::exception& e)
     {
-        log<level::INFO>("Failed to get power state. Assuming it is off.");
+        log<level::INFO>("Failed to get power state.");
     }
     return state != 0;
 }
 
+std::vector<std::string> getPSUInventoryPaths(sdbusplus::bus::bus& bus)
+{
+    std::vector<std::string> paths;
+    auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
+                                      MAPPER_INTERFACE, "GetSubTreePaths");
+    method.append(INVENTORY_OBJ_PATH);
+    method.append(0); // Depth 0 to search all
+    method.append(std::vector<std::string>({PSU_INVENTORY_IFACE}));
+    auto reply = bus.call(method);
+
+    reply.read(paths);
+    return paths;
+}
+
 } // namespace util
 } // namespace power
 } // namespace phosphor
diff --git a/utility.hpp b/utility.hpp
index ffc489c..0326cac 100644
--- a/utility.hpp
+++ b/utility.hpp
@@ -128,9 +128,23 @@
 /**
  * Check if power is on
  *
- * @return true if power is on, otherwise false
+ * @param[in] bus - D-Bus object
+ * @param[in] defaultState - The default state if the function fails to get
+ *                           the power state.
+ *
+ * @return true if power is on, otherwise false;
+ *         defaultState if it fails to get the power state.
  */
-bool isPoweredOn(sdbusplus::bus::bus& bus);
+bool isPoweredOn(sdbusplus::bus::bus& bus, bool defaultState = false);
+
+/**
+ * Get all PSU inventory paths from D-Bus
+ *
+ * @param[in] bus - D-Bus object
+ *
+ * @return The list of PSU inventory paths
+ */
+std::vector<std::string> getPSUInventoryPaths(sdbusplus::bus::bus& bus);
 
 } // namespace util
 } // namespace power