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