blob: 597a1643f8bbeba0cfd841044a562fa40b0a1998 [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
Faisal Awadab66ae502023-04-01 18:30:32 -050037const auto deviceDirPath = "/sys/bus/i2c/devices/";
38const auto driverDirName = "/driver";
39
Brandon Wymanc9e840e2022-05-10 20:48:41 +000040constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
Brandon Wyman18a24d92022-04-19 22:48:34 +000041
Patrick Williams7354ce62022-07-22 19:26:56 -050042PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000043 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
Brandon Wymanc3324422022-03-24 20:30:57 +000044 objectManager(bus, objectManagerObjPath),
Matt Spinlera068f422023-03-10 13:06:49 -060045 historyManager(bus, "/org/open_power/sensors"),
46 sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060047{
Brandon Wyman510acaa2020-11-05 18:32:04 -060048 // Subscribe to InterfacesAdded before doing a property read, otherwise
49 // the interface could be created after the read attempt but before the
50 // match is created.
51 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
52 bus,
53 sdbusplus::bus::match::rules::interfacesAdded() +
54 sdbusplus::bus::match::rules::sender(
55 "xyz.openbmc_project.EntityManager"),
56 std::bind(&PSUManager::entityManagerIfaceAdded, this,
57 std::placeholders::_1));
58 getPSUConfiguration();
59 getSystemProperties();
60
Adriana Kobylakc9b05732022-03-19 15:15:10 +000061 // Request the bus name before the analyze() function, which is the one that
62 // determines the brownout condition and sets the status d-bus property.
63 bus.request_name(managerBusName);
64
Brandon Wyman510acaa2020-11-05 18:32:04 -060065 using namespace sdeventplus;
66 auto interval = std::chrono::milliseconds(1000);
67 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
68 e, std::bind(&PSUManager::analyze, this), interval);
69
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000070 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
71 e, std::bind(&PSUManager::validateConfig, this));
72
Adriana Kobylakc0a07582021-10-13 15:52:25 +000073 try
74 {
75 powerConfigGPIO = createGPIO("power-config-full-load");
76 }
77 catch (const std::exception& e)
78 {
79 // Ignore error, GPIO may not be implemented in this system.
80 powerConfigGPIO = nullptr;
81 }
82
Brandon Wyman510acaa2020-11-05 18:32:04 -060083 // Subscribe to power state changes
84 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
85 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
86 bus,
87 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
88 POWER_IFACE),
89 [this](auto& msg) { this->powerStateChanged(msg); });
90
91 initialize();
92}
93
Jim Wrightaca86d02022-06-10 12:01:39 -050094void PSUManager::initialize()
95{
96 try
97 {
98 // pgood is the latest read of the chassis pgood
99 int pgood = 0;
100 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
101 powerService, bus, pgood);
102
103 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -0600104 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -0500105 POWER_IFACE, "getPowerState");
106 auto reply = bus.call(method);
107 int state = 0;
108 reply.read(state);
109
110 if (state)
111 {
112 // Monitor PSUs anytime state is on
113 powerOn = true;
114 // In the power fault window if pgood is off
115 powerFaultOccurring = !pgood;
116 validationTimer->restartOnce(validationTimeout);
117 }
118 else
119 {
120 // Power is off
121 powerOn = false;
122 powerFaultOccurring = false;
123 runValidateConfig = true;
124 }
125 }
126 catch (const std::exception& e)
127 {
128 log<level::INFO>(
129 fmt::format(
130 "Failed to get power state, assuming it is off, error {}",
131 e.what())
132 .c_str());
133 powerOn = false;
134 powerFaultOccurring = false;
135 runValidateConfig = true;
136 }
137
138 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
139 clearFaults();
140 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500141 setPowerConfigGPIO();
142
143 log<level::INFO>(
144 fmt::format("initialize: power on: {}, power fault occurring: {}",
145 powerOn, powerFaultOccurring)
146 .c_str());
147}
148
Brandon Wyman510acaa2020-11-05 18:32:04 -0600149void PSUManager::getPSUConfiguration()
150{
151 using namespace phosphor::power::util;
152 auto depth = 0;
153 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
154
155 psus.clear();
156
157 // I should get a map of objects back.
158 // Each object will have a path, a service, and an interface.
159 // The interface should match the one passed into this function.
160 for (const auto& [path, services] : objects)
161 {
162 auto service = services.begin()->first;
163
164 if (path.empty() || service.empty())
165 {
166 continue;
167 }
168
169 // For each object in the array of objects, I want to get properties
170 // from the service, path, and interface.
171 auto properties =
172 getAllProperties(bus, path, IBMCFFPSInterface, service);
173
174 getPSUProperties(properties);
175 }
176
177 if (psus.empty())
178 {
179 // Interface or properties not found. Let the Interfaces Added callback
180 // process the information once the interfaces are added to D-Bus.
181 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
182 }
183}
184
185void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
186{
187 // From passed in properties, I want to get: I2CBus, I2CAddress,
188 // and Name. Create a power supply object, using Name to build the inventory
189 // path.
190 const auto basePSUInvPath =
191 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
192 uint64_t* i2cbus = nullptr;
193 uint64_t* i2caddr = nullptr;
194 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000195 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600196
197 for (const auto& property : properties)
198 {
199 try
200 {
201 if (property.first == i2cBusProp)
202 {
203 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
204 }
205 else if (property.first == i2cAddressProp)
206 {
207 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
208 }
209 else if (property.first == psuNameProp)
210 {
211 psuname = std::get_if<std::string>(&properties[psuNameProp]);
212 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000213 else if (property.first == presLineName)
214 {
215 preslineptr =
216 std::get_if<std::string>(&properties[presLineName]);
217 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600218 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500219 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000220 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600221 }
222
223 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
224 {
225 std::string invpath = basePSUInvPath;
226 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000227 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600228
229 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
230
B. J. Wyman681b2a32021-04-20 22:31:22 +0000231 if (nullptr != preslineptr)
232 {
233 presline = *preslineptr;
234 }
235
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000236 auto invMatch =
237 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
238 return psu->getInventoryPath() == invpath;
239 });
240 if (invMatch != psus.end())
241 {
242 // This power supply has the same inventory path as the one with
243 // information just added to D-Bus.
244 // Changes to GPIO line name unlikely, so skip checking.
245 // Changes to the I2C bus and address unlikely, as that would
246 // require corresponding device tree updates.
247 // Return out to avoid duplicate object creation.
248 return;
249 }
250
Faisal Awadab66ae502023-04-01 18:30:32 -0500251 buildDriverName(*i2cbus, *i2caddr);
B. J. Wyman681b2a32021-04-20 22:31:22 +0000252 log<level::DEBUG>(
Faisal Awadab66ae502023-04-01 18:30:32 -0500253 fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
254 *i2cbus, *i2caddr, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000255 .c_str());
George Liu9464c422023-02-27 14:30:27 +0800256 auto psu = std::make_unique<PowerSupply>(
Faisal Awadab66ae502023-04-01 18:30:32 -0500257 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
George Liu9464c422023-02-27 14:30:27 +0800258 std::bind(
259 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
260 this));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600261 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000262
263 // Subscribe to power supply presence changes
264 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
265 bus,
266 sdbusplus::bus::match::rules::propertiesChanged(invpath,
267 INVENTORY_IFACE),
268 [this](auto& msg) { this->presenceChanged(msg); });
269 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600270 }
271
272 if (psus.empty())
273 {
274 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
275 }
276}
277
Adriana Kobylake1074d82021-03-16 20:46:44 +0000278void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
279{
280 try
281 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000282 auto propIt = properties.find("SupportedType");
283 if (propIt == properties.end())
284 {
285 return;
286 }
287 const std::string* type = std::get_if<std::string>(&(propIt->second));
288 if ((type == nullptr) || (*type != "PowerSupply"))
289 {
290 return;
291 }
292
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000293 propIt = properties.find("SupportedModel");
294 if (propIt == properties.end())
295 {
296 return;
297 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000298 const std::string* model = std::get_if<std::string>(&(propIt->second));
299 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000300 {
301 return;
302 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000303
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000304 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000305 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000306 if (propIt != properties.end())
307 {
308 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
309 if (count != nullptr)
310 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000311 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000312 }
313 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000314 propIt = properties.find("InputVoltage");
315 if (propIt != properties.end())
316 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000317 const std::vector<uint64_t>* voltage =
318 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000319 if (voltage != nullptr)
320 {
321 sys.inputVoltage = *voltage;
322 }
323 }
324
Adriana Kobylak886574c2021-11-01 18:22:28 +0000325 // The PowerConfigFullLoad is an optional property, default it to false
326 // since that's the default value of the power-config-full-load GPIO.
327 sys.powerConfigFullLoad = false;
328 propIt = properties.find("PowerConfigFullLoad");
329 if (propIt != properties.end())
330 {
331 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
332 if (fullLoad != nullptr)
333 {
334 sys.powerConfigFullLoad = *fullLoad;
335 }
336 }
337
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000338 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000339 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500340 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000341 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000342}
343
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600344void PSUManager::getSystemProperties()
345{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600346
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600347 try
348 {
349 util::DbusSubtree subtree =
350 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000351 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600352 {
353 throw std::runtime_error("Supported Configuration Not Found");
354 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600355
Adriana Kobylake1074d82021-03-16 20:46:44 +0000356 for (const auto& [objPath, services] : subtree)
357 {
358 std::string service = services.begin()->first;
359 if (objPath.empty() || service.empty())
360 {
361 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600362 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000363 auto properties = util::getAllProperties(
364 bus, objPath, supportedConfIntf, service);
365 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600366 }
367 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500368 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600369 {
370 // Interface or property not found. Let the Interfaces Added callback
371 // process the information once the interfaces are added to D-Bus.
372 }
373}
374
Patrick Williams7354ce62022-07-22 19:26:56 -0500375void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600376{
377 try
378 {
379 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000380 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600381 interfaces;
382 msg.read(objPath, interfaces);
383
384 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600385 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600386 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600387 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000388 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600389 }
390
Brandon Wyman510acaa2020-11-05 18:32:04 -0600391 itIntf = interfaces.find(IBMCFFPSInterface);
392 if (itIntf != interfaces.cend())
393 {
394 log<level::INFO>(
395 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
396 .c_str());
397 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000398 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600399 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000400
401 // Call to validate the psu configuration if the power is on and both
402 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
403 // processed
404 if (powerOn && !psus.empty() && !supportedConfigs.empty())
405 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000406 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000407 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600408 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500409 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600410 {
411 // Ignore, the property may be of a different type than expected.
412 }
413}
414
Patrick Williams7354ce62022-07-22 19:26:56 -0500415void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500416{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500417 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500418 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500419 msg.read(msgSensor, msgData);
420
Jim Wrightaca86d02022-06-10 12:01:39 -0500421 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500422 auto valPropMap = msgData.find("state");
423 if (valPropMap != msgData.end())
424 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500425 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500426 if (state)
427 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500428 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500429 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500430 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000431 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500432 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000433 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000434 setPowerConfigGPIO();
Matt Spinlera068f422023-03-10 13:06:49 -0600435 setInputVoltageRating();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500436 }
437 else
438 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500439 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500440 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500441 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000442 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500443 }
444 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500445
446 // Check if it was the pgood property that changed.
447 valPropMap = msgData.find("pgood");
448 if (valPropMap != msgData.end())
449 {
450 int pgood = std::get<int>(valPropMap->second);
451 if (!pgood)
452 {
453 // Chassis power good has turned off
454 if (powerOn)
455 {
456 // pgood is off but state is on, in power fault window
457 powerFaultOccurring = true;
458 }
459 }
460 }
461 log<level::INFO>(
462 fmt::format(
463 "powerStateChanged: power on: {}, power fault occurring: {}",
464 powerOn, powerFaultOccurring)
465 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500466}
467
Patrick Williams7354ce62022-07-22 19:26:56 -0500468void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000469{
470 std::string msgSensor;
471 std::map<std::string, std::variant<uint32_t, bool>> msgData;
472 msg.read(msgSensor, msgData);
473
474 // Check if it was the Present property that changed.
475 auto valPropMap = msgData.find(PRESENT_PROP);
476 if (valPropMap != msgData.end())
477 {
478 if (std::get<bool>(valPropMap->second))
479 {
480 // A PSU became present, force the PSU validation to run.
481 runValidateConfig = true;
482 validationTimer->restartOnce(validationTimeout);
483 }
484 }
485}
486
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000487void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
488{
489 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000490 constexpr auto method = "setPowerSupplyError";
491
492 try
493 {
494 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600495 auto methodMsg = bus.new_method_call(
496 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000497 methodMsg.append(psuErrorString);
498 auto callReply = bus.call(methodMsg);
499 }
500 catch (const std::exception& e)
501 {
502 log<level::INFO>(
503 fmt::format("Failed calling setPowerSupplyError due to error {}",
504 e.what())
505 .c_str());
506 }
507}
508
Brandon Wyman8b662882021-10-08 17:31:51 +0000509void PSUManager::createError(const std::string& faultName,
510 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500511{
512 using namespace sdbusplus::xyz::openbmc_project;
513 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
514 constexpr auto loggingCreateInterface =
515 "xyz.openbmc_project.Logging.Create";
516
517 try
518 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000519 additionalData["_PID"] = std::to_string(getpid());
520
Brandon Wymanb76ab242020-09-16 18:06:06 -0500521 auto service =
522 util::getService(loggingObjectPath, loggingCreateInterface, bus);
523
524 if (service.empty())
525 {
526 log<level::ERR>("Unable to get logging manager service");
527 return;
528 }
529
530 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
531 loggingCreateInterface, "Create");
532
533 auto level = Logging::server::Entry::Level::Error;
534 method.append(faultName, level, additionalData);
535
536 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000537 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500538 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500539 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500540 {
541 log<level::ERR>(
542 fmt::format(
543 "Failed creating event log for fault {} due to error {}",
544 faultName, e.what())
545 .c_str());
546 }
547}
548
Brandon Wyman18a24d92022-04-19 22:48:34 +0000549void PSUManager::syncHistory()
550{
551 log<level::INFO>("Synchronize INPUT_HISTORY");
552
553 if (!syncHistoryGPIO)
554 {
555 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
556 }
557 if (syncHistoryGPIO)
558 {
559 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
560 syncHistoryGPIO->toggleLowHigh(delay);
561 for (auto& psu : psus)
562 {
563 psu->clearSyncHistoryRequired();
564 }
565 }
566
567 log<level::INFO>("Synchronize INPUT_HISTORY completed");
568}
569
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500570void PSUManager::analyze()
571{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000572 auto syncHistoryRequired =
573 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
574 return psu->isSyncHistoryRequired();
575 });
576 if (syncHistoryRequired)
577 {
578 syncHistory();
579 }
580
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500581 for (auto& psu : psus)
582 {
583 psu->analyze();
584 }
585
Jim Wright7f9288c2022-12-08 11:57:04 -0600586 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000587
Jim Wrightcefe85f2022-11-18 09:57:47 -0600588 // Only perform individual PSU analysis if power is on and a brownout has
589 // not already been logged
590 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500591 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600592 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500593 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600594 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000595
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600596 if (!psu->isFaultLogged() && !psu->isPresent())
597 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000598 std::map<std::string, std::string> requiredPSUsData;
599 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500600 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000601 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000602 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000603 // Create error for power supply missing.
604 additionalData["CALLOUT_INVENTORY_PATH"] =
605 psu->getInventoryPath();
606 additionalData["CALLOUT_PRIORITY"] = "H";
607 createError(
608 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
609 additionalData);
610 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600611 psu->setFaultLogged();
612 }
613 else if (!psu->isFaultLogged() && psu->isFaulted())
614 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000615 // Add STATUS_WORD and STATUS_MFR last response, in padded
616 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600617 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000618 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600619 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000620 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600621 // If there are faults being reported, they possibly could be
622 // related to a bug in the firmware version running on the power
623 // supply. Capture that data into the error as well.
624 additionalData["FW_VERSION"] = psu->getFWVersion();
625
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000626 if (psu->hasCommFault())
627 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000628 additionalData["STATUS_CML"] =
629 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000630 /* Attempts to communicate with the power supply have
631 * reached there limit. Create an error. */
632 additionalData["CALLOUT_DEVICE_PATH"] =
633 psu->getDevicePath();
634
635 createError(
636 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
637 additionalData);
638
639 psu->setFaultLogged();
640 }
641 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600642 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000643 // Include STATUS_INPUT for input faults.
644 additionalData["STATUS_INPUT"] =
645 fmt::format("{:#02x}", psu->getStatusInput());
646
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600647 /* The power supply location might be needed if the input
648 * fault is due to a problem with the power supply itself.
649 * Include the inventory path with a call out priority of
650 * low.
651 */
652 additionalData["CALLOUT_INVENTORY_PATH"] =
653 psu->getInventoryPath();
654 additionalData["CALLOUT_PRIORITY"] = "L";
655 createError("xyz.openbmc_project.Power.PowerSupply.Error."
656 "InputFault",
657 additionalData);
658 psu->setFaultLogged();
659 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000660 else if (psu->hasPSKillFault())
661 {
662 createError(
663 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
664 additionalData);
665 psu->setFaultLogged();
666 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000667 else if (psu->hasVoutOVFault())
668 {
669 // Include STATUS_VOUT for Vout faults.
670 additionalData["STATUS_VOUT"] =
671 fmt::format("{:#02x}", psu->getStatusVout());
672
673 additionalData["CALLOUT_INVENTORY_PATH"] =
674 psu->getInventoryPath();
675
676 createError(
677 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
678 additionalData);
679
680 psu->setFaultLogged();
681 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000682 else if (psu->hasIoutOCFault())
683 {
684 // Include STATUS_IOUT for Iout faults.
685 additionalData["STATUS_IOUT"] =
686 fmt::format("{:#02x}", psu->getStatusIout());
687
688 createError(
689 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
690 additionalData);
691
692 psu->setFaultLogged();
693 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000694 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
695 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000696 {
697 // Include STATUS_VOUT for Vout faults.
698 additionalData["STATUS_VOUT"] =
699 fmt::format("{:#02x}", psu->getStatusVout());
700
701 additionalData["CALLOUT_INVENTORY_PATH"] =
702 psu->getInventoryPath();
703
704 createError(
705 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
706 additionalData);
707
708 psu->setFaultLogged();
709 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000710 // A fan fault should have priority over a temperature fault,
711 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500712 // Only process if not in power fault window.
713 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000714 {
715 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
716 additionalData["STATUS_TEMPERATURE"] =
717 fmt::format("{:#02x}", psu->getStatusTemperature());
718 additionalData["STATUS_FANS_1_2"] =
719 fmt::format("{:#02x}", psu->getStatusFans12());
720
721 additionalData["CALLOUT_INVENTORY_PATH"] =
722 psu->getInventoryPath();
723
724 createError(
725 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
726 additionalData);
727
728 psu->setFaultLogged();
729 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000730 else if (psu->hasTempFault())
731 {
732 // Include STATUS_TEMPERATURE for temperature faults.
733 additionalData["STATUS_TEMPERATURE"] =
734 fmt::format("{:#02x}", psu->getStatusTemperature());
735
736 additionalData["CALLOUT_INVENTORY_PATH"] =
737 psu->getInventoryPath();
738
739 createError(
740 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
741 additionalData);
742
743 psu->setFaultLogged();
744 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600745 else if (psu->hasMFRFault())
746 {
747 /* This can represent a variety of faults that result in
748 * calling out the power supply for replacement: Output
749 * OverCurrent, Output Under Voltage, and potentially other
750 * faults.
751 *
752 * Also plan on putting specific fault in AdditionalData,
753 * along with register names and register values
754 * (STATUS_WORD, STATUS_MFR, etc.).*/
755
756 additionalData["CALLOUT_INVENTORY_PATH"] =
757 psu->getInventoryPath();
758
759 createError(
760 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500761 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500762
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600763 psu->setFaultLogged();
764 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500765 // Only process if not in power fault window.
766 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000767 {
768 /* POWER_GOOD# is not low, or OFF is on */
769 additionalData["CALLOUT_INVENTORY_PATH"] =
770 psu->getInventoryPath();
771
772 createError(
773 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
774 additionalData);
775
776 psu->setFaultLogged();
777 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500778 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500779 }
780 }
781}
782
Jim Wright7f9288c2022-12-08 11:57:04 -0600783void PSUManager::analyzeBrownout()
784{
785 // Count number of power supplies failing
786 size_t presentCount = 0;
787 size_t notPresentCount = 0;
788 size_t acFailedCount = 0;
789 size_t pgoodFailedCount = 0;
790 for (const auto& psu : psus)
791 {
792 if (psu->isPresent())
793 {
794 ++presentCount;
795 if (psu->hasACFault())
796 {
797 ++acFailedCount;
798 }
799 else if (psu->hasPgoodFault())
800 {
801 ++pgoodFailedCount;
802 }
803 }
804 else
805 {
806 ++notPresentCount;
807 }
808 }
809
810 // Only issue brownout failure if chassis pgood has failed, it has not
811 // already been logged, at least one PSU has seen an AC fail, and all
812 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
813 // at least one PSU is present.
814 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
815 (presentCount == (acFailedCount + pgoodFailedCount)))
816 {
817 // Indicate that the system is in a brownout condition by creating an
818 // error log and setting the PowerSystemInputs status property to Fault.
819 powerSystemInputs.status(
820 sdbusplus::xyz::openbmc_project::State::Decorator::server::
821 PowerSystemInputs::Status::Fault);
822
823 std::map<std::string, std::string> additionalData;
824 additionalData.emplace("NOT_PRESENT_COUNT",
825 std::to_string(notPresentCount));
826 additionalData.emplace("VIN_FAULT_COUNT",
827 std::to_string(acFailedCount));
828 additionalData.emplace("PGOOD_FAULT_COUNT",
829 std::to_string(pgoodFailedCount));
830 log<level::INFO>(
831 fmt::format(
832 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
833 notPresentCount, acFailedCount, pgoodFailedCount)
834 .c_str());
835
836 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
837 additionalData);
838 brownoutLogged = true;
839 }
840 else
841 {
842 // If a brownout was previously logged but at least one PSU is not
843 // currently in AC fault, determine if the brownout condition can be
844 // cleared
845 if (brownoutLogged && (acFailedCount < presentCount))
846 {
847 // Chassis only recognizes the PowerSystemInputs change when it is
848 // off
849 try
850 {
851 using PowerState = sdbusplus::xyz::openbmc_project::State::
852 server::Chassis::PowerState;
853 PowerState currentPowerState;
854 util::getProperty<PowerState>(
855 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
856 "/xyz/openbmc_project/state/chassis0",
857 "xyz.openbmc_project.State.Chassis", bus,
858 currentPowerState);
859
860 if (currentPowerState == PowerState::Off)
861 {
862 // Indicate that the system is no longer in a brownout
863 // condition by setting the PowerSystemInputs status
864 // property to Good.
865 log<level::INFO>(
866 fmt::format(
867 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
868 notPresentCount, acFailedCount, pgoodFailedCount)
869 .c_str());
870 powerSystemInputs.status(
871 sdbusplus::xyz::openbmc_project::State::Decorator::
872 server::PowerSystemInputs::Status::Good);
873 brownoutLogged = false;
874 }
875 }
876 catch (const std::exception& e)
877 {
878 log<level::ERR>(
879 fmt::format("Error trying to clear brownout, error: {}",
880 e.what())
881 .c_str());
882 }
883 }
884 }
885}
886
Brandon Wyman64e97752022-06-03 23:50:13 +0000887void PSUManager::updateMissingPSUs()
888{
889 if (supportedConfigs.empty() || psus.empty())
890 {
891 return;
892 }
893
894 // Power supplies default to missing. If the power supply is present,
895 // the PowerSupply object will update the inventory Present property to
896 // true. If we have less than the required number of power supplies, and
897 // this power supply is missing, update the inventory Present property
898 // to false to indicate required power supply is missing. Avoid
899 // indicating power supply missing if not required.
900
901 auto presentCount =
902 std::count_if(psus.begin(), psus.end(),
903 [](const auto& psu) { return psu->isPresent(); });
904
905 for (const auto& config : supportedConfigs)
906 {
907 for (const auto& psu : psus)
908 {
909 auto psuModel = psu->getModelName();
910 auto psuShortName = psu->getShortName();
911 auto psuInventoryPath = psu->getInventoryPath();
912 auto relativeInvPath =
913 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
914 auto psuPresent = psu->isPresent();
915 auto presProperty = false;
916 auto propReadFail = false;
917
918 try
919 {
920 presProperty = getPresence(bus, psuInventoryPath);
921 propReadFail = false;
922 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500923 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000924 {
925 propReadFail = true;
926 // Relying on property change or interface added to retry.
927 // Log an informational trace to the journal.
928 log<level::INFO>(
929 fmt::format("D-Bus property {} access failure exception",
930 psuInventoryPath)
931 .c_str());
932 }
933
934 if (psuModel.empty())
935 {
936 if (!propReadFail && (presProperty != psuPresent))
937 {
938 // We already have this property, and it is not false
939 // set Present to false
940 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
941 }
942 continue;
943 }
944
945 if (config.first != psuModel)
946 {
947 continue;
948 }
949
950 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
951 {
952 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
953 }
954 }
955 }
956}
957
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000958void PSUManager::validateConfig()
959{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000960 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000961 {
962 return;
963 }
964
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000965 for (const auto& psu : psus)
966 {
967 if ((psu->hasInputFault() || psu->hasVINUVFault()))
968 {
969 // Do not try to validate if input voltage fault present.
970 validationTimer->restartOnce(validationTimeout);
971 return;
972 }
973 }
974
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000975 std::map<std::string, std::string> additionalData;
976 auto supported = hasRequiredPSUs(additionalData);
977 if (supported)
978 {
979 runValidateConfig = false;
980 return;
981 }
982
983 // Validation failed, create an error log.
984 // Return without setting the runValidateConfig flag to false because
985 // it may be that an additional supported configuration interface is
986 // added and we need to validate it to see if it matches this system.
987 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
988 additionalData);
989}
990
991bool PSUManager::hasRequiredPSUs(
992 std::map<std::string, std::string>& additionalData)
993{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000994 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000995 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000996 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000997 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000998 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000999
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001000 auto presentCount =
1001 std::count_if(psus.begin(), psus.end(),
1002 [](const auto& psu) { return psu->isPresent(); });
1003
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001004 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001005 // power supply model configuration. Since all configurations need to be
1006 // checked, the additional data would contain only the information of the
1007 // last configuration that did not match.
1008 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001009 for (const auto& config : supportedConfigs)
1010 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001011 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001012 {
1013 continue;
1014 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001015
Jim Wright941b60d2022-10-19 16:22:17 -05001016 // Number of power supplies present should equal or exceed the expected
1017 // count
1018 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001019 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001020 tmpAdditionalData.clear();
1021 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001022 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001023 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001024 continue;
1025 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001026
1027 bool voltageValidated = true;
1028 for (const auto& psu : psus)
1029 {
1030 if (!psu->isPresent())
1031 {
1032 // Only present PSUs report a valid input voltage
1033 continue;
1034 }
1035
1036 double actualInputVoltage;
1037 int inputVoltage;
1038 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1039
1040 if (std::find(config.second.inputVoltage.begin(),
1041 config.second.inputVoltage.end(),
1042 inputVoltage) == config.second.inputVoltage.end())
1043 {
1044 tmpAdditionalData.clear();
1045 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1046 std::to_string(actualInputVoltage);
1047 for (const auto& voltage : config.second.inputVoltage)
1048 {
1049 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1050 std::to_string(voltage) + " ";
1051 }
1052 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1053 psu->getInventoryPath();
1054
1055 voltageValidated = false;
1056 break;
1057 }
1058 }
1059 if (!voltageValidated)
1060 {
1061 continue;
1062 }
1063
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001064 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001065 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001066
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001067 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001068 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001069}
1070
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001071unsigned int PSUManager::getRequiredPSUCount()
1072{
1073 unsigned int requiredCount{0};
1074
1075 // Verify we have the supported configuration and PSU information
1076 if (!supportedConfigs.empty() && !psus.empty())
1077 {
1078 // Find PSU models. They should all be the same.
1079 std::set<std::string> models{};
1080 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1081 if (!psu->getModelName().empty())
1082 {
1083 models.insert(psu->getModelName());
1084 }
1085 });
1086
1087 // If exactly one model was found, find corresponding configuration
1088 if (models.size() == 1)
1089 {
1090 const std::string& model = *(models.begin());
1091 auto it = supportedConfigs.find(model);
1092 if (it != supportedConfigs.end())
1093 {
1094 requiredCount = it->second.powerSupplyCount;
1095 }
1096 }
1097 }
1098
1099 return requiredCount;
1100}
1101
1102bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1103{
1104 // Get required number of PSUs; if not found, we don't know if PSU required
1105 unsigned int requiredCount = getRequiredPSUCount();
1106 if (requiredCount == 0)
1107 {
1108 return false;
1109 }
1110
1111 // If total PSU count <= the required count, all PSUs are required
1112 if (psus.size() <= requiredCount)
1113 {
1114 return true;
1115 }
1116
1117 // We don't currently get information from EntityManager about which PSUs
1118 // are required, so we have to do some guesswork. First check if this PSU
1119 // is present. If so, assume it is required.
1120 if (psu.isPresent())
1121 {
1122 return true;
1123 }
1124
1125 // This PSU is not present. Count the number of other PSUs that are
1126 // present. If enough other PSUs are present, assume the specified PSU is
1127 // not required.
1128 unsigned int psuCount =
1129 std::count_if(psus.begin(), psus.end(),
1130 [](const auto& psu) { return psu->isPresent(); });
1131 if (psuCount >= requiredCount)
1132 {
1133 return false;
1134 }
1135
1136 // Check if this PSU was previously present. If so, assume it is required.
1137 // We know it was previously present if it has a non-empty model name.
1138 if (!psu.getModelName().empty())
1139 {
1140 return true;
1141 }
1142
1143 // This PSU was never present. Count the number of other PSUs that were
1144 // previously present. If including those PSUs is enough, assume the
1145 // specified PSU is not required.
1146 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1147 return (!psu->isPresent() && !psu->getModelName().empty());
1148 });
1149 if (psuCount >= requiredCount)
1150 {
1151 return false;
1152 }
1153
1154 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1155 // that were never present. PSU inventory paths typically end with the PSU
1156 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1157 std::vector<std::string> sortedPaths;
1158 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1159 if (!psu->isPresent() && psu->getModelName().empty())
1160 {
1161 sortedPaths.push_back(psu->getInventoryPath());
1162 }
1163 });
1164 std::sort(sortedPaths.begin(), sortedPaths.end());
1165
1166 // Check if specified PSU is close enough to start of list to be required
1167 for (const auto& path : sortedPaths)
1168 {
1169 if (path == psu.getInventoryPath())
1170 {
1171 return true;
1172 }
1173 if (++psuCount >= requiredCount)
1174 {
1175 break;
1176 }
1177 }
1178
1179 // PSU was not close to start of sorted list; assume not required
1180 return false;
1181}
1182
Adriana Kobylak523704d2021-09-21 15:55:41 +00001183bool PSUManager::validateModelName(
1184 std::string& model, std::map<std::string, std::string>& additionalData)
1185{
1186 // Check that all PSUs have the same model name. Initialize the model
1187 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001188 // against the rest of the PSUs and get its inventory path to use as callout
1189 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001190 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001191 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001192 for (const auto& psu : psus)
1193 {
1194 auto psuModel = psu->getModelName();
1195 if (psuModel.empty())
1196 {
1197 continue;
1198 }
1199 if (model.empty())
1200 {
1201 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001202 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001203 continue;
1204 }
1205 if (psuModel != model)
1206 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001207 if (supportedConfigs.find(model) != supportedConfigs.end())
1208 {
1209 // The base model is supported, callout the mismatched PSU. The
1210 // mismatched PSU may or may not be supported.
1211 additionalData["EXPECTED_MODEL"] = model;
1212 additionalData["ACTUAL_MODEL"] = psuModel;
1213 additionalData["CALLOUT_INVENTORY_PATH"] =
1214 psu->getInventoryPath();
1215 }
1216 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1217 {
1218 // The base model is not supported, but the mismatched PSU is,
1219 // callout the base PSU.
1220 additionalData["EXPECTED_MODEL"] = psuModel;
1221 additionalData["ACTUAL_MODEL"] = model;
1222 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1223 }
1224 else
1225 {
1226 // The base model and the mismatched PSU are not supported or
1227 // could not be found in the supported configuration, callout
1228 // the mismatched PSU.
1229 additionalData["EXPECTED_MODEL"] = model;
1230 additionalData["ACTUAL_MODEL"] = psuModel;
1231 additionalData["CALLOUT_INVENTORY_PATH"] =
1232 psu->getInventoryPath();
1233 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001234 model.clear();
1235 return false;
1236 }
1237 }
1238 return true;
1239}
1240
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001241void PSUManager::setPowerConfigGPIO()
1242{
1243 if (!powerConfigGPIO)
1244 {
1245 return;
1246 }
1247
1248 std::string model{};
1249 std::map<std::string, std::string> additionalData;
1250 if (!validateModelName(model, additionalData))
1251 {
1252 return;
1253 }
1254
1255 auto config = supportedConfigs.find(model);
1256 if (config != supportedConfigs.end())
1257 {
1258 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1259 // if the supported configuration indicates that this system model
1260 // expects the maximum number of power supplies (full load set to true).
1261 // Else, set it to high (1), this is the default.
1262 auto powerConfigValue =
1263 (config->second.powerConfigFullLoad == true ? 0 : 1);
1264 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1265 powerConfigGPIO->write(powerConfigValue, flags);
1266 }
1267}
1268
Faisal Awadab66ae502023-04-01 18:30:32 -05001269void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
1270{
1271 namespace fs = std::filesystem;
1272 std::stringstream ss;
1273 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
1274 std::string symLinkPath =
1275 deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
1276 try
1277 {
1278 fs::path linkStrPath = fs::read_symlink(symLinkPath);
1279 driverName = linkStrPath.filename();
1280 }
1281 catch (const std::exception& e)
1282 {
1283 log<level::ERR>(fmt::format("Failed to find device driver {}, error {}",
1284 symLinkPath, e.what())
1285 .c_str());
1286 }
1287}
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001288} // namespace phosphor::power::manager