chassis-psu: ChassisManager class to manage chassis PSUs

Introduced the ChassisManager class to detect chassis and manage power
supply devices associated with chassis using D-Bus interfaces. This
includes:
- Subscribing to Entity Manager interface changes
- Implementing timers to validate power supply presence and status
- Scan the system for new chassis; if a chassis is new, add it to the
  chassis list.
- Adding event loop integration for continuous monitoring
- Analysis of power supply status and error logging.

Test:
  - On simulation system, verified chassis and their power supplies are
    added to the chassis list, with each power supply correctly linked
    to its corresponding chassis.
  - Verified the power supplies in each chassis based on analysis
    performed at the specified time interval.

Note: There are some commented code indicates future implementation,
please ignore for now, as they will be implemented soon.

Change-Id: I80c271783e71f668ca1405f7aca80c8ec112f531
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/phosphor-power-supply/chassis.cpp b/phosphor-power-supply/chassis.cpp
index 505a955..85e2a88 100644
--- a/phosphor-power-supply/chassis.cpp
+++ b/phosphor-power-supply/chassis.cpp
@@ -25,9 +25,10 @@
 const auto entityMgrService = "xyz.openbmc_project.EntityManager";
 const auto decoratorChassisId = "xyz.openbmc_project.Inventory.Decorator.Slot";
 
-Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath) :
+Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
+                 const sdeventplus::Event& e) :
     bus(bus), chassisPath(chassisPath),
-    chassisPathUniqueId(getChassisPathUniqueId())
+    chassisPathUniqueId(getChassisPathUniqueId(chassisPath)), eventLoop(e)
 {
     getPSUConfiguration();
 }
@@ -149,9 +150,12 @@
         lg2::debug(
             "make PowerSupply bus: {I2CBUS} addr: {I2CADDR} presline: {PRESLINE}",
             "I2CBUS", *i2cbus, "I2CADDR", *i2caddr, "PRESLINE", presline);
+        // auto psu = std::make_unique<PowerSupply>(
+        //    bus, invpath, *i2cbus, *i2caddr, driverName, presline,
+        //   std::bind(&Chassis::isPowerOn, this), chassisPath);
         auto psu = std::make_unique<PowerSupply>(
             bus, invpath, *i2cbus, *i2caddr, driverName, presline,
-            std::bind(&Chassis::isPowerOn, this), chassisPath);
+            std::bind(&Chassis::isPowerOn, this));
         psus.emplace_back(std::move(psu));
 
         // Subscribe to power supply presence changes
@@ -300,20 +304,20 @@
                   [&driverName](auto& psu) { psu->setDriverName(driverName); });
 }
 
-uint32_t Chassis::getChassisPathUniqueId()
+uint64_t Chassis::getChassisPathUniqueId(const std::string& path)
 {
-    uint32_t chassisId = invalidObjectPathUniqueId;
     try
     {
-        getProperty(decoratorChassisId, chassisIdProp, chassisPath,
-                    INVENTORY_MGR_IFACE, bus, chassisId);
+        return getChassisInventoryUniqueId(bus, path);
     }
     catch (const sdbusplus::exception_t& e)
     {
-        lg2::error("Failed to find chassis ID  - exception: {ERROR}", "ERROR",
-                   e);
+        lg2::error(
+            "Failed to find chassis path {CHASSIS_PATH} ID - exception: {ERROR}",
+            "CHASSIS_PATH", path, "ERROR", e);
     }
-    return chassisId;
+    return invalidObjectPathUniqueId;
 }
 
+void Chassis::analyze() {}
 } // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis.hpp b/phosphor-power-supply/chassis.hpp
index 527b0ff..e976921 100644
--- a/phosphor-power-supply/chassis.hpp
+++ b/phosphor-power-supply/chassis.hpp
@@ -29,7 +29,7 @@
 namespace phosphor::power::chassis
 {
 
-constexpr uint32_t invalidObjectPathUniqueId = 9999;
+constexpr uint64_t invalidObjectPathUniqueId = 9999;
 using PowerSystemInputsInterface = sdbusplus::xyz::openbmc_project::State::
     Decorator::server::PowerSystemInputs;
 using PowerSystemInputsObject =
@@ -62,8 +62,26 @@
      *
      * @param[in] bus - D-Bus bus object
      * @param[in] chassisPath - Chassis path
+     * @param[in] event - Event loop object
      */
-    Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath);
+    Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
+            const sdeventplus::Event& e);
+
+    /**
+     * @brief Retrieves the unique identifier of the chassis.
+     *
+     * @return uint64_t The unique 64 bits identifier of the chassis.
+     */
+    uint64_t getChassisId()
+    {
+        return chassisPathUniqueId;
+    }
+
+    /**
+     * @brief Analyze the status of each of the power supplies. Log errors for
+     * faults, when and where appropriate.
+     */
+    void analyze();
 
     /**
      * @brief Get the status of Power on.
@@ -125,7 +143,13 @@
     /**
      * @brief The Chassis path unique ID
      */
