blob: 20312478c20d836b73ce3f34592d861d1575fa7b [file] [log] [blame]
Faisal Awada9864f832025-05-30 12:21:00 -05001#include "config.h"
2
3#include "chassis.hpp"
4
Faisal Awada9864f832025-05-30 12:21:00 -05005#include <iostream>
6
7using namespace phosphor::logging;
8using namespace phosphor::power::util;
9namespace phosphor::power::chassis
10{
11
Faisal Awada9ed0f382025-08-14 13:21:46 -050012constexpr auto powerSystemsInputsObjPath =
13 "/xyz/openbmc_project/power/power_supplies/chassis{}/psus";
Faisal Awada9864f832025-05-30 12:21:00 -050014constexpr auto IBMCFFPSInterface =
15 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
16constexpr auto chassisIdProp = "SlotNumber";
17constexpr auto i2cBusProp = "I2CBus";
18constexpr auto i2cAddressProp = "I2CAddress";
19constexpr auto psuNameProp = "Name";
20constexpr auto presLineName = "NamedPresenceGpio";
21constexpr auto supportedConfIntf =
22 "xyz.openbmc_project.Configuration.SupportedConfiguration";
23const auto deviceDirPath = "/sys/bus/i2c/devices/";
24const auto driverDirName = "/driver";
25
26const auto entityMgrService = "xyz.openbmc_project.EntityManager";
27const auto decoratorChassisId = "xyz.openbmc_project.Inventory.Decorator.Slot";
28
Faisal Awada9ed0f382025-08-14 13:21:46 -050029constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
30
Faisal Awada348168b2025-07-08 11:23:02 -050031Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
32 const sdeventplus::Event& e) :
Faisal Awada9864f832025-05-30 12:21:00 -050033 bus(bus), chassisPath(chassisPath),
Faisal Awada9ed0f382025-08-14 13:21:46 -050034 chassisPathUniqueId(getChassisPathUniqueId(chassisPath)),
35 powerSystemInputs(
36 bus, std::format(powerSystemsInputsObjPath, chassisPathUniqueId)),
37 eventLoop(e)
Faisal Awada9864f832025-05-30 12:21:00 -050038{
Faisal Awada9ed0f382025-08-14 13:21:46 -050039 saveChassisName();
Faisal Awada9864f832025-05-30 12:21:00 -050040 getPSUConfiguration();
Faisal Awada9ed0f382025-08-14 13:21:46 -050041 getSupportedConfiguration();
Faisal Awada9864f832025-05-30 12:21:00 -050042}
43
44void Chassis::getPSUConfiguration()
45{
Faisal Awada9864f832025-05-30 12:21:00 -050046 auto depth = 0;
47
48 try
49 {
50 if (chassisPathUniqueId == invalidObjectPathUniqueId)
51 {
52 lg2::error("Chassis does not have chassis ID: {CHASSISPATH}",
53 "CHASSISPATH", chassisPath);
54 return;
55 }
56 auto connectorsSubTree = getSubTree(bus, "/", IBMCFFPSInterface, depth);
57 for (const auto& [path, services] : connectorsSubTree)
58 {
Faisal Awada9ed0f382025-08-14 13:21:46 -050059 if (chassisPathUniqueId == getParentEMUniqueId(bus, path))
Faisal Awada9864f832025-05-30 12:21:00 -050060 {
61 // For each object in the array of objects, I want
62 // to get properties from the service, path, and
63 // interface.
64 auto properties = getAllProperties(bus, path, IBMCFFPSInterface,
65 entityMgrService);
66 getPSUProperties(properties);
67 }
68 }
69 }
70 catch (const sdbusplus::exception_t& e)
71 {
72 lg2::error("Failed while getting configuration - exception: {ERROR}",
73 "ERROR", e);
74 }
75
76 if (psus.empty())
77 {
78 // Interface or properties not found. Let the Interfaces Added callback
79 // process the information once the interfaces are added to D-Bus.
80 lg2::info("No power supplies to monitor");
81 }
82}
83
84void Chassis::getPSUProperties(util::DbusPropertyMap& properties)
85{
86 std::string basePSUInvPath = chassisPath + "/motherboard/powersupply";
87
88 // From passed in properties, I want to get: I2CBus, I2CAddress,
89 // and Name. Create a power supply object, using Name to build the inventory
90 // path.
91
92 uint64_t* i2cbus = nullptr;
93 uint64_t* i2caddr = nullptr;
94 std::string* psuname = nullptr;
95 std::string* preslineptr = nullptr;
96
97 for (const auto& property : properties)
98 {
99 try
100 {
101 if (property.first == i2cBusProp)
102 {
103 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
104 }
105 else if (property.first == i2cAddressProp)
106 {
107 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
108 }
109 else if (property.first == psuNameProp)
110 {
111 psuname = std::get_if<std::string>(&properties[psuNameProp]);
112 }
113 else if (property.first == presLineName)
114 {
115 preslineptr =
116 std::get_if<std::string>(&properties[presLineName]);
117 }
118 }
119 catch (const std::exception& e)
120 {}
121 }
122
123 if (i2cbus && i2caddr && psuname && !psuname->empty())
124 {
125 std::string invpath = basePSUInvPath;
126 invpath.push_back(psuname->back());
127 std::string presline = "";
128
129 lg2::debug("Inventory Path: {INVPATH}", "INVPATH", invpath);
130
131 if (nullptr != preslineptr)
132 {
133 presline = *preslineptr;
134 }
135
136 auto invMatch =
137 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
138 return psu->getInventoryPath() == invpath;
139 });
140 if (invMatch != psus.end())
141 {
142 // This power supply has the same inventory path as the one with
143 // information just added to D-Bus.
144 // Changes to GPIO line name unlikely, so skip checking.
145 // Changes to the I2C bus and address unlikely, as that would
146 // require corresponding device tree updates.
147 // Return out to avoid duplicate object creation.
148 return;
149 }
150
151 buildDriverName(*i2cbus, *i2caddr);
152 lg2::debug(
153 "make PowerSupply bus: {I2CBUS} addr: {I2CADDR} presline: {PRESLINE}",
154 "I2CBUS", *i2cbus, "I2CADDR", *i2caddr, "PRESLINE", presline);
Faisal Awada9ed0f382025-08-14 13:21:46 -0500155
Faisal Awada9864f832025-05-30 12:21:00 -0500156 auto psu = std::make_unique<PowerSupply>(
157 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
Faisal Awada9ed0f382025-08-14 13:21:46 -0500158 std::bind(&Chassis::isPowerOn, this), chassisShortName);
Faisal Awada9864f832025-05-30 12:21:00 -0500159 psus.emplace_back(std::move(psu));
160
161 // Subscribe to power supply presence changes
162 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
163 bus,
164 sdbusplus::bus::match::rules::propertiesChanged(invpath,
165 INVENTORY_IFACE),
166 [this](auto& msg) { this->psuPresenceChanged(msg); });
167 presenceMatches.emplace_back(std::move(presenceMatch));
168 }
169 if (psus.empty())
170 {
171 lg2::info("No power supplies to monitor");
172 }
173 else
174 {
175 populateDriverName();
176 }
177}
178
179void Chassis::getSupportedConfiguration()
180{
Faisal Awada9ed0f382025-08-14 13:21:46 -0500181 try
182 {
183 util::DbusSubtree subtree =
184 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
185 if (subtree.empty())
186 {
187 throw std::runtime_error("Supported Configuration Not Found");
188 }
189
190 for (const auto& [objPath, services] : subtree)
191 {
192 std::string service = services.begin()->first;
193 if (objPath.empty() || service.empty())
194 {
195 continue;
196 }
197
198 if (chassisPathUniqueId == getParentEMUniqueId(bus, objPath))
199 {
200 auto properties = util::getAllProperties(
201 bus, objPath, supportedConfIntf, service);
202 populateSupportedConfiguration(properties);
203 break;
204 }
205 }
206 }
207 catch (const std::exception& e)
208 {
209 // Interface or property not found. Let the Interfaces Added callback
210 // process the information once the interfaces are added to D-Bus.
211 lg2::info("Interface or Property not found, error {ERROR}", "ERROR", e);
212 }
Faisal Awada9864f832025-05-30 12:21:00 -0500213}
214
215void Chassis::populateSupportedConfiguration(
216 const util::DbusPropertyMap& properties)
217{
218 try
219 {
220 auto propIt = properties.find("SupportedType");
221 if (propIt == properties.end())
222 {
223 return;
224 }
225 const std::string* type = std::get_if<std::string>(&(propIt->second));
226 if ((type == nullptr) || (*type != "PowerSupply"))
227 {
228 return;
229 }
230
231 propIt = properties.find("SupportedModel");
232 if (propIt == properties.end())
233 {
234 return;
235 }
236 const std::string* model = std::get_if<std::string>(&(propIt->second));
237 if (model == nullptr)
238 {
239 return;
240 }
241
242 SupportedPsuConfiguration supportedPsuConfig;
243 propIt = properties.find("RedundantCount");
244 if (propIt != properties.end())
245 {
246 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
247 if (count != nullptr)
248 {
249 supportedPsuConfig.powerSupplyCount = *count;
250 }
251 }
252 propIt = properties.find("InputVoltage");
253 if (propIt != properties.end())
254 {
255 const std::vector<uint64_t>* voltage =
256 std::get_if<std::vector<uint64_t>>(&(propIt->second));
257 if (voltage != nullptr)
258 {
259 supportedPsuConfig.inputVoltage = *voltage;
260 }
261 }
262
263 // The PowerConfigFullLoad is an optional property, default it to false
264 // since that's the default value of the power-config-full-load GPIO.
265 supportedPsuConfig.powerConfigFullLoad = false;
266 propIt = properties.find("PowerConfigFullLoad");
267 if (propIt != properties.end())
268 {
269 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
270 if (fullLoad != nullptr)
271 {
272 supportedPsuConfig.powerConfigFullLoad = *fullLoad;
273 }
274 }
275
276 supportedConfigs.emplace(*model, supportedPsuConfig);
277 }
278 catch (const std::exception& e)
279 {
280 lg2::info("populateSupportedConfiguration error {ERR}", "ERR", e);
281 }
282}
283
284void Chassis::psuPresenceChanged(sdbusplus::message_t& msg)
285{
286 std::string msgSensor;
287 std::map<std::string, std::variant<uint32_t, bool>> msgData;
288 msg.read(msgSensor, msgData);
289
290 // Check if it was the Present property that changed.
291 auto valPropMap = msgData.find(PRESENT_PROP);
292 if (valPropMap != msgData.end())
293 {
294 if (std::get<bool>(valPropMap->second))
295 {
296 // A PSU became present, force the PSU validation to run.
297 runValidateConfig = true;
298 validationTimer->restartOnce(validationTimeout);
299 }
300 }
301}
302
303void Chassis::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
304{
305 namespace fs = std::filesystem;
306 std::stringstream ss;
307 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
308 std::string symLinkPath =
309 deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
310 try
311 {
312 fs::path linkStrPath = fs::read_symlink(symLinkPath);
313 driverName = linkStrPath.filename();
314 }
315 catch (const std::exception& e)
316 {
317 lg2::error(
318 "Failed to find device driver {SYM_LINK_PATH}, error {ERROR_STR}",
319 "SYM_LINK_PATH", symLinkPath, "ERROR_STR", e);
320 }
321}
322
323void Chassis::populateDriverName()
324{
325 std::string driverName;
326 // Search in PSUs for driver name
327 std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
328 if (!psu->getDriverName().empty())
329 {
330 driverName = psu->getDriverName();
331 }
332 });
333 // Assign driver name to all PSUs
334 std::for_each(psus.begin(), psus.end(),
335 [&driverName](auto& psu) { psu->setDriverName(driverName); });
336}
337
Faisal Awada348168b2025-07-08 11:23:02 -0500338uint64_t Chassis::getChassisPathUniqueId(const std::string& path)
Faisal Awada9864f832025-05-30 12:21:00 -0500339{
Faisal Awada9864f832025-05-30 12:21:00 -0500340 try
341 {
Faisal Awada348168b2025-07-08 11:23:02 -0500342 return getChassisInventoryUniqueId(bus, path);
Faisal Awada9864f832025-05-30 12:21:00 -0500343 }
344 catch (const sdbusplus::exception_t& e)
345 {
Faisal Awada348168b2025-07-08 11:23:02 -0500346 lg2::error(
347 "Failed to find chassis path {CHASSIS_PATH} ID - exception: {ERROR}",
348 "CHASSIS_PATH", path, "ERROR", e);
Faisal Awada9864f832025-05-30 12:21:00 -0500349 }
Faisal Awada348168b2025-07-08 11:23:02 -0500350 return invalidObjectPathUniqueId;
Faisal Awada9864f832025-05-30 12:21:00 -0500351}
352
Faisal Awada9ed0f382025-08-14 13:21:46 -0500353void Chassis::initPowerMonitoring()
354{
355 using namespace sdeventplus;
356
357 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
358 eventLoop, std::bind(&Chassis::validateConfig, this));
359 attemptToCreatePowerConfigGPIO();
360
361 // Subscribe to power state changes
362 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
363 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
364 bus,
365 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
366 POWER_IFACE),
367 [this](auto& msg) { this->powerStateChanged(msg); });
Faisal Awada028b03e2025-10-02 15:27:08 -0500368 initialize();
Faisal Awada9ed0f382025-08-14 13:21:46 -0500369}
370
371void Chassis::validateConfig()
372{
373 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
374 {
375 return;
376 }
377
378 for (const auto& psu : psus)
379 {
380 if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
381 {
382 // Do not try to validate if input voltage fault present.
383 validationTimer->restartOnce(validationTimeout);
384 return;
385 }
386 }
387
388 std::map<std::string, std::string> additionalData;
389 auto supported = hasRequiredPSUs(additionalData);
390 if (supported)
391 {
392 runValidateConfig = false;
393 double actualVoltage;
394 int inputVoltage;
395 int previousInputVoltage = 0;
396 bool voltageMismatch = false;
397
398 for (const auto& psu : psus)
399 {
400 if (!psu->isPresent())
401 {
402 // Only present PSUs report a valid input voltage
403 continue;
404 }
405 psu->getInputVoltage(actualVoltage, inputVoltage);
406 if (previousInputVoltage && inputVoltage &&
407 (previousInputVoltage != inputVoltage))
408 {
409 additionalData["EXPECTED_VOLTAGE"] =
410 std::to_string(previousInputVoltage);
411 additionalData["ACTUAL_VOLTAGE"] =
412 std::to_string(actualVoltage);
413 voltageMismatch = true;
414 }
415 if (!previousInputVoltage && inputVoltage)
416 {
417 previousInputVoltage = inputVoltage;
418 }
419 }
420 if (!voltageMismatch)
421 {
422 return;
423 }
424 }
425
426 // Validation failed, create an error log.
427 // Return without setting the runValidateConfig flag to false because
428 // it may be that an additional supported configuration interface is
429 // added and we need to validate it to see if it matches this system.
430 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
431 additionalData);
432}
433
434void Chassis::syncHistory()
435{
436 if (driverName != ACBEL_FSG032_DD_NAME)
437 {
438 if (!syncHistoryGPIO)
439 {
440 try
441 {
442 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
443 }
444 catch (const std::exception& e)
445 {
446 // Not an error, system just hasn't implemented the synch gpio
447 lg2::info("No synchronization GPIO found");
448 syncHistoryGPIO = nullptr;
449 }
450 }
451 if (syncHistoryGPIO)
452 {
453 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
454 lg2::info("Synchronize INPUT_HISTORY");
455 syncHistoryGPIO->toggleLowHigh(delay);
456 lg2::info("Synchronize INPUT_HISTORY completed");
457 }
458 }
459
460 // Always clear synch history required after calling this function
461 for (auto& psu : psus)
462 {
463 psu->clearSyncHistoryRequired();
464 }
465}
466
467void Chassis::analyze()
468{
469 auto syncHistoryRequired =
470 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
471 return psu->isSyncHistoryRequired();
472 });
473 if (syncHistoryRequired)
474 {
475 syncHistory();
476 }
477
478 for (auto& psu : psus)
479 {
480 psu->analyze();
481 }
482
483 analyzeBrownout();
484
485 // Only perform individual PSU analysis if power is on and a brownout has
486 // not already been logged
487 //
488 // Note: TODO Check the chassis state when the power sequencer publishes
489 // chassis power on and system power on
490 if (powerOn && !brownoutLogged)
491 {
492 for (auto& psu : psus)
493 {
494 std::map<std::string, std::string> additionalData;
495
496 if (!psu->isFaultLogged() && !psu->isPresent() &&
497 !validationTimer->isEnabled())
498 {
499 std::map<std::string, std::string> requiredPSUsData;
500 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Faisal Awada9ed0f382025-08-14 13:21:46 -0500501
Faisal Awada028b03e2025-10-02 15:27:08 -0500502 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Faisal Awada9ed0f382025-08-14 13:21:46 -0500503 {
504 additionalData.merge(requiredPSUsData);
505 // Create error for power supply missing.
506 additionalData["CALLOUT_INVENTORY_PATH"] =
507 psu->getInventoryPath();
508 additionalData["CALLOUT_PRIORITY"] = "H";
509 createError(
510 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
511 additionalData);
512 }
513 psu->setFaultLogged();
514 }
515 else if (!psu->isFaultLogged() && psu->isFaulted())
516 {
517 // Add STATUS_WORD and STATUS_MFR last response, in padded
518 // hexadecimal format.
519 additionalData["STATUS_WORD"] =
520 std::format("{:#04x}", psu->getStatusWord());
521 additionalData["STATUS_MFR"] =
522 std::format("{:#02x}", psu->getMFRFault());
523 // If there are faults being reported, they possibly could be
524 // related to a bug in the firmware version running on the power
525 // supply. Capture that data into the error as well.
526 additionalData["FW_VERSION"] = psu->getFWVersion();
527
528 if (psu->hasCommFault())
529 {
530 additionalData["STATUS_CML"] =
531 std::format("{:#02x}", psu->getStatusCML());
532 /* Attempts to communicate with the power supply have
533 * reached there limit. Create an error. */
534 additionalData["CALLOUT_DEVICE_PATH"] =
535 psu->getDevicePath();
536
537 createError(
538 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
539 additionalData);
540
541 psu->setFaultLogged();
542 }
543 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
544 {
545 // Include STATUS_INPUT for input faults.
546 additionalData["STATUS_INPUT"] =
547 std::format("{:#02x}", psu->getStatusInput());
548
549 /* The power supply location might be needed if the input
550 * fault is due to a problem with the power supply itself.
551 * Include the inventory path with a call out priority of
552 * low.
553 */
554 additionalData["CALLOUT_INVENTORY_PATH"] =
555 psu->getInventoryPath();
556 additionalData["CALLOUT_PRIORITY"] = "L";
557 createError("xyz.openbmc_project.Power.PowerSupply.Error."
558 "InputFault",
559 additionalData);
560 psu->setFaultLogged();
561 }
562 else if (psu->hasPSKillFault())
563 {
564 createError(
565 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
566 additionalData);
567 psu->setFaultLogged();
568 }
569 else if (psu->hasVoutOVFault())
570 {
571 // Include STATUS_VOUT for Vout faults.
572 additionalData["STATUS_VOUT"] =
573 std::format("{:#02x}", psu->getStatusVout());
574
575 additionalData["CALLOUT_INVENTORY_PATH"] =
576 psu->getInventoryPath();
577
578 createError(
579 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
580 additionalData);
581
582 psu->setFaultLogged();
583 }
584 else if (psu->hasIoutOCFault())
585 {
586 // Include STATUS_IOUT for Iout faults.
587 additionalData["STATUS_IOUT"] =
588 std::format("{:#02x}", psu->getStatusIout());
589
590 createError(
591 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
592 additionalData);
593
594 psu->setFaultLogged();
595 }
596 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
597 psu->hasPSCS12VFault())
598 {
599 // Include STATUS_VOUT for Vout faults.
600 additionalData["STATUS_VOUT"] =
601 std::format("{:#02x}", psu->getStatusVout());
602
603 additionalData["CALLOUT_INVENTORY_PATH"] =
604 psu->getInventoryPath();
605
606 createError(
607 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
608 additionalData);
609
610 psu->setFaultLogged();
611 }
612 // A fan fault should have priority over a temperature fault,
613 // since a failed fan may lead to a temperature problem.
614 // Only process if not in power fault window.
615 else if (psu->hasFanFault() && !powerFaultOccurring)
616 {
617 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
618 additionalData["STATUS_TEMPERATURE"] =
619 std::format("{:#02x}", psu->getStatusTemperature());
620 additionalData["STATUS_FANS_1_2"] =
621 std::format("{:#02x}", psu->getStatusFans12());
622
623 additionalData["CALLOUT_INVENTORY_PATH"] =
624 psu->getInventoryPath();
625
626 createError(
627 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
628 additionalData);
629
630 psu->setFaultLogged();
631 }
632 else if (psu->hasTempFault())
633 {
634 // Include STATUS_TEMPERATURE for temperature faults.
635 additionalData["STATUS_TEMPERATURE"] =
636 std::format("{:#02x}", psu->getStatusTemperature());
637
638 additionalData["CALLOUT_INVENTORY_PATH"] =
639 psu->getInventoryPath();
640
641 createError(
642 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
643 additionalData);
644
645 psu->setFaultLogged();
646 }
647 else if (psu->hasMFRFault())
648 {
649 /* This can represent a variety of faults that result in
650 * calling out the power supply for replacement: Output
651 * OverCurrent, Output Under Voltage, and potentially other
652 * faults.
653 *
654 * Also plan on putting specific fault in AdditionalData,
655 * along with register names and register values
656 * (STATUS_WORD, STATUS_MFR, etc.).*/
657
658 additionalData["CALLOUT_INVENTORY_PATH"] =
659 psu->getInventoryPath();
660
661 createError(
662 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
663 additionalData);
664
665 psu->setFaultLogged();
666 }
667 // Only process if not in power fault window.
668 else if (psu->hasPgoodFault() && !powerFaultOccurring)
669 {
670 /* POWER_GOOD# is not low, or OFF is on */
671 additionalData["CALLOUT_INVENTORY_PATH"] =
672 psu->getInventoryPath();
673
674 createError(
675 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
676 additionalData);
677
678 psu->setFaultLogged();
679 }
680 }
681 }
682 }
683}
684
685void Chassis::analyzeBrownout()
686{
687 // Count number of power supplies failing
688 size_t presentCount = 0;
689 size_t notPresentCount = 0;
690 size_t acFailedCount = 0;
691 size_t pgoodFailedCount = 0;
692 for (const auto& psu : psus)
693 {
694 if (psu->isPresent())
695 {
696 ++presentCount;
697 if (psu->hasACFault())
698 {
699 ++acFailedCount;
700 }
701 else if (psu->hasPgoodFault())
702 {
703 ++pgoodFailedCount;
704 }
705 }
706 else
707 {
708 ++notPresentCount;
709 }
710 }
711
712 // Only issue brownout failure if chassis pgood has failed, it has not
713 // already been logged, at least one PSU has seen an AC fail, and all
714 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
715 // at least one PSU is present.
716 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
717 (presentCount == (acFailedCount + pgoodFailedCount)))
718 {
719 // Indicate that the system is in a brownout condition by creating an
720 // error log and setting the PowerSystemInputs status property to
721 // Fault.
722 powerSystemInputs.status(
723 sdbusplus::xyz::openbmc_project::State::Decorator::server::
724 PowerSystemInputs::Status::Fault);
725
726 std::map<std::string, std::string> additionalData;
727 additionalData.emplace("NOT_PRESENT_COUNT",
728 std::to_string(notPresentCount));
729 additionalData.emplace("VIN_FAULT_COUNT",
730 std::to_string(acFailedCount));
731 additionalData.emplace("PGOOD_FAULT_COUNT",
732 std::to_string(pgoodFailedCount));
733 lg2::info(
734 "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
735 "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
736 acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
737
738 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
739 additionalData);
740 brownoutLogged = true;
741 }
742 else
743 {
744 // If a brownout was previously logged but at least one PSU is not
745 // currently in AC fault, determine if the brownout condition can be
746 // cleared
747 if (brownoutLogged && (acFailedCount < presentCount))
748 {
749 // TODO Power State
750 }
751 }
752}
753
754void Chassis::createError(const std::string& faultName,
755 std::map<std::string, std::string>& additionalData)
756{
757 using namespace sdbusplus::xyz::openbmc_project;
758 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
759 constexpr auto loggingCreateInterface =
760 "xyz.openbmc_project.Logging.Create";
761
762 try
763 {
764 additionalData["_PID"] = std::to_string(getpid());
765
766 auto service =
767 util::getService(loggingObjectPath, loggingCreateInterface, bus);
768
769 if (service.empty())
770 {
771 lg2::error("Unable to get logging manager service");
772 return;
773 }
774
775 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
776 loggingCreateInterface, "Create");
777
778 auto level = Logging::server::Entry::Level::Error;
779 method.append(faultName, level, additionalData);
780
781 auto reply = bus.call(method);
Faisal Awada028b03e2025-10-02 15:27:08 -0500782 setPowerSupplyError(faultName);
Faisal Awada9ed0f382025-08-14 13:21:46 -0500783 }
784 catch (const std::exception& e)
785 {
786 lg2::error(
787 "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
788 "FAULT_NAME", faultName, "ERROR", e);
789 }
790}
791
792void Chassis::attemptToCreatePowerConfigGPIO()
793{
794 try
795 {
796 powerConfigGPIO = createGPIO("power-config-full-load");
797 }
798 catch (const std::exception& e)
799 {
800 powerConfigGPIO = nullptr;
801 lg2::info("GPIO not implemented in {CHASSIS}", "CHASSIS",
802 chassisShortName);
803 }
804}
805
806void Chassis::supportedConfigurationInterfaceAdded(
807 const util::DbusPropertyMap& properties)
808{
809 populateSupportedConfiguration(properties);
810 updateMissingPSUs();
811}
812
813void Chassis::psuInterfaceAdded(util::DbusPropertyMap& properties)
814{
815 getPSUProperties(properties);
816 updateMissingPSUs();
817}
818
819bool Chassis::hasRequiredPSUs(
820 std::map<std::string, std::string>& additionalData)
821{
Faisal Awada028b03e2025-10-02 15:27:08 -0500822 std::string model{};
823 if (!validateModelName(model, additionalData))
Faisal Awada9ed0f382025-08-14 13:21:46 -0500824 {
Faisal Awada028b03e2025-10-02 15:27:08 -0500825 return false;
Faisal Awada9ed0f382025-08-14 13:21:46 -0500826 }
Faisal Awada9ed0f382025-08-14 13:21:46 -0500827
Faisal Awada028b03e2025-10-02 15:27:08 -0500828 auto presentCount =
829 std::count_if(psus.begin(), psus.end(),
830 [](const auto& psu) { return psu->isPresent(); });
831
832 // Validate the supported configurations. A system may support more than one
833 // power supply model configuration. Since all configurations need to be
834 // checked, the additional data would contain only the information of the
835 // last configuration that did not match.
836 std::map<std::string, std::string> tmpAdditionalData;
837 for (const auto& config : supportedConfigs)
838 {
839 if (config.first != model)
840 {
841 continue;
842 }
843
844 // Number of power supplies present should equal or exceed the expected
845 // count
846 if (presentCount < config.second.powerSupplyCount)
847 {
848 tmpAdditionalData.clear();
849 tmpAdditionalData["EXPECTED_COUNT"] =
850 std::to_string(config.second.powerSupplyCount);
851 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
852 continue;
853 }
854
855 bool voltageValidated = true;
856 for (const auto& psu : psus)
857 {
858 if (!psu->isPresent())
859 {
860 // Only present PSUs report a valid input voltage
861 continue;
862 }
863
864 double actualInputVoltage;
865 int inputVoltage;
866 psu->getInputVoltage(actualInputVoltage, inputVoltage);
867
868 if (std::find(config.second.inputVoltage.begin(),
869 config.second.inputVoltage.end(), inputVoltage) ==
870 config.second.inputVoltage.end())
871 {
872 tmpAdditionalData.clear();
873 tmpAdditionalData["ACTUAL_VOLTAGE"] =
874 std::to_string(actualInputVoltage);
875 for (const auto& voltage : config.second.inputVoltage)
876 {
877 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
878 std::to_string(voltage) + " ";
879 }
880 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
881 psu->getInventoryPath();
882
883 voltageValidated = false;
884 break;
885 }
886 }
887 if (!voltageValidated)
888 {
889 continue;
890 }
891
892 return true;
893 }
894
895 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
896 return false;
Faisal Awada9ed0f382025-08-14 13:21:46 -0500897}
898
899void Chassis::updateMissingPSUs()
900{
901 if (supportedConfigs.empty() || psus.empty())
902 {
903 return;
904 }
905
906 // Power supplies default to missing. If the power supply is present,
907 // the PowerSupply object will update the inventory Present property to
908 // true. If we have less than the required number of power supplies, and
909 // this power supply is missing, update the inventory Present property
910 // to false to indicate required power supply is missing. Avoid
911 // indicating power supply missing if not required.
912
913 auto presentCount =
914 std::count_if(psus.begin(), psus.end(),
915 [](const auto& psu) { return psu->isPresent(); });
916
917 for (const auto& config : supportedConfigs)
918 {
919 for (const auto& psu : psus)
920 {
921 auto psuModel = psu->getModelName();
922 auto psuShortName = psu->getShortName();
923 auto psuInventoryPath = psu->getInventoryPath();
924 auto relativeInvPath =
925 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
926 auto psuPresent = psu->isPresent();
927 auto presProperty = false;
928 auto propReadFail = false;
929
930 try
931 {
932 presProperty = getPresence(bus, psuInventoryPath);
933 propReadFail = false;
934 }
935 catch (const sdbusplus::exception_t& e)
936 {
937 propReadFail = true;
938 // Relying on property change or interface added to retry.
939 // Log an informational trace to the journal.
940 lg2::info(
941 "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
942 "PSU_INVENTORY_PATH", psuInventoryPath);
943 }
944
945 if (psuModel.empty())
946 {
947 if (!propReadFail && (presProperty != psuPresent))
948 {
949 // We already have this property, and it is not false
950 // set Present to false
951 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
952 }
953 continue;
954 }
955
956 if (config.first != psuModel)
957 {
958 continue;
959 }
960
961 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
962 {
963 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
964 }
965 }
966 }
967}
968
Faisal Awada028b03e2025-10-02 15:27:08 -0500969void Chassis::initialize()
970{
971 try
972 {
973 // pgood is the latest read of the chassis pgood
974 int pgood = 0;
975 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
976 powerService, bus, pgood);
977
978 // state is the latest requested power on / off transition
979 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
980 POWER_IFACE, "getPowerState");
981 auto reply = bus.call(method);
982 int state = 0;
983 reply.read(state);
984
985 if (state)
986 {
987 // Monitor PSUs anytime state is on
988 powerOn = true;
989 // In the power fault window if pgood is off
990 powerFaultOccurring = !pgood;
991 validationTimer->restartOnce(validationTimeout);
992 }
993 else
994 {
995 // Power is off
996 powerOn = false;
997 powerFaultOccurring = false;
998 runValidateConfig = true;
999 }
1000 }
1001 catch (const std::exception& e)
1002 {
1003 lg2::info(
1004 "Failed to get power state, assuming it is off, error {ERROR}",
1005 "ERROR", e);
1006 powerOn = false;
1007 powerFaultOccurring = false;
1008 runValidateConfig = true;
1009 }
1010
1011 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
1012 clearFaults();
1013 updateMissingPSUs();
1014 setPowerConfigGPIO();
1015
1016 lg2::info(
1017 "initialize: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
1018 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
1019}
1020
1021void Chassis::setPowerSupplyError(const std::string& psuErrorString)
1022{
1023 using namespace sdbusplus::xyz::openbmc_project;
1024 constexpr auto method = "setPowerSupplyError";
1025
1026 try
1027 {
1028 // Call D-Bus method to inform pseq of PSU error
1029 auto methodMsg = bus.new_method_call(
1030 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
1031 methodMsg.append(psuErrorString);
1032 auto callReply = bus.call(methodMsg);
1033 }
1034 catch (const std::exception& e)
1035 {
1036 lg2::info("Failed calling setPowerSupplyError due to error {ERROR}",
1037 "ERROR", e);
1038 }
1039}
1040
1041void Chassis::setPowerConfigGPIO()
1042{
1043 if (!powerConfigGPIO)
1044 {
1045 return;
1046 }
1047
1048 std::string model{};
1049 std::map<std::string, std::string> additionalData;
1050 if (!validateModelName(model, additionalData))
1051 {
1052 return;
1053 }
1054
1055 auto config = supportedConfigs.find(model);
1056 if (config != supportedConfigs.end())
1057 {
1058 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1059 // if the supported configuration indicates that this system model
1060 // expects the maximum number of power supplies (full load set to true).
1061 // Else, set it to high (1), this is the default.
1062 auto powerConfigValue =
1063 (config->second.powerConfigFullLoad == true ? 0 : 1);
1064 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1065 powerConfigGPIO->write(powerConfigValue, flags);
1066 }
1067}
1068
Faisal Awada9ed0f382025-08-14 13:21:46 -05001069void Chassis::powerStateChanged(sdbusplus::message_t& msg)
1070{
1071 std::string msgSensor;
1072 std::map<std::string, std::variant<int>> msgData;
1073 msg.read(msgSensor, msgData);
1074
1075 // Check if it was the state property that changed.
1076 auto valPropMap = msgData.find("state");
1077 if (valPropMap != msgData.end())
1078 {
1079 int state = std::get<int>(valPropMap->second);
1080 if (state)
1081 {
1082 // Power on requested
1083 powerOn = true;
1084 powerFaultOccurring = false;
1085 validationTimer->restartOnce(validationTimeout);
Faisal Awada9ed0f382025-08-14 13:21:46 -05001086
Faisal Awada028b03e2025-10-02 15:27:08 -05001087 clearFaults();
Faisal Awada9ed0f382025-08-14 13:21:46 -05001088 syncHistory();
Faisal Awada028b03e2025-10-02 15:27:08 -05001089 setPowerConfigGPIO();
Faisal Awada9ed0f382025-08-14 13:21:46 -05001090 setInputVoltageRating();
1091 }
1092 else
1093 {
1094 // Power off requested
1095 powerOn = false;
1096 powerFaultOccurring = false;
1097 runValidateConfig = true;
1098 }
1099 }
1100
1101 // Check if it was the pgood property that changed.
1102 valPropMap = msgData.find("pgood");
1103 if (valPropMap != msgData.end())
1104 {
1105 int pgood = std::get<int>(valPropMap->second);
1106 if (!pgood)
1107 {
1108 // Chassis power good has turned off
1109 if (powerOn)
1110 {
1111 // pgood is off but state is on, in power fault window
1112 powerFaultOccurring = true;
1113 }
1114 }
1115 }
1116 lg2::info(
1117 "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
1118 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
1119}
1120
Faisal Awada028b03e2025-10-02 15:27:08 -05001121bool Chassis::validateModelName(
1122 std::string& model, std::map<std::string, std::string>& additionalData)
1123{
1124 // Check that all PSUs have the same model name. Initialize the model
1125 // variable with the first PSU name found, then use it as a base to compare
1126 // against the rest of the PSUs and get its inventory path to use as callout
1127 // if needed.
1128 model.clear();
1129 std::string modelInventoryPath{};
1130 for (const auto& psu : psus)
1131 {
1132 auto psuModel = psu->getModelName();
1133 if (psuModel.empty())
1134 {
1135 continue;
1136 }
1137 if (model.empty())
1138 {
1139 model = psuModel;
1140 modelInventoryPath = psu->getInventoryPath();
1141 continue;
1142 }
1143 if (psuModel != model)
1144 {
1145 if (supportedConfigs.find(model) != supportedConfigs.end())
1146 {
1147 // The base model is supported, callout the mismatched PSU. The
1148 // mismatched PSU may or may not be supported.
1149 additionalData["EXPECTED_MODEL"] = model;
1150 additionalData["ACTUAL_MODEL"] = psuModel;
1151 additionalData["CALLOUT_INVENTORY_PATH"] =
1152 psu->getInventoryPath();
1153 }
1154 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1155 {
1156 // The base model is not supported, but the mismatched PSU is,
1157 // callout the base PSU.
1158 additionalData["EXPECTED_MODEL"] = psuModel;
1159 additionalData["ACTUAL_MODEL"] = model;
1160 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1161 }
1162 else
1163 {
1164 // The base model and the mismatched PSU are not supported or
1165 // could not be found in the supported configuration, callout
1166 // the mismatched PSU.
1167 additionalData["EXPECTED_MODEL"] = model;
1168 additionalData["ACTUAL_MODEL"] = psuModel;
1169 additionalData["CALLOUT_INVENTORY_PATH"] =
1170 psu->getInventoryPath();
1171 }
1172 model.clear();
1173 return false;
1174 }
1175 }
1176 return true;
1177}
1178
1179bool Chassis::isRequiredPSU(const PowerSupply& psu)
1180{
1181 // Get required number of PSUs; if not found, we don't know if PSU required
1182 unsigned int requiredCount = getRequiredPSUCount();
1183 if (requiredCount == 0)
1184 {
1185 return false;
1186 }
1187
1188 // If total PSU count <= the required count, all PSUs are required
1189 if (psus.size() <= requiredCount)
1190 {
1191 return true;
1192 }
1193
1194 // We don't currently get information from EntityManager about which PSUs
1195 // are required, so we have to do some guesswork. First check if this PSU
1196 // is present. If so, assume it is required.
1197 if (psu.isPresent())
1198 {
1199 return true;
1200 }
1201
1202 // This PSU is not present. Count the number of other PSUs that are
1203 // present. If enough other PSUs are present, assume the specified PSU is
1204 // not required.
1205 unsigned int psuCount =
1206 std::count_if(psus.begin(), psus.end(),
1207 [](const auto& psu) { return psu->isPresent(); });
1208 if (psuCount >= requiredCount)
1209 {
1210 return false;
1211 }
1212
1213 // Check if this PSU was previously present. If so, assume it is required.
1214 // We know it was previously present if it has a non-empty model name.
1215 if (!psu.getModelName().empty())
1216 {
1217 return true;
1218 }
1219
1220 // This PSU was never present. Count the number of other PSUs that were
1221 // previously present. If including those PSUs is enough, assume the
1222 // specified PSU is not required.
1223 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1224 return (!psu->isPresent() && !psu->getModelName().empty());
1225 });
1226 if (psuCount >= requiredCount)
1227 {
1228 return false;
1229 }
1230
1231 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1232 // that were never present. PSU inventory paths typically end with the PSU
1233 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1234 std::vector<std::string> sortedPaths;
1235 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1236 if (!psu->isPresent() && psu->getModelName().empty())
1237 {
1238 sortedPaths.push_back(psu->getInventoryPath());
1239 }
1240 });
1241 std::sort(sortedPaths.begin(), sortedPaths.end());
1242
1243 // Check if specified PSU is close enough to start of list to be required
1244 for (const auto& path : sortedPaths)
1245 {
1246 if (path == psu.getInventoryPath())
1247 {
1248 return true;
1249 }
1250 if (++psuCount >= requiredCount)
1251 {
1252 break;
1253 }
1254 }
1255
1256 // PSU was not close to start of sorted list; assume not required
1257 return false;
1258}
1259
Faisal Awada9864f832025-05-30 12:21:00 -05001260} // namespace phosphor::power::chassis