chassis-psu: New Functions for MultiChassis App
Implemented several functions for monitoring power supplies in
multi-chassis systems. Added a new main function, made minor
modification to the PowerSupply class, and added several functions to
the Chassis and ChassisManager classes.
The following is a summary of each object class new addition or modified
functions:
ChassisManager:
- initChassisPowerMonitoring(): Loops through all the chassis in the
system and initializes power monitoring process for each chassis's
PSUs.
Chassis:
- initPowerMonitoring(): Subscribe to D-Bus power change and initialize
power monitoring.
- supportedConfigurationInterfaceAdded(): Handle addition of supported
configuration and update missing PSUs.
- psuInterfaceAdded(): Handle addition of PSUs on D-Bus.
- validatePsuConfigAndInterfacesProcessed(): Validate the PSU
configuration and reset validation timer if power is on, supported
configs and have PSUs.
- analyzeBrownout(): Analyze PSUs for a brownout failure and log error.
- syncHistory(): Toggles the GPIO to sync power supply input history
readings.
- setInputVoltageRating(): Inform each PSUs to set its PSU input.
- createError(): Create OpenBMC error.
- hasRequiredPSUs(): TODO
- updateMissingPSUs(): Update PSU inventory.
- getSupportedConfiguration(): Retrieve supported configuration from
D-BUS and matches chassis ID with the current chassis id to update
chassis configuration.
- saveChassisName(): Save chassis short name in the class.
- powerStateChanged(): Handle for power state property changes.
- attemptToCreatePowerConfigGPIO(): Attempt to create GPIO
PowerSupply:
- PowerSupply(): Added additional class constructors. The constructors
have the same parameters as the original, except that the new
constructor include an extra parameter chassis short name.
- The PowerSupply class functions implementation remains the same as
the original, except for minor change in
PowerSupply::setupInputPowerPeakSensor(), where the sensorPath was
modified to include chassis name.
Test in simulation:
- Verified supported configuration added, where it polpulates
supported properties and updates PSUs changes.
- Validated some of the brownout functionality using fault injection.
- Validated the PowerSupply class sets the appropriate input voltage
for the target chassis PSUs.
- Verified that the chassis name is included in the PSU power input
peak sensors on D-bus.
Change-Id: I75a1ab1dd004767f072e35f3ce2c83ff281eb1ca
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/phosphor-power-supply/chassis.cpp b/phosphor-power-supply/chassis.cpp
index 85e2a88..4467db5 100644
--- a/phosphor-power-supply/chassis.cpp
+++ b/phosphor-power-supply/chassis.cpp
@@ -2,7 +2,6 @@
#include "chassis.hpp"
-#include <filesystem>
#include <iostream>
using namespace phosphor::logging;
@@ -10,6 +9,8 @@
namespace phosphor::power::chassis
{
+constexpr auto powerSystemsInputsObjPath =
+ "/xyz/openbmc_project/power/power_supplies/chassis{}/psus";
constexpr auto IBMCFFPSInterface =
"xyz.openbmc_project.Configuration.IBMCFFPSConnector";
constexpr auto chassisIdProp = "SlotNumber";
@@ -25,17 +26,23 @@
const auto entityMgrService = "xyz.openbmc_project.EntityManager";
const auto decoratorChassisId = "xyz.openbmc_project.Inventory.Decorator.Slot";
+constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
+
Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
const sdeventplus::Event& e) :
bus(bus), chassisPath(chassisPath),
- chassisPathUniqueId(getChassisPathUniqueId(chassisPath)), eventLoop(e)
+ chassisPathUniqueId(getChassisPathUniqueId(chassisPath)),
+ powerSystemInputs(
+ bus, std::format(powerSystemsInputsObjPath, chassisPathUniqueId)),
+ eventLoop(e)
{
+ saveChassisName();
getPSUConfiguration();
+ getSupportedConfiguration();
}
void Chassis::getPSUConfiguration()
{
- namespace fs = std::filesystem;
auto depth = 0;
try
@@ -49,12 +56,7 @@
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))
-
+ if (chassisPathUniqueId == getParentEMUniqueId(bus, path))
{
// For each object in the array of objects, I want
// to get properties from the service, path, and
@@ -150,12 +152,10 @@
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));
+ std::bind(&Chassis::isPowerOn, this), chassisShortName);
psus.emplace_back(std::move(psu));
// Subscribe to power supply presence changes
@@ -178,7 +178,38 @@
void Chassis::getSupportedConfiguration()
{
- // TBD
+ try
+ {
+ util::DbusSubtree subtree =
+ util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
+ if (subtree.empty())
+ {
+ throw std::runtime_error("Supported Configuration Not Found");
+ }
+
+ for (const auto& [objPath, services] : subtree)
+ {
+ std::string service = services.begin()->first;
+ if (objPath.empty() || service.empty())
+ {
+ continue;
+ }
+
+ if (chassisPathUniqueId == getParentEMUniqueId(bus, objPath))
+ {
+ auto properties = util::getAllProperties(
+ bus, objPath, supportedConfIntf, service);
+ populateSupportedConfiguration(properties);
+ break;
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ // Interface or property not found. Let the Interfaces Added callback
+ // process the information once the interfaces are added to D-Bus.
+ lg2::info("Interface or Property not found, error {ERROR}", "ERROR", e);
+ }
}
void Chassis::populateSupportedConfiguration(
@@ -319,5 +350,608 @@
return invalidObjectPathUniqueId;
}
-void Chassis::analyze() {}
+void Chassis::initPowerMonitoring()
+{
+ using namespace sdeventplus;
+
+ validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
+ eventLoop, std::bind(&Chassis::validateConfig, this));
+ attemptToCreatePowerConfigGPIO();
+
+ // 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); });
+ // TODO initialize the chassis
+}
+
+void Chassis::validateConfig()
+{
+ if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
+ {
+ return;
+ }
+
+ for (const auto& psu : psus)
+ {
+ if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
+ {
+ // Do not try to validate if input voltage fault present.
+ validationTimer->restartOnce(validationTimeout);
+ return;
+ }
+ }
+
+ std::map<std::string, std::string> additionalData;
+ auto supported = hasRequiredPSUs(additionalData);
+ if (supported)
+ {
+ runValidateConfig = false;
+ double actualVoltage;
+ int inputVoltage;
+ int previousInputVoltage = 0;
+ bool voltageMismatch = false;
+
+ for (const auto& psu : psus)
+ {
+ if (!psu->isPresent())
+ {
+ // Only present PSUs report a valid input voltage
+ continue;
+ }
+ psu->getInputVoltage(actualVoltage, inputVoltage);
+ if (previousInputVoltage && inputVoltage &&
+ (previousInputVoltage != inputVoltage))
+ {
+ additionalData["EXPECTED_VOLTAGE"] =
+ std::to_string(previousInputVoltage);
+ additionalData["ACTUAL_VOLTAGE"] =
+ std::to_string(actualVoltage);
+ voltageMismatch = true;
+ }
+ if (!previousInputVoltage && inputVoltage)
+ {
+ previousInputVoltage = inputVoltage;
+ }
+ }
+ if (!voltageMismatch)
+ {
+ return;
+ }
+ }
+
+ // Validation failed, create an error log.
+ // Return without setting the runValidateConfig flag to false because
+ // it may be that an additional supported configuration interface is
+ // added and we need to validate it to see if it matches this system.
+ createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
+ additionalData);
+}
+
+void Chassis::syncHistory()
+{
+ if (driverName != ACBEL_FSG032_DD_NAME)
+ {
+ if (!syncHistoryGPIO)
+ {
+ try
+ {
+ syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
+ }
+ catch (const std::exception& e)
+ {
+ // Not an error, system just hasn't implemented the synch gpio
+ lg2::info("No synchronization GPIO found");
+ syncHistoryGPIO = nullptr;
+ }
+ }
+ if (syncHistoryGPIO)
+ {
+ const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
+ lg2::info("Synchronize INPUT_HISTORY");
+ syncHistoryGPIO->toggleLowHigh(delay);
+ lg2::info("Synchronize INPUT_HISTORY completed");
+ }
+ }
+
+ // Always clear synch history required after calling this function
+ for (auto& psu : psus)
+ {
+ psu->clearSyncHistoryRequired();
+ }
+}
+
+void Chassis::analyze()
+{
+ auto syncHistoryRequired =
+ std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
+ return psu->isSyncHistoryRequired();
+ });
+ if (syncHistoryRequired)
+ {
+ syncHistory();
+ }
+
+ for (auto& psu : psus)
+ {
+ psu->analyze();
+ }
+
+ analyzeBrownout();
+
+ // Only perform individual PSU analysis if power is on and a brownout has
+ // not already been logged
+ //
+ // Note: TODO Check the chassis state when the power sequencer publishes
+ // chassis power on and system power on
+ if (powerOn && !brownoutLogged)
+ {
+ for (auto& psu : psus)
+ {
+ std::map<std::string, std::string> additionalData;
+
+ if (!psu->isFaultLogged() && !psu->isPresent() &&
+ !validationTimer->isEnabled())
+ {
+ std::map<std::string, std::string> requiredPSUsData;
+ auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
+ // TODO check required PSU
+
+ if (!requiredPSUsPresent)
+ {
+ additionalData.merge(requiredPSUsData);
+ // Create error for power supply missing.
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+ additionalData["CALLOUT_PRIORITY"] = "H";
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
+ additionalData);
+ }
+ psu->setFaultLogged();
+ }
+ else if (!psu->isFaultLogged() && psu->isFaulted())
+ {
+ // Add STATUS_WORD and STATUS_MFR last response, in padded
+ // hexadecimal format.
+ additionalData["STATUS_WORD"] =
+ std::format("{:#04x}", psu->getStatusWord());
+ additionalData["STATUS_MFR"] =
+ std::format("{:#02x}", psu->getMFRFault());
+ // If there are faults being reported, they possibly could be
+ // related to a bug in the firmware version running on the power
+ // supply. Capture that data into the error as well.
+ additionalData["FW_VERSION"] = psu->getFWVersion();
+
+ if (psu->hasCommFault())
+ {
+ additionalData["STATUS_CML"] =
+ std::format("{:#02x}", psu->getStatusCML());
+ /* Attempts to communicate with the power supply have
+ * reached there limit. Create an error. */
+ additionalData["CALLOUT_DEVICE_PATH"] =
+ psu->getDevicePath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ else if ((psu->hasInputFault() || psu->hasVINUVFault()))
+ {
+ // Include STATUS_INPUT for input faults.
+ additionalData["STATUS_INPUT"] =
+ std::format("{:#02x}", psu->getStatusInput());
+
+ /* The power supply location might be needed if the input
+ * fault is due to a problem with the power supply itself.
+ * Include the inventory path with a call out priority of
+ * low.
+ */
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+ additionalData["CALLOUT_PRIORITY"] = "L";
+ createError("xyz.openbmc_project.Power.PowerSupply.Error."
+ "InputFault",
+ additionalData);
+ psu->setFaultLogged();
+ }
+ else if (psu->hasPSKillFault())
+ {
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
+ additionalData);
+ psu->setFaultLogged();
+ }
+ else if (psu->hasVoutOVFault())
+ {
+ // Include STATUS_VOUT for Vout faults.
+ additionalData["STATUS_VOUT"] =
+ std::format("{:#02x}", psu->getStatusVout());
+
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ else if (psu->hasIoutOCFault())
+ {
+ // Include STATUS_IOUT for Iout faults.
+ additionalData["STATUS_IOUT"] =
+ std::format("{:#02x}", psu->getStatusIout());
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
+ psu->hasPSCS12VFault())
+ {
+ // Include STATUS_VOUT for Vout faults.
+ additionalData["STATUS_VOUT"] =
+ std::format("{:#02x}", psu->getStatusVout());
+
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ // A fan fault should have priority over a temperature fault,
+ // since a failed fan may lead to a temperature problem.
+ // Only process if not in power fault window.
+ else if (psu->hasFanFault() && !powerFaultOccurring)
+ {
+ // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
+ additionalData["STATUS_TEMPERATURE"] =
+ std::format("{:#02x}", psu->getStatusTemperature());
+ additionalData["STATUS_FANS_1_2"] =
+ std::format("{:#02x}", psu->getStatusFans12());
+
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ else if (psu->hasTempFault())
+ {
+ // Include STATUS_TEMPERATURE for temperature faults.
+ additionalData["STATUS_TEMPERATURE"] =
+ std::format("{:#02x}", psu->getStatusTemperature());
+
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ else if (psu->hasMFRFault())
+ {
+ /* This can represent a variety of faults that result in
+ * calling out the power supply for replacement: Output
+ * OverCurrent, Output Under Voltage, and potentially other
+ * faults.
+ *
+ * Also plan on putting specific fault in AdditionalData,
+ * along with register names and register values
+ * (STATUS_WORD, STATUS_MFR, etc.).*/
+
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ // Only process if not in power fault window.
+ else if (psu->hasPgoodFault() && !powerFaultOccurring)
+ {
+ /* POWER_GOOD# is not low, or OFF is on */
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ createError(
+ "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+ additionalData);
+
+ psu->setFaultLogged();
+ }
+ }
+ }
+ }
+}
+
+void Chassis::analyzeBrownout()
+{
+ // Count number of power supplies failing
+ size_t presentCount = 0;
+ size_t notPresentCount = 0;
+ size_t acFailedCount = 0;
+ size_t pgoodFailedCount = 0;
+ for (const auto& psu : psus)
+ {
+ if (psu->isPresent())
+ {
+ ++presentCount;
+ if (psu->hasACFault())
+ {
+ ++acFailedCount;
+ }
+ else if (psu->hasPgoodFault())
+ {
+ ++pgoodFailedCount;
+ }
+ }
+ else
+ {
+ ++notPresentCount;
+ }
+ }
+
+ // Only issue brownout failure if chassis pgood has failed, it has not
+ // already been logged, at least one PSU has seen an AC fail, and all
+ // present PSUs have an AC or pgood failure. Note an AC fail is only set if
+ // at least one PSU is present.
+ if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
+ (presentCount == (acFailedCount + pgoodFailedCount)))
+ {
+ // Indicate that the system is in a brownout condition by creating an
+ // error log and setting the PowerSystemInputs status property to
+ // Fault.
+ powerSystemInputs.status(
+ sdbusplus::xyz::openbmc_project::State::Decorator::server::
+ PowerSystemInputs::Status::Fault);
+
+ std::map<std::string, std::string> additionalData;
+ additionalData.emplace("NOT_PRESENT_COUNT",
+ std::to_string(notPresentCount));
+ additionalData.emplace("VIN_FAULT_COUNT",
+ std::to_string(acFailedCount));
+ additionalData.emplace("PGOOD_FAULT_COUNT",
+ std::to_string(pgoodFailedCount));
+ lg2::info(
+ "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
+ "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
+ acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
+
+ createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
+ additionalData);
+ brownoutLogged = true;
+ }
+ else
+ {
+ // If a brownout was previously logged but at least one PSU is not
+ // currently in AC fault, determine if the brownout condition can be
+ // cleared
+ if (brownoutLogged && (acFailedCount < presentCount))
+ {
+ // TODO Power State
+ }
+ }
+}
+
+void Chassis::createError(const std::string& faultName,
+ std::map<std::string, std::string>& additionalData)
+{
+ using namespace sdbusplus::xyz::openbmc_project;
+ constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
+ constexpr auto loggingCreateInterface =
+ "xyz.openbmc_project.Logging.Create";
+
+ try
+ {
+ additionalData["_PID"] = std::to_string(getpid());
+
+ auto service =
+ util::getService(loggingObjectPath, loggingCreateInterface, bus);
+
+ if (service.empty())
+ {
+ lg2::error("Unable to get logging manager service");
+ return;
+ }
+
+ auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
+ loggingCreateInterface, "Create");
+
+ auto level = Logging::server::Entry::Level::Error;
+ method.append(faultName, level, additionalData);
+
+ auto reply = bus.call(method);
+ // TODO set power supply error
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
+ "FAULT_NAME", faultName, "ERROR", e);
+ }
+}
+
+void Chassis::attemptToCreatePowerConfigGPIO()
+{
+ try
+ {
+ powerConfigGPIO = createGPIO("power-config-full-load");
+ }
+ catch (const std::exception& e)
+ {
+ powerConfigGPIO = nullptr;
+ lg2::info("GPIO not implemented in {CHASSIS}", "CHASSIS",
+ chassisShortName);
+ }
+}
+
+void Chassis::supportedConfigurationInterfaceAdded(
+ const util::DbusPropertyMap& properties)
+{
+ populateSupportedConfiguration(properties);
+ updateMissingPSUs();
+}
+
+void Chassis::psuInterfaceAdded(util::DbusPropertyMap& properties)
+{
+ getPSUProperties(properties);
+ updateMissingPSUs();
+}
+
+bool Chassis::hasRequiredPSUs(
+ std::map<std::string, std::string>& additionalData)
+{
+ // ignore the following loop so code will compile
+ for (const auto& pair : additionalData)
+ {
+ std::cout << "Key = " << pair.first
+ << " additionalData value = " << pair.second << "\n";
+ }
+ return true;
+
+ // TODO validate having the required PSUs
+}
+
+void Chassis::updateMissingPSUs()
+{
+ if (supportedConfigs.empty() || psus.empty())
+ {
+ return;
+ }
+
+ // Power supplies default to missing. If the power supply is present,
+ // the PowerSupply object will update the inventory Present property to
+ // true. If we have less than the required number of power supplies, and
+ // this power supply is missing, update the inventory Present property
+ // to false to indicate required power supply is missing. Avoid
+ // indicating power supply missing if not required.
+
+ auto presentCount =
+ std::count_if(psus.begin(), psus.end(),
+ [](const auto& psu) { return psu->isPresent(); });
+
+ for (const auto& config : supportedConfigs)
+ {
+ for (const auto& psu : psus)
+ {
+ auto psuModel = psu->getModelName();
+ auto psuShortName = psu->getShortName();
+ auto psuInventoryPath = psu->getInventoryPath();
+ auto relativeInvPath =
+ psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+ auto psuPresent = psu->isPresent();
+ auto presProperty = false;
+ auto propReadFail = false;
+
+ try
+ {
+ presProperty = getPresence(bus, psuInventoryPath);
+ propReadFail = false;
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ propReadFail = true;
+ // Relying on property change or interface added to retry.
+ // Log an informational trace to the journal.
+ lg2::info(
+ "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
+ "PSU_INVENTORY_PATH", psuInventoryPath);
+ }
+
+ if (psuModel.empty())
+ {
+ if (!propReadFail && (presProperty != psuPresent))
+ {
+ // We already have this property, and it is not false
+ // set Present to false
+ setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+ }
+ continue;
+ }
+
+ if (config.first != psuModel)
+ {
+ continue;
+ }
+
+ if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
+ {
+ setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+ }
+ }
+ }
+}
+
+void Chassis::powerStateChanged(sdbusplus::message_t& msg)
+{
+ std::string msgSensor;
+ std::map<std::string, std::variant<int>> msgData;
+ msg.read(msgSensor, msgData);
+
+ // Check if it was the state property that changed.
+ auto valPropMap = msgData.find("state");
+ if (valPropMap != msgData.end())
+ {
+ int state = std::get<int>(valPropMap->second);
+ if (state)
+ {
+ // Power on requested
+ powerOn = true;
+ powerFaultOccurring = false;
+ validationTimer->restartOnce(validationTimeout);
+ // TODO clear faults
+
+ syncHistory();
+ // TODO set power config
+
+ setInputVoltageRating();
+ }
+ else
+ {
+ // Power off requested
+ powerOn = false;
+ powerFaultOccurring = false;
+ runValidateConfig = true;
+ }
+ }
+
+ // Check if it was the pgood property that changed.
+ valPropMap = msgData.find("pgood");
+ if (valPropMap != msgData.end())
+ {
+ int pgood = std::get<int>(valPropMap->second);
+ if (!pgood)
+ {
+ // Chassis power good has turned off
+ if (powerOn)
+ {
+ // pgood is off but state is on, in power fault window
+ powerFaultOccurring = true;
+ }
+ }
+ }
+ lg2::info(
+ "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
+ "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
+}
+
} // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis.hpp b/phosphor-power-supply/chassis.hpp
index e976921..6471f26 100644
--- a/phosphor-power-supply/chassis.hpp
+++ b/phosphor-power-supply/chassis.hpp
@@ -1,6 +1,5 @@
#pragma once
-
-#include "power_supply.hpp"
+#include "new_power_supply.hpp"
#include "types.hpp"
#include "utility.hpp"
@@ -13,6 +12,7 @@
#include <sdeventplus/utility/timer.hpp>
#include <xyz/openbmc_project/State/Decorator/PowerSystemInputs/server.hpp>
+#include <filesystem>
#include <map>
#include <string>
#include <vector>
@@ -25,6 +25,8 @@
};
using namespace phosphor::power::psu;
+using namespace phosphor::power::util;
+using namespace sdeventplus;
namespace phosphor::power::chassis
{
@@ -42,6 +44,18 @@
constexpr auto validationTimeout = std::chrono::seconds(30);
/**
+ * @class PowerSystemInputs
+ * @brief A concrete implementation for the PowerSystemInputs interface.
+ */
+class PowerSystemInputs : public PowerSystemInputsObject
+{
+ public:
+ PowerSystemInputs(sdbusplus::bus_t& bus, const std::string& path) :
+ PowerSystemInputsObject(bus, path.c_str())
+ {}
+};
+
+/**
* @class Chassis
*
* @brief This class will create an object used to manage and monitor a list of
@@ -91,6 +105,47 @@
return powerOn;
}
+ /**
+ * @brief Initialize power monitoring infrastructure for Chassis.
+ * Sets up configuration validation timer, attempts to create GPIO,
+ * subscribe to D-Bus power state change events.
+ */
+ void initPowerMonitoring();
+
+ /**
+ * @brief Handles addition of the SupportedConfiguration interface.
+ * This function triggered when the SupportedConfiguration interface added
+ * to a D-Bus object. The function calls populateSupportedConfiguration()
+ * and updateMissingPSUs() to processes the provided properties.
+ *
+ * @param properties A map of D-Bus properties associated with the
+ * SupportedConfiguration interface.
+ */
+ void supportedConfigurationInterfaceAdded(
+ const util::DbusPropertyMap& properties);
+
+ /**
+ * @brief Handle the addition of PSU interface.
+ * This function is called when a Power Supply interface added to a D-Bus.
+ * This function calls getPSUProperties() and updateMissingPSUs().
+ *
+ * @param properties A map of D-Bus properties for the PSU interface.
+ */
+ void psuInterfaceAdded(util::DbusPropertyMap& properties);
+
+ /**
+ * @brief Call to validate the psu configuration if the power is on and both
+ * the IBMCFFPSConnector and SupportedConfiguration interfaces have been
+ * processed
+ */
+ void validatePsuConfigAndInterfacesProcessed()
+ {
+ if (powerOn && !psus.empty() && !supportedConfigs.empty())
+ {
+ validationTimer->restartOnce(validationTimeout);
+ }
+ };
+
private:
/**
* @brief The D-Bus object
@@ -108,6 +163,20 @@
/** @brief True if the power is on. */
bool powerOn = false;
+ /** @brief True if power control is in the window between chassis pgood loss
+ * and power off.
+ */
+ bool powerFaultOccurring = false;
+
+ /** @brief True if an error for a brownout has already been logged. */
+ bool brownoutLogged = false;
+
+ /** @brief Used as part of subscribing to power on state changes*/
+ std::string powerService;
+
+ /** @brief Used to subscribe to D-Bus power on state changes */
+ std::unique_ptr<sdbusplus::bus::match_t> powerOnMatch;
+
/** @brief Used to subscribe to D-Bus power supply presence changes */
std::vector<std::unique_ptr<sdbusplus::bus::match_t>> presenceMatches;
@@ -136,22 +205,44 @@
std::string driverName;
/**
+ * @brief The libgpiod object for setting the power supply config
+ */
+ std::unique_ptr<GPIOInterfaceBase> powerConfigGPIO = nullptr;
+
+ /**
* @brief Chassis D-Bus object path
*/
std::string chassisPath;
/**
+ * @brief Chassis name;
+ */
+ std::string chassisShortName;
+
+ /**
* @brief The Chassis path unique ID
+ *
+ * Note: chassisPathUniqueId must be declared before powerSystemInputs.
*/
uint64_t chassisPathUniqueId = invalidObjectPathUniqueId;
/**
+ * @brief PowerSystemInputs object
+ */
+ PowerSystemInputs powerSystemInputs;
+
+ /**
* @brief Declares a constant reference to an sdeventplus::Event to manage
* async processing.
*/
const sdeventplus::Event& eventLoop;
/**
+ * @brief GPIO to toggle to 'sync' power supply input history.
+ */
+ std::unique_ptr<GPIOInterfaceBase> syncHistoryGPIO = nullptr;
+
+ /**
* @brief Get PSU properties from D-Bus, use that to build a power supply
* object.
*
@@ -165,9 +256,9 @@
void getPSUConfiguration();
/**
- * @brief Initialize the chassis's supported configuration from the
- * Supported Configuration D-Bus object provided by the Entity
- * Manager.
+ * @brief Queries D-Bus for chassis configuration provided by the Entity
+ * Manager. Matches the object against the current chassis unique ID. Upon
+ * finding a match calls populateSupportedConfiguration().
*/
void getSupportedConfiguration();
@@ -209,6 +300,99 @@
* @return uint64_t - Chassis path unique ID.
*/
uint64_t getChassisPathUniqueId(const std::string& path);
+
+ /**
+ * @brief Initializes the chassis.
+ *
+ */
+ void initialize() {}; // TODO
+
+ /**
+ * @brief Perform power supply configuration validation.
+ * @details Validates if the existing power supply properties are a
+ * supported configuration, and acts on its findings such as logging
+ * errors.
+ */
+ void validateConfig();
+
+ /**
+ * @brief Analyze the set of the power supplies for a brownout failure. Log
+ * error when necessary, clear brownout condition when window has passed.
+ */
+ void analyzeBrownout();
+
+ /**
+ * @brief Toggles the GPIO to sync power supply input history readings
+ * @details This GPIO is connected to all supplies. This will clear the
+ * previous readings out of the supplies and restart them both at the
+ * same time zero and at record ID 0. The supplies will return 0
+ * bytes of data for the input history command right after this until
+ * a new entry shows up.
+ *
+ * This will cause the code to delete all previous history data and
+ * start fresh.
+ */
+ void syncHistory();
+
+ /**
+ * @brief Tells each PSU to set its power supply input
+ * voltage rating D-Bus property.
+ */
+ inline void setInputVoltageRating()
+ {
+ for (auto& psu : psus)
+ {
+ psu->setInputVoltageRating();
+ }
+ }
+
+ /**
+ * Create an error
+ *
+ * @param[in] faultName - 'name' message for the BMC error log entry
+ * @param[in,out] additionalData - The AdditionalData property for the error
+ */
+ void createError(const std::string& faultName,
+ std::map<std::string, std::string>& additionalData);
+
+ /**
+ * @brief Check that all PSUs have the same model name and that the system
+ * has the required number of PSUs present as specified in the Supported
+ * Configuration interface.
+ *
+ * @param[out] additionalData - Contains debug information on why the check
+ * might have failed. Can be used to fill in error logs.
+ * @return true if all the required PSUs are present, false otherwise.
+ */
+ bool hasRequiredPSUs(std::map<std::string, std::string>& additionalData);
+
+ /**
+ * @brief Update inventory for missing required power supplies
+ */
+ void updateMissingPSUs();
+
+ /**
+ * @brief Assign chassis short name.
+ */
+ void saveChassisName()
+ {
+ std::filesystem::path path(chassisPath);
+ chassisShortName = path.filename();
+ }
+
+ /**
+ * @brief Callback for power state property changes
+ *
+ * Process changes to the powered on state property for the chassis.
+ *
+ * @param[in] msg - Data associated with the power state signal
+ */
+ void powerStateChanged(sdbusplus::message_t& msg);
+
+ /**
+ * @breif Attempt to create GPIO
+ */
+ void attemptToCreatePowerConfigGPIO();
};
} // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis_manager.cpp b/phosphor-power-supply/chassis_manager.cpp
index 710b5dc..db16ac2 100644
--- a/phosphor-power-supply/chassis_manager.cpp
+++ b/phosphor-power-supply/chassis_manager.cpp
@@ -3,6 +3,7 @@
#include "chassis_manager.hpp"
#include <phosphor-logging/lg2.hpp>
+
using namespace phosphor::logging;
namespace phosphor::power::chassis_manager
@@ -40,6 +41,7 @@
auto interval = std::chrono::milliseconds(1000);
timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
e, std::bind(&ChassisManager::analyze, this), interval);
+ initChassisPowerMonitoring();
}
void ChassisManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
@@ -65,9 +67,8 @@
{
lg2::debug("InterfacesAdded for: {SUPPORTED_CONFIGURATION}",
"SUPPORTED_CONFIGURATION", supportedConfIntf);
- // Future implementation
- // chassisMatchPtr->supportedConfigurationInterfaceAdded(
- // itInterface->second);
+ chassisMatchPtr->supportedConfigurationInterfaceAdded(
+ itInterface->second);
}
}
itInterface = interfaces.find(IBMCFFPSInterface);
@@ -81,16 +82,14 @@
{
lg2::info("InterfacesAdded for: {IBMCFFPSINTERFACE}",
"IBMCFFPSINTERFACE", IBMCFFPSInterface);
- // Future implementation
- // chassisMatchPtr->psuInterfaceAdded(itInterface->second);
+ chassisMatchPtr->psuInterfaceAdded(itInterface->second);
}
}
if (chassisMatchPtr != nullptr)
{
lg2::debug(
"InterfacesAdded validatePsuConfigAndInterfacesProcessed()");
- // Future implementation
- // chassisMatchPtr->validatePsuConfigAndInterfacesProcessed();
+ chassisMatchPtr->validatePsuConfigAndInterfacesProcessed();
}
}
catch (const std::exception& e)
@@ -142,4 +141,13 @@
e);
}
}
+
+void ChassisManager::initChassisPowerMonitoring()
+{
+ for (const auto& chassis : listOfChassis)
+ {
+ chassis->initPowerMonitoring();
+ }
+}
+
} // namespace phosphor::power::chassis_manager
diff --git a/phosphor-power-supply/chassis_manager.hpp b/phosphor-power-supply/chassis_manager.hpp
index 100514d..cccd8db 100644
--- a/phosphor-power-supply/chassis_manager.hpp
+++ b/phosphor-power-supply/chassis_manager.hpp
@@ -122,6 +122,11 @@
*/
phosphor::power::chassis::Chassis* getMatchingChassisPtr(
uint64_t chassisId);
+
+ /**
+ * @brief Initialize chassis power monitoring.
+ */
+ void initChassisPowerMonitoring();
};
} // namespace phosphor::power::chassis_manager
diff --git a/phosphor-power-supply/new_main.cpp b/phosphor-power-supply/new_main.cpp
new file mode 100644
index 0000000..638f971
--- /dev/null
+++ b/phosphor-power-supply/new_main.cpp
@@ -0,0 +1,28 @@
+#include "chassis_manager.hpp"
+
+#include <CLI/CLI.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <filesystem>
+
+using namespace phosphor::power;
+
+int main()
+{
+ using namespace phosphor::logging;
+
+ CLI::App app{"OpenBMC Power Supply Unit Monitor"};
+
+ auto bus = sdbusplus::bus::new_default();
+ auto event = sdeventplus::Event::get_default();
+
+ // Attach the event object to the bus object so we can
+ // handle both sd_events (for the timers) and dbus signals.
+ bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ chassis_manager::ChassisManager chassis_manager(bus, event);
+
+ return chassis_manager.run();
+}
diff --git a/phosphor-power-supply/new_power_supply.cpp b/phosphor-power-supply/new_power_supply.cpp
new file mode 100644
index 0000000..b3ba1c7
--- /dev/null
+++ b/phosphor-power-supply/new_power_supply.cpp
@@ -0,0 +1,1346 @@
+#include "config.h"
+
+#include "new_power_supply.hpp"
+
+#include "types.hpp"
+#include "util.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/Device/error.hpp>
+
+#include <chrono> // sleep_for()
+#include <cmath>
+#include <cstdint> // uint8_t...
+#include <format>
+#include <fstream>
+#include <regex>
+#include <thread> // sleep_for()
+
+namespace phosphor::power::psu
+{
+// Amount of time in milliseconds to delay between power supply going from
+// missing to present before running the bind command(s).
+constexpr auto bindDelay = 1000;
+
+using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
+
+PowerSupply::PowerSupply(
+ sdbusplus::bus_t& bus, const std::string& invpath, std::uint8_t i2cbus,
+ std::uint16_t i2caddr, const std::string& driver,
+ const std::string& gpioLineName, std::function<bool()>&& callback,
+ const std::string& chassisShortName) :
+ bus(bus), inventoryPath(invpath),
+ bindPath("/sys/bus/i2c/drivers/" + driver), isPowerOn(std::move(callback)),
+ driverName(driver), chassisName(chassisShortName)
+{
+ if (inventoryPath.empty())
+ {
+ throw std::invalid_argument{"Invalid empty inventoryPath"};
+ }
+
+ if (gpioLineName.empty())
+ {
+ throw std::invalid_argument{"Invalid empty gpioLineName"};
+ }
+
+ shortName = findShortName(inventoryPath);
+
+ lg2::debug("{SHORT_NAME} gpioLineName: {GPIO_LINE_NAME}", "SHORT_NAME",
+ shortName, "GPIO_LINE_NAME", gpioLineName);
+ presenceGPIO = createGPIO(gpioLineName);
+
+ std::ostringstream ss;
+ ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
+ std::string addrStr = ss.str();
+ std::string busStr = std::to_string(i2cbus);
+ bindDevice = busStr;
+ bindDevice.append("-");
+ bindDevice.append(addrStr);
+
+ pmbusIntf = phosphor::pmbus::createPMBus(i2cbus, addrStr);
+
+ // Get the current state of the Present property.
+ try
+ {
+ updatePresenceGPIO();
+ }
+ catch (...)
+ {
+ // If the above attempt to use the GPIO failed, it likely means that the
+ // GPIOs are in use by the kernel, meaning it is using gpio-keys.
+ // So, I should rely on phosphor-gpio-presence to update D-Bus, and
+ // work that way for power supply presence.
+ presenceGPIO = nullptr;
+ // 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::argNpath(0, inventoryPath),
+ [this](auto& msg) { this->inventoryAdded(msg); });
+
+ updatePresence();
+ updateInventory();
+ setupSensors();
+ }
+ try
+ {
+ setInputVoltageRating();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info("setInputVoltageRating exception: {ERR}", "ERR", e);
+ }
+}
+
+PowerSupply::PowerSupply(
+ sdbusplus::bus_t& bus, const std::string& invpath, std::uint8_t i2cbus,
+ std::uint16_t i2caddr, const std::string& driver,
+ const std::string& gpioLineName, std::function<bool()>&& callback) :
+ PowerSupply(bus, invpath, i2cbus, i2caddr, driver, gpioLineName,
+ std::move(callback), "")
+{}
+
+void PowerSupply::bindOrUnbindDriver(bool present)
+{
+ // Symbolic link to the device will exist if the driver is bound.
+ // So exit no action required if both the link and PSU are present
+ // or neither is present.
+ namespace fs = std::filesystem;
+ fs::path path;
+ auto action = (present) ? "bind" : "unbind";
+
+ // This case should not happen, if no device driver name return.
+ if (driverName.empty())
+ {
+ lg2::info("No device driver name found");
+ return;
+ }
+ if (bindPath.string().find(driverName) != std::string::npos)
+ {
+ // bindPath has driver name
+ path = bindPath / action;
+ }
+ else
+ {
+ // Add driver name to bindPath
+ path = bindPath / driverName / action;
+ bindPath = bindPath / driverName;
+ }
+
+ if ((std::filesystem::exists(bindPath / bindDevice) && present) ||
+ (!std::filesystem::exists(bindPath / bindDevice) && !present))
+ {
+ return;
+ }
+ if (present)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(bindDelay));
+ lg2::info("Binding device driver. path: {PATH} device: {BIND_DEVICE}",
+ "PATH", path, "BIND_DEVICE", bindDevice);
+ }
+ else
+ {
+ lg2::info("Unbinding device driver. path: {PATH} device: {BIND_DEVICE}",
+ "PATH", path, "BIND_DEVICE", bindDevice);
+ }
+
+ std::ofstream file;
+
+ file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
+ std::ofstream::eofbit);
+
+ try
+ {
+ file.open(path);
+ file << bindDevice;
+ file.close();
+ }
+ catch (const std::exception& e)
+ {
+ auto err = errno;
+
+ lg2::error("Failed binding or unbinding device. errno={ERRNO}", "ERRNO",
+ err);
+ }
+}
+
+void PowerSupply::updatePresence()
+{
+ try
+ {
+ present = getPresence(bus, inventoryPath);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ // Relying on property change or interface added to retry.
+ // Log an informational trace to the journal.
+ lg2::info("D-Bus property {INVENTORY_PATH} access failure exception",
+ "INVENTORY_PATH", inventoryPath);
+ }
+}
+
+void PowerSupply::updatePresenceGPIO()
+{
+ bool presentOld = present;
+
+ try
+ {
+ if (presenceGPIO->read() > 0)
+ {
+ present = true;
+ }
+ else
+ {
+ present = false;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("presenceGPIO read fail: {ERROR}", "ERROR", e);
+ throw;
+ }
+
+ if (presentOld != present)
+ {
+ lg2::debug("{SHORT_NAME} presentOld: {PRESENT_OLD} present: {PRESENT}",
+ "SHORT_NAME", shortName, "PRESENT_OLD", presentOld,
+ "PRESENT", present);
+
+ auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+
+ bindOrUnbindDriver(present);
+ if (present)
+ {
+ // If the power supply was present, then missing, and present again,
+ // the hwmon path may have changed. We will need the correct/updated
+ // path before any reads or writes are attempted.
+ pmbusIntf->findHwmonDir();
+ }
+
+ setPresence(bus, invpath, present, shortName);
+ setupSensors();
+ updateInventory();
+
+ // Need Functional to already be correct before calling this.
+ checkAvailability();
+
+ if (present)
+ {
+ onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
+ clearFaults();
+ // Indicate that the input history data and timestamps between all
+ // the power supplies that are present in the system need to be
+ // synchronized.
+ syncHistoryRequired = true;
+ }
+ else
+ {
+ setSensorsNotAvailable();
+ }
+ }
+}
+
+void PowerSupply::analyzeCMLFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::CML_FAULT)
+ {
+ if (cmlFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error(
+ "{SHORT_NAME} CML fault: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_CML = {STATUS_CML}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord, "STATUS_CML",
+ lg2::hex | lg2::field8, statusCML);
+ }
+ cmlFault++;
+ }
+ }
+ else
+ {
+ cmlFault = 0;
+ }
+}
+
+void PowerSupply::analyzeInputFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::INPUT_FAULT_WARN)
+ {
+ if (inputFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error(
+ "{SHORT_NAME} INPUT fault: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_INPUT = {STATUS_INPUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+ lg2::hex | lg2::field8, statusMFR, "STATUS_INPUT",
+ lg2::hex | lg2::field8, statusInput);
+ }
+ inputFault++;
+ }
+ }
+
+ // If had INPUT/VIN_UV fault, and now off.
+ // Trace that odd behavior.
+ if (inputFault &&
+ !(statusWord & phosphor::pmbus::status_word::INPUT_FAULT_WARN))
+ {
+ lg2::info(
+ "{SHORT_NAME} INPUT fault cleared: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_INPUT = {STATUS_INPUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD", lg2::hex | lg2::field16,
+ statusWord, "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+ statusMFR, "STATUS_INPUT", lg2::hex | lg2::field8, statusInput);
+ inputFault = 0;
+ }
+}
+
+void PowerSupply::analyzeVoutOVFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::VOUT_OV_FAULT)
+ {
+ if (voutOVFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error(
+ "{SHORT_NAME} VOUT_OV_FAULT fault: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_VOUT = {STATUS_VOUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+ lg2::hex | lg2::field8, statusMFR, "STATUS_VOUT",
+ lg2::hex | lg2::field8, statusVout);
+ }
+
+ voutOVFault++;
+ }
+ }
+ else
+ {
+ voutOVFault = 0;
+ }
+}
+
+void PowerSupply::analyzeIoutOCFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::IOUT_OC_FAULT)
+ {
+ if (ioutOCFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error(
+ "{SHORT_NAME} IOUT fault: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_IOUT = {STATUS_IOUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+ lg2::hex | lg2::field8, statusMFR, "STATUS_IOUT",
+ lg2::hex | lg2::field8, statusIout);
+ }
+
+ ioutOCFault++;
+ }
+ }
+ else
+ {
+ ioutOCFault = 0;
+ }
+}
+
+void PowerSupply::analyzeVoutUVFault()
+{
+ if ((statusWord & phosphor::pmbus::status_word::VOUT_FAULT) &&
+ !(statusWord & phosphor::pmbus::status_word::VOUT_OV_FAULT))
+ {
+ if (voutUVFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error(
+ "{SHORT_NAME} VOUT_UV_FAULT fault: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_VOUT = {STATUS_VOUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+ lg2::hex | lg2::field8, statusMFR, "STATUS_VOUT",
+ lg2::hex | lg2::field8, statusVout);
+ }
+ voutUVFault++;
+ }
+ }
+ else
+ {
+ voutUVFault = 0;
+ }
+}
+
+void PowerSupply::analyzeFanFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::FAN_FAULT)
+ {
+ if (fanFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error("{SHORT_NAME} FANS fault/warning: "
+ "STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_FANS_1_2 = {STATUS_FANS_1_2}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord,
+ "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+ statusMFR, "STATUS_FANS_1_2", lg2::hex | lg2::field8,
+ statusFans12);
+ }
+ fanFault++;
+ }
+ }
+ else
+ {
+ fanFault = 0;
+ }
+}
+
+void PowerSupply::analyzeTemperatureFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::TEMPERATURE_FAULT_WARN)
+ {
+ if (tempFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error("{SHORT_NAME} TEMPERATURE fault/warning: "
+ "STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_TEMPERATURE = {STATUS_TEMPERATURE}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord,
+ "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+ statusMFR, "STATUS_TEMPERATURE",
+ lg2::hex | lg2::field8, statusTemperature);
+ }
+ tempFault++;
+ }
+ }
+ else
+ {
+ tempFault = 0;
+ }
+}
+
+void PowerSupply::analyzePgoodFault()
+{
+ if ((statusWord & phosphor::pmbus::status_word::POWER_GOOD_NEGATED) ||
+ (statusWord & phosphor::pmbus::status_word::UNIT_IS_OFF))
+ {
+ if (pgoodFault < PGOOD_DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error("{SHORT_NAME} PGOOD fault: "
+ "STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord,
+ "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+ statusMFR);
+ }
+ pgoodFault++;
+ }
+ }
+ else
+ {
+ pgoodFault = 0;
+ }
+}
+
+void PowerSupply::determineMFRFault()
+{
+ if (bindPath.string().find(IBMCFFPS_DD_NAME) != std::string::npos)
+ {
+ // IBM MFR_SPECIFIC[4] is PS_Kill fault
+ if (statusMFR & 0x10)
+ {
+ if (psKillFault < DEGLITCH_LIMIT)
+ {
+ psKillFault++;
+ }
+ }
+ else
+ {
+ psKillFault = 0;
+ }
+ // IBM MFR_SPECIFIC[6] is 12Vcs fault.
+ if (statusMFR & 0x40)
+ {
+ if (ps12VcsFault < DEGLITCH_LIMIT)
+ {
+ ps12VcsFault++;
+ }
+ }
+ else
+ {
+ ps12VcsFault = 0;
+ }
+ // IBM MFR_SPECIFIC[7] is 12V Current-Share fault.
+ if (statusMFR & 0x80)
+ {
+ if (psCS12VFault < DEGLITCH_LIMIT)
+ {
+ psCS12VFault++;
+ }
+ }
+ else
+ {
+ psCS12VFault = 0;
+ }
+ }
+}
+
+void PowerSupply::analyzeMFRFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::MFR_SPECIFIC_FAULT)
+ {
+ if (mfrFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error("{SHORT_NAME} MFR fault: "
+ "STATUS_WORD = {STATUS_WORD} "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord,
+ "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+ statusMFR);
+ }
+ mfrFault++;
+ }
+
+ determineMFRFault();
+ }
+ else
+ {
+ mfrFault = 0;
+ }
+}
+
+void PowerSupply::analyzeVinUVFault()
+{
+ if (statusWord & phosphor::pmbus::status_word::VIN_UV_FAULT)
+ {
+ if (vinUVFault < DEGLITCH_LIMIT)
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::error(
+ "{SHORT_NAME} VIN_UV fault: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_INPUT = {STATUS_INPUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+ lg2::hex | lg2::field8, statusMFR, "STATUS_INPUT",
+ lg2::hex | lg2::field8, statusInput);
+ }
+ vinUVFault++;
+ }
+ // Remember that this PSU has seen an AC fault
+ acFault = AC_FAULT_LIMIT;
+ }
+ else
+ {
+ if (vinUVFault != 0)
+ {
+ lg2::info(
+ "{SHORT_NAME} VIN_UV fault cleared: STATUS_WORD = {STATUS_WORD}, "
+ "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+ "STATUS_INPUT = {STATUS_INPUT}",
+ "SHORT_NAME", shortName, "STATUS_WORD", lg2::hex | lg2::field16,
+ statusWord, "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+ statusMFR, "STATUS_INPUT", lg2::hex | lg2::field8, statusInput);
+ vinUVFault = 0;
+ }
+ // No AC fail, decrement counter
+ if (acFault != 0)
+ {
+ --acFault;
+ }
+ }
+}
+
+void PowerSupply::analyze()
+{
+ using namespace phosphor::pmbus;
+
+ if (presenceGPIO)
+ {
+ updatePresenceGPIO();
+ }
+
+ if (present)
+ {
+ try
+ {
+ statusWordOld = statusWord;
+ statusWord = pmbusIntf->read(STATUS_WORD, Type::Debug,
+ (readFail < LOG_LIMIT));
+ // Read worked, reset the fail count.
+ readFail = 0;
+
+ if (statusWord)
+ {
+ statusInput = pmbusIntf->read(STATUS_INPUT, Type::Debug);
+ if (bindPath.string().find(IBMCFFPS_DD_NAME) !=
+ std::string::npos)
+ {
+ statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug);
+ }
+ statusCML = pmbusIntf->read(STATUS_CML, Type::Debug);
+ auto status0Vout = pmbusIntf->insertPageNum(STATUS_VOUT, 0);
+ statusVout = pmbusIntf->read(status0Vout, Type::Debug);
+ statusIout = pmbusIntf->read(STATUS_IOUT, Type::Debug);
+ statusFans12 = pmbusIntf->read(STATUS_FANS_1_2, Type::Debug);
+ statusTemperature =
+ pmbusIntf->read(STATUS_TEMPERATURE, Type::Debug);
+
+ analyzeCMLFault();
+
+ analyzeInputFault();
+
+ analyzeVoutOVFault();
+
+ analyzeIoutOCFault();
+
+ analyzeVoutUVFault();
+
+ analyzeFanFault();
+
+ analyzeTemperatureFault();
+
+ analyzePgoodFault();
+
+ analyzeMFRFault();
+
+ analyzeVinUVFault();
+ }
+ else
+ {
+ if (statusWord != statusWordOld)
+ {
+ lg2::info("{SHORT_NAME} STATUS_WORD = {STATUS_WORD}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord);
+ }
+
+ // if INPUT/VIN_UV fault was on, it cleared, trace it.
+ if (inputFault)
+ {
+ lg2::info(
+ "{SHORT_NAME} INPUT fault cleared: STATUS_WORD = {STATUS_WORD}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord);
+ }
+
+ if (vinUVFault)
+ {
+ lg2::info(
+ "{SHORT_NAME} VIN_UV cleared: STATUS_WORD = {STATUS_WORD}",
+ "SHORT_NAME", shortName, "STATUS_WORD",
+ lg2::hex | lg2::field16, statusWord);
+ }
+
+ if (pgoodFault > 0)
+ {
+ lg2::info("{SHORT_NAME} pgoodFault cleared", "SHORT_NAME",
+ shortName);
+ }
+
+ clearFaultFlags();
+ // No AC fail, decrement counter
+ if (acFault != 0)
+ {
+ --acFault;
+ }
+ }
+
+ // Save off old inputVoltage value.
+ // Get latest inputVoltage.
+ // If voltage went from below minimum, and now is not, clear faults.
+ // Note: getInputVoltage() has its own try/catch.
+ int inputVoltageOld = inputVoltage;
+ double actualInputVoltageOld = actualInputVoltage;
+ getInputVoltage(actualInputVoltage, inputVoltage);
+ if ((inputVoltageOld == in_input::VIN_VOLTAGE_0) &&
+ (inputVoltage != in_input::VIN_VOLTAGE_0))
+ {
+ lg2::info(
+ "{SHORT_NAME} READ_VIN back in range: actualInputVoltageOld = {ACTUAL_INPUT_VOLTAGE_OLD} "
+ "actualInputVoltage = {ACTUAL_INPUT_VOLTAGE}",
+ "SHORT_NAME", shortName, "ACTUAL_INPUT_VOLTAGE_OLD",
+ actualInputVoltageOld, "ACTUAL_INPUT_VOLTAGE",
+ actualInputVoltage);
+ clearVinUVFault();
+ }
+ else if (vinUVFault && (inputVoltage != in_input::VIN_VOLTAGE_0))
+ {
+ lg2::info(
+ "{SHORT_NAME} CLEAR_FAULTS: vinUVFault {VIN_UV_FAULT} actualInputVoltage {ACTUAL_INPUT_VOLTAGE}",
+ "SHORT_NAME", shortName, "VIN_UV_FAULT", vinUVFault,
+ "ACTUAL_INPUT_VOLTAGE", actualInputVoltage);
+ // Do we have a VIN_UV fault latched that can now be cleared
+ // due to voltage back in range? Attempt to clear the
+ // fault(s), re-check faults on next call.
+ clearVinUVFault();
+ }
+ else if (std::abs(actualInputVoltageOld - actualInputVoltage) >
+ 10.0)
+ {
+ lg2::info(
+ "{SHORT_NAME} actualInputVoltageOld = {ACTUAL_INPUT_VOLTAGE_OLD} actualInputVoltage = {ACTUAL_INPUT_VOLTAGE}",
+ "SHORT_NAME", shortName, "ACTUAL_INPUT_VOLTAGE_OLD",
+ actualInputVoltageOld, "ACTUAL_INPUT_VOLTAGE",
+ actualInputVoltage);
+ }
+
+ monitorSensors();
+
+ checkAvailability();
+ }
+ catch (const ReadFailure& e)
+ {
+ if (readFail < SIZE_MAX)
+ {
+ readFail++;
+ }
+ if (readFail == LOG_LIMIT)
+ {
+ phosphor::logging::commit<ReadFailure>();
+ }
+ }
+ }
+}
+
+void PowerSupply::onOffConfig(uint8_t data)
+{
+ using namespace phosphor::pmbus;
+
+ if (present && driverName != ACBEL_FSG032_DD_NAME)
+ {
+ lg2::info("ON_OFF_CONFIG write: DATA={DATA}", "DATA",
+ lg2::hex | lg2::field8, data);
+ try
+ {
+ std::vector<uint8_t> configData{data};
+ pmbusIntf->writeBinary(ON_OFF_CONFIG, configData,
+ Type::HwmonDeviceDebug);
+ }
+ catch (...)
+ {
+ // The underlying code in writeBinary will log a message to the
+ // journal if the write fails. If the ON_OFF_CONFIG is not setup
+ // as desired, later fault detection and analysis code should
+ // catch any of the fall out. We should not need to terminate
+ // the application if this write fails.
+ }
+ }
+}
+
+void PowerSupply::clearVinUVFault()
+{
+ // Read in1_lcrit_alarm to clear bits 3 and 4 of STATUS_INPUT.
+ // The fault bits in STAUTS_INPUT roll-up to STATUS_WORD. Clearing those
+ // bits in STATUS_INPUT should result in the corresponding STATUS_WORD bits
+ // also clearing.
+ //
+ // Do not care about return value. Should be 1 if active, 0 if not.
+ if (driverName != ACBEL_FSG032_DD_NAME)
+ {
+ static_cast<void>(
+ pmbusIntf->read("in1_lcrit_alarm", phosphor::pmbus::Type::Hwmon));
+ }
+ else
+ {
+ static_cast<void>(
+ pmbusIntf->read("curr1_crit_alarm", phosphor::pmbus::Type::Hwmon));
+ }
+ vinUVFault = 0;
+}
+
+void PowerSupply::clearFaults()
+{
+ lg2::debug("clearFaults() inventoryPath: {INVENTORY_PATH}",
+ "INVENTORY_PATH", inventoryPath);
+ faultLogged = false;
+ // The PMBus device driver does not allow for writing CLEAR_FAULTS
+ // directly. However, the pmbus hwmon device driver code will send a
+ // CLEAR_FAULTS after reading from any of the hwmon "files" in sysfs, so
+ // reading in1_input should result in clearing the fault bits in
+ // STATUS_BYTE/STATUS_WORD.
+ // I do not care what the return value is.
+ if (present)
+ {
+ clearFaultFlags();
+ checkAvailability();
+ readFail = 0;
+
+ try
+ {
+ clearVinUVFault();
+ static_cast<void>(
+ pmbusIntf->read("in1_input", phosphor::pmbus::Type::Hwmon));
+ }
+ catch (const ReadFailure& e)
+ {
+ // Since I do not care what the return value is, I really do not
+ // care much if it gets a ReadFailure either. However, this
+ // should not prevent the application from continuing to run, so
+ // catching the read failure.
+ }
+ }
+}
+
+void PowerSupply::inventoryChanged(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))
+ {
+ present = true;
+ // TODO: Immediately trying to read or write the "files" causes
+ // read or write failures.
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(20ms);
+ pmbusIntf->findHwmonDir();
+ onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
+ clearFaults();
+ updateInventory();
+ }
+ else
+ {
+ present = false;
+
+ // Clear out the now outdated inventory properties
+ updateInventory();
+ }
+ checkAvailability();
+ }
+}
+
+void PowerSupply::inventoryAdded(sdbusplus::message_t& msg)
+{
+ sdbusplus::message::object_path path;
+ msg.read(path);
+ // Make sure the signal is for the PSU inventory path
+ if (path == inventoryPath)
+ {
+ std::map<std::string, std::map<std::string, std::variant<bool>>>
+ interfaces;
+ // Get map of interfaces and their properties
+ msg.read(interfaces);
+
+ auto properties = interfaces.find(INVENTORY_IFACE);
+ if (properties != interfaces.end())
+ {
+ auto property = properties->second.find(PRESENT_PROP);
+ if (property != properties->second.end())
+ {
+ present = std::get<bool>(property->second);
+
+ lg2::info("Power Supply {INVENTORY_PATH} Present {PRESENT}",
+ "INVENTORY_PATH", inventoryPath, "PRESENT", present);
+
+ updateInventory();
+ checkAvailability();
+ }
+ }
+ }
+}
+
+auto PowerSupply::readVPDValue(const std::string& vpdName,
+ const phosphor::pmbus::Type& type,
+ const std::size_t& vpdSize)
+{
+ std::string vpdValue;
+ const std::regex illegalVPDRegex =
+ std::regex("[^[:alnum:]]", std::regex::basic);
+
+ try
+ {
+ vpdValue = pmbusIntf->readString(vpdName, type);
+ }
+ catch (const ReadFailure& e)
+ {
+ // Ignore the read failure, let pmbus code indicate failure,
+ // path...
+ // TODO - ibm918
+ // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md
+ // The BMC must log errors if any of the VPD cannot be properly
+ // parsed or fails ECC checks.
+ }
+
+ if (vpdValue.size() != vpdSize)
+ {
+ lg2::info("{SHORT_NAME} {VPD_NAME} resize needed. size: {SIZE}",
+ "SHORT_NAME", shortName, "VPD_NAME", vpdName, "SIZE",
+ vpdValue.size());
+ vpdValue.resize(vpdSize, ' ');
+ }
+
+ // Replace any illegal values with space(s).
+ std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(),
+ illegalVPDRegex, " ");
+
+ return vpdValue;
+}
+
+void PowerSupply::updateInventory()
+{
+ using namespace phosphor::pmbus;
+
+#if IBM_VPD
+ std::string pn;
+ std::string fn;
+ std::string header;
+ std::string sn;
+ // The IBM power supply splits the full serial number into two parts.
+ // Each part is 6 bytes long, which should match up with SN_KW_SIZE.
+ const auto HEADER_SIZE = 6;
+ const auto SERIAL_SIZE = 6;
+ // The IBM PSU firmware version size is a bit complicated. It was originally
+ // 1-byte, per command. It was later expanded to 2-bytes per command, then
+ // up to 8-bytes per command. The device driver only reads up to 2 bytes per
+ // command, but combines all three of the 2-byte reads, or all 4 of the
+ // 1-byte reads into one string. So, the maximum size expected is 6 bytes.
+ // However, it is formatted by the driver as a hex string with two ASCII
+ // characters per byte. So the maximum ASCII string size is 12.
+ const auto IBMCFFPS_FW_VERSION_SIZE = 12;
+ const auto ACBEL_FSG032_FW_VERSION_SIZE = 6;
+
+ using PropertyMap =
+ std::map<std::string,
+ std::variant<std::string, std::vector<uint8_t>, bool>>;
+ PropertyMap assetProps;
+ PropertyMap operProps;
+ PropertyMap versionProps;
+ PropertyMap ipzvpdDINFProps;
+ PropertyMap ipzvpdVINIProps;
+ using InterfaceMap = std::map<std::string, PropertyMap>;
+ InterfaceMap interfaces;
+ using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>;
+ ObjectMap object;
+#endif
+ lg2::debug("updateInventory() inventoryPath: {INVENTORY_PATH}",
+ "INVENTORY_PATH", inventoryPath);
+
+ if (present)
+ {
+ // TODO: non-IBM inventory updates?
+
+#if IBM_VPD
+ if (driverName == ACBEL_FSG032_DD_NAME)
+ {
+ getPsuVpdFromDbus("CC", modelName);
+ getPsuVpdFromDbus("PN", pn);
+ getPsuVpdFromDbus("FN", fn);
+ getPsuVpdFromDbus("SN", sn);
+ assetProps.emplace(SN_PROP, sn);
+ fwVersion = readVPDValue(FW_VERSION, Type::Debug,
+ ACBEL_FSG032_FW_VERSION_SIZE);
+ versionProps.emplace(VERSION_PROP, fwVersion);
+ }
+ else
+ {
+ modelName = readVPDValue(CCIN, Type::HwmonDeviceDebug, CC_KW_SIZE);
+ pn = readVPDValue(PART_NUMBER, Type::Debug, PN_KW_SIZE);
+ fn = readVPDValue(FRU_NUMBER, Type::Debug, FN_KW_SIZE);
+
+ header = readVPDValue(SERIAL_HEADER, Type::Debug, HEADER_SIZE);
+ sn = readVPDValue(SERIAL_NUMBER, Type::Debug, SERIAL_SIZE);
+ assetProps.emplace(SN_PROP, header + sn);
+ fwVersion = readVPDValue(FW_VERSION, Type::HwmonDeviceDebug,
+ IBMCFFPS_FW_VERSION_SIZE);
+ versionProps.emplace(VERSION_PROP, fwVersion);
+ }
+
+ assetProps.emplace(MODEL_PROP, modelName);
+ assetProps.emplace(PN_PROP, pn);
+ assetProps.emplace(SPARE_PN_PROP, fn);
+
+ ipzvpdVINIProps.emplace(
+ "CC", std::vector<uint8_t>(modelName.begin(), modelName.end()));
+ ipzvpdVINIProps.emplace("PN",
+ std::vector<uint8_t>(pn.begin(), pn.end()));
+ ipzvpdVINIProps.emplace("FN",
+ std::vector<uint8_t>(fn.begin(), fn.end()));
+ std::string header_sn = header + sn;
+ ipzvpdVINIProps.emplace(
+ "SN", std::vector<uint8_t>(header_sn.begin(), header_sn.end()));
+ std::string description = "IBM PS";
+ ipzvpdVINIProps.emplace(
+ "DR", std::vector<uint8_t>(description.begin(), description.end()));
+
+ // Populate the VINI Resource Type (RT) keyword
+ ipzvpdVINIProps.emplace("RT", std::vector<uint8_t>{'V', 'I', 'N', 'I'});
+
+ // Update the Resource Identifier (RI) keyword
+ // 2 byte FRC: 0x0003
+ // 2 byte RID: 0x1000, 0x1001...
+ std::uint8_t num = std::stoul(
+ inventoryPath.substr(inventoryPath.size() - 1, 1), nullptr, 0);
+ std::vector<uint8_t> ri{0x00, 0x03, 0x10, num};
+ ipzvpdDINFProps.emplace("RI", ri);
+
+ // Fill in the FRU Label (FL) keyword.
+ std::string fl = "E";
+ fl.push_back(inventoryPath.back());
+ fl.resize(FL_KW_SIZE, ' ');
+ ipzvpdDINFProps.emplace("FL",
+ std::vector<uint8_t>(fl.begin(), fl.end()));
+
+ // Populate the DINF Resource Type (RT) keyword
+ ipzvpdDINFProps.emplace("RT", std::vector<uint8_t>{'D', 'I', 'N', 'F'});
+
+ interfaces.emplace(ASSET_IFACE, std::move(assetProps));
+ interfaces.emplace(VERSION_IFACE, std::move(versionProps));
+ interfaces.emplace(DINF_IFACE, std::move(ipzvpdDINFProps));
+ interfaces.emplace(VINI_IFACE, std::move(ipzvpdVINIProps));
+
+ // Update the Functional
+ operProps.emplace(FUNCTIONAL_PROP, present);
+ interfaces.emplace(OPERATIONAL_STATE_IFACE, std::move(operProps));
+
+ auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+ object.emplace(path, std::move(interfaces));
+
+ try
+ {
+ auto service =
+ util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
+
+ if (service.empty())
+ {
+ lg2::error("Unable to get inventory manager service");
+ return;
+ }
+
+ auto method =
+ bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH,
+ INVENTORY_MGR_IFACE, "Notify");
+
+ method.append(std::move(object));
+
+ auto reply = bus.call(method);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Exception in updateInventory(): {ERROR}, PATH={INVENTORY_PATH}",
+ "ERROR", e, "INVENTORY_PATH", inventoryPath);
+ }
+#endif
+ }
+}
+
+auto PowerSupply::getMaxPowerOut() const
+{
+ using namespace phosphor::pmbus;
+
+ auto maxPowerOut = 0;
+
+ if (present)
+ {
+ try
+ {
+ // Read max_power_out, should be direct format
+ auto maxPowerOutStr =
+ pmbusIntf->readString(MFR_POUT_MAX, Type::HwmonDeviceDebug);
+ lg2::info("{SHORT_NAME} MFR_POUT_MAX read {MAX_POWER_OUT_STR}",
+ "SHORT_NAME", shortName, "MAX_POWER_OUT_STR",
+ maxPowerOutStr);
+ maxPowerOut = std::stod(maxPowerOutStr);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("{SHORT_NAME} MFR_POUT_MAX read error: {ERROR}",
+ "SHORT_NAME", shortName, "ERROR", e);
+ }
+ }
+
+ return maxPowerOut;
+}
+
+void PowerSupply::setupSensors()
+{
+ setupInputPowerPeakSensor();
+}
+
+void PowerSupply::setupInputPowerPeakSensor()
+{
+ if (peakInputPowerSensor || !present ||
+ (bindPath.string().find(IBMCFFPS_DD_NAME) == std::string::npos))
+ {
+ return;
+ }
+
+ // This PSU has problems with the input_history command
+ if (getMaxPowerOut() == phosphor::pmbus::IBM_CFFPS_1400W)
+ {
+ return;
+ }
+
+ auto sensorPath = std::format(
+ "/xyz/openbmc_project/sensors/power/{}_ps{}_input_power_peak",
+ chassisName, shortName.back());
+ peakInputPowerSensor = std::make_unique<PowerSensorObject>(
+ bus, sensorPath.c_str(), PowerSensorObject::action::defer_emit);
+
+ // The others can remain at the defaults.
+ peakInputPowerSensor->functional(true, true);
+ peakInputPowerSensor->available(true, true);
+ peakInputPowerSensor->value(0, true);
+ peakInputPowerSensor->unit(
+ sdbusplus::xyz::openbmc_project::Sensor::server::Value::Unit::Watts,
+ true);
+
+ auto associations = getSensorAssociations();
+ peakInputPowerSensor->associations(associations, true);
+
+ peakInputPowerSensor->emit_object_added();
+}
+
+void PowerSupply::setSensorsNotAvailable()
+{
+ if (peakInputPowerSensor)
+ {
+ peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+ peakInputPowerSensor->available(false);
+ }
+}
+
+void PowerSupply::monitorSensors()
+{
+ monitorPeakInputPowerSensor();
+}
+
+void PowerSupply::monitorPeakInputPowerSensor()
+{
+ if (!peakInputPowerSensor)
+ {
+ return;
+ }
+
+ constexpr size_t recordSize = 5;
+ std::vector<uint8_t> data;
+
+ // Get the peak input power with input history command.
+ // New data only shows up every 30s, but just try to read it every 1s
+ // anyway so we always have the most up to date value.
+ try
+ {
+ data = pmbusIntf->readBinary(INPUT_HISTORY,
+ pmbus::Type::HwmonDeviceDebug, recordSize);
+ }
+ catch (const ReadFailure& e)
+ {
+ peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+ peakInputPowerSensor->functional(false);
+ throw;
+ }
+
+ if (data.size() != recordSize)
+ {
+ lg2::debug(
+ "Input history command returned {DATA_SIZE} bytes instead of 5",
+ "DATA_SIZE", data.size());
+ peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+ peakInputPowerSensor->functional(false);
+ return;
+ }
+
+ // The format is SSAAAAPPPP:
+ // SS = packet sequence number
+ // AAAA = average power (linear format, little endian)
+ // PPPP = peak power (linear format, little endian)
+ auto peak = static_cast<uint16_t>(data[4]) << 8 | data[3];
+ auto peakPower = linearToInteger(peak);
+
+ peakInputPowerSensor->value(peakPower);
+ peakInputPowerSensor->functional(true);
+ peakInputPowerSensor->available(true);
+}
+
+void PowerSupply::getInputVoltage(double& actualInputVoltage,
+ int& inputVoltage) const
+{
+ using namespace phosphor::pmbus;
+
+ actualInputVoltage = in_input::VIN_VOLTAGE_0;
+ inputVoltage = in_input::VIN_VOLTAGE_0;
+
+ if (present)
+ {
+ try
+ {
+ // Read input voltage in millivolts
+ auto inputVoltageStr = pmbusIntf->readString(READ_VIN, Type::Hwmon);
+
+ // Convert to volts
+ actualInputVoltage = std::stod(inputVoltageStr) / 1000;
+
+ // Calculate the voltage based on voltage thresholds
+ if (actualInputVoltage < in_input::VIN_VOLTAGE_MIN)
+ {
+ inputVoltage = in_input::VIN_VOLTAGE_0;
+ }
+ else if (actualInputVoltage < in_input::VIN_VOLTAGE_110_THRESHOLD)
+ {
+ inputVoltage = in_input::VIN_VOLTAGE_110;
+ }
+ else
+ {
+ inputVoltage = in_input::VIN_VOLTAGE_220;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("{SHORT_NAME} READ_VIN read error: {ERROR}",
+ "SHORT_NAME", shortName, "ERROR", e);
+ }
+ }
+}
+
+void PowerSupply::checkAvailability()
+{
+ bool origAvailability = available;
+ bool faulted = isPowerOn() && (hasPSKillFault() || hasIoutOCFault());
+ available = present && !hasInputFault() && !hasVINUVFault() && !faulted;
+
+ if (origAvailability != available)
+ {
+ auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+ phosphor::power::psu::setAvailable(bus, invpath, available);
+
+ // Check if the health rollup needs to change based on the
+ // new availability value.
+ phosphor::power::psu::handleChassisHealthRollup(bus, inventoryPath,
+ !available);
+ }
+}
+
+void PowerSupply::setInputVoltageRating()
+{
+ if (!present)
+ {
+ if (inputVoltageRatingIface)
+ {
+ inputVoltageRatingIface->value(0);
+ inputVoltageRatingIface.reset();
+ }
+ return;
+ }
+
+ double inputVoltageValue{};
+ int inputVoltageRating{};
+ getInputVoltage(inputVoltageValue, inputVoltageRating);
+
+ if (!inputVoltageRatingIface)
+ {
+ auto path = std::format(
+ "/xyz/openbmc_project/sensors/voltage/ps{}_input_voltage_rating",
+ shortName.back());
+
+ inputVoltageRatingIface = std::make_unique<SensorObject>(
+ bus, path.c_str(), SensorObject::action::defer_emit);
+
+ // Leave other properties at their defaults
+ inputVoltageRatingIface->unit(SensorInterface::Unit::Volts, true);
+ inputVoltageRatingIface->value(static_cast<double>(inputVoltageRating),
+ true);
+
+ inputVoltageRatingIface->emit_object_added();
+ }
+ else
+ {
+ inputVoltageRatingIface->value(static_cast<double>(inputVoltageRating));
+ }
+}
+
+void PowerSupply::getPsuVpdFromDbus(const std::string& keyword,
+ std::string& vpdStr)
+{
+ try
+ {
+ std::vector<uint8_t> value;
+ vpdStr.clear();
+ util::getProperty(VINI_IFACE, keyword, inventoryPath,
+ INVENTORY_MGR_IFACE, bus, value);
+ for (char c : value)
+ {
+ vpdStr += c;
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed getProperty error: {ERROR}", "ERROR", e);
+ }
+}
+
+double PowerSupply::linearToInteger(uint16_t data)
+{
+ // The exponent is the first 5 bits, followed by 11 bits of mantissa.
+ int8_t exponent = (data & 0xF800) >> 11;
+ int16_t mantissa = (data & 0x07FF);
+
+ // If exponent's MSB on, then it's negative.
+ // Convert from two's complement.
+ if (exponent & 0x10)
+ {
+ exponent = (~exponent) & 0x1F;
+ exponent = (exponent + 1) * -1;
+ }
+
+ // If mantissa's MSB on, then it's negative.
+ // Convert from two's complement.
+ if (mantissa & 0x400)
+ {
+ mantissa = (~mantissa) & 0x07FF;
+ mantissa = (mantissa + 1) * -1;
+ }
+
+ auto value = static_cast<double>(mantissa) * pow(2, exponent);
+ return value;
+}
+
+std::vector<AssociationTuple> PowerSupply::getSensorAssociations()
+{
+ std::vector<AssociationTuple> associations;
+
+ associations.emplace_back("inventory", "sensors", inventoryPath);
+
+ auto chassis = getChassis(bus, inventoryPath);
+ associations.emplace_back("chassis", "all_sensors", std::move(chassis));
+
+ return associations;
+}
+
+} // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/new_power_supply.hpp b/phosphor-power-supply/new_power_supply.hpp
new file mode 100644
index 0000000..162a4f8
--- /dev/null
+++ b/phosphor-power-supply/new_power_supply.hpp
@@ -0,0 +1,1099 @@
+#pragma once
+
+#include "pmbus.hpp"
+#include "types.hpp"
+#include "util.hpp"
+#include "utility.hpp"
+
+#include <gpiod.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/Availability/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/OperationalStatus/server.hpp>
+
+#include <filesystem>
+#include <stdexcept>
+
+namespace phosphor::power::psu
+{
+
+#if IBM_VPD
+// PMBus device driver "file name" to read for CCIN value.
+constexpr auto CCIN = "ccin";
+constexpr auto PART_NUMBER = "mfr_revision";
+constexpr auto FRU_NUMBER = "mfr_model";
+constexpr auto SERIAL_HEADER = "mfr_location";
+constexpr auto SERIAL_NUMBER = "mfr_serial";
+constexpr auto FW_VERSION = "fw_version";
+
+// The D-Bus property name to update with the CCIN value.
+constexpr auto MODEL_PROP = "Model";
+constexpr auto PN_PROP = "PartNumber";
+constexpr auto SPARE_PN_PROP = "SparePartNumber";
+constexpr auto SN_PROP = "SerialNumber";
+constexpr auto VERSION_PROP = "Version";
+
+// ipzVPD Keyword sizes
+static constexpr auto FL_KW_SIZE = 20;
+static constexpr auto FN_KW_SIZE = 7;
+static constexpr auto PN_KW_SIZE = 7;
+// For IBM power supplies, the SN is 6-byte header + 6-byte serial.
+static constexpr auto SN_KW_SIZE = 12;
+static constexpr auto CC_KW_SIZE = 4;
+#endif
+
+constexpr auto LOG_LIMIT = 3;
+constexpr auto DEGLITCH_LIMIT = 3;
+constexpr auto PGOOD_DEGLITCH_LIMIT = 5;
+// Number of polls to remember that an AC fault occured. Should remain greater
+// than PGOOD_DEGLITCH_LIMIT.
+constexpr auto AC_FAULT_LIMIT = 6;
+
+constexpr auto IBMCFFPS_DD_NAME = "ibm-cffps";
+constexpr auto ACBEL_FSG032_DD_NAME = "acbel-fsg032";
+
+using AvailabilityInterface =
+ sdbusplus::xyz::openbmc_project::State::Decorator::server::Availability;
+using OperationalStatusInterface = sdbusplus::xyz::openbmc_project::State::
+ Decorator::server::OperationalStatus;
+using AssocDefInterface =
+ sdbusplus::xyz::openbmc_project::Association::server::Definitions;
+using SensorInterface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
+using SensorObject = sdbusplus::server::object_t<SensorInterface>;
+using PowerSensorObject =
+ sdbusplus::server::object_t<SensorInterface, OperationalStatusInterface,
+ AvailabilityInterface, AssocDefInterface>;
+
+using AssociationTuple = std::tuple<std::string, std::string, std::string>;
+
+/**
+ * @class PowerSupply
+ * Represents a PMBus power supply device.
+ */
+class PowerSupply
+{
+ public:
+ PowerSupply() = delete;
+ PowerSupply(const PowerSupply&) = delete;
+ PowerSupply(PowerSupply&&) = delete;
+ PowerSupply& operator=(const PowerSupply&) = delete;
+ PowerSupply& operator=(PowerSupply&&) = delete;
+ ~PowerSupply() = default;
+
+ /**
+ * @param[in] invpath - String for inventory path to use
+ * @param[in] i2cbus - The bus number this power supply is on
+ * @param[in] i2caddr - The 16-bit I2C address of the power supply
+ * @param[in] driver - i2c driver name for power supply
+ * @param[in] gpioLineName - The gpio-line-name to read for presence. See
+ * https://github.com/openbmc/docs/blob/master/designs/device-tree-gpio-naming.md
+ * @param[in] callback - Get the power on status of the psu manager class
+ */
+ PowerSupply(sdbusplus::bus_t& bus, const std::string& invpath,
+ std::uint8_t i2cbus, const std::uint16_t i2caddr,
+ const std::string& driver, const std::string& gpioLineName,
+ std::function<bool()>&& callback);
+
+ /**
+ * @param[in] invpath - String for inventory path to use
+ * @param[in] i2cbus - The bus number this power supply is on
+ * @param[in] i2caddr - The 16-bit I2C address of the power supply
+ * @param[in] driver - i2c driver name for power supply
+ * @param[in] gpioLineName - The gpio-line-name to read for presence. See
+ * https://github.com/openbmc/docs/blob/master/designs/device-tree-gpio-naming.md
+ * @param[in] callback - Get the power on status of the psu manager class
+ * @param[in] chassisShortName - Chassis name
+ */
+ PowerSupply(sdbusplus::bus_t& bus, const std::string& invpath,
+ std::uint8_t i2cbus, const std::uint16_t i2caddr,
+ const std::string& driver, const std::string& gpioLineName,
+ std::function<bool()>&& callback,
+ const std::string& chassisShortName);
+
+ phosphor::pmbus::PMBusBase& getPMBus()
+ {
+ return *pmbusIntf;
+ }
+
+ GPIOInterfaceBase* getPresenceGPIO()
+ {
+ return presenceGPIO.get();
+ }
+
+ std::string getPresenceGPIOName() const
+ {
+ if (presenceGPIO != nullptr)
+ {
+ return presenceGPIO->getName();
+ }
+ else
+ {
+ return std::string();
+ }
+ }
+
+ /**
+ * Power supply specific function to analyze for faults/errors.
+ *
+ * Various PMBus status bits will be checked for fault conditions.
+ * If a certain fault bits are on, the appropriate error will be
+ * committed.
+ */
+ void analyze();
+
+ /**
+ * Write PMBus ON_OFF_CONFIG
+ *
+ * This function will be called to cause the PMBus device driver to send the
+ * ON_OFF_CONFIG command. Takes one byte of data.
+ *
+ * @param[in] data - The ON_OFF_CONFIG data byte mask.
+ */
+ void onOffConfig(uint8_t data);
+
+ /**
+ * Clears all the member variables that indicate if a fault bit was seen as
+ * on in the STATUS_WORD or STATUS_MFR_SPECIFIC response.
+ */
+ void clearFaultFlags()
+ {
+ inputFault = 0;
+ mfrFault = 0;
+ statusMFR = 0;
+ vinUVFault = 0;
+ cmlFault = 0;
+ voutOVFault = 0;
+ ioutOCFault = 0;
+ voutUVFault = 0;
+ fanFault = 0;
+ tempFault = 0;
+ pgoodFault = 0;
+ psKillFault = 0;
+ ps12VcsFault = 0;
+ psCS12VFault = 0;
+ faultLogged = false;
+ }
+
+ /**
+ * @brief Function to specifically clear VIN_UV/OFF fault(s).
+ *
+ * The PMBus HWMON device driver has various alarm "files" to read out of
+ * sysfs. Reading those files will indicate if various alarms are active or
+ * not, and then specifically clear those faults that go with that alarm.
+ *
+ * The VIN_UV fault, indicated in STATUS_INPUT, goes with in1_lcrit_alarm.
+ * When a VIN_UV fault occurs, the "Unit Off For Insufficient Input Voltage"
+ * may also be active. Reading in1_lcrit_alarm should clear both fault bits,
+ * resulting in the corresponding fault bits in STATUS_WORD also clearing.
+ *
+ * See: https://www.kernel.org/doc/html/latest/hwmon/pmbus.html
+ */
+ void clearVinUVFault();
+
+ /**
+ * Write PMBus CLEAR_FAULTS
+ *
+ * This function will be called in various situations in order to clear
+ * any fault status bits that may have been set, in order to start over
+ * with a clean state. Presence changes and power state changes will
+ * want to clear any faults logged.
+ */
+ void clearFaults();
+
+ /**
+ * @brief Adds properties to the inventory.
+ *
+ * Reads the values from the device and writes them to the
+ * associated power supply D-Bus inventory object.
+ *
+ * This needs to be done on startup, and each time the presence
+ * state changes.
+ *
+ * Properties added:
+ * - Serial Number
+ * - Part Number
+ * - CCIN (Customer Card Identification Number) - added as the Model
+ * - Firmware version
+ */
+ void updateInventory();
+
+ /**
+ * @brief Accessor function to indicate present status
+ */
+ bool isPresent() const
+ {
+ return present;
+ }
+
+ /**
+ * @brief Returns the last read value from STATUS_WORD.
+ */
+ uint64_t getStatusWord() const
+ {
+ return statusWord;
+ }
+
+ /**
+ * @brief Returns the last read value from STATUS_INPUT.
+ */
+ uint64_t getStatusInput() const
+ {
+ return statusInput;
+ }
+
+ /**
+ * @brief Returns the last read value from STATUS_MFR.
+ */
+ uint64_t getMFRFault() const
+ {
+ return statusMFR;
+ }
+
+ /**
+ * @brief Returns the last read value from STATUS_CML.
+ */
+ uint64_t getStatusCML() const
+ {
+ return statusCML;
+ }
+
+ /**
+ * @brief Returns the last read value from STATUS_VOUT.
+ */
+ uint64_t getStatusVout() const
+ {
+ return statusVout;
+ }
+
+ /**
+ * @brief Returns the last value read from STATUS_IOUT.
+ */
+ uint64_t getStatusIout() const
+ {
+ return statusIout;
+ }
+
+ /**
+ * @brief Returns the last value read from STATUS_FANS_1_2.
+ */
+ uint64_t getStatusFans12() const
+ {
+ return statusFans12;
+ }
+
+ /**
+ * @brief Returns the last value read from STATUS_TEMPERATURE.
+ */
+ uint64_t getStatusTemperature() const
+ {
+ return statusTemperature;
+ }
+
+ /**
+ * @brief Returns true if a fault was found.
+ */
+ bool isFaulted() const
+ {
+ return (hasCommFault() || (vinUVFault >= DEGLITCH_LIMIT) ||
+ (inputFault >= DEGLITCH_LIMIT) ||
+ (voutOVFault >= DEGLITCH_LIMIT) ||
+ (ioutOCFault >= DEGLITCH_LIMIT) ||
+ (voutUVFault >= DEGLITCH_LIMIT) ||
+ (fanFault >= DEGLITCH_LIMIT) || (tempFault >= DEGLITCH_LIMIT) ||
+ (pgoodFault >= PGOOD_DEGLITCH_LIMIT) ||
+ (mfrFault >= DEGLITCH_LIMIT));
+ }
+
+ /**
+ * @brief Return whether a fault has been logged for this power supply
+ */
+ bool isFaultLogged() const
+ {
+ return faultLogged;
+ }
+
+ /**
+ * @brief Called when a fault for this power supply has been logged.
+ */
+ void setFaultLogged()
+ {
+ faultLogged = true;
+ }
+
+ /**
+ * @brief Returns true if INPUT fault occurred.
+ */
+ bool hasInputFault() const
+ {
+ return (inputFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if MFRSPECIFIC occurred.
+ */
+ bool hasMFRFault() const
+ {
+ return (mfrFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if VIN_UV_FAULT occurred.
+ */
+ bool hasVINUVFault() const
+ {
+ return (vinUVFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if VOUT_OV_FAULT occurred.
+ */
+ bool hasVoutOVFault() const
+ {
+ return (voutOVFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if IOUT_OC fault occurred (bit 4 STATUS_BYTE).
+ */
+ bool hasIoutOCFault() const
+ {
+ return (ioutOCFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if VOUT_UV_FAULT occurred.
+ */
+ bool hasVoutUVFault() const
+ {
+ return (voutUVFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ *@brief Returns true if fan fault occurred.
+ */
+ bool hasFanFault() const
+ {
+ return (fanFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if TEMPERATURE fault occurred.
+ */
+ bool hasTempFault() const
+ {
+ return (tempFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if there is a PGood fault (PGOOD# inactive, or OFF
+ * bit on).
+ */
+ bool hasPgoodFault() const
+ {
+ return (pgoodFault >= PGOOD_DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Return true if there is a PS_Kill fault.
+ */
+ bool hasPSKillFault() const
+ {
+ return (psKillFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if there is a 12Vcs (standy power) fault.
+ */
+ bool hasPS12VcsFault() const
+ {
+ return (ps12VcsFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if there is a 12V current-share fault.
+ */
+ bool hasPSCS12VFault() const
+ {
+ return (psCS12VFault >= DEGLITCH_LIMIT);
+ }
+
+ /**
+ * @brief Returns true if an AC fault has occurred in the window of
+ * interest.
+ */
+ bool hasACFault() const
+ {
+ return acFault != 0;
+ }
+
+ /**
+ * @brief Returns the device path
+ *
+ * This can be used for error call outs.
+ * Example: /sys/bus/i2c/devices/3-0068
+ */
+ const std::string getDevicePath() const
+ {
+ return pmbusIntf->path();
+ }
+
+ /**
+ * @brief Returns this power supply's inventory path.
+ *
+ * This can be used for error call outs.
+ * Example:
+ * /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
+ */
+ const std::string& getInventoryPath() const
+ {
+ return inventoryPath;
+ }
+
+ /**
+ * @brief Returns the short name (last part of inventoryPath).
+ */
+ const std::string& getShortName() const
+ {
+ return shortName;
+ }
+
+ /**
+ * @brief Returns the firmware revision version read from the power supply
+ */
+ const std::string& getFWVersion() const
+ {
+ return fwVersion;
+ }
+
+ /**
+ * @brief Returns the model name of the power supply
+ */
+ const std::string& getModelName() const
+ {
+ return modelName;
+ }
+
+ /**
+ * @brief Returns true if the number of failed reads exceeds limit
+ */
+ bool hasCommFault() const
+ {
+ return (readFail >= LOG_LIMIT);
+ }
+
+ /**
+ * @brief Reads the pmbus input voltage and returns that actual voltage
+ * reading and the calculated input voltage based on thresholds.
+ * @param[out] actualInputVoltage - The actual voltage reading, in Volts.
+ * @param[out] inputVoltage - A rounded up/down value of the actual input
+ * voltage based on thresholds, in Volts.
+ */
+ 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();
+
+ /**
+ * @brief Returns true when INPUT_HISTORY sync is required.
+ */
+ bool isSyncHistoryRequired() const
+ {
+ return syncHistoryRequired;
+ }
+
+ /**
+ * @brief Clears the indicator that sync required for INPUT_HISTORY.
+ *
+ * Sets variable to false to indicate that the sync is no longer required.
+ * This can be used after the PSUManager has reacted to the need for the
+ * INPUT_HISTORY data to be synchronized.
+ */
+ void clearSyncHistoryRequired()
+ {
+ syncHistoryRequired = false;
+ }
+
+ /**
+ * @brief Puts the input voltage rating on D-Bus.
+ *
+ * The rating is like 0, 110, 220.
+ */
+ void setInputVoltageRating();
+
+ /**
+ * @brief Returns the peak input power value if there is one,
+ * otherwise std::nullopt.
+ */
+ std::optional<double> getPeakInputPower() const
+ {
+ std::optional<double> value;
+ if (peakInputPowerSensor)
+ {
+ value = peakInputPowerSensor->value();
+ }
+ return value;
+ }
+
+ /**
+ * @brief Converts a Linear Format power number to an integer
+ *
+ * The PMBus spec describes a 2 byte Linear Format
+ * number that is composed of an exponent and mantissa
+ * in two's complement notation.
+ *
+ * Value = Mantissa * 2**Exponent
+ *
+ * @return double - The converted value
+ */
+ static double linearToInteger(uint16_t data);
+
+ /**
+ * @brief Retrieve device driver name
+ */
+ const std::string& getDriverName() const
+ {
+ return driverName;
+ }
+
+ /**
+ * @brief Set device driver name
+ * @param[in] newDriver - device driver name.
+ */
+ void setDriverName(const std::string& newDriver)
+ {
+ driverName = newDriver;
+ }
+
+ private:
+ /**
+ * @brief Examine STATUS_WORD for CML (communication, memory, logic fault).
+ */
+ void analyzeCMLFault();
+
+ /**
+ * @brief Examine STATUS_WORD for INPUT bit on.
+ *
+ * "An input voltage, input current, or input power fault or warning has
+ * occurred."
+ */
+ void analyzeInputFault();
+
+ /**
+ * @brief Examine STATUS_WORD for VOUT being set.
+ *
+ * If VOUT is on, "An output voltage fault or warning has occurred.", and
+ * VOUT_OV_FAULT is on, there is an output over-voltage fault.
+ */
+ void analyzeVoutOVFault();
+
+ /**
+ * @brief Examine STATUS_WORD value read for IOUT_OC_FAULT.
+ *
+ * "An output overcurrent fault has occurred." If it is on, and fault not
+ * set, trace STATUS_WORD, STATUS_MFR_SPECIFIC, and STATUS_IOUT values.
+ */
+ void analyzeIoutOCFault();
+
+ /**
+ * @brief Examines STATUS_WORD value read to see if there is a UV fault.
+ *
+ * Checks if the VOUT bit is on, indicating "An output voltage fault or
+ * warning has occurred", if it is on, but VOUT_OV_FAULT is off, it is
+ * determined to be an indication of an output under-voltage fault.
+ */
+ void analyzeVoutUVFault();
+
+ /**
+ * @brief Examine STATUS_WORD for the fan fault/warning bit.
+ *
+ * If fanFault is not on, trace that the bit now came on, include
+ * STATUS_WORD, STATUS_MFR_SPECIFIC, and STATUS_FANS_1_2 values as well, to
+ * help with understanding what may have caused it to be set.
+ */
+ void analyzeFanFault();
+
+ /**
+ * @brief Examine STATUS_WORD for temperature fault.
+ */
+ void analyzeTemperatureFault();
+
+ /**
+ * @brief Examine STATUS_WORD for pgood or unit off faults.
+ */
+ void analyzePgoodFault();
+
+ /**
+ * @brief Determine possible manufacturer-specific faults from bits in
+ * STATUS_MFR.
+ *
+ * The bits in the STATUS_MFR_SPECIFIC command response have "Manufacturer
+ * Defined" meanings. Determine which faults, if any, are present based on
+ * the power supply (device driver) type.
+ */
+ void determineMFRFault();
+
+ /**
+ * @brief Examine STATUS_WORD value read for MFRSPECIFIC bit on.
+ *
+ * "A manufacturer specific fault or warning has occurred."
+ *
+ * If it is on, call the determineMFRFault() helper function to examine the
+ * value read from STATUS_MFR_SPECIFIC.
+ */
+ void analyzeMFRFault();
+
+ /**
+ * @brief Analyzes the STATUS_WORD for a VIN_UV_FAULT indicator.
+ */
+ void analyzeVinUVFault();
+
+ /**
+ * @brief Given a full inventory path, returns the last node of the path as
+ * the "short name"
+ */
+ std::string findShortName(const std::string& invPath)
+ {
+ const auto lastSlashPos = invPath.find_last_of('/');
+
+ if ((lastSlashPos == std::string::npos) ||
+ ((lastSlashPos + 1) == invPath.size()))
+ {
+ return invPath;
+ }
+ else
+ {
+ return invPath.substr(lastSlashPos + 1);
+ }
+ }
+
+ /**
+ * @brief Binds or unbinds the power supply device driver
+ *
+ * Called when a presence change is detected to either bind the device
+ * driver for the power supply when it is installed, or unbind the device
+ * driver when the power supply is removed.
+ *
+ * Note:
+ * Bind device when device present and i2cbus-i2caddr does not exist
+ * UnBind device when device not present and i2cbus-i2caddr exist
+
+ * Writes <device> to <path>/bind (or unbind)
+ *
+ * @param present - when true, will bind the device driver
+ * when false, will unbind the device driver
+ */
+ void bindOrUnbindDriver(bool present);
+
+ /**
+ * @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 Updates the power supply presence by reading the GPIO line.
+ */
+ void updatePresenceGPIO();
+
+ /**
+ * @brief Callback for inventory property changes
+ *
+ * Process change of Present property for power supply.
+ *
+ * This is used if we are watching the D-Bus properties instead of reading
+ * the GPIO presence line ourselves.
+ *
+ * @param[in] msg - Data associated with Present change signal
+ **/
+ void inventoryChanged(sdbusplus::message_t& msg);
+
+ /**
+ * @brief Callback for inventory property added.
+ *
+ * Process add of the interface with the Present property for power supply.
+ *
+ * This is used if we are watching the D-Bus properties instead of reading
+ * the GPIO presence line ourselves.
+ *
+ * @param[in] msg - Data associated with Present add signal
+ **/
+ void inventoryAdded(sdbusplus::message_t& msg);
+
+ /**
+ * @brief Reads the pmbus MFR_POUT_MAX value.
+ *
+ * "The MFR_POUT_MAX command sets or retrieves the maximum rated output
+ * power, in watts, that the unit is rated to supply."
+ *
+ * @return max_power_out value converted from string.
+ */
+ auto getMaxPowerOut() const;
+
+ /**
+ * @brief Reads a VPD value from PMBus, correct size, and contents.
+ *
+ * If the VPD data read is not the passed in size, resize and fill with
+ * spaces. If the data contains a non-alphanumeric value, replace any of
+ * those values with spaces.
+ *
+ * @param[in] vpdName - The name of the sysfs "file" to read data from.
+ * @param[in] type - The HWMON file type to read from.
+ * @param[in] vpdSize - The expacted size of the data for this VPD/property
+ *
+ * @return A string containing the VPD data read, resized if necessary
+ */
+ auto readVPDValue(const std::string& vpdName,
+ const phosphor::pmbus::Type& type,
+ const std::size_t& vpdSize);
+
+ /**
+ * @brief Retrieve PSU VPD keyword from D-Bus
+ *
+ * It retrieves PSU VPD keyword from D-Bus and assign the associated
+ * string to vpdStr.
+ * @param[in] keyword - The VPD search keyword
+ * @param[out] vpdStr - The VPD string associated with the keyword.
+ */
+ void getPsuVpdFromDbus(const std::string& keyword, std::string& vpdStr);
+
+ /**
+ * @brief Creates the appropriate sensor D-Bus objects.
+ */
+ void setupSensors();
+
+ /**
+ * @brief Monitors sensor values and updates D-Bus.
+ * Called from analyze().
+ */
+ void monitorSensors();
+
+ /**
+ * @brief Creates the peak input power sensor D-Bus object
+ * if the PS supports it.
+ */
+ void setupInputPowerPeakSensor();
+
+ /**
+ * @brief Monitors the peak input power sensor
+ */
+ void monitorPeakInputPowerSensor();
+
+ /**
+ * @brief Sets any sensor objects to Available = false on D-Bus.
+ */
+ void setSensorsNotAvailable();
+
+ /**
+ * @brief Returns the associations to create for a sensor on this
+ * power supply.
+ */
+ std::vector<AssociationTuple> getSensorAssociations();
+
+ /**
+ * @brief systemd bus member
+ */
+ sdbusplus::bus_t& bus;
+
+ /**
+ * @brief D-Bus path to use for this power supply's inventory status.
+ **/
+ std::string inventoryPath;
+
+ /**
+ * @brief The file system path used for binding the device driver.
+ */
+ std::filesystem::path bindPath;
+
+ /**
+ * @brief Get the power on status of the psu manager class.
+ *
+ * This is a callback method used to get the power on status of the psu
+ * manager class.
+ */
+ std::function<bool()> isPowerOn;
+
+ /**
+ * @brief Set to true when INPUT_HISTORY sync is required.
+ *
+ * A power supply will need to synchronize its INPUT_HISTORY data with the
+ * other power supplies installed in the system when it goes from missing to
+ * present.
+ */
+ bool syncHistoryRequired{false};
+
+ /**
+ * @brief Store the short name to avoid string processing.
+ *
+ * The short name will be something like powersupply1, the last part of the
+ * inventoryPath.
+ */
+ std::string shortName;
+
+ /**
+ * @brief The libgpiod object for monitoring PSU presence
+ */
+ std::unique_ptr<GPIOInterfaceBase> presenceGPIO = nullptr;
+
+ /**
+ * @brief True if the power supply is present.
+ */
+ bool present = false;
+
+ /**
+ * @brief Power supply model name.
+ */
+ std::string modelName;
+
+ /**
+ * @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 Pointer to the PMBus interface
+ *
+ * Used to read or write to/from PMBus power supply devices.
+ */
+ std::unique_ptr<phosphor::pmbus::PMBusBase> pmbusIntf = nullptr;
+
+ /**
+ * @brief Stored copy of the firmware version/revision string
+ */
+ std::string fwVersion;
+
+ /**
+ * @brief The string to pass in for binding the device driver.
+ */
+ 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 Will be updated to the latest/lastvalue read from STATUS_WORD.
+ */
+ uint64_t statusWord = 0;
+
+ /**
+ * @brief Will be set to the last read value of STATUS_WORD.
+ */
+ uint64_t statusWordOld = 0;
+
+ /**
+ * @brief Will be updated to the latest/lastvalue read from STATUS_INPUT.
+ */
+ uint64_t statusInput = 0;
+
+ /**
+ * @brief Will be updated to the latest/lastvalue read from STATUS_MFR.
+ */
+ uint64_t statusMFR = 0;
+
+ /**
+ * @brief Will be updated to the latest/last value read from STATUS_CML.
+ */
+ uint64_t statusCML = 0;
+
+ /**
+ * @brief Will be updated to the latest/last value read from STATUS_VOUT.
+ */
+ uint64_t statusVout = 0;
+
+ /**
+ * @brief Will be updated to the latest/last value read from STATUS_IOUT.
+ */
+ uint64_t statusIout = 0;
+
+ /**
+ * @brief Will be updated to the latest/last value read from
+ * STATUS_FANS_1_2.
+ */
+ uint64_t statusFans12 = 0;
+
+ /**
+ * @brief Will be updated to the latest/last value read from
+ * STATUS_TEMPERATURE.
+ */
+ uint64_t statusTemperature = 0;
+
+ /**
+ * @brief Will be updated with latest converted value read from READ_VIN
+ */
+ int inputVoltage = phosphor::pmbus::in_input::VIN_VOLTAGE_0;
+
+ /**
+ * @brief Will be updated with the actual voltage last read from READ_VIN
+ */
+ double actualInputVoltage = 0;
+
+ /**
+ * @brief True if an error for a fault has already been logged.
+ */
+ bool faultLogged = false;
+
+ /**
+ * @brief Incremented if bit 1 of STATUS_WORD low byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t cmlFault = 0;
+
+ /**
+ * @brief Incremented if bit 5 of STATUS_WORD high byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t inputFault = 0;
+
+ /**
+ * @brief Incremented if bit 4 of STATUS_WORD high byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t mfrFault = 0;
+
+ /**
+ * @brief Incremented if bit 3 of STATUS_WORD low byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t vinUVFault = 0;
+
+ /**
+ * @brief Incremented if bit 5 of STATUS_WORD low byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t voutOVFault = 0;
+
+ /**
+ * @brief Incremented if bit 4 of STATUS_WORD low byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t ioutOCFault = 0;
+
+ /**
+ * @brief Incremented if bit 7 of STATUS_WORD high byte is on and bit 5
+ * (VOUT_OV) of low byte is off.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t voutUVFault = 0;
+
+ /**
+ * @brief Incremented if FANS fault/warn bit on in STATUS_WORD.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t fanFault = 0;
+
+ /**
+ * @brief Incremented if bit 2 of STATUS_WORD low byte is on.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t tempFault = 0;
+
+ /**
+ * @brief Incremented if bit 11 or 6 of STATUS_WORD is on. PGOOD# is
+ * inactive, or the unit is off.
+ *
+ * Considered faulted if reaches DEGLITCH_LIMIT.
+ */
+ size_t pgoodFault = 0;
+
+ /**
+ * @brief Power Supply Kill fault.
+ *
+ * Incremented based on bits in STATUS_MFR_SPECIFIC. IBM power supplies use
+ * bit 4 to indicate this fault. Considered faulted if it reaches
+ * DEGLITCH_LIMIT.
+ */
+ size_t psKillFault = 0;
+
+ /**
+ * @brief Power Supply 12Vcs fault (standby power).
+ *
+ * Incremented based on bits in STATUS_MFR_SPECIFIC. IBM power supplies use
+ * bit 6 to indicate this fault. Considered faulted if it reaches
+ * DEGLITCH_LIMIT.
+ */
+ size_t ps12VcsFault = 0;
+
+ /**
+ * @brief Power Supply Current-Share fault in 12V domain.
+ *
+ * Incremented based on bits in STATUS_MFR_SPECIFIC. IBM power supplies use
+ * bit 7 to indicate this fault. Considered faulted if it reaches
+ * DEGLITCH_LIMIT.
+ */
+ size_t psCS12VFault = 0;
+
+ /**
+ * @brief Set to AC_FAULT_LIMIT when AC fault is detected, decremented when
+ * AC fault has cleared. Effectively forms a timer since last AC failure.
+ * Zero indicates being outside the window of concern.
+ */
+ size_t acFault = 0;
+
+ /**
+ * @brief Count of the number of read failures.
+ */
+ size_t readFail = 0;
+
+ /**
+ * @brief The D-Bus object for the input voltage rating
+ *
+ * It is updated at startup and power on. If a power supply is
+ * added or removed after that, it does not need to be updated
+ * again (though that could be done as a future improvement).
+ */
+ std::unique_ptr<SensorObject> inputVoltageRatingIface;
+
+ /**
+ * @brief The D-Bus object for the peak input power sensor.
+ */
+ std::unique_ptr<PowerSensorObject> peakInputPowerSensor;
+
+ /**
+ * @brief The device driver name
+ */
+ std::string driverName;
+
+ /**
+ * @brief The chassis unique name
+ */
+ std::string chassisName;
+};
+
+} // namespace phosphor::power::psu