blob: 08b103d1fe100bc56564fe811fddca28aec2ffb5 [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";
B. J. Wyman681b2a32021-04-20 22:31:22 +000019constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060020
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060021constexpr auto supportedConfIntf =
22 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060023
Brandon Wyman510acaa2020-11-05 18:32:04 -060024PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
25 bus(bus)
26{
Brandon Wyman510acaa2020-11-05 18:32:04 -060027 // 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
Brandon Wyman510acaa2020-11-05 18:32:04 -060056void 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
92void 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;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000102 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600103
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 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000120 else if (property.first == presLineName)
121 {
122 preslineptr =
123 std::get_if<std::string>(&properties[presLineName]);
124 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600125 }
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());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000135 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600136
137 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
138
B. J. Wyman681b2a32021-04-20 22:31:22 +0000139 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);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600150 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
Adriana Kobylake1074d82021-03-16 20:46:44 +0000159void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
160{
161 try
162 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000163 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
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000174 propIt = properties.find("SupportedModel");
175 if (propIt == properties.end())
176 {
177 return;
178 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000179 const std::string* model = std::get_if<std::string>(&(propIt->second));
180 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000181 {
182 return;
183 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000184
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000185 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000186 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000187 if (propIt != properties.end())
188 {
189 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
190 if (count != nullptr)
191 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000192 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000193 }
194 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000195 propIt = properties.find("InputVoltage");
196 if (propIt != properties.end())
197 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000198 const std::vector<uint64_t>* voltage =
199 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000200 if (voltage != nullptr)
201 {
202 sys.inputVoltage = *voltage;
203 }
204 }
205
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000206 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000207 }
208 catch (std::exception& e)
209 {
210 }
211}
212
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600213void PSUManager::getSystemProperties()
214{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600215
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600216 try
217 {
218 util::DbusSubtree subtree =
219 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000220 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600221 {
222 throw std::runtime_error("Supported Configuration Not Found");
223 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600224
Adriana Kobylake1074d82021-03-16 20:46:44 +0000225 for (const auto& [objPath, services] : subtree)
226 {
227 std::string service = services.begin()->first;
228 if (objPath.empty() || service.empty())
229 {
230 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600231 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000232 auto properties = util::getAllProperties(
233 bus, objPath, supportedConfIntf, service);
234 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600235 }
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
Brandon Wyman3e429132021-03-18 18:03:14 -0500244void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600245{
246 try
247 {
248 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000249 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600250 interfaces;
251 msg.read(objPath, interfaces);
252
253 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600254 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600255 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600256 populateSysProperties(itIntf->second);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600257 }
258
Brandon Wyman510acaa2020-11-05 18:32:04 -0600259 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 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000267
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 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600275 }
276 catch (std::exception& e)
277 {
278 // Ignore, the property may be of a different type than expected.
279 }
280}
281
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500282void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
283{
284 int32_t state = 0;
285 std::string msgSensor;
Patrick Williamsabe49412020-05-13 17:59:47 -0500286 std::map<std::string, std::variant<int32_t>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500287 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;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000299 validateConfig();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500300 clearFaults();
301 }
302 else
303 {
304 powerOn = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000305 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500306 }
307 }
308}
309
Brandon Wymanb76ab242020-09-16 18:06:06 -0500310void 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
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500348void PSUManager::analyze()
349{
350 for (auto& psu : psus)
351 {
352 psu->analyze();
353 }
354
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600355 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500356 {
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000357 std::map<std::string, std::string> additionalData;
358 auto requiredPSUsPresent = hasRequiredPSUs(additionalData);
359
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600360 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500361 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600362 additionalData["_PID"] = std::to_string(getpid());
363 // TODO: Fault priorities #918
364 if (!psu->isFaultLogged() && !psu->isPresent())
365 {
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000366 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 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600376 psu->setFaultLogged();
377 }
378 else if (!psu->isFaultLogged() && psu->isFaulted())
379 {
380 additionalData["STATUS_WORD"] =
381 std::to_string(psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600382 additionalData["STATUS_MFR"] =
383 std::to_string(psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600384 // 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",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500420 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500421
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600422 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();
Brandon Wymanb76ab242020-09-16 18:06:06 -0500430
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600431 createError(
432 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
433 additionalData);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500434
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600435 psu->setFaultLogged();
436 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500437 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500438 }
439 }
440}
441
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000442void PSUManager::validateConfig()
443{
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000444 if (!runValidateConfig || supportedConfigs.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000445 {
446 return;
447 }
448
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000449 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
465bool PSUManager::hasRequiredPSUs(
466 std::map<std::string, std::string>& additionalData)
467{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000468 // 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{};
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000472 for (const auto& psu : psus)
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000473 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000474 auto psuModel = psu->getModelName();
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000475 if (psuModel.empty())
476 {
477 continue;
478 }
479 if (model.empty())
480 {
481 model = psuModel;
482 continue;
483 }
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000484 if (psuModel != model)
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000485 {
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000486 additionalData["EXPECTED_MODEL"] = model;
487 additionalData["ACTUAL_MODEL"] = psuModel;
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000488 additionalData["CALLOUT_INVENTORY_PATH"] = psu->getInventoryPath();
489 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000490 }
491 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000492
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000493 auto presentCount =
494 std::count_if(psus.begin(), psus.end(),
495 [](const auto& psu) { return psu->isPresent(); });
496
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000497 // Validate the supported configurations. A system may support more than one
498 // power supply model configuration.
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000499 for (const auto& config : supportedConfigs)
500 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000501 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000502 {
503 continue;
504 }
505 if (presentCount != config.second.powerSupplyCount)
506 {
507 additionalData["EXPECTED_COUNT"] =
508 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000509 additionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000510 continue;
511 }
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000512 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000513 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000514
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000515 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000516}
517
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500518} // namespace phosphor::power::manager