1 #include "psu_manager.hpp"
2 
3 #include "utility.hpp"
4 
5 #include <fmt/format.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 
9 #include <regex>
10 
11 using namespace phosphor::logging;
12 
13 namespace phosphor::power::manager
14 {
15 constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
16 constexpr auto objectManagerObjPath =
17     "/xyz/openbmc_project/power/power_supplies";
18 constexpr auto powerSystemsInputsObjPath =
19     "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
20 
21 constexpr auto IBMCFFPSInterface =
22     "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
23 constexpr auto i2cBusProp = "I2CBus";
24 constexpr auto i2cAddressProp = "I2CAddress";
25 constexpr auto psuNameProp = "Name";
26 constexpr auto presLineName = "NamedPresenceGpio";
27 
28 constexpr auto supportedConfIntf =
29     "xyz.openbmc_project.Configuration.SupportedConfiguration";
30 
31 PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
32     bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
33     objectManager(bus, objectManagerObjPath)
34 {
35     // Subscribe to InterfacesAdded before doing a property read, otherwise
36     // the interface could be created after the read attempt but before the
37     // match is created.
38     entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
39         bus,
40         sdbusplus::bus::match::rules::interfacesAdded() +
41             sdbusplus::bus::match::rules::sender(
42                 "xyz.openbmc_project.EntityManager"),
43         std::bind(&PSUManager::entityManagerIfaceAdded, this,
44                   std::placeholders::_1));
45     getPSUConfiguration();
46     getSystemProperties();
47 
48     // Request the bus name before the analyze() function, which is the one that
49     // determines the brownout condition and sets the status d-bus property.
50     bus.request_name(managerBusName);
51 
52     using namespace sdeventplus;
53     auto interval = std::chrono::milliseconds(1000);
54     timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
55         e, std::bind(&PSUManager::analyze, this), interval);
56 
57     validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
58         e, std::bind(&PSUManager::validateConfig, this));
59 
60     try
61     {
62         powerConfigGPIO = createGPIO("power-config-full-load");
63     }
64     catch (const std::exception& e)
65     {
66         // Ignore error, GPIO may not be implemented in this system.
67         powerConfigGPIO = nullptr;
68     }
69 
70     // Subscribe to power state changes
71     powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
72     powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
73         bus,
74         sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
75                                                         POWER_IFACE),
76         [this](auto& msg) { this->powerStateChanged(msg); });
77 
78     initialize();
79 }
80 
81 void PSUManager::getPSUConfiguration()
82 {
83     using namespace phosphor::power::util;
84     auto depth = 0;
85     auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
86 
87     psus.clear();
88 
89     // I should get a map of objects back.
90     // Each object will have a path, a service, and an interface.
91     // The interface should match the one passed into this function.
92     for (const auto& [path, services] : objects)
93     {
94         auto service = services.begin()->first;
95 
96         if (path.empty() || service.empty())
97         {
98             continue;
99         }
100 
101         // For each object in the array of objects, I want to get properties
102         // from the service, path, and interface.
103         auto properties =
104             getAllProperties(bus, path, IBMCFFPSInterface, service);
105 
106         getPSUProperties(properties);
107     }
108 
109     if (psus.empty())
110     {
111         // Interface or properties not found. Let the Interfaces Added callback
112         // process the information once the interfaces are added to D-Bus.
113         log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
114     }
115 }
116 
117 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
118 {
119     // From passed in properties, I want to get: I2CBus, I2CAddress,
120     // and Name. Create a power supply object, using Name to build the inventory
121     // path.
122     const auto basePSUInvPath =
123         "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
124     uint64_t* i2cbus = nullptr;
125     uint64_t* i2caddr = nullptr;
126     std::string* psuname = nullptr;
127     std::string* preslineptr = nullptr;
128 
129     for (const auto& property : properties)
130     {
131         try
132         {
133             if (property.first == i2cBusProp)
134             {
135                 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
136             }
137             else if (property.first == i2cAddressProp)
138             {
139                 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
140             }
141             else if (property.first == psuNameProp)
142             {
143                 psuname = std::get_if<std::string>(&properties[psuNameProp]);
144             }
145             else if (property.first == presLineName)
146             {
147                 preslineptr =
148                     std::get_if<std::string>(&properties[presLineName]);
149             }
150         }
151         catch (const std::exception& e)
152         {}
153     }
154 
155     if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
156     {
157         std::string invpath = basePSUInvPath;
158         invpath.push_back(psuname->back());
159         std::string presline = "";
160 
161         log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
162 
163         if (nullptr != preslineptr)
164         {
165             presline = *preslineptr;
166         }
167 
168         auto invMatch =
169             std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
170                 return psu->getInventoryPath() == invpath;
171             });
172         if (invMatch != psus.end())
173         {
174             // This power supply has the same inventory path as the one with
175             // information just added to D-Bus.
176             // Changes to GPIO line name unlikely, so skip checking.
177             // Changes to the I2C bus and address unlikely, as that would
178             // require corresponding device tree updates.
179             // Return out to avoid duplicate object creation.
180             return;
181         }
182 
183         log<level::DEBUG>(
184             fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
185                         *i2cbus, *i2caddr, presline)
186                 .c_str());
187         auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
188                                                  *i2caddr, presline);
189         psus.emplace_back(std::move(psu));
190 
191         // Subscribe to power supply presence changes
192         auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
193             bus,
194             sdbusplus::bus::match::rules::propertiesChanged(invpath,
195                                                             INVENTORY_IFACE),
196             [this](auto& msg) { this->presenceChanged(msg); });
197         presenceMatches.emplace_back(std::move(presenceMatch));
198     }
199 
200     if (psus.empty())
201     {
202         log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
203     }
204 }
205 
206 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
207 {
208     try
209     {
210         auto propIt = properties.find("SupportedType");
211         if (propIt == properties.end())
212         {
213             return;
214         }
215         const std::string* type = std::get_if<std::string>(&(propIt->second));
216         if ((type == nullptr) || (*type != "PowerSupply"))
217         {
218             return;
219         }
220 
221         propIt = properties.find("SupportedModel");
222         if (propIt == properties.end())
223         {
224             return;
225         }
226         const std::string* model = std::get_if<std::string>(&(propIt->second));
227         if (model == nullptr)
228         {
229             return;
230         }
231 
232         sys_properties sys;
233         propIt = properties.find("RedundantCount");
234         if (propIt != properties.end())
235         {
236             const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
237             if (count != nullptr)
238             {
239                 sys.powerSupplyCount = *count;
240             }
241         }
242         propIt = properties.find("InputVoltage");
243         if (propIt != properties.end())
244         {
245             const std::vector<uint64_t>* voltage =
246                 std::get_if<std::vector<uint64_t>>(&(propIt->second));
247             if (voltage != nullptr)
248             {
249                 sys.inputVoltage = *voltage;
250             }
251         }
252 
253         // The PowerConfigFullLoad is an optional property, default it to false
254         // since that's the default value of the power-config-full-load GPIO.
255         sys.powerConfigFullLoad = false;
256         propIt = properties.find("PowerConfigFullLoad");
257         if (propIt != properties.end())
258         {
259             const bool* fullLoad = std::get_if<bool>(&(propIt->second));
260             if (fullLoad != nullptr)
261             {
262                 sys.powerConfigFullLoad = *fullLoad;
263             }
264         }
265 
266         supportedConfigs.emplace(*model, sys);
267     }
268     catch (const std::exception& e)
269     {}
270 }
271 
272 void PSUManager::getSystemProperties()
273 {
274 
275     try
276     {
277         util::DbusSubtree subtree =
278             util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
279         if (subtree.empty())
280         {
281             throw std::runtime_error("Supported Configuration Not Found");
282         }
283 
284         for (const auto& [objPath, services] : subtree)
285         {
286             std::string service = services.begin()->first;
287             if (objPath.empty() || service.empty())
288             {
289                 continue;
290             }
291             auto properties = util::getAllProperties(
292                 bus, objPath, supportedConfIntf, service);
293             populateSysProperties(properties);
294         }
295     }
296     catch (const std::exception& e)
297     {
298         // Interface or property not found. Let the Interfaces Added callback
299         // process the information once the interfaces are added to D-Bus.
300     }
301 }
302 
303 void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
304 {
305     try
306     {
307         sdbusplus::message::object_path objPath;
308         std::map<std::string, std::map<std::string, util::DbusVariant>>
309             interfaces;
310         msg.read(objPath, interfaces);
311 
312         auto itIntf = interfaces.find(supportedConfIntf);
313         if (itIntf != interfaces.cend())
314         {
315             populateSysProperties(itIntf->second);
316         }
317 
318         itIntf = interfaces.find(IBMCFFPSInterface);
319         if (itIntf != interfaces.cend())
320         {
321             log<level::INFO>(
322                 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
323                     .c_str());
324             getPSUProperties(itIntf->second);
325         }
326 
327         // Call to validate the psu configuration if the power is on and both
328         // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
329         // processed
330         if (powerOn && !psus.empty() && !supportedConfigs.empty())
331         {
332             validationTimer->restartOnce(validationTimeout);
333         }
334     }
335     catch (const std::exception& e)
336     {
337         // Ignore, the property may be of a different type than expected.
338     }
339 }
340 
341 void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
342 {
343     int32_t state = 0;
344     std::string msgSensor;
345     std::map<std::string, std::variant<int32_t>> msgData;
346     msg.read(msgSensor, msgData);
347 
348     // Check if it was the Present property that changed.
349     auto valPropMap = msgData.find("state");
350     if (valPropMap != msgData.end())
351     {
352         state = std::get<int32_t>(valPropMap->second);
353 
354         // Power is on when state=1. Clear faults.
355         if (state)
356         {
357             powerOn = true;
358             validationTimer->restartOnce(validationTimeout);
359             clearFaults();
360             setPowerConfigGPIO();
361         }
362         else
363         {
364             powerOn = false;
365             runValidateConfig = true;
366         }
367     }
368 }
369 
370 void PSUManager::presenceChanged(sdbusplus::message::message& msg)
371 {
372     std::string msgSensor;
373     std::map<std::string, std::variant<uint32_t, bool>> msgData;
374     msg.read(msgSensor, msgData);
375 
376     // Check if it was the Present property that changed.
377     auto valPropMap = msgData.find(PRESENT_PROP);
378     if (valPropMap != msgData.end())
379     {
380         if (std::get<bool>(valPropMap->second))
381         {
382             // A PSU became present, force the PSU validation to run.
383             runValidateConfig = true;
384             validationTimer->restartOnce(validationTimeout);
385         }
386     }
387 }
388 
389 void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
390 {
391     using namespace sdbusplus::xyz::openbmc_project;
392     constexpr auto service = "org.openbmc.control.Power";
393     constexpr auto objPath = "/org/openbmc/control/power0";
394     constexpr auto interface = "org.openbmc.control.Power";
395     constexpr auto method = "setPowerSupplyError";
396 
397     try
398     {
399         // Call D-Bus method to inform pseq of PSU error
400         auto methodMsg =
401             bus.new_method_call(service, objPath, interface, method);
402         methodMsg.append(psuErrorString);
403         auto callReply = bus.call(methodMsg);
404     }
405     catch (const std::exception& e)
406     {
407         log<level::INFO>(
408             fmt::format("Failed calling setPowerSupplyError due to error {}",
409                         e.what())
410                 .c_str());
411     }
412 }
413 
414 void PSUManager::createError(const std::string& faultName,
415                              std::map<std::string, std::string>& additionalData)
416 {
417     using namespace sdbusplus::xyz::openbmc_project;
418     constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
419     constexpr auto loggingCreateInterface =
420         "xyz.openbmc_project.Logging.Create";
421 
422     try
423     {
424         additionalData["_PID"] = std::to_string(getpid());
425 
426         auto service =
427             util::getService(loggingObjectPath, loggingCreateInterface, bus);
428 
429         if (service.empty())
430         {
431             log<level::ERR>("Unable to get logging manager service");
432             return;
433         }
434 
435         auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
436                                           loggingCreateInterface, "Create");
437 
438         auto level = Logging::server::Entry::Level::Error;
439         method.append(faultName, level, additionalData);
440 
441         auto reply = bus.call(method);
442         setPowerSupplyError(faultName);
443     }
444     catch (const std::exception& e)
445     {
446         log<level::ERR>(
447             fmt::format(
448                 "Failed creating event log for fault {} due to error {}",
449                 faultName, e.what())
450                 .c_str());
451     }
452 }
453 
454 void PSUManager::analyze()
455 {
456     for (auto& psu : psus)
457     {
458         psu->analyze();
459     }
460 
461     std::map<std::string, std::string> additionalData;
462 
463     auto notPresentCount = decltype(psus.size())(
464         std::count_if(psus.begin(), psus.end(),
465                       [](const auto& psu) { return !psu->isPresent(); }));
466 
467     auto hasVINUVFaultCount = decltype(psus.size())(
468         std::count_if(psus.begin(), psus.end(),
469                       [](const auto& psu) { return psu->hasVINUVFault(); }));
470 
471     // The PSU D-Bus objects may not be available yet, so ignore if all
472     // PSUs are not present or the number of PSUs is still 0.
473     if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) &&
474         (psus.size() != notPresentCount) && (psus.size() != 0))
475     {
476         // Brownout: All PSUs report an AC failure: At least one PSU reports
477         // AC loss VIN fault and the rest either report AC loss VIN fault as
478         // well or are not present.
479         additionalData["NOT_PRESENT_COUNT"] = std::to_string(notPresentCount);
480         additionalData["VIN_FAULT_COUNT"] = std::to_string(hasVINUVFaultCount);
481         setBrownout(additionalData);
482     }
483     else
484     {
485         // Brownout condition is not present or has been cleared
486         clearBrownout();
487     }
488 
489     if (powerOn)
490     {
491         for (auto& psu : psus)
492         {
493             additionalData.clear();
494 
495             if (!psu->isFaultLogged() && !psu->isPresent())
496             {
497                 std::map<std::string, std::string> requiredPSUsData;
498                 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
499                 if (!requiredPSUsPresent)
500                 {
501                     additionalData.merge(requiredPSUsData);
502                     // Create error for power supply missing.
503                     additionalData["CALLOUT_INVENTORY_PATH"] =
504                         psu->getInventoryPath();
505                     additionalData["CALLOUT_PRIORITY"] = "H";
506                     createError(
507                         "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
508                         additionalData);
509                 }
510                 psu->setFaultLogged();
511             }
512             else if (!psu->isFaultLogged() && psu->isFaulted())
513             {
514                 // Add STATUS_WORD and STATUS_MFR last response, in padded
515                 // hexadecimal format.
516                 additionalData["STATUS_WORD"] =
517                     fmt::format("{:#04x}", psu->getStatusWord());
518                 additionalData["STATUS_MFR"] =
519                     fmt::format("{:#02x}", psu->getMFRFault());
520                 // If there are faults being reported, they possibly could be
521                 // related to a bug in the firmware version running on the power
522                 // supply. Capture that data into the error as well.
523                 additionalData["FW_VERSION"] = psu->getFWVersion();
524 
525                 if (psu->hasCommFault())
526                 {
527                     additionalData["STATUS_CML"] =
528                         fmt::format("{:#02x}", psu->getStatusCML());
529                     /* Attempts to communicate with the power supply have
530                      * reached there limit. Create an error. */
531                     additionalData["CALLOUT_DEVICE_PATH"] =
532                         psu->getDevicePath();
533 
534                     createError(
535                         "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
536                         additionalData);
537 
538                     psu->setFaultLogged();
539                 }
540                 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
541                 {
542                     // Include STATUS_INPUT for input faults.
543                     additionalData["STATUS_INPUT"] =
544                         fmt::format("{:#02x}", psu->getStatusInput());
545 
546                     /* The power supply location might be needed if the input
547                      * fault is due to a problem with the power supply itself.
548                      * Include the inventory path with a call out priority of
549                      * low.
550                      */
551                     additionalData["CALLOUT_INVENTORY_PATH"] =
552                         psu->getInventoryPath();
553                     additionalData["CALLOUT_PRIORITY"] = "L";
554                     createError("xyz.openbmc_project.Power.PowerSupply.Error."
555                                 "InputFault",
556                                 additionalData);
557                     psu->setFaultLogged();
558                 }
559                 else if (psu->hasPSKillFault())
560                 {
561                     createError(
562                         "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
563                         additionalData);
564                     psu->setFaultLogged();
565                 }
566                 else if (psu->hasVoutOVFault())
567                 {
568                     // Include STATUS_VOUT for Vout faults.
569                     additionalData["STATUS_VOUT"] =
570                         fmt::format("{:#02x}", psu->getStatusVout());
571 
572                     additionalData["CALLOUT_INVENTORY_PATH"] =
573                         psu->getInventoryPath();
574 
575                     createError(
576                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
577                         additionalData);
578 
579                     psu->setFaultLogged();
580                 }
581                 else if (psu->hasIoutOCFault())
582                 {
583                     // Include STATUS_IOUT for Iout faults.
584                     additionalData["STATUS_IOUT"] =
585                         fmt::format("{:#02x}", psu->getStatusIout());
586 
587                     createError(
588                         "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
589                         additionalData);
590 
591                     psu->setFaultLogged();
592                 }
593                 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
594                          psu->hasPSCS12VFault())
595                 {
596                     // Include STATUS_VOUT for Vout faults.
597                     additionalData["STATUS_VOUT"] =
598                         fmt::format("{:#02x}", psu->getStatusVout());
599 
600                     additionalData["CALLOUT_INVENTORY_PATH"] =
601                         psu->getInventoryPath();
602 
603                     createError(
604                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
605                         additionalData);
606 
607                     psu->setFaultLogged();
608                 }
609                 // A fan fault should have priority over a temperature fault,
610                 // since a failed fan may lead to a temperature problem.
611                 else if (psu->hasFanFault())
612                 {
613                     // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
614                     additionalData["STATUS_TEMPERATURE"] =
615                         fmt::format("{:#02x}", psu->getStatusTemperature());
616                     additionalData["STATUS_FANS_1_2"] =
617                         fmt::format("{:#02x}", psu->getStatusFans12());
618 
619                     additionalData["CALLOUT_INVENTORY_PATH"] =
620                         psu->getInventoryPath();
621 
622                     createError(
623                         "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
624                         additionalData);
625 
626                     psu->setFaultLogged();
627                 }
628                 else if (psu->hasTempFault())
629                 {
630                     // Include STATUS_TEMPERATURE for temperature faults.
631                     additionalData["STATUS_TEMPERATURE"] =
632                         fmt::format("{:#02x}", psu->getStatusTemperature());
633 
634                     additionalData["CALLOUT_INVENTORY_PATH"] =
635                         psu->getInventoryPath();
636 
637                     createError(
638                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
639                         additionalData);
640 
641                     psu->setFaultLogged();
642                 }
643                 else if (psu->hasMFRFault())
644                 {
645                     /* This can represent a variety of faults that result in
646                      * calling out the power supply for replacement: Output
647                      * OverCurrent, Output Under Voltage, and potentially other
648                      * faults.
649                      *
650                      * Also plan on putting specific fault in AdditionalData,
651                      * along with register names and register values
652                      * (STATUS_WORD, STATUS_MFR, etc.).*/
653 
654                     additionalData["CALLOUT_INVENTORY_PATH"] =
655                         psu->getInventoryPath();
656 
657                     createError(
658                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
659                         additionalData);
660 
661                     psu->setFaultLogged();
662                 }
663                 else if (psu->hasPgoodFault())
664                 {
665                     /* POWER_GOOD# is not low, or OFF is on */
666                     additionalData["CALLOUT_INVENTORY_PATH"] =
667                         psu->getInventoryPath();
668 
669                     createError(
670                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
671                         additionalData);
672 
673                     psu->setFaultLogged();
674                 }
675             }
676         }
677     }
678 }
679 
680 void PSUManager::validateConfig()
681 {
682     if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
683     {
684         return;
685     }
686 
687     std::map<std::string, std::string> additionalData;
688     auto supported = hasRequiredPSUs(additionalData);
689     if (supported)
690     {
691         runValidateConfig = false;
692         return;
693     }
694 
695     // Validation failed, create an error log.
696     // Return without setting the runValidateConfig flag to false because
697     // it may be that an additional supported configuration interface is
698     // added and we need to validate it to see if it matches this system.
699     createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
700                 additionalData);
701 }
702 
703 bool PSUManager::hasRequiredPSUs(
704     std::map<std::string, std::string>& additionalData)
705 {
706     std::string model{};
707     if (!validateModelName(model, additionalData))
708     {
709         return false;
710     }
711 
712     auto presentCount =
713         std::count_if(psus.begin(), psus.end(),
714                       [](const auto& psu) { return psu->isPresent(); });
715 
716     // Validate the supported configurations. A system may support more than one
717     // power supply model configuration. Since all configurations need to be
718     // checked, the additional data would contain only the information of the
719     // last configuration that did not match.
720     std::map<std::string, std::string> tmpAdditionalData;
721     for (const auto& config : supportedConfigs)
722     {
723         if (config.first != model)
724         {
725             continue;
726         }
727         if (presentCount != config.second.powerSupplyCount)
728         {
729             tmpAdditionalData.clear();
730             tmpAdditionalData["EXPECTED_COUNT"] =
731                 std::to_string(config.second.powerSupplyCount);
732             tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
733             continue;
734         }
735 
736         bool voltageValidated = true;
737         for (const auto& psu : psus)
738         {
739             if (!psu->isPresent())
740             {
741                 // Only present PSUs report a valid input voltage
742                 continue;
743             }
744 
745             double actualInputVoltage;
746             int inputVoltage;
747             psu->getInputVoltage(actualInputVoltage, inputVoltage);
748 
749             if (std::find(config.second.inputVoltage.begin(),
750                           config.second.inputVoltage.end(),
751                           inputVoltage) == config.second.inputVoltage.end())
752             {
753                 tmpAdditionalData.clear();
754                 tmpAdditionalData["ACTUAL_VOLTAGE"] =
755                     std::to_string(actualInputVoltage);
756                 for (const auto& voltage : config.second.inputVoltage)
757                 {
758                     tmpAdditionalData["EXPECTED_VOLTAGE"] +=
759                         std::to_string(voltage) + " ";
760                 }
761                 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
762                     psu->getInventoryPath();
763 
764                 voltageValidated = false;
765                 break;
766             }
767         }
768         if (!voltageValidated)
769         {
770             continue;
771         }
772 
773         return true;
774     }
775 
776     additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
777     return false;
778 }
779 
780 bool PSUManager::validateModelName(
781     std::string& model, std::map<std::string, std::string>& additionalData)
782 {
783     // Check that all PSUs have the same model name. Initialize the model
784     // variable with the first PSU name found, then use it as a base to compare
785     // against the rest of the PSUs and get its inventory path to use as callout
786     // if needed.
787     model.clear();
788     std::string modelInventoryPath{};
789     for (const auto& psu : psus)
790     {
791         auto psuModel = psu->getModelName();
792         if (psuModel.empty())
793         {
794             continue;
795         }
796         if (model.empty())
797         {
798             model = psuModel;
799             modelInventoryPath = psu->getInventoryPath();
800             continue;
801         }
802         if (psuModel != model)
803         {
804             if (supportedConfigs.find(model) != supportedConfigs.end())
805             {
806                 // The base model is supported, callout the mismatched PSU. The
807                 // mismatched PSU may or may not be supported.
808                 additionalData["EXPECTED_MODEL"] = model;
809                 additionalData["ACTUAL_MODEL"] = psuModel;
810                 additionalData["CALLOUT_INVENTORY_PATH"] =
811                     psu->getInventoryPath();
812             }
813             else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
814             {
815                 // The base model is not supported, but the mismatched PSU is,
816                 // callout the base PSU.
817                 additionalData["EXPECTED_MODEL"] = psuModel;
818                 additionalData["ACTUAL_MODEL"] = model;
819                 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
820             }
821             else
822             {
823                 // The base model and the mismatched PSU are not supported or
824                 // could not be found in the supported configuration, callout
825                 // the mismatched PSU.
826                 additionalData["EXPECTED_MODEL"] = model;
827                 additionalData["ACTUAL_MODEL"] = psuModel;
828                 additionalData["CALLOUT_INVENTORY_PATH"] =
829                     psu->getInventoryPath();
830             }
831             model.clear();
832             return false;
833         }
834     }
835     return true;
836 }
837 
838 void PSUManager::setPowerConfigGPIO()
839 {
840     if (!powerConfigGPIO)
841     {
842         return;
843     }
844 
845     std::string model{};
846     std::map<std::string, std::string> additionalData;
847     if (!validateModelName(model, additionalData))
848     {
849         return;
850     }
851 
852     auto config = supportedConfigs.find(model);
853     if (config != supportedConfigs.end())
854     {
855         // The power-config-full-load is an open drain GPIO. Set it to low (0)
856         // if the supported configuration indicates that this system model
857         // expects the maximum number of power supplies (full load set to true).
858         // Else, set it to high (1), this is the default.
859         auto powerConfigValue =
860             (config->second.powerConfigFullLoad == true ? 0 : 1);
861         auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
862         powerConfigGPIO->write(powerConfigValue, flags);
863     }
864 }
865 
866 void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData)
867 {
868     powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
869                                  server::PowerSystemInputs::Status::Fault);
870     if (!brownoutLogged)
871     {
872         if (powerOn)
873         {
874             createError(
875                 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
876                 additionalData);
877             brownoutLogged = true;
878         }
879     }
880 }
881 
882 void PSUManager::clearBrownout()
883 {
884     powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
885                                  server::PowerSystemInputs::Status::Good);
886     brownoutLogged = false;
887 }
888 
889 } // namespace phosphor::power::manager
890