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