1 #include "psu_manager.hpp"
2 
3 #include "utility.hpp"
4 
5 #include <fmt/format.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 
9 #include <regex>
10 
11 using namespace phosphor::logging;
12 
13 namespace phosphor::power::manager
14 {
15 
16 constexpr auto IBMCFFPSInterface =
17     "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
18 constexpr auto i2cBusProp = "I2CBus";
19 constexpr auto i2cAddressProp = "I2CAddress";
20 constexpr auto psuNameProp = "Name";
21 constexpr auto presLineName = "NamedPresenceGpio";
22 
23 constexpr auto supportedConfIntf =
24     "xyz.openbmc_project.Configuration.SupportedConfiguration";
25 
26 PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
27     bus(bus)
28 {
29     // Subscribe to InterfacesAdded before doing a property read, otherwise
30     // the interface could be created after the read attempt but before the
31     // match is created.
32     entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
33         bus,
34         sdbusplus::bus::match::rules::interfacesAdded() +
35             sdbusplus::bus::match::rules::sender(
36                 "xyz.openbmc_project.EntityManager"),
37         std::bind(&PSUManager::entityManagerIfaceAdded, this,
38                   std::placeholders::_1));
39     getPSUConfiguration();
40     getSystemProperties();
41 
42     using namespace sdeventplus;
43     auto interval = std::chrono::milliseconds(1000);
44     timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
45         e, std::bind(&PSUManager::analyze, this), interval);
46 
47     validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
48         e, std::bind(&PSUManager::validateConfig, this));
49 
50     // Subscribe to power state changes
51     powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
52     powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
53         bus,
54         sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
55                                                         POWER_IFACE),
56         [this](auto& msg) { this->powerStateChanged(msg); });
57 
58     initialize();
59 }
60 
61 void PSUManager::getPSUConfiguration()
62 {
63     using namespace phosphor::power::util;
64     auto depth = 0;
65     auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
66 
67     psus.clear();
68 
69     // I should get a map of objects back.
70     // Each object will have a path, a service, and an interface.
71     // The interface should match the one passed into this function.
72     for (const auto& [path, services] : objects)
73     {
74         auto service = services.begin()->first;
75 
76         if (path.empty() || service.empty())
77         {
78             continue;
79         }
80 
81         // For each object in the array of objects, I want to get properties
82         // from the service, path, and interface.
83         auto properties =
84             getAllProperties(bus, path, IBMCFFPSInterface, service);
85 
86         getPSUProperties(properties);
87     }
88 
89     if (psus.empty())
90     {
91         // Interface or properties not found. Let the Interfaces Added callback
92         // process the information once the interfaces are added to D-Bus.
93         log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
94     }
95 }
96 
97 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
98 {
99     // From passed in properties, I want to get: I2CBus, I2CAddress,
100     // and Name. Create a power supply object, using Name to build the inventory
101     // path.
102     const auto basePSUInvPath =
103         "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
104     uint64_t* i2cbus = nullptr;
105     uint64_t* i2caddr = nullptr;
106     std::string* psuname = nullptr;
107     std::string* preslineptr = nullptr;
108 
109     for (const auto& property : properties)
110     {
111         try
112         {
113             if (property.first == i2cBusProp)
114             {
115                 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
116             }
117             else if (property.first == i2cAddressProp)
118             {
119                 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
120             }
121             else if (property.first == psuNameProp)
122             {
123                 psuname = std::get_if<std::string>(&properties[psuNameProp]);
124             }
125             else if (property.first == presLineName)
126             {
127                 preslineptr =
128                     std::get_if<std::string>(&properties[presLineName]);
129             }
130         }
131         catch (const std::exception& e)
132         {}
133     }
134 
135     if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
136     {
137         std::string invpath = basePSUInvPath;
138         invpath.push_back(psuname->back());
139         std::string presline = "";
140 
141         log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
142 
143         if (nullptr != preslineptr)
144         {
145             presline = *preslineptr;
146         }
147 
148         auto invMatch =
149             std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
150                 return psu->getInventoryPath() == invpath;
151             });
152         if (invMatch != psus.end())
153         {
154             // This power supply has the same inventory path as the one with
155             // information just added to D-Bus.
156             // Changes to GPIO line name unlikely, so skip checking.
157             // Changes to the I2C bus and address unlikely, as that would
158             // require corresponding device tree updates.
159             // Return out to avoid duplicate object creation.
160             return;
161         }
162 
163         log<level::DEBUG>(
164             fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
165                         *i2cbus, *i2caddr, presline)
166                 .c_str());
167         auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
168                                                  *i2caddr, presline);
169         psus.emplace_back(std::move(psu));
170     }
171 
172     if (psus.empty())
173     {
174         log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
175     }
176 }
177 
178 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
179 {
180     try
181     {
182         auto propIt = properties.find("SupportedType");
183         if (propIt == properties.end())
184         {
185             return;
186         }
187         const std::string* type = std::get_if<std::string>(&(propIt->second));
188         if ((type == nullptr) || (*type != "PowerSupply"))
189         {
190             return;
191         }
192 
193         propIt = properties.find("SupportedModel");
194         if (propIt == properties.end())
195         {
196             return;
197         }
198         const std::string* model = std::get_if<std::string>(&(propIt->second));
199         if (model == nullptr)
200         {
201             return;
202         }
203 
204         sys_properties sys;
205         propIt = properties.find("RedundantCount");
206         if (propIt != properties.end())
207         {
208             const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
209             if (count != nullptr)
210             {
211                 sys.powerSupplyCount = *count;
212             }
213         }
214         propIt = properties.find("InputVoltage");
215         if (propIt != properties.end())
216         {
217             const std::vector<uint64_t>* voltage =
218                 std::get_if<std::vector<uint64_t>>(&(propIt->second));
219             if (voltage != nullptr)
220             {
221                 sys.inputVoltage = *voltage;
222             }
223         }
224 
225         supportedConfigs.emplace(*model, sys);
226     }
227     catch (const std::exception& e)
228     {}
229 }
230 
231 void PSUManager::getSystemProperties()
232 {
233 
234     try
235     {
236         util::DbusSubtree subtree =
237             util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
238         if (subtree.empty())
239         {
240             throw std::runtime_error("Supported Configuration Not Found");
241         }
242 
243         for (const auto& [objPath, services] : subtree)
244         {
245             std::string service = services.begin()->first;
246             if (objPath.empty() || service.empty())
247             {
248                 continue;
249             }
250             auto properties = util::getAllProperties(
251                 bus, objPath, supportedConfIntf, service);
252             populateSysProperties(properties);
253         }
254     }
255     catch (const std::exception& e)
256     {
257         // Interface or property not found. Let the Interfaces Added callback
258         // process the information once the interfaces are added to D-Bus.
259     }
260 }
261 
262 void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
263 {
264     try
265     {
266         sdbusplus::message::object_path objPath;
267         std::map<std::string, std::map<std::string, util::DbusVariant>>
268             interfaces;
269         msg.read(objPath, interfaces);
270 
271         auto itIntf = interfaces.find(supportedConfIntf);
272         if (itIntf != interfaces.cend())
273         {
274             populateSysProperties(itIntf->second);
275         }
276 
277         itIntf = interfaces.find(IBMCFFPSInterface);
278         if (itIntf != interfaces.cend())
279         {
280             log<level::INFO>(
281                 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
282                     .c_str());
283             getPSUProperties(itIntf->second);
284         }
285 
286         // Call to validate the psu configuration if the power is on and both
287         // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
288         // processed
289         if (powerOn && !psus.empty() && !supportedConfigs.empty())
290         {
291             validationTimer->restartOnce(validationTimeout);
292         }
293     }
294     catch (const std::exception& e)
295     {
296         // Ignore, the property may be of a different type than expected.
297     }
298 }
299 
300 void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
301 {
302     int32_t state = 0;
303     std::string msgSensor;
304     std::map<std::string, std::variant<int32_t>> msgData;
305     msg.read(msgSensor, msgData);
306 
307     // Check if it was the Present property that changed.
308     auto valPropMap = msgData.find("state");
309     if (valPropMap != msgData.end())
310     {
311         state = std::get<int32_t>(valPropMap->second);
312 
313         // Power is on when state=1. Clear faults.
314         if (state)
315         {
316             powerOn = true;
317             validationTimer->restartOnce(validationTimeout);
318             clearFaults();
319         }
320         else
321         {
322             powerOn = false;
323             runValidateConfig = true;
324         }
325     }
326 }
327 
328 void PSUManager::createError(const std::string& faultName,
329                              std::map<std::string, std::string>& additionalData)
330 {
331     using namespace sdbusplus::xyz::openbmc_project;
332     constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
333     constexpr auto loggingCreateInterface =
334         "xyz.openbmc_project.Logging.Create";
335 
336     try
337     {
338         additionalData["_PID"] = std::to_string(getpid());
339 
340         auto service =
341             util::getService(loggingObjectPath, loggingCreateInterface, bus);
342 
343         if (service.empty())
344         {
345             log<level::ERR>("Unable to get logging manager service");
346             return;
347         }
348 
349         auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
350                                           loggingCreateInterface, "Create");
351 
352         auto level = Logging::server::Entry::Level::Error;
353         method.append(faultName, level, additionalData);
354 
355         auto reply = bus.call(method);
356     }
357     catch (const std::exception& e)
358     {
359         log<level::ERR>(
360             fmt::format(
361                 "Failed creating event log for fault {} due to error {}",
362                 faultName, e.what())
363                 .c_str());
364     }
365 }
366 
367 void PSUManager::analyze()
368 {
369     for (auto& psu : psus)
370     {
371         psu->analyze();
372     }
373 
374     if (powerOn)
375     {
376         std::map<std::string, std::string> additionalData;
377 
378         for (auto& psu : psus)
379         {
380             additionalData.clear();
381             // TODO: Fault priorities #918
382             if (!psu->isFaultLogged() && !psu->isPresent())
383             {
384                 std::map<std::string, std::string> requiredPSUsData;
385                 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
386                 if (!requiredPSUsPresent)
387                 {
388                     additionalData.merge(requiredPSUsData);
389                     // Create error for power supply missing.
390                     additionalData["CALLOUT_INVENTORY_PATH"] =
391                         psu->getInventoryPath();
392                     additionalData["CALLOUT_PRIORITY"] = "H";
393                     createError(
394                         "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
395                         additionalData);
396                 }
397                 psu->setFaultLogged();
398             }
399             else if (!psu->isFaultLogged() && psu->isFaulted())
400             {
401                 // Add STATUS_WORD and STATUS_MFR last response, in padded
402                 // hexadecimal format.
403                 additionalData["STATUS_WORD"] =
404                     fmt::format("{:#04x}", psu->getStatusWord());
405                 additionalData["STATUS_MFR"] =
406                     fmt::format("{:#02x}", psu->getMFRFault());
407                 // If there are faults being reported, they possibly could be
408                 // related to a bug in the firmware version running on the power
409                 // supply. Capture that data into the error as well.
410                 additionalData["FW_VERSION"] = psu->getFWVersion();
411 
412                 if (psu->hasCommFault())
413                 {
414                     additionalData["STATUS_CML"] =
415                         fmt::format("{:#02x}", psu->getStatusCML());
416                     /* Attempts to communicate with the power supply have
417                      * reached there limit. Create an error. */
418                     additionalData["CALLOUT_DEVICE_PATH"] =
419                         psu->getDevicePath();
420 
421                     createError(
422                         "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
423                         additionalData);
424 
425                     psu->setFaultLogged();
426                 }
427                 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
428                 {
429                     // Include STATUS_INPUT for input faults.
430                     additionalData["STATUS_INPUT"] =
431                         fmt::format("{:#02x}", psu->getStatusInput());
432 
433                     /* The power supply location might be needed if the input
434                      * fault is due to a problem with the power supply itself.
435                      * Include the inventory path with a call out priority of
436                      * low.
437                      */
438                     additionalData["CALLOUT_INVENTORY_PATH"] =
439                         psu->getInventoryPath();
440                     additionalData["CALLOUT_PRIORITY"] = "L";
441                     createError("xyz.openbmc_project.Power.PowerSupply.Error."
442                                 "InputFault",
443                                 additionalData);
444                     psu->setFaultLogged();
445                 }
446                 else if (psu->hasVoutOVFault())
447                 {
448                     // Include STATUS_VOUT for Vout faults.
449                     additionalData["STATUS_VOUT"] =
450                         fmt::format("{:#02x}", psu->getStatusVout());
451 
452                     additionalData["CALLOUT_INVENTORY_PATH"] =
453                         psu->getInventoryPath();
454 
455                     createError(
456                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
457                         additionalData);
458 
459                     psu->setFaultLogged();
460                 }
461                 else if (psu->hasMFRFault())
462                 {
463                     /* This can represent a variety of faults that result in
464                      * calling out the power supply for replacement: Output
465                      * OverCurrent, Output Under Voltage, and potentially other
466                      * faults.
467                      *
468                      * Also plan on putting specific fault in AdditionalData,
469                      * along with register names and register values
470                      * (STATUS_WORD, STATUS_MFR, etc.).*/
471 
472                     additionalData["CALLOUT_INVENTORY_PATH"] =
473                         psu->getInventoryPath();
474 
475                     createError(
476                         "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
477                         additionalData);
478 
479                     psu->setFaultLogged();
480                 }
481             }
482         }
483     }
484 }
485 
486 void PSUManager::validateConfig()
487 {
488     if (!runValidateConfig || supportedConfigs.empty())
489     {
490         return;
491     }
492 
493     std::map<std::string, std::string> additionalData;
494     auto supported = hasRequiredPSUs(additionalData);
495     if (supported)
496     {
497         runValidateConfig = false;
498         return;
499     }
500 
501     // Validation failed, create an error log.
502     // Return without setting the runValidateConfig flag to false because
503     // it may be that an additional supported configuration interface is
504     // added and we need to validate it to see if it matches this system.
505     createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
506                 additionalData);
507 }
508 
509 bool PSUManager::hasRequiredPSUs(
510     std::map<std::string, std::string>& additionalData)
511 {
512     std::string model{};
513     if (!validateModelName(model, additionalData))
514     {
515         return false;
516     }
517 
518     auto presentCount =
519         std::count_if(psus.begin(), psus.end(),
520                       [](const auto& psu) { return psu->isPresent(); });
521 
522     // Validate the supported configurations. A system may support more than one
523     // power supply model configuration. Since all configurations need to be
524     // checked, the additional data would contain only the information of the
525     // last configuration that did not match.
526     std::map<std::string, std::string> tmpAdditionalData;
527     for (const auto& config : supportedConfigs)
528     {
529         if (config.first != model)
530         {
531             continue;
532         }
533         if (presentCount != config.second.powerSupplyCount)
534         {
535             tmpAdditionalData.clear();
536             tmpAdditionalData["EXPECTED_COUNT"] =
537                 std::to_string(config.second.powerSupplyCount);
538             tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
539             continue;
540         }
541 
542         bool voltageValidated = true;
543         for (const auto& psu : psus)
544         {
545             if (!psu->isPresent())
546             {
547                 // Only present PSUs report a valid input voltage
548                 continue;
549             }
550 
551             double actualInputVoltage;
552             int inputVoltage;
553             psu->getInputVoltage(actualInputVoltage, inputVoltage);
554 
555             if (std::find(config.second.inputVoltage.begin(),
556                           config.second.inputVoltage.end(),
557                           inputVoltage) == config.second.inputVoltage.end())
558             {
559                 tmpAdditionalData.clear();
560                 tmpAdditionalData["ACTUAL_VOLTAGE"] =
561                     std::to_string(actualInputVoltage);
562                 for (const auto& voltage : config.second.inputVoltage)
563                 {
564                     tmpAdditionalData["EXPECTED_VOLTAGE"] +=
565                         std::to_string(voltage) + " ";
566                 }
567                 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
568                     psu->getInventoryPath();
569 
570                 voltageValidated = false;
571                 break;
572             }
573         }
574         if (!voltageValidated)
575         {
576             continue;
577         }
578 
579         return true;
580     }
581 
582     additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
583     return false;
584 }
585 
586 bool PSUManager::validateModelName(
587     std::string& model, std::map<std::string, std::string>& additionalData)
588 {
589     // Check that all PSUs have the same model name. Initialize the model
590     // variable with the first PSU name found, then use it as a base to compare
591     // against the rest of the PSUs.
592     model.clear();
593     for (const auto& psu : psus)
594     {
595         auto psuModel = psu->getModelName();
596         if (psuModel.empty())
597         {
598             continue;
599         }
600         if (model.empty())
601         {
602             model = psuModel;
603             continue;
604         }
605         if (psuModel != model)
606         {
607             additionalData["EXPECTED_MODEL"] = model;
608             additionalData["ACTUAL_MODEL"] = psuModel;
609             additionalData["CALLOUT_INVENTORY_PATH"] = psu->getInventoryPath();
610             model.clear();
611             return false;
612         }
613     }
614     return true;
615 }
616 
617 } // namespace phosphor::power::manager
618