psu-ng: Add Availability support to power supplies

Add support for determining if a power supply is considered 'available'
or not, and set the Available D-Bus property on the
xyz.openbmc_project.State.Decorator.Availability interface with the
result.

A power supply is considered unavailable if any of the following are
true:
 - it isn't present
 - there is a input fault active
 - there is a Vin UV fault active
 - there is a PS KILL fault active
 - there is a Iout OC fault active

The latter four faults are the faults where it can't provide good
output power, hence the PS is unavailable.  These faults also don't
call out the power supply directly, so the Functional property won't
get set to false, unlike other faults.

The Available D-Bus property is then able to be used in Redfish
responses for the heath and state properties in the power supply
schema.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I349eddb8c052cf238276c832af0dde9e49f6d3f0
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 27bc502..e5fa9c8 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -186,6 +186,7 @@
         std::string prettyName = invpath.substr(lastSlashPos + 1);
         setPresence(bus, invpath, present, prettyName);
         updateInventory();
+        checkAvailability();
     }
 }
 
@@ -563,6 +564,8 @@
                         .c_str());
                 clearFaults();
             }
+
+            checkAvailability();
         }
         catch (const ReadFailure& e)
         {
@@ -616,6 +619,7 @@
     if (present)
     {
         clearFaultFlags();
+        checkAvailability();
         readFail = 0;
 
         try
@@ -662,6 +666,7 @@
             // Clear out the now outdated inventory properties
             updateInventory();
         }
+        checkAvailability();
     }
 }
 
@@ -690,6 +695,7 @@
                                      .c_str());
 
                 updateInventory();
+                checkAvailability();
             }
         }
     }
@@ -905,4 +911,17 @@
     }
 }
 
+void PowerSupply::checkAvailability()
+{
+    bool origAvailability = available;
+    available = present && !hasInputFault() && !hasVINUVFault() &&
+                !hasPSKillFault() && !hasIoutOCFault();
+
+    if (origAvailability != available)
+    {
+        auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+        phosphor::power::psu::setAvailable(bus, invpath, available);
+    }
+}
+
 } // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index b7459f5..c49039a 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -407,6 +407,23 @@
      */
     void getInputVoltage(double& actualInputVoltage, int& inputVoltage) const;
 
+    /**
+     * @brief Check if the PS is considered to be available or not
+     *
+     * It is unavailable if any of:
+     * - not present
+     * - input fault active
+     * - Vin UV fault active
+     * - PS KILL fault active
+     * - Iout OC fault active
+     *
+     * Other faults will, through creating error logs with callouts, already
+     * be setting the Functional property to false.
+     *
+     * On changes, the Available property is updated in the inventory.
+     */
+    void checkAvailability();
+
   private:
     /** @brief systemd bus member */
     sdbusplus::bus::bus& bus;
