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