xref: /openbmc/phosphor-power/phosphor-power-supply/chassis.cpp (revision 028b03ee38fbd32a8e8cb1fa83dc347b6e344c12)
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     initialize();
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 
502                 if (!requiredPSUsPresent && isRequiredPSU(*psu))
503                 {
504                     additionalData.merge(requiredPSUsData);
505                     // Create error for power supply missing.
506                     additionalData["CALLOUT_INVENTORY_PATH"] =
507                         psu->getInventoryPath();
508                     additionalData["CALLOUT_PRIORITY"] = "H";
509                     createError(
510                         "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
511                         additionalData);
512                 }
513                 psu->setFaultLogged();
514             }
515             else if (!psu->isFaultLogged() && psu->isFaulted())
516             {
517                 // Add STATUS_WORD and STATUS_MFR last response, in padded
518                 // hexadecimal format.
519                 additionalData["STATUS_WORD"] =
520                     std::format("{:#04x}", psu->getStatusWord());
521                 additionalData["STATUS_MFR"] =
522                     std::format("{:#02x}", psu->getMFRFault());
523                 // If there are faults being reported, they possibly could be
524                 // related to a bug in the firmware version running on the power
525                 // supply. Capture that data into the error as well.
526                 additionalData["FW_VERSION"] = psu->getFWVersion();
527 
528                 if (psu->hasCommFault())
529                 {
530                     additionalData["STATUS_CML"] =
531                         std::format("{:#02x}", psu->getStatusCML());
532                     /* Attempts to communicate with the power supply have
533                      * reached there limit. Create an error. */
534                     additionalData["CALLOUT_DEVICE_PATH"] =
535                         psu->getDevicePath();
536 
537                     createError(
538                         "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
539                         additionalData);
540 
541                     psu->setFaultLogged();
542                 }
543                 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
544                 {
545                     // Include STATUS_INPUT for input faults.
546                     additionalData["STATUS_INPUT"] =
547                         std::format("{:#02x}", psu->getStatusInput());
548 
549                     /* The power supply location might be needed if the input
550                      * fault is due to a problem with the power supply itself.
551                      * Include the inventory path with a call out priority of
552                      * low.
553                      */
554                     additionalData["CALLOUT_INVENTORY_PATH"] =
555                         psu->getInventoryPath();
556                     additionalData["CALLOUT_PRIORITY"] = "L";
557                     createError("xyz.openbmc_project.Power.PowerSupply.Error."
558                                 "InputFault",
559                                 additionalData);
560                     psu->setFaultLogged();
561                 }
562                 else if (psu->hasPSKillFault())
563                 {
564                     createError(
565                         "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
566                         additionalData);
567                     psu->setFaultLogged();
568                 }
569                 else if (psu->hasVoutOVFault())
570                 {
571                     // Include STATUS_VOUT for Vout faults.
572                     additionalData["STATUS_VOUT"] =
573                         std::format("{:#02x}", psu->getStatusVout());
574 
575                     additionalData["CALLOUT_INVENTORY_PATH"] =
576                         psu->getInventoryPath();
577 
578                     createError(
579                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
580                         additionalData);
581 
582                     psu->setFaultLogged();
583                 }
584                 else if (psu->hasIoutOCFault())
585                 {
586                     // Include STATUS_IOUT for Iout faults.
587                     additionalData["STATUS_IOUT"] =
588                         std::format("{:#02x}", psu->getStatusIout());
589 
590                     createError(
591                         "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
592                         additionalData);
593 
594                     psu->setFaultLogged();
595                 }
596                 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
597                          psu->hasPSCS12VFault())
598                 {
599                     // Include STATUS_VOUT for Vout faults.
600                     additionalData["STATUS_VOUT"] =
601                         std::format("{:#02x}", psu->getStatusVout());
602 
603                     additionalData["CALLOUT_INVENTORY_PATH"] =
604                         psu->getInventoryPath();
605 
606                     createError(
607                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
608                         additionalData);
609 
610                     psu->setFaultLogged();
611                 }
612                 // A fan fault should have priority over a temperature fault,
613                 // since a failed fan may lead to a temperature problem.
614                 // Only process if not in power fault window.
615                 else if (psu->hasFanFault() && !powerFaultOccurring)
616                 {
617                     // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
618                     additionalData["STATUS_TEMPERATURE"] =
619                         std::format("{:#02x}", psu->getStatusTemperature());
620                     additionalData["STATUS_FANS_1_2"] =
621                         std::format("{:#02x}", psu->getStatusFans12());
622 
623                     additionalData["CALLOUT_INVENTORY_PATH"] =
624                         psu->getInventoryPath();
625 
626                     createError(
627                         "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
628                         additionalData);
629 
630                     psu->setFaultLogged();
631                 }
632                 else if (psu->hasTempFault())
633                 {
634                     // Include STATUS_TEMPERATURE for temperature faults.
635                     additionalData["STATUS_TEMPERATURE"] =
636                         std::format("{:#02x}", psu->getStatusTemperature());
637 
638                     additionalData["CALLOUT_INVENTORY_PATH"] =
639                         psu->getInventoryPath();
640 
641                     createError(
642                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
643                         additionalData);
644 
645                     psu->setFaultLogged();
646                 }
647                 else if (psu->hasMFRFault())
648                 {
649                     /* This can represent a variety of faults that result in
650                      * calling out the power supply for replacement: Output
651                      * OverCurrent, Output Under Voltage, and potentially other
652                      * faults.
653                      *
654                      * Also plan on putting specific fault in AdditionalData,
655                      * along with register names and register values
656                      * (STATUS_WORD, STATUS_MFR, etc.).*/
657 
658                     additionalData["CALLOUT_INVENTORY_PATH"] =
659                         psu->getInventoryPath();
660 
661                     createError(
662                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
663                         additionalData);
664 
665                     psu->setFaultLogged();
666                 }
667                 // Only process if not in power fault window.
668                 else if (psu->hasPgoodFault() && !powerFaultOccurring)
669                 {
670                     /* POWER_GOOD# is not low, or OFF is on */
671                     additionalData["CALLOUT_INVENTORY_PATH"] =
672                         psu->getInventoryPath();
673 
674                     createError(
675                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
676                         additionalData);
677 
678                     psu->setFaultLogged();
679                 }
680             }
681         }
682     }
683 }
684 
analyzeBrownout()685 void Chassis::analyzeBrownout()
686 {
687     // Count number of power supplies failing
688     size_t presentCount = 0;
689     size_t notPresentCount = 0;
690     size_t acFailedCount = 0;
691     size_t pgoodFailedCount = 0;
692     for (const auto& psu : psus)
693     {
694         if (psu->isPresent())
695         {
696             ++presentCount;
697             if (psu->hasACFault())
698             {
699                 ++acFailedCount;
700             }
701             else if (psu->hasPgoodFault())
702             {
703                 ++pgoodFailedCount;
704             }
705         }
706         else
707         {
708             ++notPresentCount;
709         }
710     }
711 
712     // Only issue brownout failure if chassis pgood has failed, it has not
713     // already been logged, at least one PSU has seen an AC fail, and all
714     // present PSUs have an AC or pgood failure. Note an AC fail is only set if
715     // at least one PSU is present.
716     if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
717         (presentCount == (acFailedCount + pgoodFailedCount)))
718     {
719         //  Indicate that the system is in a brownout condition by creating an
720         //  error log and setting the PowerSystemInputs status property to
721         //  Fault.
722         powerSystemInputs.status(
723             sdbusplus::xyz::openbmc_project::State::Decorator::server::
724                 PowerSystemInputs::Status::Fault);
725 
726         std::map<std::string, std::string> additionalData;
727         additionalData.emplace("NOT_PRESENT_COUNT",
728                                std::to_string(notPresentCount));
729         additionalData.emplace("VIN_FAULT_COUNT",
730                                std::to_string(acFailedCount));
731         additionalData.emplace("PGOOD_FAULT_COUNT",
732                                std::to_string(pgoodFailedCount));
733         lg2::info(
734             "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
735             "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
736             acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
737 
738         createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
739                     additionalData);
740         brownoutLogged = true;
741     }
742     else
743     {
744         // If a brownout was previously logged but at least one PSU is not
745         // currently in AC fault, determine if the brownout condition can be
746         // cleared
747         if (brownoutLogged && (acFailedCount < presentCount))
748         {
749             // TODO Power State
750         }
751     }
752 }
753 
createError(const std::string & faultName,std::map<std::string,std::string> & additionalData)754 void Chassis::createError(const std::string& faultName,
755                           std::map<std::string, std::string>& additionalData)
756 {
757     using namespace sdbusplus::xyz::openbmc_project;
758     constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
759     constexpr auto loggingCreateInterface =
760         "xyz.openbmc_project.Logging.Create";
761 
762     try
763     {
764         additionalData["_PID"] = std::to_string(getpid());
765 
766         auto service =
767             util::getService(loggingObjectPath, loggingCreateInterface, bus);
768 
769         if (service.empty())
770         {
771             lg2::error("Unable to get logging manager service");
772             return;
773         }
774 
775         auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
776                                           loggingCreateInterface, "Create");
777 
778         auto level = Logging::server::Entry::Level::Error;
779         method.append(faultName, level, additionalData);
780 
781         auto reply = bus.call(method);
782         setPowerSupplyError(faultName);
783     }
784     catch (const std::exception& e)
785     {
786         lg2::error(
787             "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
788             "FAULT_NAME", faultName, "ERROR", e);
789     }
790 }
791 
attemptToCreatePowerConfigGPIO()792 void Chassis::attemptToCreatePowerConfigGPIO()
793 {
794     try
795     {
796         powerConfigGPIO = createGPIO("power-config-full-load");
797     }
798     catch (const std::exception& e)
799     {
800         powerConfigGPIO = nullptr;
801         lg2::info("GPIO not implemented in {CHASSIS}", "CHASSIS",
802                   chassisShortName);
803     }
804 }
805 
supportedConfigurationInterfaceAdded(const util::DbusPropertyMap & properties)806 void Chassis::supportedConfigurationInterfaceAdded(
807     const util::DbusPropertyMap& properties)
808 {
809     populateSupportedConfiguration(properties);
810     updateMissingPSUs();
811 }
812 
psuInterfaceAdded(util::DbusPropertyMap & properties)813 void Chassis::psuInterfaceAdded(util::DbusPropertyMap& properties)
814 {
815     getPSUProperties(properties);
816     updateMissingPSUs();
817 }
818 
hasRequiredPSUs(std::map<std::string,std::string> & additionalData)819 bool Chassis::hasRequiredPSUs(
820     std::map<std::string, std::string>& additionalData)
821 {
822     std::string model{};
823     if (!validateModelName(model, additionalData))
824     {
825         return false;
826     }
827 
828     auto presentCount =
829         std::count_if(psus.begin(), psus.end(),
830                       [](const auto& psu) { return psu->isPresent(); });
831 
832     // Validate the supported configurations. A system may support more than one
833     // power supply model configuration. Since all configurations need to be
834     // checked, the additional data would contain only the information of the
835     // last configuration that did not match.
836     std::map<std::string, std::string> tmpAdditionalData;
837     for (const auto& config : supportedConfigs)
838     {
839         if (config.first != model)
840         {
841             continue;
842         }
843 
844         // Number of power supplies present should equal or exceed the expected
845         // count
846         if (presentCount < config.second.powerSupplyCount)
847         {
848             tmpAdditionalData.clear();
849             tmpAdditionalData["EXPECTED_COUNT"] =
850                 std::to_string(config.second.powerSupplyCount);
851             tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
852             continue;
853         }
854 
855         bool voltageValidated = true;
856         for (const auto& psu : psus)
857         {
858             if (!psu->isPresent())
859             {
860                 // Only present PSUs report a valid input voltage
861                 continue;
862             }
863 
864             double actualInputVoltage;
865             int inputVoltage;
866             psu->getInputVoltage(actualInputVoltage, inputVoltage);
867 
868             if (std::find(config.second.inputVoltage.begin(),
869                           config.second.inputVoltage.end(), inputVoltage) ==
870                 config.second.inputVoltage.end())
871             {
872                 tmpAdditionalData.clear();
873                 tmpAdditionalData["ACTUAL_VOLTAGE"] =
874                     std::to_string(actualInputVoltage);
875                 for (const auto& voltage : config.second.inputVoltage)
876                 {
877                     tmpAdditionalData["EXPECTED_VOLTAGE"] +=
878                         std::to_string(voltage) + " ";
879                 }
880                 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
881                     psu->getInventoryPath();
882 
883                 voltageValidated = false;
884                 break;
885             }
886         }
887         if (!voltageValidated)
888         {
889             continue;
890         }
891 
892         return true;
893     }
894 
895     additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
896     return false;
897 }
898 
updateMissingPSUs()899 void Chassis::updateMissingPSUs()
900 {
901     if (supportedConfigs.empty() || psus.empty())
902     {
903         return;
904     }
905 
906     // Power supplies default to missing. If the power supply is present,
907     // the PowerSupply object will update the inventory Present property to
908     // true. If we have less than the required number of power supplies, and
909     // this power supply is missing, update the inventory Present property
910     // to false to indicate required power supply is missing. Avoid
911     // indicating power supply missing if not required.
912 
913     auto presentCount =
914         std::count_if(psus.begin(), psus.end(),
915                       [](const auto& psu) { return psu->isPresent(); });
916 
917     for (const auto& config : supportedConfigs)
918     {
919         for (const auto& psu : psus)
920         {
921             auto psuModel = psu->getModelName();
922             auto psuShortName = psu->getShortName();
923             auto psuInventoryPath = psu->getInventoryPath();
924             auto relativeInvPath =
925                 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
926             auto psuPresent = psu->isPresent();
927             auto presProperty = false;
928             auto propReadFail = false;
929 
930             try
931             {
932                 presProperty = getPresence(bus, psuInventoryPath);
933                 propReadFail = false;
934             }
935             catch (const sdbusplus::exception_t& e)
936             {
937                 propReadFail = true;
938                 // Relying on property change or interface added to retry.
939                 // Log an informational trace to the journal.
940                 lg2::info(
941                     "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
942                     "PSU_INVENTORY_PATH", psuInventoryPath);
943             }
944 
945             if (psuModel.empty())
946             {
947                 if (!propReadFail && (presProperty != psuPresent))
948                 {
949                     // We already have this property, and it is not false
950                     // set Present to false
951                     setPresence(bus, relativeInvPath, psuPresent, psuShortName);
952                 }
953                 continue;
954             }
955 
956             if (config.first != psuModel)
957             {
958                 continue;
959             }
960 
961             if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
962             {
963                 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
964             }
965         }
966     }
967 }
968 
initialize()969 void Chassis::initialize()
970 {
971     try
972     {
973         // pgood is the latest read of the chassis pgood
974         int pgood = 0;
975         util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
976                                powerService, bus, pgood);
977 
978         // state is the latest requested power on / off transition
979         auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
980                                           POWER_IFACE, "getPowerState");
981         auto reply = bus.call(method);
982         int state = 0;
983         reply.read(state);
984 
985         if (state)
986         {
987             // Monitor PSUs anytime state is on
988             powerOn = true;
989             // In the power fault window if pgood is off
990             powerFaultOccurring = !pgood;
991             validationTimer->restartOnce(validationTimeout);
992         }
993         else
994         {
995             // Power is off
996             powerOn = false;
997             powerFaultOccurring = false;
998             runValidateConfig = true;
999         }
1000     }
1001     catch (const std::exception& e)
1002     {
1003         lg2::info(
1004             "Failed to get power state, assuming it is off, error {ERROR}",
1005             "ERROR", e);
1006         powerOn = false;
1007         powerFaultOccurring = false;
1008         runValidateConfig = true;
1009     }
1010 
1011     onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
1012     clearFaults();
1013     updateMissingPSUs();
1014     setPowerConfigGPIO();
1015 
1016     lg2::info(
1017         "initialize: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
1018         "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
1019 }
1020 
setPowerSupplyError(const std::string & psuErrorString)1021 void Chassis::setPowerSupplyError(const std::string& psuErrorString)
1022 {
1023     using namespace sdbusplus::xyz::openbmc_project;
1024     constexpr auto method = "setPowerSupplyError";
1025 
1026     try
1027     {
1028         // Call D-Bus method to inform pseq of PSU error
1029         auto methodMsg = bus.new_method_call(
1030             powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
1031         methodMsg.append(psuErrorString);
1032         auto callReply = bus.call(methodMsg);
1033     }
1034     catch (const std::exception& e)
1035     {
1036         lg2::info("Failed calling setPowerSupplyError due to error {ERROR}",
1037                   "ERROR", e);
1038     }
1039 }
1040 
setPowerConfigGPIO()1041 void Chassis::setPowerConfigGPIO()
1042 {
1043     if (!powerConfigGPIO)
1044     {
1045         return;
1046     }
1047 
1048     std::string model{};
1049     std::map<std::string, std::string> additionalData;
1050     if (!validateModelName(model, additionalData))
1051     {
1052         return;
1053     }
1054 
1055     auto config = supportedConfigs.find(model);
1056     if (config != supportedConfigs.end())
1057     {
1058         // The power-config-full-load is an open drain GPIO. Set it to low (0)
1059         // if the supported configuration indicates that this system model
1060         // expects the maximum number of power supplies (full load set to true).
1061         // Else, set it to high (1), this is the default.
1062         auto powerConfigValue =
1063             (config->second.powerConfigFullLoad == true ? 0 : 1);
1064         auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1065         powerConfigGPIO->write(powerConfigValue, flags);
1066     }
1067 }
1068 
powerStateChanged(sdbusplus::message_t & msg)1069 void Chassis::powerStateChanged(sdbusplus::message_t& msg)
1070 {
1071     std::string msgSensor;
1072     std::map<std::string, std::variant<int>> msgData;
1073     msg.read(msgSensor, msgData);
1074 
1075     // Check if it was the state property that changed.
1076     auto valPropMap = msgData.find("state");
1077     if (valPropMap != msgData.end())
1078     {
1079         int state = std::get<int>(valPropMap->second);
1080         if (state)
1081         {
1082             // Power on requested
1083             powerOn = true;
1084             powerFaultOccurring = false;
1085             validationTimer->restartOnce(validationTimeout);
1086 
1087             clearFaults();
1088             syncHistory();
1089             setPowerConfigGPIO();
1090             setInputVoltageRating();
1091         }
1092         else
1093         {
1094             // Power off requested
1095             powerOn = false;
1096             powerFaultOccurring = false;
1097             runValidateConfig = true;
1098         }
1099     }
1100 
1101     // Check if it was the pgood property that changed.
1102     valPropMap = msgData.find("pgood");
1103     if (valPropMap != msgData.end())
1104     {
1105         int pgood = std::get<int>(valPropMap->second);
1106         if (!pgood)
1107         {
1108             // Chassis power good has turned off
1109             if (powerOn)
1110             {
1111                 // pgood is off but state is on, in power fault window
1112                 powerFaultOccurring = true;
1113             }
1114         }
1115     }
1116     lg2::info(
1117         "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
1118         "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
1119 }
1120 
validateModelName(std::string & model,std::map<std::string,std::string> & additionalData)1121 bool Chassis::validateModelName(
1122     std::string& model, std::map<std::string, std::string>& additionalData)
1123 {
1124     // Check that all PSUs have the same model name. Initialize the model
1125     // variable with the first PSU name found, then use it as a base to compare
1126     // against the rest of the PSUs and get its inventory path to use as callout
1127     // if needed.
1128     model.clear();
1129     std::string modelInventoryPath{};
1130     for (const auto& psu : psus)
1131     {
1132         auto psuModel = psu->getModelName();
1133         if (psuModel.empty())
1134         {
1135             continue;
1136         }
1137         if (model.empty())
1138         {
1139             model = psuModel;
1140             modelInventoryPath = psu->getInventoryPath();
1141             continue;
1142         }
1143         if (psuModel != model)
1144         {
1145             if (supportedConfigs.find(model) != supportedConfigs.end())
1146             {
1147                 // The base model is supported, callout the mismatched PSU. The
1148                 // mismatched PSU may or may not be supported.
1149                 additionalData["EXPECTED_MODEL"] = model;
1150                 additionalData["ACTUAL_MODEL"] = psuModel;
1151                 additionalData["CALLOUT_INVENTORY_PATH"] =
1152                     psu->getInventoryPath();
1153             }
1154             else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1155             {
1156                 // The base model is not supported, but the mismatched PSU is,
1157                 // callout the base PSU.
1158                 additionalData["EXPECTED_MODEL"] = psuModel;
1159                 additionalData["ACTUAL_MODEL"] = model;
1160                 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1161             }
1162             else
1163             {
1164                 // The base model and the mismatched PSU are not supported or
1165                 // could not be found in the supported configuration, callout
1166                 // the mismatched PSU.
1167                 additionalData["EXPECTED_MODEL"] = model;
1168                 additionalData["ACTUAL_MODEL"] = psuModel;
1169                 additionalData["CALLOUT_INVENTORY_PATH"] =
1170                     psu->getInventoryPath();
1171             }
1172             model.clear();
1173             return false;
1174         }
1175     }
1176     return true;
1177 }
1178 
isRequiredPSU(const PowerSupply & psu)1179 bool Chassis::isRequiredPSU(const PowerSupply& psu)
1180 {
1181     // Get required number of PSUs; if not found, we don't know if PSU required
1182     unsigned int requiredCount = getRequiredPSUCount();
1183     if (requiredCount == 0)
1184     {
1185         return false;
1186     }
1187 
1188     // If total PSU count <= the required count, all PSUs are required
1189     if (psus.size() <= requiredCount)
1190     {
1191         return true;
1192     }
1193 
1194     // We don't currently get information from EntityManager about which PSUs
1195     // are required, so we have to do some guesswork.  First check if this PSU
1196     // is present.  If so, assume it is required.
1197     if (psu.isPresent())
1198     {
1199         return true;
1200     }
1201 
1202     // This PSU is not present.  Count the number of other PSUs that are
1203     // present.  If enough other PSUs are present, assume the specified PSU is
1204     // not required.
1205     unsigned int psuCount =
1206         std::count_if(psus.begin(), psus.end(),
1207                       [](const auto& psu) { return psu->isPresent(); });
1208     if (psuCount >= requiredCount)
1209     {
1210         return false;
1211     }
1212 
1213     // Check if this PSU was previously present.  If so, assume it is required.
1214     // We know it was previously present if it has a non-empty model name.
1215     if (!psu.getModelName().empty())
1216     {
1217         return true;
1218     }
1219 
1220     // This PSU was never present.  Count the number of other PSUs that were
1221     // previously present.  If including those PSUs is enough, assume the
1222     // specified PSU is not required.
1223     psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1224         return (!psu->isPresent() && !psu->getModelName().empty());
1225     });
1226     if (psuCount >= requiredCount)
1227     {
1228         return false;
1229     }
1230 
1231     // We still haven't found enough PSUs.  Sort the inventory paths of PSUs
1232     // that were never present.  PSU inventory paths typically end with the PSU
1233     // number (0, 1, 2, ...).  Assume that lower-numbered PSUs are required.
1234     std::vector<std::string> sortedPaths;
1235     std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1236         if (!psu->isPresent() && psu->getModelName().empty())
1237         {
1238             sortedPaths.push_back(psu->getInventoryPath());
1239         }
1240     });
1241     std::sort(sortedPaths.begin(), sortedPaths.end());
1242 
1243     // Check if specified PSU is close enough to start of list to be required
1244     for (const auto& path : sortedPaths)
1245     {
1246         if (path == psu.getInventoryPath())
1247         {
1248             return true;
1249         }
1250         if (++psuCount >= requiredCount)
1251         {
1252             break;
1253         }
1254     }
1255 
1256     // PSU was not close to start of sorted list; assume not required
1257     return false;
1258 }
1259 
1260 } // namespace phosphor::power::chassis
1261