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