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