psu: Updates for power supply presence

Update the JSON file to indicate the minimum and maximum number of power
supplies this system can have.

Update the JSON file to have an array of power supply objects, each with
their various own properties. Each power supply will have:
1) An associated D-Bus path for the inventory. This will have the
   Present property.
2) An associated I2C bus number.
3) An associated I2C bus adddress.

Update PowerSupply class to have a present member variable, an
isPresent() accessor function, and two functions for updating the
presence. One function will be the call back function for either
inventory change events or interface added events, which will use
associated match member variables. The second function being one that
attempts to read the inventory Present property on startup. If reading
the Present property on startup fails, the property changed or interface
added events should work to get the value when that property is
available.

Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: Ib34510a6da66ba1b8f1e927093b3d10b09beb410
diff --git a/phosphor-power-supply/README.md b/phosphor-power-supply/README.md
index 50724d3..c5eb7cd 100644
--- a/phosphor-power-supply/README.md
+++ b/phosphor-power-supply/README.md
@@ -10,3 +10,23 @@
 Required property that specifies the delay between updating the power supply 
 status, period in milliseconds.
 
+## MinPowerSupplies
+Optional property, integer, that indicates the minimum number of power supplies
+that should be present.
+
+## MaxPowerSupplies
+Optional property, integer, that indicates the maximum number of power supplies
+that should be present.
+
+## PowerSupplies
+An array of power supply properties.
+
+### Inventory
+The D-Bus path used to check the power supply presence (Present) property.
+
+### Bus
+An integer specifying the I2C bus that the PMBus power supply is on.
+
+### Address
+A string representing the 16-bit I2C address of the PMBus power supply.
+
diff --git a/phosphor-power-supply/configurations/witherspoon/psu_config.json b/phosphor-power-supply/configurations/witherspoon/psu_config.json
index aecbd20..6724ab0 100644
--- a/phosphor-power-supply/configurations/witherspoon/psu_config.json
+++ b/phosphor-power-supply/configurations/witherspoon/psu_config.json
@@ -1,3 +1,19 @@
 {
-    "pollInterval" : 1000
+    "SystemProperties" : {
+        "pollInterval" : 1000,
+        "MinPowerSupplies" : 1,
+        "MaxPowerSupplies" : 2
+    },
+    "PowerSupplies": [
+        {
+            "Inventory" : "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0",
+            "Bus" : 3,
+            "Address" : "0x68"
+        },
+        {
+            "Inventory" : "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1",
+            "Bus" : 3,
+            "Address" : "0x69"
+        }
+    ]
 }
diff --git a/phosphor-power-supply/meson.build b/phosphor-power-supply/meson.build
index b6fb80f..9554dd9 100644
--- a/phosphor-power-supply/meson.build
+++ b/phosphor-power-supply/meson.build
@@ -3,6 +3,7 @@
     'phosphor-psu-monitor',
     'main.cpp',
     'psu_manager.cpp',
+    'power_supply.cpp',
     dependencies: [
         sdbusplus,
         sdeventplus,
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
new file mode 100644
index 0000000..a6be9f5
--- /dev/null
+++ b/phosphor-power-supply/power_supply.cpp
@@ -0,0 +1,58 @@
+#include "power_supply.hpp"
+
+#include "types.hpp"
+#include "utility.hpp"
+
+namespace phosphor
+{
+namespace power
+{
+namespace psu
+{
+
+using namespace phosphor::logging;
+
+void PowerSupply::updatePresence()
+{
+    try
+    {
+        // Use getProperty utility function to get presence status.
+        util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
+                          INVENTORY_MGR_IFACE, bus, this->present);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        // Relying on property change or interface added to retry.
+        // Log an informational trace to the journal.
+        log<level::INFO>("D-Bus property access failure exception");
+    }
+}
+
+void PowerSupply::inventoryChanged(sdbusplus::message::message& msg)
+{
+    std::string msgSensor;
+    std::map<std::string, sdbusplus::message::variant<uint32_t, bool>> msgData;
+    msg.read(msgSensor, msgData);
+
+    // Check if it was the Present property that changed.
+    auto valPropMap = msgData.find(PRESENT_PROP);
+    if (valPropMap != msgData.end())
+    {
+        if (std::get<bool>(valPropMap->second))
+        {
+            present = true;
+            clearFaults();
+        }
+        else
+        {
+            present = false;
+
+            // Clear out the now outdated inventory properties
+            updateInventory();
+        }
+    }
+}
+
+} // namespace psu
+} // namespace power
+} // namespace phosphor
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 3bfa77a..e5c1e76 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -1,5 +1,9 @@
 #pragma once
 
