| #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 |