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