blob: 3195a584e447adf984c093467c5e670022b9411d [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 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 sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060046{
Brandon Wyman510acaa2020-11-05 18:32:04 -060047 // Subscribe to InterfacesAdded before doing a property read, otherwise
48 // the interface could be created after the read attempt but before the
49 // match is created.
50 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
51 bus,
52 sdbusplus::bus::match::rules::interfacesAdded() +
53 sdbusplus::bus::match::rules::sender(
54 "xyz.openbmc_project.EntityManager"),
55 std::bind(&PSUManager::entityManagerIfaceAdded, this,
56 std::placeholders::_1));
57 getPSUConfiguration();
58 getSystemProperties();
59
Adriana Kobylakc9b05732022-03-19 15:15:10 +000060 // Request the bus name before the analyze() function, which is the one that
61 // determines the brownout condition and sets the status d-bus property.
62 bus.request_name(managerBusName);
63
Brandon Wyman510acaa2020-11-05 18:32:04 -060064 using namespace sdeventplus;
65 auto interval = std::chrono::milliseconds(1000);
66 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
67 e, std::bind(&PSUManager::analyze, this), interval);
68
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000069 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
70 e, std::bind(&PSUManager::validateConfig, this));
71
Adriana Kobylakc0a07582021-10-13 15:52:25 +000072 try
73 {
74 powerConfigGPIO = createGPIO("power-config-full-load");
75 }
76 catch (const std::exception& e)
77 {
78 // Ignore error, GPIO may not be implemented in this system.
79 powerConfigGPIO = nullptr;
80 }
81
Brandon Wyman510acaa2020-11-05 18:32:04 -060082 // Subscribe to power state changes
83 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
84 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
85 bus,
86 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
87 POWER_IFACE),
88 [this](auto& msg) { this->powerStateChanged(msg); });
89
90 initialize();
91}
92
Jim Wrightaca86d02022-06-10 12:01:39 -050093void PSUManager::initialize()
94{
95 try
96 {
97 // pgood is the latest read of the chassis pgood
98 int pgood = 0;
99 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
100 powerService, bus, pgood);
101
102 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -0600103 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -0500104 POWER_IFACE, "getPowerState");
105 auto reply = bus.call(method);
106 int state = 0;
107 reply.read(state);
108
109 if (state)
110 {
111 // Monitor PSUs anytime state is on
112 powerOn = true;
113 // In the power fault window if pgood is off
114 powerFaultOccurring = !pgood;
115 validationTimer->restartOnce(validationTimeout);
116 }
117 else
118 {
119 // Power is off
120 powerOn = false;
121 powerFaultOccurring = false;
122 runValidateConfig = true;
123 }
124 }
125 catch (const std::exception& e)
126 {
127 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500128 std::format(
Jim Wrightaca86d02022-06-10 12:01:39 -0500129 "Failed to get power state, assuming it is off, error {}",
130 e.what())
131 .c_str());
132 powerOn = false;
133 powerFaultOccurring = false;
134 runValidateConfig = true;
135 }
136
137 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
138 clearFaults();
139 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500140 setPowerConfigGPIO();
141
142 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500143 std::format("initialize: power on: {}, power fault occurring: {}",
Jim Wrightaca86d02022-06-10 12:01:39 -0500144 powerOn, powerFaultOccurring)
145 .c_str());
146}
147
Brandon Wyman510acaa2020-11-05 18:32:04 -0600148void PSUManager::getPSUConfiguration()
149{
150 using namespace phosphor::power::util;
151 auto depth = 0;
152 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
153
154 psus.clear();
155
156 // I should get a map of objects back.
157 // Each object will have a path, a service, and an interface.
158 // The interface should match the one passed into this function.
159 for (const auto& [path, services] : objects)
160 {
161 auto service = services.begin()->first;
162
163 if (path.empty() || service.empty())
164 {
165 continue;
166 }
167
168 // For each object in the array of objects, I want to get properties
169 // from the service, path, and interface.
Patrick Williamsf5402192024-08-16 15:20:53 -0400170 auto properties =
171 getAllProperties(bus, path, IBMCFFPSInterface, service);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600172
173 getPSUProperties(properties);
174 }
175
176 if (psus.empty())
177 {
178 // Interface or properties not found. Let the Interfaces Added callback
179 // process the information once the interfaces are added to D-Bus.
Shawn McCarney768d2262024-07-09 15:02:59 -0500180 log<level::INFO>(std::format("No power supplies to monitor").c_str());
Brandon Wyman510acaa2020-11-05 18:32:04 -0600181 }
182}
183
184void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
185{
186 // From passed in properties, I want to get: I2CBus, I2CAddress,
187 // and Name. Create a power supply object, using Name to build the inventory
188 // path.
189 const auto basePSUInvPath =
190 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
191 uint64_t* i2cbus = nullptr;
192 uint64_t* i2caddr = nullptr;
193 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000194 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600195
196 for (const auto& property : properties)
197 {
198 try
199 {
200 if (property.first == i2cBusProp)
201 {
202 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
203 }
204 else if (property.first == i2cAddressProp)
205 {
206 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
207 }
208 else if (property.first == psuNameProp)
209 {
210 psuname = std::get_if<std::string>(&properties[psuNameProp]);
211 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000212 else if (property.first == presLineName)
213 {
214 preslineptr =
215 std::get_if<std::string>(&properties[presLineName]);
216 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600217 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500218 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000219 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600220 }
221
222 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
223 {
224 std::string invpath = basePSUInvPath;
225 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000226 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600227
Shawn McCarney768d2262024-07-09 15:02:59 -0500228 log<level::DEBUG>(std::format("Inventory Path: {}", invpath).c_str());
Brandon Wyman510acaa2020-11-05 18:32:04 -0600229
B. J. Wyman681b2a32021-04-20 22:31:22 +0000230 if (nullptr != preslineptr)
231 {
232 presline = *preslineptr;
233 }
234
Patrick Williamsf5402192024-08-16 15:20:53 -0400235 auto invMatch =
236 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
237 return psu->getInventoryPath() == invpath;
238 });
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000239 if (invMatch != psus.end())
240 {
241 // This power supply has the same inventory path as the one with
242 // information just added to D-Bus.
243 // Changes to GPIO line name unlikely, so skip checking.
244 // Changes to the I2C bus and address unlikely, as that would
245 // require corresponding device tree updates.
246 // Return out to avoid duplicate object creation.
247 return;
248 }
249
Faisal Awadab66ae502023-04-01 18:30:32 -0500250 buildDriverName(*i2cbus, *i2caddr);
B. J. Wyman681b2a32021-04-20 22:31:22 +0000251 log<level::DEBUG>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500252 std::format("make PowerSupply bus: {} addr: {} presline: {}",
Faisal Awadab66ae502023-04-01 18:30:32 -0500253 *i2cbus, *i2caddr, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000254 .c_str());
George Liu9464c422023-02-27 14:30:27 +0800255 auto psu = std::make_unique<PowerSupply>(
Faisal Awadab66ae502023-04-01 18:30:32 -0500256 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
George Liu9464c422023-02-27 14:30:27 +0800257 std::bind(
258 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
259 this));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600260 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000261
262 // Subscribe to power supply presence changes
263 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
264 bus,
265 sdbusplus::bus::match::rules::propertiesChanged(invpath,
266 INVENTORY_IFACE),
267 [this](auto& msg) { this->presenceChanged(msg); });
268 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600269 }
270
271 if (psus.empty())
272 {
Shawn McCarney768d2262024-07-09 15:02:59 -0500273 log<level::INFO>(std::format("No power supplies to monitor").c_str());
Brandon Wyman510acaa2020-11-05 18:32:04 -0600274 }
Faisal Awadab7131a12023-10-26 19:38:45 -0500275 else
276 {
277 populateDriverName();
278 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600279}
280
Adriana Kobylake1074d82021-03-16 20:46:44 +0000281void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
282{
283 try
284 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000285 auto propIt = properties.find("SupportedType");
286 if (propIt == properties.end())
287 {
288 return;
289 }
290 const std::string* type = std::get_if<std::string>(&(propIt->second));
291 if ((type == nullptr) || (*type != "PowerSupply"))
292 {
293 return;
294 }
295
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000296 propIt = properties.find("SupportedModel");
297 if (propIt == properties.end())
298 {
299 return;
300 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000301 const std::string* model = std::get_if<std::string>(&(propIt->second));
302 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000303 {
304 return;
305 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000306
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000307 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000308 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000309 if (propIt != properties.end())
310 {
311 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
312 if (count != nullptr)
313 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000314 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000315 }
316 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000317 propIt = properties.find("InputVoltage");
318 if (propIt != properties.end())
319 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000320 const std::vector<uint64_t>* voltage =
321 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000322 if (voltage != nullptr)
323 {
324 sys.inputVoltage = *voltage;
325 }
326 }
327
Adriana Kobylak886574c2021-11-01 18:22:28 +0000328 // The PowerConfigFullLoad is an optional property, default it to false
329 // since that's the default value of the power-config-full-load GPIO.
330 sys.powerConfigFullLoad = false;
331 propIt = properties.find("PowerConfigFullLoad");
332 if (propIt != properties.end())
333 {
334 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
335 if (fullLoad != nullptr)
336 {
337 sys.powerConfigFullLoad = *fullLoad;
338 }
339 }
340
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000341 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000342 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500343 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000344 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000345}
346
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600347void PSUManager::getSystemProperties()
348{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600349 try
350 {
Patrick Williamsf5402192024-08-16 15:20:53 -0400351 util::DbusSubtree subtree =
352 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000353 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600354 {
355 throw std::runtime_error("Supported Configuration Not Found");
356 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600357
Adriana Kobylake1074d82021-03-16 20:46:44 +0000358 for (const auto& [objPath, services] : subtree)
359 {
360 std::string service = services.begin()->first;
361 if (objPath.empty() || service.empty())
362 {
363 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600364 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000365 auto properties = util::getAllProperties(
366 bus, objPath, supportedConfIntf, service);
367 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600368 }
369 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500370 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600371 {
372 // Interface or property not found. Let the Interfaces Added callback
373 // process the information once the interfaces are added to D-Bus.
374 }
375}
376
Patrick Williams7354ce62022-07-22 19:26:56 -0500377void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600378{
379 try
380 {
381 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000382 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600383 interfaces;
384 msg.read(objPath, interfaces);
385
386 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600387 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600388 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600389 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000390 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600391 }
392
Brandon Wyman510acaa2020-11-05 18:32:04 -0600393 itIntf = interfaces.find(IBMCFFPSInterface);
394 if (itIntf != interfaces.cend())
395 {
396 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500397 std::format("InterfacesAdded for: {}", IBMCFFPSInterface)
Brandon Wyman510acaa2020-11-05 18:32:04 -0600398 .c_str());
399 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000400 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600401 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000402
403 // Call to validate the psu configuration if the power is on and both
404 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
405 // processed
406 if (powerOn && !psus.empty() && !supportedConfigs.empty())
407 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000408 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000409 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600410 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500411 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600412 {
413 // Ignore, the property may be of a different type than expected.
414 }
415}
416
Patrick Williams7354ce62022-07-22 19:26:56 -0500417void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500418{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500419 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500420 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500421 msg.read(msgSensor, msgData);
422
Jim Wrightaca86d02022-06-10 12:01:39 -0500423 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500424 auto valPropMap = msgData.find("state");
425 if (valPropMap != msgData.end())
426 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500427 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500428 if (state)
429 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500430 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500431 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500432 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000433 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500434 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000435 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000436 setPowerConfigGPIO();
Matt Spinlera068f422023-03-10 13:06:49 -0600437 setInputVoltageRating();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500438 }
439 else
440 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500441 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500442 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500443 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000444 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500445 }
446 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500447
448 // Check if it was the pgood property that changed.
449 valPropMap = msgData.find("pgood");
450 if (valPropMap != msgData.end())
451 {
452 int pgood = std::get<int>(valPropMap->second);
453 if (!pgood)
454 {
455 // Chassis power good has turned off
456 if (powerOn)
457 {
458 // pgood is off but state is on, in power fault window
459 powerFaultOccurring = true;
460 }
461 }
462 }
463 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500464 std::format(
Jim Wrightaca86d02022-06-10 12:01:39 -0500465 "powerStateChanged: power on: {}, power fault occurring: {}",
466 powerOn, powerFaultOccurring)
467 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500468}
469
Patrick Williams7354ce62022-07-22 19:26:56 -0500470void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000471{
472 std::string msgSensor;
473 std::map<std::string, std::variant<uint32_t, bool>> msgData;
474 msg.read(msgSensor, msgData);
475
476 // Check if it was the Present property that changed.
477 auto valPropMap = msgData.find(PRESENT_PROP);
478 if (valPropMap != msgData.end())
479 {
480 if (std::get<bool>(valPropMap->second))
481 {
482 // A PSU became present, force the PSU validation to run.
483 runValidateConfig = true;
484 validationTimer->restartOnce(validationTimeout);
485 }
486 }
487}
488
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000489void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
490{
491 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000492 constexpr auto method = "setPowerSupplyError";
493
494 try
495 {
496 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600497 auto methodMsg = bus.new_method_call(
498 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000499 methodMsg.append(psuErrorString);
500 auto callReply = bus.call(methodMsg);
501 }
502 catch (const std::exception& e)
503 {
504 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500505 std::format("Failed calling setPowerSupplyError due to error {}",
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000506 e.what())
507 .c_str());
508 }
509}
510
Brandon Wyman8b662882021-10-08 17:31:51 +0000511void PSUManager::createError(const std::string& faultName,
512 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500513{
514 using namespace sdbusplus::xyz::openbmc_project;
515 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
516 constexpr auto loggingCreateInterface =
517 "xyz.openbmc_project.Logging.Create";
518
519 try
520 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000521 additionalData["_PID"] = std::to_string(getpid());
522
Patrick Williamsf5402192024-08-16 15:20:53 -0400523 auto service =
524 util::getService(loggingObjectPath, loggingCreateInterface, bus);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500525
526 if (service.empty())
527 {
528 log<level::ERR>("Unable to get logging manager service");
529 return;
530 }
531
532 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
533 loggingCreateInterface, "Create");
534
535 auto level = Logging::server::Entry::Level::Error;
536 method.append(faultName, level, additionalData);
537
538 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000539 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500540 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500541 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500542 {
543 log<level::ERR>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500544 std::format(
Brandon Wymanb76ab242020-09-16 18:06:06 -0500545 "Failed creating event log for fault {} due to error {}",
546 faultName, e.what())
547 .c_str());
548 }
549}
550
Brandon Wyman18a24d92022-04-19 22:48:34 +0000551void PSUManager::syncHistory()
552{
Faisal Awadae9b37262023-07-30 21:02:16 -0500553 if (driverName != ACBEL_FSG032_DD_NAME)
Brandon Wyman18a24d92022-04-19 22:48:34 +0000554 {
Faisal Awadae9b37262023-07-30 21:02:16 -0500555 if (!syncHistoryGPIO)
Brandon Wyman18a24d92022-04-19 22:48:34 +0000556 {
Andrew Geissler3a492522023-11-30 13:30:51 -0600557 try
558 {
559 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
560 }
561 catch (const std::exception& e)
562 {
563 // Not an error, system just hasn't implemented the synch gpio
564 log<level::INFO>("No synchronization GPIO found");
565 syncHistoryGPIO = nullptr;
566 }
Faisal Awadae9b37262023-07-30 21:02:16 -0500567 }
568 if (syncHistoryGPIO)
569 {
570 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
571 log<level::INFO>("Synchronize INPUT_HISTORY");
572 syncHistoryGPIO->toggleLowHigh(delay);
Faisal Awadae9b37262023-07-30 21:02:16 -0500573 log<level::INFO>("Synchronize INPUT_HISTORY completed");
Brandon Wyman18a24d92022-04-19 22:48:34 +0000574 }
575 }
Andrew Geissler3a492522023-11-30 13:30:51 -0600576
577 // Always clear synch history required after calling this function
578 for (auto& psu : psus)
579 {
580 psu->clearSyncHistoryRequired();
581 }
Brandon Wyman18a24d92022-04-19 22:48:34 +0000582}
583
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500584void PSUManager::analyze()
585{
Patrick Williamsf5402192024-08-16 15:20:53 -0400586 auto syncHistoryRequired =
587 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
588 return psu->isSyncHistoryRequired();
589 });
Brandon Wyman18a24d92022-04-19 22:48:34 +0000590 if (syncHistoryRequired)
591 {
592 syncHistory();
593 }
594
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500595 for (auto& psu : psus)
596 {
597 psu->analyze();
598 }
599
Jim Wright7f9288c2022-12-08 11:57:04 -0600600 analyzeBrownout();
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000601
Jim Wrightcefe85f2022-11-18 09:57:47 -0600602 // Only perform individual PSU analysis if power is on and a brownout has
603 // not already been logged
604 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500605 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600606 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500607 {
Jim Wright7f9288c2022-12-08 11:57:04 -0600608 std::map<std::string, std::string> additionalData;
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000609
Faisal Awada03e2a022023-11-15 20:31:13 +0000610 if (!psu->isFaultLogged() && !psu->isPresent() &&
611 !validationTimer->isEnabled())
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600612 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000613 std::map<std::string, std::string> requiredPSUsData;
614 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500615 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000616 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000617 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000618 // Create error for power supply missing.
619 additionalData["CALLOUT_INVENTORY_PATH"] =
620 psu->getInventoryPath();
621 additionalData["CALLOUT_PRIORITY"] = "H";
622 createError(
623 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
624 additionalData);
625 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600626 psu->setFaultLogged();
627 }
628 else if (!psu->isFaultLogged() && psu->isFaulted())
629 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000630 // Add STATUS_WORD and STATUS_MFR last response, in padded
631 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600632 additionalData["STATUS_WORD"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500633 std::format("{:#04x}", psu->getStatusWord());
Patrick Williamsf5402192024-08-16 15:20:53 -0400634 additionalData["STATUS_MFR"] =
635 std::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600636 // If there are faults being reported, they possibly could be
637 // related to a bug in the firmware version running on the power
638 // supply. Capture that data into the error as well.
639 additionalData["FW_VERSION"] = psu->getFWVersion();
640
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000641 if (psu->hasCommFault())
642 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000643 additionalData["STATUS_CML"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500644 std::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000645 /* Attempts to communicate with the power supply have
646 * reached there limit. Create an error. */
647 additionalData["CALLOUT_DEVICE_PATH"] =
648 psu->getDevicePath();
649
650 createError(
651 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
652 additionalData);
653
654 psu->setFaultLogged();
655 }
656 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600657 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000658 // Include STATUS_INPUT for input faults.
659 additionalData["STATUS_INPUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500660 std::format("{:#02x}", psu->getStatusInput());
Brandon Wymanf07bc792021-10-12 19:00:35 +0000661
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600662 /* The power supply location might be needed if the input
663 * fault is due to a problem with the power supply itself.
664 * Include the inventory path with a call out priority of
665 * low.
666 */
667 additionalData["CALLOUT_INVENTORY_PATH"] =
668 psu->getInventoryPath();
669 additionalData["CALLOUT_PRIORITY"] = "L";
670 createError("xyz.openbmc_project.Power.PowerSupply.Error."
671 "InputFault",
672 additionalData);
673 psu->setFaultLogged();
674 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000675 else if (psu->hasPSKillFault())
676 {
677 createError(
678 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
679 additionalData);
680 psu->setFaultLogged();
681 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000682 else if (psu->hasVoutOVFault())
683 {
684 // Include STATUS_VOUT for Vout faults.
685 additionalData["STATUS_VOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500686 std::format("{:#02x}", psu->getStatusVout());
Brandon Wyman6710ba22021-10-27 17:39:31 +0000687
688 additionalData["CALLOUT_INVENTORY_PATH"] =
689 psu->getInventoryPath();
690
691 createError(
692 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
693 additionalData);
694
695 psu->setFaultLogged();
696 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000697 else if (psu->hasIoutOCFault())
698 {
699 // Include STATUS_IOUT for Iout faults.
700 additionalData["STATUS_IOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500701 std::format("{:#02x}", psu->getStatusIout());
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000702
703 createError(
704 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
705 additionalData);
706
707 psu->setFaultLogged();
708 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000709 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
710 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000711 {
712 // Include STATUS_VOUT for Vout faults.
713 additionalData["STATUS_VOUT"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500714 std::format("{:#02x}", psu->getStatusVout());
Brandon Wyman2cf46942021-10-28 19:09:16 +0000715
716 additionalData["CALLOUT_INVENTORY_PATH"] =
717 psu->getInventoryPath();
718
719 createError(
720 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
721 additionalData);
722
723 psu->setFaultLogged();
724 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000725 // A fan fault should have priority over a temperature fault,
726 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500727 // Only process if not in power fault window.
728 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000729 {
730 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
731 additionalData["STATUS_TEMPERATURE"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500732 std::format("{:#02x}", psu->getStatusTemperature());
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000733 additionalData["STATUS_FANS_1_2"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500734 std::format("{:#02x}", psu->getStatusFans12());
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000735
736 additionalData["CALLOUT_INVENTORY_PATH"] =
737 psu->getInventoryPath();
738
739 createError(
740 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
741 additionalData);
742
743 psu->setFaultLogged();
744 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000745 else if (psu->hasTempFault())
746 {
747 // Include STATUS_TEMPERATURE for temperature faults.
748 additionalData["STATUS_TEMPERATURE"] =
Shawn McCarney768d2262024-07-09 15:02:59 -0500749 std::format("{:#02x}", psu->getStatusTemperature());
Brandon Wyman96893a42021-11-05 19:56:57 +0000750
751 additionalData["CALLOUT_INVENTORY_PATH"] =
752 psu->getInventoryPath();
753
754 createError(
755 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
756 additionalData);
757
758 psu->setFaultLogged();
759 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600760 else if (psu->hasMFRFault())
761 {
762 /* This can represent a variety of faults that result in
763 * calling out the power supply for replacement: Output
764 * OverCurrent, Output Under Voltage, and potentially other
765 * faults.
766 *
767 * Also plan on putting specific fault in AdditionalData,
768 * along with register names and register values
769 * (STATUS_WORD, STATUS_MFR, etc.).*/
770
771 additionalData["CALLOUT_INVENTORY_PATH"] =
772 psu->getInventoryPath();
773
774 createError(
775 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500776 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500777
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600778 psu->setFaultLogged();
779 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500780 // Only process if not in power fault window.
781 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000782 {
783 /* POWER_GOOD# is not low, or OFF is on */
784 additionalData["CALLOUT_INVENTORY_PATH"] =
785 psu->getInventoryPath();
786
787 createError(
788 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
789 additionalData);
790
791 psu->setFaultLogged();
792 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500793 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500794 }
795 }
796}
797
Jim Wright7f9288c2022-12-08 11:57:04 -0600798void PSUManager::analyzeBrownout()
799{
800 // Count number of power supplies failing
801 size_t presentCount = 0;
802 size_t notPresentCount = 0;
803 size_t acFailedCount = 0;
804 size_t pgoodFailedCount = 0;
805 for (const auto& psu : psus)
806 {
807 if (psu->isPresent())
808 {
809 ++presentCount;
810 if (psu->hasACFault())
811 {
812 ++acFailedCount;
813 }
814 else if (psu->hasPgoodFault())
815 {
816 ++pgoodFailedCount;
817 }
818 }
819 else
820 {
821 ++notPresentCount;
822 }
823 }
824
825 // Only issue brownout failure if chassis pgood has failed, it has not
826 // already been logged, at least one PSU has seen an AC fail, and all
827 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
828 // at least one PSU is present.
829 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
830 (presentCount == (acFailedCount + pgoodFailedCount)))
831 {
832 // Indicate that the system is in a brownout condition by creating an
833 // error log and setting the PowerSystemInputs status property to Fault.
834 powerSystemInputs.status(
835 sdbusplus::xyz::openbmc_project::State::Decorator::server::
836 PowerSystemInputs::Status::Fault);
837
838 std::map<std::string, std::string> additionalData;
839 additionalData.emplace("NOT_PRESENT_COUNT",
840 std::to_string(notPresentCount));
841 additionalData.emplace("VIN_FAULT_COUNT",
842 std::to_string(acFailedCount));
843 additionalData.emplace("PGOOD_FAULT_COUNT",
844 std::to_string(pgoodFailedCount));
845 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500846 std::format(
Jim Wright7f9288c2022-12-08 11:57:04 -0600847 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
848 notPresentCount, acFailedCount, pgoodFailedCount)
849 .c_str());
850
851 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
852 additionalData);
853 brownoutLogged = true;
854 }
855 else
856 {
857 // If a brownout was previously logged but at least one PSU is not
858 // currently in AC fault, determine if the brownout condition can be
859 // cleared
860 if (brownoutLogged && (acFailedCount < presentCount))
861 {
862 // Chassis only recognizes the PowerSystemInputs change when it is
863 // off
864 try
865 {
866 using PowerState = sdbusplus::xyz::openbmc_project::State::
867 server::Chassis::PowerState;
868 PowerState currentPowerState;
869 util::getProperty<PowerState>(
870 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
871 "/xyz/openbmc_project/state/chassis0",
Patrick Williams7affb1f2024-01-19 14:20:46 -0600872 "xyz.openbmc_project.State.Chassis0", bus,
Jim Wright7f9288c2022-12-08 11:57:04 -0600873 currentPowerState);
874
875 if (currentPowerState == PowerState::Off)
876 {
877 // Indicate that the system is no longer in a brownout
878 // condition by setting the PowerSystemInputs status
879 // property to Good.
880 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500881 std::format(
Jim Wright7f9288c2022-12-08 11:57:04 -0600882 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
883 notPresentCount, acFailedCount, pgoodFailedCount)
884 .c_str());
885 powerSystemInputs.status(
886 sdbusplus::xyz::openbmc_project::State::Decorator::
887 server::PowerSystemInputs::Status::Good);
888 brownoutLogged = false;
889 }
890 }
891 catch (const std::exception& e)
892 {
893 log<level::ERR>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500894 std::format("Error trying to clear brownout, error: {}",
Jim Wright7f9288c2022-12-08 11:57:04 -0600895 e.what())
896 .c_str());
897 }
898 }
899 }
900}
901
Brandon Wyman64e97752022-06-03 23:50:13 +0000902void PSUManager::updateMissingPSUs()
903{
904 if (supportedConfigs.empty() || psus.empty())
905 {
906 return;
907 }
908
909 // Power supplies default to missing. If the power supply is present,
910 // the PowerSupply object will update the inventory Present property to
911 // true. If we have less than the required number of power supplies, and
912 // this power supply is missing, update the inventory Present property
913 // to false to indicate required power supply is missing. Avoid
914 // indicating power supply missing if not required.
915
916 auto presentCount =
917 std::count_if(psus.begin(), psus.end(),
918 [](const auto& psu) { return psu->isPresent(); });
919
920 for (const auto& config : supportedConfigs)
921 {
922 for (const auto& psu : psus)
923 {
924 auto psuModel = psu->getModelName();
925 auto psuShortName = psu->getShortName();
926 auto psuInventoryPath = psu->getInventoryPath();
927 auto relativeInvPath =
928 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
929 auto psuPresent = psu->isPresent();
930 auto presProperty = false;
931 auto propReadFail = false;
932
933 try
934 {
935 presProperty = getPresence(bus, psuInventoryPath);
936 propReadFail = false;
937 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500938 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000939 {
940 propReadFail = true;
941 // Relying on property change or interface added to retry.
942 // Log an informational trace to the journal.
943 log<level::INFO>(
Shawn McCarney768d2262024-07-09 15:02:59 -0500944 std::format("D-Bus property {} access failure exception",
Brandon Wyman64e97752022-06-03 23:50:13 +0000945 psuInventoryPath)
946 .c_str());
947 }
948
949 if (psuModel.empty())
950 {
951 if (!propReadFail && (presProperty != psuPresent))
952 {
953 // We already have this property, and it is not false
954 // set Present to false
955 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
956 }
957 continue;
958 }
959
960 if (config.first != psuModel)
961 {
962 continue;
963 }
964
965 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
966 {
967 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
968 }
969 }
970 }
971}
972
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000973void PSUManager::validateConfig()
974{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000975 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000976 {
977 return;
978 }
979
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000980 for (const auto& psu : psus)
981 {
Faisal Awada2ae827a2024-03-20 16:45:12 -0500982 if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000983 {
984 // Do not try to validate if input voltage fault present.
985 validationTimer->restartOnce(validationTimeout);
986 return;
987 }
988 }
989
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000990 std::map<std::string, std::string> additionalData;
991 auto supported = hasRequiredPSUs(additionalData);
992 if (supported)
993 {
994 runValidateConfig = false;
Faisal Awadac6fa6662023-04-25 22:43:01 -0500995 double actualVoltage;
996 int inputVoltage;
997 int previousInputVoltage = 0;
998 bool voltageMismatch = false;
999
1000 for (const auto& psu : psus)
1001 {
1002 if (!psu->isPresent())
1003 {
1004 // Only present PSUs report a valid input voltage
1005 continue;
1006 }
1007 psu->getInputVoltage(actualVoltage, inputVoltage);
1008 if (previousInputVoltage && inputVoltage &&
1009 (previousInputVoltage != inputVoltage))
1010 {
1011 additionalData["EXPECTED_VOLTAGE"] =
1012 std::to_string(previousInputVoltage);
1013 additionalData["ACTUAL_VOLTAGE"] =
1014 std::to_string(actualVoltage);
1015 voltageMismatch = true;
1016 }
1017 if (!previousInputVoltage && inputVoltage)
1018 {
1019 previousInputVoltage = inputVoltage;
1020 }
1021 }
1022 if (!voltageMismatch)
1023 {
1024 return;
1025 }
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001026 }
1027
1028 // Validation failed, create an error log.
1029 // Return without setting the runValidateConfig flag to false because
1030 // it may be that an additional supported configuration interface is
1031 // added and we need to validate it to see if it matches this system.
1032 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
1033 additionalData);
1034}
1035
1036bool PSUManager::hasRequiredPSUs(
1037 std::map<std::string, std::string>& additionalData)
1038{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001039 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001040 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001041 {
Adriana Kobylak523704d2021-09-21 15:55:41 +00001042 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001043 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001044
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001045 auto presentCount =
1046 std::count_if(psus.begin(), psus.end(),
1047 [](const auto& psu) { return psu->isPresent(); });
1048
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001049 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001050 // power supply model configuration. Since all configurations need to be
1051 // checked, the additional data would contain only the information of the
1052 // last configuration that did not match.
1053 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001054 for (const auto& config : supportedConfigs)
1055 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001056 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001057 {
1058 continue;
1059 }
Brandon Wyman64e97752022-06-03 23:50:13 +00001060
Jim Wright941b60d2022-10-19 16:22:17 -05001061 // Number of power supplies present should equal or exceed the expected
1062 // count
1063 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001064 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001065 tmpAdditionalData.clear();
1066 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001067 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001068 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001069 continue;
1070 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001071
1072 bool voltageValidated = true;
1073 for (const auto& psu : psus)
1074 {
1075 if (!psu->isPresent())
1076 {
1077 // Only present PSUs report a valid input voltage
1078 continue;
1079 }
1080
1081 double actualInputVoltage;
1082 int inputVoltage;
1083 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1084
1085 if (std::find(config.second.inputVoltage.begin(),
Patrick Williamsf5402192024-08-16 15:20:53 -04001086 config.second.inputVoltage.end(), inputVoltage) ==
1087 config.second.inputVoltage.end())
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001088 {
1089 tmpAdditionalData.clear();
1090 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1091 std::to_string(actualInputVoltage);
1092 for (const auto& voltage : config.second.inputVoltage)
1093 {
1094 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1095 std::to_string(voltage) + " ";
1096 }
1097 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1098 psu->getInventoryPath();
1099
1100 voltageValidated = false;
1101 break;
1102 }
1103 }
1104 if (!voltageValidated)
1105 {
1106 continue;
1107 }
1108
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001109 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001110 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001111
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001112 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001113 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001114}
1115
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001116unsigned int PSUManager::getRequiredPSUCount()
1117{
1118 unsigned int requiredCount{0};
1119
1120 // Verify we have the supported configuration and PSU information
1121 if (!supportedConfigs.empty() && !psus.empty())
1122 {
1123 // Find PSU models. They should all be the same.
1124 std::set<std::string> models{};
1125 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1126 if (!psu->getModelName().empty())
1127 {
1128 models.insert(psu->getModelName());
1129 }
1130 });
1131
1132 // If exactly one model was found, find corresponding configuration
1133 if (models.size() == 1)
1134 {
1135 const std::string& model = *(models.begin());
1136 auto it = supportedConfigs.find(model);
1137 if (it != supportedConfigs.end())
1138 {
1139 requiredCount = it->second.powerSupplyCount;
1140 }
1141 }
1142 }
1143
1144 return requiredCount;
1145}
1146
1147bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1148{
1149 // Get required number of PSUs; if not found, we don't know if PSU required
1150 unsigned int requiredCount = getRequiredPSUCount();
1151 if (requiredCount == 0)
1152 {
1153 return false;
1154 }
1155
1156 // If total PSU count <= the required count, all PSUs are required
1157 if (psus.size() <= requiredCount)
1158 {
1159 return true;
1160 }
1161
1162 // We don't currently get information from EntityManager about which PSUs
1163 // are required, so we have to do some guesswork. First check if this PSU
1164 // is present. If so, assume it is required.
1165 if (psu.isPresent())
1166 {
1167 return true;
1168 }
1169
1170 // This PSU is not present. Count the number of other PSUs that are
1171 // present. If enough other PSUs are present, assume the specified PSU is
1172 // not required.
1173 unsigned int psuCount =
1174 std::count_if(psus.begin(), psus.end(),
1175 [](const auto& psu) { return psu->isPresent(); });
1176 if (psuCount >= requiredCount)
1177 {
1178 return false;
1179 }
1180
1181 // Check if this PSU was previously present. If so, assume it is required.
1182 // We know it was previously present if it has a non-empty model name.
1183 if (!psu.getModelName().empty())
1184 {
1185 return true;
1186 }
1187
1188 // This PSU was never present. Count the number of other PSUs that were
1189 // previously present. If including those PSUs is enough, assume the
1190 // specified PSU is not required.
1191 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1192 return (!psu->isPresent() && !psu->getModelName().empty());
1193 });
1194 if (psuCount >= requiredCount)
1195 {
1196 return false;
1197 }
1198
1199 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1200 // that were never present. PSU inventory paths typically end with the PSU
1201 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1202 std::vector<std::string> sortedPaths;
1203 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1204 if (!psu->isPresent() && psu->getModelName().empty())
1205 {
1206 sortedPaths.push_back(psu->getInventoryPath());
1207 }
1208 });
1209 std::sort(sortedPaths.begin(), sortedPaths.end());
1210
1211 // Check if specified PSU is close enough to start of list to be required
1212 for (const auto& path : sortedPaths)
1213 {
1214 if (path == psu.getInventoryPath())
1215 {
1216 return true;
1217 }
1218 if (++psuCount >= requiredCount)
1219 {
1220 break;
1221 }
1222 }
1223
1224 // PSU was not close to start of sorted list; assume not required
1225 return false;
1226}
1227
Adriana Kobylak523704d2021-09-21 15:55:41 +00001228bool PSUManager::validateModelName(
1229 std::string& model, std::map<std::string, std::string>& additionalData)
1230{
1231 // Check that all PSUs have the same model name. Initialize the model
1232 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001233 // against the rest of the PSUs and get its inventory path to use as callout
1234 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001235 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001236 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001237 for (const auto& psu : psus)
1238 {
1239 auto psuModel = psu->getModelName();
1240 if (psuModel.empty())
1241 {
1242 continue;
1243 }
1244 if (model.empty())
1245 {
1246 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001247 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001248 continue;
1249 }
1250 if (psuModel != model)
1251 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001252 if (supportedConfigs.find(model) != supportedConfigs.end())
1253 {
1254 // The base model is supported, callout the mismatched PSU. The
1255 // mismatched PSU may or may not be supported.
1256 additionalData["EXPECTED_MODEL"] = model;
1257 additionalData["ACTUAL_MODEL"] = psuModel;
1258 additionalData["CALLOUT_INVENTORY_PATH"] =
1259 psu->getInventoryPath();
1260 }
1261 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1262 {
1263 // The base model is not supported, but the mismatched PSU is,
1264 // callout the base PSU.
1265 additionalData["EXPECTED_MODEL"] = psuModel;
1266 additionalData["ACTUAL_MODEL"] = model;
1267 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1268 }
1269 else
1270 {
1271 // The base model and the mismatched PSU are not supported or
1272 // could not be found in the supported configuration, callout
1273 // the mismatched PSU.
1274 additionalData["EXPECTED_MODEL"] = model;
1275 additionalData["ACTUAL_MODEL"] = psuModel;
1276 additionalData["CALLOUT_INVENTORY_PATH"] =
1277 psu->getInventoryPath();
1278 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001279 model.clear();
1280 return false;
1281 }
1282 }
1283 return true;
1284}
1285
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001286void PSUManager::setPowerConfigGPIO()
1287{
1288 if (!powerConfigGPIO)
1289 {
1290 return;
1291 }
1292
1293 std::string model{};
1294 std::map<std::string, std::string> additionalData;
1295 if (!validateModelName(model, additionalData))
1296 {
1297 return;
1298 }
1299
1300 auto config = supportedConfigs.find(model);
1301 if (config != supportedConfigs.end())
1302 {
1303 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1304 // if the supported configuration indicates that this system model
1305 // expects the maximum number of power supplies (full load set to true).
1306 // Else, set it to high (1), this is the default.
1307 auto powerConfigValue =
1308 (config->second.powerConfigFullLoad == true ? 0 : 1);
1309 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1310 powerConfigGPIO->write(powerConfigValue, flags);
1311 }
1312}
1313
Faisal Awadab66ae502023-04-01 18:30:32 -05001314void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
1315{
1316 namespace fs = std::filesystem;
1317 std::stringstream ss;
1318 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
Patrick Williamsf5402192024-08-16 15:20:53 -04001319 std::string symLinkPath =
1320 deviceDirPath + std::to_string(i2cbus) + "-" + ss.str() + driverDirName;
Faisal Awadab66ae502023-04-01 18:30:32 -05001321 try
1322 {
1323 fs::path linkStrPath = fs::read_symlink(symLinkPath);
1324 driverName = linkStrPath.filename();
1325 }
1326 catch (const std::exception& e)
1327 {
Shawn McCarney768d2262024-07-09 15:02:59 -05001328 log<level::ERR>(std::format("Failed to find device driver {}, error {}",
Faisal Awadab66ae502023-04-01 18:30:32 -05001329 symLinkPath, e.what())
1330 .c_str());
1331 }
1332}
Faisal Awadab7131a12023-10-26 19:38:45 -05001333
1334void PSUManager::populateDriverName()
1335{
1336 std::string driverName;
1337 // Search in PSUs for driver name
1338 std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
1339 if (!psu->getDriverName().empty())
1340 {
1341 driverName = psu->getDriverName();
1342 }
1343 });
1344 // Assign driver name to all PSUs
1345 std::for_each(psus.begin(), psus.end(),
1346 [=](auto& psu) { psu->setDriverName(driverName); });
1347}
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001348} // namespace phosphor::power::manager