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