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