1 #include "config.h"
2
3 #include "chassis.hpp"
4
5 #include <iostream>
6
7 using namespace phosphor::logging;
8 using namespace phosphor::power::util;
9 namespace phosphor::power::chassis
10 {
11
12 constexpr auto powerSystemsInputsObjPath =
13 "/xyz/openbmc_project/power/power_supplies/chassis{}/psus";
14 constexpr auto IBMCFFPSInterface =
15 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
16 constexpr auto chassisIdProp = "SlotNumber";
17 constexpr auto i2cBusProp = "I2CBus";
18 constexpr auto i2cAddressProp = "I2CAddress";
19 constexpr auto psuNameProp = "Name";
20 constexpr auto presLineName = "NamedPresenceGpio";
21 constexpr auto supportedConfIntf =
22 "xyz.openbmc_project.Configuration.SupportedConfiguration";
23 const auto deviceDirPath = "/sys/bus/i2c/devices/";
24 const auto driverDirName = "/driver";
25
26 const auto entityMgrService = "xyz.openbmc_project.EntityManager";
27 const auto decoratorChassisId = "xyz.openbmc_project.Inventory.Decorator.Slot";
28
29 constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
30
Chassis(sdbusplus::bus_t & bus,const std::string & chassisPath,const sdeventplus::Event & e)31 Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
32 const sdeventplus::Event& e) :
33 bus(bus), chassisPath(chassisPath),
34 chassisPathUniqueId(getChassisPathUniqueId(chassisPath)),
35 powerSystemInputs(
36 bus, std::format(powerSystemsInputsObjPath, chassisPathUniqueId)),
37 eventLoop(e)
38 {
39 saveChassisName();
40 getPSUConfiguration();
41 getSupportedConfiguration();
42 }
43
getPSUConfiguration()44 void Chassis::getPSUConfiguration()
45 {
46 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 {
59 if (chassisPathUniqueId == getParentEMUniqueId(bus, path))
60 {
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
getPSUProperties(util::DbusPropertyMap & properties)84 void 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);
155
156 auto psu = std::make_unique<PowerSupply>(
157 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
158 std::bind(&Chassis::isPowerOn, this), chassisShortName);
159 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
getSupportedConfiguration()179 void Chassis::getSupportedConfiguration()
180 {
181 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 }
213 }
214
populateSupportedConfiguration(const util::DbusPropertyMap & properties)215 void 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
psuPresenceChanged(sdbusplus::message_t & msg)284 void 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
buildDriverName(uint64_t i2cbus,uint64_t i2caddr)303 void 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
populateDriverName()323 void 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
getChassisPathUniqueId(const std::string & path)338 uint64_t Chassis::getChassisPathUniqueId(const std::string& path)
339 {
340 try
341 {
342 return getChassisInventoryUniqueId(bus, path);
343 }
344 catch (const sdbusplus::exception_t& e)
345 {
346 lg2::error(
347 "Failed to find chassis path {CHASSIS_PATH} ID - exception: {ERROR}",
348 "CHASSIS_PATH", path, "ERROR", e);
349 }
350 return invalidObjectPathUniqueId;
351 }
352
initPowerMonitoring()353 void 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); });
368 // TODO initialize the chassis
369 }
370
validateConfig()371 void 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
syncHistory()434 void 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
analyze()467 void 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);
501 // TODO check required PSU
502
503 if (!requiredPSUsPresent)
504 {
505 additionalData.merge(requiredPSUsData);
506 // Create error for power supply missing.
507 additionalData["CALLOUT_INVENTORY_PATH"] =
508 psu->getInventoryPath();
509 additionalData["CALLOUT_PRIORITY"] = "H";
510 createError(
511 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
512 additionalData);
513 }
514 psu->setFaultLogged();
515 }
516 else if (!psu->isFaultLogged() && psu->isFaulted())
517 {
518 // Add STATUS_WORD and STATUS_MFR last response, in padded
519 // hexadecimal format.
520 additionalData["STATUS_WORD"] =
521 std::format("{:#04x}", psu->getStatusWord());
522 additionalData["STATUS_MFR"] =
523 std::format("{:#02x}", psu->getMFRFault());
524 // If there are faults being reported, they possibly could be
525 // related to a bug in the firmware version running on the power
526 // supply. Capture that data into the error as well.
527 additionalData["FW_VERSION"] = psu->getFWVersion();
528
529 if (psu->hasCommFault())
530 {
531 additionalData["STATUS_CML"] =
532 std::format("{:#02x}", psu->getStatusCML());
533 /* Attempts to communicate with the power supply have
534 * reached there limit. Create an error. */
535 additionalData["CALLOUT_DEVICE_PATH"] =
536 psu->getDevicePath();
537
538 createError(
539 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
540 additionalData);
541
542 psu->setFaultLogged();
543 }
544 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
545 {
546 // Include STATUS_INPUT for input faults.
547 additionalData["STATUS_INPUT"] =
548 std::format("{:#02x}", psu->getStatusInput());
549
550 /* The power supply location might be needed if the input
551 * fault is due to a problem with the power supply itself.
552 * Include the inventory path with a call out priority of
553 * low.
554 */
555 additionalData["CALLOUT_INVENTORY_PATH"] =
556 psu->getInventoryPath();
557 additionalData["CALLOUT_PRIORITY"] = "L";
558 createError("xyz.openbmc_project.Power.PowerSupply.Error."
559 "InputFault",
560 additionalData);
561 psu->setFaultLogged();
562 }
563 else if (psu->hasPSKillFault())
564 {
565 createError(
566 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
567 additionalData);
568 psu->setFaultLogged();
569 }
570 else if (psu->hasVoutOVFault())
571 {
572 // Include STATUS_VOUT for Vout faults.
573 additionalData["STATUS_VOUT"] =
574 std::format("{:#02x}", psu->getStatusVout());
575
576 additionalData["CALLOUT_INVENTORY_PATH"] =
577 psu->getInventoryPath();
578
579 createError(
580 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
581 additionalData);
582
583 psu->setFaultLogged();
584 }
585 else if (psu->hasIoutOCFault())
586 {
587 // Include STATUS_IOUT for Iout faults.
588 additionalData["STATUS_IOUT"] =
589 std::format("{:#02x}", psu->getStatusIout());
590
591 createError(
592 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
593 additionalData);
594
595 psu->setFaultLogged();
596 }
597 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
598 psu->hasPSCS12VFault())
599 {
600 // Include STATUS_VOUT for Vout faults.
601 additionalData["STATUS_VOUT"] =
602 std::format("{:#02x}", psu->getStatusVout());
603
604 additionalData["CALLOUT_INVENTORY_PATH"] =
605 psu->getInventoryPath();
606
607 createError(
608 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
609 additionalData);
610
611 psu->setFaultLogged();
612 }
613 // A fan fault should have priority over a temperature fault,
614 // since a failed fan may lead to a temperature problem.
615 // Only process if not in power fault window.
616 else if (psu->hasFanFault() && !powerFaultOccurring)
617 {
618 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
619 additionalData["STATUS_TEMPERATURE"] =
620 std::format("{:#02x}", psu->getStatusTemperature());
621 additionalData["STATUS_FANS_1_2"] =
622 std::format("{:#02x}", psu->getStatusFans12());
623
624 additionalData["CALLOUT_INVENTORY_PATH"] =
625 psu->getInventoryPath();
626
627 createError(
628 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
629 additionalData);
630
631 psu->setFaultLogged();
632 }
633 else if (psu->hasTempFault())
634 {
635 // Include STATUS_TEMPERATURE for temperature faults.
636 additionalData["STATUS_TEMPERATURE"] =
637 std::format("{:#02x}", psu->getStatusTemperature());
638
639 additionalData["CALLOUT_INVENTORY_PATH"] =
640 psu->getInventoryPath();
641
642 createError(
643 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
644 additionalData);
645
646 psu->setFaultLogged();
647 }
648 else if (psu->hasMFRFault())
649 {
650 /* This can represent a variety of faults that result in
651 * calling out the power supply for replacement: Output
652 * OverCurrent, Output Under Voltage, and potentially other
653 * faults.
654 *
655 * Also plan on putting specific fault in AdditionalData,
656 * along with register names and register values
657 * (STATUS_WORD, STATUS_MFR, etc.).*/
658
659 additionalData["CALLOUT_INVENTORY_PATH"] =
660 psu->getInventoryPath();
661
662 createError(
663 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
664 additionalData);
665
666 psu->setFaultLogged();
667 }
668 // Only process if not in power fault window.
669 else if (psu->hasPgoodFault() && !powerFaultOccurring)
670 {
671 /* POWER_GOOD# is not low, or OFF is on */
672 additionalData["CALLOUT_INVENTORY_PATH"] =
673 psu->getInventoryPath();
674
675 createError(
676 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
677 additionalData);
678
679 psu->setFaultLogged();
680 }
681 }
682 }
683 }
684 }
685
analyzeBrownout()686 void Chassis::analyzeBrownout()
687 {
688 // Count number of power supplies failing
689 size_t presentCount = 0;
690 size_t notPresentCount = 0;
691 size_t acFailedCount = 0;
692 size_t pgoodFailedCount = 0;
693 for (const auto& psu : psus)
694 {
695 if (psu->isPresent())
696 {
697 ++presentCount;
698 if (psu->hasACFault())
699 {
700 ++acFailedCount;
701 }
702 else if (psu->hasPgoodFault())
703 {
704 ++pgoodFailedCount;
705 }
706 }
707 else
708 {
709 ++notPresentCount;
710 }
711 }
712
713 // Only issue brownout failure if chassis pgood has failed, it has not
714 // already been logged, at least one PSU has seen an AC fail, and all
715 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
716 // at least one PSU is present.
717 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
718 (presentCount == (acFailedCount + pgoodFailedCount)))
719 {
720 // Indicate that the system is in a brownout condition by creating an
721 // error log and setting the PowerSystemInputs status property to
722 // Fault.
723 powerSystemInputs.status(
724 sdbusplus::xyz::openbmc_project::State::Decorator::server::
725 PowerSystemInputs::Status::Fault);
726
727 std::map<std::string, std::string> additionalData;
728 additionalData.emplace("NOT_PRESENT_COUNT",
729 std::to_string(notPresentCount));
730 additionalData.emplace("VIN_FAULT_COUNT",
731 std::to_string(acFailedCount));
732 additionalData.emplace("PGOOD_FAULT_COUNT",
733 std::to_string(pgoodFailedCount));
734 lg2::info(
735 "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
736 "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
737 acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
738
739 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
740 additionalData);
741 brownoutLogged = true;
742 }
743 else
744 {
745 // If a brownout was previously logged but at least one PSU is not
746 // currently in AC fault, determine if the brownout condition can be
747 // cleared
748 if (brownoutLogged && (acFailedCount < presentCount))
749 {
750 // TODO Power State
751 }
752 }
753 }
754
createError(const std::string & faultName,std::map<std::string,std::string> & additionalData)755 void Chassis::createError(const std::string& faultName,
756 std::map<std::string, std::string>& additionalData)
757 {
758 using namespace sdbusplus::xyz::openbmc_project;
759 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
760 constexpr auto loggingCreateInterface =
761 "xyz.openbmc_project.Logging.Create";
762
763 try
764 {
765 additionalData["_PID"] = std::to_string(getpid());
766
767 auto service =
768 util::getService(loggingObjectPath, loggingCreateInterface, bus);
769
770 if (service.empty())
771 {
772 lg2::error("Unable to get logging manager service");
773 return;
774 }
775
776 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
777 loggingCreateInterface, "Create");
778
779 auto level = Logging::server::Entry::Level::Error;
780 method.append(faultName, level, additionalData);
781
782 auto reply = bus.call(method);
783 // TODO set power supply error
784 }
785 catch (const std::exception& e)
786 {
787 lg2::error(
788 "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
789 "FAULT_NAME", faultName, "ERROR", e);
790 }
791 }
792
attemptToCreatePowerConfigGPIO()793 void Chassis::attemptToCreatePowerConfigGPIO()
794 {
795 try
796 {
797 powerConfigGPIO = createGPIO("power-config-full-load");
798 }
799 catch (const std::exception& e)
800 {
801 powerConfigGPIO = nullptr;
802 lg2::info("GPIO not implemented in {CHASSIS}", "CHASSIS",
803 chassisShortName);
804 }
805 }
806
supportedConfigurationInterfaceAdded(const util::DbusPropertyMap & properties)807 void Chassis::supportedConfigurationInterfaceAdded(
808 const util::DbusPropertyMap& properties)
809 {
810 populateSupportedConfiguration(properties);
811 updateMissingPSUs();
812 }
813
psuInterfaceAdded(util::DbusPropertyMap & properties)814 void Chassis::psuInterfaceAdded(util::DbusPropertyMap& properties)
815 {
816 getPSUProperties(properties);
817 updateMissingPSUs();
818 }
819
hasRequiredPSUs(std::map<std::string,std::string> & additionalData)820 bool Chassis::hasRequiredPSUs(
821 std::map<std::string, std::string>& additionalData)
822 {
823 // ignore the following loop so code will compile
824 for (const auto& pair : additionalData)
825 {
826 std::cout << "Key = " << pair.first
827 << " additionalData value = " << pair.second << "\n";
828 }
829 return true;
830
831 // TODO validate having the required PSUs
832 }
833
updateMissingPSUs()834 void Chassis::updateMissingPSUs()
835 {
836 if (supportedConfigs.empty() || psus.empty())
837 {
838 return;
839 }
840
841 // Power supplies default to missing. If the power supply is present,
842 // the PowerSupply object will update the inventory Present property to
843 // true. If we have less than the required number of power supplies, and
844 // this power supply is missing, update the inventory Present property
845 // to false to indicate required power supply is missing. Avoid
846 // indicating power supply missing if not required.
847
848 auto presentCount =
849 std::count_if(psus.begin(), psus.end(),
850 [](const auto& psu) { return psu->isPresent(); });
851
852 for (const auto& config : supportedConfigs)
853 {
854 for (const auto& psu : psus)
855 {
856 auto psuModel = psu->getModelName();
857 auto psuShortName = psu->getShortName();
858 auto psuInventoryPath = psu->getInventoryPath();
859 auto relativeInvPath =
860 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
861 auto psuPresent = psu->isPresent();
862 auto presProperty = false;
863 auto propReadFail = false;
864
865 try
866 {
867 presProperty = getPresence(bus, psuInventoryPath);
868 propReadFail = false;
869 }
870 catch (const sdbusplus::exception_t& e)
871 {
872 propReadFail = true;
873 // Relying on property change or interface added to retry.
874 // Log an informational trace to the journal.
875 lg2::info(
876 "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
877 "PSU_INVENTORY_PATH", psuInventoryPath);
878 }
879
880 if (psuModel.empty())
881 {
882 if (!propReadFail && (presProperty != psuPresent))
883 {
884 // We already have this property, and it is not false
885 // set Present to false
886 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
887 }
888 continue;
889 }
890
891 if (config.first != psuModel)
892 {
893 continue;
894 }
895
896 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
897 {
898 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
899 }
900 }
901 }
902 }
903
powerStateChanged(sdbusplus::message_t & msg)904 void Chassis::powerStateChanged(sdbusplus::message_t& msg)
905 {
906 std::string msgSensor;
907 std::map<std::string, std::variant<int>> msgData;
908 msg.read(msgSensor, msgData);
909
910 // Check if it was the state property that changed.
911 auto valPropMap = msgData.find("state");
912 if (valPropMap != msgData.end())
913 {
914 int state = std::get<int>(valPropMap->second);
915 if (state)
916 {
917 // Power on requested
918 powerOn = true;
919 powerFaultOccurring = false;
920 validationTimer->restartOnce(validationTimeout);
921 // TODO clear faults
922
923 syncHistory();
924 // TODO set power config
925
926 setInputVoltageRating();
927 }
928 else
929 {
930 // Power off requested
931 powerOn = false;
932 powerFaultOccurring = false;
933 runValidateConfig = true;
934 }
935 }
936
937 // Check if it was the pgood property that changed.
938 valPropMap = msgData.find("pgood");
939 if (valPropMap != msgData.end())
940 {
941 int pgood = std::get<int>(valPropMap->second);
942 if (!pgood)
943 {
944 // Chassis power good has turned off
945 if (powerOn)
946 {
947 // pgood is off but state is on, in power fault window
948 powerFaultOccurring = true;
949 }
950 }
951 }
952 lg2::info(
953 "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
954 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
955 }
956
957 } // namespace phosphor::power::chassis
958