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