blob: 32da768a04df8181de1c2a7507d7f7329bbe0656 [file] [log] [blame]
#include "config.h"
#include "psu_manager.hpp"
#include "utility.hpp"
#include <sys/types.h>
#include <unistd.h>
#include <xyz/openbmc_project/State/Chassis/server.hpp>
#include <algorithm>
#include <format>
#include <regex>
#include <set>
using namespace phosphor::logging;
namespace phosphor::power::manager
{
constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
constexpr auto objectManagerObjPath =
"/xyz/openbmc_project/power/power_supplies";
constexpr auto powerSystemsInputsObjPath =
"/xyz/openbmc_project/power/power_supplies/chassis0/psus";
constexpr auto IBMCFFPSInterface =
"xyz.openbmc_project.Configuration.IBMCFFPSConnector";
constexpr auto i2cBusProp = "I2CBus";
constexpr auto i2cAddressProp = "I2CAddress";
constexpr auto psuNameProp = "Name";
constexpr auto presLineName = "NamedPresenceGpio";
constexpr auto supportedConfIntf =
"xyz.openbmc_project.Configuration.SupportedConfiguration";
const auto deviceDirPath = "/sys/bus/i2c/devices/";
const auto driverDirName = "/driver";
constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
objectManager(bus, objectManagerObjPath),
sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
{
// Subscribe to InterfacesAdded before doing a property read, otherwise
// the interface could be created after the read attempt but before the
// match is created.
entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusplus::bus::match::rules::interfacesAdded() +
sdbusplus::bus::match::rules::sender(
"xyz.openbmc_project.EntityManager"),
std::bind(&PSUManager::entityManagerIfaceAdded, this,
std::placeholders::_1));
getPSUConfiguration();
getSystemProperties();
// Request the bus name before the analyze() function, which is the one that
// determines the brownout condition and sets the status d-bus property.
bus.request_name(managerBusName);
using namespace sdeventplus;
auto interval = std::chrono::milliseconds(1000);
timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
e, std::bind(&PSUManager::analyze, this), interval);
validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
e, std::bind(&PSUManager::validateConfig, this));
try
{
powerConfigGPIO = createGPIO("power-config-full-load");
}
catch (const std::exception& e)
{
// Ignore error, GPIO may not be implemented in this system.
powerConfigGPIO = nullptr;
}
// Subscribe to power state changes
powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
POWER_IFACE),
[this](auto& msg) { this->powerStateChanged(msg); });
initialize();
}
void PSUManager::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)
{
log<level::INFO>(
std::format(
"Failed to get power state, assuming it is off, error {}",
e.what())
.c_str());
powerOn = false;
powerFaultOccurring = false;
runValidateConfig = true;
}
onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
clearFaults();
updateMissingPSUs();
setPowerConfigGPIO();
log<level::INFO>(
std::format("initialize: power on: {}, power fault occurring: {}",
powerOn, powerFaultOccurring)
.c_str());
}
void PSUManager::getPSUConfiguration()
{
using namespace phosphor::power::util;
auto depth = 0;
auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
psus.clear();
// I should get a map of objects back.
// Each object will have a path, a service, and an interface.
// The interface should match the one passed into this function.
for (const auto& [path, services] : objects)
{
auto service = services.begin()->first;
if (path.empty() || service.empty())
{
continue;
}
// For each object in the array of objects, I want to get properties
// from the service, path, and interface.
auto properties = getAllProperties(bus, path, IBMCFFPSInterface,
service);
getPSUProperties(properties);
}
if (psus.empty())
{
// Interface or properties not found. Let the Interfaces Added callback
// process the information once the interfaces are added to D-Bus.
log<level::INFO>(std::format("No power supplies to monitor").c_str());
}
}
void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
{
// From passed in properties, I want to get: I2CBus, I2CAddress,
// and Name. Create a power supply object, using Name to build the inventory
// path.
const auto basePSUInvPath =
"/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
uint64_t* i2cbus = nullptr;
uint64_t* i2caddr = nullptr;
std::string* psuname = nullptr;
std::string* preslineptr = nullptr;
for (const auto& property : properties)
{
try
{
if (property.first == i2cBusProp)
{
i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
}
else if (property.first == i2cAddressProp)
{
i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
}
else if (property.first == psuNameProp)
{
psuname = std::get_if<std::string>(&properties[psuNameProp]);
}
else if (property.first == presLineName)
{
preslineptr =
std::get_if<std::string>(&properties[presLineName]);
}
}
catch (const std::exception& e)
{}
}
if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
{
std::string invpath = basePSUInvPath;
invpath.push_back(psuname->back());
std::string presline = "";
log<level::DEBUG>(std::format("Inventory Path: {}", invpath).c_str());
if (nullptr != preslineptr)
{
presline = *preslineptr;
}
auto invMatch = std::find_if(psus.begin(), psus.end(),
[&invpath](auto& psu) {
return psu->getInventoryPath() == invpath;
});
if (invMatch != psus.end())
{
// This power supply has the same inventory path as the one with
// information just added to D-Bus.
// Changes to GPIO line name unlikely, so skip checking.
// Changes to the I2C bus and address unlikely, as that would
// require corresponding device tree updates.
// Return out to avoid duplicate object creation.
return;
}
buildDriverName(*i2cbus, *i2caddr);
log<level::DEBUG>(
std::format("make PowerSupply bus: {} addr: {} presline: {}",
*i2cbus, *i2caddr, presline)
.c_str());
auto psu = std::make_unique<PowerSupply>(
bus, invpath, *i2cbus, *i2caddr, driverName, presline,
std::bind(
std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
this));
psus.emplace_back(std::move(psu));
// Subscribe to power supply presence changes
auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
bus,
sdbusplus::bus::match::rules::propertiesChanged(invpath,
INVENTORY_IFACE),
[this](auto& msg) { this->presenceChanged(msg); });
presenceMatches.emplace_back(std::move(presenceMatch));
}
if (psus.empty())
{
log<level::INFO>(std::format("No power supplies to monitor").c_str());
}
else
{
populateDriverName();
}
}
void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
{
try
{
auto propIt = properties.find("SupportedType");
if (propIt == properties.end())
{
return;
}
const std::string* type = std::get_if<std::string>(&(propIt->second));
if ((type == nullptr) || (*type != "PowerSupply"))
{
return;
}
propIt = properties.find("SupportedModel");
if (propIt == properties.end())
{
return;
}
const std::string* model = std::get_if<std::string>(&(propIt->second));
if (model == nullptr)
{
return;
}
sys_properties sys;
propIt = properties.find("RedundantCount");
if (propIt != properties.end())
{
const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
if (count != nullptr)
{
sys.powerSupplyCount = *count;
}
}
propIt = properties.find("InputVoltage");
if (propIt != properties.end())
{
const std::vector<uint64_t>* voltage =
std::get_if<std::vector<uint64_t>>(&(propIt->second));
if (voltage != nullptr)
{
sys.inputVoltage = *voltage;
}
}
// The PowerConfigFullLoad is an optional property, default it to false
// since that's the default value of the power-config-full-load GPIO.
sys.powerConfigFullLoad = false;
propIt = properties.find("PowerConfigFullLoad");
if (propIt != properties.end())
{
const bool* fullLoad = std::get_if<bool>(&(propIt->second));
if (fullLoad != nullptr)
{
sys.powerConfigFullLoad = *fullLoad;
}
}
supportedConfigs.emplace(*model, sys);
}
catch (const std::exception& e)
{}
}
void PSUManager::getSystemProperties()
{
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;
}
auto properties = util::getAllProperties(
bus, objPath, supportedConfIntf, service);
populateSysProperties(properties);
}
}
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.
}
}
void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
{
try
{
sdbusplus::message::object_path objPath;
std::map<std::string, std::map<std::string, util::DbusVariant>>
interfaces;
msg.read(objPath, interfaces);
auto itIntf = interfaces.find(supportedConfIntf);
if (itIntf != interfaces.cend())
{
populateSysProperties(itIntf->second);
updateMissingPSUs();
}
itIntf = interfaces.find(IBMCFFPSInterface);
if (itIntf != interfaces.cend())
{
log<level::INFO>(
std::format("InterfacesAdded for: {}", IBMCFFPSInterface)
.c_str());
getPSUProperties(itIntf->second);
updateMissingPSUs();
}
// Call to validate the psu configuration if the power is on and both
// the IBMCFFPSConnector and SupportedConfiguration interfaces have been
// processed
if (powerOn && !psus.empty() && !supportedConfigs.empty())
{
validationTimer->restartOnce(validationTimeout);
}
}
catch (const std::exception& e)
{
// Ignore, the property may be of a different type than expected.
}
}
void PSUManager::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);
clearFaults();
syncHistory();
setPowerConfigGPIO();
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;
}
}
}
log<level::INFO>(
std::format(
"powerStateChanged: power on: {}, power fault occurring: {}",
powerOn, powerFaultOccurring)
.c_str());
}
void PSUManager::presenceChanged(sdbusplus::message_t& msg)
{
std::string msgSensor;
std::map<std::string, std::variant<uint32_t, bool>> msgData;
msg.read(msgSensor, msgData);
// Check if it was the Present property that changed.
auto valPropMap = msgData.find(PRESENT_PROP);
if (valPropMap != msgData.end())
{
if (std::get<bool>(valPropMap->second))
{
// A PSU became present, force the PSU validation to run.
runValidateConfig = true;
validationTimer->restartOnce(validationTimeout);
}
}
}
void PSUManager::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)
{
log<level::INFO>(
std::format("Failed calling setPowerSupplyError due to error {}",
e.what())
.c_str());
}
}
void PSUManager::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())
{
log<level::ERR>("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);
setPowerSupplyError(faultName);
}
catch (const std::exception& e)
{
log<level::ERR>(
std::format(
"Failed creating event log for fault {} due to error {}",
faultName, e.what())
.c_str());
}
}
void PSUManager::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
log<level::INFO>("No synchronization GPIO found");
syncHistoryGPIO = nullptr;
}
}
if (syncHistoryGPIO)
{
const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
log<level::INFO>("Synchronize INPUT_HISTORY");
syncHistoryGPIO->toggleLowHigh(delay);
log<level::INFO>("Synchronize INPUT_HISTORY completed");
}
}
// Always clear synch history required after calling this function
for (auto& psu : psus)
{
psu->clearSyncHistoryRequired();
}
}
void PSUManager::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
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);
if (!requiredPSUsPresent && isRequiredPSU(*psu))
{
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 PSUManager::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));
log<level::INFO>(
std::format(
"Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
notPresentCount, acFailedCount, pgoodFailedCount)
.c_str());
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))
{
// Chassis only recognizes the PowerSystemInputs change when it is
// off
try
{
using PowerState = sdbusplus::xyz::openbmc_project::State::
server::Chassis::PowerState;
PowerState currentPowerState;
util::getProperty<PowerState>(
"xyz.openbmc_project.State.Chassis", "CurrentPowerState",
"/xyz/openbmc_project/state/chassis0",
"xyz.openbmc_project.State.Chassis0", bus,
currentPowerState);
if (currentPowerState == PowerState::Off)
{
// Indicate that the system is no longer in a brownout
// condition by setting the PowerSystemInputs status
// property to Good.
log<level::INFO>(
std::format(
"Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
notPresentCount, acFailedCount, pgoodFailedCount)
.c_str());
powerSystemInputs.status(
sdbusplus::xyz::openbmc_project::State::Decorator::
server::PowerSystemInputs::Status::Good);
brownoutLogged = false;
}
}
catch (const std::exception& e)
{
log<level::ERR>(
std::format("Error trying to clear brownout, error: {}",
e.what())
.c_str());
}
}
}
}
void PSUManager::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.
log<level::INFO>(
std::format("D-Bus property {} access failure exception",
psuInventoryPath)
.c_str());
}
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 PSUManager::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);
}
bool PSUManager::hasRequiredPSUs(
std::map<std::string, std::string>& additionalData)
{
std::string model{};
if (!validateModelName(model, additionalData))
{
return false;
}
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;
}
unsigned int PSUManager::getRequiredPSUCount()
{
unsigned int requiredCount{0};
// Verify we have the supported configuration and PSU information
if (!supportedConfigs.empty() && !psus.empty())
{
// Find PSU models. They should all be the same.
std::set<std::string> models{};
std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
if (!psu->getModelName().empty())
{
models.insert(psu->getModelName());
}
});
// If exactly one model was found, find corresponding configuration
if (models.size() == 1)
{
const std::string& model = *(models.begin());
auto it = supportedConfigs.find(model);
if (it != supportedConfigs.end())
{
requiredCount = it->second.powerSupplyCount;
}
}
}
return requiredCount;
}
bool PSUManager::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;
}
bool PSUManager::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;
}
void PSUManager::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 PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
{
namespace fs = std::filesystem;
std::stringstream ss;
ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
std::string symLinkPath = deviceDirPath + std::to_string(i2cbus) + "-" +
ss.str() + driverDirName;
try
{
fs::path linkStrPath = fs::read_symlink(symLinkPath);
driverName = linkStrPath.filename();
}
catch (const std::exception& e)
{
log<level::ERR>(std::format("Failed to find device driver {}, error {}",
symLinkPath, e.what())
.c_str());
}
}
void PSUManager::populateDriverName()
{
std::string driverName;
// Search in PSUs for driver name
std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
if (!psu->getDriverName().empty())
{
driverName = psu->getDriverName();
}
});
// Assign driver name to all PSUs
std::for_each(psus.begin(), psus.end(),
[=](auto& psu) { psu->setDriverName(driverName); });
}
} // namespace phosphor::power::manager