blob: 51cb5cb697b595e7a6d20dbbf44c0e3270bbac08 [file] [log] [blame]
Brandon Wyman18a24d92022-04-19 22:48:34 +00001#include "config.h"
2
Brandon Wymana0f33ce2019-10-17 18:32:29 -05003#include "psu_manager.hpp"
4
5#include "utility.hpp"
6
Brandon Wymanb76ab242020-09-16 18:06:06 -05007#include <sys/types.h>
8#include <unistd.h>
9
Jim Wright7f9288c2022-12-08 11:57:04 -060010#include <xyz/openbmc_project/State/Chassis/server.hpp>
11
Shawn McCarney9252b7e2022-06-10 12:47:38 -050012#include <algorithm>
Shawn McCarney768d2262024-07-09 15:02:59 -050013#include <format>
Brandon Wymanecbecbc2021-08-31 22:53:21 +000014#include <regex>
Shawn McCarney9252b7e2022-06-10 12:47:38 -050015#include <set>
Brandon Wymanecbecbc2021-08-31 22:53:21 +000016
Brandon Wymanaed1f752019-11-25 18:10:52 -060017using namespace phosphor::logging;
18
Brandon Wyman63ea78b2020-09-24 16:49:09 -050019namespace phosphor::power::manager
Brandon Wymana0f33ce2019-10-17 18:32:29 -050020{
Adriana Kobylakc9b05732022-03-19 15:15:10 +000021constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
22constexpr auto objectManagerObjPath =
23 "/xyz/openbmc_project/power/power_supplies";
24constexpr auto powerSystemsInputsObjPath =
25 "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
Brandon Wymana0f33ce2019-10-17 18:32:29 -050026
Brandon Wyman510acaa2020-11-05 18:32:04 -060027constexpr auto IBMCFFPSInterface =
28 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
29constexpr auto i2cBusProp = "I2CBus";
30constexpr auto i2cAddressProp = "I2CAddress";
31constexpr auto psuNameProp = "Name";
B. J. Wyman681b2a32021-04-20 22:31:22 +000032constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060033
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060034constexpr auto supportedConfIntf =
35 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060036
Faisal Awadab66ae502023-04-01 18:30:32 -050037const auto deviceDirPath = "/sys/bus/i2c/devices/";
38const auto driverDirName = "/driver";
39
Brandon Wymanc9e840e2022-05-10 20:48:41 +000040constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
Brandon Wyman18a24d92022-04-19 22:48:34 +000041
Patrick Williams7354ce62022-07-22 19:26:56 -050042PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000043 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
Brandon Wymanc3324422022-03-24 20:30:57 +000044 objectManager(bus, objectManagerObjPath),
Matt Spinlera068f422023-03-10 13:06:49 -060045 sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060046{
Brandon Wyman510acaa2020-11-05 18:32:04 -060047 // Subscribe to InterfacesAdded before doing a property read, otherwise
48 // the interface could be created after the read attempt but before the
49 // match is created.
50 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
51 bus,
52 sdbusplus::bus::match::rules::interfacesAdded() +
53 sdbusplus::bus::match::rules::sender(
54 "xyz.openbmc_project.EntityManager"),
55 std::bind(&PSUManager::entityManagerIfaceAdded, this,
56 std::placeholders::_1));
57 getPSUConfiguration();
58 getSystemProperties();
59
Adriana Kobylakc9b05732022-03-19 15:15:10 +000060 // Request the bus name before the analyze() function, which is the one that
61 // determines the brownout condition and sets the status d-bus property.
62 bus.request_name(managerBusName);
63
Brandon Wyman510acaa2020-11-05 18:32:04 -060064 using namespace sdeventplus;
65 auto interval = std::chrono::milliseconds(1000);
66 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
67 e, std::bind(&PSUManager::analyze, this), interval);
68
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000069 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
70 e, std::bind(&PSUManager::validateConfig, this));
71
Adriana Kobylakc0a07582021-10-13 15:52:25 +000072 try
73 {
74 powerConfigGPIO = createGPIO("power-config-full-load");
75 }
76 catch (const std::exception& e)
77 {
78 // Ignore error, GPIO may not be implemented in this system.
79 powerConfigGPIO = nullptr;
80 }
81
Brandon Wyman510acaa2020-11-05 18:32:04 -060082 // Subscribe to power state changes
83 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
84 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
85 bus,
86 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
87 POWER_IFACE),
88 [this](auto& msg) { this->powerStateChanged(msg); });
89
90 initialize();
91}
92
Jim Wrightaca86d02022-06-10 12:01:39 -050093void PSUManager::initialize()
94{
95 try
96 {
97 // pgood is the latest read of the chassis pgood
98 int pgood = 0;
99 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
100 powerService, bus, pgood);
101
102 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -0600103 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -0500104 POWER_IFACE, "getPowerState");
105 auto reply = bus.call(method);
106 int state = 0;
107 reply.read(state);
108
109 if (state)
110 {
111 // Monitor PSUs anytime state is on
112 powerOn = true;
113 // In the power fault window if pgood is off
114 powerFaultOccurring = !pgood;
115 validationTimer->restartOnce(validationTimeout);
116 }
117 else
118 {
119 // Power is off
120 powerOn = false;
121 powerFaultOccurring = false;
122 runValidateConfig = true;
123 }
124 }
125 catch (const std::exception& e)
126 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000127 lg2::info(
128 "Failed to get power state, assuming it is off, error {ERROR}",
129 "ERROR", e);
Jim Wrightaca86d02022-06-10 12:01:39 -0500130 powerOn = false;
131 powerFaultOccurring = false;
132 runValidateConfig = true;
133 }
134
135 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
136 clearFaults();
137 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500138 setPowerConfigGPIO();
139
Anwaar Hadib64228d2025-05-30 23:55:26 +0000140 lg2::info(
141 "initialize: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
142 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
Jim Wrightaca86d02022-06-10 12:01:39 -0500143}
144
Brandon Wyman510acaa2020-11-05 18:32:04 -0600145void PSUManager::getPSUConfiguration()
146{
147 using namespace phosphor::power::util;
148 auto depth = 0;
149 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
150
151 psus.clear();
152
153 // I should get a map of objects back.
154 // Each object will have a path, a service, and an interface.
155 // The interface should match the one passed into this function.
156 for (const auto& [path, services] : objects)
157 {
158 auto service = services.begin()->first;
159
160 if (path.empty() || service.empty())
161 {
162 continue;
163 }
164
165 // For each object in the array of objects, I want to get properties
166 // from the service, path, and interface.
Patrick Williamsf5402192024-08-16 15:20:53 -0400167 auto properties =
168 getAllProperties(bus, path, IBMCFFPSInterface, service);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600169
170 getPSUProperties(properties);
171 }
172
173 if (psus.empty())
174 {
175 // Interface or properties not found. Let the Interfaces Added callback
176 // process the information once the interfaces are added to D-Bus.
Anwaar Hadib64228d2025-05-30 23:55:26 +0000177 lg2::info("No power supplies to monitor");
Brandon Wyman510acaa2020-11-05 18:32:04 -0600178 }
179}
180
181void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
182{
183 // From passed in properties, I want to get: I2CBus, I2CAddress,
184 // and Name. Create a power supply object, using Name to build the inventory
185 // path.
186 const auto basePSUInvPath =
187 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
188 uint64_t* i2cbus = nullptr;
189 uint64_t* i2caddr = nullptr;
190 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000191 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600192
193 for (const auto& property : properties)
194 {
195 try
196 {
197 if (property.first == i2cBusProp)
198 {
199 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
200 }
201 else if (property.first == i2cAddressProp)
202 {
203 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
204 }
205 else if (property.first == psuNameProp)
206 {
207 psuname = std::get_if<std::string>(&properties[psuNameProp]);
208 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000209 else if (property.first == presLineName)
210 {
211 preslineptr =
212 std::get_if<std::string>(&properties[presLineName]);
213 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600214 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500215 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000216 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600217 }
218
219 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
220 {
221 std::string invpath = basePSUInvPath;
222 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000223 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600224
Anwaar Hadib64228d2025-05-30 23:55:26 +0000225 lg2::debug("Inventory Path: {INVPATH}", "INVPATH", invpath);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600226
B. J. Wyman681b2a32021-04-20 22:31:22 +0000227 if (nullptr != preslineptr)
228 {
229 presline = *preslineptr;
230 }
231
Patrick Williamsf5402192024-08-16 15:20:53 -0400232 auto invMatch =
233 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
234 return psu->getInventoryPath() == invpath;
235 });
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000236 if (invMatch != psus.end())
237 {
238 // This power supply has the same inventory path as the one with
239 // information just added to D-Bus.
240 // Changes to GPIO line name unlikely, so skip checking.
241 // Changes to the I2C bus and address unlikely, as that would
242 // require corresponding device tree updates.
243 // Return out to avoid duplicate object creation.
244 return;
245 }
246
Faisal Awadab66ae502023-04-01 18:30:32 -0500247 buildDriverName(*i2cbus, *i2caddr);
Anwaar Hadib64228d2025-05-30 23:55:26 +0000248 lg2::debug(
249 "make PowerSupply bus: {I2CBUS} addr: {I2CADDR} presline: {PRESLINE}",
250 "I2CBUS", *i2cbus, "I2CADDR", *i2caddr, "PRESLINE", presline);
George Liu9464c422023-02-27 14:30:27 +0800251 auto psu = std::make_unique<PowerSupply>(
Faisal Awadab66ae502023-04-01 18:30:32 -0500252 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
George Liu9464c422023-02-27 14:30:27 +0800253 std::bind(
254 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
255 this));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600256 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000257
258 // Subscribe to power supply presence changes
259 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
260 bus,
261 sdbusplus::bus::match::rules::propertiesChanged(invpath,
262 INVENTORY_IFACE),
263 [this](auto& msg) { this->presenceChanged(msg); });
264 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600265 }
266
267 if (psus.empty())
268 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000269 lg2::info("No power supplies to monitor");
Brandon Wyman510acaa2020-11-05 18:32:04 -0600270 }
Faisal Awadab7131a12023-10-26 19:38:45 -0500271 else
272 {
273 populateDriverName();
274 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600275}
276
Adriana Kobylake1074d82021-03-16 20:46:44 +0000277void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
278{
279 try
280 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000281 auto propIt = properties.find("SupportedType");
282 if (propIt == properties.end())
283 {
284 return;
285 }
286 const std::string* type = std::get_if<std::string>(&(propIt->second));
287 if ((type == nullptr) || (*type != "PowerSupply"))
288 {
289 return;
290 }
291
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000292 propIt = properties.find("SupportedModel");
293 if (propIt == properties.end())
294 {
295 return;
296 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000297 const std::string* model = std::get_if<std::string>(&(propIt->second));
298 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000299 {
300 return;
301 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000302
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000303 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000304 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000305 if (propIt != properties.end())
306 {
307 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
308 if (count != nullptr)
309 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000310 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000311 }
312 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000313 propIt = properties.find("InputVoltage");
314 if (propIt != properties.end())
315 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000316 const std::vector<uint64_t>* voltage =
317 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000318 if (voltage != nullptr)
319 {
320 sys.inputVoltage = *voltage;
321 }
322 }
323
Adriana Kobylak886574c2021-11-01 18:22:28 +0000324 // The PowerConfigFullLoad is an optional property, default it to false
325 // since that's the default value of the power-config-full-load GPIO.
326 sys.powerConfigFullLoad = false;
327 propIt = properties.find("PowerConfigFullLoad");
328 if (propIt != properties.end())
329 {
330 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
331 if (fullLoad != nullptr)
332 {
333 sys.powerConfigFullLoad = *fullLoad;
334 }
335 }
336
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000337 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000338 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500339 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000340 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000341}
342
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600343void PSUManager::getSystemProperties()
344{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600345 try
346 {
Patrick Williamsf5402192024-08-16 15:20:53 -0400347 util::DbusSubtree subtree =
348 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000349 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600350 {
351 throw std::runtime_error("Supported Configuration Not Found");
352 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600353
Adriana Kobylake1074d82021-03-16 20:46:44 +0000354 for (const auto& [objPath, services] : subtree)
355 {
356 std::string service = services.begin()->first;
357 if (objPath.empty() || service.empty())
358 {
359 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600360 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000361 auto properties = util::getAllProperties(
362 bus, objPath, supportedConfIntf, service);
363 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600364 }
365 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500366 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600367 {
368 // Interface or property not found. Let the Interfaces Added callback
369 // process the information once the interfaces are added to D-Bus.
370 }
371}
372
Patrick Williams7354ce62022-07-22 19:26:56 -0500373void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600374{
375 try
376 {
377 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000378 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600379 interfaces;
380 msg.read(objPath, interfaces);
381
382 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600383 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600384 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600385 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000386 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600387 }
388
Brandon Wyman510acaa2020-11-05 18:32:04 -0600389 itIntf = interfaces.find(IBMCFFPSInterface);
390 if (itIntf != interfaces.cend())
391 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000392 lg2::info("InterfacesAdded for: {IBMCFFPSINTERFACE}",
393 "IBMCFFPSINTERFACE", IBMCFFPSInterface);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600394 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000395 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600396 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000397
398 // Call to validate the psu configuration if the power is on and both
399 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
400 // processed
401 if (powerOn && !psus.empty() && !supportedConfigs.empty())
402 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000403 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000404 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600405 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500406 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600407 {
408 // Ignore, the property may be of a different type than expected.
409 }
410}
411
Patrick Williams7354ce62022-07-22 19:26:56 -0500412void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500413{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500414 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500415 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500416 msg.read(msgSensor, msgData);
417
Jim Wrightaca86d02022-06-10 12:01:39 -0500418 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500419 auto valPropMap = msgData.find("state");
420 if (valPropMap != msgData.end())
421 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500422 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500423 if (state)
424 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500425 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500426 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500427 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000428 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500429 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000430 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000431 setPowerConfigGPIO();
Matt Spinlera068f422023-03-10 13:06:49 -0600432 setInputVoltageRating();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500433 }
434 else
435 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500436 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500437 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500438 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000439 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500440 }
441 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500442
443 // Check if it was the pgood property that changed.
444 valPropMap = msgData.find("pgood");
445 if (valPropMap != msgData.end())
446 {
447 int pgood = std::get<int>(valPropMap->second);
448 if (!pgood)
449 {
450 // Chassis power good has turned off
451 if (powerOn)
452 {
453 // pgood is off but state is on, in power fault window
454 powerFaultOccurring = true;
455 }
456 }
457 }
Anwaar Hadib64228d2025-05-30 23:55:26 +0000458 lg2::info(
459 "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
460 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500461}
462
Patrick Williams7354ce62022-07-22 19:26:56 -0500463void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000464{
465 std::string msgSensor;
466 std::map<std::string, std::variant<uint32_t, bool>> msgData;
467 msg.read(msgSensor, msgData);
468
469 // Check if it was the Present property that changed.
470 auto valPropMap = msgData.find(PRESENT_PROP);
471 if (valPropMap != msgData.end())
472 {
473 if (std::get<bool>(valPropMap->second))
474 {
475 // A PSU became present, force the PSU validation to run.
476 runValidateConfig = true;
477 validationTimer->restartOnce(validationTimeout);
478 }
479 }
480}
481
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000482void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
483{
484 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000485 constexpr auto method = "setPowerSupplyError";
486
487 try
488 {
489 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600490 auto methodMsg = bus.new_method_call(
491 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000492 methodMsg.append(psuErrorString);
493 auto callReply = bus.call(methodMsg);
494 }
495 catch (const std::exception& e)
496 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000497 lg2::info("Failed calling setPowerSupplyError due to error {ERROR}",
498 "ERROR", e);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000499 }
500}
501
Brandon Wyman8b662882021-10-08 17:31:51 +0000502void PSUManager::createError(const std::string& faultName,
503 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500504{
505 using namespace sdbusplus::xyz::openbmc_project;
506 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
507 constexpr auto loggingCreateInterface =
508 "xyz.openbmc_project.Logging.Create";
509
510 try
511 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000512 additionalData["_PID"] = std::to_string(getpid());
513
Patrick Williamsf5402192024-08-16 15:20:53 -0400514 auto service =
515 util::getService(loggingObjectPath, loggingCreateInterface, bus);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500516
517 if (service.empty())
518 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000519 lg2::error("Unable to get logging manager service");
Brandon Wymanb76ab242020-09-16 18:06:06 -0500520 return;
521 }
522
523 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
524 loggingCreateInterface, "Create");
525
526 auto level = Logging::server::Entry::Level::Error;
527 method.append(faultName, level, additionalData);
528
529 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000530 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500531 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500532 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500533 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000534 lg2::error(
535 "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
536 "FAULT_NAME", faultName, "ERROR", e);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500537 }
538}
539
Brandon Wyman18a24d92022-04-19 22:48:34 +0000540void PSUManager::syncHistory()
541{
Faisal Awadae9b37262023-07-30 21:02:16 -0500542 if (driverName != ACBEL_FSG032_DD_NAME)
Brandon Wyman18a24d92022-04-19 22:48:34 +0000543 {
Faisal Awadae9b37262023-07-30 21:02:16 -0500544 if (!syncHistoryGPIO)
Brandon Wyman18a24d92022-04-19 22:48:34 +0000545 {
Andrew Geissler3a492522023-11-30 13:30:51 -0600546 try
547 {
548 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
549 }
550 catch (const std::exception& e)
551 {
552 // Not an error, system just hasn't implemented the synch gpio
Anwaar Hadib64228d2025-05-30 23:55:26 +0000553 lg2::info("No synchronization GPIO found");
Andrew Geissler3a492522023-11-30 13:30:51 -0600554 syncHistoryGPIO = nullptr;
555 }
Faisal Awadae9b37262023-07-30 21:02:16 -0500556 }
557 if (syncHistoryGPIO)
558 {
559 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
Anwaar Hadib64228d2025-05-30 23:55:26 +0000560 lg2::info("Synchronize INPUT_HISTORY");
Faisal Awadae9b37262023-07-30 21:02:16 -0500561 syncHistoryGPIO->toggleLowHigh(delay);
Anwaar Hadib64228d2025-05-30 23:55:26 +0000562 lg2::info("Synchronize INPUT_HISTORY completed");
Brandon Wyman18a24d92022-04-19 22:48:34 +0000563 }
564 }
Andrew Geissler3a492522023-11-30 13:30:51 -0600565
566 // Always clear synch history required after calling this function
567 for (auto& psu : psus)
568 {
569 psu->clearSyncHistoryRequired();
570 }
Brandon Wyman18a24d92022-04-19 22:48:34 +0000571}
572
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500573void PSUManager::analyze()
574{
Patrick Williamsf5402192024-08-16 15:20:53 -0400575 auto syncHistoryRequired =
576 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
577 return psu->isSyncHistoryRequired();
578 });
Brandon Wyman18a24d92022-04-19 22:48:34 +0000579 if (syncHistoryRequired)
580 {
581 syncHistory();
582 }
583
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500584 for (auto& psu : psus)
585 {
586 psu->analyze();
587 }
588
Jim Wright7f9288c2022-12-08 11:57:04 -0600589 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000590
Jim Wrightcefe85f2022-11-18 09:57:47 -0600591 // Only perform individual PSU analysis if power is on and a brownout has
592 // not already been logged
593 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500594 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600595 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500596 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600597 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000598
Faisal Awada03e2a022023-11-15 20:31:13 +0000599 if (!psu->isFaultLogged() && !psu->isPresent() &&
600 !validationTimer->isEnabled())
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600601 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000602 std::map<std::string, std::string> requiredPSUsData;
603 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500604 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000605 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000606 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000607 // Create error for power supply missing.
608 additionalData["CALLOUT_INVENTORY_PATH"] =
609 psu->getInventoryPath();
610 additionalData["CALLOUT_PRIORITY"] = "H";
611 createError(
612 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
613 additionalData);
614 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600615 psu->setFaultLogged();
616 }
617 else if (!psu->isFaultLogged() && psu->isFaulted())
618 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000619 // Add STATUS_WORD and STATUS_MFR last response, in padded
620 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600621 additionalData["STATUS_WORD"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500622 std::format("{:#04x}", psu->getStatusWord());
Patrick Williamsf5402192024-08-16 15:20:53 -0400623 additionalData["STATUS_MFR"] =
624 std::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600625 // If there are faults being reported, they possibly could be
626 // related to a bug in the firmware version running on the power
627 // supply. Capture that data into the error as well.
628 additionalData["FW_VERSION"] = psu->getFWVersion();
629
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000630 if (psu->hasCommFault())
631 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000632 additionalData["STATUS_CML"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500633 std::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000634 /* Attempts to communicate with the power supply have
635 * reached there limit. Create an error. */
636 additionalData["CALLOUT_DEVICE_PATH"] =
637 psu->getDevicePath();
638
639 createError(
640 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
641 additionalData);
642
643 psu->setFaultLogged();
644 }
645 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600646 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000647 // Include STATUS_INPUT for input faults.
648 additionalData["STATUS_INPUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500649 std::format("{:#02x}", psu->getStatusInput());
Brandon Wymanf07bc792021-10-12 19:00:35 +0000650
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600651 /* The power supply location might be needed if the input
652 * fault is due to a problem with the power supply itself.
653 * Include the inventory path with a call out priority of
654 * low.
655 */
656 additionalData["CALLOUT_INVENTORY_PATH"] =
657 psu->getInventoryPath();
658 additionalData["CALLOUT_PRIORITY"] = "L";
659 createError("xyz.openbmc_project.Power.PowerSupply.Error."
660 "InputFault",
661 additionalData);
662 psu->setFaultLogged();
663 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000664 else if (psu->hasPSKillFault())
665 {
666 createError(
667 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
668 additionalData);
669 psu->setFaultLogged();
670 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000671 else if (psu->hasVoutOVFault())
672 {
673 // Include STATUS_VOUT for Vout faults.
674 additionalData["STATUS_VOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500675 std::format("{:#02x}", psu->getStatusVout());
Brandon Wyman6710ba22021-10-27 17:39:31 +0000676
677 additionalData["CALLOUT_INVENTORY_PATH"] =
678 psu->getInventoryPath();
679
680 createError(
681 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
682 additionalData);
683
684 psu->setFaultLogged();
685 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000686 else if (psu->hasIoutOCFault())
687 {
688 // Include STATUS_IOUT for Iout faults.
689 additionalData["STATUS_IOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500690 std::format("{:#02x}", psu->getStatusIout());
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000691
692 createError(
693 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
694 additionalData);
695
696 psu->setFaultLogged();
697 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000698 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
699 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000700 {
701 // Include STATUS_VOUT for Vout faults.
702 additionalData["STATUS_VOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500703 std::format("{:#02x}", psu->getStatusVout());
Brandon Wyman2cf46942021-10-28 19:09:16 +0000704
705 additionalData["CALLOUT_INVENTORY_PATH"] =
706 psu->getInventoryPath();
707
708 createError(
709 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
710 additionalData);
711
712 psu->setFaultLogged();
713 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000714 // A fan fault should have priority over a temperature fault,
715 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500716 // Only process if not in power fault window.
717 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000718 {
719 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
720 additionalData["STATUS_TEMPERATURE"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500721 std::format("{:#02x}", psu->getStatusTemperature());
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000722 additionalData["STATUS_FANS_1_2"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500723 std::format("{:#02x}", psu->getStatusFans12());
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000724
725 additionalData["CALLOUT_INVENTORY_PATH"] =
726 psu->getInventoryPath();
727
728 createError(
729 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
730 additionalData);
731
732 psu->setFaultLogged();
733 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000734 else if (psu->hasTempFault())
735 {
736 // Include STATUS_TEMPERATURE for temperature faults.
737 additionalData["STATUS_TEMPERATURE"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500738 std::format("{:#02x}", psu->getStatusTemperature());
Brandon Wyman96893a42021-11-05 19:56:57 +0000739
740 additionalData["CALLOUT_INVENTORY_PATH"] =
741 psu->getInventoryPath();
742
743 createError(
744 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
745 additionalData);
746
747 psu->setFaultLogged();
748 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600749 else if (psu->hasMFRFault())
750 {
751 /* This can represent a variety of faults that result in
752 * calling out the power supply for replacement: Output
753 * OverCurrent, Output Under Voltage, and potentially other
754 * faults.
755 *
756 * Also plan on putting specific fault in AdditionalData,
757 * along with register names and register values
758 * (STATUS_WORD, STATUS_MFR, etc.).*/
759
760 additionalData["CALLOUT_INVENTORY_PATH"] =
761 psu->getInventoryPath();
762
763 createError(
764 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500765 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500766
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600767 psu->setFaultLogged();
768 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500769 // Only process if not in power fault window.
770 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000771 {
772 /* POWER_GOOD# is not low, or OFF is on */
773 additionalData["CALLOUT_INVENTORY_PATH"] =
774 psu->getInventoryPath();
775
776 createError(
777 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
778 additionalData);
779
780 psu->setFaultLogged();
781 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500782 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500783 }
784 }
785}
786
Jim Wright7f9288c2022-12-08 11:57:04 -0600787void PSUManager::analyzeBrownout()
788{
789 // Count number of power supplies failing
790 size_t presentCount = 0;
791 size_t notPresentCount = 0;
792 size_t acFailedCount = 0;
793 size_t pgoodFailedCount = 0;
794 for (const auto& psu : psus)
795 {
796 if (psu->isPresent())
797 {
798 ++presentCount;
799 if (psu->hasACFault())
800 {
801 ++acFailedCount;
802 }
803 else if (psu->hasPgoodFault())
804 {
805 ++pgoodFailedCount;
806 }
807 }
808 else
809 {
810 ++notPresentCount;
811 }
812 }
813
814 // Only issue brownout failure if chassis pgood has failed, it has not
815 // already been logged, at least one PSU has seen an AC fail, and all
816 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
817 // at least one PSU is present.
818 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
819 (presentCount == (acFailedCount + pgoodFailedCount)))
820 {
821 // Indicate that the system is in a brownout condition by creating an
822 // error log and setting the PowerSystemInputs status property to Fault.
823 powerSystemInputs.status(
824 sdbusplus::xyz::openbmc_project::State::Decorator::server::
825 PowerSystemInputs::Status::Fault);
826
827 std::map<std::string, std::string> additionalData;
828 additionalData.emplace("NOT_PRESENT_COUNT",
829 std::to_string(notPresentCount));
830 additionalData.emplace("VIN_FAULT_COUNT",
831 std::to_string(acFailedCount));
832 additionalData.emplace("PGOOD_FAULT_COUNT",
833 std::to_string(pgoodFailedCount));
Anwaar Hadib64228d2025-05-30 23:55:26 +0000834 lg2::info(
835 "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
836 "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
837 acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
Jim Wright7f9288c2022-12-08 11:57:04 -0600838
839 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
840 additionalData);
841 brownoutLogged = true;
842 }
843 else
844 {
845 // If a brownout was previously logged but at least one PSU is not
846 // currently in AC fault, determine if the brownout condition can be
847 // cleared
848 if (brownoutLogged && (acFailedCount < presentCount))
849 {
850 // Chassis only recognizes the PowerSystemInputs change when it is
851 // off
852 try
853 {
854 using PowerState = sdbusplus::xyz::openbmc_project::State::
855 server::Chassis::PowerState;
856 PowerState currentPowerState;
857 util::getProperty<PowerState>(
858 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
859 "/xyz/openbmc_project/state/chassis0",
Patrick Williams7affb1f2024-01-19 14:20:46 -0600860 "xyz.openbmc_project.State.Chassis0", bus,
Jim Wright7f9288c2022-12-08 11:57:04 -0600861 currentPowerState);
862
863 if (currentPowerState == PowerState::Off)
864 {
865 // Indicate that the system is no longer in a brownout
866 // condition by setting the PowerSystemInputs status
867 // property to Good.
Anwaar Hadib64228d2025-05-30 23:55:26 +0000868 lg2::info(
869 "Brownout cleared, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
870 "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
871 acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
Jim Wright7f9288c2022-12-08 11:57:04 -0600872 powerSystemInputs.status(
873 sdbusplus::xyz::openbmc_project::State::Decorator::
874 server::PowerSystemInputs::Status::Good);
875 brownoutLogged = false;
876 }
877 }
878 catch (const std::exception& e)
879 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000880 lg2::error("Error trying to clear brownout, error: {ERROR}",
881 "ERROR", e);
Jim Wright7f9288c2022-12-08 11:57:04 -0600882 }
883 }
884 }
885}
886
Brandon Wyman64e97752022-06-03 23:50:13 +0000887void PSUManager::updateMissingPSUs()
888{
889 if (supportedConfigs.empty() || psus.empty())
890 {
891 return;
892 }
893
894 // Power supplies default to missing. If the power supply is present,
895 // the PowerSupply object will update the inventory Present property to
896 // true. If we have less than the required number of power supplies, and
897 // this power supply is missing, update the inventory Present property
898 // to false to indicate required power supply is missing. Avoid
899 // indicating power supply missing if not required.
900
901 auto presentCount =
902 std::count_if(psus.begin(), psus.end(),
903 [](const auto& psu) { return psu->isPresent(); });
904
905 for (const auto& config : supportedConfigs)
906 {
907 for (const auto& psu : psus)
908 {
909 auto psuModel = psu->getModelName();
910 auto psuShortName = psu->getShortName();
911 auto psuInventoryPath = psu->getInventoryPath();
912 auto relativeInvPath =
913 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
914 auto psuPresent = psu->isPresent();
915 auto presProperty = false;
916 auto propReadFail = false;
917
918 try
919 {
920 presProperty = getPresence(bus, psuInventoryPath);
921 propReadFail = false;
922 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500923 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000924 {
925 propReadFail = true;
926 // Relying on property change or interface added to retry.
927 // Log an informational trace to the journal.
Anwaar Hadib64228d2025-05-30 23:55:26 +0000928 lg2::info(
929 "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
930 "PSU_INVENTORY_PATH", psuInventoryPath);
Brandon Wyman64e97752022-06-03 23:50:13 +0000931 }
932
933 if (psuModel.empty())
934 {
935 if (!propReadFail && (presProperty != psuPresent))
936 {
937 // We already have this property, and it is not false
938 // set Present to false
939 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
940 }
941 continue;
942 }
943
944 if (config.first != psuModel)
945 {
946 continue;
947 }
948
949 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
950 {
951 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
952 }
953 }
954 }
955}
956
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000957void PSUManager::validateConfig()
958{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000959 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000960 {
961 return;
962 }
963
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000964 for (const auto& psu : psus)
965 {
Faisal Awada2ae827a2024-03-20 16:45:12 -0500966 if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000967 {
968 // Do not try to validate if input voltage fault present.
969 validationTimer->restartOnce(validationTimeout);
970 return;
971 }
972 }
973
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000974 std::map<std::string, std::string> additionalData;
975 auto supported = hasRequiredPSUs(additionalData);
976 if (supported)
977 {
978 runValidateConfig = false;
Faisal Awadac6fa6662023-04-25 22:43:01 -0500979 double actualVoltage;
980 int inputVoltage;
981 int previousInputVoltage = 0;
982 bool voltageMismatch = false;
983
984 for (const auto& psu : psus)
985 {
986 if (!psu->isPresent())
987 {
988 // Only present PSUs report a valid input voltage
989 continue;
990 }
991 psu->getInputVoltage(actualVoltage, inputVoltage);
992 if (previousInputVoltage && inputVoltage &&
993 (previousInputVoltage != inputVoltage))
994 {
995 additionalData["EXPECTED_VOLTAGE"] =
996 std::to_string(previousInputVoltage);
997 additionalData["ACTUAL_VOLTAGE"] =
998 std::to_string(actualVoltage);
999 voltageMismatch = true;
1000 }
1001 if (!previousInputVoltage && inputVoltage)
1002 {
1003 previousInputVoltage = inputVoltage;
1004 }
1005 }
1006 if (!voltageMismatch)
1007 {
1008 return;
1009 }
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001010 }
1011
1012 // Validation failed, create an error log.
1013 // Return without setting the runValidateConfig flag to false because
1014 // it may be that an additional supported configuration interface is
1015 // added and we need to validate it to see if it matches this system.
1016 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
1017 additionalData);
1018}
1019
1020bool PSUManager::hasRequiredPSUs(
1021 std::map<std::string, std::string>& additionalData)
1022{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001023 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001024 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001025 {
Adriana Kobylak523704d2021-09-21 15:55:41 +00001026 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001027 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001028
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001029 auto presentCount =
1030 std::count_if(psus.begin(), psus.end(),
1031 [](const auto& psu) { return psu->isPresent(); });
1032
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001033 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001034 // power supply model configuration. Since all configurations need to be
1035 // checked, the additional data would contain only the information of the
1036 // last configuration that did not match.
1037 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001038 for (const auto& config : supportedConfigs)
1039 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001040 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001041 {
1042 continue;
1043 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001044
Jim Wright941b60d2022-10-19 16:22:17 -05001045 // Number of power supplies present should equal or exceed the expected
1046 // count
1047 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001048 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001049 tmpAdditionalData.clear();
1050 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001051 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001052 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001053 continue;
1054 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001055
1056 bool voltageValidated = true;
1057 for (const auto& psu : psus)
1058 {
1059 if (!psu->isPresent())
1060 {
1061 // Only present PSUs report a valid input voltage
1062 continue;
1063 }
1064
1065 double actualInputVoltage;
1066 int inputVoltage;
1067 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1068
1069 if (std::find(config.second.inputVoltage.begin(),
Patrick Williamsf5402192024-08-16 15:20:53 -04001070 config.second.inputVoltage.end(), inputVoltage) ==
1071 config.second.inputVoltage.end())
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001072 {
1073 tmpAdditionalData.clear();
1074 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1075 std::to_string(actualInputVoltage);
1076 for (const auto& voltage : config.second.inputVoltage)
1077 {
1078 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1079 std::to_string(voltage) + " ";
1080 }
1081 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1082 psu->getInventoryPath();
1083
1084 voltageValidated = false;
1085 break;
1086 }
1087 }
1088 if (!voltageValidated)
1089 {
1090 continue;
1091 }
1092
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001093 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001094 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001095
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001096 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001097 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001098}
1099
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001100unsigned int PSUManager::getRequiredPSUCount()
1101{
1102 unsigned int requiredCount{0};
1103
1104 // Verify we have the supported configuration and PSU information
1105 if (!supportedConfigs.empty() && !psus.empty())
1106 {
1107 // Find PSU models. They should all be the same.
1108 std::set<std::string> models{};
1109 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1110 if (!psu->getModelName().empty())
1111 {
1112 models.insert(psu->getModelName());
1113 }
1114 });
1115
1116 // If exactly one model was found, find corresponding configuration
1117 if (models.size() == 1)
1118 {
1119 const std::string& model = *(models.begin());
1120 auto it = supportedConfigs.find(model);
1121 if (it != supportedConfigs.end())
1122 {
1123 requiredCount = it->second.powerSupplyCount;
1124 }
1125 }
1126 }
1127
1128 return requiredCount;
1129}
1130
1131bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1132{
1133 // Get required number of PSUs; if not found, we don't know if PSU required
1134 unsigned int requiredCount = getRequiredPSUCount();
1135 if (requiredCount == 0)
1136 {
1137 return false;
1138 }
1139
1140 // If total PSU count <= the required count, all PSUs are required
1141 if (psus.size() <= requiredCount)
1142 {
1143 return true;
1144 }
1145
1146 // We don't currently get information from EntityManager about which PSUs
1147 // are required, so we have to do some guesswork. First check if this PSU
1148 // is present. If so, assume it is required.
1149 if (psu.isPresent())
1150 {
1151 return true;
1152 }
1153
1154 // This PSU is not present. Count the number of other PSUs that are
1155 // present. If enough other PSUs are present, assume the specified PSU is
1156 // not required.
1157 unsigned int psuCount =
1158 std::count_if(psus.begin(), psus.end(),
1159 [](const auto& psu) { return psu->isPresent(); });
1160 if (psuCount >= requiredCount)
1161 {
1162 return false;
1163 }
1164
1165 // Check if this PSU was previously present. If so, assume it is required.
1166 // We know it was previously present if it has a non-empty model name.
1167 if (!psu.getModelName().empty())
1168 {
1169 return true;
1170 }
1171
1172 // This PSU was never present. Count the number of other PSUs that were
1173 // previously present. If including those PSUs is enough, assume the
1174 // specified PSU is not required.
1175 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1176 return (!psu->isPresent() && !psu->getModelName().empty());
1177 });
1178 if (psuCount >= requiredCount)
1179 {
1180 return false;
1181 }
1182
1183 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1184 // that were never present. PSU inventory paths typically end with the PSU
1185 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1186 std::vector<std::string> sortedPaths;
1187 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1188 if (!psu->isPresent() && psu->getModelName().empty())
1189 {
1190 sortedPaths.push_back(psu->getInventoryPath());
1191 }
1192 });
1193 std::sort(sortedPaths.begin(), sortedPaths.end());
1194
1195 // Check if specified PSU is close enough to start of list to be required
1196 for (const auto& path : sortedPaths)
1197 {
1198 if (path == psu.getInventoryPath())
1199 {
1200 return true;
1201 }
1202 if (++psuCount >= requiredCount)
1203 {
1204 break;
1205 }
1206 }
1207
1208 // PSU was not close to start of sorted list; assume not required
1209 return false;
1210}
1211
Adriana Kobylak523704d2021-09-21 15:55:41 +00001212bool PSUManager::validateModelName(
1213 std::string& model, std::map<std::string, std::string>& additionalData)
1214{
1215 // Check that all PSUs have the same model name. Initialize the model
1216 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001217 // against the rest of the PSUs and get its inventory path to use as callout
1218 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001219 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001220 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001221 for (const auto& psu : psus)
1222 {
1223 auto psuModel = psu->getModelName();
1224 if (psuModel.empty())
1225 {
1226 continue;
1227 }
1228 if (model.empty())
1229 {
1230 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001231 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001232 continue;
1233 }
1234 if (psuModel != model)
1235 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001236 if (supportedConfigs.find(model) != supportedConfigs.end())
1237 {
1238 // The base model is supported, callout the mismatched PSU. The
1239 // mismatched PSU may or may not be supported.
1240 additionalData["EXPECTED_MODEL"] = model;
1241 additionalData["ACTUAL_MODEL"] = psuModel;
1242 additionalData["CALLOUT_INVENTORY_PATH"] =
1243 psu->getInventoryPath();
1244 }
1245 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1246 {
1247 // The base model is not supported, but the mismatched PSU is,
1248 // callout the base PSU.
1249 additionalData["EXPECTED_MODEL"] = psuModel;
1250 additionalData["ACTUAL_MODEL"] = model;
1251 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1252 }
1253 else
1254 {
1255 // The base model and the mismatched PSU are not supported or
1256 // could not be found in the supported configuration, callout
1257 // the mismatched PSU.
1258 additionalData["EXPECTED_MODEL"] = model;
1259 additionalData["ACTUAL_MODEL"] = psuModel;
1260 additionalData["CALLOUT_INVENTORY_PATH"] =
1261 psu->getInventoryPath();
1262 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001263 model.clear();
1264 return false;
1265 }
1266 }
1267 return true;
1268}
1269
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001270void PSUManager::setPowerConfigGPIO()
1271{
1272 if (!powerConfigGPIO)
1273 {
1274 return;
1275 }
1276
1277 std::string model{};
1278 std::map<std::string, std::string> additionalData;
1279 if (!validateModelName(model, additionalData))
1280 {
1281 return;
1282 }
1283
1284 auto config = supportedConfigs.find(model);
1285 if (config != supportedConfigs.end())
1286 {
1287 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1288 // if the supported configuration indicates that this system model
1289 // expects the maximum number of power supplies (full load set to true).
1290 // Else, set it to high (1), this is the default.
1291 auto powerConfigValue =
1292 (config->second.powerConfigFullLoad == true ? 0 : 1);
1293 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1294 powerConfigGPIO->write(powerConfigValue, flags);
1295 }
1296}
1297
Faisal Awadab66ae502023-04-01 18:30:32 -05001298void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
1299{
1300 namespace fs = std::filesystem;
1301 std::stringstream ss;
1302 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
Patrick Williamsf5402192024-08-16 15:20:53 -04001303 std::string symLinkPath =
1304 deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
Faisal Awadab66ae502023-04-01 18:30:32 -05001305 try
1306 {
1307 fs::path linkStrPath = fs::read_symlink(symLinkPath);
1308 driverName = linkStrPath.filename();
1309 }
1310 catch (const std::exception& e)
1311 {
Anwaar Hadib64228d2025-05-30 23:55:26 +00001312 lg2::error(
1313 "Failed to find device driver {SYM_LINK_PATH}, error {ERROR}",
1314 "SYM_LINK_PATH", symLinkPath, "ERROR", e);
Faisal Awadab66ae502023-04-01 18:30:32 -05001315 }
1316}
Faisal Awadab7131a12023-10-26 19:38:45 -05001317
1318void PSUManager::populateDriverName()
1319{
1320 std::string driverName;
1321 // Search in PSUs for driver name
1322 std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
1323 if (!psu->getDriverName().empty())
1324 {
1325 driverName = psu->getDriverName();
1326 }
1327 });
1328 // Assign driver name to all PSUs
1329 std::for_each(psus.begin(), psus.end(),
1330 [=](auto& psu) { psu->setDriverName(driverName); });
1331}
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001332} // namespace phosphor::power::manager