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