blob: 2ab8cfb9cdc22fb2f12d475882d4be2a649b4367 [file] [log] [blame]
Brandon Wymana0f33ce2019-10-17 18:32:29 -05001#include "psu_manager.hpp"
2
3#include "utility.hpp"
4
Brandon Wymanb76ab242020-09-16 18:06:06 -05005#include <fmt/format.h>
6#include <sys/types.h>
7#include <unistd.h>
8
Brandon Wymanaed1f752019-11-25 18:10:52 -06009using namespace phosphor::logging;
10
Brandon Wyman63ea78b2020-09-24 16:49:09 -050011namespace phosphor::power::manager
Brandon Wymana0f33ce2019-10-17 18:32:29 -050012{
13
Brandon Wyman510acaa2020-11-05 18:32:04 -060014constexpr auto IBMCFFPSInterface =
15 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
16constexpr auto i2cBusProp = "I2CBus";
17constexpr auto i2cAddressProp = "I2CAddress";
18constexpr auto psuNameProp = "Name";
19
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060020constexpr auto supportedConfIntf =
21 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060022
Brandon Wyman510acaa2020-11-05 18:32:04 -060023PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
24 bus(bus)
25{
Brandon Wyman510acaa2020-11-05 18:32:04 -060026 // 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
Brandon Wyman510acaa2020-11-05 18:32:04 -060055void 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
91void 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
Adriana Kobylake1074d82021-03-16 20:46:44 +0000142void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
143{
144 try
145 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000146 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
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000157 propIt = properties.find("SupportedModel");
158 if (propIt == properties.end())
159 {
160 return;
161 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000162 const std::string* model = std::get_if<std::string>(&(propIt->second));
163 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000164 {
165 return;
166 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000167
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000168 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000169 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000170 if (propIt != properties.end())
171 {
172 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
173 if (count != nullptr)
174 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000175 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000176 }
177 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000178 propIt = properties.find("InputVoltage");
179 if (propIt != properties.end())
180 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000181 const std::vector<uint64_t>* voltage =
182 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000183 if (voltage != nullptr)
184 {
185 sys.inputVoltage = *voltage;
186 }
187 }
188
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000189 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000190 }
191 catch (std::exception& e)
192 {
193 }
194}
195
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600196void PSUManager::getSystemProperties()
197{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600198
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600199 try
200 {
201 util::DbusSubtree subtree =
202 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000203 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600204 {
205 throw std::runtime_error("Supported Configuration Not Found");
206 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600207
Adriana Kobylake1074d82021-03-16 20:46:44 +0000208 for (const auto& [objPath, services] : subtree)
209 {
210 std::string service = services.begin()->first;
211 if (objPath.empty() || service.empty())
212 {
213 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600214 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000215 auto properties = util::getAllProperties(
216 bus, objPath, supportedConfIntf, service);
217 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600218 }
219 }
220 catch (std::exception& e)
221 {
222 // Interface or property not found. Let the Interfaces Added callback
223 // process the information once the interfaces are added to D-Bus.
224 }
225}
226
Brandon Wyman3e429132021-03-18 18:03:14 -0500227void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600228{
229 try
230 {
231 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000232 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600233 interfaces;
234 msg.read(objPath, interfaces);
235
236 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600237 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600238 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600239 populateSysProperties(itIntf->second);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600240 }
241
Brandon Wyman510acaa2020-11-05 18:32:04 -0600242 itIntf = interfaces.find(IBMCFFPSInterface);
243 if (itIntf != interfaces.cend())
244 {
245 log<level::INFO>(
246 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
247 .c_str());
248 getPSUProperties(itIntf->second);
249 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000250
251 // Call to validate the psu configuration if the power is on and both
252 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
253 // processed
254 if (powerOn && !psus.empty() && !supportedConfigs.empty())
255 {
256 validateConfig();
257 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600258 }
259 catch (std::exception& e)
260 {
261 // Ignore, the property may be of a different type than expected.
262 }
263}
264
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500265void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
266{
267 int32_t state = 0;
268 std::string msgSensor;
Patrick Williamsabe49412020-05-13 17:59:47 -0500269 std::map<std::string, std::variant<int32_t>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500270 msg.read(msgSensor, msgData);
271
272 // Check if it was the Present property that changed.
273 auto valPropMap = msgData.find("state");
274 if (valPropMap != msgData.end())
275 {
276 state = std::get<int32_t>(valPropMap->second);
277
278 // Power is on when state=1. Clear faults.
279 if (state)
280 {
281 powerOn = true;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000282 validateConfig();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500283 clearFaults();
284 }
285 else
286 {
287 powerOn = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000288 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500289 }
290 }
291}
292
Brandon Wymanb76ab242020-09-16 18:06:06 -0500293void PSUManager::createError(
294 const std::string& faultName,
295 const std::map<std::string, std::string>& additionalData)
296{
297 using namespace sdbusplus::xyz::openbmc_project;
298 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
299 constexpr auto loggingCreateInterface =
300 "xyz.openbmc_project.Logging.Create";
301
302 try
303 {
304 auto service =
305 util::getService(loggingObjectPath, loggingCreateInterface, bus);
306
307 if (service.empty())
308 {
309 log<level::ERR>("Unable to get logging manager service");
310 return;
311 }
312
313 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
314 loggingCreateInterface, "Create");
315
316 auto level = Logging::server::Entry::Level::Error;
317 method.append(faultName, level, additionalData);
318
319 auto reply = bus.call(method);
320 }
321 catch (std::exception& e)
322 {
323 log<level::ERR>(
324 fmt::format(
325 "Failed creating event log for fault {} due to error {}",
326 faultName, e.what())
327 .c_str());
328 }
329}
330
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500331void PSUManager::analyze()
332{
333 for (auto& psu : psus)
334 {
335 psu->analyze();
336 }
337
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600338 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500339 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600340 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500341 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600342 std::map<std::string, std::string> additionalData;
343 additionalData["_PID"] = std::to_string(getpid());
344 // TODO: Fault priorities #918
345 if (!psu->isFaultLogged() && !psu->isPresent())
346 {
347 // Create error for power supply missing.
348 additionalData["CALLOUT_INVENTORY_PATH"] =
349 psu->getInventoryPath();
350 additionalData["CALLOUT_PRIORITY"] = "H";
351 createError(
352 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
353 additionalData);
354 psu->setFaultLogged();
355 }
356 else if (!psu->isFaultLogged() && psu->isFaulted())
357 {
358 additionalData["STATUS_WORD"] =
359 std::to_string(psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600360 additionalData["STATUS_MFR"] =
361 std::to_string(psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600362 // If there are faults being reported, they possibly could be
363 // related to a bug in the firmware version running on the power
364 // supply. Capture that data into the error as well.
365 additionalData["FW_VERSION"] = psu->getFWVersion();
366
367 if ((psu->hasInputFault() || psu->hasVINUVFault()))
368 {
369 /* The power supply location might be needed if the input
370 * fault is due to a problem with the power supply itself.
371 * Include the inventory path with a call out priority of
372 * low.
373 */
374 additionalData["CALLOUT_INVENTORY_PATH"] =
375 psu->getInventoryPath();
376 additionalData["CALLOUT_PRIORITY"] = "L";
377 createError("xyz.openbmc_project.Power.PowerSupply.Error."
378 "InputFault",
379 additionalData);
380 psu->setFaultLogged();
381 }
382 else if (psu->hasMFRFault())
383 {
384 /* This can represent a variety of faults that result in
385 * calling out the power supply for replacement: Output
386 * OverCurrent, Output Under Voltage, and potentially other
387 * faults.
388 *
389 * Also plan on putting specific fault in AdditionalData,
390 * along with register names and register values
391 * (STATUS_WORD, STATUS_MFR, etc.).*/
392
393 additionalData["CALLOUT_INVENTORY_PATH"] =
394 psu->getInventoryPath();
395
396 createError(
397 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500398 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500399
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600400 psu->setFaultLogged();
401 }
402 else if (psu->hasCommFault())
403 {
404 /* Attempts to communicate with the power supply have
405 * reached there limit. Create an error. */
406 additionalData["CALLOUT_DEVICE_PATH"] =
407 psu->getDevicePath();
Brandon Wymanb76ab242020-09-16 18:06:06 -0500408
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600409 createError(
410 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
411 additionalData);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500412
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600413 psu->setFaultLogged();
414 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500415 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500416 }
417 }
418}
419
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000420void PSUManager::validateConfig()
421{
422 if (!runValidateConfig)
423 {
424 return;
425 }
426
427 // Check that all PSUs have the same model name. Initialize the model
428 // variable with the first PSU name found, then use it as a base to compare
429 // against the rest of the PSUs.
430 std::string model{};
431 for (const auto& p : psus)
432 {
433 auto psuModel = p->getModelName();
434 if (psuModel.empty())
435 {
436 continue;
437 }
438 if (model.empty())
439 {
440 model = psuModel;
441 continue;
442 }
443 if (psuModel.compare(model) != 0)
444 {
445 log<level::ERR>(
446 fmt::format("Mismatched power supply models: {}, {}",
447 model.c_str(), psuModel.c_str())
448 .c_str());
449 std::map<std::string, std::string> additionalData;
450 additionalData["EXPECTED_MODEL"] = model;
451 additionalData["ACTUAL_MODEL"] = psuModel;
452 additionalData["CALLOUT_INVENTORY_PATH"] = p->getInventoryPath();
453 createError(
454 "xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
455 additionalData);
456
457 // No need to do the validation anymore, a mismatched model needs to
458 // be fixed by the user.
459 runValidateConfig = false;
460 return;
461 }
462 }
463}
464
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500465} // namespace phosphor::power::manager