blob: 62501e9b899a99af50d91f6c497c70fb88330bd4 [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
Jim Wright7f9288c2022-12-08 11:57:04 -060011#include <xyz/openbmc_project/State/Chassis/server.hpp>
12
Shawn McCarney9252b7e2022-06-10 12:47:38 -050013#include <algorithm>
Brandon Wymanecbecbc2021-08-31 22:53:21 +000014#include <regex>
Shawn McCarney9252b7e2022-06-10 12:47:38 -050015#include <set>
Brandon Wymanecbecbc2021-08-31 22:53:21 +000016
Brandon Wymanaed1f752019-11-25 18:10:52 -060017using namespace phosphor::logging;
18
Brandon Wyman63ea78b2020-09-24 16:49:09 -050019namespace phosphor::power::manager
Brandon Wymana0f33ce2019-10-17 18:32:29 -050020{
Adriana Kobylakc9b05732022-03-19 15:15:10 +000021constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
22constexpr auto objectManagerObjPath =
23 "/xyz/openbmc_project/power/power_supplies";
24constexpr auto powerSystemsInputsObjPath =
25 "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
Brandon Wymana0f33ce2019-10-17 18:32:29 -050026
Brandon Wyman510acaa2020-11-05 18:32:04 -060027constexpr auto IBMCFFPSInterface =
28 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
29constexpr auto i2cBusProp = "I2CBus";
30constexpr auto i2cAddressProp = "I2CAddress";
31constexpr auto psuNameProp = "Name";
B. J. Wyman681b2a32021-04-20 22:31:22 +000032constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060033
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060034constexpr auto supportedConfIntf =
35 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060036
Brandon Wymanc9e840e2022-05-10 20:48:41 +000037constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
Brandon Wyman18a24d92022-04-19 22:48:34 +000038
Patrick Williams7354ce62022-07-22 19:26:56 -050039PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000040 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
Brandon Wymanc3324422022-03-24 20:30:57 +000041 objectManager(bus, objectManagerObjPath),
Matt Spinlera068f422023-03-10 13:06:49 -060042 historyManager(bus, "/org/open_power/sensors"),
43 sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060044{
Brandon Wyman510acaa2020-11-05 18:32:04 -060045 // Subscribe to InterfacesAdded before doing a property read, otherwise
46 // the interface could be created after the read attempt but before the
47 // match is created.
48 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
49 bus,
50 sdbusplus::bus::match::rules::interfacesAdded() +
51 sdbusplus::bus::match::rules::sender(
52 "xyz.openbmc_project.EntityManager"),
53 std::bind(&PSUManager::entityManagerIfaceAdded, this,
54 std::placeholders::_1));
55 getPSUConfiguration();
56 getSystemProperties();
57
Adriana Kobylakc9b05732022-03-19 15:15:10 +000058 // Request the bus name before the analyze() function, which is the one that
59 // determines the brownout condition and sets the status d-bus property.
60 bus.request_name(managerBusName);
61
Brandon Wyman510acaa2020-11-05 18:32:04 -060062 using namespace sdeventplus;
63 auto interval = std::chrono::milliseconds(1000);
64 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
65 e, std::bind(&PSUManager::analyze, this), interval);
66
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000067 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
68 e, std::bind(&PSUManager::validateConfig, this));
69
Adriana Kobylakc0a07582021-10-13 15:52:25 +000070 try
71 {
72 powerConfigGPIO = createGPIO("power-config-full-load");
73 }
74 catch (const std::exception& e)
75 {
76 // Ignore error, GPIO may not be implemented in this system.
77 powerConfigGPIO = nullptr;
78 }
79
Brandon Wyman510acaa2020-11-05 18:32:04 -060080 // Subscribe to power state changes
81 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
82 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
83 bus,
84 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
85 POWER_IFACE),
86 [this](auto& msg) { this->powerStateChanged(msg); });
87
88 initialize();
89}
90
Jim Wrightaca86d02022-06-10 12:01:39 -050091void PSUManager::initialize()
92{
93 try
94 {
95 // pgood is the latest read of the chassis pgood
96 int pgood = 0;
97 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
98 powerService, bus, pgood);
99
100 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -0600101 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -0500102 POWER_IFACE, "getPowerState");
103 auto reply = bus.call(method);
104 int state = 0;
105 reply.read(state);
106
107 if (state)
108 {
109 // Monitor PSUs anytime state is on
110 powerOn = true;
111 // In the power fault window if pgood is off
112 powerFaultOccurring = !pgood;
113 validationTimer->restartOnce(validationTimeout);
114 }
115 else
116 {
117 // Power is off
118 powerOn = false;
119 powerFaultOccurring = false;
120 runValidateConfig = true;
121 }
122 }
123 catch (const std::exception& e)
124 {
125 log<level::INFO>(
126 fmt::format(
127 "Failed to get power state, assuming it is off, error {}",
128 e.what())
129 .c_str());
130 powerOn = false;
131 powerFaultOccurring = false;
132 runValidateConfig = true;
133 }
134
135 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
136 clearFaults();
137 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500138 setPowerConfigGPIO();
139
140 log<level::INFO>(
141 fmt::format("initialize: power on: {}, power fault occurring: {}",
142 powerOn, powerFaultOccurring)
143 .c_str());
144}
145
Brandon Wyman510acaa2020-11-05 18:32:04 -0600146void PSUManager::getPSUConfiguration()
147{
148 using namespace phosphor::power::util;
149 auto depth = 0;
150 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
151
152 psus.clear();
153
154 // I should get a map of objects back.
155 // Each object will have a path, a service, and an interface.
156 // The interface should match the one passed into this function.
157 for (const auto& [path, services] : objects)
158 {
159 auto service = services.begin()->first;
160
161 if (path.empty() || service.empty())
162 {
163 continue;
164 }
165
166 // For each object in the array of objects, I want to get properties
167 // from the service, path, and interface.
168 auto properties =
169 getAllProperties(bus, path, IBMCFFPSInterface, service);
170
171 getPSUProperties(properties);
172 }
173
174 if (psus.empty())
175 {
176 // Interface or properties not found. Let the Interfaces Added callback
177 // process the information once the interfaces are added to D-Bus.
178 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
179 }
180}
181
182void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
183{
184 // From passed in properties, I want to get: I2CBus, I2CAddress,
185 // and Name. Create a power supply object, using Name to build the inventory
186 // path.
187 const auto basePSUInvPath =
188 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
189 uint64_t* i2cbus = nullptr;
190 uint64_t* i2caddr = nullptr;
191 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000192 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600193
194 for (const auto& property : properties)
195 {
196 try
197 {
198 if (property.first == i2cBusProp)
199 {
200 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
201 }
202 else if (property.first == i2cAddressProp)
203 {
204 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
205 }
206 else if (property.first == psuNameProp)
207 {
208 psuname = std::get_if<std::string>(&properties[psuNameProp]);
209 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000210 else if (property.first == presLineName)
211 {
212 preslineptr =
213 std::get_if<std::string>(&properties[presLineName]);
214 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600215 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500216 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000217 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600218 }
219
220 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
221 {
222 std::string invpath = basePSUInvPath;
223 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000224 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600225
226 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
227
B. J. Wyman681b2a32021-04-20 22:31:22 +0000228 if (nullptr != preslineptr)
229 {
230 presline = *preslineptr;
231 }
232
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000233 auto invMatch =
234 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
235 return psu->getInventoryPath() == invpath;
236 });
237 if (invMatch != psus.end())
238 {
239 // This power supply has the same inventory path as the one with
240 // information just added to D-Bus.
241 // Changes to GPIO line name unlikely, so skip checking.
242 // Changes to the I2C bus and address unlikely, as that would
243 // require corresponding device tree updates.
244 // Return out to avoid duplicate object creation.
245 return;
246 }
247
Brandon Wymanc3324422022-03-24 20:30:57 +0000248 constexpr auto driver = "ibm-cffps";
B. J. Wyman681b2a32021-04-20 22:31:22 +0000249 log<level::DEBUG>(
Brandon Wymanc3324422022-03-24 20:30:57 +0000250 fmt::format(
251 "make PowerSupply bus: {} addr: {} driver: {} presline: {}",
252 *i2cbus, *i2caddr, driver, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000253 .c_str());
George Liu9464c422023-02-27 14:30:27 +0800254 auto psu = std::make_unique<PowerSupply>(
255 bus, invpath, *i2cbus, *i2caddr, driver, presline,
256 std::bind(
257 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
258 this));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600259 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000260
261 // Subscribe to power supply presence changes
262 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
263 bus,
264 sdbusplus::bus::match::rules::propertiesChanged(invpath,
265 INVENTORY_IFACE),
266 [this](auto& msg) { this->presenceChanged(msg); });
267 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600268 }
269
270 if (psus.empty())
271 {
272 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
273 }
274}
275
Adriana Kobylake1074d82021-03-16 20:46:44 +0000276void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
277{
278 try
279 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000280 auto propIt = properties.find("SupportedType");
281 if (propIt == properties.end())
282 {
283 return;
284 }
285 const std::string* type = std::get_if<std::string>(&(propIt->second));
286 if ((type == nullptr) || (*type != "PowerSupply"))
287 {
288 return;
289 }
290
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000291 propIt = properties.find("SupportedModel");
292 if (propIt == properties.end())
293 {
294 return;
295 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000296 const std::string* model = std::get_if<std::string>(&(propIt->second));
297 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000298 {
299 return;
300 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000301
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000302 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000303 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000304 if (propIt != properties.end())
305 {
306 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
307 if (count != nullptr)
308 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000309 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000310 }
311 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000312 propIt = properties.find("InputVoltage");
313 if (propIt != properties.end())
314 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000315 const std::vector<uint64_t>* voltage =
316 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000317 if (voltage != nullptr)
318 {
319 sys.inputVoltage = *voltage;
320 }
321 }
322
Adriana Kobylak886574c2021-11-01 18:22:28 +0000323 // The PowerConfigFullLoad is an optional property, default it to false
324 // since that's the default value of the power-config-full-load GPIO.
325 sys.powerConfigFullLoad = false;
326 propIt = properties.find("PowerConfigFullLoad");
327 if (propIt != properties.end())
328 {
329 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
330 if (fullLoad != nullptr)
331 {
332 sys.powerConfigFullLoad = *fullLoad;
333 }
334 }
335
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000336 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000337 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500338 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000339 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000340}
341
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600342void PSUManager::getSystemProperties()
343{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600344
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600345 try
346 {
347 util::DbusSubtree subtree =
348 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000349 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600350 {
351 throw std::runtime_error("Supported Configuration Not Found");
352 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600353
Adriana Kobylake1074d82021-03-16 20:46:44 +0000354 for (const auto& [objPath, services] : subtree)
355 {
356 std::string service = services.begin()->first;
357 if (objPath.empty() || service.empty())
358 {
359 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600360 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000361 auto properties = util::getAllProperties(
362 bus, objPath, supportedConfIntf, service);
363 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600364 }
365 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500366 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600367 {
368 // Interface or property not found. Let the Interfaces Added callback
369 // process the information once the interfaces are added to D-Bus.
370 }
371}
372
Patrick Williams7354ce62022-07-22 19:26:56 -0500373void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600374{
375 try
376 {
377 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000378 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600379 interfaces;
380 msg.read(objPath, interfaces);
381
382 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600383 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600384 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600385 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000386 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600387 }
388
Brandon Wyman510acaa2020-11-05 18:32:04 -0600389 itIntf = interfaces.find(IBMCFFPSInterface);
390 if (itIntf != interfaces.cend())
391 {
392 log<level::INFO>(
393 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
394 .c_str());
395 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000396 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600397 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000398
399 // Call to validate the psu configuration if the power is on and both
400 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
401 // processed
402 if (powerOn && !psus.empty() && !supportedConfigs.empty())
403 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000404 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000405 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600406 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500407 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600408 {
409 // Ignore, the property may be of a different type than expected.
410 }
411}
412
Patrick Williams7354ce62022-07-22 19:26:56 -0500413void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500414{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500415 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500416 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500417 msg.read(msgSensor, msgData);
418
Jim Wrightaca86d02022-06-10 12:01:39 -0500419 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500420 auto valPropMap = msgData.find("state");
421 if (valPropMap != msgData.end())
422 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500423 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500424 if (state)
425 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500426 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500427 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500428 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000429 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500430 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000431 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000432 setPowerConfigGPIO();
Matt Spinlera068f422023-03-10 13:06:49 -0600433 setInputVoltageRating();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500434 }
435 else
436 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500437 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500438 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500439 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000440 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500441 }
442 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500443
444 // Check if it was the pgood property that changed.
445 valPropMap = msgData.find("pgood");
446 if (valPropMap != msgData.end())
447 {
448 int pgood = std::get<int>(valPropMap->second);
449 if (!pgood)
450 {
451 // Chassis power good has turned off
452 if (powerOn)
453 {
454 // pgood is off but state is on, in power fault window
455 powerFaultOccurring = true;
456 }
457 }
458 }
459 log<level::INFO>(
460 fmt::format(
461 "powerStateChanged: power on: {}, power fault occurring: {}",
462 powerOn, powerFaultOccurring)
463 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500464}
465
Patrick Williams7354ce62022-07-22 19:26:56 -0500466void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000467{
468 std::string msgSensor;
469 std::map<std::string, std::variant<uint32_t, bool>> msgData;
470 msg.read(msgSensor, msgData);
471
472 // Check if it was the Present property that changed.
473 auto valPropMap = msgData.find(PRESENT_PROP);
474 if (valPropMap != msgData.end())
475 {
476 if (std::get<bool>(valPropMap->second))
477 {
478 // A PSU became present, force the PSU validation to run.
479 runValidateConfig = true;
480 validationTimer->restartOnce(validationTimeout);
481 }
482 }
483}
484
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000485void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
486{
487 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000488 constexpr auto method = "setPowerSupplyError";
489
490 try
491 {
492 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600493 auto methodMsg = bus.new_method_call(
494 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000495 methodMsg.append(psuErrorString);
496 auto callReply = bus.call(methodMsg);
497 }
498 catch (const std::exception& e)
499 {
500 log<level::INFO>(
501 fmt::format("Failed calling setPowerSupplyError due to error {}",
502 e.what())
503 .c_str());
504 }
505}
506
Brandon Wyman8b662882021-10-08 17:31:51 +0000507void PSUManager::createError(const std::string& faultName,
508 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500509{
510 using namespace sdbusplus::xyz::openbmc_project;
511 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
512 constexpr auto loggingCreateInterface =
513 "xyz.openbmc_project.Logging.Create";
514
515 try
516 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000517 additionalData["_PID"] = std::to_string(getpid());
518
Brandon Wymanb76ab242020-09-16 18:06:06 -0500519 auto service =
520 util::getService(loggingObjectPath, loggingCreateInterface, bus);
521
522 if (service.empty())
523 {
524 log<level::ERR>("Unable to get logging manager service");
525 return;
526 }
527
528 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
529 loggingCreateInterface, "Create");
530
531 auto level = Logging::server::Entry::Level::Error;
532 method.append(faultName, level, additionalData);
533
534 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000535 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500536 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500537 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500538 {
539 log<level::ERR>(
540 fmt::format(
541 "Failed creating event log for fault {} due to error {}",
542 faultName, e.what())
543 .c_str());
544 }
545}
546
Brandon Wyman18a24d92022-04-19 22:48:34 +0000547void PSUManager::syncHistory()
548{
549 log<level::INFO>("Synchronize INPUT_HISTORY");
550
551 if (!syncHistoryGPIO)
552 {
553 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
554 }
555 if (syncHistoryGPIO)
556 {
557 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
558 syncHistoryGPIO->toggleLowHigh(delay);
559 for (auto& psu : psus)
560 {
561 psu->clearSyncHistoryRequired();
562 }
563 }
564
565 log<level::INFO>("Synchronize INPUT_HISTORY completed");
566}
567
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500568void PSUManager::analyze()
569{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000570 auto syncHistoryRequired =
571 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
572 return psu->isSyncHistoryRequired();
573 });
574 if (syncHistoryRequired)
575 {
576 syncHistory();
577 }
578
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500579 for (auto& psu : psus)
580 {
581 psu->analyze();
582 }
583
Jim Wright7f9288c2022-12-08 11:57:04 -0600584 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000585
Jim Wrightcefe85f2022-11-18 09:57:47 -0600586 // Only perform individual PSU analysis if power is on and a brownout has
587 // not already been logged
588 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500589 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600590 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500591 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600592 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000593
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600594 if (!psu->isFaultLogged() && !psu->isPresent())
595 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000596 std::map<std::string, std::string> requiredPSUsData;
597 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500598 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000599 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000600 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000601 // Create error for power supply missing.
602 additionalData["CALLOUT_INVENTORY_PATH"] =
603 psu->getInventoryPath();
604 additionalData["CALLOUT_PRIORITY"] = "H";
605 createError(
606 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
607 additionalData);
608 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600609 psu->setFaultLogged();
610 }
611 else if (!psu->isFaultLogged() && psu->isFaulted())
612 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000613 // Add STATUS_WORD and STATUS_MFR last response, in padded
614 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600615 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000616 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600617 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000618 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600619 // If there are faults being reported, they possibly could be
620 // related to a bug in the firmware version running on the power
621 // supply. Capture that data into the error as well.
622 additionalData["FW_VERSION"] = psu->getFWVersion();
623
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000624 if (psu->hasCommFault())
625 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000626 additionalData["STATUS_CML"] =
627 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000628 /* Attempts to communicate with the power supply have
629 * reached there limit. Create an error. */
630 additionalData["CALLOUT_DEVICE_PATH"] =
631 psu->getDevicePath();
632
633 createError(
634 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
635 additionalData);
636
637 psu->setFaultLogged();
638 }
639 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600640 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000641 // Include STATUS_INPUT for input faults.
642 additionalData["STATUS_INPUT"] =
643 fmt::format("{:#02x}", psu->getStatusInput());
644
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600645 /* The power supply location might be needed if the input
646 * fault is due to a problem with the power supply itself.
647 * Include the inventory path with a call out priority of
648 * low.
649 */
650 additionalData["CALLOUT_INVENTORY_PATH"] =
651 psu->getInventoryPath();
652 additionalData["CALLOUT_PRIORITY"] = "L";
653 createError("xyz.openbmc_project.Power.PowerSupply.Error."
654 "InputFault",
655 additionalData);
656 psu->setFaultLogged();
657 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000658 else if (psu->hasPSKillFault())
659 {
660 createError(
661 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
662 additionalData);
663 psu->setFaultLogged();
664 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000665 else if (psu->hasVoutOVFault())
666 {
667 // Include STATUS_VOUT for Vout faults.
668 additionalData["STATUS_VOUT"] =
669 fmt::format("{:#02x}", psu->getStatusVout());
670
671 additionalData["CALLOUT_INVENTORY_PATH"] =
672 psu->getInventoryPath();
673
674 createError(
675 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
676 additionalData);
677
678 psu->setFaultLogged();
679 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000680 else if (psu->hasIoutOCFault())
681 {
682 // Include STATUS_IOUT for Iout faults.
683 additionalData["STATUS_IOUT"] =
684 fmt::format("{:#02x}", psu->getStatusIout());
685
686 createError(
687 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
688 additionalData);
689
690 psu->setFaultLogged();
691 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000692 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
693 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000694 {
695 // Include STATUS_VOUT for Vout faults.
696 additionalData["STATUS_VOUT"] =
697 fmt::format("{:#02x}", psu->getStatusVout());
698
699 additionalData["CALLOUT_INVENTORY_PATH"] =
700 psu->getInventoryPath();
701
702 createError(
703 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
704 additionalData);
705
706 psu->setFaultLogged();
707 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000708 // A fan fault should have priority over a temperature fault,
709 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500710 // Only process if not in power fault window.
711 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000712 {
713 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
714 additionalData["STATUS_TEMPERATURE"] =
715 fmt::format("{:#02x}", psu->getStatusTemperature());
716 additionalData["STATUS_FANS_1_2"] =
717 fmt::format("{:#02x}", psu->getStatusFans12());
718
719 additionalData["CALLOUT_INVENTORY_PATH"] =
720 psu->getInventoryPath();
721
722 createError(
723 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
724 additionalData);
725
726 psu->setFaultLogged();
727 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000728 else if (psu->hasTempFault())
729 {
730 // Include STATUS_TEMPERATURE for temperature faults.
731 additionalData["STATUS_TEMPERATURE"] =
732 fmt::format("{:#02x}", psu->getStatusTemperature());
733
734 additionalData["CALLOUT_INVENTORY_PATH"] =
735 psu->getInventoryPath();
736
737 createError(
738 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
739 additionalData);
740
741 psu->setFaultLogged();
742 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600743 else if (psu->hasMFRFault())
744 {
745 /* This can represent a variety of faults that result in
746 * calling out the power supply for replacement: Output
747 * OverCurrent, Output Under Voltage, and potentially other
748 * faults.
749 *
750 * Also plan on putting specific fault in AdditionalData,
751 * along with register names and register values
752 * (STATUS_WORD, STATUS_MFR, etc.).*/
753
754 additionalData["CALLOUT_INVENTORY_PATH"] =
755 psu->getInventoryPath();
756
757 createError(
758 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500759 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500760
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600761 psu->setFaultLogged();
762 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500763 // Only process if not in power fault window.
764 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000765 {
766 /* POWER_GOOD# is not low, or OFF is on */
767 additionalData["CALLOUT_INVENTORY_PATH"] =
768 psu->getInventoryPath();
769
770 createError(
771 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
772 additionalData);
773
774 psu->setFaultLogged();
775 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500776 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500777 }
778 }
779}
780
Jim Wright7f9288c2022-12-08 11:57:04 -0600781void PSUManager::analyzeBrownout()
782{
783 // Count number of power supplies failing
784 size_t presentCount = 0;
785 size_t notPresentCount = 0;
786 size_t acFailedCount = 0;
787 size_t pgoodFailedCount = 0;
788 for (const auto& psu : psus)
789 {
790 if (psu->isPresent())
791 {
792 ++presentCount;
793 if (psu->hasACFault())
794 {
795 ++acFailedCount;
796 }
797 else if (psu->hasPgoodFault())
798 {
799 ++pgoodFailedCount;
800 }
801 }
802 else
803 {
804 ++notPresentCount;
805 }
806 }
807
808 // Only issue brownout failure if chassis pgood has failed, it has not
809 // already been logged, at least one PSU has seen an AC fail, and all
810 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
811 // at least one PSU is present.
812 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
813 (presentCount == (acFailedCount + pgoodFailedCount)))
814 {
815 // Indicate that the system is in a brownout condition by creating an
816 // error log and setting the PowerSystemInputs status property to Fault.
817 powerSystemInputs.status(
818 sdbusplus::xyz::openbmc_project::State::Decorator::server::
819 PowerSystemInputs::Status::Fault);
820
821 std::map<std::string, std::string> additionalData;
822 additionalData.emplace("NOT_PRESENT_COUNT",
823 std::to_string(notPresentCount));
824 additionalData.emplace("VIN_FAULT_COUNT",
825 std::to_string(acFailedCount));
826 additionalData.emplace("PGOOD_FAULT_COUNT",
827 std::to_string(pgoodFailedCount));
828 log<level::INFO>(
829 fmt::format(
830 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
831 notPresentCount, acFailedCount, pgoodFailedCount)
832 .c_str());
833
834 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
835 additionalData);
836 brownoutLogged = true;
837 }
838 else
839 {
840 // If a brownout was previously logged but at least one PSU is not
841 // currently in AC fault, determine if the brownout condition can be
842 // cleared
843 if (brownoutLogged && (acFailedCount < presentCount))
844 {
845 // Chassis only recognizes the PowerSystemInputs change when it is
846 // off
847 try
848 {
849 using PowerState = sdbusplus::xyz::openbmc_project::State::
850 server::Chassis::PowerState;
851 PowerState currentPowerState;
852 util::getProperty<PowerState>(
853 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
854 "/xyz/openbmc_project/state/chassis0",
855 "xyz.openbmc_project.State.Chassis", bus,
856 currentPowerState);
857
858 if (currentPowerState == PowerState::Off)
859 {
860 // Indicate that the system is no longer in a brownout
861 // condition by setting the PowerSystemInputs status
862 // property to Good.
863 log<level::INFO>(
864 fmt::format(
865 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
866 notPresentCount, acFailedCount, pgoodFailedCount)
867 .c_str());
868 powerSystemInputs.status(
869 sdbusplus::xyz::openbmc_project::State::Decorator::
870 server::PowerSystemInputs::Status::Good);
871 brownoutLogged = false;
872 }
873 }
874 catch (const std::exception& e)
875 {
876 log<level::ERR>(
877 fmt::format("Error trying to clear brownout, error: {}",
878 e.what())
879 .c_str());
880 }
881 }
882 }
883}
884
Brandon Wyman64e97752022-06-03 23:50:13 +0000885void PSUManager::updateMissingPSUs()
886{
887 if (supportedConfigs.empty() || psus.empty())
888 {
889 return;
890 }
891
892 // Power supplies default to missing. If the power supply is present,
893 // the PowerSupply object will update the inventory Present property to
894 // true. If we have less than the required number of power supplies, and
895 // this power supply is missing, update the inventory Present property
896 // to false to indicate required power supply is missing. Avoid
897 // indicating power supply missing if not required.
898
899 auto presentCount =
900 std::count_if(psus.begin(), psus.end(),
901 [](const auto& psu) { return psu->isPresent(); });
902
903 for (const auto& config : supportedConfigs)
904 {
905 for (const auto& psu : psus)
906 {
907 auto psuModel = psu->getModelName();
908 auto psuShortName = psu->getShortName();
909 auto psuInventoryPath = psu->getInventoryPath();
910 auto relativeInvPath =
911 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
912 auto psuPresent = psu->isPresent();
913 auto presProperty = false;
914 auto propReadFail = false;
915
916 try
917 {
918 presProperty = getPresence(bus, psuInventoryPath);
919 propReadFail = false;
920 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500921 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000922 {
923 propReadFail = true;
924 // Relying on property change or interface added to retry.
925 // Log an informational trace to the journal.
926 log<level::INFO>(
927 fmt::format("D-Bus property {} access failure exception",
928 psuInventoryPath)
929 .c_str());
930 }
931
932 if (psuModel.empty())
933 {
934 if (!propReadFail && (presProperty != psuPresent))
935 {
936 // We already have this property, and it is not false
937 // set Present to false
938 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
939 }
940 continue;
941 }
942
943 if (config.first != psuModel)
944 {
945 continue;
946 }
947
948 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
949 {
950 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
951 }
952 }
953 }
954}
955
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000956void PSUManager::validateConfig()
957{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000958 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000959 {
960 return;
961 }
962
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000963 for (const auto& psu : psus)
964 {
965 if ((psu->hasInputFault() || psu->hasVINUVFault()))
966 {
967 // Do not try to validate if input voltage fault present.
968 validationTimer->restartOnce(validationTimeout);
969 return;
970 }
971 }
972
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000973 std::map<std::string, std::string> additionalData;
974 auto supported = hasRequiredPSUs(additionalData);
975 if (supported)
976 {
977 runValidateConfig = false;
978 return;
979 }
980
981 // Validation failed, create an error log.
982 // Return without setting the runValidateConfig flag to false because
983 // it may be that an additional supported configuration interface is
984 // added and we need to validate it to see if it matches this system.
985 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
986 additionalData);
987}
988
989bool PSUManager::hasRequiredPSUs(
990 std::map<std::string, std::string>& additionalData)
991{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000992 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000993 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000994 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000995 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000996 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000997
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000998 auto presentCount =
999 std::count_if(psus.begin(), psus.end(),
1000 [](const auto& psu) { return psu->isPresent(); });
1001
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001002 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001003 // power supply model configuration. Since all configurations need to be
1004 // checked, the additional data would contain only the information of the
1005 // last configuration that did not match.
1006 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001007 for (const auto& config : supportedConfigs)
1008 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001009 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001010 {
1011 continue;
1012 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001013
Jim Wright941b60d2022-10-19 16:22:17 -05001014 // Number of power supplies present should equal or exceed the expected
1015 // count
1016 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001017 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001018 tmpAdditionalData.clear();
1019 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001020 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001021 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001022 continue;
1023 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001024
1025 bool voltageValidated = true;
1026 for (const auto& psu : psus)
1027 {
1028 if (!psu->isPresent())
1029 {
1030 // Only present PSUs report a valid input voltage
1031 continue;
1032 }
1033
1034 double actualInputVoltage;
1035 int inputVoltage;
1036 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1037
1038 if (std::find(config.second.inputVoltage.begin(),
1039 config.second.inputVoltage.end(),
1040 inputVoltage) == config.second.inputVoltage.end())
1041 {
1042 tmpAdditionalData.clear();
1043 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1044 std::to_string(actualInputVoltage);
1045 for (const auto& voltage : config.second.inputVoltage)
1046 {
1047 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1048 std::to_string(voltage) + " ";
1049 }
1050 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1051 psu->getInventoryPath();
1052
1053 voltageValidated = false;
1054 break;
1055 }
1056 }
1057 if (!voltageValidated)
1058 {
1059 continue;
1060 }
1061
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001062 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001063 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001064
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001065 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001066 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001067}
1068
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001069unsigned int PSUManager::getRequiredPSUCount()
1070{
1071 unsigned int requiredCount{0};
1072
1073 // Verify we have the supported configuration and PSU information
1074 if (!supportedConfigs.empty() && !psus.empty())
1075 {
1076 // Find PSU models. They should all be the same.
1077 std::set<std::string> models{};
1078 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1079 if (!psu->getModelName().empty())
1080 {
1081 models.insert(psu->getModelName());
1082 }
1083 });
1084
1085 // If exactly one model was found, find corresponding configuration
1086 if (models.size() == 1)
1087 {
1088 const std::string& model = *(models.begin());
1089 auto it = supportedConfigs.find(model);
1090 if (it != supportedConfigs.end())
1091 {
1092 requiredCount = it->second.powerSupplyCount;
1093 }
1094 }
1095 }
1096
1097 return requiredCount;
1098}
1099
1100bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1101{
1102 // Get required number of PSUs; if not found, we don't know if PSU required
1103 unsigned int requiredCount = getRequiredPSUCount();
1104 if (requiredCount == 0)
1105 {
1106 return false;
1107 }
1108
1109 // If total PSU count <= the required count, all PSUs are required
1110 if (psus.size() <= requiredCount)
1111 {
1112 return true;
1113 }
1114
1115 // We don't currently get information from EntityManager about which PSUs
1116 // are required, so we have to do some guesswork. First check if this PSU
1117 // is present. If so, assume it is required.
1118 if (psu.isPresent())
1119 {
1120 return true;
1121 }
1122
1123 // This PSU is not present. Count the number of other PSUs that are
1124 // present. If enough other PSUs are present, assume the specified PSU is
1125 // not required.
1126 unsigned int psuCount =
1127 std::count_if(psus.begin(), psus.end(),
1128 [](const auto& psu) { return psu->isPresent(); });
1129 if (psuCount >= requiredCount)
1130 {
1131 return false;
1132 }
1133
1134 // Check if this PSU was previously present. If so, assume it is required.
1135 // We know it was previously present if it has a non-empty model name.
1136 if (!psu.getModelName().empty())
1137 {
1138 return true;
1139 }
1140
1141 // This PSU was never present. Count the number of other PSUs that were
1142 // previously present. If including those PSUs is enough, assume the
1143 // specified PSU is not required.
1144 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1145 return (!psu->isPresent() && !psu->getModelName().empty());
1146 });
1147 if (psuCount >= requiredCount)
1148 {
1149 return false;
1150 }
1151
1152 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1153 // that were never present. PSU inventory paths typically end with the PSU
1154 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1155 std::vector<std::string> sortedPaths;
1156 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1157 if (!psu->isPresent() && psu->getModelName().empty())
1158 {
1159 sortedPaths.push_back(psu->getInventoryPath());
1160 }
1161 });
1162 std::sort(sortedPaths.begin(), sortedPaths.end());
1163
1164 // Check if specified PSU is close enough to start of list to be required
1165 for (const auto& path : sortedPaths)
1166 {
1167 if (path == psu.getInventoryPath())
1168 {
1169 return true;
1170 }
1171 if (++psuCount >= requiredCount)
1172 {
1173 break;
1174 }
1175 }
1176
1177 // PSU was not close to start of sorted list; assume not required
1178 return false;
1179}
1180
Adriana Kobylak523704d2021-09-21 15:55:41 +00001181bool PSUManager::validateModelName(
1182 std::string& model, std::map<std::string, std::string>& additionalData)
1183{
1184 // Check that all PSUs have the same model name. Initialize the model
1185 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001186 // against the rest of the PSUs and get its inventory path to use as callout
1187 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001188 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001189 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001190 for (const auto& psu : psus)
1191 {
1192 auto psuModel = psu->getModelName();
1193 if (psuModel.empty())
1194 {
1195 continue;
1196 }
1197 if (model.empty())
1198 {
1199 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001200 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001201 continue;
1202 }
1203 if (psuModel != model)
1204 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001205 if (supportedConfigs.find(model) != supportedConfigs.end())
1206 {
1207 // The base model is supported, callout the mismatched PSU. The
1208 // mismatched PSU may or may not be supported.
1209 additionalData["EXPECTED_MODEL"] = model;
1210 additionalData["ACTUAL_MODEL"] = psuModel;
1211 additionalData["CALLOUT_INVENTORY_PATH"] =
1212 psu->getInventoryPath();
1213 }
1214 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1215 {
1216 // The base model is not supported, but the mismatched PSU is,
1217 // callout the base PSU.
1218 additionalData["EXPECTED_MODEL"] = psuModel;
1219 additionalData["ACTUAL_MODEL"] = model;
1220 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1221 }
1222 else
1223 {
1224 // The base model and the mismatched PSU are not supported or
1225 // could not be found in the supported configuration, callout
1226 // the mismatched PSU.
1227 additionalData["EXPECTED_MODEL"] = model;
1228 additionalData["ACTUAL_MODEL"] = psuModel;
1229 additionalData["CALLOUT_INVENTORY_PATH"] =
1230 psu->getInventoryPath();
1231 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001232 model.clear();
1233 return false;
1234 }
1235 }
1236 return true;
1237}
1238
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001239void PSUManager::setPowerConfigGPIO()
1240{
1241 if (!powerConfigGPIO)
1242 {
1243 return;
1244 }
1245
1246 std::string model{};
1247 std::map<std::string, std::string> additionalData;
1248 if (!validateModelName(model, additionalData))
1249 {
1250 return;
1251 }
1252
1253 auto config = supportedConfigs.find(model);
1254 if (config != supportedConfigs.end())
1255 {
1256 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1257 // if the supported configuration indicates that this system model
1258 // expects the maximum number of power supplies (full load set to true).
1259 // Else, set it to high (1), this is the default.
1260 auto powerConfigValue =
1261 (config->second.powerConfigFullLoad == true ? 0 : 1);
1262 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1263 powerConfigGPIO->write(powerConfigValue, flags);
1264 }
1265}
1266
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001267} // namespace phosphor::power::manager