blob: 1f7244049a4ff43f4fa6a6dfc8ebb8066f0e2131 [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 <sys/types.h>
8#include <unistd.h>
9
Jim Wright7f9288c2022-12-08 11:57:04 -060010#include <xyz/openbmc_project/State/Chassis/server.hpp>
11
Shawn McCarney9252b7e2022-06-10 12:47:38 -050012#include <algorithm>
Shawn McCarney768d2262024-07-09 15:02:59 -050013#include <format>
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 Wyman63ea78b2020-09-24 16:49:09 -050017namespace phosphor::power::manager
Brandon Wymana0f33ce2019-10-17 18:32:29 -050018{
Adriana Kobylakc9b05732022-03-19 15:15:10 +000019constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
20constexpr auto objectManagerObjPath =
21 "/xyz/openbmc_project/power/power_supplies";
22constexpr auto powerSystemsInputsObjPath =
23 "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
Brandon Wymana0f33ce2019-10-17 18:32:29 -050024
Brandon Wyman510acaa2020-11-05 18:32:04 -060025constexpr auto IBMCFFPSInterface =
26 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
27constexpr auto i2cBusProp = "I2CBus";
28constexpr auto i2cAddressProp = "I2CAddress";
29constexpr auto psuNameProp = "Name";
B. J. Wyman681b2a32021-04-20 22:31:22 +000030constexpr auto presLineName = "NamedPresenceGpio";
Brandon Wyman510acaa2020-11-05 18:32:04 -060031
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060032constexpr auto supportedConfIntf =
33 "xyz.openbmc_project.Configuration.SupportedConfiguration";
Adriana Kobylak9bab9e12021-02-24 15:32:03 -060034
Faisal Awadab66ae502023-04-01 18:30:32 -050035const auto deviceDirPath = "/sys/bus/i2c/devices/";
36const auto driverDirName = "/driver";
37
Brandon Wymanc9e840e2022-05-10 20:48:41 +000038constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
Brandon Wyman18a24d92022-04-19 22:48:34 +000039
Patrick Williams7354ce62022-07-22 19:26:56 -050040PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000041 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
Brandon Wymanc3324422022-03-24 20:30:57 +000042 objectManager(bus, objectManagerObjPath),
Matt Spinlera068f422023-03-10 13:06:49 -060043 sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060044{
Brandon Wyman510acaa2020-11-05 18:32:04 -060045 // Subscribe to InterfacesAdded before doing a property read, otherwise
46 // the interface could be created after the read attempt but before the
47 // match is created.
48 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
49 bus,
50 sdbusplus::bus::match::rules::interfacesAdded() +
51 sdbusplus::bus::match::rules::sender(
52 "xyz.openbmc_project.EntityManager"),
53 std::bind(&PSUManager::entityManagerIfaceAdded, this,
54 std::placeholders::_1));
55 getPSUConfiguration();
56 getSystemProperties();
57
Adriana Kobylakc9b05732022-03-19 15:15:10 +000058 // Request the bus name before the analyze() function, which is the one that
59 // determines the brownout condition and sets the status d-bus property.
60 bus.request_name(managerBusName);
61
Brandon Wyman510acaa2020-11-05 18:32:04 -060062 using namespace sdeventplus;
63 auto interval = std::chrono::milliseconds(1000);
64 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
65 e, std::bind(&PSUManager::analyze, this), interval);
66
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000067 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
68 e, std::bind(&PSUManager::validateConfig, this));
69
Adriana Kobylakc0a07582021-10-13 15:52:25 +000070 try
71 {
72 powerConfigGPIO = createGPIO("power-config-full-load");
73 }
74 catch (const std::exception& e)
75 {
76 // Ignore error, GPIO may not be implemented in this system.
77 powerConfigGPIO = nullptr;
78 }
79
Brandon Wyman510acaa2020-11-05 18:32:04 -060080 // Subscribe to power state changes
81 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
82 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
83 bus,
84 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
85 POWER_IFACE),
86 [this](auto& msg) { this->powerStateChanged(msg); });
87
88 initialize();
89}
90
Jim Wrightaca86d02022-06-10 12:01:39 -050091void PSUManager::initialize()
92{
93 try
94 {
95 // pgood is the latest read of the chassis pgood
96 int pgood = 0;
97 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
98 powerService, bus, pgood);
99
100 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -0600101 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -0500102 POWER_IFACE, "getPowerState");
103 auto reply = bus.call(method);
104 int state = 0;
105 reply.read(state);
106
107 if (state)
108 {
109 // Monitor PSUs anytime state is on
110 powerOn = true;
111 // In the power fault window if pgood is off
112 powerFaultOccurring = !pgood;
113 validationTimer->restartOnce(validationTimeout);
114 }
115 else
116 {
117 // Power is off
118 powerOn = false;
119 powerFaultOccurring = false;
120 runValidateConfig = true;
121 }
122 }
123 catch (const std::exception& e)
124 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000125 lg2::info(
126 "Failed to get power state, assuming it is off, error {ERROR}",
127 "ERROR", e);
Jim Wrightaca86d02022-06-10 12:01:39 -0500128 powerOn = false;
129 powerFaultOccurring = false;
130 runValidateConfig = true;
131 }
132
133 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
134 clearFaults();
135 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500136 setPowerConfigGPIO();
137
Anwaar Hadib64228d2025-05-30 23:55:26 +0000138 lg2::info(
139 "initialize: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
140 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
Jim Wrightaca86d02022-06-10 12:01:39 -0500141}
142
Brandon Wyman510acaa2020-11-05 18:32:04 -0600143void PSUManager::getPSUConfiguration()
144{
145 using namespace phosphor::power::util;
146 auto depth = 0;
147 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
148
149 psus.clear();
150
151 // I should get a map of objects back.
152 // Each object will have a path, a service, and an interface.
153 // The interface should match the one passed into this function.
154 for (const auto& [path, services] : objects)
155 {
156 auto service = services.begin()->first;
157
158 if (path.empty() || service.empty())
159 {
160 continue;
161 }
162
163 // For each object in the array of objects, I want to get properties
164 // from the service, path, and interface.
Patrick Williamsf5402192024-08-16 15:20:53 -0400165 auto properties =
166 getAllProperties(bus, path, IBMCFFPSInterface, service);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600167
168 getPSUProperties(properties);
169 }
170
171 if (psus.empty())
172 {
173 // Interface or properties not found. Let the Interfaces Added callback
174 // process the information once the interfaces are added to D-Bus.
Anwaar Hadib64228d2025-05-30 23:55:26 +0000175 lg2::info("No power supplies to monitor");
Brandon Wyman510acaa2020-11-05 18:32:04 -0600176 }
177}
178
179void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
180{
181 // From passed in properties, I want to get: I2CBus, I2CAddress,
182 // and Name. Create a power supply object, using Name to build the inventory
183 // path.
184 const auto basePSUInvPath =
185 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
186 uint64_t* i2cbus = nullptr;
187 uint64_t* i2caddr = nullptr;
188 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000189 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600190
191 for (const auto& property : properties)
192 {
193 try
194 {
195 if (property.first == i2cBusProp)
196 {
197 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
198 }
199 else if (property.first == i2cAddressProp)
200 {
201 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
202 }
203 else if (property.first == psuNameProp)
204 {
205 psuname = std::get_if<std::string>(&properties[psuNameProp]);
206 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000207 else if (property.first == presLineName)
208 {
209 preslineptr =
210 std::get_if<std::string>(&properties[presLineName]);
211 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600212 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500213 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000214 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600215 }
216
217 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
218 {
219 std::string invpath = basePSUInvPath;
220 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000221 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600222
Anwaar Hadib64228d2025-05-30 23:55:26 +0000223 lg2::debug("Inventory Path: {INVPATH}", "INVPATH", invpath);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600224
B. J. Wyman681b2a32021-04-20 22:31:22 +0000225 if (nullptr != preslineptr)
226 {
227 presline = *preslineptr;
228 }
229
Patrick Williamsf5402192024-08-16 15:20:53 -0400230 auto invMatch =
231 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
232 return psu->getInventoryPath() == invpath;
233 });
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000234 if (invMatch != psus.end())
235 {
236 // This power supply has the same inventory path as the one with
237 // information just added to D-Bus.
238 // Changes to GPIO line name unlikely, so skip checking.
239 // Changes to the I2C bus and address unlikely, as that would
240 // require corresponding device tree updates.
241 // Return out to avoid duplicate object creation.
242 return;
243 }
244
Faisal Awadab66ae502023-04-01 18:30:32 -0500245 buildDriverName(*i2cbus, *i2caddr);
Anwaar Hadib64228d2025-05-30 23:55:26 +0000246 lg2::debug(
247 "make PowerSupply bus: {I2CBUS} addr: {I2CADDR} presline: {PRESLINE}",
248 "I2CBUS", *i2cbus, "I2CADDR", *i2caddr, "PRESLINE", presline);
George Liu9464c422023-02-27 14:30:27 +0800249 auto psu = std::make_unique<PowerSupply>(
Faisal Awadab66ae502023-04-01 18:30:32 -0500250 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
George Liu9464c422023-02-27 14:30:27 +0800251 std::bind(
252 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
253 this));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600254 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000255
256 // Subscribe to power supply presence changes
257 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
258 bus,
259 sdbusplus::bus::match::rules::propertiesChanged(invpath,
260 INVENTORY_IFACE),
261 [this](auto& msg) { this->presenceChanged(msg); });
262 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600263 }
264
265 if (psus.empty())
266 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000267 lg2::info("No power supplies to monitor");
Brandon Wyman510acaa2020-11-05 18:32:04 -0600268 }
Faisal Awadab7131a12023-10-26 19:38:45 -0500269 else
270 {
271 populateDriverName();
272 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600273}
274
Adriana Kobylake1074d82021-03-16 20:46:44 +0000275void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
276{
277 try
278 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000279 auto propIt = properties.find("SupportedType");
280 if (propIt == properties.end())
281 {
282 return;
283 }
284 const std::string* type = std::get_if<std::string>(&(propIt->second));
285 if ((type == nullptr) || (*type != "PowerSupply"))
286 {
287 return;
288 }
289
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000290 propIt = properties.find("SupportedModel");
291 if (propIt == properties.end())
292 {
293 return;
294 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000295 const std::string* model = std::get_if<std::string>(&(propIt->second));
296 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000297 {
298 return;
299 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000300
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000301 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000302 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000303 if (propIt != properties.end())
304 {
305 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
306 if (count != nullptr)
307 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000308 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000309 }
310 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000311 propIt = properties.find("InputVoltage");
312 if (propIt != properties.end())
313 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000314 const std::vector<uint64_t>* voltage =
315 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000316 if (voltage != nullptr)
317 {
318 sys.inputVoltage = *voltage;
319 }
320 }
321
Adriana Kobylak886574c2021-11-01 18:22:28 +0000322 // The PowerConfigFullLoad is an optional property, default it to false
323 // since that's the default value of the power-config-full-load GPIO.
324 sys.powerConfigFullLoad = false;
325 propIt = properties.find("PowerConfigFullLoad");
326 if (propIt != properties.end())
327 {
328 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
329 if (fullLoad != nullptr)
330 {
331 sys.powerConfigFullLoad = *fullLoad;
332 }
333 }
334
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000335 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000336 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500337 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000338 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000339}
340
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600341void PSUManager::getSystemProperties()
342{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600343 try
344 {
Patrick Williamsf5402192024-08-16 15:20:53 -0400345 util::DbusSubtree subtree =
346 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000347 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600348 {
349 throw std::runtime_error("Supported Configuration Not Found");
350 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600351
Adriana Kobylake1074d82021-03-16 20:46:44 +0000352 for (const auto& [objPath, services] : subtree)
353 {
354 std::string service = services.begin()->first;
355 if (objPath.empty() || service.empty())
356 {
357 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600358 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000359 auto properties = util::getAllProperties(
360 bus, objPath, supportedConfIntf, service);
361 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600362 }
363 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500364 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600365 {
366 // Interface or property not found. Let the Interfaces Added callback
367 // process the information once the interfaces are added to D-Bus.
368 }
369}
370
Patrick Williams7354ce62022-07-22 19:26:56 -0500371void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600372{
373 try
374 {
375 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000376 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600377 interfaces;
378 msg.read(objPath, interfaces);
379
380 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600381 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600382 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600383 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000384 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600385 }
386
Brandon Wyman510acaa2020-11-05 18:32:04 -0600387 itIntf = interfaces.find(IBMCFFPSInterface);
388 if (itIntf != interfaces.cend())
389 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000390 lg2::info("InterfacesAdded for: {IBMCFFPSINTERFACE}",
391 "IBMCFFPSINTERFACE", IBMCFFPSInterface);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600392 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000393 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600394 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000395
396 // Call to validate the psu configuration if the power is on and both
397 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
398 // processed
399 if (powerOn && !psus.empty() && !supportedConfigs.empty())
400 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000401 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000402 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600403 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500404 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600405 {
406 // Ignore, the property may be of a different type than expected.
407 }
408}
409
Patrick Williams7354ce62022-07-22 19:26:56 -0500410void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500411{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500412 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500413 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500414 msg.read(msgSensor, msgData);
415
Jim Wrightaca86d02022-06-10 12:01:39 -0500416 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500417 auto valPropMap = msgData.find("state");
418 if (valPropMap != msgData.end())
419 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500420 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500421 if (state)
422 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500423 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500424 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500425 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000426 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500427 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000428 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000429 setPowerConfigGPIO();
Matt Spinlera068f422023-03-10 13:06:49 -0600430 setInputVoltageRating();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500431 }
432 else
433 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500434 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500435 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500436 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000437 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500438 }
439 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500440
441 // Check if it was the pgood property that changed.
442 valPropMap = msgData.find("pgood");
443 if (valPropMap != msgData.end())
444 {
445 int pgood = std::get<int>(valPropMap->second);
446 if (!pgood)
447 {
448 // Chassis power good has turned off
449 if (powerOn)
450 {
451 // pgood is off but state is on, in power fault window
452 powerFaultOccurring = true;
453 }
454 }
455 }
Anwaar Hadib64228d2025-05-30 23:55:26 +0000456 lg2::info(
457 "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
458 "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500459}
460
Patrick Williams7354ce62022-07-22 19:26:56 -0500461void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000462{
463 std::string msgSensor;
464 std::map<std::string, std::variant<uint32_t, bool>> msgData;
465 msg.read(msgSensor, msgData);
466
467 // Check if it was the Present property that changed.
468 auto valPropMap = msgData.find(PRESENT_PROP);
469 if (valPropMap != msgData.end())
470 {
471 if (std::get<bool>(valPropMap->second))
472 {
473 // A PSU became present, force the PSU validation to run.
474 runValidateConfig = true;
475 validationTimer->restartOnce(validationTimeout);
476 }
477 }
478}
479
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000480void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
481{
482 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000483 constexpr auto method = "setPowerSupplyError";
484
485 try
486 {
487 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600488 auto methodMsg = bus.new_method_call(
489 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000490 methodMsg.append(psuErrorString);
491 auto callReply = bus.call(methodMsg);
492 }
493 catch (const std::exception& e)
494 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000495 lg2::info("Failed calling setPowerSupplyError due to error {ERROR}",
496 "ERROR", e);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000497 }
498}
499
Brandon Wyman8b662882021-10-08 17:31:51 +0000500void PSUManager::createError(const std::string& faultName,
501 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500502{
503 using namespace sdbusplus::xyz::openbmc_project;
504 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
505 constexpr auto loggingCreateInterface =
506 "xyz.openbmc_project.Logging.Create";
507
508 try
509 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000510 additionalData["_PID"] = std::to_string(getpid());
511
Patrick Williamsf5402192024-08-16 15:20:53 -0400512 auto service =
513 util::getService(loggingObjectPath, loggingCreateInterface, bus);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500514
515 if (service.empty())
516 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000517 lg2::error("Unable to get logging manager service");
Brandon Wymanb76ab242020-09-16 18:06:06 -0500518 return;
519 }
520
521 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
522 loggingCreateInterface, "Create");
523
524 auto level = Logging::server::Entry::Level::Error;
525 method.append(faultName, level, additionalData);
526
527 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000528 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500529 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500530 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500531 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000532 lg2::error(
533 "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
534 "FAULT_NAME", faultName, "ERROR", e);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500535 }
536}
537
Brandon Wyman18a24d92022-04-19 22:48:34 +0000538void PSUManager::syncHistory()
539{
Faisal Awadae9b37262023-07-30 21:02:16 -0500540 if (driverName != ACBEL_FSG032_DD_NAME)
Brandon Wyman18a24d92022-04-19 22:48:34 +0000541 {
Faisal Awadae9b37262023-07-30 21:02:16 -0500542 if (!syncHistoryGPIO)
Brandon Wyman18a24d92022-04-19 22:48:34 +0000543 {
Andrew Geissler3a492522023-11-30 13:30:51 -0600544 try
545 {
546 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
547 }
548 catch (const std::exception& e)
549 {
550 // Not an error, system just hasn't implemented the synch gpio
Anwaar Hadib64228d2025-05-30 23:55:26 +0000551 lg2::info("No synchronization GPIO found");
Andrew Geissler3a492522023-11-30 13:30:51 -0600552 syncHistoryGPIO = nullptr;
553 }
Faisal Awadae9b37262023-07-30 21:02:16 -0500554 }
555 if (syncHistoryGPIO)
556 {
557 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
Anwaar Hadib64228d2025-05-30 23:55:26 +0000558 lg2::info("Synchronize INPUT_HISTORY");
Faisal Awadae9b37262023-07-30 21:02:16 -0500559 syncHistoryGPIO->toggleLowHigh(delay);
Anwaar Hadib64228d2025-05-30 23:55:26 +0000560 lg2::info("Synchronize INPUT_HISTORY completed");
Brandon Wyman18a24d92022-04-19 22:48:34 +0000561 }
562 }
Andrew Geissler3a492522023-11-30 13:30:51 -0600563
564 // Always clear synch history required after calling this function
565 for (auto& psu : psus)
566 {
567 psu->clearSyncHistoryRequired();
568 }
Brandon Wyman18a24d92022-04-19 22:48:34 +0000569}
570
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500571void PSUManager::analyze()
572{
Patrick Williamsf5402192024-08-16 15:20:53 -0400573 auto syncHistoryRequired =
574 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
575 return psu->isSyncHistoryRequired();
576 });
Brandon Wyman18a24d92022-04-19 22:48:34 +0000577 if (syncHistoryRequired)
578 {
579 syncHistory();
580 }
581
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500582 for (auto& psu : psus)
583 {
584 psu->analyze();
585 }
586
Jim Wright7f9288c2022-12-08 11:57:04 -0600587 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000588
Jim Wrightcefe85f2022-11-18 09:57:47 -0600589 // Only perform individual PSU analysis if power is on and a brownout has
590 // not already been logged
591 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500592 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600593 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500594 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600595 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000596
Faisal Awada03e2a022023-11-15 20:31:13 +0000597 if (!psu->isFaultLogged() && !psu->isPresent() &&
598 !validationTimer->isEnabled())
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600599 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000600 std::map<std::string, std::string> requiredPSUsData;
601 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500602 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000603 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000604 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000605 // Create error for power supply missing.
606 additionalData["CALLOUT_INVENTORY_PATH"] =
607 psu->getInventoryPath();
608 additionalData["CALLOUT_PRIORITY"] = "H";
609 createError(
610 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
611 additionalData);
612 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600613 psu->setFaultLogged();
614 }
615 else if (!psu->isFaultLogged() && psu->isFaulted())
616 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000617 // Add STATUS_WORD and STATUS_MFR last response, in padded
618 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600619 additionalData["STATUS_WORD"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500620 std::format("{:#04x}", psu->getStatusWord());
Patrick Williamsf5402192024-08-16 15:20:53 -0400621 additionalData["STATUS_MFR"] =
622 std::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600623 // If there are faults being reported, they possibly could be
624 // related to a bug in the firmware version running on the power
625 // supply. Capture that data into the error as well.
626 additionalData["FW_VERSION"] = psu->getFWVersion();
627
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000628 if (psu->hasCommFault())
629 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000630 additionalData["STATUS_CML"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500631 std::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000632 /* Attempts to communicate with the power supply have
633 * reached there limit. Create an error. */
634 additionalData["CALLOUT_DEVICE_PATH"] =
635 psu->getDevicePath();
636
637 createError(
638 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
639 additionalData);
640
641 psu->setFaultLogged();
642 }
643 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600644 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000645 // Include STATUS_INPUT for input faults.
646 additionalData["STATUS_INPUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500647 std::format("{:#02x}", psu->getStatusInput());
Brandon Wymanf07bc792021-10-12 19:00:35 +0000648
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600649 /* The power supply location might be needed if the input
650 * fault is due to a problem with the power supply itself.
651 * Include the inventory path with a call out priority of
652 * low.
653 */
654 additionalData["CALLOUT_INVENTORY_PATH"] =
655 psu->getInventoryPath();
656 additionalData["CALLOUT_PRIORITY"] = "L";
657 createError("xyz.openbmc_project.Power.PowerSupply.Error."
658 "InputFault",
659 additionalData);
660 psu->setFaultLogged();
661 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000662 else if (psu->hasPSKillFault())
663 {
664 createError(
665 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
666 additionalData);
667 psu->setFaultLogged();
668 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000669 else if (psu->hasVoutOVFault())
670 {
671 // Include STATUS_VOUT for Vout faults.
672 additionalData["STATUS_VOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500673 std::format("{:#02x}", psu->getStatusVout());
Brandon Wyman6710ba22021-10-27 17:39:31 +0000674
675 additionalData["CALLOUT_INVENTORY_PATH"] =
676 psu->getInventoryPath();
677
678 createError(
679 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
680 additionalData);
681
682 psu->setFaultLogged();
683 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000684 else if (psu->hasIoutOCFault())
685 {
686 // Include STATUS_IOUT for Iout faults.
687 additionalData["STATUS_IOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500688 std::format("{:#02x}", psu->getStatusIout());
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000689
690 createError(
691 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
692 additionalData);
693
694 psu->setFaultLogged();
695 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000696 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
697 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000698 {
699 // Include STATUS_VOUT for Vout faults.
700 additionalData["STATUS_VOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500701 std::format("{:#02x}", psu->getStatusVout());
Brandon Wyman2cf46942021-10-28 19:09:16 +0000702
703 additionalData["CALLOUT_INVENTORY_PATH"] =
704 psu->getInventoryPath();
705
706 createError(
707 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
708 additionalData);
709
710 psu->setFaultLogged();
711 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000712 // A fan fault should have priority over a temperature fault,
713 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500714 // Only process if not in power fault window.
715 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000716 {
717 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
718 additionalData["STATUS_TEMPERATURE"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500719 std::format("{:#02x}", psu->getStatusTemperature());
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000720 additionalData["STATUS_FANS_1_2"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500721 std::format("{:#02x}", psu->getStatusFans12());
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000722
723 additionalData["CALLOUT_INVENTORY_PATH"] =
724 psu->getInventoryPath();
725
726 createError(
727 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
728 additionalData);
729
730 psu->setFaultLogged();
731 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000732 else if (psu->hasTempFault())
733 {
734 // Include STATUS_TEMPERATURE for temperature faults.
735 additionalData["STATUS_TEMPERATURE"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500736 std::format("{:#02x}", psu->getStatusTemperature());
Brandon Wyman96893a42021-11-05 19:56:57 +0000737
738 additionalData["CALLOUT_INVENTORY_PATH"] =
739 psu->getInventoryPath();
740
741 createError(
742 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
743 additionalData);
744
745 psu->setFaultLogged();
746 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600747 else if (psu->hasMFRFault())
748 {
749 /* This can represent a variety of faults that result in
750 * calling out the power supply for replacement: Output
751 * OverCurrent, Output Under Voltage, and potentially other
752 * faults.
753 *
754 * Also plan on putting specific fault in AdditionalData,
755 * along with register names and register values
756 * (STATUS_WORD, STATUS_MFR, etc.).*/
757
758 additionalData["CALLOUT_INVENTORY_PATH"] =
759 psu->getInventoryPath();
760
761 createError(
762 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500763 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500764
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600765 psu->setFaultLogged();
766 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500767 // Only process if not in power fault window.
768 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000769 {
770 /* POWER_GOOD# is not low, or OFF is on */
771 additionalData["CALLOUT_INVENTORY_PATH"] =
772 psu->getInventoryPath();
773
774 createError(
775 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
776 additionalData);
777
778 psu->setFaultLogged();
779 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500780 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500781 }
782 }
783}
784
Jim Wright7f9288c2022-12-08 11:57:04 -0600785void PSUManager::analyzeBrownout()
786{
787 // Count number of power supplies failing
788 size_t presentCount = 0;
789 size_t notPresentCount = 0;
790 size_t acFailedCount = 0;
791 size_t pgoodFailedCount = 0;
792 for (const auto& psu : psus)
793 {
794 if (psu->isPresent())
795 {
796 ++presentCount;
797 if (psu->hasACFault())
798 {
799 ++acFailedCount;
800 }
801 else if (psu->hasPgoodFault())
802 {
803 ++pgoodFailedCount;
804 }
805 }
806 else
807 {
808 ++notPresentCount;
809 }
810 }
811
812 // Only issue brownout failure if chassis pgood has failed, it has not
813 // already been logged, at least one PSU has seen an AC fail, and all
814 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
815 // at least one PSU is present.
816 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
817 (presentCount == (acFailedCount + pgoodFailedCount)))
818 {
819 // Indicate that the system is in a brownout condition by creating an
820 // error log and setting the PowerSystemInputs status property to Fault.
821 powerSystemInputs.status(
822 sdbusplus::xyz::openbmc_project::State::Decorator::server::
823 PowerSystemInputs::Status::Fault);
824
825 std::map<std::string, std::string> additionalData;
826 additionalData.emplace("NOT_PRESENT_COUNT",
827 std::to_string(notPresentCount));
828 additionalData.emplace("VIN_FAULT_COUNT",
829 std::to_string(acFailedCount));
830 additionalData.emplace("PGOOD_FAULT_COUNT",
831 std::to_string(pgoodFailedCount));
Anwaar Hadib64228d2025-05-30 23:55:26 +0000832 lg2::info(
833 "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
834 "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
835 acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
Jim Wright7f9288c2022-12-08 11:57:04 -0600836
837 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
838 additionalData);
839 brownoutLogged = true;
840 }
841 else
842 {
843 // If a brownout was previously logged but at least one PSU is not
844 // currently in AC fault, determine if the brownout condition can be
845 // cleared
846 if (brownoutLogged && (acFailedCount < presentCount))
847 {
848 // Chassis only recognizes the PowerSystemInputs change when it is
849 // off
850 try
851 {
852 using PowerState = sdbusplus::xyz::openbmc_project::State::
853 server::Chassis::PowerState;
854 PowerState currentPowerState;
855 util::getProperty<PowerState>(
856 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
857 "/xyz/openbmc_project/state/chassis0",
Patrick Williams7affb1f2024-01-19 14:20:46 -0600858 "xyz.openbmc_project.State.Chassis0", bus,
Jim Wright7f9288c2022-12-08 11:57:04 -0600859 currentPowerState);
860
861 if (currentPowerState == PowerState::Off)
862 {
863 // Indicate that the system is no longer in a brownout
864 // condition by setting the PowerSystemInputs status
865 // property to Good.
Anwaar Hadib64228d2025-05-30 23:55:26 +0000866 lg2::info(
867 "Brownout cleared, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
868 "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
869 acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
Jim Wright7f9288c2022-12-08 11:57:04 -0600870 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 {
Anwaar Hadib64228d2025-05-30 23:55:26 +0000878 lg2::error("Error trying to clear brownout, error: {ERROR}",
879 "ERROR", e);
Jim Wright7f9288c2022-12-08 11:57:04 -0600880 }
881 }
882 }
883}
884
Brandon Wyman64e97752022-06-03 23:50:13 +0000885void PSUManager::updateMissingPSUs()
886{
887 if (supportedConfigs.empty() || psus.empty())
888 {
889 return;
890 }
891
892 // Power supplies default to missing. If the power supply is present,
893 // the PowerSupply object will update the inventory Present property to
894 // true. If we have less than the required number of power supplies, and
895 // this power supply is missing, update the inventory Present property
896 // to false to indicate required power supply is missing. Avoid
897 // indicating power supply missing if not required.
898
899 auto presentCount =
900 std::count_if(psus.begin(), psus.end(),
901 [](const auto& psu) { return psu->isPresent(); });
902
903 for (const auto& config : supportedConfigs)
904 {
905 for (const auto& psu : psus)
906 {
907 auto psuModel = psu->getModelName();
908 auto psuShortName = psu->getShortName();
909 auto psuInventoryPath = psu->getInventoryPath();
910 auto relativeInvPath =
911 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
912 auto psuPresent = psu->isPresent();
913 auto presProperty = false;
914 auto propReadFail = false;
915
916 try
917 {
918 presProperty = getPresence(bus, psuInventoryPath);
919 propReadFail = false;
920 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500921 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000922 {
923 propReadFail = true;
924 // Relying on property change or interface added to retry.
925 // Log an informational trace to the journal.
Anwaar Hadib64228d2025-05-30 23:55:26 +0000926 lg2::info(
927 "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
928 "PSU_INVENTORY_PATH", psuInventoryPath);
Brandon Wyman64e97752022-06-03 23:50:13 +0000929 }
930
931 if (psuModel.empty())
932 {
933 if (!propReadFail && (presProperty != psuPresent))
934 {
935 // We already have this property, and it is not false
936 // set Present to false
937 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
938 }
939 continue;
940 }
941
942 if (config.first != psuModel)
943 {
944 continue;
945 }
946
947 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
948 {
949 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
950 }
951 }
952 }
953}
954
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000955void PSUManager::validateConfig()
956{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000957 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000958 {
959 return;
960 }
961
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000962 for (const auto& psu : psus)
963 {
Faisal Awada2ae827a2024-03-20 16:45:12 -0500964 if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000965 {
966 // Do not try to validate if input voltage fault present.
967 validationTimer->restartOnce(validationTimeout);
968 return;
969 }
970 }
971
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000972 std::map<std::string, std::string> additionalData;
973 auto supported = hasRequiredPSUs(additionalData);
974 if (supported)
975 {
976 runValidateConfig = false;
Faisal Awadac6fa6662023-04-25 22:43:01 -0500977 double actualVoltage;
978 int inputVoltage;
979 int previousInputVoltage = 0;
980 bool voltageMismatch = false;
981
982 for (const auto& psu : psus)
983 {
984 if (!psu->isPresent())
985 {
986 // Only present PSUs report a valid input voltage
987 continue;
988 }
989 psu->getInputVoltage(actualVoltage, inputVoltage);
990 if (previousInputVoltage && inputVoltage &&
991 (previousInputVoltage != inputVoltage))
992 {
993 additionalData["EXPECTED_VOLTAGE"] =
994 std::to_string(previousInputVoltage);
995 additionalData["ACTUAL_VOLTAGE"] =
996 std::to_string(actualVoltage);
997 voltageMismatch = true;
998 }
999 if (!previousInputVoltage && inputVoltage)
1000 {
1001 previousInputVoltage = inputVoltage;
1002 }
1003 }
1004 if (!voltageMismatch)
1005 {
1006 return;
1007 }
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001008 }
1009
1010 // Validation failed, create an error log.
1011 // Return without setting the runValidateConfig flag to false because
1012 // it may be that an additional supported configuration interface is
1013 // added and we need to validate it to see if it matches this system.
1014 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
1015 additionalData);
1016}
1017
1018bool PSUManager::hasRequiredPSUs(
1019 std::map<std::string, std::string>& additionalData)
1020{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001021 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001022 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001023 {
Adriana Kobylak523704d2021-09-21 15:55:41 +00001024 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001025 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001026
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001027 auto presentCount =
1028 std::count_if(psus.begin(), psus.end(),
1029 [](const auto& psu) { return psu->isPresent(); });
1030
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001031 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001032 // power supply model configuration. Since all configurations need to be
1033 // checked, the additional data would contain only the information of the
1034 // last configuration that did not match.
1035 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001036 for (const auto& config : supportedConfigs)
1037 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001038 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001039 {
1040 continue;
1041 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001042
Jim Wright941b60d2022-10-19 16:22:17 -05001043 // Number of power supplies present should equal or exceed the expected
1044 // count
1045 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001046 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001047 tmpAdditionalData.clear();
1048 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001049 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001050 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001051 continue;
1052 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001053
1054 bool voltageValidated = true;
1055 for (const auto& psu : psus)
1056 {
1057 if (!psu->isPresent())
1058 {
1059 // Only present PSUs report a valid input voltage
1060 continue;
1061 }
1062
1063 double actualInputVoltage;
1064 int inputVoltage;
1065 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1066
1067 if (std::find(config.second.inputVoltage.begin(),
Patrick Williamsf5402192024-08-16 15:20:53 -04001068 config.second.inputVoltage.end(), inputVoltage) ==
1069 config.second.inputVoltage.end())
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001070 {
1071 tmpAdditionalData.clear();
1072 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1073 std::to_string(actualInputVoltage);
1074 for (const auto& voltage : config.second.inputVoltage)
1075 {
1076 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1077 std::to_string(voltage) + " ";
1078 }
1079 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1080 psu->getInventoryPath();
1081
1082 voltageValidated = false;
1083 break;
1084 }
1085 }
1086 if (!voltageValidated)
1087 {
1088 continue;
1089 }
1090
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001091 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001092 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001093
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001094 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001095 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001096}
1097
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001098unsigned int PSUManager::getRequiredPSUCount()
1099{
1100 unsigned int requiredCount{0};
1101
1102 // Verify we have the supported configuration and PSU information
1103 if (!supportedConfigs.empty() && !psus.empty())
1104 {
1105 // Find PSU models. They should all be the same.
1106 std::set<std::string> models{};
1107 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1108 if (!psu->getModelName().empty())
1109 {
1110 models.insert(psu->getModelName());
1111 }
1112 });
1113
1114 // If exactly one model was found, find corresponding configuration
1115 if (models.size() == 1)
1116 {
1117 const std::string& model = *(models.begin());
1118 auto it = supportedConfigs.find(model);
1119 if (it != supportedConfigs.end())
1120 {
1121 requiredCount = it->second.powerSupplyCount;
1122 }
1123 }
1124 }
1125
1126 return requiredCount;
1127}
1128
1129bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1130{
1131 // Get required number of PSUs; if not found, we don't know if PSU required
1132 unsigned int requiredCount = getRequiredPSUCount();
1133 if (requiredCount == 0)
1134 {
1135 return false;
1136 }
1137
1138 // If total PSU count <= the required count, all PSUs are required
1139 if (psus.size() <= requiredCount)
1140 {
1141 return true;
1142 }
1143
1144 // We don't currently get information from EntityManager about which PSUs
1145 // are required, so we have to do some guesswork. First check if this PSU
1146 // is present. If so, assume it is required.
1147 if (psu.isPresent())
1148 {
1149 return true;
1150 }
1151
1152 // This PSU is not present. Count the number of other PSUs that are
1153 // present. If enough other PSUs are present, assume the specified PSU is
1154 // not required.
1155 unsigned int psuCount =
1156 std::count_if(psus.begin(), psus.end(),
1157 [](const auto& psu) { return psu->isPresent(); });
1158 if (psuCount >= requiredCount)
1159 {
1160 return false;
1161 }
1162
1163 // Check if this PSU was previously present. If so, assume it is required.
1164 // We know it was previously present if it has a non-empty model name.
1165 if (!psu.getModelName().empty())
1166 {
1167 return true;
1168 }
1169
1170 // This PSU was never present. Count the number of other PSUs that were
1171 // previously present. If including those PSUs is enough, assume the
1172 // specified PSU is not required.
1173 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1174 return (!psu->isPresent() && !psu->getModelName().empty());
1175 });
1176 if (psuCount >= requiredCount)
1177 {
1178 return false;
1179 }
1180
1181 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1182 // that were never present. PSU inventory paths typically end with the PSU
1183 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1184 std::vector<std::string> sortedPaths;
1185 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1186 if (!psu->isPresent() && psu->getModelName().empty())
1187 {
1188 sortedPaths.push_back(psu->getInventoryPath());
1189 }
1190 });
1191 std::sort(sortedPaths.begin(), sortedPaths.end());
1192
1193 // Check if specified PSU is close enough to start of list to be required
1194 for (const auto& path : sortedPaths)
1195 {
1196 if (path == psu.getInventoryPath())
1197 {
1198 return true;
1199 }
1200 if (++psuCount >= requiredCount)
1201 {
1202 break;
1203 }
1204 }
1205
1206 // PSU was not close to start of sorted list; assume not required
1207 return false;
1208}
1209
Adriana Kobylak523704d2021-09-21 15:55:41 +00001210bool PSUManager::validateModelName(
1211 std::string& model, std::map<std::string, std::string>& additionalData)
1212{
1213 // Check that all PSUs have the same model name. Initialize the model
1214 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001215 // against the rest of the PSUs and get its inventory path to use as callout
1216 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001217 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001218 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001219 for (const auto& psu : psus)
1220 {
1221 auto psuModel = psu->getModelName();
1222 if (psuModel.empty())
1223 {
1224 continue;
1225 }
1226 if (model.empty())
1227 {
1228 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001229 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001230 continue;
1231 }
1232 if (psuModel != model)
1233 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001234 if (supportedConfigs.find(model) != supportedConfigs.end())
1235 {
1236 // The base model is supported, callout the mismatched PSU. The
1237 // mismatched PSU may or may not be supported.
1238 additionalData["EXPECTED_MODEL"] = model;
1239 additionalData["ACTUAL_MODEL"] = psuModel;
1240 additionalData["CALLOUT_INVENTORY_PATH"] =
1241 psu->getInventoryPath();
1242 }
1243 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1244 {
1245 // The base model is not supported, but the mismatched PSU is,
1246 // callout the base PSU.
1247 additionalData["EXPECTED_MODEL"] = psuModel;
1248 additionalData["ACTUAL_MODEL"] = model;
1249 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1250 }
1251 else
1252 {
1253 // The base model and the mismatched PSU are not supported or
1254 // could not be found in the supported configuration, callout
1255 // the mismatched PSU.
1256 additionalData["EXPECTED_MODEL"] = model;
1257 additionalData["ACTUAL_MODEL"] = psuModel;
1258 additionalData["CALLOUT_INVENTORY_PATH"] =
1259 psu->getInventoryPath();
1260 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001261 model.clear();
1262 return false;
1263 }
1264 }
1265 return true;
1266}
1267
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001268void PSUManager::setPowerConfigGPIO()
1269{
1270 if (!powerConfigGPIO)
1271 {
1272 return;
1273 }
1274
1275 std::string model{};
1276 std::map<std::string, std::string> additionalData;
1277 if (!validateModelName(model, additionalData))
1278 {
1279 return;
1280 }
1281
1282 auto config = supportedConfigs.find(model);
1283 if (config != supportedConfigs.end())
1284 {
1285 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1286 // if the supported configuration indicates that this system model
1287 // expects the maximum number of power supplies (full load set to true).
1288 // Else, set it to high (1), this is the default.
1289 auto powerConfigValue =
1290 (config->second.powerConfigFullLoad == true ? 0 : 1);
1291 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1292 powerConfigGPIO->write(powerConfigValue, flags);
1293 }
1294}
1295
Faisal Awadab66ae502023-04-01 18:30:32 -05001296void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
1297{
1298 namespace fs = std::filesystem;
1299 std::stringstream ss;
1300 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
Patrick Williamsf5402192024-08-16 15:20:53 -04001301 std::string symLinkPath =
1302 deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
Faisal Awadab66ae502023-04-01 18:30:32 -05001303 try
1304 {
1305 fs::path linkStrPath = fs::read_symlink(symLinkPath);
1306 driverName = linkStrPath.filename();
1307 }
1308 catch (const std::exception& e)
1309 {
Anwaar Hadib64228d2025-05-30 23:55:26 +00001310 lg2::error(
1311 "Failed to find device driver {SYM_LINK_PATH}, error {ERROR}",
1312 "SYM_LINK_PATH", symLinkPath, "ERROR", e);
Faisal Awadab66ae502023-04-01 18:30:32 -05001313 }
1314}
Faisal Awadab7131a12023-10-26 19:38:45 -05001315
1316void PSUManager::populateDriverName()
1317{
1318 std::string driverName;
1319 // Search in PSUs for driver name
1320 std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
1321 if (!psu->getDriverName().empty())
1322 {
1323 driverName = psu->getDriverName();
1324 }
1325 });
1326 // Assign driver name to all PSUs
1327 std::for_each(psus.begin(), psus.end(),
1328 [=](auto& psu) { psu->setDriverName(driverName); });
1329}
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001330} // namespace phosphor::power::manager