blob: a8d0d5527f68f45dc93a9f60e655874a14a300c7 [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 Wymanecbecbc2021-08-31 22:53:21 +00009#include <regex>
10
Brandon Wymanaed1f752019-11-25 18:10:52 -060011using namespace phosphor::logging;
12
Brandon Wyman63ea78b2020-09-24 16:49:09 -050013namespace phosphor::power::manager
Brandon Wymana0f33ce2019-10-17 18:32:29 -050014{
Adriana Kobylakc9b05732022-03-19 15:15:10 +000015constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
16constexpr auto objectManagerObjPath =
17 "/xyz/openbmc_project/power/power_supplies";
18constexpr auto powerSystemsInputsObjPath =
19 "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
Brandon Wymana0f33ce2019-10-17 18:32:29 -050020
Brandon Wyman510acaa2020-11-05 18:32:04 -060021constexpr auto IBMCFFPSInterface =
22 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
23constexpr auto i2cBusProp = "I2CBus";
24constexpr auto i2cAddressProp = "I2CAddress";
25constexpr auto psuNameProp = "Name";
B. J. Wyman681b2a32021-04-20 22:31:22 +000026constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060027
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060028constexpr auto supportedConfIntf =
29 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060030
Brandon Wyman510acaa2020-11-05 18:32:04 -060031PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000032 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
33 objectManager(bus, objectManagerObjPath)
Brandon Wyman510acaa2020-11-05 18:32:04 -060034{
Brandon Wyman510acaa2020-11-05 18:32:04 -060035 // Subscribe to InterfacesAdded before doing a property read, otherwise
36 // the interface could be created after the read attempt but before the
37 // match is created.
38 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
39 bus,
40 sdbusplus::bus::match::rules::interfacesAdded() +
41 sdbusplus::bus::match::rules::sender(
42 "xyz.openbmc_project.EntityManager"),
43 std::bind(&PSUManager::entityManagerIfaceAdded, this,
44 std::placeholders::_1));
45 getPSUConfiguration();
46 getSystemProperties();
47
Adriana Kobylakc9b05732022-03-19 15:15:10 +000048 // Request the bus name before the analyze() function, which is the one that
49 // determines the brownout condition and sets the status d-bus property.
50 bus.request_name(managerBusName);
51
Brandon Wyman510acaa2020-11-05 18:32:04 -060052 using namespace sdeventplus;
53 auto interval = std::chrono::milliseconds(1000);
54 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
55 e, std::bind(&PSUManager::analyze, this), interval);
56
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000057 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
58 e, std::bind(&PSUManager::validateConfig, this));
59
Adriana Kobylakc0a07582021-10-13 15:52:25 +000060 try
61 {
62 powerConfigGPIO = createGPIO("power-config-full-load");
63 }
64 catch (const std::exception& e)
65 {
66 // Ignore error, GPIO may not be implemented in this system.
67 powerConfigGPIO = nullptr;
68 }
69
Brandon Wyman510acaa2020-11-05 18:32:04 -060070 // Subscribe to power state changes
71 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
72 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
73 bus,
74 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
75 POWER_IFACE),
76 [this](auto& msg) { this->powerStateChanged(msg); });
77
78 initialize();
79}
80
Brandon Wyman510acaa2020-11-05 18:32:04 -060081void PSUManager::getPSUConfiguration()
82{
83 using namespace phosphor::power::util;
84 auto depth = 0;
85 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
86
87 psus.clear();
88
89 // I should get a map of objects back.
90 // Each object will have a path, a service, and an interface.
91 // The interface should match the one passed into this function.
92 for (const auto& [path, services] : objects)
93 {
94 auto service = services.begin()->first;
95
96 if (path.empty() || service.empty())
97 {
98 continue;
99 }
100
101 // For each object in the array of objects, I want to get properties
102 // from the service, path, and interface.
103 auto properties =
104 getAllProperties(bus, path, IBMCFFPSInterface, service);
105
106 getPSUProperties(properties);
107 }
108
109 if (psus.empty())
110 {
111 // Interface or properties not found. Let the Interfaces Added callback
112 // process the information once the interfaces are added to D-Bus.
113 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
114 }
115}
116
117void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
118{
119 // From passed in properties, I want to get: I2CBus, I2CAddress,
120 // and Name. Create a power supply object, using Name to build the inventory
121 // path.
122 const auto basePSUInvPath =
123 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
124 uint64_t* i2cbus = nullptr;
125 uint64_t* i2caddr = nullptr;
126 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000127 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600128
129 for (const auto& property : properties)
130 {
131 try
132 {
133 if (property.first == i2cBusProp)
134 {
135 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
136 }
137 else if (property.first == i2cAddressProp)
138 {
139 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
140 }
141 else if (property.first == psuNameProp)
142 {
143 psuname = std::get_if<std::string>(&properties[psuNameProp]);
144 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000145 else if (property.first == presLineName)
146 {
147 preslineptr =
148 std::get_if<std::string>(&properties[presLineName]);
149 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600150 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500151 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000152 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600153 }
154
155 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
156 {
157 std::string invpath = basePSUInvPath;
158 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000159 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600160
161 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
162
B. J. Wyman681b2a32021-04-20 22:31:22 +0000163 if (nullptr != preslineptr)
164 {
165 presline = *preslineptr;
166 }
167
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000168 auto invMatch =
169 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
170 return psu->getInventoryPath() == invpath;
171 });
172 if (invMatch != psus.end())
173 {
174 // This power supply has the same inventory path as the one with
175 // information just added to D-Bus.
176 // Changes to GPIO line name unlikely, so skip checking.
177 // Changes to the I2C bus and address unlikely, as that would
178 // require corresponding device tree updates.
179 // Return out to avoid duplicate object creation.
180 return;
181 }
182
B. J. Wyman681b2a32021-04-20 22:31:22 +0000183 log<level::DEBUG>(
184 fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
185 *i2cbus, *i2caddr, presline)
186 .c_str());
187 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
188 *i2caddr, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600189 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000190
191 // Subscribe to power supply presence changes
192 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
193 bus,
194 sdbusplus::bus::match::rules::propertiesChanged(invpath,
195 INVENTORY_IFACE),
196 [this](auto& msg) { this->presenceChanged(msg); });
197 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600198 }
199
200 if (psus.empty())
201 {
202 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
203 }
204}
205
Adriana Kobylake1074d82021-03-16 20:46:44 +0000206void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
207{
208 try
209 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000210 auto propIt = properties.find("SupportedType");
211 if (propIt == properties.end())
212 {
213 return;
214 }
215 const std::string* type = std::get_if<std::string>(&(propIt->second));
216 if ((type == nullptr) || (*type != "PowerSupply"))
217 {
218 return;
219 }
220
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000221 propIt = properties.find("SupportedModel");
222 if (propIt == properties.end())
223 {
224 return;
225 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000226 const std::string* model = std::get_if<std::string>(&(propIt->second));
227 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000228 {
229 return;
230 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000231
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000232 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000233 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000234 if (propIt != properties.end())
235 {
236 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
237 if (count != nullptr)
238 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000239 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000240 }
241 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000242 propIt = properties.find("InputVoltage");
243 if (propIt != properties.end())
244 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000245 const std::vector<uint64_t>* voltage =
246 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000247 if (voltage != nullptr)
248 {
249 sys.inputVoltage = *voltage;
250 }
251 }
252
Adriana Kobylak886574c2021-11-01 18:22:28 +0000253 // The PowerConfigFullLoad is an optional property, default it to false
254 // since that's the default value of the power-config-full-load GPIO.
255 sys.powerConfigFullLoad = false;
256 propIt = properties.find("PowerConfigFullLoad");
257 if (propIt != properties.end())
258 {
259 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
260 if (fullLoad != nullptr)
261 {
262 sys.powerConfigFullLoad = *fullLoad;
263 }
264 }
265
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000266 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000267 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500268 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000269 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000270}
271
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600272void PSUManager::getSystemProperties()
273{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600274
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600275 try
276 {
277 util::DbusSubtree subtree =
278 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000279 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600280 {
281 throw std::runtime_error("Supported Configuration Not Found");
282 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600283
Adriana Kobylake1074d82021-03-16 20:46:44 +0000284 for (const auto& [objPath, services] : subtree)
285 {
286 std::string service = services.begin()->first;
287 if (objPath.empty() || service.empty())
288 {
289 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600290 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000291 auto properties = util::getAllProperties(
292 bus, objPath, supportedConfIntf, service);
293 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600294 }
295 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500296 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600297 {
298 // Interface or property not found. Let the Interfaces Added callback
299 // process the information once the interfaces are added to D-Bus.
300 }
301}
302
Brandon Wyman3e429132021-03-18 18:03:14 -0500303void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600304{
305 try
306 {
307 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000308 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600309 interfaces;
310 msg.read(objPath, interfaces);
311
312 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600313 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600314 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600315 populateSysProperties(itIntf->second);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600316 }
317
Brandon Wyman510acaa2020-11-05 18:32:04 -0600318 itIntf = interfaces.find(IBMCFFPSInterface);
319 if (itIntf != interfaces.cend())
320 {
321 log<level::INFO>(
322 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
323 .c_str());
324 getPSUProperties(itIntf->second);
325 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000326
327 // Call to validate the psu configuration if the power is on and both
328 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
329 // processed
330 if (powerOn && !psus.empty() && !supportedConfigs.empty())
331 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000332 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000333 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600334 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500335 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600336 {
337 // Ignore, the property may be of a different type than expected.
338 }
339}
340
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500341void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
342{
343 int32_t state = 0;
344 std::string msgSensor;
Patrick Williamsabe49412020-05-13 17:59:47 -0500345 std::map<std::string, std::variant<int32_t>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500346 msg.read(msgSensor, msgData);
347
348 // Check if it was the Present property that changed.
349 auto valPropMap = msgData.find("state");
350 if (valPropMap != msgData.end())
351 {
352 state = std::get<int32_t>(valPropMap->second);
353
354 // Power is on when state=1. Clear faults.
355 if (state)
356 {
357 powerOn = true;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000358 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500359 clearFaults();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000360 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500361 }
362 else
363 {
364 powerOn = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000365 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500366 }
367 }
368}
369
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000370void PSUManager::presenceChanged(sdbusplus::message::message& msg)
371{
372 std::string msgSensor;
373 std::map<std::string, std::variant<uint32_t, bool>> msgData;
374 msg.read(msgSensor, msgData);
375
376 // Check if it was the Present property that changed.
377 auto valPropMap = msgData.find(PRESENT_PROP);
378 if (valPropMap != msgData.end())
379 {
380 if (std::get<bool>(valPropMap->second))
381 {
382 // A PSU became present, force the PSU validation to run.
383 runValidateConfig = true;
384 validationTimer->restartOnce(validationTimeout);
385 }
386 }
387}
388
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000389void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
390{
391 using namespace sdbusplus::xyz::openbmc_project;
392 constexpr auto service = "org.openbmc.control.Power";
393 constexpr auto objPath = "/org/openbmc/control/power0";
394 constexpr auto interface = "org.openbmc.control.Power";
395 constexpr auto method = "setPowerSupplyError";
396
397 try
398 {
399 // Call D-Bus method to inform pseq of PSU error
400 auto methodMsg =
401 bus.new_method_call(service, objPath, interface, method);
402 methodMsg.append(psuErrorString);
403 auto callReply = bus.call(methodMsg);
404 }
405 catch (const std::exception& e)
406 {
407 log<level::INFO>(
408 fmt::format("Failed calling setPowerSupplyError due to error {}",
409 e.what())
410 .c_str());
411 }
412}
413
Brandon Wyman8b662882021-10-08 17:31:51 +0000414void PSUManager::createError(const std::string& faultName,
415 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500416{
417 using namespace sdbusplus::xyz::openbmc_project;
418 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
419 constexpr auto loggingCreateInterface =
420 "xyz.openbmc_project.Logging.Create";
421
422 try
423 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000424 additionalData["_PID"] = std::to_string(getpid());
425
Brandon Wymanb76ab242020-09-16 18:06:06 -0500426 auto service =
427 util::getService(loggingObjectPath, loggingCreateInterface, bus);
428
429 if (service.empty())
430 {
431 log<level::ERR>("Unable to get logging manager service");
432 return;
433 }
434
435 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
436 loggingCreateInterface, "Create");
437
438 auto level = Logging::server::Entry::Level::Error;
439 method.append(faultName, level, additionalData);
440
441 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000442 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500443 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500444 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500445 {
446 log<level::ERR>(
447 fmt::format(
448 "Failed creating event log for fault {} due to error {}",
449 faultName, e.what())
450 .c_str());
451 }
452}
453
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500454void PSUManager::analyze()
455{
456 for (auto& psu : psus)
457 {
458 psu->analyze();
459 }
460
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000461 std::map<std::string, std::string> additionalData;
462
463 auto notPresentCount = decltype(psus.size())(
464 std::count_if(psus.begin(), psus.end(),
465 [](const auto& psu) { return !psu->isPresent(); }));
466
467 auto hasVINUVFaultCount = decltype(psus.size())(
468 std::count_if(psus.begin(), psus.end(),
469 [](const auto& psu) { return psu->hasVINUVFault(); }));
470
471 // The PSU D-Bus objects may not be available yet, so ignore if all
472 // PSUs are not present or the number of PSUs is still 0.
473 if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) &&
474 (psus.size() != notPresentCount) && (psus.size() != 0))
475 {
476 // Brownout: All PSUs report an AC failure: At least one PSU reports
477 // AC loss VIN fault and the rest either report AC loss VIN fault as
478 // well or are not present.
479 additionalData["NOT_PRESENT_COUNT"] = std::to_string(notPresentCount);
480 additionalData["VIN_FAULT_COUNT"] = std::to_string(hasVINUVFaultCount);
481 setBrownout(additionalData);
482 }
483 else
484 {
485 // Brownout condition is not present or has been cleared
486 clearBrownout();
487 }
488
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600489 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500490 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600491 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500492 {
Brandon Wymanec0b8dc2021-10-08 21:49:43 +0000493 additionalData.clear();
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000494
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600495 if (!psu->isFaultLogged() && !psu->isPresent())
496 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000497 std::map<std::string, std::string> requiredPSUsData;
498 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000499 if (!requiredPSUsPresent)
500 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000501 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000502 // Create error for power supply missing.
503 additionalData["CALLOUT_INVENTORY_PATH"] =
504 psu->getInventoryPath();
505 additionalData["CALLOUT_PRIORITY"] = "H";
506 createError(
507 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
508 additionalData);
509 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600510 psu->setFaultLogged();
511 }
512 else if (!psu->isFaultLogged() && psu->isFaulted())
513 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000514 // Add STATUS_WORD and STATUS_MFR last response, in padded
515 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600516 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000517 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600518 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000519 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600520 // If there are faults being reported, they possibly could be
521 // related to a bug in the firmware version running on the power
522 // supply. Capture that data into the error as well.
523 additionalData["FW_VERSION"] = psu->getFWVersion();
524
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000525 if (psu->hasCommFault())
526 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000527 additionalData["STATUS_CML"] =
528 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000529 /* Attempts to communicate with the power supply have
530 * reached there limit. Create an error. */
531 additionalData["CALLOUT_DEVICE_PATH"] =
532 psu->getDevicePath();
533
534 createError(
535 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
536 additionalData);
537
538 psu->setFaultLogged();
539 }
540 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600541 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000542 // Include STATUS_INPUT for input faults.
543 additionalData["STATUS_INPUT"] =
544 fmt::format("{:#02x}", psu->getStatusInput());
545
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600546 /* The power supply location might be needed if the input
547 * fault is due to a problem with the power supply itself.
548 * Include the inventory path with a call out priority of
549 * low.
550 */
551 additionalData["CALLOUT_INVENTORY_PATH"] =
552 psu->getInventoryPath();
553 additionalData["CALLOUT_PRIORITY"] = "L";
554 createError("xyz.openbmc_project.Power.PowerSupply.Error."
555 "InputFault",
556 additionalData);
557 psu->setFaultLogged();
558 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000559 else if (psu->hasPSKillFault())
560 {
561 createError(
562 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
563 additionalData);
564 psu->setFaultLogged();
565 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000566 else if (psu->hasVoutOVFault())
567 {
568 // Include STATUS_VOUT for Vout faults.
569 additionalData["STATUS_VOUT"] =
570 fmt::format("{:#02x}", psu->getStatusVout());
571
572 additionalData["CALLOUT_INVENTORY_PATH"] =
573 psu->getInventoryPath();
574
575 createError(
576 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
577 additionalData);
578
579 psu->setFaultLogged();
580 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000581 else if (psu->hasIoutOCFault())
582 {
583 // Include STATUS_IOUT for Iout faults.
584 additionalData["STATUS_IOUT"] =
585 fmt::format("{:#02x}", psu->getStatusIout());
586
587 createError(
588 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
589 additionalData);
590
591 psu->setFaultLogged();
592 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000593 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
594 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000595 {
596 // Include STATUS_VOUT for Vout faults.
597 additionalData["STATUS_VOUT"] =
598 fmt::format("{:#02x}", psu->getStatusVout());
599
600 additionalData["CALLOUT_INVENTORY_PATH"] =
601 psu->getInventoryPath();
602
603 createError(
604 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
605 additionalData);
606
607 psu->setFaultLogged();
608 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000609 // A fan fault should have priority over a temperature fault,
610 // since a failed fan may lead to a temperature problem.
611 else if (psu->hasFanFault())
612 {
613 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
614 additionalData["STATUS_TEMPERATURE"] =
615 fmt::format("{:#02x}", psu->getStatusTemperature());
616 additionalData["STATUS_FANS_1_2"] =
617 fmt::format("{:#02x}", psu->getStatusFans12());
618
619 additionalData["CALLOUT_INVENTORY_PATH"] =
620 psu->getInventoryPath();
621
622 createError(
623 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
624 additionalData);
625
626 psu->setFaultLogged();
627 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000628 else if (psu->hasTempFault())
629 {
630 // Include STATUS_TEMPERATURE for temperature faults.
631 additionalData["STATUS_TEMPERATURE"] =
632 fmt::format("{:#02x}", psu->getStatusTemperature());
633
634 additionalData["CALLOUT_INVENTORY_PATH"] =
635 psu->getInventoryPath();
636
637 createError(
638 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
639 additionalData);
640
641 psu->setFaultLogged();
642 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600643 else if (psu->hasMFRFault())
644 {
645 /* This can represent a variety of faults that result in
646 * calling out the power supply for replacement: Output
647 * OverCurrent, Output Under Voltage, and potentially other
648 * faults.
649 *
650 * Also plan on putting specific fault in AdditionalData,
651 * along with register names and register values
652 * (STATUS_WORD, STATUS_MFR, etc.).*/
653
654 additionalData["CALLOUT_INVENTORY_PATH"] =
655 psu->getInventoryPath();
656
657 createError(
658 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500659 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500660
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600661 psu->setFaultLogged();
662 }
Brandon Wyman2916ea52021-11-06 03:31:18 +0000663 else if (psu->hasPgoodFault())
664 {
665 /* POWER_GOOD# is not low, or OFF is on */
666 additionalData["CALLOUT_INVENTORY_PATH"] =
667 psu->getInventoryPath();
668
669 createError(
670 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
671 additionalData);
672
673 psu->setFaultLogged();
674 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500675 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500676 }
677 }
678}
679
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000680void PSUManager::validateConfig()
681{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000682 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000683 {
684 return;
685 }
686
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000687 std::map<std::string, std::string> additionalData;
688 auto supported = hasRequiredPSUs(additionalData);
689 if (supported)
690 {
691 runValidateConfig = false;
692 return;
693 }
694
695 // Validation failed, create an error log.
696 // Return without setting the runValidateConfig flag to false because
697 // it may be that an additional supported configuration interface is
698 // added and we need to validate it to see if it matches this system.
699 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
700 additionalData);
701}
702
703bool PSUManager::hasRequiredPSUs(
704 std::map<std::string, std::string>& additionalData)
705{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000706 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000707 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000708 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000709 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000710 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000711
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000712 auto presentCount =
713 std::count_if(psus.begin(), psus.end(),
714 [](const auto& psu) { return psu->isPresent(); });
715
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000716 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000717 // power supply model configuration. Since all configurations need to be
718 // checked, the additional data would contain only the information of the
719 // last configuration that did not match.
720 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000721 for (const auto& config : supportedConfigs)
722 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000723 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000724 {
725 continue;
726 }
727 if (presentCount != config.second.powerSupplyCount)
728 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000729 tmpAdditionalData.clear();
730 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000731 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000732 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000733 continue;
734 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000735
736 bool voltageValidated = true;
737 for (const auto& psu : psus)
738 {
739 if (!psu->isPresent())
740 {
741 // Only present PSUs report a valid input voltage
742 continue;
743 }
744
745 double actualInputVoltage;
746 int inputVoltage;
747 psu->getInputVoltage(actualInputVoltage, inputVoltage);
748
749 if (std::find(config.second.inputVoltage.begin(),
750 config.second.inputVoltage.end(),
751 inputVoltage) == config.second.inputVoltage.end())
752 {
753 tmpAdditionalData.clear();
754 tmpAdditionalData["ACTUAL_VOLTAGE"] =
755 std::to_string(actualInputVoltage);
756 for (const auto& voltage : config.second.inputVoltage)
757 {
758 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
759 std::to_string(voltage) + " ";
760 }
761 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
762 psu->getInventoryPath();
763
764 voltageValidated = false;
765 break;
766 }
767 }
768 if (!voltageValidated)
769 {
770 continue;
771 }
772
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000773 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000774 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000775
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000776 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000777 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000778}
779
Adriana Kobylak523704d2021-09-21 15:55:41 +0000780bool PSUManager::validateModelName(
781 std::string& model, std::map<std::string, std::string>& additionalData)
782{
783 // Check that all PSUs have the same model name. Initialize the model
784 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000785 // against the rest of the PSUs and get its inventory path to use as callout
786 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +0000787 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000788 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000789 for (const auto& psu : psus)
790 {
791 auto psuModel = psu->getModelName();
792 if (psuModel.empty())
793 {
794 continue;
795 }
796 if (model.empty())
797 {
798 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000799 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +0000800 continue;
801 }
802 if (psuModel != model)
803 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000804 if (supportedConfigs.find(model) != supportedConfigs.end())
805 {
806 // The base model is supported, callout the mismatched PSU. The
807 // mismatched PSU may or may not be supported.
808 additionalData["EXPECTED_MODEL"] = model;
809 additionalData["ACTUAL_MODEL"] = psuModel;
810 additionalData["CALLOUT_INVENTORY_PATH"] =
811 psu->getInventoryPath();
812 }
813 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
814 {
815 // The base model is not supported, but the mismatched PSU is,
816 // callout the base PSU.
817 additionalData["EXPECTED_MODEL"] = psuModel;
818 additionalData["ACTUAL_MODEL"] = model;
819 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
820 }
821 else
822 {
823 // The base model and the mismatched PSU are not supported or
824 // could not be found in the supported configuration, callout
825 // the mismatched PSU.
826 additionalData["EXPECTED_MODEL"] = model;
827 additionalData["ACTUAL_MODEL"] = psuModel;
828 additionalData["CALLOUT_INVENTORY_PATH"] =
829 psu->getInventoryPath();
830 }
Adriana Kobylak523704d2021-09-21 15:55:41 +0000831 model.clear();
832 return false;
833 }
834 }
835 return true;
836}
837
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000838void PSUManager::setPowerConfigGPIO()
839{
840 if (!powerConfigGPIO)
841 {
842 return;
843 }
844
845 std::string model{};
846 std::map<std::string, std::string> additionalData;
847 if (!validateModelName(model, additionalData))
848 {
849 return;
850 }
851
852 auto config = supportedConfigs.find(model);
853 if (config != supportedConfigs.end())
854 {
855 // The power-config-full-load is an open drain GPIO. Set it to low (0)
856 // if the supported configuration indicates that this system model
857 // expects the maximum number of power supplies (full load set to true).
858 // Else, set it to high (1), this is the default.
859 auto powerConfigValue =
860 (config->second.powerConfigFullLoad == true ? 0 : 1);
861 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
862 powerConfigGPIO->write(powerConfigValue, flags);
863 }
864}
865
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000866void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData)
867{
868 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
869 server::PowerSystemInputs::Status::Fault);
870 if (!brownoutLogged)
871 {
872 if (powerOn)
873 {
874 createError(
875 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
876 additionalData);
877 brownoutLogged = true;
878 }
879 }
880}
881
882void PSUManager::clearBrownout()
883{
884 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
885 server::PowerSystemInputs::Status::Good);
886 brownoutLogged = false;
887}
888
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500889} // namespace phosphor::power::manager