blob: 13a35a3d92bcf73ecaa0e5fd2b9a52ae660452c5 [file] [log] [blame]
Brandon Wyman18a24d92022-04-19 22:48:34 +00001#include "config.h"
2
Brandon Wymana0f33ce2019-10-17 18:32:29 -05003#include "psu_manager.hpp"
4
5#include "utility.hpp"
6
Brandon Wymanb76ab242020-09-16 18:06:06 -05007#include <fmt/format.h>
8#include <sys/types.h>
9#include <unistd.h>
10
Brandon Wymanecbecbc2021-08-31 22:53:21 +000011#include <regex>
12
Brandon Wymanaed1f752019-11-25 18:10:52 -060013using namespace phosphor::logging;
14
Brandon Wyman63ea78b2020-09-24 16:49:09 -050015namespace phosphor::power::manager
Brandon Wymana0f33ce2019-10-17 18:32:29 -050016{
Adriana Kobylakc9b05732022-03-19 15:15:10 +000017constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
18constexpr auto objectManagerObjPath =
19 "/xyz/openbmc_project/power/power_supplies";
20constexpr auto powerSystemsInputsObjPath =
21 "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
Brandon Wymana0f33ce2019-10-17 18:32:29 -050022
Brandon Wyman510acaa2020-11-05 18:32:04 -060023constexpr auto IBMCFFPSInterface =
24 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
25constexpr auto i2cBusProp = "I2CBus";
26constexpr auto i2cAddressProp = "I2CAddress";
27constexpr auto psuNameProp = "Name";
B. J. Wyman681b2a32021-04-20 22:31:22 +000028constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060029
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060030constexpr auto supportedConfIntf =
31 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060032
Brandon Wymanc9e840e2022-05-10 20:48:41 +000033constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
Brandon Wyman18a24d92022-04-19 22:48:34 +000034
Brandon Wyman510acaa2020-11-05 18:32:04 -060035PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000036 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
Brandon Wymanc3324422022-03-24 20:30:57 +000037 objectManager(bus, objectManagerObjPath),
38 historyManager(bus, "/org/open_power/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060039{
Brandon Wyman510acaa2020-11-05 18:32:04 -060040 // Subscribe to InterfacesAdded before doing a property read, otherwise
41 // the interface could be created after the read attempt but before the
42 // match is created.
43 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
44 bus,
45 sdbusplus::bus::match::rules::interfacesAdded() +
46 sdbusplus::bus::match::rules::sender(
47 "xyz.openbmc_project.EntityManager"),
48 std::bind(&PSUManager::entityManagerIfaceAdded, this,
49 std::placeholders::_1));
50 getPSUConfiguration();
51 getSystemProperties();
52
Adriana Kobylakc9b05732022-03-19 15:15:10 +000053 // Request the bus name before the analyze() function, which is the one that
54 // determines the brownout condition and sets the status d-bus property.
55 bus.request_name(managerBusName);
56
Brandon Wyman510acaa2020-11-05 18:32:04 -060057 using namespace sdeventplus;
58 auto interval = std::chrono::milliseconds(1000);
59 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
60 e, std::bind(&PSUManager::analyze, this), interval);
61
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000062 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
63 e, std::bind(&PSUManager::validateConfig, this));
64
Adriana Kobylakc0a07582021-10-13 15:52:25 +000065 try
66 {
67 powerConfigGPIO = createGPIO("power-config-full-load");
68 }
69 catch (const std::exception& e)
70 {
71 // Ignore error, GPIO may not be implemented in this system.
72 powerConfigGPIO = nullptr;
73 }
74
Brandon Wyman510acaa2020-11-05 18:32:04 -060075 // Subscribe to power state changes
76 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
77 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
78 bus,
79 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
80 POWER_IFACE),
81 [this](auto& msg) { this->powerStateChanged(msg); });
82
83 initialize();
84}
85
Brandon Wyman510acaa2020-11-05 18:32:04 -060086void PSUManager::getPSUConfiguration()
87{
88 using namespace phosphor::power::util;
89 auto depth = 0;
90 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
91
92 psus.clear();
93
94 // I should get a map of objects back.
95 // Each object will have a path, a service, and an interface.
96 // The interface should match the one passed into this function.
97 for (const auto& [path, services] : objects)
98 {
99 auto service = services.begin()->first;
100
101 if (path.empty() || service.empty())
102 {
103 continue;
104 }
105
106 // For each object in the array of objects, I want to get properties
107 // from the service, path, and interface.
108 auto properties =
109 getAllProperties(bus, path, IBMCFFPSInterface, service);
110
111 getPSUProperties(properties);
112 }
113
114 if (psus.empty())
115 {
116 // Interface or properties not found. Let the Interfaces Added callback
117 // process the information once the interfaces are added to D-Bus.
118 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
119 }
120}
121
122void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
123{
124 // From passed in properties, I want to get: I2CBus, I2CAddress,
125 // and Name. Create a power supply object, using Name to build the inventory
126 // path.
127 const auto basePSUInvPath =
128 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
129 uint64_t* i2cbus = nullptr;
130 uint64_t* i2caddr = nullptr;
131 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000132 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600133
134 for (const auto& property : properties)
135 {
136 try
137 {
138 if (property.first == i2cBusProp)
139 {
140 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
141 }
142 else if (property.first == i2cAddressProp)
143 {
144 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
145 }
146 else if (property.first == psuNameProp)
147 {
148 psuname = std::get_if<std::string>(&properties[psuNameProp]);
149 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000150 else if (property.first == presLineName)
151 {
152 preslineptr =
153 std::get_if<std::string>(&properties[presLineName]);
154 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600155 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500156 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000157 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600158 }
159
160 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
161 {
162 std::string invpath = basePSUInvPath;
163 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000164 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600165
166 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
167
B. J. Wyman681b2a32021-04-20 22:31:22 +0000168 if (nullptr != preslineptr)
169 {
170 presline = *preslineptr;
171 }
172
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000173 auto invMatch =
174 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
175 return psu->getInventoryPath() == invpath;
176 });
177 if (invMatch != psus.end())
178 {
179 // This power supply has the same inventory path as the one with
180 // information just added to D-Bus.
181 // Changes to GPIO line name unlikely, so skip checking.
182 // Changes to the I2C bus and address unlikely, as that would
183 // require corresponding device tree updates.
184 // Return out to avoid duplicate object creation.
185 return;
186 }
187
Brandon Wymanc3324422022-03-24 20:30:57 +0000188 constexpr auto driver = "ibm-cffps";
B. J. Wyman681b2a32021-04-20 22:31:22 +0000189 log<level::DEBUG>(
Brandon Wymanc3324422022-03-24 20:30:57 +0000190 fmt::format(
191 "make PowerSupply bus: {} addr: {} driver: {} presline: {}",
192 *i2cbus, *i2caddr, driver, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000193 .c_str());
194 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
Brandon Wymanc3324422022-03-24 20:30:57 +0000195 *i2caddr, driver, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600196 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000197
198 // Subscribe to power supply presence changes
199 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
200 bus,
201 sdbusplus::bus::match::rules::propertiesChanged(invpath,
202 INVENTORY_IFACE),
203 [this](auto& msg) { this->presenceChanged(msg); });
204 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600205 }
206
207 if (psus.empty())
208 {
209 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
210 }
211}
212
Adriana Kobylake1074d82021-03-16 20:46:44 +0000213void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
214{
215 try
216 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000217 auto propIt = properties.find("SupportedType");
218 if (propIt == properties.end())
219 {
220 return;
221 }
222 const std::string* type = std::get_if<std::string>(&(propIt->second));
223 if ((type == nullptr) || (*type != "PowerSupply"))
224 {
225 return;
226 }
227
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000228 propIt = properties.find("SupportedModel");
229 if (propIt == properties.end())
230 {
231 return;
232 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000233 const std::string* model = std::get_if<std::string>(&(propIt->second));
234 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000235 {
236 return;
237 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000238
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000239 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000240 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000241 if (propIt != properties.end())
242 {
243 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
244 if (count != nullptr)
245 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000246 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000247 }
248 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000249 propIt = properties.find("InputVoltage");
250 if (propIt != properties.end())
251 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000252 const std::vector<uint64_t>* voltage =
253 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000254 if (voltage != nullptr)
255 {
256 sys.inputVoltage = *voltage;
257 }
258 }
259
Adriana Kobylak886574c2021-11-01 18:22:28 +0000260 // The PowerConfigFullLoad is an optional property, default it to false
261 // since that's the default value of the power-config-full-load GPIO.
262 sys.powerConfigFullLoad = false;
263 propIt = properties.find("PowerConfigFullLoad");
264 if (propIt != properties.end())
265 {
266 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
267 if (fullLoad != nullptr)
268 {
269 sys.powerConfigFullLoad = *fullLoad;
270 }
271 }
272
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000273 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000274 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500275 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000276 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000277}
278
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600279void PSUManager::getSystemProperties()
280{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600281
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600282 try
283 {
284 util::DbusSubtree subtree =
285 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000286 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600287 {
288 throw std::runtime_error("Supported Configuration Not Found");
289 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600290
Adriana Kobylake1074d82021-03-16 20:46:44 +0000291 for (const auto& [objPath, services] : subtree)
292 {
293 std::string service = services.begin()->first;
294 if (objPath.empty() || service.empty())
295 {
296 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600297 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000298 auto properties = util::getAllProperties(
299 bus, objPath, supportedConfIntf, service);
300 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600301 }
302 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500303 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600304 {
305 // Interface or property not found. Let the Interfaces Added callback
306 // process the information once the interfaces are added to D-Bus.
307 }
308}
309
Brandon Wyman3e429132021-03-18 18:03:14 -0500310void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600311{
312 try
313 {
314 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000315 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600316 interfaces;
317 msg.read(objPath, interfaces);
318
319 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600320 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600321 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600322 populateSysProperties(itIntf->second);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600323 }
324
Brandon Wyman510acaa2020-11-05 18:32:04 -0600325 itIntf = interfaces.find(IBMCFFPSInterface);
326 if (itIntf != interfaces.cend())
327 {
328 log<level::INFO>(
329 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
330 .c_str());
331 getPSUProperties(itIntf->second);
332 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000333
Brandon Wyman64e97752022-06-03 23:50:13 +0000334 updateMissingPSUs();
335
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000336 // Call to validate the psu configuration if the power is on and both
337 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
338 // processed
339 if (powerOn && !psus.empty() && !supportedConfigs.empty())
340 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000341 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000342 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600343 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500344 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600345 {
346 // Ignore, the property may be of a different type than expected.
347 }
348}
349
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500350void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
351{
352 int32_t state = 0;
353 std::string msgSensor;
Patrick Williamsabe49412020-05-13 17:59:47 -0500354 std::map<std::string, std::variant<int32_t>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500355 msg.read(msgSensor, msgData);
356
357 // Check if it was the Present property that changed.
358 auto valPropMap = msgData.find("state");
359 if (valPropMap != msgData.end())
360 {
361 state = std::get<int32_t>(valPropMap->second);
362
363 // Power is on when state=1. Clear faults.
364 if (state)
365 {
366 powerOn = true;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000367 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500368 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000369 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000370 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500371 }
372 else
373 {
374 powerOn = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000375 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500376 }
377 }
378}
379
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000380void PSUManager::presenceChanged(sdbusplus::message::message& msg)
381{
382 std::string msgSensor;
383 std::map<std::string, std::variant<uint32_t, bool>> msgData;
384 msg.read(msgSensor, msgData);
385
386 // Check if it was the Present property that changed.
387 auto valPropMap = msgData.find(PRESENT_PROP);
388 if (valPropMap != msgData.end())
389 {
390 if (std::get<bool>(valPropMap->second))
391 {
392 // A PSU became present, force the PSU validation to run.
393 runValidateConfig = true;
394 validationTimer->restartOnce(validationTimeout);
395 }
396 }
397}
398
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000399void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
400{
401 using namespace sdbusplus::xyz::openbmc_project;
402 constexpr auto service = "org.openbmc.control.Power";
403 constexpr auto objPath = "/org/openbmc/control/power0";
404 constexpr auto interface = "org.openbmc.control.Power";
405 constexpr auto method = "setPowerSupplyError";
406
407 try
408 {
409 // Call D-Bus method to inform pseq of PSU error
410 auto methodMsg =
411 bus.new_method_call(service, objPath, interface, method);
412 methodMsg.append(psuErrorString);
413 auto callReply = bus.call(methodMsg);
414 }
415 catch (const std::exception& e)
416 {
417 log<level::INFO>(
418 fmt::format("Failed calling setPowerSupplyError due to error {}",
419 e.what())
420 .c_str());
421 }
422}
423
Brandon Wyman8b662882021-10-08 17:31:51 +0000424void PSUManager::createError(const std::string& faultName,
425 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500426{
427 using namespace sdbusplus::xyz::openbmc_project;
428 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
429 constexpr auto loggingCreateInterface =
430 "xyz.openbmc_project.Logging.Create";
431
432 try
433 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000434 additionalData["_PID"] = std::to_string(getpid());
435
Brandon Wymanb76ab242020-09-16 18:06:06 -0500436 auto service =
437 util::getService(loggingObjectPath, loggingCreateInterface, bus);
438
439 if (service.empty())
440 {
441 log<level::ERR>("Unable to get logging manager service");
442 return;
443 }
444
445 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
446 loggingCreateInterface, "Create");
447
448 auto level = Logging::server::Entry::Level::Error;
449 method.append(faultName, level, additionalData);
450
451 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000452 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500453 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500454 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500455 {
456 log<level::ERR>(
457 fmt::format(
458 "Failed creating event log for fault {} due to error {}",
459 faultName, e.what())
460 .c_str());
461 }
462}
463
Brandon Wyman18a24d92022-04-19 22:48:34 +0000464void PSUManager::syncHistory()
465{
466 log<level::INFO>("Synchronize INPUT_HISTORY");
467
468 if (!syncHistoryGPIO)
469 {
470 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
471 }
472 if (syncHistoryGPIO)
473 {
474 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
475 syncHistoryGPIO->toggleLowHigh(delay);
476 for (auto& psu : psus)
477 {
478 psu->clearSyncHistoryRequired();
479 }
480 }
481
482 log<level::INFO>("Synchronize INPUT_HISTORY completed");
483}
484
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500485void PSUManager::analyze()
486{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000487 auto syncHistoryRequired =
488 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
489 return psu->isSyncHistoryRequired();
490 });
491 if (syncHistoryRequired)
492 {
493 syncHistory();
494 }
495
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500496 for (auto& psu : psus)
497 {
498 psu->analyze();
499 }
500
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000501 std::map<std::string, std::string> additionalData;
502
503 auto notPresentCount = decltype(psus.size())(
504 std::count_if(psus.begin(), psus.end(),
505 [](const auto& psu) { return !psu->isPresent(); }));
506
507 auto hasVINUVFaultCount = decltype(psus.size())(
508 std::count_if(psus.begin(), psus.end(),
509 [](const auto& psu) { return psu->hasVINUVFault(); }));
510
511 // The PSU D-Bus objects may not be available yet, so ignore if all
512 // PSUs are not present or the number of PSUs is still 0.
513 if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) &&
514 (psus.size() != notPresentCount) && (psus.size() != 0))
515 {
516 // Brownout: All PSUs report an AC failure: At least one PSU reports
517 // AC loss VIN fault and the rest either report AC loss VIN fault as
518 // well or are not present.
519 additionalData["NOT_PRESENT_COUNT"] = std::to_string(notPresentCount);
520 additionalData["VIN_FAULT_COUNT"] = std::to_string(hasVINUVFaultCount);
521 setBrownout(additionalData);
522 }
523 else
524 {
525 // Brownout condition is not present or has been cleared
526 clearBrownout();
527 }
528
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600529 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500530 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600531 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500532 {
Brandon Wymanec0b8dc2021-10-08 21:49:43 +0000533 additionalData.clear();
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000534
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600535 if (!psu->isFaultLogged() && !psu->isPresent())
536 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000537 std::map<std::string, std::string> requiredPSUsData;
538 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000539 if (!requiredPSUsPresent)
540 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000541 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000542 // Create error for power supply missing.
543 additionalData["CALLOUT_INVENTORY_PATH"] =
544 psu->getInventoryPath();
545 additionalData["CALLOUT_PRIORITY"] = "H";
546 createError(
547 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
548 additionalData);
549 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600550 psu->setFaultLogged();
551 }
552 else if (!psu->isFaultLogged() && psu->isFaulted())
553 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000554 // Add STATUS_WORD and STATUS_MFR last response, in padded
555 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600556 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000557 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600558 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000559 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600560 // If there are faults being reported, they possibly could be
561 // related to a bug in the firmware version running on the power
562 // supply. Capture that data into the error as well.
563 additionalData["FW_VERSION"] = psu->getFWVersion();
564
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000565 if (psu->hasCommFault())
566 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000567 additionalData["STATUS_CML"] =
568 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000569 /* Attempts to communicate with the power supply have
570 * reached there limit. Create an error. */
571 additionalData["CALLOUT_DEVICE_PATH"] =
572 psu->getDevicePath();
573
574 createError(
575 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
576 additionalData);
577
578 psu->setFaultLogged();
579 }
580 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600581 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000582 // Include STATUS_INPUT for input faults.
583 additionalData["STATUS_INPUT"] =
584 fmt::format("{:#02x}", psu->getStatusInput());
585
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600586 /* The power supply location might be needed if the input
587 * fault is due to a problem with the power supply itself.
588 * Include the inventory path with a call out priority of
589 * low.
590 */
591 additionalData["CALLOUT_INVENTORY_PATH"] =
592 psu->getInventoryPath();
593 additionalData["CALLOUT_PRIORITY"] = "L";
594 createError("xyz.openbmc_project.Power.PowerSupply.Error."
595 "InputFault",
596 additionalData);
597 psu->setFaultLogged();
598 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000599 else if (psu->hasPSKillFault())
600 {
601 createError(
602 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
603 additionalData);
604 psu->setFaultLogged();
605 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000606 else if (psu->hasVoutOVFault())
607 {
608 // Include STATUS_VOUT for Vout faults.
609 additionalData["STATUS_VOUT"] =
610 fmt::format("{:#02x}", psu->getStatusVout());
611
612 additionalData["CALLOUT_INVENTORY_PATH"] =
613 psu->getInventoryPath();
614
615 createError(
616 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
617 additionalData);
618
619 psu->setFaultLogged();
620 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000621 else if (psu->hasIoutOCFault())
622 {
623 // Include STATUS_IOUT for Iout faults.
624 additionalData["STATUS_IOUT"] =
625 fmt::format("{:#02x}", psu->getStatusIout());
626
627 createError(
628 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
629 additionalData);
630
631 psu->setFaultLogged();
632 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000633 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
634 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000635 {
636 // Include STATUS_VOUT for Vout faults.
637 additionalData["STATUS_VOUT"] =
638 fmt::format("{:#02x}", psu->getStatusVout());
639
640 additionalData["CALLOUT_INVENTORY_PATH"] =
641 psu->getInventoryPath();
642
643 createError(
644 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
645 additionalData);
646
647 psu->setFaultLogged();
648 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000649 // A fan fault should have priority over a temperature fault,
650 // since a failed fan may lead to a temperature problem.
651 else if (psu->hasFanFault())
652 {
653 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
654 additionalData["STATUS_TEMPERATURE"] =
655 fmt::format("{:#02x}", psu->getStatusTemperature());
656 additionalData["STATUS_FANS_1_2"] =
657 fmt::format("{:#02x}", psu->getStatusFans12());
658
659 additionalData["CALLOUT_INVENTORY_PATH"] =
660 psu->getInventoryPath();
661
662 createError(
663 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
664 additionalData);
665
666 psu->setFaultLogged();
667 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000668 else if (psu->hasTempFault())
669 {
670 // Include STATUS_TEMPERATURE for temperature faults.
671 additionalData["STATUS_TEMPERATURE"] =
672 fmt::format("{:#02x}", psu->getStatusTemperature());
673
674 additionalData["CALLOUT_INVENTORY_PATH"] =
675 psu->getInventoryPath();
676
677 createError(
678 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
679 additionalData);
680
681 psu->setFaultLogged();
682 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600683 else if (psu->hasMFRFault())
684 {
685 /* This can represent a variety of faults that result in
686 * calling out the power supply for replacement: Output
687 * OverCurrent, Output Under Voltage, and potentially other
688 * faults.
689 *
690 * Also plan on putting specific fault in AdditionalData,
691 * along with register names and register values
692 * (STATUS_WORD, STATUS_MFR, etc.).*/
693
694 additionalData["CALLOUT_INVENTORY_PATH"] =
695 psu->getInventoryPath();
696
697 createError(
698 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500699 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500700
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600701 psu->setFaultLogged();
702 }
Brandon Wyman2916ea52021-11-06 03:31:18 +0000703 else if (psu->hasPgoodFault())
704 {
705 /* POWER_GOOD# is not low, or OFF is on */
706 additionalData["CALLOUT_INVENTORY_PATH"] =
707 psu->getInventoryPath();
708
709 createError(
710 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
711 additionalData);
712
713 psu->setFaultLogged();
714 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500715 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500716 }
717 }
718}
719
Brandon Wyman64e97752022-06-03 23:50:13 +0000720void PSUManager::updateMissingPSUs()
721{
722 if (supportedConfigs.empty() || psus.empty())
723 {
724 return;
725 }
726
727 // Power supplies default to missing. If the power supply is present,
728 // the PowerSupply object will update the inventory Present property to
729 // true. If we have less than the required number of power supplies, and
730 // this power supply is missing, update the inventory Present property
731 // to false to indicate required power supply is missing. Avoid
732 // indicating power supply missing if not required.
733
734 auto presentCount =
735 std::count_if(psus.begin(), psus.end(),
736 [](const auto& psu) { return psu->isPresent(); });
737
738 for (const auto& config : supportedConfigs)
739 {
740 for (const auto& psu : psus)
741 {
742 auto psuModel = psu->getModelName();
743 auto psuShortName = psu->getShortName();
744 auto psuInventoryPath = psu->getInventoryPath();
745 auto relativeInvPath =
746 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
747 auto psuPresent = psu->isPresent();
748 auto presProperty = false;
749 auto propReadFail = false;
750
751 try
752 {
753 presProperty = getPresence(bus, psuInventoryPath);
754 propReadFail = false;
755 }
756 catch (const sdbusplus::exception::exception& e)
757 {
758 propReadFail = true;
759 // Relying on property change or interface added to retry.
760 // Log an informational trace to the journal.
761 log<level::INFO>(
762 fmt::format("D-Bus property {} access failure exception",
763 psuInventoryPath)
764 .c_str());
765 }
766
767 if (psuModel.empty())
768 {
769 if (!propReadFail && (presProperty != psuPresent))
770 {
771 // We already have this property, and it is not false
772 // set Present to false
773 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
774 }
775 continue;
776 }
777
778 if (config.first != psuModel)
779 {
780 continue;
781 }
782
783 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
784 {
785 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
786 }
787 }
788 }
789}
790
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000791void PSUManager::validateConfig()
792{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000793 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000794 {
795 return;
796 }
797
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000798 for (const auto& psu : psus)
799 {
800 if ((psu->hasInputFault() || psu->hasVINUVFault()))
801 {
802 // Do not try to validate if input voltage fault present.
803 validationTimer->restartOnce(validationTimeout);
804 return;
805 }
806 }
807
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000808 std::map<std::string, std::string> additionalData;
809 auto supported = hasRequiredPSUs(additionalData);
810 if (supported)
811 {
812 runValidateConfig = false;
813 return;
814 }
815
816 // Validation failed, create an error log.
817 // Return without setting the runValidateConfig flag to false because
818 // it may be that an additional supported configuration interface is
819 // added and we need to validate it to see if it matches this system.
820 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
821 additionalData);
822}
823
824bool PSUManager::hasRequiredPSUs(
825 std::map<std::string, std::string>& additionalData)
826{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000827 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000828 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000829 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000830 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000831 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000832
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000833 auto presentCount =
834 std::count_if(psus.begin(), psus.end(),
835 [](const auto& psu) { return psu->isPresent(); });
836
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000837 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000838 // power supply model configuration. Since all configurations need to be
839 // checked, the additional data would contain only the information of the
840 // last configuration that did not match.
841 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000842 for (const auto& config : supportedConfigs)
843 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000844 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000845 {
846 continue;
847 }
Brandon Wyman64e97752022-06-03 23:50:13 +0000848
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000849 if (presentCount != config.second.powerSupplyCount)
850 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000851 tmpAdditionalData.clear();
852 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000853 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000854 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000855 continue;
856 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000857
858 bool voltageValidated = true;
859 for (const auto& psu : psus)
860 {
861 if (!psu->isPresent())
862 {
863 // Only present PSUs report a valid input voltage
864 continue;
865 }
866
867 double actualInputVoltage;
868 int inputVoltage;
869 psu->getInputVoltage(actualInputVoltage, inputVoltage);
870
871 if (std::find(config.second.inputVoltage.begin(),
872 config.second.inputVoltage.end(),
873 inputVoltage) == config.second.inputVoltage.end())
874 {
875 tmpAdditionalData.clear();
876 tmpAdditionalData["ACTUAL_VOLTAGE"] =
877 std::to_string(actualInputVoltage);
878 for (const auto& voltage : config.second.inputVoltage)
879 {
880 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
881 std::to_string(voltage) + " ";
882 }
883 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
884 psu->getInventoryPath();
885
886 voltageValidated = false;
887 break;
888 }
889 }
890 if (!voltageValidated)
891 {
892 continue;
893 }
894
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000895 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000896 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000897
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000898 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000899 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000900}
901
Adriana Kobylak523704d2021-09-21 15:55:41 +0000902bool PSUManager::validateModelName(
903 std::string& model, std::map<std::string, std::string>& additionalData)
904{
905 // Check that all PSUs have the same model name. Initialize the model
906 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000907 // against the rest of the PSUs and get its inventory path to use as callout
908 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +0000909 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000910 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000911 for (const auto& psu : psus)
912 {
913 auto psuModel = psu->getModelName();
914 if (psuModel.empty())
915 {
916 continue;
917 }
918 if (model.empty())
919 {
920 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000921 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +0000922 continue;
923 }
924 if (psuModel != model)
925 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000926 if (supportedConfigs.find(model) != supportedConfigs.end())
927 {
928 // The base model is supported, callout the mismatched PSU. The
929 // mismatched PSU may or may not be supported.
930 additionalData["EXPECTED_MODEL"] = model;
931 additionalData["ACTUAL_MODEL"] = psuModel;
932 additionalData["CALLOUT_INVENTORY_PATH"] =
933 psu->getInventoryPath();
934 }
935 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
936 {
937 // The base model is not supported, but the mismatched PSU is,
938 // callout the base PSU.
939 additionalData["EXPECTED_MODEL"] = psuModel;
940 additionalData["ACTUAL_MODEL"] = model;
941 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
942 }
943 else
944 {
945 // The base model and the mismatched PSU are not supported or
946 // could not be found in the supported configuration, callout
947 // the mismatched PSU.
948 additionalData["EXPECTED_MODEL"] = model;
949 additionalData["ACTUAL_MODEL"] = psuModel;
950 additionalData["CALLOUT_INVENTORY_PATH"] =
951 psu->getInventoryPath();
952 }
Adriana Kobylak523704d2021-09-21 15:55:41 +0000953 model.clear();
954 return false;
955 }
956 }
957 return true;
958}
959
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000960void PSUManager::setPowerConfigGPIO()
961{
962 if (!powerConfigGPIO)
963 {
964 return;
965 }
966
967 std::string model{};
968 std::map<std::string, std::string> additionalData;
969 if (!validateModelName(model, additionalData))
970 {
971 return;
972 }
973
974 auto config = supportedConfigs.find(model);
975 if (config != supportedConfigs.end())
976 {
977 // The power-config-full-load is an open drain GPIO. Set it to low (0)
978 // if the supported configuration indicates that this system model
979 // expects the maximum number of power supplies (full load set to true).
980 // Else, set it to high (1), this is the default.
981 auto powerConfigValue =
982 (config->second.powerConfigFullLoad == true ? 0 : 1);
983 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
984 powerConfigGPIO->write(powerConfigValue, flags);
985 }
986}
987
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000988void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData)
989{
990 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
991 server::PowerSystemInputs::Status::Fault);
992 if (!brownoutLogged)
993 {
994 if (powerOn)
995 {
996 createError(
997 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
998 additionalData);
999 brownoutLogged = true;
1000 }
1001 }
1002}
1003
1004void PSUManager::clearBrownout()
1005{
1006 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
1007 server::PowerSystemInputs::Status::Good);
1008 brownoutLogged = false;
1009}
1010
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001011} // namespace phosphor::power::manager