+#include "types.hpp"
+
+#include <sdbusplus/bus/match.hpp>
+
 namespace phosphor::power::psu
 {
 /**
@@ -9,7 +13,7 @@
 class PowerSupply
 {
   public:
-    PowerSupply();
+    PowerSupply() = delete;
     PowerSupply(const PowerSupply&) = delete;
     PowerSupply(PowerSupply&&) = delete;
     PowerSupply& operator=(const PowerSupply&) = delete;
@@ -17,6 +21,28 @@
     ~PowerSupply() = default;
 
     /**
+     * @param[in] invpath - string for inventory path to use
+     */
+    PowerSupply(sdbusplus::bus::bus& bus, const std::string& invpath) :
+        bus(bus), inventoryPath(invpath)
+    {
+        // Setup the functions to call when the D-Bus inventory path for the
+        // Present property changes.
+        presentMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::propertiesChanged(inventoryPath,
+                                                            INVENTORY_IFACE),
+            [this](auto& msg) { this->inventoryChanged(msg); });
+        presentAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::path_namespace(inventoryPath),
+            [this](auto& msg) { this->inventoryChanged(msg); });
+        // Get the current state of the Present property.
+        updatePresence();
+    }
+
+    /**
      * Power supply specific function to analyze for faults/errors.
      *
      * Various PMBus status bits will be checked for fault conditions.
@@ -58,9 +84,56 @@
     {
     }
 
+    /**
+     * @brief Accessor function to indicate present status
+     */
+    bool isPresent() const
+    {
+        return present;
+    }
+
   private:
+    /** @brief systemd bus member */
+    sdbusplus::bus::bus& bus;
+
     /** @brief True if a fault has already been found and not cleared */
     bool faultFound = false;
+
+    /**
+     * @brief D-Bus path to use for this power supply's inventory status.
+     **/
+    std::string inventoryPath;
+
+    /** @brief True if the power supply is present. */
+    bool present = false;
+
+    /** @brief D-Bus match variable used to subscribe to Present property
+     * changes.
+     **/
+    std::unique_ptr<sdbusplus::bus::match_t> presentMatch;
+
+    /** @brief D-Bus match variable used to subscribe for Present property
+     * interface added.
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> presentAddedMatch;
+
+    /**
+     *  @brief Updates the presence status by querying D-Bus
+     *
+     * The D-Bus inventory properties for this power supply will be read to
+     * determine if the power supply is present or not and update this
+     * object's present member variable to reflect current status.
+     **/
+    void updatePresence();
+
+    /**
+     * @brief Callback for inventory property changes
+     *
+     * Process change of Present property for power supply.
+     *
+     * @param[in]  msg - Data associated with Present change signal
+     **/
+    void inventoryChanged(sdbusplus::message::message& msg);
 };
 
 } // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/psu_manager.cpp b/phosphor-power-supply/psu_manager.cpp
index 54b20d8..922d555 100644
--- a/phosphor-power-supply/psu_manager.cpp
+++ b/phosphor-power-supply/psu_manager.cpp
@@ -2,6 +2,8 @@
 
 #include "utility.hpp"
 
