blob: 3ad94cc4a0fccd71f7a0b0e5bd9ec2ba2eec9830 [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{
15
Brandon Wyman510acaa2020-11-05 18:32:04 -060016constexpr auto IBMCFFPSInterface =
17 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
18constexpr auto i2cBusProp = "I2CBus";
19constexpr auto i2cAddressProp = "I2CAddress";
20constexpr auto psuNameProp = "Name";
B. J. Wyman681b2a32021-04-20 22:31:22 +000021constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060022
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060023constexpr auto supportedConfIntf =
24 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060025
Brandon Wyman510acaa2020-11-05 18:32:04 -060026PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) :
27 bus(bus)
28{
Brandon Wyman510acaa2020-11-05 18:32:04 -060029 // Subscribe to InterfacesAdded before doing a property read, otherwise
30 // the interface could be created after the read attempt but before the
31 // match is created.
32 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
33 bus,
34 sdbusplus::bus::match::rules::interfacesAdded() +
35 sdbusplus::bus::match::rules::sender(
36 "xyz.openbmc_project.EntityManager"),
37 std::bind(&PSUManager::entityManagerIfaceAdded, this,
38 std::placeholders::_1));
39 getPSUConfiguration();
40 getSystemProperties();
41
42 using namespace sdeventplus;
43 auto interval = std::chrono::milliseconds(1000);
44 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
45 e, std::bind(&PSUManager::analyze, this), interval);
46
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000047 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
48 e, std::bind(&PSUManager::validateConfig, this));
49
Adriana Kobylakc0a07582021-10-13 15:52:25 +000050 try
51 {
52 powerConfigGPIO = createGPIO("power-config-full-load");
53 }
54 catch (const std::exception& e)
55 {
56 // Ignore error, GPIO may not be implemented in this system.
57 powerConfigGPIO = nullptr;
58 }
59
Brandon Wyman510acaa2020-11-05 18:32:04 -060060 // Subscribe to power state changes
61 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
62 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
63 bus,
64 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
65 POWER_IFACE),
66 [this](auto& msg) { this->powerStateChanged(msg); });
67
68 initialize();
69}
70
Brandon Wyman510acaa2020-11-05 18:32:04 -060071void PSUManager::getPSUConfiguration()
72{
73 using namespace phosphor::power::util;
74 auto depth = 0;
75 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
76
77 psus.clear();
78
79 // I should get a map of objects back.
80 // Each object will have a path, a service, and an interface.
81 // The interface should match the one passed into this function.
82 for (const auto& [path, services] : objects)
83 {
84 auto service = services.begin()->first;
85
86 if (path.empty() || service.empty())
87 {
88 continue;
89 }
90
91 // For each object in the array of objects, I want to get properties
92 // from the service, path, and interface.
93 auto properties =
94 getAllProperties(bus, path, IBMCFFPSInterface, service);
95
96 getPSUProperties(properties);
97 }
98
99 if (psus.empty())
100 {
101 // Interface or properties not found. Let the Interfaces Added callback
102 // process the information once the interfaces are added to D-Bus.
103 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
104 }
105}
106
107void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
108{
109 // From passed in properties, I want to get: I2CBus, I2CAddress,
110 // and Name. Create a power supply object, using Name to build the inventory
111 // path.
112 const auto basePSUInvPath =
113 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
114 uint64_t* i2cbus = nullptr;
115 uint64_t* i2caddr = nullptr;
116 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000117 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600118
119 for (const auto& property : properties)
120 {
121 try
122 {
123 if (property.first == i2cBusProp)
124 {
125 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
126 }
127 else if (property.first == i2cAddressProp)
128 {
129 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
130 }
131 else if (property.first == psuNameProp)
132 {
133 psuname = std::get_if<std::string>(&properties[psuNameProp]);
134 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000135 else if (property.first == presLineName)
136 {
137 preslineptr =
138 std::get_if<std::string>(&properties[presLineName]);
139 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600140 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500141 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000142 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600143 }
144
145 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
146 {
147 std::string invpath = basePSUInvPath;
148 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000149 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600150
151 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
152
B. J. Wyman681b2a32021-04-20 22:31:22 +0000153 if (nullptr != preslineptr)
154 {
155 presline = *preslineptr;
156 }
157
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000158 auto invMatch =
159 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
160 return psu->getInventoryPath() == invpath;
161 });
162 if (invMatch != psus.end())
163 {
164 // This power supply has the same inventory path as the one with
165 // information just added to D-Bus.
166 // Changes to GPIO line name unlikely, so skip checking.
167 // Changes to the I2C bus and address unlikely, as that would
168 // require corresponding device tree updates.
169 // Return out to avoid duplicate object creation.
170 return;
171 }
172
B. J. Wyman681b2a32021-04-20 22:31:22 +0000173 log<level::DEBUG>(
174 fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
175 *i2cbus, *i2caddr, presline)
176 .c_str());
177 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
178 *i2caddr, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600179 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000180
181 // Subscribe to power supply presence changes
182 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
183 bus,
184 sdbusplus::bus::match::rules::propertiesChanged(invpath,
185 INVENTORY_IFACE),
186 [this](auto& msg) { this->presenceChanged(msg); });
187 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600188 }
189
190 if (psus.empty())
191 {
192 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
193 }
194}
195
Adriana Kobylake1074d82021-03-16 20:46:44 +0000196void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
197{
198 try
199 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000200 auto propIt = properties.find("SupportedType");
201 if (propIt == properties.end())
202 {
203 return;
204 }
205 const std::string* type = std::get_if<std::string>(&(propIt->second));
206 if ((type == nullptr) || (*type != "PowerSupply"))
207 {
208 return;
209 }
210
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000211 propIt = properties.find("SupportedModel");
212 if (propIt == properties.end())
213 {
214 return;
215 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000216 const std::string* model = std::get_if<std::string>(&(propIt->second));
217 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000218 {
219 return;
220 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000221
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000222 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000223 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000224 if (propIt != properties.end())
225 {
226 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
227 if (count != nullptr)
228 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000229 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000230 }
231 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000232 propIt = properties.find("InputVoltage");
233 if (propIt != properties.end())
234 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000235 const std::vector<uint64_t>* voltage =
236 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000237 if (voltage != nullptr)
238 {
239 sys.inputVoltage = *voltage;
240 }
241 }
242
Adriana Kobylak886574c2021-11-01 18:22:28 +0000243 // The PowerConfigFullLoad is an optional property, default it to false
244 // since that's the default value of the power-config-full-load GPIO.
245 sys.powerConfigFullLoad = false;
246 propIt = properties.find("PowerConfigFullLoad");
247 if (propIt != properties.end())
248 {
249 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
250 if (fullLoad != nullptr)
251 {
252 sys.powerConfigFullLoad = *fullLoad;
253 }
254 }
255
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000256 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000257 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500258 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000259 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000260}
261
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600262void PSUManager::getSystemProperties()
263{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600264
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600265 try
266 {
267 util::DbusSubtree subtree =
268 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000269 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600270 {
271 throw std::runtime_error("Supported Configuration Not Found");
272 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600273
Adriana Kobylake1074d82021-03-16 20:46:44 +0000274 for (const auto& [objPath, services] : subtree)
275 {
276 std::string service = services.begin()->first;
277 if (objPath.empty() || service.empty())
278 {
279 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600280 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000281 auto properties = util::getAllProperties(
282 bus, objPath, supportedConfIntf, service);
283 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600284 }
285 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500286 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600287 {
288 // Interface or property not found. Let the Interfaces Added callback
289 // process the information once the interfaces are added to D-Bus.
290 }
291}
292
Brandon Wyman3e429132021-03-18 18:03:14 -0500293void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600294{
295 try
296 {
297 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000298 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600299 interfaces;
300 msg.read(objPath, interfaces);
301
302 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600303 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600304 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600305 populateSysProperties(itIntf->second);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600306 }
307
Brandon Wyman510acaa2020-11-05 18:32:04 -0600308 itIntf = interfaces.find(IBMCFFPSInterface);
309 if (itIntf != interfaces.cend())
310 {
311 log<level::INFO>(
312 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
313 .c_str());
314 getPSUProperties(itIntf->second);
315 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000316
317 // Call to validate the psu configuration if the power is on and both
318 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
319 // processed
320 if (powerOn && !psus.empty() && !supportedConfigs.empty())
321 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000322 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000323 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600324 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500325 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600326 {
327 // Ignore, the property may be of a different type than expected.
328 }
329}
330
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500331void PSUManager::powerStateChanged(sdbusplus::message::message& msg)
332{
333 int32_t state = 0;
334 std::string msgSensor;
Patrick Williamsabe49412020-05-13 17:59:47 -0500335 std::map<std::string, std::variant<int32_t>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500336 msg.read(msgSensor, msgData);
337
338 // Check if it was the Present property that changed.
339 auto valPropMap = msgData.find("state");
340 if (valPropMap != msgData.end())
341 {
342 state = std::get<int32_t>(valPropMap->second);
343
344 // Power is on when state=1. Clear faults.
345 if (state)
346 {
347 powerOn = true;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000348 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500349 clearFaults();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000350 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500351 }
352 else
353 {
354 powerOn = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000355 runValidateConfig = true;
Adriana Kobylak2549d792022-01-26 20:51:30 +0000356 brownoutLogged = false;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500357 }
358 }
359}
360
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000361void PSUManager::presenceChanged(sdbusplus::message::message& msg)
362{
363 std::string msgSensor;
364 std::map<std::string, std::variant<uint32_t, bool>> msgData;
365 msg.read(msgSensor, msgData);
366
367 // Check if it was the Present property that changed.
368 auto valPropMap = msgData.find(PRESENT_PROP);
369 if (valPropMap != msgData.end())
370 {
371 if (std::get<bool>(valPropMap->second))
372 {
373 // A PSU became present, force the PSU validation to run.
374 runValidateConfig = true;
375 validationTimer->restartOnce(validationTimeout);
376 }
377 }
378}
379
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000380void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
381{
382 using namespace sdbusplus::xyz::openbmc_project;
383 constexpr auto service = "org.openbmc.control.Power";
384 constexpr auto objPath = "/org/openbmc/control/power0";
385 constexpr auto interface = "org.openbmc.control.Power";
386 constexpr auto method = "setPowerSupplyError";
387
388 try
389 {
390 // Call D-Bus method to inform pseq of PSU error
391 auto methodMsg =
392 bus.new_method_call(service, objPath, interface, method);
393 methodMsg.append(psuErrorString);
394 auto callReply = bus.call(methodMsg);
395 }
396 catch (const std::exception& e)
397 {
398 log<level::INFO>(
399 fmt::format("Failed calling setPowerSupplyError due to error {}",
400 e.what())
401 .c_str());
402 }
403}
404
Brandon Wyman8b662882021-10-08 17:31:51 +0000405void PSUManager::createError(const std::string& faultName,
406 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500407{
408 using namespace sdbusplus::xyz::openbmc_project;
409 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
410 constexpr auto loggingCreateInterface =
411 "xyz.openbmc_project.Logging.Create";
412
413 try
414 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000415 additionalData["_PID"] = std::to_string(getpid());
416
Brandon Wymanb76ab242020-09-16 18:06:06 -0500417 auto service =
418 util::getService(loggingObjectPath, loggingCreateInterface, bus);
419
420 if (service.empty())
421 {
422 log<level::ERR>("Unable to get logging manager service");
423 return;
424 }
425
426 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
427 loggingCreateInterface, "Create");
428
429 auto level = Logging::server::Entry::Level::Error;
430 method.append(faultName, level, additionalData);
431
432 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000433 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500434 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500435 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500436 {
437 log<level::ERR>(
438 fmt::format(
439 "Failed creating event log for fault {} due to error {}",
440 faultName, e.what())
441 .c_str());
442 }
443}
444
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500445void PSUManager::analyze()
446{
447 for (auto& psu : psus)
448 {
449 psu->analyze();
450 }
451
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600452 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500453 {
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000454 std::map<std::string, std::string> additionalData;
Adriana Kobylake4c3a292022-02-10 16:07:03 +0000455
456 auto notPresentCount = decltype(psus.size())(
457 std::count_if(psus.begin(), psus.end(),
458 [](const auto& psu) { return !psu->isPresent(); }));
459
460 auto hasVINUVFaultCount = decltype(psus.size())(
461 std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
462 return psu->hasVINUVFault();
463 }));
464
465 // The PSU D-Bus objects may not be available yet, so ignore if all
466 // PSUs are not present or the number of PSUs is still 0.
467 if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) &&
468 (psus.size() != notPresentCount) && (psus.size() != 0))
469 {
470 // Brownout: All PSUs report an AC failure: At least one PSU reports
471 // AC loss VIN fault and the rest either report AC loss VIN fault as
472 // well or are not present.
473 if (!brownoutLogged)
474 {
475 createError(
476 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
477 additionalData);
478 brownoutLogged = true;
479 }
480 }
481 else
482 {
483 // Brownout condition is not present or has been cleared
484 brownoutLogged = false;
485 }
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000486
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600487 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500488 {
Brandon Wymanec0b8dc2021-10-08 21:49:43 +0000489 additionalData.clear();
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000490
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600491 if (!psu->isFaultLogged() && !psu->isPresent())
492 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000493 std::map<std::string, std::string> requiredPSUsData;
494 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000495 if (!requiredPSUsPresent)
496 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000497 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000498 // Create error for power supply missing.
499 additionalData["CALLOUT_INVENTORY_PATH"] =
500 psu->getInventoryPath();
501 additionalData["CALLOUT_PRIORITY"] = "H";
502 createError(
503 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
504 additionalData);
505 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600506 psu->setFaultLogged();
507 }
508 else if (!psu->isFaultLogged() && psu->isFaulted())
509 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000510 // Add STATUS_WORD and STATUS_MFR last response, in padded
511 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600512 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000513 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600514 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000515 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600516 // If there are faults being reported, they possibly could be
517 // related to a bug in the firmware version running on the power
518 // supply. Capture that data into the error as well.
519 additionalData["FW_VERSION"] = psu->getFWVersion();
520
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000521 if (psu->hasCommFault())
522 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000523 additionalData["STATUS_CML"] =
524 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000525 /* Attempts to communicate with the power supply have
526 * reached there limit. Create an error. */
527 additionalData["CALLOUT_DEVICE_PATH"] =
528 psu->getDevicePath();
529
530 createError(
531 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
532 additionalData);
533
534 psu->setFaultLogged();
535 }
536 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600537 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000538 // Include STATUS_INPUT for input faults.
539 additionalData["STATUS_INPUT"] =
540 fmt::format("{:#02x}", psu->getStatusInput());
541
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600542 /* The power supply location might be needed if the input
543 * fault is due to a problem with the power supply itself.
544 * Include the inventory path with a call out priority of
545 * low.
546 */
547 additionalData["CALLOUT_INVENTORY_PATH"] =
548 psu->getInventoryPath();
549 additionalData["CALLOUT_PRIORITY"] = "L";
550 createError("xyz.openbmc_project.Power.PowerSupply.Error."
551 "InputFault",
552 additionalData);
553 psu->setFaultLogged();
554 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000555 else if (psu->hasPSKillFault())
556 {
557 createError(
558 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
559 additionalData);
560 psu->setFaultLogged();
561 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000562 else if (psu->hasVoutOVFault())
563 {
564 // Include STATUS_VOUT for Vout faults.
565 additionalData["STATUS_VOUT"] =
566 fmt::format("{:#02x}", psu->getStatusVout());
567
568 additionalData["CALLOUT_INVENTORY_PATH"] =
569 psu->getInventoryPath();
570
571 createError(
572 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
573 additionalData);
574
575 psu->setFaultLogged();
576 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000577 else if (psu->hasIoutOCFault())
578 {
579 // Include STATUS_IOUT for Iout faults.
580 additionalData["STATUS_IOUT"] =
581 fmt::format("{:#02x}", psu->getStatusIout());
582
583 createError(
584 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
585 additionalData);
586
587 psu->setFaultLogged();
588 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000589 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
590 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000591 {
592 // Include STATUS_VOUT for Vout faults.
593 additionalData["STATUS_VOUT"] =
594 fmt::format("{:#02x}", psu->getStatusVout());
595
596 additionalData["CALLOUT_INVENTORY_PATH"] =
597 psu->getInventoryPath();
598
599 createError(
600 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
601 additionalData);
602
603 psu->setFaultLogged();
604 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000605 // A fan fault should have priority over a temperature fault,
606 // since a failed fan may lead to a temperature problem.
607 else if (psu->hasFanFault())
608 {
609 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
610 additionalData["STATUS_TEMPERATURE"] =
611 fmt::format("{:#02x}", psu->getStatusTemperature());
612 additionalData["STATUS_FANS_1_2"] =
613 fmt::format("{:#02x}", psu->getStatusFans12());
614
615 additionalData["CALLOUT_INVENTORY_PATH"] =
616 psu->getInventoryPath();
617
618 createError(
619 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
620 additionalData);
621
622 psu->setFaultLogged();
623 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000624 else if (psu->hasTempFault())
625 {
626 // Include STATUS_TEMPERATURE for temperature faults.
627 additionalData["STATUS_TEMPERATURE"] =
628 fmt::format("{:#02x}", psu->getStatusTemperature());
629
630 additionalData["CALLOUT_INVENTORY_PATH"] =
631 psu->getInventoryPath();
632
633 createError(
634 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
635 additionalData);
636
637 psu->setFaultLogged();
638 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600639 else if (psu->hasMFRFault())
640 {
641 /* This can represent a variety of faults that result in
642 * calling out the power supply for replacement: Output
643 * OverCurrent, Output Under Voltage, and potentially other
644 * faults.
645 *
646 * Also plan on putting specific fault in AdditionalData,
647 * along with register names and register values
648 * (STATUS_WORD, STATUS_MFR, etc.).*/
649
650 additionalData["CALLOUT_INVENTORY_PATH"] =
651 psu->getInventoryPath();
652
653 createError(
654 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500655 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500656
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600657 psu->setFaultLogged();
658 }
Brandon Wyman2916ea52021-11-06 03:31:18 +0000659 else if (psu->hasPgoodFault())
660 {
661 /* POWER_GOOD# is not low, or OFF is on */
662 additionalData["CALLOUT_INVENTORY_PATH"] =
663 psu->getInventoryPath();
664
665 createError(
666 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
667 additionalData);
668
669 psu->setFaultLogged();
670 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500671 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500672 }
673 }
674}
675
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000676void PSUManager::validateConfig()
677{
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000678 if (!runValidateConfig || supportedConfigs.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000679 {
680 return;
681 }
682
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000683 std::map<std::string, std::string> additionalData;
684 auto supported = hasRequiredPSUs(additionalData);
685 if (supported)
686 {
687 runValidateConfig = false;
688 return;
689 }
690
691 // Validation failed, create an error log.
692 // Return without setting the runValidateConfig flag to false because
693 // it may be that an additional supported configuration interface is
694 // added and we need to validate it to see if it matches this system.
695 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
696 additionalData);
697}
698
699bool PSUManager::hasRequiredPSUs(
700 std::map<std::string, std::string>& additionalData)
701{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000702 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000703 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000704 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000705 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000706 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000707
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000708 auto presentCount =
709 std::count_if(psus.begin(), psus.end(),
710 [](const auto& psu) { return psu->isPresent(); });
711
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000712 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000713 // power supply model configuration. Since all configurations need to be
714 // checked, the additional data would contain only the information of the
715 // last configuration that did not match.
716 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000717 for (const auto& config : supportedConfigs)
718 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000719 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000720 {
721 continue;
722 }
723 if (presentCount != config.second.powerSupplyCount)
724 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000725 tmpAdditionalData.clear();
726 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000727 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000728 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000729 continue;
730 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000731
732 bool voltageValidated = true;
733 for (const auto& psu : psus)
734 {
735 if (!psu->isPresent())
736 {
737 // Only present PSUs report a valid input voltage
738 continue;
739 }
740
741 double actualInputVoltage;
742 int inputVoltage;
743 psu->getInputVoltage(actualInputVoltage, inputVoltage);
744
745 if (std::find(config.second.inputVoltage.begin(),
746 config.second.inputVoltage.end(),
747 inputVoltage) == config.second.inputVoltage.end())
748 {
749 tmpAdditionalData.clear();
750 tmpAdditionalData["ACTUAL_VOLTAGE"] =
751 std::to_string(actualInputVoltage);
752 for (const auto& voltage : config.second.inputVoltage)
753 {
754 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
755 std::to_string(voltage) + " ";
756 }
757 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
758 psu->getInventoryPath();
759
760 voltageValidated = false;
761 break;
762 }
763 }
764 if (!voltageValidated)
765 {
766 continue;
767 }
768
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000769 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000770 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000771
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000772 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000773 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000774}
775
Adriana Kobylak523704d2021-09-21 15:55:41 +0000776bool PSUManager::validateModelName(
777 std::string& model, std::map<std::string, std::string>& additionalData)
778{
779 // Check that all PSUs have the same model name. Initialize the model
780 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000781 // against the rest of the PSUs and get its inventory path to use as callout
782 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +0000783 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000784 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000785 for (const auto& psu : psus)
786 {
787 auto psuModel = psu->getModelName();
788 if (psuModel.empty())
789 {
790 continue;
791 }
792 if (model.empty())
793 {
794 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000795 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +0000796 continue;
797 }
798 if (psuModel != model)
799 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +0000800 if (supportedConfigs.find(model) != supportedConfigs.end())
801 {
802 // The base model is supported, callout the mismatched PSU. The
803 // mismatched PSU may or may not be supported.
804 additionalData["EXPECTED_MODEL"] = model;
805 additionalData["ACTUAL_MODEL"] = psuModel;
806 additionalData["CALLOUT_INVENTORY_PATH"] =
807 psu->getInventoryPath();
808 }
809 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
810 {
811 // The base model is not supported, but the mismatched PSU is,
812 // callout the base PSU.
813 additionalData["EXPECTED_MODEL"] = psuModel;
814 additionalData["ACTUAL_MODEL"] = model;
815 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
816 }
817 else
818 {
819 // The base model and the mismatched PSU are not supported or
820 // could not be found in the supported configuration, callout
821 // the mismatched PSU.
822 additionalData["EXPECTED_MODEL"] = model;
823 additionalData["ACTUAL_MODEL"] = psuModel;
824 additionalData["CALLOUT_INVENTORY_PATH"] =
825 psu->getInventoryPath();
826 }
Adriana Kobylak523704d2021-09-21 15:55:41 +0000827 model.clear();
828 return false;
829 }
830 }
831 return true;
832}
833
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000834void PSUManager::setPowerConfigGPIO()
835{
836 if (!powerConfigGPIO)
837 {
838 return;
839 }
840
841 std::string model{};
842 std::map<std::string, std::string> additionalData;
843 if (!validateModelName(model, additionalData))
844 {
845 return;
846 }
847
848 auto config = supportedConfigs.find(model);
849 if (config != supportedConfigs.end())
850 {
851 // The power-config-full-load is an open drain GPIO. Set it to low (0)
852 // if the supported configuration indicates that this system model
853 // expects the maximum number of power supplies (full load set to true).
854 // Else, set it to high (1), this is the default.
855 auto powerConfigValue =
856 (config->second.powerConfigFullLoad == true ? 0 : 1);
857 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
858 powerConfigGPIO->write(powerConfigValue, flags);
859 }
860}
861
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500862} // namespace phosphor::power::manager