blob: 242632bdf69e6c796666902670de1f193e607a9b [file] [log] [blame]
Brandon Wyman18a24d92022-04-19 22:48:34 +00001#include "config.h"
2
Brandon Wymana0f33ce2019-10-17 18:32:29 -05003#include "psu_manager.hpp"
4
5#include "utility.hpp"
6
Brandon Wymanb76ab242020-09-16 18:06:06 -05007#include <fmt/format.h>
8#include <sys/types.h>
9#include <unistd.h>
10
Shawn McCarney9252b7e2022-06-10 12:47:38 -050011#include <algorithm>
Brandon Wymanecbecbc2021-08-31 22:53:21 +000012#include <regex>
Shawn McCarney9252b7e2022-06-10 12:47:38 -050013#include <set>
Brandon Wymanecbecbc2021-08-31 22:53:21 +000014
Brandon Wymanaed1f752019-11-25 18:10:52 -060015using namespace phosphor::logging;
16
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
Brandon Wymanc9e840e2022-05-10 20:48:41 +000035constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
Brandon Wyman18a24d92022-04-19 22:48:34 +000036
Patrick Williams7354ce62022-07-22 19:26:56 -050037PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
Adriana Kobylakc9b05732022-03-19 15:15:10 +000038 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
Brandon Wymanc3324422022-03-24 20:30:57 +000039 objectManager(bus, objectManagerObjPath),
40 historyManager(bus, "/org/open_power/sensors")
Brandon Wyman510acaa2020-11-05 18:32:04 -060041{
Brandon Wyman510acaa2020-11-05 18:32:04 -060042 // Subscribe to InterfacesAdded before doing a property read, otherwise
43 // the interface could be created after the read attempt but before the
44 // match is created.
45 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
46 bus,
47 sdbusplus::bus::match::rules::interfacesAdded() +
48 sdbusplus::bus::match::rules::sender(
49 "xyz.openbmc_project.EntityManager"),
50 std::bind(&PSUManager::entityManagerIfaceAdded, this,
51 std::placeholders::_1));
52 getPSUConfiguration();
53 getSystemProperties();
54
Adriana Kobylakc9b05732022-03-19 15:15:10 +000055 // Request the bus name before the analyze() function, which is the one that
56 // determines the brownout condition and sets the status d-bus property.
57 bus.request_name(managerBusName);
58
Brandon Wyman510acaa2020-11-05 18:32:04 -060059 using namespace sdeventplus;
60 auto interval = std::chrono::milliseconds(1000);
61 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
62 e, std::bind(&PSUManager::analyze, this), interval);
63
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +000064 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
65 e, std::bind(&PSUManager::validateConfig, this));
66
Adriana Kobylakc0a07582021-10-13 15:52:25 +000067 try
68 {
69 powerConfigGPIO = createGPIO("power-config-full-load");
70 }
71 catch (const std::exception& e)
72 {
73 // Ignore error, GPIO may not be implemented in this system.
74 powerConfigGPIO = nullptr;
75 }
76
Brandon Wyman510acaa2020-11-05 18:32:04 -060077 // Subscribe to power state changes
78 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
79 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
80 bus,
81 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
82 POWER_IFACE),
83 [this](auto& msg) { this->powerStateChanged(msg); });
84
85 initialize();
86}
87
Jim Wrightaca86d02022-06-10 12:01:39 -050088void PSUManager::initialize()
89{
90 try
91 {
92 // pgood is the latest read of the chassis pgood
93 int pgood = 0;
94 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
95 powerService, bus, pgood);
96
97 // state is the latest requested power on / off transition
Jim Wright5c186c82022-11-17 17:09:33 -060098 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
Jim Wrightaca86d02022-06-10 12:01:39 -050099 POWER_IFACE, "getPowerState");
100 auto reply = bus.call(method);
101 int state = 0;
102 reply.read(state);
103
104 if (state)
105 {
106 // Monitor PSUs anytime state is on
107 powerOn = true;
108 // In the power fault window if pgood is off
109 powerFaultOccurring = !pgood;
110 validationTimer->restartOnce(validationTimeout);
111 }
112 else
113 {
114 // Power is off
115 powerOn = false;
116 powerFaultOccurring = false;
117 runValidateConfig = true;
118 }
119 }
120 catch (const std::exception& e)
121 {
122 log<level::INFO>(
123 fmt::format(
124 "Failed to get power state, assuming it is off, error {}",
125 e.what())
126 .c_str());
127 powerOn = false;
128 powerFaultOccurring = false;
129 runValidateConfig = true;
130 }
131
132 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
133 clearFaults();
134 updateMissingPSUs();
Jim Wrightaca86d02022-06-10 12:01:39 -0500135 setPowerConfigGPIO();
136
137 log<level::INFO>(
138 fmt::format("initialize: power on: {}, power fault occurring: {}",
139 powerOn, powerFaultOccurring)
140 .c_str());
141}
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.
165 auto properties =
166 getAllProperties(bus, path, IBMCFFPSInterface, service);
167
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.
175 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
176 }
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
223 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
224
B. J. Wyman681b2a32021-04-20 22:31:22 +0000225 if (nullptr != preslineptr)
226 {
227 presline = *preslineptr;
228 }
229
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000230 auto invMatch =
231 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
232 return psu->getInventoryPath() == invpath;
233 });
234 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
Brandon Wymanc3324422022-03-24 20:30:57 +0000245 constexpr auto driver = "ibm-cffps";
B. J. Wyman681b2a32021-04-20 22:31:22 +0000246 log<level::DEBUG>(
Brandon Wymanc3324422022-03-24 20:30:57 +0000247 fmt::format(
248 "make PowerSupply bus: {} addr: {} driver: {} presline: {}",
249 *i2cbus, *i2caddr, driver, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000250 .c_str());
251 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
Brandon Wymanc3324422022-03-24 20:30:57 +0000252 *i2caddr, driver, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600253 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000254
255 // Subscribe to power supply presence changes
256 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
257 bus,
258 sdbusplus::bus::match::rules::propertiesChanged(invpath,
259 INVENTORY_IFACE),
260 [this](auto& msg) { this->presenceChanged(msg); });
261 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600262 }
263
264 if (psus.empty())
265 {
266 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
267 }
268}
269
Adriana Kobylake1074d82021-03-16 20:46:44 +0000270void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
271{
272 try
273 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000274 auto propIt = properties.find("SupportedType");
275 if (propIt == properties.end())
276 {
277 return;
278 }
279 const std::string* type = std::get_if<std::string>(&(propIt->second));
280 if ((type == nullptr) || (*type != "PowerSupply"))
281 {
282 return;
283 }
284
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000285 propIt = properties.find("SupportedModel");
286 if (propIt == properties.end())
287 {
288 return;
289 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000290 const std::string* model = std::get_if<std::string>(&(propIt->second));
291 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000292 {
293 return;
294 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000295
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000296 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000297 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000298 if (propIt != properties.end())
299 {
300 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
301 if (count != nullptr)
302 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000303 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000304 }
305 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000306 propIt = properties.find("InputVoltage");
307 if (propIt != properties.end())
308 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000309 const std::vector<uint64_t>* voltage =
310 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000311 if (voltage != nullptr)
312 {
313 sys.inputVoltage = *voltage;
314 }
315 }
316
Adriana Kobylak886574c2021-11-01 18:22:28 +0000317 // The PowerConfigFullLoad is an optional property, default it to false
318 // since that's the default value of the power-config-full-load GPIO.
319 sys.powerConfigFullLoad = false;
320 propIt = properties.find("PowerConfigFullLoad");
321 if (propIt != properties.end())
322 {
323 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
324 if (fullLoad != nullptr)
325 {
326 sys.powerConfigFullLoad = *fullLoad;
327 }
328 }
329
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000330 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000331 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500332 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000333 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000334}
335
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600336void PSUManager::getSystemProperties()
337{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600338
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600339 try
340 {
341 util::DbusSubtree subtree =
342 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000343 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600344 {
345 throw std::runtime_error("Supported Configuration Not Found");
346 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600347
Adriana Kobylake1074d82021-03-16 20:46:44 +0000348 for (const auto& [objPath, services] : subtree)
349 {
350 std::string service = services.begin()->first;
351 if (objPath.empty() || service.empty())
352 {
353 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600354 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000355 auto properties = util::getAllProperties(
356 bus, objPath, supportedConfIntf, service);
357 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600358 }
359 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500360 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600361 {
362 // Interface or property not found. Let the Interfaces Added callback
363 // process the information once the interfaces are added to D-Bus.
364 }
365}
366
Patrick Williams7354ce62022-07-22 19:26:56 -0500367void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600368{
369 try
370 {
371 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000372 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600373 interfaces;
374 msg.read(objPath, interfaces);
375
376 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600377 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600378 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600379 populateSysProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000380 updateMissingPSUs();
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600381 }
382
Brandon Wyman510acaa2020-11-05 18:32:04 -0600383 itIntf = interfaces.find(IBMCFFPSInterface);
384 if (itIntf != interfaces.cend())
385 {
386 log<level::INFO>(
387 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
388 .c_str());
389 getPSUProperties(itIntf->second);
Brandon Wymanf477eb72022-07-28 16:30:54 +0000390 updateMissingPSUs();
Brandon Wyman510acaa2020-11-05 18:32:04 -0600391 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000392
393 // Call to validate the psu configuration if the power is on and both
394 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
395 // processed
396 if (powerOn && !psus.empty() && !supportedConfigs.empty())
397 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000398 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000399 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600400 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500401 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600402 {
403 // Ignore, the property may be of a different type than expected.
404 }
405}
406
Patrick Williams7354ce62022-07-22 19:26:56 -0500407void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500408{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500409 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500410 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500411 msg.read(msgSensor, msgData);
412
Jim Wrightaca86d02022-06-10 12:01:39 -0500413 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500414 auto valPropMap = msgData.find("state");
415 if (valPropMap != msgData.end())
416 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500417 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500418 if (state)
419 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500420 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500421 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500422 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000423 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500424 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000425 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000426 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500427 }
428 else
429 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500430 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500431 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500432 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000433 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500434 }
435 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500436
437 // Check if it was the pgood property that changed.
438 valPropMap = msgData.find("pgood");
439 if (valPropMap != msgData.end())
440 {
441 int pgood = std::get<int>(valPropMap->second);
442 if (!pgood)
443 {
444 // Chassis power good has turned off
445 if (powerOn)
446 {
447 // pgood is off but state is on, in power fault window
448 powerFaultOccurring = true;
449 }
450 }
451 }
452 log<level::INFO>(
453 fmt::format(
454 "powerStateChanged: power on: {}, power fault occurring: {}",
455 powerOn, powerFaultOccurring)
456 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500457}
458
Patrick Williams7354ce62022-07-22 19:26:56 -0500459void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000460{
461 std::string msgSensor;
462 std::map<std::string, std::variant<uint32_t, bool>> msgData;
463 msg.read(msgSensor, msgData);
464
465 // Check if it was the Present property that changed.
466 auto valPropMap = msgData.find(PRESENT_PROP);
467 if (valPropMap != msgData.end())
468 {
469 if (std::get<bool>(valPropMap->second))
470 {
471 // A PSU became present, force the PSU validation to run.
472 runValidateConfig = true;
473 validationTimer->restartOnce(validationTimeout);
474 }
475 }
476}
477
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000478void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
479{
480 using namespace sdbusplus::xyz::openbmc_project;
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000481 constexpr auto method = "setPowerSupplyError";
482
483 try
484 {
485 // Call D-Bus method to inform pseq of PSU error
Jim Wright5c186c82022-11-17 17:09:33 -0600486 auto methodMsg = bus.new_method_call(
487 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000488 methodMsg.append(psuErrorString);
489 auto callReply = bus.call(methodMsg);
490 }
491 catch (const std::exception& e)
492 {
493 log<level::INFO>(
494 fmt::format("Failed calling setPowerSupplyError due to error {}",
495 e.what())
496 .c_str());
497 }
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
Brandon Wymanb76ab242020-09-16 18:06:06 -0500512 auto service =
513 util::getService(loggingObjectPath, loggingCreateInterface, bus);
514
515 if (service.empty())
516 {
517 log<level::ERR>("Unable to get logging manager service");
518 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 {
532 log<level::ERR>(
533 fmt::format(
534 "Failed creating event log for fault {} due to error {}",
535 faultName, e.what())
536 .c_str());
537 }
538}
539
Brandon Wyman18a24d92022-04-19 22:48:34 +0000540void PSUManager::syncHistory()
541{
542 log<level::INFO>("Synchronize INPUT_HISTORY");
543
544 if (!syncHistoryGPIO)
545 {
546 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
547 }
548 if (syncHistoryGPIO)
549 {
550 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
551 syncHistoryGPIO->toggleLowHigh(delay);
552 for (auto& psu : psus)
553 {
554 psu->clearSyncHistoryRequired();
555 }
556 }
557
558 log<level::INFO>("Synchronize INPUT_HISTORY completed");
559}
560
Jim Wright4ab86562022-11-18 14:05:46 -0600561bool PSUManager::isBrownout(std::map<std::string, std::string>& additionalData)
562{
563 size_t presentCount = 0;
564 size_t notPresentCount = 0;
565 size_t acFailedCount = 0;
566 size_t pgoodFailedCount = 0;
567 for (const auto& psu : psus)
568 {
569 if (psu->isPresent())
570 {
571 ++presentCount;
572 if (psu->hasACFault())
573 {
574 ++acFailedCount;
575 }
576 else if (psu->hasPgoodFault())
577 {
578 ++pgoodFailedCount;
579 }
580 }
581 else
582 {
583 ++notPresentCount;
584 }
585 }
586
587 // In brownout if at least one PS has seen an AC fail and all present PSUs
588 // have an AC or pgood failure. Note an AC fail is only set if at least one
589 // PSU is present.
590 bool isBrownout =
591 acFailedCount && (presentCount == (acFailedCount + pgoodFailedCount));
592 if (isBrownout)
593 {
594 additionalData.emplace("NOT_PRESENT_COUNT",
595 std::to_string(notPresentCount));
596 additionalData.emplace("VIN_FAULT_COUNT",
597 std::to_string(acFailedCount));
598 additionalData.emplace("PGOOD_FAULT_COUNT",
599 std::to_string(pgoodFailedCount));
600 }
601 return isBrownout;
602}
603
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500604void PSUManager::analyze()
605{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000606 auto syncHistoryRequired =
607 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
608 return psu->isSyncHistoryRequired();
609 });
610 if (syncHistoryRequired)
611 {
612 syncHistory();
613 }
614
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500615 for (auto& psu : psus)
616 {
617 psu->analyze();
618 }
619
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000620 std::map<std::string, std::string> additionalData;
621
Jim Wright4ab86562022-11-18 14:05:46 -0600622 // Only issue brownout failure if chassis pgood has failed and PSUs indicate
623 // AC failure
624 if (powerFaultOccurring && isBrownout(additionalData))
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000625 {
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000626 setBrownout(additionalData);
627 }
628 else
629 {
630 // Brownout condition is not present or has been cleared
631 clearBrownout();
632 }
633
Jim Wrightcefe85f2022-11-18 09:57:47 -0600634 // Only perform individual PSU analysis if power is on and a brownout has
635 // not already been logged
636 if (powerOn && !brownoutLogged)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500637 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600638 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500639 {
Brandon Wymanec0b8dc2021-10-08 21:49:43 +0000640 additionalData.clear();
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000641
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600642 if (!psu->isFaultLogged() && !psu->isPresent())
643 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000644 std::map<std::string, std::string> requiredPSUsData;
645 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500646 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000647 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000648 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000649 // Create error for power supply missing.
650 additionalData["CALLOUT_INVENTORY_PATH"] =
651 psu->getInventoryPath();
652 additionalData["CALLOUT_PRIORITY"] = "H";
653 createError(
654 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
655 additionalData);
656 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600657 psu->setFaultLogged();
658 }
659 else if (!psu->isFaultLogged() && psu->isFaulted())
660 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000661 // Add STATUS_WORD and STATUS_MFR last response, in padded
662 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600663 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000664 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600665 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000666 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600667 // If there are faults being reported, they possibly could be
668 // related to a bug in the firmware version running on the power
669 // supply. Capture that data into the error as well.
670 additionalData["FW_VERSION"] = psu->getFWVersion();
671
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000672 if (psu->hasCommFault())
673 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000674 additionalData["STATUS_CML"] =
675 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000676 /* Attempts to communicate with the power supply have
677 * reached there limit. Create an error. */
678 additionalData["CALLOUT_DEVICE_PATH"] =
679 psu->getDevicePath();
680
681 createError(
682 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
683 additionalData);
684
685 psu->setFaultLogged();
686 }
687 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600688 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000689 // Include STATUS_INPUT for input faults.
690 additionalData["STATUS_INPUT"] =
691 fmt::format("{:#02x}", psu->getStatusInput());
692
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600693 /* The power supply location might be needed if the input
694 * fault is due to a problem with the power supply itself.
695 * Include the inventory path with a call out priority of
696 * low.
697 */
698 additionalData["CALLOUT_INVENTORY_PATH"] =
699 psu->getInventoryPath();
700 additionalData["CALLOUT_PRIORITY"] = "L";
701 createError("xyz.openbmc_project.Power.PowerSupply.Error."
702 "InputFault",
703 additionalData);
704 psu->setFaultLogged();
705 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000706 else if (psu->hasPSKillFault())
707 {
708 createError(
709 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
710 additionalData);
711 psu->setFaultLogged();
712 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000713 else if (psu->hasVoutOVFault())
714 {
715 // Include STATUS_VOUT for Vout faults.
716 additionalData["STATUS_VOUT"] =
717 fmt::format("{:#02x}", psu->getStatusVout());
718
719 additionalData["CALLOUT_INVENTORY_PATH"] =
720 psu->getInventoryPath();
721
722 createError(
723 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
724 additionalData);
725
726 psu->setFaultLogged();
727 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000728 else if (psu->hasIoutOCFault())
729 {
730 // Include STATUS_IOUT for Iout faults.
731 additionalData["STATUS_IOUT"] =
732 fmt::format("{:#02x}", psu->getStatusIout());
733
734 createError(
735 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
736 additionalData);
737
738 psu->setFaultLogged();
739 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000740 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
741 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000742 {
743 // Include STATUS_VOUT for Vout faults.
744 additionalData["STATUS_VOUT"] =
745 fmt::format("{:#02x}", psu->getStatusVout());
746
747 additionalData["CALLOUT_INVENTORY_PATH"] =
748 psu->getInventoryPath();
749
750 createError(
751 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
752 additionalData);
753
754 psu->setFaultLogged();
755 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000756 // A fan fault should have priority over a temperature fault,
757 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500758 // Only process if not in power fault window.
759 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000760 {
761 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
762 additionalData["STATUS_TEMPERATURE"] =
763 fmt::format("{:#02x}", psu->getStatusTemperature());
764 additionalData["STATUS_FANS_1_2"] =
765 fmt::format("{:#02x}", psu->getStatusFans12());
766
767 additionalData["CALLOUT_INVENTORY_PATH"] =
768 psu->getInventoryPath();
769
770 createError(
771 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
772 additionalData);
773
774 psu->setFaultLogged();
775 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000776 else if (psu->hasTempFault())
777 {
778 // Include STATUS_TEMPERATURE for temperature faults.
779 additionalData["STATUS_TEMPERATURE"] =
780 fmt::format("{:#02x}", psu->getStatusTemperature());
781
782 additionalData["CALLOUT_INVENTORY_PATH"] =
783 psu->getInventoryPath();
784
785 createError(
786 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
787 additionalData);
788
789 psu->setFaultLogged();
790 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600791 else if (psu->hasMFRFault())
792 {
793 /* This can represent a variety of faults that result in
794 * calling out the power supply for replacement: Output
795 * OverCurrent, Output Under Voltage, and potentially other
796 * faults.
797 *
798 * Also plan on putting specific fault in AdditionalData,
799 * along with register names and register values
800 * (STATUS_WORD, STATUS_MFR, etc.).*/
801
802 additionalData["CALLOUT_INVENTORY_PATH"] =
803 psu->getInventoryPath();
804
805 createError(
806 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500807 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500808
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600809 psu->setFaultLogged();
810 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500811 // Only process if not in power fault window.
812 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000813 {
814 /* POWER_GOOD# is not low, or OFF is on */
815 additionalData["CALLOUT_INVENTORY_PATH"] =
816 psu->getInventoryPath();
817
818 createError(
819 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
820 additionalData);
821
822 psu->setFaultLogged();
823 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500824 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500825 }
826 }
827}
828
Brandon Wyman64e97752022-06-03 23:50:13 +0000829void PSUManager::updateMissingPSUs()
830{
831 if (supportedConfigs.empty() || psus.empty())
832 {
833 return;
834 }
835
836 // Power supplies default to missing. If the power supply is present,
837 // the PowerSupply object will update the inventory Present property to
838 // true. If we have less than the required number of power supplies, and
839 // this power supply is missing, update the inventory Present property
840 // to false to indicate required power supply is missing. Avoid
841 // indicating power supply missing if not required.
842
843 auto presentCount =
844 std::count_if(psus.begin(), psus.end(),
845 [](const auto& psu) { return psu->isPresent(); });
846
847 for (const auto& config : supportedConfigs)
848 {
849 for (const auto& psu : psus)
850 {
851 auto psuModel = psu->getModelName();
852 auto psuShortName = psu->getShortName();
853 auto psuInventoryPath = psu->getInventoryPath();
854 auto relativeInvPath =
855 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
856 auto psuPresent = psu->isPresent();
857 auto presProperty = false;
858 auto propReadFail = false;
859
860 try
861 {
862 presProperty = getPresence(bus, psuInventoryPath);
863 propReadFail = false;
864 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500865 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000866 {
867 propReadFail = true;
868 // Relying on property change or interface added to retry.
869 // Log an informational trace to the journal.
870 log<level::INFO>(
871 fmt::format("D-Bus property {} access failure exception",
872 psuInventoryPath)
873 .c_str());
874 }
875
876 if (psuModel.empty())
877 {
878 if (!propReadFail && (presProperty != psuPresent))
879 {
880 // We already have this property, and it is not false
881 // set Present to false
882 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
883 }
884 continue;
885 }
886
887 if (config.first != psuModel)
888 {
889 continue;
890 }
891
892 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
893 {
894 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
895 }
896 }
897 }
898}
899
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000900void PSUManager::validateConfig()
901{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000902 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000903 {
904 return;
905 }
906
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000907 for (const auto& psu : psus)
908 {
909 if ((psu->hasInputFault() || psu->hasVINUVFault()))
910 {
911 // Do not try to validate if input voltage fault present.
912 validationTimer->restartOnce(validationTimeout);
913 return;
914 }
915 }
916
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000917 std::map<std::string, std::string> additionalData;
918 auto supported = hasRequiredPSUs(additionalData);
919 if (supported)
920 {
921 runValidateConfig = false;
922 return;
923 }
924
925 // Validation failed, create an error log.
926 // Return without setting the runValidateConfig flag to false because
927 // it may be that an additional supported configuration interface is
928 // added and we need to validate it to see if it matches this system.
929 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
930 additionalData);
931}
932
933bool PSUManager::hasRequiredPSUs(
934 std::map<std::string, std::string>& additionalData)
935{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000936 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000937 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000938 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000939 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000940 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000941
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000942 auto presentCount =
943 std::count_if(psus.begin(), psus.end(),
944 [](const auto& psu) { return psu->isPresent(); });
945
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000946 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000947 // power supply model configuration. Since all configurations need to be
948 // checked, the additional data would contain only the information of the
949 // last configuration that did not match.
950 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000951 for (const auto& config : supportedConfigs)
952 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000953 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000954 {
955 continue;
956 }
Brandon Wyman64e97752022-06-03 23:50:13 +0000957
Jim Wright941b60d2022-10-19 16:22:17 -0500958 // Number of power supplies present should equal or exceed the expected
959 // count
960 if (presentCount < config.second.powerSupplyCount)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000961 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000962 tmpAdditionalData.clear();
963 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000964 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000965 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000966 continue;
967 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000968
969 bool voltageValidated = true;
970 for (const auto& psu : psus)
971 {
972 if (!psu->isPresent())
973 {
974 // Only present PSUs report a valid input voltage
975 continue;
976 }
977
978 double actualInputVoltage;
979 int inputVoltage;
980 psu->getInputVoltage(actualInputVoltage, inputVoltage);
981
982 if (std::find(config.second.inputVoltage.begin(),
983 config.second.inputVoltage.end(),
984 inputVoltage) == config.second.inputVoltage.end())
985 {
986 tmpAdditionalData.clear();
987 tmpAdditionalData["ACTUAL_VOLTAGE"] =
988 std::to_string(actualInputVoltage);
989 for (const auto& voltage : config.second.inputVoltage)
990 {
991 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
992 std::to_string(voltage) + " ";
993 }
994 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
995 psu->getInventoryPath();
996
997 voltageValidated = false;
998 break;
999 }
1000 }
1001 if (!voltageValidated)
1002 {
1003 continue;
1004 }
1005
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001006 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001007 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +00001008
Adriana Kobylak4175ffb2021-08-02 14:51:05 +00001009 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +00001010 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +00001011}
1012
Shawn McCarney9252b7e2022-06-10 12:47:38 -05001013unsigned int PSUManager::getRequiredPSUCount()
1014{
1015 unsigned int requiredCount{0};
1016
1017 // Verify we have the supported configuration and PSU information
1018 if (!supportedConfigs.empty() && !psus.empty())
1019 {
1020 // Find PSU models. They should all be the same.
1021 std::set<std::string> models{};
1022 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1023 if (!psu->getModelName().empty())
1024 {
1025 models.insert(psu->getModelName());
1026 }
1027 });
1028
1029 // If exactly one model was found, find corresponding configuration
1030 if (models.size() == 1)
1031 {
1032 const std::string& model = *(models.begin());
1033 auto it = supportedConfigs.find(model);
1034 if (it != supportedConfigs.end())
1035 {
1036 requiredCount = it->second.powerSupplyCount;
1037 }
1038 }
1039 }
1040
1041 return requiredCount;
1042}
1043
1044bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1045{
1046 // Get required number of PSUs; if not found, we don't know if PSU required
1047 unsigned int requiredCount = getRequiredPSUCount();
1048 if (requiredCount == 0)
1049 {
1050 return false;
1051 }
1052
1053 // If total PSU count <= the required count, all PSUs are required
1054 if (psus.size() <= requiredCount)
1055 {
1056 return true;
1057 }
1058
1059 // We don't currently get information from EntityManager about which PSUs
1060 // are required, so we have to do some guesswork. First check if this PSU
1061 // is present. If so, assume it is required.
1062 if (psu.isPresent())
1063 {
1064 return true;
1065 }
1066
1067 // This PSU is not present. Count the number of other PSUs that are
1068 // present. If enough other PSUs are present, assume the specified PSU is
1069 // not required.
1070 unsigned int psuCount =
1071 std::count_if(psus.begin(), psus.end(),
1072 [](const auto& psu) { return psu->isPresent(); });
1073 if (psuCount >= requiredCount)
1074 {
1075 return false;
1076 }
1077
1078 // Check if this PSU was previously present. If so, assume it is required.
1079 // We know it was previously present if it has a non-empty model name.
1080 if (!psu.getModelName().empty())
1081 {
1082 return true;
1083 }
1084
1085 // This PSU was never present. Count the number of other PSUs that were
1086 // previously present. If including those PSUs is enough, assume the
1087 // specified PSU is not required.
1088 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1089 return (!psu->isPresent() && !psu->getModelName().empty());
1090 });
1091 if (psuCount >= requiredCount)
1092 {
1093 return false;
1094 }
1095
1096 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1097 // that were never present. PSU inventory paths typically end with the PSU
1098 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1099 std::vector<std::string> sortedPaths;
1100 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1101 if (!psu->isPresent() && psu->getModelName().empty())
1102 {
1103 sortedPaths.push_back(psu->getInventoryPath());
1104 }
1105 });
1106 std::sort(sortedPaths.begin(), sortedPaths.end());
1107
1108 // Check if specified PSU is close enough to start of list to be required
1109 for (const auto& path : sortedPaths)
1110 {
1111 if (path == psu.getInventoryPath())
1112 {
1113 return true;
1114 }
1115 if (++psuCount >= requiredCount)
1116 {
1117 break;
1118 }
1119 }
1120
1121 // PSU was not close to start of sorted list; assume not required
1122 return false;
1123}
1124
Adriana Kobylak523704d2021-09-21 15:55:41 +00001125bool PSUManager::validateModelName(
1126 std::string& model, std::map<std::string, std::string>& additionalData)
1127{
1128 // Check that all PSUs have the same model name. Initialize the model
1129 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001130 // against the rest of the PSUs and get its inventory path to use as callout
1131 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001132 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001133 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001134 for (const auto& psu : psus)
1135 {
1136 auto psuModel = psu->getModelName();
1137 if (psuModel.empty())
1138 {
1139 continue;
1140 }
1141 if (model.empty())
1142 {
1143 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001144 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001145 continue;
1146 }
1147 if (psuModel != model)
1148 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001149 if (supportedConfigs.find(model) != supportedConfigs.end())
1150 {
1151 // The base model is supported, callout the mismatched PSU. The
1152 // mismatched PSU may or may not be supported.
1153 additionalData["EXPECTED_MODEL"] = model;
1154 additionalData["ACTUAL_MODEL"] = psuModel;
1155 additionalData["CALLOUT_INVENTORY_PATH"] =
1156 psu->getInventoryPath();
1157 }
1158 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1159 {
1160 // The base model is not supported, but the mismatched PSU is,
1161 // callout the base PSU.
1162 additionalData["EXPECTED_MODEL"] = psuModel;
1163 additionalData["ACTUAL_MODEL"] = model;
1164 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1165 }
1166 else
1167 {
1168 // The base model and the mismatched PSU are not supported or
1169 // could not be found in the supported configuration, callout
1170 // the mismatched PSU.
1171 additionalData["EXPECTED_MODEL"] = model;
1172 additionalData["ACTUAL_MODEL"] = psuModel;
1173 additionalData["CALLOUT_INVENTORY_PATH"] =
1174 psu->getInventoryPath();
1175 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001176 model.clear();
1177 return false;
1178 }
1179 }
1180 return true;
1181}
1182
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001183void PSUManager::setPowerConfigGPIO()
1184{
1185 if (!powerConfigGPIO)
1186 {
1187 return;
1188 }
1189
1190 std::string model{};
1191 std::map<std::string, std::string> additionalData;
1192 if (!validateModelName(model, additionalData))
1193 {
1194 return;
1195 }
1196
1197 auto config = supportedConfigs.find(model);
1198 if (config != supportedConfigs.end())
1199 {
1200 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1201 // if the supported configuration indicates that this system model
1202 // expects the maximum number of power supplies (full load set to true).
1203 // Else, set it to high (1), this is the default.
1204 auto powerConfigValue =
1205 (config->second.powerConfigFullLoad == true ? 0 : 1);
1206 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1207 powerConfigGPIO->write(powerConfigValue, flags);
1208 }
1209}
1210
Adriana Kobylake5b1e082022-03-02 15:37:32 +00001211void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData)
1212{
1213 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
1214 server::PowerSystemInputs::Status::Fault);
1215 if (!brownoutLogged)
1216 {
1217 if (powerOn)
1218 {
1219 createError(
1220 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
1221 additionalData);
1222 brownoutLogged = true;
1223 }
1224 }
1225}
1226
1227void PSUManager::clearBrownout()
1228{
1229 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
1230 server::PowerSystemInputs::Status::Good);
1231 brownoutLogged = false;
1232}
1233
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001234} // namespace phosphor::power::manager