+using namespace phosphor::logging;
+
 namespace phosphor
 {
 namespace power
@@ -9,6 +11,97 @@
 namespace manager
 {
 
+PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e,
+                       const std::string& configfile) :
+    bus(bus)
+{
+    // Parse out the JSON properties
+    sys_properties properties;
+    getJSONProperties(configfile, bus, properties, psus);
+
+    using namespace sdeventplus;
+    auto interval = std::chrono::milliseconds(properties.pollInterval);
+    timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
+        e, std::bind(&PSUManager::analyze, this), interval);
+
+    minPSUs = {properties.minPowerSupplies};
+    maxPSUs = {properties.maxPowerSupplies};
+
+    // Subscribe to power state changes
+    powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
+    powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
+        bus,
+        sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
+                                                        POWER_IFACE),
+        [this](auto& msg) { this->powerStateChanged(msg); });
+
+    initialize();
+}
+
+void PSUManager::getJSONProperties(
+    const std::string& path, sdbusplus::bus::bus& bus, sys_properties& p,
+    std::vector<std::unique_ptr<PowerSupply>>& psus)
+{
+    nlohmann::json configFileJSON = util::loadJSONFromFile(path.c_str());
+
+    if (configFileJSON == nullptr)
+    {
+        throw std::runtime_error("Failed to load JSON configuration file");
+    }
+
+    if (!configFileJSON.contains("SystemProperties"))
+    {
+        throw std::runtime_error("Missing required SystemProperties");
+    }
+
+    if (!configFileJSON.contains("PowerSupplies"))
+    {
+        throw std::runtime_error("Missing required PowerSupplies");
+    }
+
+    auto sysProps = configFileJSON["SystemProperties"];
+
+    if (!sysProps.contains("pollInterval"))
+    {
+        throw std::runtime_error("Missing required pollInterval property");
+    }
+
+    p.pollInterval = sysProps["pollInterval"];
+
+    if (sysProps.contains("MinPowerSupplies"))
+    {
+        p.minPowerSupplies = sysProps["MinPowerSupplies"];
+    }
+    else
+    {
+        p.minPowerSupplies = 0;
+    }
+
+    if (sysProps.contains("MaxPowerSupplies"))
+    {
+        p.maxPowerSupplies = sysProps["MaxPowerSupplies"];
+    }
+    else
+    {
+        p.maxPowerSupplies = 0;
+    }
+
+    for (auto psuJSON : configFileJSON["PowerSupplies"])
+    {
+        if (psuJSON.contains("Inventory"))
+        {
+            std::string invpath = psuJSON["Inventory"];
+            auto psu = std::make_unique<PowerSupply>(bus, invpath);
+            psus.emplace_back(std::move(psu));
+        }
+    }
+
+    if (psus.empty())
+    {
+        throw std::runtime_error("No power supplies to monitor");
+    }
+}
+
 void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
 {
     int32_t state = 0;
diff --git a/phosphor-power-supply/psu_manager.hpp b/phosphor-power-supply/psu_manager.hpp
index b3877b7..a4628c4 100644
--- a/phosphor-power-supply/psu_manager.hpp
+++ b/phosphor-power-supply/psu_manager.hpp
@@ -9,6 +9,13 @@
 #include <sdeventplus/event.hpp>
 #include <sdeventplus/utility/timer.hpp>
 
+struct sys_properties
+{
+    int pollInterval;
+    int minPowerSupplies;
+    int maxPowerSupplies;
+};
+
 using namespace phosphor::power::psu;
 using namespace phosphor::logging;
 
@@ -19,11 +26,6 @@
 namespace manager
 {
 
-struct json_properties
-{
-    int pollInterval;
-};
-
 /**
  * @class PSUManager
  *
@@ -48,45 +50,11 @@
      * @param[in] configfile - string path to the configuration file
      */
     PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e,
-               const std::string& configfile) :
-        bus(bus)
-    {
-        // Parse out the JSON properties
-        json_properties properties = {0};
-        getJSONProperties(configfile, properties);
+               const std::string& configfile);
 
-        using namespace sdeventplus;
-        auto pollInterval = std::chrono::milliseconds(properties.pollInterval);
-        timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
-            e, std::bind(&PSUManager::analyze, this), pollInterval);
-
-        // Subscribe to power state changes
-        powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
-        powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
-            bus,
-            sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
-                                                            POWER_IFACE),
-            [this](auto& msg) { this->powerStateChanged(msg); });
-
-        initialize();
-    }
-
-    void getJSONProperties(const std::string& path, json_properties& p)
-    {
-        nlohmann::json configFileJSON = util::loadJSONFromFile(path.c_str());
-
-        if (configFileJSON == nullptr)
-        {
-            throw std::runtime_error("Failed to load JSON configuration file");
-        }
-
-        if (!configFileJSON.contains("pollInterval"))
-        {
-            throw std::runtime_error("Missing required pollInterval property");
-        }
-
-        p.pollInterval = configFileJSON.at("pollInterval");
-    }
+    void getJSONProperties(const std::string& path, sdbusplus::bus::bus& bus,
+                           sys_properties& p,
+                           std::vector<std::unique_ptr<PowerSupply>>& psus);
 
     /**
      * Initializes the manager.
@@ -141,7 +109,7 @@
     {
         for (auto& psu : psus)
         {
-            psu.clearFaults();
+            psu->clearFaults();
         }
     }
 
@@ -165,7 +133,7 @@
     {
         for (auto& psu : psus)
         {
-            psu.analyze();
+            psu->analyze();
         }
     }
 
@@ -200,14 +168,24 @@
     {
         for (auto& psu : psus)
         {
-            psu.updateInventory();
+            psu->updateInventory();
         }
     }
 
     /**
+     * @brief Minimum number of power supplies to operate.
+     */
+    int minPSUs = 1;
+
+    /**
+     * @brief Maximum number of power supplies possible.
+     */
+    int maxPSUs = 1;
+
+    /**
      * @brief The vector for power supplies.
      */
-    std::vector<PowerSupply> psus;
+    std::vector<std::unique_ptr<PowerSupply>> psus;
 };
 
 } // namespace manager