blob: 5ad749cf0895afd0276708fd7fa5d66801c55491 [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;
Faisal Awadac6fa6662023-04-25 22:43:01 -0500980 double actualVoltage;
981 int inputVoltage;
982 int previousInputVoltage = 0;
983 bool voltageMismatch = false;
984
985 for (const auto& psu : psus)
986 {
987 if (!psu->isPresent())
988 {
989 // Only present PSUs report a valid input voltage
990 continue;
991 }
992 psu->getInputVoltage(actualVoltage, inputVoltage);
993 if (previousInputVoltage && inputVoltage &&
994 (previousInputVoltage != inputVoltage))
995 {
996 additionalData["EXPECTED_VOLTAGE"] =
997 std::to_string(previousInputVoltage);
998 additionalData["ACTUAL_VOLTAGE"] =
999 std::to_string(actualVoltage);
1000 voltageMismatch = true;
1001 }
1002 if (!previousInputVoltage && inputVoltage)
1003 {
1004 previousInputVoltage = inputVoltage;
1005 }
1006 }
1007 if (!voltageMismatch)
1008 {
1009 return;
1010 }
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001011 }
1012
1013 // Validation failed, create an error log.
1014 // Return without setting the runValidateConfig flag to false because
1015 // it may be that an additional supported configuration interface is
1016 // added and we need to validate it to see if it matches this system.
1017 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
1018 additionalData);
1019}
1020
1021bool PSUManager::hasRequiredPSUs(
1022 std::map<std::string, std::string>& additionalData)
1023{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001024 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001025 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001026 {
Adriana Kobylak523704d2021-09-21 15:55:41 +00001027 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001028 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001029
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001030 auto presentCount =
1031 std::count_if(psus.begin(), psus.end(),
1032 [](const auto& psu) { return psu->isPresent(); });
1033
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001034 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001035 // power supply model configuration. Since all configurations need to be
1036 // checked, the additional data would contain only the information of the
1037 // last configuration that did not match.
1038 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001039 for (const auto& config : supportedConfigs)
1040 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001041 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001042 {
1043 continue;
1044 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001045
Jim Wright941b60d2022-10-19 16:22:17 -05001046 // Number of power supplies present should equal or exceed the expected
1047 // count
1048 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001049 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001050 tmpAdditionalData.clear();
1051 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001052 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001053 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001054 continue;
1055 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001056
1057 bool voltageValidated = true;
1058 for (const auto& psu : psus)
1059 {
1060 if (!psu->isPresent())
1061 {
1062 // Only present PSUs report a valid input voltage
1063 continue;
1064 }
1065
1066 double actualInputVoltage;
1067 int inputVoltage;
1068 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1069
1070 if (std::find(config.second.inputVoltage.begin(),
1071 config.second.inputVoltage.end(),
1072 inputVoltage) == config.second.inputVoltage.end())
1073 {
1074 tmpAdditionalData.clear();
1075 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1076 std::to_string(actualInputVoltage);
1077 for (const auto& voltage : config.second.inputVoltage)
1078 {
1079 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1080 std::to_string(voltage) + " ";
1081 }
1082 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1083 psu->getInventoryPath();
1084
1085 voltageValidated = false;
1086 break;
1087 }
1088 }
1089 if (!voltageValidated)
1090 {
1091 continue;
1092 }
1093
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001094 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001095 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001096
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001097 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001098 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001099}
1100
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001101unsigned int PSUManager::getRequiredPSUCount()
1102{
1103 unsigned int requiredCount{0};
1104
1105 // Verify we have the supported configuration and PSU information
1106 if (!supportedConfigs.empty() && !psus.empty())
1107 {
1108 // Find PSU models. They should all be the same.
1109 std::set<std::string> models{};
1110 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1111 if (!psu->getModelName().empty())
1112 {
1113 models.insert(psu->getModelName());
1114 }
1115 });
1116
1117 // If exactly one model was found, find corresponding configuration
1118 if (models.size() == 1)
1119 {
1120 const std::string& model = *(models.begin());
1121 auto it = supportedConfigs.find(model);
1122 if (it != supportedConfigs.end())
1123 {
1124 requiredCount = it->second.powerSupplyCount;
1125 }
1126 }
1127 }
1128
1129 return requiredCount;
1130}
1131
1132bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1133{
1134 // Get required number of PSUs; if not found, we don't know if PSU required
1135 unsigned int requiredCount = getRequiredPSUCount();
1136 if (requiredCount == 0)
1137 {
1138 return false;
1139 }
1140
1141 // If total PSU count <= the required count, all PSUs are required
1142 if (psus.size() <= requiredCount)
1143 {
1144 return true;
1145 }
1146
1147 // We don't currently get information from EntityManager about which PSUs
1148 // are required, so we have to do some guesswork. First check if this PSU
1149 // is present. If so, assume it is required.
1150 if (psu.isPresent())
1151 {
1152 return true;
1153 }
1154
1155 // This PSU is not present. Count the number of other PSUs that are
1156 // present. If enough other PSUs are present, assume the specified PSU is
1157 // not required.
1158 unsigned int psuCount =
1159 std::count_if(psus.begin(), psus.end(),
1160 [](const auto& psu) { return psu->isPresent(); });
1161 if (psuCount >= requiredCount)
1162 {
1163 return false;
1164 }
1165
1166 // Check if this PSU was previously present. If so, assume it is required.
1167 // We know it was previously present if it has a non-empty model name.
1168 if (!psu.getModelName().empty())
1169 {
1170 return true;
1171 }
1172
1173 // This PSU was never present. Count the number of other PSUs that were
1174 // previously present. If including those PSUs is enough, assume the
1175 // specified PSU is not required.
1176 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1177 return (!psu->isPresent() && !psu->getModelName().empty());
1178 });
1179 if (psuCount >= requiredCount)
1180 {
1181 return false;
1182 }
1183
1184 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1185 // that were never present. PSU inventory paths typically end with the PSU
1186 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1187 std::vector<std::string> sortedPaths;
1188 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1189 if (!psu->isPresent() && psu->getModelName().empty())
1190 {
1191 sortedPaths.push_back(psu->getInventoryPath());
1192 }
1193 });
1194 std::sort(sortedPaths.begin(), sortedPaths.end());
1195
1196 // Check if specified PSU is close enough to start of list to be required
1197 for (const auto& path : sortedPaths)
1198 {
1199 if (path == psu.getInventoryPath())
1200 {
1201 return true;
1202 }
1203 if (++psuCount >= requiredCount)
1204 {
1205 break;
1206 }
1207 }
1208
1209 // PSU was not close to start of sorted list; assume not required
1210 return false;
1211}
1212
Adriana Kobylak523704d2021-09-21 15:55:41 +00001213bool PSUManager::validateModelName(
1214 std::string& model, std::map<std::string, std::string>& additionalData)
1215{
1216 // Check that all PSUs have the same model name. Initialize the model
1217 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001218 // against the rest of the PSUs and get its inventory path to use as callout
1219 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001220 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001221 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001222 for (const auto& psu : psus)
1223 {
1224 auto psuModel = psu->getModelName();
1225 if (psuModel.empty())
1226 {
1227 continue;
1228 }
1229 if (model.empty())
1230 {
1231 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001232 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001233 continue;
1234 }
1235 if (psuModel != model)
1236 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001237 if (supportedConfigs.find(model) != supportedConfigs.end())
1238 {
1239 // The base model is supported, callout the mismatched PSU. The
1240 // mismatched PSU may or may not be supported.
1241 additionalData["EXPECTED_MODEL"] = model;
1242 additionalData["ACTUAL_MODEL"] = psuModel;
1243 additionalData["CALLOUT_INVENTORY_PATH"] =
1244 psu->getInventoryPath();
1245 }
1246 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1247 {
1248 // The base model is not supported, but the mismatched PSU is,
1249 // callout the base PSU.
1250 additionalData["EXPECTED_MODEL"] = psuModel;
1251 additionalData["ACTUAL_MODEL"] = model;
1252 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1253 }
1254 else
1255 {
1256 // The base model and the mismatched PSU are not supported or
1257 // could not be found in the supported configuration, callout
1258 // the mismatched PSU.
1259 additionalData["EXPECTED_MODEL"] = model;
1260 additionalData["ACTUAL_MODEL"] = psuModel;
1261 additionalData["CALLOUT_INVENTORY_PATH"] =
1262 psu->getInventoryPath();
1263 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001264 model.clear();
1265 return false;
1266 }
1267 }
1268 return true;
1269}
1270
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001271void PSUManager::setPowerConfigGPIO()
1272{
1273 if (!powerConfigGPIO)
1274 {
1275 return;
1276 }
1277
1278 std::string model{};
1279 std::map<std::string, std::string> additionalData;
1280 if (!validateModelName(model, additionalData))
1281 {
1282 return;
1283 }
1284
1285 auto config = supportedConfigs.find(model);
1286 if (config != supportedConfigs.end())
1287 {
1288 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1289 // if the supported configuration indicates that this system model
1290 // expects the maximum number of power supplies (full load set to true).
1291 // Else, set it to high (1), this is the default.
1292 auto powerConfigValue =
1293 (config->second.powerConfigFullLoad == true ? 0 : 1);
1294 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1295 powerConfigGPIO->write(powerConfigValue, flags);
1296 }
1297}
1298
Faisal Awadab66ae502023-04-01 18:30:32 -05001299void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
1300{
1301 namespace fs = std::filesystem;
1302 std::stringstream ss;
1303 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
1304 std::string symLinkPath =
1305 deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
1306 try
1307 {
1308 fs::path linkStrPath = fs::read_symlink(symLinkPath);
1309 driverName = linkStrPath.filename();
1310 }
1311 catch (const std::exception& e)
1312 {
1313 log<level::ERR>(fmt::format("Failed to find device driver {}, error {}",
1314 symLinkPath, e.what())
1315 .c_str());
1316 }
1317}
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001318} // namespace phosphor::power::manager