blob: acc3c2b44900411aed64bef65aa09355936347f5 [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),
Brandon Wymanc3324422022-03-24 20:30:57 +000033 objectManager(bus, objectManagerObjPath),
34 historyManager(bus, "/org/open_power/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060035{
Brandon Wyman510acaa2020-11-05 18:32:04 -060036 // Subscribe to InterfacesAdded before doing a property read, otherwise
37 // the interface could be created after the read attempt but before the
38 // match is created.
39 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
40 bus,
41 sdbusplus::bus::match::rules::interfacesAdded() +
42 sdbusplus::bus::match::rules::sender(
43 "xyz.openbmc_project.EntityManager"),
44 std::bind(&PSUManager::entityManagerIfaceAdded, this,
45 std::placeholders::_1));
46 getPSUConfiguration();
47 getSystemProperties();
48
Adriana Kobylakc9b05732022-03-19 15:15:10 +000049 // Request the bus name before the analyze() function, which is the one that
50 // determines the brownout condition and sets the status d-bus property.
51 bus.request_name(managerBusName);
52
Brandon Wyman510acaa2020-11-05 18:32:04 -060053 using namespace sdeventplus;
54 auto interval = std::chrono::milliseconds(1000);
55 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
56 e, std::bind(&PSUManager::analyze, this), interval);
57
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000058 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
59 e, std::bind(&PSUManager::validateConfig, this));
60
Adriana Kobylakc0a07582021-10-13 15:52:25 +000061 try
62 {
63 powerConfigGPIO = createGPIO("power-config-full-load");
64 }
65 catch (const std::exception& e)
66 {
67 // Ignore error, GPIO may not be implemented in this system.
68 powerConfigGPIO = nullptr;
69 }
70
Brandon Wyman510acaa2020-11-05 18:32:04 -060071 // Subscribe to power state changes
72 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
73 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
74 bus,
75 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
76 POWER_IFACE),
77 [this](auto& msg) { this->powerStateChanged(msg); });
78
79 initialize();
80}
81
Brandon Wyman510acaa2020-11-05 18:32:04 -060082void PSUManager::getPSUConfiguration()
83{
84 using namespace phosphor::power::util;
85 auto depth = 0;
86 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
87
88 psus.clear();
89
90 // I should get a map of objects back.
91 // Each object will have a path, a service, and an interface.
92 // The interface should match the one passed into this function.
93 for (const auto& [path, services] : objects)
94 {
95 auto service = services.begin()->first;
96
97 if (path.empty() || service.empty())
98 {
99 continue;
100 }
101
102 // For each object in the array of objects, I want to get properties
103 // from the service, path, and interface.
104 auto properties =
105 getAllProperties(bus, path, IBMCFFPSInterface, service);
106
107 getPSUProperties(properties);
108 }
109
110 if (psus.empty())
111 {
112 // Interface or properties not found. Let the Interfaces Added callback
113 // process the information once the interfaces are added to D-Bus.
114 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
115 }
116}
117
118void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
119{
120 // From passed in properties, I want to get: I2CBus, I2CAddress,
121 // and Name. Create a power supply object, using Name to build the inventory
122 // path.
123 const auto basePSUInvPath =
124 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
125 uint64_t* i2cbus = nullptr;
126 uint64_t* i2caddr = nullptr;
127 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000128 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600129
130 for (const auto& property : properties)
131 {
132 try
133 {
134 if (property.first == i2cBusProp)
135 {
136 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
137 }
138 else if (property.first == i2cAddressProp)
139 {
140 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
141 }
142 else if (property.first == psuNameProp)
143 {
144 psuname = std::get_if<std::string>(&properties[psuNameProp]);
145 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000146 else if (property.first == presLineName)
147 {
148 preslineptr =
149 std::get_if<std::string>(&properties[presLineName]);
150 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600151 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500152 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000153 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600154 }
155
156 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
157 {
158 std::string invpath = basePSUInvPath;
159 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000160 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600161
162 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
163
B. J. Wyman681b2a32021-04-20 22:31:22 +0000164 if (nullptr != preslineptr)
165 {
166 presline = *preslineptr;
167 }
168
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000169 auto invMatch =
170 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
171 return psu->getInventoryPath() == invpath;
172 });
173 if (invMatch != psus.end())
174 {
175 // This power supply has the same inventory path as the one with
176 // information just added to D-Bus.
177 // Changes to GPIO line name unlikely, so skip checking.
178 // Changes to the I2C bus and address unlikely, as that would
179 // require corresponding device tree updates.
180 // Return out to avoid duplicate object creation.
181 return;
182 }
183
Brandon Wymanc3324422022-03-24 20:30:57 +0000184 constexpr auto driver = "ibm-cffps";
B. J. Wyman681b2a32021-04-20 22:31:22 +0000185 log<level::DEBUG>(
Brandon Wymanc3324422022-03-24 20:30:57 +0000186 fmt::format(
187 "make PowerSupply bus: {} addr: {} driver: {} presline: {}",
188 *i2cbus, *i2caddr, driver, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000189 .c_str());
190 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
Brandon Wymanc3324422022-03-24 20:30:57 +0000191 *i2caddr, driver, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600192 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000193
194 // Subscribe to power supply presence changes
195 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
196 bus,
197 sdbusplus::bus::match::rules::propertiesChanged(invpath,
198 INVENTORY_IFACE),
199 [this](auto& msg) { this->presenceChanged(msg); });
200 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600201 }
202
203 if (psus.empty())
204 {
205 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
206 }
207}
208
Adriana Kobylake1074d82021-03-16 20:46:44 +0000209void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
210{
211 try
212 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000213 auto propIt = properties.find("SupportedType");
214 if (propIt == properties.end())
215 {
216 return;
217 }
218 const std::string* type = std::get_if<std::string>(&(propIt->second));
219 if ((type == nullptr) || (*type != "PowerSupply"))
220 {
221 return;
222 }
223
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000224 propIt = properties.find("SupportedModel");
225 if (propIt == properties.end())
226 {
227 return;
228 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000229 const std::string* model = std::get_if<std::string>(&(propIt->second));
230 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000231 {
232 return;
233 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000234
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000235 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000236 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000237 if (propIt != properties.end())
238 {
239 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
240 if (count != nullptr)
241 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000242 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000243 }
244 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000245 propIt = properties.find("InputVoltage");
246 if (propIt != properties.end())
247 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000248 const std::vector<uint64_t>* voltage =
249 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000250 if (voltage != nullptr)
251 {
252 sys.inputVoltage = *voltage;
253 }
254 }
255
Adriana Kobylak886574c2021-11-01 18:22:28 +0000256 // The PowerConfigFullLoad is an optional property, default it to false
257 // since that's the default value of the power-config-full-load GPIO.
258 sys.powerConfigFullLoad = false;
259 propIt = properties.find("PowerConfigFullLoad");
260 if (propIt != properties.end())
261 {
262 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
263 if (fullLoad != nullptr)
264 {
265 sys.powerConfigFullLoad = *fullLoad;
266 }
267 }
268
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000269 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000270 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500271 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000272 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000273}
274
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600275void PSUManager::getSystemProperties()
276{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600277
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600278 try
279 {
280 util::DbusSubtree subtree =
281 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000282 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600283 {
284 throw std::runtime_error("Supported Configuration Not Found");
285 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600286
Adriana Kobylake1074d82021-03-16 20:46:44 +0000287 for (const auto& [objPath, services] : subtree)
288 {
289 std::string service = services.begin()->first;
290 if (objPath.empty() || service.empty())
291 {
292 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600293 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000294 auto properties = util::getAllProperties(
295 bus, objPath, supportedConfIntf, service);
296 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600297 }
298 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500299 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600300 {
301 // Interface or property not found. Let the Interfaces Added callback
302 // process the information once the interfaces are added to D-Bus.
303 }
304}
305
Brandon Wyman3e429132021-03-18 18:03:14 -0500306void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600307{
308 try
309 {
310 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000311 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600312 interfaces;
313 msg.read(objPath, interfaces);
314
315 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600316 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600317 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600318 populateSysProperties(itIntf->second);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600319 }
320
Brandon Wyman510acaa2020-11-05 18:32:04 -0600321 itIntf = interfaces.find(IBMCFFPSInterface);
322 if (itIntf != interfaces.cend())
323 {
324 log<level::INFO>(
325 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
326 .c_str());
327 getPSUProperties(itIntf->second);
328 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000329
330 // Call to validate the psu configuration if the power is on and both
331 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
332 // processed
333 if (powerOn && !psus.empty() && !supportedConfigs.empty())
334 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000335 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000336 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600337 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500338 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600339 {
340 // Ignore, the property may be of a different type than expected.
341 }
342}
343
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500344void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
345{
346 int32_t state = 0;
347 std::string msgSensor;
Patrick Williamsabe49412020-05-13 17:59:47 -0500348 std::map<std::string, std::variant<int32_t>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500349 msg.read(msgSensor, msgData);
350
351 // Check if it was the Present property that changed.
352 auto valPropMap = msgData.find("state");
353 if (valPropMap != msgData.end())
354 {
355 state = std::get<int32_t>(valPropMap->second);
356
357 // Power is on when state=1. Clear faults.
358 if (state)
359 {
360 powerOn = true;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000361 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500362 clearFaults();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000363 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500364 }
365 else
366 {
367 powerOn = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000368 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500369 }
370 }
371}
372
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000373void PSUManager::presenceChanged(sdbusplus::message::message& msg)
374{
375 std::string msgSensor;
376 std::map<std::string, std::variant<uint32_t, bool>> msgData;
377 msg.read(msgSensor, msgData);
378
379 // Check if it was the Present property that changed.
380 auto valPropMap = msgData.find(PRESENT_PROP);
381 if (valPropMap != msgData.end())
382 {
383 if (std::get<bool>(valPropMap->second))
384 {
385 // A PSU became present, force the PSU validation to run.
386 runValidateConfig = true;
387 validationTimer->restartOnce(validationTimeout);
388 }
389 }
390}
391
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000392void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
393{
394 using namespace sdbusplus::xyz::openbmc_project;
395 constexpr auto service = "org.openbmc.control.Power";
396 constexpr auto objPath = "/org/openbmc/control/power0";
397 constexpr auto interface = "org.openbmc.control.Power";
398 constexpr auto method = "setPowerSupplyError";
399
400 try
401 {
402 // Call D-Bus method to inform pseq of PSU error
403 auto methodMsg =
404 bus.new_method_call(service, objPath, interface, method);
405 methodMsg.append(psuErrorString);
406 auto callReply = bus.call(methodMsg);
407 }
408 catch (const std::exception& e)
409 {
410 log<level::INFO>(
411 fmt::format("Failed calling setPowerSupplyError due to error {}",
412 e.what())
413 .c_str());
414 }
415}
416
Brandon Wyman8b662882021-10-08 17:31:51 +0000417void PSUManager::createError(const std::string& faultName,
418 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500419{
420 using namespace sdbusplus::xyz::openbmc_project;
421 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
422 constexpr auto loggingCreateInterface =
423 "xyz.openbmc_project.Logging.Create";
424
425 try
426 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000427 additionalData["_PID"] = std::to_string(getpid());
428
Brandon Wymanb76ab242020-09-16 18:06:06 -0500429 auto service =
430 util::getService(loggingObjectPath, loggingCreateInterface, bus);
431
432 if (service.empty())
433 {
434 log<level::ERR>("Unable to get logging manager service");
435 return;
436 }
437
438 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
439 loggingCreateInterface, "Create");
440
441 auto level = Logging::server::Entry::Level::Error;
442 method.append(faultName, level, additionalData);
443
444 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000445 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500446 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500447 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500448 {
449 log<level::ERR>(
450 fmt::format(
451 "Failed creating event log for fault {} due to error {}",
452 faultName, e.what())
453 .c_str());
454 }
455}
456
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500457void PSUManager::analyze()
458{
459 for (auto& psu : psus)
460 {
461 psu->analyze();
462 }
463
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000464 std::map<std::string, std::string> additionalData;
465
466 auto notPresentCount = decltype(psus.size())(
467 std::count_if(psus.begin(), psus.end(),
468 [](const auto& psu) { return !psu->isPresent(); }));
469
470 auto hasVINUVFaultCount = decltype(psus.size())(
471 std::count_if(psus.begin(), psus.end(),
472 [](const auto& psu) { return psu->hasVINUVFault(); }));
473
474 // The PSU D-Bus objects may not be available yet, so ignore if all
475 // PSUs are not present or the number of PSUs is still 0.
476 if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) &&
477 (psus.size() != notPresentCount) && (psus.size() != 0))
478 {
479 // Brownout: All PSUs report an AC failure: At least one PSU reports
480 // AC loss VIN fault and the rest either report AC loss VIN fault as
481 // well or are not present.
482 additionalData["NOT_PRESENT_COUNT"] = std::to_string(notPresentCount);
483 additionalData["VIN_FAULT_COUNT"] = std::to_string(hasVINUVFaultCount);
484 setBrownout(additionalData);
485 }
486 else
487 {
488 // Brownout condition is not present or has been cleared
489 clearBrownout();
490 }
491
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600492 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500493 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600494 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500495 {
Brandon Wymanec0b8dc2021-10-08 21:49:43 +0000496 additionalData.clear();
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000497
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600498 if (!psu->isFaultLogged() && !psu->isPresent())
499 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000500 std::map<std::string, std::string> requiredPSUsData;
501 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000502 if (!requiredPSUsPresent)
503 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000504 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000505 // Create error for power supply missing.
506 additionalData["CALLOUT_INVENTORY_PATH"] =
507 psu->getInventoryPath();
508 additionalData["CALLOUT_PRIORITY"] = "H";
509 createError(
510 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
511 additionalData);
512 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600513 psu->setFaultLogged();
514 }
515 else if (!psu->isFaultLogged() && psu->isFaulted())
516 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000517 // Add STATUS_WORD and STATUS_MFR last response, in padded
518 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600519 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000520 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600521 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000522 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600523 // If there are faults being reported, they possibly could be
524 // related to a bug in the firmware version running on the power
525 // supply. Capture that data into the error as well.
526 additionalData["FW_VERSION"] = psu->getFWVersion();
527
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000528 if (psu->hasCommFault())
529 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000530 additionalData["STATUS_CML"] =
531 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000532 /* Attempts to communicate with the power supply have
533 * reached there limit. Create an error. */
534 additionalData["CALLOUT_DEVICE_PATH"] =
535 psu->getDevicePath();
536
537 createError(
538 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
539 additionalData);
540
541 psu->setFaultLogged();
542 }
543 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600544 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000545 // Include STATUS_INPUT for input faults.
546 additionalData["STATUS_INPUT"] =
547 fmt::format("{:#02x}", psu->getStatusInput());
548
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600549 /* The power supply location might be needed if the input
550 * fault is due to a problem with the power supply itself.
551 * Include the inventory path with a call out priority of
552 * low.
553 */
554 additionalData["CALLOUT_INVENTORY_PATH"] =
555 psu->getInventoryPath();
556 additionalData["CALLOUT_PRIORITY"] = "L";
557 createError("xyz.openbmc_project.Power.PowerSupply.Error."
558 "InputFault",
559 additionalData);
560 psu->setFaultLogged();
561 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000562 else if (psu->hasPSKillFault())
563 {
564 createError(
565 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
566 additionalData);
567 psu->setFaultLogged();
568 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000569 else if (psu->hasVoutOVFault())
570 {
571 // Include STATUS_VOUT for Vout faults.
572 additionalData["STATUS_VOUT"] =
573 fmt::format("{:#02x}", psu->getStatusVout());
574
575 additionalData["CALLOUT_INVENTORY_PATH"] =
576 psu->getInventoryPath();
577
578 createError(
579 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
580 additionalData);
581
582 psu->setFaultLogged();
583 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000584 else if (psu->hasIoutOCFault())
585 {
586 // Include STATUS_IOUT for Iout faults.
587 additionalData["STATUS_IOUT"] =
588 fmt::format("{:#02x}", psu->getStatusIout());
589
590 createError(
591 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
592 additionalData);
593
594 psu->setFaultLogged();
595 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000596 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
597 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000598 {
599 // Include STATUS_VOUT for Vout faults.
600 additionalData["STATUS_VOUT"] =
601 fmt::format("{:#02x}", psu->getStatusVout());
602
603 additionalData["CALLOUT_INVENTORY_PATH"] =
604 psu->getInventoryPath();
605
606 createError(
607 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
608 additionalData);
609
610 psu->setFaultLogged();
611 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000612 // A fan fault should have priority over a temperature fault,
613 // since a failed fan may lead to a temperature problem.
614 else if (psu->hasFanFault())
615 {
616 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
617 additionalData["STATUS_TEMPERATURE"] =
618 fmt::format("{:#02x}", psu->getStatusTemperature());
619 additionalData["STATUS_FANS_1_2"] =
620 fmt::format("{:#02x}", psu->getStatusFans12());
621
622 additionalData["CALLOUT_INVENTORY_PATH"] =
623 psu->getInventoryPath();
624
625 createError(
626 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
627 additionalData);
628
629 psu->setFaultLogged();
630 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000631 else if (psu->hasTempFault())
632 {
633 // Include STATUS_TEMPERATURE for temperature faults.
634 additionalData["STATUS_TEMPERATURE"] =
635 fmt::format("{:#02x}", psu->getStatusTemperature());
636
637 additionalData["CALLOUT_INVENTORY_PATH"] =
638 psu->getInventoryPath();
639
640 createError(
641 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
642 additionalData);
643
644 psu->setFaultLogged();
645 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600646 else if (psu->hasMFRFault())
647 {
648 /* This can represent a variety of faults that result in
649 * calling out the power supply for replacement: Output
650 * OverCurrent, Output Under Voltage, and potentially other
651 * faults.
652 *
653 * Also plan on putting specific fault in AdditionalData,
654 * along with register names and register values
655 * (STATUS_WORD, STATUS_MFR, etc.).*/
656
657 additionalData["CALLOUT_INVENTORY_PATH"] =
658 psu->getInventoryPath();
659
660 createError(
661 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500662 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500663
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600664 psu->setFaultLogged();
665 }
Brandon Wyman2916ea52021-11-06 03:31:18 +0000666 else if (psu->hasPgoodFault())
667 {
668 /* POWER_GOOD# is not low, or OFF is on */
669 additionalData["CALLOUT_INVENTORY_PATH"] =
670 psu->getInventoryPath();
671
672 createError(
673 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
674 additionalData);
675
676 psu->setFaultLogged();
677 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500678 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500679 }
680 }
681}
682
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000683void PSUManager::validateConfig()
684{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000685 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000686 {
687 return;
688 }
689
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000690 std::map<std::string, std::string> additionalData;
691 auto supported = hasRequiredPSUs(additionalData);
692 if (supported)
693 {
694 runValidateConfig = false;
695 return;
696 }
697
698 // Validation failed, create an error log.
699 // Return without setting the runValidateConfig flag to false because
700 // it may be that an additional supported configuration interface is
701 // added and we need to validate it to see if it matches this system.
702 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
703 additionalData);
704}
705
706bool PSUManager::hasRequiredPSUs(
707 std::map<std::string, std::string>& additionalData)
708{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000709 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000710 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000711 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000712 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000713 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000714
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000715 auto presentCount =
716 std::count_if(psus.begin(), psus.end(),
717 [](const auto& psu) { return psu->isPresent(); });
718
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000719 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000720 // power supply model configuration. Since all configurations need to be
721 // checked, the additional data would contain only the information of the
722 // last configuration that did not match.
723 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000724 for (const auto& config : supportedConfigs)
725 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000726 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000727 {
728 continue;
729 }
730 if (presentCount != config.second.powerSupplyCount)
731 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000732 tmpAdditionalData.clear();
733 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000734 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000735 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000736 continue;
737 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000738
739 bool voltageValidated = true;
740 for (const auto& psu : psus)
741 {
742 if (!psu->isPresent())
743 {
744 // Only present PSUs report a valid input voltage
745 continue;
746 }
747
748 double actualInputVoltage;
749 int inputVoltage;
750 psu->getInputVoltage(actualInputVoltage, inputVoltage);
751
752 if (std::find(config.second.inputVoltage.begin(),
753 config.second.inputVoltage.end(),
754 inputVoltage) == config.second.inputVoltage.end())
755 {
756 tmpAdditionalData.clear();
757 tmpAdditionalData["ACTUAL_VOLTAGE"] =
758 std::to_string(actualInputVoltage);
759 for (const auto& voltage : config.second.inputVoltage)
760 {
761 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
762 std::to_string(voltage) + " ";
763 }
764 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
765 psu->getInventoryPath();
766
767 voltageValidated = false;
768 break;
769 }
770 }
771 if (!voltageValidated)
772 {
773 continue;
774 }
775
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000776 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000777 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000778
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000779 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000780 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000781}
782
Adriana Kobylak523704d2021-09-21 15:55:41 +0000783bool PSUManager::validateModelName(
784 std::string& model, std::map<std::string, std::string>& additionalData)
785{
786 // Check that all PSUs have the same model name. Initialize the model
787 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000788 // against the rest of the PSUs and get its inventory path to use as callout
789 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +0000790 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000791 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000792 for (const auto& psu : psus)
793 {
794 auto psuModel = psu->getModelName();
795 if (psuModel.empty())
796 {
797 continue;
798 }
799 if (model.empty())
800 {
801 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000802 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +0000803 continue;
804 }
805 if (psuModel != model)
806 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000807 if (supportedConfigs.find(model) != supportedConfigs.end())
808 {
809 // The base model is supported, callout the mismatched PSU. The
810 // mismatched PSU may or may not be supported.
811 additionalData["EXPECTED_MODEL"] = model;
812 additionalData["ACTUAL_MODEL"] = psuModel;
813 additionalData["CALLOUT_INVENTORY_PATH"] =
814 psu->getInventoryPath();
815 }
816 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
817 {
818 // The base model is not supported, but the mismatched PSU is,
819 // callout the base PSU.
820 additionalData["EXPECTED_MODEL"] = psuModel;
821 additionalData["ACTUAL_MODEL"] = model;
822 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
823 }
824 else
825 {
826 // The base model and the mismatched PSU are not supported or
827 // could not be found in the supported configuration, callout
828 // the mismatched PSU.
829 additionalData["EXPECTED_MODEL"] = model;
830 additionalData["ACTUAL_MODEL"] = psuModel;
831 additionalData["CALLOUT_INVENTORY_PATH"] =
832 psu->getInventoryPath();
833 }
Adriana Kobylak523704d2021-09-21 15:55:41 +0000834 model.clear();
835 return false;
836 }
837 }
838 return true;
839}
840
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000841void PSUManager::setPowerConfigGPIO()
842{
843 if (!powerConfigGPIO)
844 {
845 return;
846 }
847
848 std::string model{};
849 std::map<std::string, std::string> additionalData;
850 if (!validateModelName(model, additionalData))
851 {
852 return;
853 }
854
855 auto config = supportedConfigs.find(model);
856 if (config != supportedConfigs.end())
857 {
858 // The power-config-full-load is an open drain GPIO. Set it to low (0)
859 // if the supported configuration indicates that this system model
860 // expects the maximum number of power supplies (full load set to true).
861 // Else, set it to high (1), this is the default.
862 auto powerConfigValue =
863 (config->second.powerConfigFullLoad == true ? 0 : 1);
864 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
865 powerConfigGPIO->write(powerConfigValue, flags);
866 }
867}
868
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000869void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData)
870{
871 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
872 server::PowerSystemInputs::Status::Fault);
873 if (!brownoutLogged)
874 {
875 if (powerOn)
876 {
877 createError(
878 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
879 additionalData);
880 brownoutLogged = true;
881 }
882 }
883}
884
885void PSUManager::clearBrownout()
886{
887 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
888 server::PowerSystemInputs::Status::Good);
889 brownoutLogged = false;
890}
891
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500892} // namespace phosphor::power::manager