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