chassis-psu: Enhance PSU function state management
Introduced new utility functions to enhance PSU handling in
Multi-Chassis environment.
- Read initial chassis power state (on/off, pgood) and sets up
monitoring when power is on.
- Validate supported PSU configurations.
- Validate all present PSUs have same model.
- Validate number of PSUs against supported configuration.
- Validate each PSU reports an input voltage within range.
- Set Power Config GPIO based on the system supported configuration.
- Adding D-Bus call to report PSU errors setPowerSupplyError()
- Clears PSU fault status bit and resets to a clean state during PSU
presence or power state changes.
- Implemented "onOffConfig()" to write PMBus ON_OFF_CONFIG command to
all PSUs to ensure consistent PSU control.
Test:
The following tests were done in simulated environment:
- Verified `initialize()` retrieved and set current BMC state.
- Validated supported configuration had all the PSUs present and had
same model.Validated that mismatching PSUs models reported an
unsupported configuration error.
- Confirmed that setPowerSupplyError() was invoked when mismatches
occurred.
- Validated all present PSUs reported voltage in the supported range.
Change-Id: I5a7a6e40140829e6252e7c0ec0a04be369e3d445
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/phosphor-power-supply/chassis.cpp b/phosphor-power-supply/chassis.cpp
index 4467db5..2031247 100644
--- a/phosphor-power-supply/chassis.cpp
+++ b/phosphor-power-supply/chassis.cpp
@@ -365,7 +365,7 @@
sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
POWER_IFACE),
[this](auto& msg) { this->powerStateChanged(msg); });
- // TODO initialize the chassis
+ initialize();
}
void Chassis::validateConfig()
@@ -498,9 +498,8 @@
{
std::map<std::string, std::string> requiredPSUsData;
auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
- // TODO check required PSU
- if (!requiredPSUsPresent)
+ if (!requiredPSUsPresent && isRequiredPSU(*psu))
{
additionalData.merge(requiredPSUsData);
// Create error for power supply missing.
@@ -780,7 +779,7 @@
method.append(faultName, level, additionalData);
auto reply = bus.call(method);
- // TODO set power supply error
+ setPowerSupplyError(faultName);
}
catch (const std::exception& e)
{
@@ -820,15 +819,81 @@
bool Chassis::hasRequiredPSUs(
std::map<std::string, std::string>& additionalData)
{
- // ignore the following loop so code will compile
- for (const auto& pair : additionalData)
+ std::string model{};
+ if (!validateModelName(model, additionalData))
{
- std::cout << "Key = " << pair.first
- << " additionalData value = " << pair.second << "\n";
+ return false;
}
- return true;
- // TODO validate having the required PSUs
+ auto presentCount =
+ std::count_if(psus.begin(), psus.end(),
+ [](const auto& psu) { return psu->isPresent(); });
+
+ // Validate the supported configurations. A system may support more than one
+ // power supply model configuration. Since all configurations need to be
+ // checked, the additional data would contain only the information of the
+ // last configuration that did not match.
+ std::map<std::string, std::string> tmpAdditionalData;
+ for (const auto& config : supportedConfigs)
+ {
+ if (config.first != model)
+ {
+ continue;
+ }
+
+ // Number of power supplies present should equal or exceed the expected
+ // count
+ if (presentCount < config.second.powerSupplyCount)
+ {
+ tmpAdditionalData.clear();
+ tmpAdditionalData["EXPECTED_COUNT"] =
+ std::to_string(config.second.powerSupplyCount);
+ tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
+ continue;
+ }
+
+ bool voltageValidated = true;
+ for (const auto& psu : psus)
+ {
+ if (!psu->isPresent())
+ {
+ // Only present PSUs report a valid input voltage
+ continue;
+ }
+
+ double actualInputVoltage;
+ int inputVoltage;
+ psu->getInputVoltage(actualInputVoltage, inputVoltage);
+
+ if (std::find(config.second.inputVoltage.begin(),
+ config.second.inputVoltage.end(), inputVoltage) ==
+ config.second.inputVoltage.end())
+ {
+ tmpAdditionalData.clear();
+ tmpAdditionalData["ACTUAL_VOLTAGE"] =
+ std::to_string(actualInputVoltage);
+ for (const auto& voltage : config.second.inputVoltage)
+ {
+ tmpAdditionalData["EXPECTED_VOLTAGE"] +=
+ std::to_string(voltage) + " ";
+ }
+ tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+
+ voltageValidated = false;
+ break;
+ }
+ }
+ if (!voltageValidated)
+ {
+ continue;
+ }
+
+ return true;
+ }
+
+ additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
+ return false;
}
void Chassis::updateMissingPSUs()
@@ -901,6 +966,106 @@
}
}
+void Chassis::initialize()
+{
+ try
+ {
+ // pgood is the latest read of the chassis pgood
+ int pgood = 0;
+ util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
+ powerService, bus, pgood);
+
+ // state is the latest requested power on / off transition
+ auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
+ POWER_IFACE, "getPowerState");
+ auto reply = bus.call(method);
+ int state = 0;
+ reply.read(state);
+
+ if (state)
+ {
+ // Monitor PSUs anytime state is on
+ powerOn = true;
+ // In the power fault window if pgood is off
+ powerFaultOccurring = !pgood;
+ validationTimer->restartOnce(validationTimeout);
+ }
+ else
+ {
+ // Power is off
+ powerOn = false;
+ powerFaultOccurring = false;
+ runValidateConfig = true;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info(
+ "Failed to get power state, assuming it is off, error {ERROR}",
+ "ERROR", e);
+ powerOn = false;
+ powerFaultOccurring = false;
+ runValidateConfig = true;
+ }
+
+ onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
+ clearFaults();
+ updateMissingPSUs();
+ setPowerConfigGPIO();
+
+ lg2::info(
+ "initialize: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
+ "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
+}
+
+void Chassis::setPowerSupplyError(const std::string& psuErrorString)
+{
+ using namespace sdbusplus::xyz::openbmc_project;
+ constexpr auto method = "setPowerSupplyError";
+
+ try
+ {
+ // Call D-Bus method to inform pseq of PSU error
+ auto methodMsg = bus.new_method_call(
+ powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
+ methodMsg.append(psuErrorString);
+ auto callReply = bus.call(methodMsg);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info("Failed calling setPowerSupplyError due to error {ERROR}",
+ "ERROR", e);
+ }
+}
+
+void Chassis::setPowerConfigGPIO()
+{
+ if (!powerConfigGPIO)
+ {
+ return;
+ }
+
+ std::string model{};
+ std::map<std::string, std::string> additionalData;
+ if (!validateModelName(model, additionalData))
+ {
+ return;
+ }
+
+ auto config = supportedConfigs.find(model);
+ if (config != supportedConfigs.end())
+ {
+ // The power-config-full-load is an open drain GPIO. Set it to low (0)
+ // if the supported configuration indicates that this system model
+ // expects the maximum number of power supplies (full load set to true).
+ // Else, set it to high (1), this is the default.
+ auto powerConfigValue =
+ (config->second.powerConfigFullLoad == true ? 0 : 1);
+ auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
+ powerConfigGPIO->write(powerConfigValue, flags);
+ }
+}
+
void Chassis::powerStateChanged(sdbusplus::message_t& msg)
{
std::string msgSensor;
@@ -918,11 +1083,10 @@
powerOn = true;
powerFaultOccurring = false;
validationTimer->restartOnce(validationTimeout);
- // TODO clear faults
+ clearFaults();
syncHistory();
- // TODO set power config
-
+ setPowerConfigGPIO();
setInputVoltageRating();
}
else
@@ -954,4 +1118,143 @@
"POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
}
+bool Chassis::validateModelName(
+ std::string& model, std::map<std::string, std::string>& additionalData)
+{
+ // Check that all PSUs have the same model name. Initialize the model
+ // variable with the first PSU name found, then use it as a base to compare
+ // against the rest of the PSUs and get its inventory path to use as callout
+ // if needed.
+ model.clear();
+ std::string modelInventoryPath{};
+ for (const auto& psu : psus)
+ {
+ auto psuModel = psu->getModelName();
+ if (psuModel.empty())
+ {
+ continue;
+ }
+ if (model.empty())
+ {
+ model = psuModel;
+ modelInventoryPath = psu->getInventoryPath();
+ continue;
+ }
+ if (psuModel != model)
+ {
+ if (supportedConfigs.find(model) != supportedConfigs.end())
+ {
+ // The base model is supported, callout the mismatched PSU. The
+ // mismatched PSU may or may not be supported.
+ additionalData["EXPECTED_MODEL"] = model;
+ additionalData["ACTUAL_MODEL"] = psuModel;
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+ }
+ else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
+ {
+ // The base model is not supported, but the mismatched PSU is,
+ // callout the base PSU.
+ additionalData["EXPECTED_MODEL"] = psuModel;
+ additionalData["ACTUAL_MODEL"] = model;
+ additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
+ }
+ else
+ {
+ // The base model and the mismatched PSU are not supported or
+ // could not be found in the supported configuration, callout
+ // the mismatched PSU.
+ additionalData["EXPECTED_MODEL"] = model;
+ additionalData["ACTUAL_MODEL"] = psuModel;
+ additionalData["CALLOUT_INVENTORY_PATH"] =
+ psu->getInventoryPath();
+ }
+ model.clear();
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Chassis::isRequiredPSU(const PowerSupply& psu)
+{
+ // Get required number of PSUs; if not found, we don't know if PSU required
+ unsigned int requiredCount = getRequiredPSUCount();
+ if (requiredCount == 0)
+ {
+ return false;
+ }
+
+ // If total PSU count <= the required count, all PSUs are required
+ if (psus.size() <= requiredCount)
+ {
+ return true;
+ }
+
+ // We don't currently get information from EntityManager about which PSUs
+ // are required, so we have to do some guesswork. First check if this PSU
+ // is present. If so, assume it is required.
+ if (psu.isPresent())
+ {
+ return true;
+ }
+
+ // This PSU is not present. Count the number of other PSUs that are
+ // present. If enough other PSUs are present, assume the specified PSU is
+ // not required.
+ unsigned int psuCount =
+ std::count_if(psus.begin(), psus.end(),
+ [](const auto& psu) { return psu->isPresent(); });
+ if (psuCount >= requiredCount)
+ {
+ return false;
+ }
+
+ // Check if this PSU was previously present. If so, assume it is required.
+ // We know it was previously present if it has a non-empty model name.
+ if (!psu.getModelName().empty())
+ {
+ return true;
+ }
+
+ // This PSU was never present. Count the number of other PSUs that were
+ // previously present. If including those PSUs is enough, assume the
+ // specified PSU is not required.
+ psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
+ return (!psu->isPresent() && !psu->getModelName().empty());
+ });
+ if (psuCount >= requiredCount)
+ {
+ return false;
+ }
+
+ // We still haven't found enough PSUs. Sort the inventory paths of PSUs
+ // that were never present. PSU inventory paths typically end with the PSU
+ // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
+ std::vector<std::string> sortedPaths;
+ std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
+ if (!psu->isPresent() && psu->getModelName().empty())
+ {
+ sortedPaths.push_back(psu->getInventoryPath());
+ }
+ });
+ std::sort(sortedPaths.begin(), sortedPaths.end());
+
+ // Check if specified PSU is close enough to start of list to be required
+ for (const auto& path : sortedPaths)
+ {
+ if (path == psu.getInventoryPath())
+ {
+ return true;
+ }
+ if (++psuCount >= requiredCount)
+ {
+ break;
+ }
+ }
+
+ // PSU was not close to start of sorted list; assume not required
+ return false;
+}
+
} // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis.hpp b/phosphor-power-supply/chassis.hpp
index 6471f26..48bdefd 100644
--- a/phosphor-power-supply/chassis.hpp
+++ b/phosphor-power-supply/chassis.hpp
@@ -305,7 +305,7 @@
* @brief Initializes the chassis.
*
*/
- void initialize() {}; // TODO
+ void initialize();
/**
* @brief Perform power supply configuration validation.
@@ -393,6 +393,76 @@
* @breif Attempt to create GPIO
*/
void attemptToCreatePowerConfigGPIO();
+
+ /**
+ * 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.
+ */
+ void onOffConfig(const uint8_t data)
+ {
+ for (auto& psu : psus)
+ {
+ psu->onOffConfig(data);
+ }
+ }
+
+ /**
+ * 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()
+ {
+ setPowerSupplyError("");
+ for (auto& psu : psus)
+ {
+ psu->clearFaults();
+ }
+ }
+
+ /**
+ * Let power control/sequencer application know of PSU error(s).
+ *
+ * @param[in] psuErrorString - string for power supply error
+ */
+ void setPowerSupplyError(const std::string& psuErrorString);
+
+ /**
+ * @brief Set the power-config-full-load GPIO depending on the EM full load
+ * property value.
+ */
+ void setPowerConfigGPIO();
+
+ /**
+ * @brief Helper function to validate that all PSUs have the same model name
+ *
+ * @param[out] model - The model name. Empty if there is a mismatch.
+ * @param[out] additionalData - If there is a mismatch, it contains debug
+ * information such as the mismatched model name.
+ * @return true if all the PSUs have the same model name, false otherwise.
+ */
+ bool validateModelName(std::string& model,
+ std::map<std::string, std::string>& additionalData);
+
+ /**
+ * @brief Returns the number of PSUs that are required to be present.
+ */
+ unsigned int getRequiredPSUCount()
+ {
+ // TODO
+ return 1;
+ }
+
+ /**
+ * @brief Returns whether the specified PSU is required to be present.
+ *
+ * @param[in] psu - Power supply to check
+ * @return true if PSU is required, false otherwise.
+ */
+ bool isRequiredPSU(const PowerSupply& psu);
};
} // namespace phosphor::power::chassis