xref: /openbmc/phosphor-power/phosphor-power-supply/chassis.cpp (revision 9ed0f38e1d59564106c6020b854416ece6753776)
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