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