chassis-psu: Add Chassis class for PSU management
This commit introduces the `Chassis` class within the
`phosphor::power::chassis` namespace. This class is responsible for
managing and monitoring power supply units (PSUs) associated with a
chassis.
Key functionalities include:
- Discovering individual PSUs and retrieving their properties and
configuration from D-Bus.
- Monitoring PSU presence changes and system power state.
- Retrieving overall system power properties.
- Implementing a validation timer for PSU configurations.
- Use an `sdeventplus` timer to delay configuration validation
until all EM interfaces are available.
- Maintain internal state flags (`powerOn`, `powerFaultOccurring`,
`brownoutLogged`,`runValidateConfig`) and data structures:
- `supportedConfigs` map of model names to `sys_properties`.
- Vector of `PowerSupply` instances.
- Building and populating PSU driver names.
Tested:
Simulated a multi-chassis setup, with each chassis containing a
couple of PSUs. Executed a simulated application to invoke the Chassis
object.
- Verified each Chassis object listed only PSUs belong to the chassis,
by verifying the PSU path has the correct chassis ID.
Note: Removed implementation of getSupportedConfiguration until design
issue resolved.
Change-Id: Ic4bda5ab0bd6173a67284ed27a0bc883ed04f92f
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/phosphor-power-supply/chassis.cpp b/phosphor-power-supply/chassis.cpp
new file mode 100644
index 0000000..505a955
--- /dev/null
+++ b/phosphor-power-supply/chassis.cpp
@@ -0,0 +1,319 @@
+#include "config.h"
+
+#include "chassis.hpp"
+
+#include <filesystem>
+#include <iostream>
+
+using namespace phosphor::logging;
+using namespace phosphor::power::util;
+namespace phosphor::power::chassis
+{
+
+constexpr auto IBMCFFPSInterface =
+ "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
+constexpr auto chassisIdProp = "SlotNumber";
+constexpr auto i2cBusProp = "I2CBus";
+constexpr auto i2cAddressProp = "I2CAddress";
+constexpr auto psuNameProp = "Name";
+constexpr auto presLineName = "NamedPresenceGpio";
+constexpr auto supportedConfIntf =
+ "xyz.openbmc_project.Configuration.SupportedConfiguration";
+const auto deviceDirPath = "/sys/bus/i2c/devices/";
+const auto driverDirName = "/driver";
+
+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) :
+ bus(bus), chassisPath(chassisPath),
+ chassisPathUniqueId(getChassisPathUniqueId())
+{
+ getPSUConfiguration();
+}
+
+void Chassis::getPSUConfiguration()
+{
+ namespace fs = std::filesystem;
+ auto depth = 0;
+
+ try
+ {
+ if (chassisPathUniqueId == invalidObjectPathUniqueId)
+ {
+ lg2::error("Chassis does not have chassis ID: {CHASSISPATH}",
+ "CHASSISPATH", chassisPath);
+ return;
+ }
+ auto connectorsSubTree = getSubTree(bus, "/", IBMCFFPSInterface, depth);
+ for (const auto& [path, services] : connectorsSubTree)
+ {
+ uint64_t id;
+ fs::path fspath(path);
+ getProperty(decoratorChassisId, chassisIdProp, fspath.parent_path(),
+ entityMgrService, bus, id);
+ if (id == static_cast<uint64_t>(chassisPathUniqueId))
+
+ {
+ // For each object in the array of objects, I want
+ // to get properties from the service, path, and
+ // interface.
+ auto properties = getAllProperties(bus, path, IBMCFFPSInterface,
+ entityMgrService);
+ getPSUProperties(properties);
+ }
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed while getting configuration - exception: {ERROR}",
+ "ERROR", e);
+ }
+
+ if (psus.empty())
+ {
+ // Interface or properties not found. Let the Interfaces Added callback
+ // process the information once the interfaces are added to D-Bus.
+ lg2::info("No power supplies to monitor");
+ }
+}
+
+void Chassis::getPSUProperties(util::DbusPropertyMap& properties)
+{
+ std::string basePSUInvPath = chassisPath + "/motherboard/powersupply";
+
+ // From passed in properties, I want to get: I2CBus, I2CAddress,
+ // and Name. Create a power supply object, using Name to build the inventory
+ // path.
+
+ uint64_t* i2cbus = nullptr;
+ uint64_t* i2caddr = nullptr;
+ std::string* psuname = nullptr;
+ std::string* preslineptr = nullptr;
+
+ for (const auto& property : properties)
+ {
+ try
+ {
+ if (property.first == i2cBusProp)
+ {
+ i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
+ }
+ else if (property.first == i2cAddressProp)
+ {
+ i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
+ }
+ else if (property.first == psuNameProp)
+ {
+ psuname = std::get_if<std::string>(&properties[psuNameProp]);
+ }
+ else if (property.first == presLineName)
+ {
+ preslineptr =
+ std::get_if<std::string>(&properties[presLineName]);
+ }
+ }
+ catch (const std::exception& e)
+ {}
+ }
+
+ if (i2cbus && i2caddr && psuname && !psuname->empty())
+ {
+ std::string invpath = basePSUInvPath;
+ invpath.push_back(psuname->back());
+ std::string presline = "";
+
+ lg2::debug("Inventory Path: {INVPATH}", "INVPATH", invpath);
+
+ if (nullptr != preslineptr)
+ {
+ presline = *preslineptr;
+ }
+
+ auto invMatch =
+ std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
+ return psu->getInventoryPath() == invpath;
+ });
+ if (invMatch != psus.end())
+ {
+ // This power supply has the same inventory path as the one with
+ // information just added to D-Bus.
+ // Changes to GPIO line name unlikely, so skip checking.
+ // Changes to the I2C bus and address unlikely, as that would
+ // require corresponding device tree updates.
+ // Return out to avoid duplicate object creation.
+ return;
+ }
+
+ buildDriverName(*i2cbus, *i2caddr);
+ 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);
+ psus.emplace_back(std::move(psu));
+
+ // Subscribe to power supply presence changes
+ auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::propertiesChanged(invpath,
+ INVENTORY_IFACE),
+ [this](auto& msg) { this->psuPresenceChanged(msg); });
+ presenceMatches.emplace_back(std::move(presenceMatch));
+ }
+ if (psus.empty())
+ {
+ lg2::info("No power supplies to monitor");
+ }
+ else
+ {
+ populateDriverName();
+ }
+}
+
+void Chassis::getSupportedConfiguration()
+{
+ // TBD
+}
+
+void Chassis::populateSupportedConfiguration(
+ const util::DbusPropertyMap& properties)
+{
+ try
+ {
+ auto propIt = properties.find("SupportedType");
+ if (propIt == properties.end())
+ {
+ return;
+ }
+ const std::string* type = std::get_if<std::string>(&(propIt->second));
+ if ((type == nullptr) || (*type != "PowerSupply"))
+ {
+ return;
+ }
+
+ propIt = properties.find("SupportedModel");
+ if (propIt == properties.end())
+ {
+ return;
+ }
+ const std::string* model = std::get_if<std::string>(&(propIt->second));
+ if (model == nullptr)
+ {
+ return;
+ }
+
+ SupportedPsuConfiguration supportedPsuConfig;
+ propIt = properties.find("RedundantCount");
+ if (propIt != properties.end())
+ {
+ const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
+ if (count != nullptr)
+ {
+ supportedPsuConfig.powerSupplyCount = *count;
+ }
+ }
+ propIt = properties.find("InputVoltage");
+ if (propIt != properties.end())
+ {
+ const std::vector<uint64_t>* voltage =
+ std::get_if<std::vector<uint64_t>>(&(propIt->second));
+ if (voltage != nullptr)
+ {
+ supportedPsuConfig.inputVoltage = *voltage;
+ }
+ }
+
+ // The PowerConfigFullLoad is an optional property, default it to false
+ // since that's the default value of the power-config-full-load GPIO.
+ supportedPsuConfig.powerConfigFullLoad = false;
+ propIt = properties.find("PowerConfigFullLoad");
+ if (propIt != properties.end())
+ {
+ const bool* fullLoad = std::get_if<bool>(&(propIt->second));
+ if (fullLoad != nullptr)
+ {
+ supportedPsuConfig.powerConfigFullLoad = *fullLoad;
+ }
+ }
+
+ supportedConfigs.emplace(*model, supportedPsuConfig);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info("populateSupportedConfiguration error {ERR}", "ERR", e);
+ }
+}
+
+void Chassis::psuPresenceChanged(sdbusplus::message_t& msg)
+{
+ std::string msgSensor;
+ std::map<std::string, std::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))
+ {
+ // A PSU became present, force the PSU validation to run.
+ runValidateConfig = true;
+ validationTimer->restartOnce(validationTimeout);
+ }
+ }
+}
+
+void Chassis::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
+{
+ namespace fs = std::filesystem;
+ std::stringstream ss;
+ ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
+ std::string symLinkPath =
+ deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
+ try
+ {
+ fs::path linkStrPath = fs::read_symlink(symLinkPath);
+ driverName = linkStrPath.filename();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to find device driver {SYM_LINK_PATH}, error {ERROR_STR}",
+ "SYM_LINK_PATH", symLinkPath, "ERROR_STR", e);
+ }
+}
+
+void Chassis::populateDriverName()
+{
+ std::string driverName;
+ // Search in PSUs for driver name
+ std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
+ if (!psu->getDriverName().empty())
+ {
+ driverName = psu->getDriverName();
+ }
+ });
+ // Assign driver name to all PSUs
+ std::for_each(psus.begin(), psus.end(),
+ [&driverName](auto& psu) { psu->setDriverName(driverName); });
+}
+
+uint32_t Chassis::getChassisPathUniqueId()
+{
+ uint32_t chassisId = invalidObjectPathUniqueId;
+ try
+ {
+ getProperty(decoratorChassisId, chassisIdProp, chassisPath,
+ INVENTORY_MGR_IFACE, bus, chassisId);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to find chassis ID - exception: {ERROR}", "ERROR",
+ e);
+ }
+ return chassisId;
+}
+
+} // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis.hpp b/phosphor-power-supply/chassis.hpp
new file mode 100644
index 0000000..527b0ff
--- /dev/null
+++ b/phosphor-power-supply/chassis.hpp
@@ -0,0 +1,189 @@
+#pragma once
+
+#include "power_supply.hpp"
+#include "types.hpp"
+#include "utility.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.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>
+
+#include <map>
+#include <string>
+#include <vector>
+
+struct SupportedPsuConfiguration
+{
+ int powerSupplyCount;
+ std::vector<uint64_t> inputVoltage;
+ bool powerConfigFullLoad;
+};
+
+using namespace phosphor::power::psu;
+
+namespace phosphor::power::chassis
+{
+
+constexpr uint32_t invalidObjectPathUniqueId = 9999;
+using PowerSystemInputsInterface = sdbusplus::xyz::openbmc_project::State::
+ Decorator::server::PowerSystemInputs;
+using PowerSystemInputsObject =
+ sdbusplus::server::object_t<PowerSystemInputsInterface>;
+
+// Validation timeout. Allow 30s to detect if new EM interfaces show up in D-Bus
+// before performing the validation.
+// Previously the timer was set to 10 seconds was too short, it results in
+// incorrect errors being logged, but no real consequence of longer timeout.
+constexpr auto validationTimeout = std::chrono::seconds(30);
+
+/**
+ * @class Chassis
+ *
+ * @brief This class will create an object used to manage and monitor a list of
+ * power supply devices attached to the chassis.
+ */
+class Chassis
+{
+ public:
+ Chassis() = delete;
+ ~Chassis() = default;
+ Chassis(const Chassis&) = delete;
+ Chassis& operator=(const Chassis&) = delete;
+ Chassis(Chassis&&) = delete;
+ Chassis& operator=(Chassis&&) = delete;
+
+ /**
+ * @brief Constructor to read configuration from D-Bus.
+ *
+ * @param[in] bus - D-Bus bus object
+ * @param[in] chassisPath - Chassis path
+ */
+ Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath);
+
+ /**
+ * @brief Get the status of Power on.
+ */
+ bool isPowerOn()
+ {
+ return powerOn;
+ }
+
+ private:
+ /**
+ * @brief The D-Bus object
+ */
+ sdbusplus::bus_t& bus;
+
+ /**
+ * @brief The timer that performs power supply validation as the entity
+ * manager interfaces show up in d-bus.
+ */
+ std::unique_ptr<
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>
+ validationTimer;
+
+ /** @brief True if the power is on. */
+ bool powerOn = false;
+
+ /** @brief Used to subscribe to D-Bus power supply presence changes */
+ std::vector<std::unique_ptr<sdbusplus::bus::match_t>> presenceMatches;
+
+ /**
+ * @brief Flag to indicate if the validateConfig() function should be run.
+ * Set to false once the configuration has been validated to avoid
+ * running multiple times due to interfaces added signal. Set to
+ * true during power off to trigger the validation on power on.
+ */
+ bool runValidateConfig = true;
+
+ /**
+ * @brief Map of supported PSU configurations that include the model name
+ * and their properties.
+ */
+ std::map<std::string, SupportedPsuConfiguration> supportedConfigs;
+
+ /**
+ * @brief The vector for power supplies.
+ */
+ std::vector<std::unique_ptr<PowerSupply>> psus;
+
+ /**
+ * @brief The device driver name for all power supplies.
+ */
+ std::string driverName;
+
+ /**
+ * @brief Chassis D-Bus object path
+ */
+ std::string chassisPath;
+
+ /**
+ * @brief The Chassis path unique ID
+ */
+ uint32_t chassisPathUniqueId = invalidObjectPathUniqueId;
+
+ /**
+ * @brief Get PSU properties from D-Bus, use that to build a power supply
+ * object.
+ *
+ * @param[in] properties - A map of property names and values
+ */
+ void getPSUProperties(util::DbusPropertyMap& properties);
+
+ /**
+ * @brief Get PSU configuration from D-Bus
+ */
+ void getPSUConfiguration();
+
+ /**
+ * @brief Initialize the chassis's supported configuration from the
+ * Supported Configuration D-Bus object provided by the Entity
+ * Manager.
+ */
+ void getSupportedConfiguration();
+
+ /**
+ * @brief Callback for inventory property changes
+ *
+ * Process change of the Power Supply presence.
+ *
+ * @param[in] msg - Data associated with the Present change signal
+ **/
+ void psuPresenceChanged(sdbusplus::message_t& msg);
+
+ /**
+ * @brief Helper function to populate the PSU supported configuration
+ *
+ * @param[in] properties - A map of property names and values
+ */
+ void populateSupportedConfiguration(
+ const util::DbusPropertyMap& properties);
+
+ /**
+ * @brief Build the device driver name for the power supply.
+ *
+ * @param[in] i2cbus - i2c bus
+ * @param[in] i2caddr - i2c bus address
+ */
+ void buildDriverName(uint64_t i2cbus, uint64_t i2caddr);
+
+ /**
+ * @brief Find PSU with device driver name, then populate the device
+ * driver name to all PSUs (including missing PSUs).
+ */
+ void populateDriverName();
+
+ /**
+ * @brief Get chassis path unique ID.
+ *
+ * @return uint32_t - Chassis path unique ID.
+ */
+ uint32_t getChassisPathUniqueId();
+};
+
+} // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/docs/MultiChassis.md b/phosphor-power-supply/docs/MultiChassis.md
index 106e614..cf3bea4 100644
--- a/phosphor-power-supply/docs/MultiChassis.md
+++ b/phosphor-power-supply/docs/MultiChassis.md
@@ -12,6 +12,7 @@
```text
/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0
+
```
## Goal: Support Multi-Chassis PSUs
@@ -46,7 +47,7 @@
for D-BUS events to track chassis PGOOD Status.
- Retrieve the PSU configurations from **entity manager**
- Find all PSUs associated with chassis from **inventory manager** (using
- **[getSubTree](https://github.com/openbmc/phosphor-power/blob/master/utility.hpp)**)
+ [`getSubTree`](https://github.com/openbmc/phosphor-power/blob/master/utility.hpp))
and add them to vector in the chassis object. **getSubTree** is a function
interacts with the D-Bus object mapper to retrieve all objects under a
specified path that implement interface to certain depth.