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