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