blob: 13d93565d4935dcc46ff45047b4ead318468240d [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),
42 historyManager(bus, "/org/open_power/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060043{
Brandon Wyman510acaa2020-11-05 18:32:04 -060044 // Subscribe to InterfacesAdded before doing a property read, otherwise
45 // the interface could be created after the read attempt but before the
46 // match is created.
47 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
48 bus,
49 sdbusplus::bus::match::rules::interfacesAdded() +
50 sdbusplus::bus::match::rules::sender(
51 "xyz.openbmc_project.EntityManager"),
52 std::bind(&PSUManager::entityManagerIfaceAdded, this,
53 std::placeholders::_1));
54 getPSUConfiguration();
55 getSystemProperties();
56
Adriana Kobylakc9b05732022-03-19 15:15:10 +000057 // Request the bus name before the analyze() function, which is the one that
58 // determines the brownout condition and sets the status d-bus property.
59 bus.request_name(managerBusName);
60
Brandon Wyman510acaa2020-11-05 18:32:04 -060061 using namespace sdeventplus;
62 auto interval = std::chrono::milliseconds(1000);
63 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
64 e, std::bind(&PSUManager::analyze, this), interval);
65
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000066 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
67 e, std::bind(&PSUManager::validateConfig, this));
68
Adriana Kobylakc0a07582021-10-13 15:52:25 +000069 try
70 {
71 powerConfigGPIO = createGPIO("power-config-full-load");
72 }
73 catch (const std::exception& e)
74 {
75 // Ignore error, GPIO may not be implemented in this system.
76 powerConfigGPIO = nullptr;
77 }
78
Brandon Wyman510acaa2020-11-05 18:32:04 -060079 // Subscribe to power state changes
80 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
81 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
82 bus,
83 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
84 POWER_IFACE),
85 [this](auto& msg) { this->powerStateChanged(msg); });
86
87 initialize();
88}
89
Jim Wrightaca86d02022-06-10 12:01:39 -050090void PSUManager::initialize()
91{
92 try
93 {
94 // pgood is the latest read of the chassis pgood
95 int pgood = 0;
96 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
97 powerService, bus, pgood);
98
99 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -0600100 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -0500101 POWER_IFACE, "getPowerState");
102 auto reply = bus.call(method);
103 int state = 0;
104 reply.read(state);
105
106 if (state)
107 {
108 // Monitor PSUs anytime state is on
109 powerOn = true;
110 // In the power fault window if pgood is off
111 powerFaultOccurring = !pgood;
112 validationTimer->restartOnce(validationTimeout);
113 }
114 else
115 {
116 // Power is off
117 powerOn = false;
118 powerFaultOccurring = false;
119 runValidateConfig = true;
120 }
121 }
122 catch (const std::exception& e)
123 {
124 log<level::INFO>(
125 fmt::format(
126 "Failed to get power state, assuming it is off, error {}",
127 e.what())
128 .c_str());
129 powerOn = false;
130 powerFaultOccurring = false;
131 runValidateConfig = true;
132 }
133
134 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
135 clearFaults();
136 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500137 setPowerConfigGPIO();
138
139 log<level::INFO>(
140 fmt::format("initialize: power on: {}, power fault occurring: {}",
141 powerOn, powerFaultOccurring)
142 .c_str());
143}
144
Brandon Wyman510acaa2020-11-05 18:32:04 -0600145void PSUManager::getPSUConfiguration()
146{
147 using namespace phosphor::power::util;
148 auto depth = 0;
149 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
150
151 psus.clear();
152
153 // I should get a map of objects back.
154 // Each object will have a path, a service, and an interface.
155 // The interface should match the one passed into this function.
156 for (const auto& [path, services] : objects)
157 {
158 auto service = services.begin()->first;
159
160 if (path.empty() || service.empty())
161 {
162 continue;
163 }
164
165 // For each object in the array of objects, I want to get properties
166 // from the service, path, and interface.
167 auto properties =
168 getAllProperties(bus, path, IBMCFFPSInterface, service);
169
170 getPSUProperties(properties);
171 }
172
173 if (psus.empty())
174 {
175 // Interface or properties not found. Let the Interfaces Added callback
176 // process the information once the interfaces are added to D-Bus.
177 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
178 }
179}
180
181void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
182{
183 // From passed in properties, I want to get: I2CBus, I2CAddress,
184 // and Name. Create a power supply object, using Name to build the inventory
185 // path.
186 const auto basePSUInvPath =
187 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
188 uint64_t* i2cbus = nullptr;
189 uint64_t* i2caddr = nullptr;
190 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000191 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600192
193 for (const auto& property : properties)
194 {
195 try
196 {
197 if (property.first == i2cBusProp)
198 {
199 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
200 }
201 else if (property.first == i2cAddressProp)
202 {
203 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
204 }
205 else if (property.first == psuNameProp)
206 {
207 psuname = std::get_if<std::string>(&properties[psuNameProp]);
208 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000209 else if (property.first == presLineName)
210 {
211 preslineptr =
212 std::get_if<std::string>(&properties[presLineName]);
213 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600214 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500215 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000216 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600217 }
218
219 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
220 {
221 std::string invpath = basePSUInvPath;
222 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000223 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600224
225 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
226
B. J. Wyman681b2a32021-04-20 22:31:22 +0000227 if (nullptr != preslineptr)
228 {
229 presline = *preslineptr;
230 }
231
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000232 auto invMatch =
233 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
234 return psu->getInventoryPath() == invpath;
235 });
236 if (invMatch != psus.end())
237 {
238 // This power supply has the same inventory path as the one with
239 // information just added to D-Bus.
240 // Changes to GPIO line name unlikely, so skip checking.
241 // Changes to the I2C bus and address unlikely, as that would
242 // require corresponding device tree updates.
243 // Return out to avoid duplicate object creation.
244 return;
245 }
246
Brandon Wymanc3324422022-03-24 20:30:57 +0000247 constexpr auto driver = "ibm-cffps";
B. J. Wyman681b2a32021-04-20 22:31:22 +0000248 log<level::DEBUG>(
Brandon Wymanc3324422022-03-24 20:30:57 +0000249 fmt::format(
250 "make PowerSupply bus: {} addr: {} driver: {} presline: {}",
251 *i2cbus, *i2caddr, driver, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000252 .c_str());
253 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
Brandon Wymanc3324422022-03-24 20:30:57 +0000254 *i2caddr, driver, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600255 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000256
257 // Subscribe to power supply presence changes
258 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
259 bus,
260 sdbusplus::bus::match::rules::propertiesChanged(invpath,
261 INVENTORY_IFACE),
262 [this](auto& msg) { this->presenceChanged(msg); });
263 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600264 }
265
266 if (psus.empty())
267 {
268 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
269 }
270}
271
Adriana Kobylake1074d82021-03-16 20:46:44 +0000272void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
273{
274 try
275 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000276 auto propIt = properties.find("SupportedType");
277 if (propIt == properties.end())
278 {
279 return;
280 }
281 const std::string* type = std::get_if<std::string>(&(propIt->second));
282 if ((type == nullptr) || (*type != "PowerSupply"))
283 {
284 return;
285 }
286
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000287 propIt = properties.find("SupportedModel");
288 if (propIt == properties.end())
289 {
290 return;
291 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000292 const std::string* model = std::get_if<std::string>(&(propIt->second));
293 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000294 {
295 return;
296 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000297
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000298 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000299 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000300 if (propIt != properties.end())
301 {
302 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
303 if (count != nullptr)
304 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000305 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000306 }
307 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000308 propIt = properties.find("InputVoltage");
309 if (propIt != properties.end())
310 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000311 const std::vector<uint64_t>* voltage =
312 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000313 if (voltage != nullptr)
314 {
315 sys.inputVoltage = *voltage;
316 }
317 }
318
Adriana Kobylak886574c2021-11-01 18:22:28 +0000319 // The PowerConfigFullLoad is an optional property, default it to false
320 // since that's the default value of the power-config-full-load GPIO.
321 sys.powerConfigFullLoad = false;
322 propIt = properties.find("PowerConfigFullLoad");
323 if (propIt != properties.end())
324 {
325 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
326 if (fullLoad != nullptr)
327 {
328 sys.powerConfigFullLoad = *fullLoad;
329 }
330 }
331
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000332 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000333 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500334 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000335 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000336}
337
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600338void PSUManager::getSystemProperties()
339{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600340
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600341 try
342 {
343 util::DbusSubtree subtree =
344 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000345 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600346 {
347 throw std::runtime_error("Supported Configuration Not Found");
348 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600349
Adriana Kobylake1074d82021-03-16 20:46:44 +0000350 for (const auto& [objPath, services] : subtree)
351 {
352 std::string service = services.begin()->first;
353 if (objPath.empty() || service.empty())
354 {
355 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600356 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000357 auto properties = util::getAllProperties(
358 bus, objPath, supportedConfIntf, service);
359 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600360 }
361 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500362 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600363 {
364 // Interface or property not found. Let the Interfaces Added callback
365 // process the information once the interfaces are added to D-Bus.
366 }
367}
368
Patrick Williams7354ce62022-07-22 19:26:56 -0500369void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600370{
371 try
372 {
373 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000374 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600375 interfaces;
376 msg.read(objPath, interfaces);
377
378 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600379 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600380 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600381 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000382 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600383 }
384
Brandon Wyman510acaa2020-11-05 18:32:04 -0600385 itIntf = interfaces.find(IBMCFFPSInterface);
386 if (itIntf != interfaces.cend())
387 {
388 log<level::INFO>(
389 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
390 .c_str());
391 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000392 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600393 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000394
395 // Call to validate the psu configuration if the power is on and both
396 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
397 // processed
398 if (powerOn && !psus.empty() && !supportedConfigs.empty())
399 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000400 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000401 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600402 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500403 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600404 {
405 // Ignore, the property may be of a different type than expected.
406 }
407}
408
Patrick Williams7354ce62022-07-22 19:26:56 -0500409void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500410{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500411 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500412 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500413 msg.read(msgSensor, msgData);
414
Jim Wrightaca86d02022-06-10 12:01:39 -0500415 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500416 auto valPropMap = msgData.find("state");
417 if (valPropMap != msgData.end())
418 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500419 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500420 if (state)
421 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500422 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500423 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500424 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000425 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500426 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000427 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000428 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500429 }
430 else
431 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500432 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500433 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500434 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000435 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500436 }
437 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500438
439 // Check if it was the pgood property that changed.
440 valPropMap = msgData.find("pgood");
441 if (valPropMap != msgData.end())
442 {
443 int pgood = std::get<int>(valPropMap->second);
444 if (!pgood)
445 {
446 // Chassis power good has turned off
447 if (powerOn)
448 {
449 // pgood is off but state is on, in power fault window
450 powerFaultOccurring = true;
451 }
452 }
453 }
454 log<level::INFO>(
455 fmt::format(
456 "powerStateChanged: power on: {}, power fault occurring: {}",
457 powerOn, powerFaultOccurring)
458 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500459}
460
Patrick Williams7354ce62022-07-22 19:26:56 -0500461void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000462{
463 std::string msgSensor;
464 std::map<std::string, std::variant<uint32_t, bool>> msgData;
465 msg.read(msgSensor, msgData);
466
467 // Check if it was the Present property that changed.
468 auto valPropMap = msgData.find(PRESENT_PROP);
469 if (valPropMap != msgData.end())
470 {
471 if (std::get<bool>(valPropMap->second))
472 {
473 // A PSU became present, force the PSU validation to run.
474 runValidateConfig = true;
475 validationTimer->restartOnce(validationTimeout);
476 }
477 }
478}
479
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000480void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
481{
482 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000483 constexpr auto method = "setPowerSupplyError";
484
485 try
486 {
487 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600488 auto methodMsg = bus.new_method_call(
489 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000490 methodMsg.append(psuErrorString);
491 auto callReply = bus.call(methodMsg);
492 }
493 catch (const std::exception& e)
494 {
495 log<level::INFO>(
496 fmt::format("Failed calling setPowerSupplyError due to error {}",
497 e.what())
498 .c_str());
499 }
500}
501
Brandon Wyman8b662882021-10-08 17:31:51 +0000502void PSUManager::createError(const std::string& faultName,
503 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500504{
505 using namespace sdbusplus::xyz::openbmc_project;
506 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
507 constexpr auto loggingCreateInterface =
508 "xyz.openbmc_project.Logging.Create";
509
510 try
511 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000512 additionalData["_PID"] = std::to_string(getpid());
513
Brandon Wymanb76ab242020-09-16 18:06:06 -0500514 auto service =
515 util::getService(loggingObjectPath, loggingCreateInterface, bus);
516
517 if (service.empty())
518 {
519 log<level::ERR>("Unable to get logging manager service");
520 return;
521 }
522
523 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
524 loggingCreateInterface, "Create");
525
526 auto level = Logging::server::Entry::Level::Error;
527 method.append(faultName, level, additionalData);
528
529 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000530 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500531 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500532 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500533 {
534 log<level::ERR>(
535 fmt::format(
536 "Failed creating event log for fault {} due to error {}",
537 faultName, e.what())
538 .c_str());
539 }
540}
541
Brandon Wyman18a24d92022-04-19 22:48:34 +0000542void PSUManager::syncHistory()
543{
544 log<level::INFO>("Synchronize INPUT_HISTORY");
545
546 if (!syncHistoryGPIO)
547 {
548 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
549 }
550 if (syncHistoryGPIO)
551 {
552 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
553 syncHistoryGPIO->toggleLowHigh(delay);
554 for (auto& psu : psus)
555 {
556 psu->clearSyncHistoryRequired();
557 }
558 }
559
560 log<level::INFO>("Synchronize INPUT_HISTORY completed");
561}
562
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500563void PSUManager::analyze()
564{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000565 auto syncHistoryRequired =
566 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
567 return psu->isSyncHistoryRequired();
568 });
569 if (syncHistoryRequired)
570 {
571 syncHistory();
572 }
573
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500574 for (auto& psu : psus)
575 {
576 psu->analyze();
577 }
578
Jim Wright7f9288c2022-12-08 11:57:04 -0600579 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000580
Jim Wrightcefe85f2022-11-18 09:57:47 -0600581 // Only perform individual PSU analysis if power is on and a brownout has
582 // not already been logged
583 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500584 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600585 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500586 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600587 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000588
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600589 if (!psu->isFaultLogged() && !psu->isPresent())
590 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000591 std::map<std::string, std::string> requiredPSUsData;
592 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500593 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000594 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000595 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000596 // Create error for power supply missing.
597 additionalData["CALLOUT_INVENTORY_PATH"] =
598 psu->getInventoryPath();
599 additionalData["CALLOUT_PRIORITY"] = "H";
600 createError(
601 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
602 additionalData);
603 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600604 psu->setFaultLogged();
605 }
606 else if (!psu->isFaultLogged() && psu->isFaulted())
607 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000608 // Add STATUS_WORD and STATUS_MFR last response, in padded
609 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600610 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000611 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600612 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000613 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600614 // If there are faults being reported, they possibly could be
615 // related to a bug in the firmware version running on the power
616 // supply. Capture that data into the error as well.
617 additionalData["FW_VERSION"] = psu->getFWVersion();
618
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000619 if (psu->hasCommFault())
620 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000621 additionalData["STATUS_CML"] =
622 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000623 /* Attempts to communicate with the power supply have
624 * reached there limit. Create an error. */
625 additionalData["CALLOUT_DEVICE_PATH"] =
626 psu->getDevicePath();
627
628 createError(
629 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
630 additionalData);
631
632 psu->setFaultLogged();
633 }
634 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600635 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000636 // Include STATUS_INPUT for input faults.
637 additionalData["STATUS_INPUT"] =
638 fmt::format("{:#02x}", psu->getStatusInput());
639
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600640 /* The power supply location might be needed if the input
641 * fault is due to a problem with the power supply itself.
642 * Include the inventory path with a call out priority of
643 * low.
644 */
645 additionalData["CALLOUT_INVENTORY_PATH"] =
646 psu->getInventoryPath();
647 additionalData["CALLOUT_PRIORITY"] = "L";
648 createError("xyz.openbmc_project.Power.PowerSupply.Error."
649 "InputFault",
650 additionalData);
651 psu->setFaultLogged();
652 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000653 else if (psu->hasPSKillFault())
654 {
655 createError(
656 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
657 additionalData);
658 psu->setFaultLogged();
659 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000660 else if (psu->hasVoutOVFault())
661 {
662 // Include STATUS_VOUT for Vout faults.
663 additionalData["STATUS_VOUT"] =
664 fmt::format("{:#02x}", psu->getStatusVout());
665
666 additionalData["CALLOUT_INVENTORY_PATH"] =
667 psu->getInventoryPath();
668
669 createError(
670 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
671 additionalData);
672
673 psu->setFaultLogged();
674 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000675 else if (psu->hasIoutOCFault())
676 {
677 // Include STATUS_IOUT for Iout faults.
678 additionalData["STATUS_IOUT"] =
679 fmt::format("{:#02x}", psu->getStatusIout());
680
681 createError(
682 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
683 additionalData);
684
685 psu->setFaultLogged();
686 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000687 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
688 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000689 {
690 // Include STATUS_VOUT for Vout faults.
691 additionalData["STATUS_VOUT"] =
692 fmt::format("{:#02x}", psu->getStatusVout());
693
694 additionalData["CALLOUT_INVENTORY_PATH"] =
695 psu->getInventoryPath();
696
697 createError(
698 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
699 additionalData);
700
701 psu->setFaultLogged();
702 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000703 // A fan fault should have priority over a temperature fault,
704 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500705 // Only process if not in power fault window.
706 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000707 {
708 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
709 additionalData["STATUS_TEMPERATURE"] =
710 fmt::format("{:#02x}", psu->getStatusTemperature());
711 additionalData["STATUS_FANS_1_2"] =
712 fmt::format("{:#02x}", psu->getStatusFans12());
713
714 additionalData["CALLOUT_INVENTORY_PATH"] =
715 psu->getInventoryPath();
716
717 createError(
718 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
719 additionalData);
720
721 psu->setFaultLogged();
722 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000723 else if (psu->hasTempFault())
724 {
725 // Include STATUS_TEMPERATURE for temperature faults.
726 additionalData["STATUS_TEMPERATURE"] =
727 fmt::format("{:#02x}", psu->getStatusTemperature());
728
729 additionalData["CALLOUT_INVENTORY_PATH"] =
730 psu->getInventoryPath();
731
732 createError(
733 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
734 additionalData);
735
736 psu->setFaultLogged();
737 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600738 else if (psu->hasMFRFault())
739 {
740 /* This can represent a variety of faults that result in
741 * calling out the power supply for replacement: Output
742 * OverCurrent, Output Under Voltage, and potentially other
743 * faults.
744 *
745 * Also plan on putting specific fault in AdditionalData,
746 * along with register names and register values
747 * (STATUS_WORD, STATUS_MFR, etc.).*/
748
749 additionalData["CALLOUT_INVENTORY_PATH"] =
750 psu->getInventoryPath();
751
752 createError(
753 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500754 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500755
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600756 psu->setFaultLogged();
757 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500758 // Only process if not in power fault window.
759 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000760 {
761 /* POWER_GOOD# is not low, or OFF is on */
762 additionalData["CALLOUT_INVENTORY_PATH"] =
763 psu->getInventoryPath();
764
765 createError(
766 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
767 additionalData);
768
769 psu->setFaultLogged();
770 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500771 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500772 }
773 }
774}
775
Jim Wright7f9288c2022-12-08 11:57:04 -0600776void PSUManager::analyzeBrownout()
777{
778 // Count number of power supplies failing
779 size_t presentCount = 0;
780 size_t notPresentCount = 0;
781 size_t acFailedCount = 0;
782 size_t pgoodFailedCount = 0;
783 for (const auto& psu : psus)
784 {
785 if (psu->isPresent())
786 {
787 ++presentCount;
788 if (psu->hasACFault())
789 {
790 ++acFailedCount;
791 }
792 else if (psu->hasPgoodFault())
793 {
794 ++pgoodFailedCount;
795 }
796 }
797 else
798 {
799 ++notPresentCount;
800 }
801 }
802
803 // Only issue brownout failure if chassis pgood has failed, it has not
804 // already been logged, at least one PSU has seen an AC fail, and all
805 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
806 // at least one PSU is present.
807 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
808 (presentCount == (acFailedCount + pgoodFailedCount)))
809 {
810 // Indicate that the system is in a brownout condition by creating an
811 // error log and setting the PowerSystemInputs status property to Fault.
812 powerSystemInputs.status(
813 sdbusplus::xyz::openbmc_project::State::Decorator::server::
814 PowerSystemInputs::Status::Fault);
815
816 std::map<std::string, std::string> additionalData;
817 additionalData.emplace("NOT_PRESENT_COUNT",
818 std::to_string(notPresentCount));
819 additionalData.emplace("VIN_FAULT_COUNT",
820 std::to_string(acFailedCount));
821 additionalData.emplace("PGOOD_FAULT_COUNT",
822 std::to_string(pgoodFailedCount));
823 log<level::INFO>(
824 fmt::format(
825 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
826 notPresentCount, acFailedCount, pgoodFailedCount)
827 .c_str());
828
829 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
830 additionalData);
831 brownoutLogged = true;
832 }
833 else
834 {
835 // If a brownout was previously logged but at least one PSU is not
836 // currently in AC fault, determine if the brownout condition can be
837 // cleared
838 if (brownoutLogged && (acFailedCount < presentCount))
839 {
840 // Chassis only recognizes the PowerSystemInputs change when it is
841 // off
842 try
843 {
844 using PowerState = sdbusplus::xyz::openbmc_project::State::
845 server::Chassis::PowerState;
846 PowerState currentPowerState;
847 util::getProperty<PowerState>(
848 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
849 "/xyz/openbmc_project/state/chassis0",
850 "xyz.openbmc_project.State.Chassis", bus,
851 currentPowerState);
852
853 if (currentPowerState == PowerState::Off)
854 {
855 // Indicate that the system is no longer in a brownout
856 // condition by setting the PowerSystemInputs status
857 // property to Good.
858 log<level::INFO>(
859 fmt::format(
860 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
861 notPresentCount, acFailedCount, pgoodFailedCount)
862 .c_str());
863 powerSystemInputs.status(
864 sdbusplus::xyz::openbmc_project::State::Decorator::
865 server::PowerSystemInputs::Status::Good);
866 brownoutLogged = false;
867 }
868 }
869 catch (const std::exception& e)
870 {
871 log<level::ERR>(
872 fmt::format("Error trying to clear brownout, error: {}",
873 e.what())
874 .c_str());
875 }
876 }
877 }
878}
879
Brandon Wyman64e97752022-06-03 23:50:13 +0000880void PSUManager::updateMissingPSUs()
881{
882 if (supportedConfigs.empty() || psus.empty())
883 {
884 return;
885 }
886
887 // Power supplies default to missing. If the power supply is present,
888 // the PowerSupply object will update the inventory Present property to
889 // true. If we have less than the required number of power supplies, and
890 // this power supply is missing, update the inventory Present property
891 // to false to indicate required power supply is missing. Avoid
892 // indicating power supply missing if not required.
893
894 auto presentCount =
895 std::count_if(psus.begin(), psus.end(),
896 [](const auto& psu) { return psu->isPresent(); });
897
898 for (const auto& config : supportedConfigs)
899 {
900 for (const auto& psu : psus)
901 {
902 auto psuModel = psu->getModelName();
903 auto psuShortName = psu->getShortName();
904 auto psuInventoryPath = psu->getInventoryPath();
905 auto relativeInvPath =
906 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
907 auto psuPresent = psu->isPresent();
908 auto presProperty = false;
909 auto propReadFail = false;
910
911 try
912 {
913 presProperty = getPresence(bus, psuInventoryPath);
914 propReadFail = false;
915 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500916 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000917 {
918 propReadFail = true;
919 // Relying on property change or interface added to retry.
920 // Log an informational trace to the journal.
921 log<level::INFO>(
922 fmt::format("D-Bus property {} access failure exception",
923 psuInventoryPath)
924 .c_str());
925 }
926
927 if (psuModel.empty())
928 {
929 if (!propReadFail && (presProperty != psuPresent))
930 {
931 // We already have this property, and it is not false
932 // set Present to false
933 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
934 }
935 continue;
936 }
937
938 if (config.first != psuModel)
939 {
940 continue;
941 }
942
943 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
944 {
945 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
946 }
947 }
948 }
949}
950
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000951void PSUManager::validateConfig()
952{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000953 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000954 {
955 return;
956 }
957
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000958 for (const auto& psu : psus)
959 {
960 if ((psu->hasInputFault() || psu->hasVINUVFault()))
961 {
962 // Do not try to validate if input voltage fault present.
963 validationTimer->restartOnce(validationTimeout);
964 return;
965 }
966 }
967
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000968 std::map<std::string, std::string> additionalData;
969 auto supported = hasRequiredPSUs(additionalData);
970 if (supported)
971 {
972 runValidateConfig = false;
973 return;
974 }
975
976 // Validation failed, create an error log.
977 // Return without setting the runValidateConfig flag to false because
978 // it may be that an additional supported configuration interface is
979 // added and we need to validate it to see if it matches this system.
980 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
981 additionalData);
982}
983
984bool PSUManager::hasRequiredPSUs(
985 std::map<std::string, std::string>& additionalData)
986{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000987 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000988 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000989 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000990 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000991 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000992
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000993 auto presentCount =
994 std::count_if(psus.begin(), psus.end(),
995 [](const auto& psu) { return psu->isPresent(); });
996
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000997 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000998 // power supply model configuration. Since all configurations need to be
999 // checked, the additional data would contain only the information of the
1000 // last configuration that did not match.
1001 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001002 for (const auto& config : supportedConfigs)
1003 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001004 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001005 {
1006 continue;
1007 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001008
Jim Wright941b60d2022-10-19 16:22:17 -05001009 // Number of power supplies present should equal or exceed the expected
1010 // count
1011 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001012 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001013 tmpAdditionalData.clear();
1014 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001015 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001016 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001017 continue;
1018 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001019
1020 bool voltageValidated = true;
1021 for (const auto& psu : psus)
1022 {
1023 if (!psu->isPresent())
1024 {
1025 // Only present PSUs report a valid input voltage
1026 continue;
1027 }
1028
1029 double actualInputVoltage;
1030 int inputVoltage;
1031 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1032
1033 if (std::find(config.second.inputVoltage.begin(),
1034 config.second.inputVoltage.end(),
1035 inputVoltage) == config.second.inputVoltage.end())
1036 {
1037 tmpAdditionalData.clear();
1038 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1039 std::to_string(actualInputVoltage);
1040 for (const auto& voltage : config.second.inputVoltage)
1041 {
1042 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1043 std::to_string(voltage) + " ";
1044 }
1045 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1046 psu->getInventoryPath();
1047
1048 voltageValidated = false;
1049 break;
1050 }
1051 }
1052 if (!voltageValidated)
1053 {
1054 continue;
1055 }
1056
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001057 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001058 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001059
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001060 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001061 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001062}
1063
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001064unsigned int PSUManager::getRequiredPSUCount()
1065{
1066 unsigned int requiredCount{0};
1067
1068 // Verify we have the supported configuration and PSU information
1069 if (!supportedConfigs.empty() && !psus.empty())
1070 {
1071 // Find PSU models. They should all be the same.
1072 std::set<std::string> models{};
1073 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1074 if (!psu->getModelName().empty())
1075 {
1076 models.insert(psu->getModelName());
1077 }
1078 });
1079
1080 // If exactly one model was found, find corresponding configuration
1081 if (models.size() == 1)
1082 {
1083 const std::string& model = *(models.begin());
1084 auto it = supportedConfigs.find(model);
1085 if (it != supportedConfigs.end())
1086 {
1087 requiredCount = it->second.powerSupplyCount;
1088 }
1089 }
1090 }
1091
1092 return requiredCount;
1093}
1094
1095bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1096{
1097 // Get required number of PSUs; if not found, we don't know if PSU required
1098 unsigned int requiredCount = getRequiredPSUCount();
1099 if (requiredCount == 0)
1100 {
1101 return false;
1102 }
1103
1104 // If total PSU count <= the required count, all PSUs are required
1105 if (psus.size() <= requiredCount)
1106 {
1107 return true;
1108 }
1109
1110 // We don't currently get information from EntityManager about which PSUs
1111 // are required, so we have to do some guesswork. First check if this PSU
1112 // is present. If so, assume it is required.
1113 if (psu.isPresent())
1114 {
1115 return true;
1116 }
1117
1118 // This PSU is not present. Count the number of other PSUs that are
1119 // present. If enough other PSUs are present, assume the specified PSU is
1120 // not required.
1121 unsigned int psuCount =
1122 std::count_if(psus.begin(), psus.end(),
1123 [](const auto& psu) { return psu->isPresent(); });
1124 if (psuCount >= requiredCount)
1125 {
1126 return false;
1127 }
1128
1129 // Check if this PSU was previously present. If so, assume it is required.
1130 // We know it was previously present if it has a non-empty model name.
1131 if (!psu.getModelName().empty())
1132 {
1133 return true;
1134 }
1135
1136 // This PSU was never present. Count the number of other PSUs that were
1137 // previously present. If including those PSUs is enough, assume the
1138 // specified PSU is not required.
1139 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1140 return (!psu->isPresent() && !psu->getModelName().empty());
1141 });
1142 if (psuCount >= requiredCount)
1143 {
1144 return false;
1145 }
1146
1147 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1148 // that were never present. PSU inventory paths typically end with the PSU
1149 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1150 std::vector<std::string> sortedPaths;
1151 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1152 if (!psu->isPresent() && psu->getModelName().empty())
1153 {
1154 sortedPaths.push_back(psu->getInventoryPath());
1155 }
1156 });
1157 std::sort(sortedPaths.begin(), sortedPaths.end());
1158
1159 // Check if specified PSU is close enough to start of list to be required
1160 for (const auto& path : sortedPaths)
1161 {
1162 if (path == psu.getInventoryPath())
1163 {
1164 return true;
1165 }
1166 if (++psuCount >= requiredCount)
1167 {
1168 break;
1169 }
1170 }
1171
1172 // PSU was not close to start of sorted list; assume not required
1173 return false;
1174}
1175
Adriana Kobylak523704d2021-09-21 15:55:41 +00001176bool PSUManager::validateModelName(
1177 std::string& model, std::map<std::string, std::string>& additionalData)
1178{
1179 // Check that all PSUs have the same model name. Initialize the model
1180 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001181 // against the rest of the PSUs and get its inventory path to use as callout
1182 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001183 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001184 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001185 for (const auto& psu : psus)
1186 {
1187 auto psuModel = psu->getModelName();
1188 if (psuModel.empty())
1189 {
1190 continue;
1191 }
1192 if (model.empty())
1193 {
1194 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001195 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001196 continue;
1197 }
1198 if (psuModel != model)
1199 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001200 if (supportedConfigs.find(model) != supportedConfigs.end())
1201 {
1202 // The base model is supported, callout the mismatched PSU. The
1203 // mismatched PSU may or may not be supported.
1204 additionalData["EXPECTED_MODEL"] = model;
1205 additionalData["ACTUAL_MODEL"] = psuModel;
1206 additionalData["CALLOUT_INVENTORY_PATH"] =
1207 psu->getInventoryPath();
1208 }
1209 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1210 {
1211 // The base model is not supported, but the mismatched PSU is,
1212 // callout the base PSU.
1213 additionalData["EXPECTED_MODEL"] = psuModel;
1214 additionalData["ACTUAL_MODEL"] = model;
1215 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1216 }
1217 else
1218 {
1219 // The base model and the mismatched PSU are not supported or
1220 // could not be found in the supported configuration, callout
1221 // the mismatched PSU.
1222 additionalData["EXPECTED_MODEL"] = model;
1223 additionalData["ACTUAL_MODEL"] = psuModel;
1224 additionalData["CALLOUT_INVENTORY_PATH"] =
1225 psu->getInventoryPath();
1226 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001227 model.clear();
1228 return false;
1229 }
1230 }
1231 return true;
1232}
1233
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001234void PSUManager::setPowerConfigGPIO()
1235{
1236 if (!powerConfigGPIO)
1237 {
1238 return;
1239 }
1240
1241 std::string model{};
1242 std::map<std::string, std::string> additionalData;
1243 if (!validateModelName(model, additionalData))
1244 {
1245 return;
1246 }
1247
1248 auto config = supportedConfigs.find(model);
1249 if (config != supportedConfigs.end())
1250 {
1251 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1252 // if the supported configuration indicates that this system model
1253 // expects the maximum number of power supplies (full load set to true).
1254 // Else, set it to high (1), this is the default.
1255 auto powerConfigValue =
1256 (config->second.powerConfigFullLoad == true ? 0 : 1);
1257 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1258 powerConfigGPIO->write(powerConfigValue, flags);
1259 }
1260}
1261
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001262} // namespace phosphor::power::manager