@@ -662,6 +679,13 @@
     std::string bindDevice;
 
     /**
+     * @brief The result of the most recent availability check
+     *
+     * Saved on the object so changes can be detected.
+     */
+    bool available = false;
+
+    /**
      * @brief Binds or unbinds the power supply device driver
      *
      * Called when a presence change is detected to either bind the device
diff --git a/phosphor-power-supply/test/mock.hpp b/phosphor-power-supply/test/mock.hpp
index cf4d3ae..706aea4 100644
--- a/phosphor-power-supply/test/mock.hpp
+++ b/phosphor-power-supply/test/mock.hpp
@@ -51,6 +51,10 @@
                 (sdbusplus::bus::bus & bus, const std::string& invpath,
                  bool present, const std::string& name),
                 (const, override));
+    MOCK_METHOD(void, setAvailable,
+                (sdbusplus::bus::bus & bus, const std::string& invpath,
+                 bool available),
+                (const, override));
 };
 
 class MockedGPIOInterface : public GPIOInterfaceBase
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index c556f82..8bcbf16 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -703,6 +703,7 @@
     try
     {
         // Assume GPIO presence, not inventory presence?
+        EXPECT_CALL(mockedUtil, setAvailable(_, _, _)).Times(0);
         PowerSupply psu{bus, PSUInventoryPath, 4, 0x69, PSUGPIOLineName};
 
         MockedGPIOInterface* mockPresenceGPIO =
@@ -721,6 +722,7 @@
     try
     {
         // Assume GPIO presence, not inventory presence?
+        EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
         PowerSupply psu{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
         MockedGPIOInterface* mockPresenceGPIO =
             static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
@@ -804,6 +806,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("0"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.isPresent(), true);
         // Cannot have VOUT_OV_FAULT and VOUT_UV_FAULT.
@@ -828,6 +834,7 @@
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _))
         .Times(1)
         .WillOnce(Return(207000));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.clearFaults();
     EXPECT_EQ(psu.isPresent(), true);
     EXPECT_EQ(psu.isFaulted(), false);
@@ -870,6 +877,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("0"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
     }
 
@@ -902,6 +913,7 @@
         .Times(1)
         .WillOnce(Return("206000"));
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(0));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.isPresent(), true);
     EXPECT_EQ(psu.isFaulted(), false);
@@ -1001,6 +1013,7 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("123456"));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.isPresent(), true);
 }
@@ -1049,6 +1062,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("125790"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.isFaulted(), x >= DEGLITCH_LIMIT);
     }
@@ -1085,6 +1102,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("201200"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.hasInputFault(), x >= DEGLITCH_LIMIT);
     }
@@ -1095,6 +1116,7 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("201300"));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.hasInputFault(), false);
 }
@@ -1183,6 +1205,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("19876"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.hasVINUVFault(), x >= DEGLITCH_LIMIT);
     }
@@ -1196,6 +1222,7 @@
         .WillOnce(Return("201300"));
     // Went from below minimum to within range, expect CLEAR_FAULTS.
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(3));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.hasVINUVFault(), false);
 }
@@ -1275,6 +1302,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("203200"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.hasIoutOCFault(), x >= DEGLITCH_LIMIT);
     }
@@ -1284,6 +1315,7 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("203300"));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.hasIoutOCFault(), false);
 }
@@ -1336,6 +1368,9 @@
 {
     auto bus = sdbusplus::bus::new_default();
 
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true)).Times(1);
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, false)).Times(0);
+
     PowerSupply psu{bus, PSUInventoryPath, 3, 0x6d, PSUGPIOLineName};
     MockedGPIOInterface* mockPresenceGPIO =
         static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
@@ -1382,6 +1417,9 @@
 {
     auto bus = sdbusplus::bus::new_default();
 
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true)).Times(1);
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, false)).Times(0);
+
     PowerSupply psu{bus, PSUInventoryPath, 3, 0x6a, PSUGPIOLineName};
     MockedGPIOInterface* mockPresenceGPIO =
         static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
@@ -1560,6 +1598,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("208200"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.hasPSKillFault(), x >= DEGLITCH_LIMIT);
     }
@@ -1571,6 +1613,7 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("208300"));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.hasPSKillFault(), false);
     // Next return STATUS_WORD with MFR fault bit on.
@@ -1585,6 +1628,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("208400"));
+        if (x == DEGLITCH_LIMIT)
+        {
+            EXPECT_CALL(mockedUtil, setAvailable(_, _, false));
+        }
         psu.analyze();
         EXPECT_EQ(psu.hasPSKillFault(), x >= DEGLITCH_LIMIT);
     }
@@ -1596,6 +1643,7 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("208500"));
+    EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.hasPSKillFault(), false);
 }
diff --git a/phosphor-power-supply/util.hpp b/phosphor-power-supply/util.hpp
index 40fe5ca..ff9f13d 100644
--- a/phosphor-power-supply/util.hpp
+++ b/phosphor-power-supply/util.hpp
@@ -15,6 +15,14 @@
 namespace phosphor::power::psu
 {
 
+using Property = std::string;
+using Value = std::variant<bool, std::string>;
+using PropertyMap = std::map<Property, Value>;
+using Interface = std::string;
+using InterfaceMap = std::map<Interface, PropertyMap>;
+using Object = sdbusplus::message::object_path;
+using ObjectMap = std::map<Object, InterfaceMap>;
+
 class Util : public UtilBase
 {
   public:
@@ -35,18 +43,11 @@
     {
         using InternalFailure =
             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
-        using Property = std::string;
-        using Value = std::variant<bool, std::string>;
-        // Association between property and its value
-        using PropertyMap = std::map<Property, Value>;
         PropertyMap invProp;
 
         invProp.emplace("Present", present);
         invProp.emplace("PrettyName", name);
 
-        using Interface = std::string;
-        // Association between interface and the D-Bus property map
-        using InterfaceMap = std::map<Interface, PropertyMap>;
         InterfaceMap invIntf;
         invIntf.emplace("xyz.openbmc_project.Inventory.Item",
                         std::move(invProp));
@@ -55,9 +56,6 @@
 
         invIntf.emplace(extraIface, PropertyMap());
 
-        using Object = sdbusplus::message::object_path;
-        // Association between object and the interface map
-        using ObjectMap = std::map<Object, InterfaceMap>;
         ObjectMap invObj;
         invObj.emplace(std::move(invpath), std::move(invIntf));
 
@@ -89,6 +87,42 @@
             elog<InternalFailure>();
         }
     }
+
+    void setAvailable(sdbusplus::bus::bus& bus, const std::string& invpath,
+                      bool available) const override
+    {
+        PropertyMap invProp;
+        InterfaceMap invIntf;
+        ObjectMap invObj;
+
+        invProp.emplace(AVAILABLE_PROP, available);
+        invIntf.emplace(AVAILABILITY_IFACE, std::move(invProp));
+
+        invObj.emplace(std::move(invpath), std::move(invIntf));
+
+        try
+        {
+
+            auto invService = phosphor::power::util::getService(
+                INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
+
+            auto invMsg =
+                bus.new_method_call(invService.c_str(), INVENTORY_OBJ_PATH,
+                                    INVENTORY_MGR_IFACE, "Notify");
+            invMsg.append(std::move(invObj));
+            auto invMgrResponseMsg = bus.call(invMsg);
+        }
+        catch (const sdbusplus::exception::exception& e)
+        {
+            using namespace phosphor::logging;
+            log<level::ERR>(
+                fmt::format("Error in inventory manager call to update "
+                            "availability interface: {}",
+                            e.what())
+                    .c_str());
+            throw;
+        }
+    }
 };
 
 std::unique_ptr<GPIOInterfaceBase> createGPIO(const std::string& namedGpio);
diff --git a/phosphor-power-supply/util_base.hpp b/phosphor-power-supply/util_base.hpp
index b5086f4..bb7dc3c 100644
--- a/phosphor-power-supply/util_base.hpp
+++ b/phosphor-power-supply/util_base.hpp
@@ -24,6 +24,10 @@
     virtual void setPresence(sdbusplus::bus::bus& bus,
                              const std::string& invpath, bool present,
                              const std::string& name) const = 0;
+
+    virtual void setAvailable(sdbusplus::bus::bus& bus,
+                              const std::string& invpath,
+                              bool available) const = 0;
 };
 
 const UtilBase& getUtils();
@@ -39,6 +43,12 @@
     return getUtils().setPresence(bus, invpath, present, name);
 }
 
+inline void setAvailable(sdbusplus::bus::bus& bus, const std::string& invpath,
+                         bool available)
+{
+    getUtils().setAvailable(bus, invpath, available);
+}
+
 class GPIOInterfaceBase
 {
   public:
diff --git a/types.hpp b/types.hpp
index b79bbb9..19563e3 100644
--- a/types.hpp
+++ b/types.hpp
@@ -13,6 +13,8 @@
 constexpr auto OPERATIONAL_STATE_IFACE =
     "xyz.openbmc_project.State.Decorator.OperationalStatus";
 constexpr auto VERSION_IFACE = "xyz.openbmc_project.Software.Version";
+constexpr auto AVAILABILITY_IFACE =
+    "xyz.openbmc_project.State.Decorator.Availability";
 #ifdef IBM_VPD
 constexpr auto DINF_IFACE = "com.ibm.ipzvpd.DINF";
 constexpr auto VINI_IFACE = "com.ibm.ipzvpd.VINI";
@@ -23,6 +25,7 @@
 constexpr auto RESOLVED_PROP = "Resolved";
 constexpr auto PRESENT_PROP = "Present";
 constexpr auto FUNCTIONAL_PROP = "Functional";
+constexpr auto AVAILABLE_PROP = "Available";
 
 constexpr auto INVENTORY_OBJ_PATH = "/xyz/openbmc_project/inventory";
 constexpr auto POWER_OBJ_PATH = "/org/openbmc/control/power0";