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