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