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