-    uint32_t chassisPathUniqueId = invalidObjectPathUniqueId;
+    uint64_t chassisPathUniqueId = invalidObjectPathUniqueId;
+
+    /**
+     * @brief Declares a constant reference to an sdeventplus::Event to manage
+     * async processing.
+     */
+    const sdeventplus::Event& eventLoop;
 
     /**
      * @brief Get PSU properties from D-Bus, use that to build a power supply
@@ -181,9 +205,10 @@
     /**
      * @brief Get chassis path unique ID.
      *
-     * @return uint32_t - Chassis path unique ID.
+     * @param [in] path - Chassis path.
+     * @return uint64_t - Chassis path unique ID.
      */
-    uint32_t getChassisPathUniqueId();
+    uint64_t getChassisPathUniqueId(const std::string& path);
 };
 
 } // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis_manager.cpp b/phosphor-power-supply/chassis_manager.cpp
new file mode 100644
index 0000000..710b5dc
--- /dev/null
+++ b/phosphor-power-supply/chassis_manager.cpp
@@ -0,0 +1,145 @@
+#include "config.h"
+
+#include "chassis_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+using namespace phosphor::logging;
+
+namespace phosphor::power::chassis_manager
+{
+using namespace phosphor::power::util;
+constexpr auto managerBusName =
+    "xyz.openbmc_project.Power.MultiChassisPSUMonitor";
+constexpr auto IBMCFFPSInterface =
+    "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
+constexpr auto supportedConfIntf =
+    "xyz.openbmc_project.Configuration.SupportedConfiguration";
+
+ChassisManager::ChassisManager(sdbusplus::bus_t& bus,
+                               const sdeventplus::Event& e) :
+    bus(bus), eventLoop(e)
+{
+    // Subscribe to InterfacesAdded before doing a property read, otherwise
+    // the interface could be created after the read attempt but before the
+    // match is created.
+    entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+        bus,
+        sdbusplus::bus::match::rules::interfacesAdded() +
+            sdbusplus::bus::match::rules::sender(
+                "xyz.openbmc_project.EntityManager"),
+        std::bind(&ChassisManager::entityManagerIfaceAdded, this,
+                  std::placeholders::_1));
+
+    initializeChassisList();
+
+    // Request the bus name before the analyze() function, which is the one that
+    // determines the brownout condition and sets the status d-bus property.
+    bus.request_name(managerBusName);
+
+    using namespace sdeventplus;
+    auto interval = std::chrono::milliseconds(1000);
+    timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
+        e, std::bind(&ChassisManager::analyze, this), interval);
+}
+
+void ChassisManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
+{
+    try
+    {
+        phosphor::power::chassis::Chassis* chassisMatchPtr = nullptr;
+        sdbusplus::message::object_path objPath;
+        std::map<std::string, std::map<std::string, util::DbusVariant>>
+            interfaces;
+        msg.read(objPath, interfaces);
+
+        std::string objPathStr = objPath;
+
+        auto itInterface = interfaces.find(supportedConfIntf);
+        if (itInterface != interfaces.cend())
+        {
+            lg2::info("InterfacesAdded supportedConfIntf- objPathStr= {OBJ}",
+                      "OBJ", objPathStr);
+            auto myChassisId = getParentEMUniqueId(bus, objPathStr);
+            chassisMatchPtr = getMatchingChassisPtr(myChassisId);
+            if (chassisMatchPtr)
+            {
+                lg2::debug("InterfacesAdded for: {SUPPORTED_CONFIGURATION}",
+                           "SUPPORTED_CONFIGURATION", supportedConfIntf);
+                // Future implementation
+                // chassisMatchPtr->supportedConfigurationInterfaceAdded(
+                //     itInterface->second);
+            }
+        }
+        itInterface = interfaces.find(IBMCFFPSInterface);
+        if (itInterface != interfaces.cend())
+        {
+            lg2::debug("InterfacesAdded IBMCFFPSInterface- objPathStr= {OBJ}",
+                       "OBJ", objPathStr);
+            auto myChassisId = getParentEMUniqueId(bus, objPathStr);
+            chassisMatchPtr = getMatchingChassisPtr(myChassisId);
+            if (chassisMatchPtr)
+            {
+                lg2::info("InterfacesAdded for: {IBMCFFPSINTERFACE}",
+                          "IBMCFFPSINTERFACE", IBMCFFPSInterface);
+                // Future implementation
+                // chassisMatchPtr->psuInterfaceAdded(itInterface->second);
+            }
+        }
+        if (chassisMatchPtr != nullptr)
+        {
+            lg2::debug(
+                "InterfacesAdded validatePsuConfigAndInterfacesProcessed()");
+            // Future implementation
+            // chassisMatchPtr->validatePsuConfigAndInterfacesProcessed();
+        }
+    }
+    catch (const std::exception& e)
+    {
+        // Ignore, the property may be of a different type than expected.
+    }
+}
+
+phosphor::power::chassis::Chassis* ChassisManager::getMatchingChassisPtr(
+    uint64_t chassisId)
+{
+    for (const auto& chassisPtr : listOfChassis)
+    {
+        if (chassisPtr->getChassisId() == chassisId)
+        {
+            return chassisPtr.get();
+        }
+    }
+    lg2::debug("Chassis ID {ID} not found", "ID", chassisId);
+    return nullptr;
+}
+
+void ChassisManager::analyze()
+{
+    for (const auto& chassis : listOfChassis)
+    {
+        chassis->analyze();
+    }
+}
+
+void ChassisManager::initializeChassisList()
+{
+    try
+    {
+        auto chassisPathList = getChassisInventoryPaths(bus);
+        for (const auto& chassisPath : chassisPathList)
+        {
+            lg2::info(
+                "ChassisManager::initializeChassisList chassisPath= {CHASSIS_PATH}",
+                "CHASSIS_PATH", chassisPath);
+            auto chassis = std::make_unique<phosphor::power::chassis::Chassis>(
+                bus, chassisPath, eventLoop);
+            listOfChassis.push_back(std::move(chassis));
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed to initialize chassis list, error: {ERROR}", "ERROR",
+                   e);
+    }
+}
+} // namespace phosphor::power::chassis_manager
diff --git a/phosphor-power-supply/chassis_manager.hpp b/phosphor-power-supply/chassis_manager.hpp
new file mode 100644
index 0000000..100514d
--- /dev/null
+++ b/phosphor-power-supply/chassis_manager.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include "chassis.hpp"
+#include "types.hpp"
+#include "utility.hpp"
+
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/manager.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+#include <xyz/openbmc_project/State/Decorator/PowerSystemInputs/server.hpp>
+
+using namespace phosphor::power::psu;
+
+namespace phosphor::power::chassis_manager
+{
+
+// Validation timeout. Allow 30s to detect if new EM interfaces show up in D-Bus
+// before performing the validation.
+constexpr auto validationTimeout = std::chrono::seconds(30);
+
+/**
+ * @class ChassisManager
+ *
+ * @brief Manages and monitors power supply devices for the chassis.
+ *
+ * @detail This class interacts with D-Bus to detect chassis power supply,
+ * subscribe to Entity Manager interface changes.
+ */
+class ChassisManager
+{
+  public:
+    ChassisManager() = delete;
+    ~ChassisManager() = default;
+    ChassisManager(const ChassisManager&) = delete;
+    ChassisManager& operator=(const ChassisManager&) = delete;
+    ChassisManager(ChassisManager&&) = delete;
+    ChassisManager& operator=(ChassisManager&&) = delete;
+
+    /**
+     * @brief Constructs a ChassisManager instance.
+     *
+     * @details Sets up D-Bus interfaces, creates timer for power supply
+     * validation and monitoring, and subscribes to entity-manager interfaces.
+     *
+     * @param[in] bus - Reference to the system D-Bus object.
+     * @param[in] e - Reference to event loop.
+     */
+    ChassisManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e);
+
+    /**
+     * @brief Starts the main event loop for monitoring.
+     *
+     * @return int Returns the result the result of the event loop execution.
+     */
+    int run()
+    {
+        return timer->get_event().loop();
+    }
+
+  private:
+    /**
+     * @brief The D-Bus object
+     */
+    sdbusplus::bus_t& bus;
+
+    /**
+     * @brief The timer that runs to periodically check the power supplies.
+     */
+    std::unique_ptr<
+        sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>
+        timer;
+
+    /**
+     * @brief Used to subscribe to Entity Manager interfaces added
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> entityManagerIfacesAddedMatch;
+
+    /**
+     * @brief List of chassis objects populated dynamically.
+     */
+    std::vector<std::unique_ptr<phosphor::power::chassis::Chassis>>
+        listOfChassis;
+
+    /**
+     * @brief Declares a constant reference to an sdeventplus::Envent to manage
+     * async processing.
+     */
+    const sdeventplus::Event& eventLoop;
+
+    /**
+     * @brief Callback for entity-manager interface added
+     *
+     * @details Process the information from the supported configuration and
+     * or IBM CFFPS Connector interface being added.
+     *
+     * @param[in] msg - D-Bus message containing the interface details.
+     */
+    void entityManagerIfaceAdded(sdbusplus::message_t& msg);
+
+    /**
+     * @brief Invoke the PSU analysis method in each chassis on the system.
+     *
+     * @details Scan the system for chassis and analyze each chassis power
+     * supplies and log any detected errors.
+     */
+    void analyze();
+
+    /**
+     * @brief Initialize the list of chassis object from the inventory, scans
+     * the D-Bus subtree for chassis and creates Chassis instances.
+     */
+    void initializeChassisList();
+
+    /**
+     * @brief Retrieves a pointer to a Chassis object matching the given ID.
+     *
+     * @param[in] chassisId - Unique identifier of the chassis to search for.
+     * @return Raw pointer to the matching Chassis object if found, otherwise a
+     * nullptr.
+     */
+    phosphor::power::chassis::Chassis* getMatchingChassisPtr(
+        uint64_t chassisId);
+};
+
+} // namespace phosphor::power::chassis_manager