blob: 68ff7fb5585880d5f6b84329029d4a5f3404e2a5 [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());
George Liu9464c422023-02-27 14:30:27 +0800253 auto psu = std::make_unique<PowerSupply>(
254 bus, invpath, *i2cbus, *i2caddr, driver, presline,
255 std::bind(
256 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
257 this));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600258 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000259
260 // Subscribe to power supply presence changes
261 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
262 bus,
263 sdbusplus::bus::match::rules::propertiesChanged(invpath,
264 INVENTORY_IFACE),
265 [this](auto& msg) { this->presenceChanged(msg); });
266 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600267 }
268
269 if (psus.empty())
270 {
271 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
272 }
273}
274
Adriana Kobylake1074d82021-03-16 20:46:44 +0000275void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
276{
277 try
278 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000279 auto propIt = properties.find("SupportedType");
280 if (propIt == properties.end())
281 {
282 return;
283 }
284 const std::string* type = std::get_if<std::string>(&(propIt->second));
285 if ((type == nullptr) || (*type != "PowerSupply"))
286 {
287 return;
288 }
289
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000290 propIt = properties.find("SupportedModel");
291 if (propIt == properties.end())
292 {
293 return;
294 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000295 const std::string* model = std::get_if<std::string>(&(propIt->second));
296 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000297 {
298 return;
299 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000300
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000301 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000302 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000303 if (propIt != properties.end())
304 {
305 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
306 if (count != nullptr)
307 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000308 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000309 }
310 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000311 propIt = properties.find("InputVoltage");
312 if (propIt != properties.end())
313 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000314 const std::vector<uint64_t>* voltage =
315 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000316 if (voltage != nullptr)
317 {
318 sys.inputVoltage = *voltage;
319 }
320 }
321
Adriana Kobylak886574c2021-11-01 18:22:28 +0000322 // The PowerConfigFullLoad is an optional property, default it to false
323 // since that's the default value of the power-config-full-load GPIO.
324 sys.powerConfigFullLoad = false;
325 propIt = properties.find("PowerConfigFullLoad");
326 if (propIt != properties.end())
327 {
328 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
329 if (fullLoad != nullptr)
330 {
331 sys.powerConfigFullLoad = *fullLoad;
332 }
333 }
334
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000335 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000336 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500337 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000338 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000339}
340
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600341void PSUManager::getSystemProperties()
342{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600343
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600344 try
345 {
346 util::DbusSubtree subtree =
347 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000348 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600349 {
350 throw std::runtime_error("Supported Configuration Not Found");
351 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600352
Adriana Kobylake1074d82021-03-16 20:46:44 +0000353 for (const auto& [objPath, services] : subtree)
354 {
355 std::string service = services.begin()->first;
356 if (objPath.empty() || service.empty())
357 {
358 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600359 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000360 auto properties = util::getAllProperties(
361 bus, objPath, supportedConfIntf, service);
362 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600363 }
364 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500365 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600366 {
367 // Interface or property not found. Let the Interfaces Added callback
368 // process the information once the interfaces are added to D-Bus.
369 }
370}
371
Patrick Williams7354ce62022-07-22 19:26:56 -0500372void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600373{
374 try
375 {
376 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000377 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600378 interfaces;
379 msg.read(objPath, interfaces);
380
381 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600382 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600383 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600384 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000385 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600386 }
387
Brandon Wyman510acaa2020-11-05 18:32:04 -0600388 itIntf = interfaces.find(IBMCFFPSInterface);
389 if (itIntf != interfaces.cend())
390 {
391 log<level::INFO>(
392 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
393 .c_str());
394 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000395 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600396 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000397
398 // Call to validate the psu configuration if the power is on and both
399 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
400 // processed
401 if (powerOn && !psus.empty() && !supportedConfigs.empty())
402 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000403 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000404 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600405 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500406 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600407 {
408 // Ignore, the property may be of a different type than expected.
409 }
410}
411
Patrick Williams7354ce62022-07-22 19:26:56 -0500412void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500413{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500414 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500415 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500416 msg.read(msgSensor, msgData);
417
Jim Wrightaca86d02022-06-10 12:01:39 -0500418 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500419 auto valPropMap = msgData.find("state");
420 if (valPropMap != msgData.end())
421 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500422 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500423 if (state)
424 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500425 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500426 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500427 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000428 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500429 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000430 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000431 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500432 }
433 else
434 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500435 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500436 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500437 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000438 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500439 }
440 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500441
442 // Check if it was the pgood property that changed.
443 valPropMap = msgData.find("pgood");
444 if (valPropMap != msgData.end())
445 {
446 int pgood = std::get<int>(valPropMap->second);
447 if (!pgood)
448 {
449 // Chassis power good has turned off
450 if (powerOn)
451 {
452 // pgood is off but state is on, in power fault window
453 powerFaultOccurring = true;
454 }
455 }
456 }
457 log<level::INFO>(
458 fmt::format(
459 "powerStateChanged: power on: {}, power fault occurring: {}",
460 powerOn, powerFaultOccurring)
461 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500462}
463
Patrick Williams7354ce62022-07-22 19:26:56 -0500464void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000465{
466 std::string msgSensor;
467 std::map<std::string, std::variant<uint32_t, bool>> msgData;
468 msg.read(msgSensor, msgData);
469
470 // Check if it was the Present property that changed.
471 auto valPropMap = msgData.find(PRESENT_PROP);
472 if (valPropMap != msgData.end())
473 {
474 if (std::get<bool>(valPropMap->second))
475 {
476 // A PSU became present, force the PSU validation to run.
477 runValidateConfig = true;
478 validationTimer->restartOnce(validationTimeout);
479 }
480 }
481}
482
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000483void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
484{
485 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000486 constexpr auto method = "setPowerSupplyError";
487
488 try
489 {
490 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600491 auto methodMsg = bus.new_method_call(
492 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000493 methodMsg.append(psuErrorString);
494 auto callReply = bus.call(methodMsg);
495 }
496 catch (const std::exception& e)
497 {
498 log<level::INFO>(
499 fmt::format("Failed calling setPowerSupplyError due to error {}",
500 e.what())
501 .c_str());
502 }
503}
504
Brandon Wyman8b662882021-10-08 17:31:51 +0000505void PSUManager::createError(const std::string& faultName,
506 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500507{
508 using namespace sdbusplus::xyz::openbmc_project;
509 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
510 constexpr auto loggingCreateInterface =
511 "xyz.openbmc_project.Logging.Create";
512
513 try
514 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000515 additionalData["_PID"] = std::to_string(getpid());
516
Brandon Wymanb76ab242020-09-16 18:06:06 -0500517 auto service =
518 util::getService(loggingObjectPath, loggingCreateInterface, bus);
519
520 if (service.empty())
521 {
522 log<level::ERR>("Unable to get logging manager service");
523 return;
524 }
525
526 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
527 loggingCreateInterface, "Create");
528
529 auto level = Logging::server::Entry::Level::Error;
530 method.append(faultName, level, additionalData);
531
532 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000533 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500534 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500535 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500536 {
537 log<level::ERR>(
538 fmt::format(
539 "Failed creating event log for fault {} due to error {}",
540 faultName, e.what())
541 .c_str());
542 }
543}
544
Brandon Wyman18a24d92022-04-19 22:48:34 +0000545void PSUManager::syncHistory()
546{
547 log<level::INFO>("Synchronize INPUT_HISTORY");
548
549 if (!syncHistoryGPIO)
550 {
551 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
552 }
553 if (syncHistoryGPIO)
554 {
555 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
556 syncHistoryGPIO->toggleLowHigh(delay);
557 for (auto& psu : psus)
558 {
559 psu->clearSyncHistoryRequired();
560 }
561 }
562
563 log<level::INFO>("Synchronize INPUT_HISTORY completed");
564}
565
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500566void PSUManager::analyze()
567{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000568 auto syncHistoryRequired =
569 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
570 return psu->isSyncHistoryRequired();
571 });
572 if (syncHistoryRequired)
573 {
574 syncHistory();
575 }
576
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500577 for (auto& psu : psus)
578 {
579 psu->analyze();
580 }
581
Jim Wright7f9288c2022-12-08 11:57:04 -0600582 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000583
Jim Wrightcefe85f2022-11-18 09:57:47 -0600584 // Only perform individual PSU analysis if power is on and a brownout has
585 // not already been logged
586 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500587 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600588 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500589 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600590 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000591
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600592 if (!psu->isFaultLogged() && !psu->isPresent())
593 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000594 std::map<std::string, std::string> requiredPSUsData;
595 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500596 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000597 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000598 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000599 // Create error for power supply missing.
600 additionalData["CALLOUT_INVENTORY_PATH"] =
601 psu->getInventoryPath();
602 additionalData["CALLOUT_PRIORITY"] = "H";
603 createError(
604 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
605 additionalData);
606 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600607 psu->setFaultLogged();
608 }
609 else if (!psu->isFaultLogged() && psu->isFaulted())
610 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000611 // Add STATUS_WORD and STATUS_MFR last response, in padded
612 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600613 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000614 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600615 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000616 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600617 // If there are faults being reported, they possibly could be
618 // related to a bug in the firmware version running on the power
619 // supply. Capture that data into the error as well.
620 additionalData["FW_VERSION"] = psu->getFWVersion();
621
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000622 if (psu->hasCommFault())
623 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000624 additionalData["STATUS_CML"] =
625 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000626 /* Attempts to communicate with the power supply have
627 * reached there limit. Create an error. */
628 additionalData["CALLOUT_DEVICE_PATH"] =
629 psu->getDevicePath();
630
631 createError(
632 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
633 additionalData);
634
635 psu->setFaultLogged();
636 }
637 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600638 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000639 // Include STATUS_INPUT for input faults.
640 additionalData["STATUS_INPUT"] =
641 fmt::format("{:#02x}", psu->getStatusInput());
642
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600643 /* The power supply location might be needed if the input
644 * fault is due to a problem with the power supply itself.
645 * Include the inventory path with a call out priority of
646 * low.
647 */
648 additionalData["CALLOUT_INVENTORY_PATH"] =
649 psu->getInventoryPath();
650 additionalData["CALLOUT_PRIORITY"] = "L";
651 createError("xyz.openbmc_project.Power.PowerSupply.Error."
652 "InputFault",
653 additionalData);
654 psu->setFaultLogged();
655 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000656 else if (psu->hasPSKillFault())
657 {
658 createError(
659 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
660 additionalData);
661 psu->setFaultLogged();
662 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000663 else if (psu->hasVoutOVFault())
664 {
665 // Include STATUS_VOUT for Vout faults.
666 additionalData["STATUS_VOUT"] =
667 fmt::format("{:#02x}", psu->getStatusVout());
668
669 additionalData["CALLOUT_INVENTORY_PATH"] =
670 psu->getInventoryPath();
671
672 createError(
673 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
674 additionalData);
675
676 psu->setFaultLogged();
677 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000678 else if (psu->hasIoutOCFault())
679 {
680 // Include STATUS_IOUT for Iout faults.
681 additionalData["STATUS_IOUT"] =
682 fmt::format("{:#02x}", psu->getStatusIout());
683
684 createError(
685 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
686 additionalData);
687
688 psu->setFaultLogged();
689 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000690 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
691 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000692 {
693 // Include STATUS_VOUT for Vout faults.
694 additionalData["STATUS_VOUT"] =
695 fmt::format("{:#02x}", psu->getStatusVout());
696
697 additionalData["CALLOUT_INVENTORY_PATH"] =
698 psu->getInventoryPath();
699
700 createError(
701 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
702 additionalData);
703
704 psu->setFaultLogged();
705 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000706 // A fan fault should have priority over a temperature fault,
707 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500708 // Only process if not in power fault window.
709 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000710 {
711 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
712 additionalData["STATUS_TEMPERATURE"] =
713 fmt::format("{:#02x}", psu->getStatusTemperature());
714 additionalData["STATUS_FANS_1_2"] =
715 fmt::format("{:#02x}", psu->getStatusFans12());
716
717 additionalData["CALLOUT_INVENTORY_PATH"] =
718 psu->getInventoryPath();
719
720 createError(
721 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
722 additionalData);
723
724 psu->setFaultLogged();
725 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000726 else if (psu->hasTempFault())
727 {
728 // Include STATUS_TEMPERATURE for temperature faults.
729 additionalData["STATUS_TEMPERATURE"] =
730 fmt::format("{:#02x}", psu->getStatusTemperature());
731
732 additionalData["CALLOUT_INVENTORY_PATH"] =
733 psu->getInventoryPath();
734
735 createError(
736 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
737 additionalData);
738
739 psu->setFaultLogged();
740 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600741 else if (psu->hasMFRFault())
742 {
743 /* This can represent a variety of faults that result in
744 * calling out the power supply for replacement: Output
745 * OverCurrent, Output Under Voltage, and potentially other
746 * faults.
747 *
748 * Also plan on putting specific fault in AdditionalData,
749 * along with register names and register values
750 * (STATUS_WORD, STATUS_MFR, etc.).*/
751
752 additionalData["CALLOUT_INVENTORY_PATH"] =
753 psu->getInventoryPath();
754
755 createError(
756 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500757 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500758
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600759 psu->setFaultLogged();
760 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500761 // Only process if not in power fault window.
762 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000763 {
764 /* POWER_GOOD# is not low, or OFF is on */
765 additionalData["CALLOUT_INVENTORY_PATH"] =
766 psu->getInventoryPath();
767
768 createError(
769 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
770 additionalData);
771
772 psu->setFaultLogged();
773 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500774 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500775 }
776 }
777}
778
Jim Wright7f9288c2022-12-08 11:57:04 -0600779void PSUManager::analyzeBrownout()
780{
781 // Count number of power supplies failing
782 size_t presentCount = 0;
783 size_t notPresentCount = 0;
784 size_t acFailedCount = 0;
785 size_t pgoodFailedCount = 0;
786 for (const auto& psu : psus)
787 {
788 if (psu->isPresent())
789 {
790 ++presentCount;
791 if (psu->hasACFault())
792 {
793 ++acFailedCount;
794 }
795 else if (psu->hasPgoodFault())
796 {
797 ++pgoodFailedCount;
798 }
799 }
800 else
801 {
802 ++notPresentCount;
803 }
804 }
805
806 // Only issue brownout failure if chassis pgood has failed, it has not
807 // already been logged, at least one PSU has seen an AC fail, and all
808 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
809 // at least one PSU is present.
810 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
811 (presentCount == (acFailedCount + pgoodFailedCount)))
812 {
813 // Indicate that the system is in a brownout condition by creating an
814 // error log and setting the PowerSystemInputs status property to Fault.
815 powerSystemInputs.status(
816 sdbusplus::xyz::openbmc_project::State::Decorator::server::
817 PowerSystemInputs::Status::Fault);
818
819 std::map<std::string, std::string> additionalData;
820 additionalData.emplace("NOT_PRESENT_COUNT",
821 std::to_string(notPresentCount));
822 additionalData.emplace("VIN_FAULT_COUNT",
823 std::to_string(acFailedCount));
824 additionalData.emplace("PGOOD_FAULT_COUNT",
825 std::to_string(pgoodFailedCount));
826 log<level::INFO>(
827 fmt::format(
828 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
829 notPresentCount, acFailedCount, pgoodFailedCount)
830 .c_str());
831
832 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
833 additionalData);
834 brownoutLogged = true;
835 }
836 else
837 {
838 // If a brownout was previously logged but at least one PSU is not
839 // currently in AC fault, determine if the brownout condition can be
840 // cleared
841 if (brownoutLogged && (acFailedCount < presentCount))
842 {
843 // Chassis only recognizes the PowerSystemInputs change when it is
844 // off
845 try
846 {
847 using PowerState = sdbusplus::xyz::openbmc_project::State::
848 server::Chassis::PowerState;
849 PowerState currentPowerState;
850 util::getProperty<PowerState>(
851 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
852 "/xyz/openbmc_project/state/chassis0",
853 "xyz.openbmc_project.State.Chassis", bus,
854 currentPowerState);
855
856 if (currentPowerState == PowerState::Off)
857 {
858 // Indicate that the system is no longer in a brownout
859 // condition by setting the PowerSystemInputs status
860 // property to Good.
861 log<level::INFO>(
862 fmt::format(
863 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
864 notPresentCount, acFailedCount, pgoodFailedCount)
865 .c_str());
866 powerSystemInputs.status(
867 sdbusplus::xyz::openbmc_project::State::Decorator::
868 server::PowerSystemInputs::Status::Good);
869 brownoutLogged = false;
870 }
871 }
872 catch (const std::exception& e)
873 {
874 log<level::ERR>(
875 fmt::format("Error trying to clear brownout, error: {}",
876 e.what())
877 .c_str());
878 }
879 }
880 }
881}
882
Brandon Wyman64e97752022-06-03 23:50:13 +0000883void PSUManager::updateMissingPSUs()
884{
885 if (supportedConfigs.empty() || psus.empty())
886 {
887 return;
888 }
889
890 // Power supplies default to missing. If the power supply is present,
891 // the PowerSupply object will update the inventory Present property to
892 // true. If we have less than the required number of power supplies, and
893 // this power supply is missing, update the inventory Present property
894 // to false to indicate required power supply is missing. Avoid
895 // indicating power supply missing if not required.
896
897 auto presentCount =
898 std::count_if(psus.begin(), psus.end(),
899 [](const auto& psu) { return psu->isPresent(); });
900
901 for (const auto& config : supportedConfigs)
902 {
903 for (const auto& psu : psus)
904 {
905 auto psuModel = psu->getModelName();
906 auto psuShortName = psu->getShortName();
907 auto psuInventoryPath = psu->getInventoryPath();
908 auto relativeInvPath =
909 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
910 auto psuPresent = psu->isPresent();
911 auto presProperty = false;
912 auto propReadFail = false;
913
914 try
915 {
916 presProperty = getPresence(bus, psuInventoryPath);
917 propReadFail = false;
918 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500919 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000920 {
921 propReadFail = true;
922 // Relying on property change or interface added to retry.
923 // Log an informational trace to the journal.
924 log<level::INFO>(
925 fmt::format("D-Bus property {} access failure exception",
926 psuInventoryPath)
927 .c_str());
928 }
929
930 if (psuModel.empty())
931 {
932 if (!propReadFail && (presProperty != psuPresent))
933 {
934 // We already have this property, and it is not false
935 // set Present to false
936 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
937 }
938 continue;
939 }
940
941 if (config.first != psuModel)
942 {
943 continue;
944 }
945
946 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
947 {
948 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
949 }
950 }
951 }
952}
953
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000954void PSUManager::validateConfig()
955{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000956 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000957 {
958 return;
959 }
960
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000961 for (const auto& psu : psus)
962 {
963 if ((psu->hasInputFault() || psu->hasVINUVFault()))
964 {
965 // Do not try to validate if input voltage fault present.
966 validationTimer->restartOnce(validationTimeout);
967 return;
968 }
969 }
970
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000971 std::map<std::string, std::string> additionalData;
972 auto supported = hasRequiredPSUs(additionalData);
973 if (supported)
974 {
975 runValidateConfig = false;
976 return;
977 }
978
979 // Validation failed, create an error log.
980 // Return without setting the runValidateConfig flag to false because
981 // it may be that an additional supported configuration interface is
982 // added and we need to validate it to see if it matches this system.
983 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
984 additionalData);
985}
986
987bool PSUManager::hasRequiredPSUs(
988 std::map<std::string, std::string>& additionalData)
989{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000990 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000991 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000992 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000993 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000994 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000995
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000996 auto presentCount =
997 std::count_if(psus.begin(), psus.end(),
998 [](const auto& psu) { return psu->isPresent(); });
999
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001000 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001001 // power supply model configuration. Since all configurations need to be
1002 // checked, the additional data would contain only the information of the
1003 // last configuration that did not match.
1004 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001005 for (const auto& config : supportedConfigs)
1006 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001007 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001008 {
1009 continue;
1010 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001011
Jim Wright941b60d2022-10-19 16:22:17 -05001012 // Number of power supplies present should equal or exceed the expected
1013 // count
1014 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001015 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001016 tmpAdditionalData.clear();
1017 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001018 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001019 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001020 continue;
1021 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001022
1023 bool voltageValidated = true;
1024 for (const auto& psu : psus)
1025 {
1026 if (!psu->isPresent())
1027 {
1028 // Only present PSUs report a valid input voltage
1029 continue;
1030 }
1031
1032 double actualInputVoltage;
1033 int inputVoltage;
1034 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1035
1036 if (std::find(config.second.inputVoltage.begin(),
1037 config.second.inputVoltage.end(),
1038 inputVoltage) == config.second.inputVoltage.end())
1039 {
1040 tmpAdditionalData.clear();
1041 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1042 std::to_string(actualInputVoltage);
1043 for (const auto& voltage : config.second.inputVoltage)
1044 {
1045 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1046 std::to_string(voltage) + " ";
1047 }
1048 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1049 psu->getInventoryPath();
1050
1051 voltageValidated = false;
1052 break;
1053 }
1054 }
1055 if (!voltageValidated)
1056 {
1057 continue;
1058 }
1059
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001060 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001061 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001062
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001063 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001064 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001065}
1066
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001067unsigned int PSUManager::getRequiredPSUCount()
1068{
1069 unsigned int requiredCount{0};
1070
1071 // Verify we have the supported configuration and PSU information
1072 if (!supportedConfigs.empty() && !psus.empty())
1073 {
1074 // Find PSU models. They should all be the same.
1075 std::set<std::string> models{};
1076 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1077 if (!psu->getModelName().empty())
1078 {
1079 models.insert(psu->getModelName());
1080 }
1081 });
1082
1083 // If exactly one model was found, find corresponding configuration
1084 if (models.size() == 1)
1085 {
1086 const std::string& model = *(models.begin());
1087 auto it = supportedConfigs.find(model);
1088 if (it != supportedConfigs.end())
1089 {
1090 requiredCount = it->second.powerSupplyCount;
1091 }
1092 }
1093 }
1094
1095 return requiredCount;
1096}
1097
1098bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1099{
1100 // Get required number of PSUs; if not found, we don't know if PSU required
1101 unsigned int requiredCount = getRequiredPSUCount();
1102 if (requiredCount == 0)
1103 {
1104 return false;
1105 }
1106
1107 // If total PSU count <= the required count, all PSUs are required
1108 if (psus.size() <= requiredCount)
1109 {
1110 return true;
1111 }
1112
1113 // We don't currently get information from EntityManager about which PSUs
1114 // are required, so we have to do some guesswork. First check if this PSU
1115 // is present. If so, assume it is required.
1116 if (psu.isPresent())
1117 {
1118 return true;
1119 }
1120
1121 // This PSU is not present. Count the number of other PSUs that are
1122 // present. If enough other PSUs are present, assume the specified PSU is
1123 // not required.
1124 unsigned int psuCount =
1125 std::count_if(psus.begin(), psus.end(),
1126 [](const auto& psu) { return psu->isPresent(); });
1127 if (psuCount >= requiredCount)
1128 {
1129 return false;
1130 }
1131
1132 // Check if this PSU was previously present. If so, assume it is required.
1133 // We know it was previously present if it has a non-empty model name.
1134 if (!psu.getModelName().empty())
1135 {
1136 return true;
1137 }
1138
1139 // This PSU was never present. Count the number of other PSUs that were
1140 // previously present. If including those PSUs is enough, assume the
1141 // specified PSU is not required.
1142 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1143 return (!psu->isPresent() && !psu->getModelName().empty());
1144 });
1145 if (psuCount >= requiredCount)
1146 {
1147 return false;
1148 }
1149
1150 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1151 // that were never present. PSU inventory paths typically end with the PSU
1152 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1153 std::vector<std::string> sortedPaths;
1154 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1155 if (!psu->isPresent() && psu->getModelName().empty())
1156 {
1157 sortedPaths.push_back(psu->getInventoryPath());
1158 }
1159 });
1160 std::sort(sortedPaths.begin(), sortedPaths.end());
1161
1162 // Check if specified PSU is close enough to start of list to be required
1163 for (const auto& path : sortedPaths)
1164 {
1165 if (path == psu.getInventoryPath())
1166 {
1167 return true;
1168 }
1169 if (++psuCount >= requiredCount)
1170 {
1171 break;
1172 }
1173 }
1174
1175 // PSU was not close to start of sorted list; assume not required
1176 return false;
1177}
1178
Adriana Kobylak523704d2021-09-21 15:55:41 +00001179bool PSUManager::validateModelName(
1180 std::string& model, std::map<std::string, std::string>& additionalData)
1181{
1182 // Check that all PSUs have the same model name. Initialize the model
1183 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001184 // against the rest of the PSUs and get its inventory path to use as callout
1185 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001186 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001187 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001188 for (const auto& psu : psus)
1189 {
1190 auto psuModel = psu->getModelName();
1191 if (psuModel.empty())
1192 {
1193 continue;
1194 }
1195 if (model.empty())
1196 {
1197 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001198 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001199 continue;
1200 }
1201 if (psuModel != model)
1202 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001203 if (supportedConfigs.find(model) != supportedConfigs.end())
1204 {
1205 // The base model is supported, callout the mismatched PSU. The
1206 // mismatched PSU may or may not be supported.
1207 additionalData["EXPECTED_MODEL"] = model;
1208 additionalData["ACTUAL_MODEL"] = psuModel;
1209 additionalData["CALLOUT_INVENTORY_PATH"] =
1210 psu->getInventoryPath();
1211 }
1212 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1213 {
1214 // The base model is not supported, but the mismatched PSU is,
1215 // callout the base PSU.
1216 additionalData["EXPECTED_MODEL"] = psuModel;
1217 additionalData["ACTUAL_MODEL"] = model;
1218 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1219 }
1220 else
1221 {
1222 // The base model and the mismatched PSU are not supported or
1223 // could not be found in the supported configuration, callout
1224 // the mismatched PSU.
1225 additionalData["EXPECTED_MODEL"] = model;
1226 additionalData["ACTUAL_MODEL"] = psuModel;
1227 additionalData["CALLOUT_INVENTORY_PATH"] =
1228 psu->getInventoryPath();
1229 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001230 model.clear();
1231 return false;
1232 }
1233 }
1234 return true;
1235}
1236
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001237void PSUManager::setPowerConfigGPIO()
1238{
1239 if (!powerConfigGPIO)
1240 {
1241 return;
1242 }
1243
1244 std::string model{};
1245 std::map<std::string, std::string> additionalData;
1246 if (!validateModelName(model, additionalData))
1247 {
1248 return;
1249 }
1250
1251 auto config = supportedConfigs.find(model);
1252 if (config != supportedConfigs.end())
1253 {
1254 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1255 // if the supported configuration indicates that this system model
1256 // expects the maximum number of power supplies (full load set to true).
1257 // Else, set it to high (1), this is the default.
1258 auto powerConfigValue =
1259 (config->second.powerConfigFullLoad == true ? 0 : 1);
1260 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1261 powerConfigGPIO->write(powerConfigValue, flags);
1262 }
1263}
1264
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001265} // namespace phosphor::power::manager