blob: ebc953f8ab59c626c4b529987421042309a5158d [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
98 auto method = bus.new_method_call(POWER_IFACE, POWER_OBJ_PATH,
99 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();
135 updateInventory();
136 setPowerConfigGPIO();
137
138 log<level::INFO>(
139 fmt::format("initialize: power on: {}, power fault occurring: {}",
140 powerOn, powerFaultOccurring)
141 .c_str());
142}
143
Brandon Wyman510acaa2020-11-05 18:32:04 -0600144void PSUManager::getPSUConfiguration()
145{
146 using namespace phosphor::power::util;
147 auto depth = 0;
148 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
149
150 psus.clear();
151
152 // I should get a map of objects back.
153 // Each object will have a path, a service, and an interface.
154 // The interface should match the one passed into this function.
155 for (const auto& [path, services] : objects)
156 {
157 auto service = services.begin()->first;
158
159 if (path.empty() || service.empty())
160 {
161 continue;
162 }
163
164 // For each object in the array of objects, I want to get properties
165 // from the service, path, and interface.
166 auto properties =
167 getAllProperties(bus, path, IBMCFFPSInterface, service);
168
169 getPSUProperties(properties);
170 }
171
172 if (psus.empty())
173 {
174 // Interface or properties not found. Let the Interfaces Added callback
175 // process the information once the interfaces are added to D-Bus.
176 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
177 }
178}
179
180void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
181{
182 // From passed in properties, I want to get: I2CBus, I2CAddress,
183 // and Name. Create a power supply object, using Name to build the inventory
184 // path.
185 const auto basePSUInvPath =
186 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
187 uint64_t* i2cbus = nullptr;
188 uint64_t* i2caddr = nullptr;
189 std::string* psuname = nullptr;
B. J. Wyman681b2a32021-04-20 22:31:22 +0000190 std::string* preslineptr = nullptr;
Brandon Wyman510acaa2020-11-05 18:32:04 -0600191
192 for (const auto& property : properties)
193 {
194 try
195 {
196 if (property.first == i2cBusProp)
197 {
198 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
199 }
200 else if (property.first == i2cAddressProp)
201 {
202 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
203 }
204 else if (property.first == psuNameProp)
205 {
206 psuname = std::get_if<std::string>(&properties[psuNameProp]);
207 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000208 else if (property.first == presLineName)
209 {
210 preslineptr =
211 std::get_if<std::string>(&properties[presLineName]);
212 }
Brandon Wyman510acaa2020-11-05 18:32:04 -0600213 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500214 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000215 {}
Brandon Wyman510acaa2020-11-05 18:32:04 -0600216 }
217
218 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
219 {
220 std::string invpath = basePSUInvPath;
221 invpath.push_back(psuname->back());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000222 std::string presline = "";
Brandon Wyman510acaa2020-11-05 18:32:04 -0600223
224 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
225
B. J. Wyman681b2a32021-04-20 22:31:22 +0000226 if (nullptr != preslineptr)
227 {
228 presline = *preslineptr;
229 }
230
Brandon Wymanecbecbc2021-08-31 22:53:21 +0000231 auto invMatch =
232 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) {
233 return psu->getInventoryPath() == invpath;
234 });
235 if (invMatch != psus.end())
236 {
237 // This power supply has the same inventory path as the one with
238 // information just added to D-Bus.
239 // Changes to GPIO line name unlikely, so skip checking.
240 // Changes to the I2C bus and address unlikely, as that would
241 // require corresponding device tree updates.
242 // Return out to avoid duplicate object creation.
243 return;
244 }
245
Brandon Wymanc3324422022-03-24 20:30:57 +0000246 constexpr auto driver = "ibm-cffps";
B. J. Wyman681b2a32021-04-20 22:31:22 +0000247 log<level::DEBUG>(
Brandon Wymanc3324422022-03-24 20:30:57 +0000248 fmt::format(
249 "make PowerSupply bus: {} addr: {} driver: {} presline: {}",
250 *i2cbus, *i2caddr, driver, presline)
B. J. Wyman681b2a32021-04-20 22:31:22 +0000251 .c_str());
252 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus,
Brandon Wymanc3324422022-03-24 20:30:57 +0000253 *i2caddr, driver, presline);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600254 psus.emplace_back(std::move(psu));
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000255
256 // Subscribe to power supply presence changes
257 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
258 bus,
259 sdbusplus::bus::match::rules::propertiesChanged(invpath,
260 INVENTORY_IFACE),
261 [this](auto& msg) { this->presenceChanged(msg); });
262 presenceMatches.emplace_back(std::move(presenceMatch));
Brandon Wyman510acaa2020-11-05 18:32:04 -0600263 }
264
265 if (psus.empty())
266 {
267 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
268 }
269}
270
Adriana Kobylake1074d82021-03-16 20:46:44 +0000271void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
272{
273 try
274 {
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000275 auto propIt = properties.find("SupportedType");
276 if (propIt == properties.end())
277 {
278 return;
279 }
280 const std::string* type = std::get_if<std::string>(&(propIt->second));
281 if ((type == nullptr) || (*type != "PowerSupply"))
282 {
283 return;
284 }
285
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000286 propIt = properties.find("SupportedModel");
287 if (propIt == properties.end())
288 {
289 return;
290 }
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000291 const std::string* model = std::get_if<std::string>(&(propIt->second));
292 if (model == nullptr)
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000293 {
294 return;
295 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000296
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000297 sys_properties sys;
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000298 propIt = properties.find("RedundantCount");
Adriana Kobylake1074d82021-03-16 20:46:44 +0000299 if (propIt != properties.end())
300 {
301 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
302 if (count != nullptr)
303 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000304 sys.powerSupplyCount = *count;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000305 }
306 }
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000307 propIt = properties.find("InputVoltage");
308 if (propIt != properties.end())
309 {
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000310 const std::vector<uint64_t>* voltage =
311 std::get_if<std::vector<uint64_t>>(&(propIt->second));
Adriana Kobylak9ea66a62021-03-24 17:54:14 +0000312 if (voltage != nullptr)
313 {
314 sys.inputVoltage = *voltage;
315 }
316 }
317
Adriana Kobylak886574c2021-11-01 18:22:28 +0000318 // The PowerConfigFullLoad is an optional property, default it to false
319 // since that's the default value of the power-config-full-load GPIO.
320 sys.powerConfigFullLoad = false;
321 propIt = properties.find("PowerConfigFullLoad");
322 if (propIt != properties.end())
323 {
324 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
325 if (fullLoad != nullptr)
326 {
327 sys.powerConfigFullLoad = *fullLoad;
328 }
329 }
330
Adriana Kobylakd3a70d92021-06-04 16:24:45 +0000331 supportedConfigs.emplace(*model, sys);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000332 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500333 catch (const std::exception& e)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000334 {}
Adriana Kobylake1074d82021-03-16 20:46:44 +0000335}
336
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600337void PSUManager::getSystemProperties()
338{
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600339
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600340 try
341 {
342 util::DbusSubtree subtree =
343 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
Adriana Kobylake1074d82021-03-16 20:46:44 +0000344 if (subtree.empty())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600345 {
346 throw std::runtime_error("Supported Configuration Not Found");
347 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600348
Adriana Kobylake1074d82021-03-16 20:46:44 +0000349 for (const auto& [objPath, services] : subtree)
350 {
351 std::string service = services.begin()->first;
352 if (objPath.empty() || service.empty())
353 {
354 continue;
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600355 }
Adriana Kobylake1074d82021-03-16 20:46:44 +0000356 auto properties = util::getAllProperties(
357 bus, objPath, supportedConfIntf, service);
358 populateSysProperties(properties);
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600359 }
360 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500361 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600362 {
363 // Interface or property not found. Let the Interfaces Added callback
364 // process the information once the interfaces are added to D-Bus.
365 }
366}
367
Patrick Williams7354ce62022-07-22 19:26:56 -0500368void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600369{
370 try
371 {
372 sdbusplus::message::object_path objPath;
Adriana Kobylake1074d82021-03-16 20:46:44 +0000373 std::map<std::string, std::map<std::string, util::DbusVariant>>
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600374 interfaces;
375 msg.read(objPath, interfaces);
376
377 auto itIntf = interfaces.find(supportedConfIntf);
Brandon Wyman510acaa2020-11-05 18:32:04 -0600378 if (itIntf != interfaces.cend())
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600379 {
Brandon Wyman510acaa2020-11-05 18:32:04 -0600380 populateSysProperties(itIntf->second);
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);
390 }
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000391
Brandon Wyman64e97752022-06-03 23:50:13 +0000392 updateMissingPSUs();
393
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000394 // Call to validate the psu configuration if the power is on and both
395 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
396 // processed
397 if (powerOn && !psus.empty() && !supportedConfigs.empty())
398 {
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000399 validationTimer->restartOnce(validationTimeout);
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000400 }
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600401 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500402 catch (const std::exception& e)
Adriana Kobylak9bab9e12021-02-24 15:32:03 -0600403 {
404 // Ignore, the property may be of a different type than expected.
405 }
406}
407
Patrick Williams7354ce62022-07-22 19:26:56 -0500408void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500409{
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500410 std::string msgSensor;
Jim Wrightaca86d02022-06-10 12:01:39 -0500411 std::map<std::string, std::variant<int>> msgData;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500412 msg.read(msgSensor, msgData);
413
Jim Wrightaca86d02022-06-10 12:01:39 -0500414 // Check if it was the state property that changed.
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500415 auto valPropMap = msgData.find("state");
416 if (valPropMap != msgData.end())
417 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500418 int state = std::get<int>(valPropMap->second);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500419 if (state)
420 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500421 // Power on requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500422 powerOn = true;
Jim Wrightaca86d02022-06-10 12:01:39 -0500423 powerFaultOccurring = false;
Adriana Kobylaka4d38fa2021-10-05 19:57:47 +0000424 validationTimer->restartOnce(validationTimeout);
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500425 clearFaults();
Brandon Wyman49b8ec42022-04-20 21:18:33 +0000426 syncHistory();
Adriana Kobylakc0a07582021-10-13 15:52:25 +0000427 setPowerConfigGPIO();
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500428 }
429 else
430 {
Jim Wrightaca86d02022-06-10 12:01:39 -0500431 // Power off requested
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500432 powerOn = false;
Jim Wrightaca86d02022-06-10 12:01:39 -0500433 powerFaultOccurring = false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000434 runValidateConfig = true;
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500435 }
436 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500437
438 // Check if it was the pgood property that changed.
439 valPropMap = msgData.find("pgood");
440 if (valPropMap != msgData.end())
441 {
442 int pgood = std::get<int>(valPropMap->second);
443 if (!pgood)
444 {
445 // Chassis power good has turned off
446 if (powerOn)
447 {
448 // pgood is off but state is on, in power fault window
449 powerFaultOccurring = true;
450 }
451 }
452 }
453 log<level::INFO>(
454 fmt::format(
455 "powerStateChanged: power on: {}, power fault occurring: {}",
456 powerOn, powerFaultOccurring)
457 .c_str());
Brandon Wymana0f33ce2019-10-17 18:32:29 -0500458}
459
Patrick Williams7354ce62022-07-22 19:26:56 -0500460void PSUManager::presenceChanged(sdbusplus::message_t& msg)
Adriana Kobylak9ba38232021-11-16 20:27:45 +0000461{
462 std::string msgSensor;
463 std::map<std::string, std::variant<uint32_t, bool>> msgData;
464 msg.read(msgSensor, msgData);
465
466 // Check if it was the Present property that changed.
467 auto valPropMap = msgData.find(PRESENT_PROP);
468 if (valPropMap != msgData.end())
469 {
470 if (std::get<bool>(valPropMap->second))
471 {
472 // A PSU became present, force the PSU validation to run.
473 runValidateConfig = true;
474 validationTimer->restartOnce(validationTimeout);
475 }
476 }
477}
478
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000479void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
480{
481 using namespace sdbusplus::xyz::openbmc_project;
482 constexpr auto service = "org.openbmc.control.Power";
483 constexpr auto objPath = "/org/openbmc/control/power0";
484 constexpr auto interface = "org.openbmc.control.Power";
485 constexpr auto method = "setPowerSupplyError";
486
487 try
488 {
489 // Call D-Bus method to inform pseq of PSU error
490 auto methodMsg =
491 bus.new_method_call(service, objPath, interface, method);
492 methodMsg.append(psuErrorString);
493 auto callReply = bus.call(methodMsg);
494 }
495 catch (const std::exception& e)
496 {
497 log<level::INFO>(
498 fmt::format("Failed calling setPowerSupplyError due to error {}",
499 e.what())
500 .c_str());
501 }
502}
503
Brandon Wyman8b662882021-10-08 17:31:51 +0000504void PSUManager::createError(const std::string& faultName,
505 std::map<std::string, std::string>& additionalData)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500506{
507 using namespace sdbusplus::xyz::openbmc_project;
508 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
509 constexpr auto loggingCreateInterface =
510 "xyz.openbmc_project.Logging.Create";
511
512 try
513 {
Brandon Wyman8b662882021-10-08 17:31:51 +0000514 additionalData["_PID"] = std::to_string(getpid());
515
Brandon Wymanb76ab242020-09-16 18:06:06 -0500516 auto service =
517 util::getService(loggingObjectPath, loggingCreateInterface, bus);
518
519 if (service.empty())
520 {
521 log<level::ERR>("Unable to get logging manager service");
522 return;
523 }
524
525 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
526 loggingCreateInterface, "Create");
527
528 auto level = Logging::server::Entry::Level::Error;
529 method.append(faultName, level, additionalData);
530
531 auto reply = bus.call(method);
Brandon Wyman10fc6e82022-02-08 20:51:22 +0000532 setPowerSupplyError(faultName);
Brandon Wymanb76ab242020-09-16 18:06:06 -0500533 }
Patrick Williamsc1d4de52021-10-06 12:45:57 -0500534 catch (const std::exception& e)
Brandon Wymanb76ab242020-09-16 18:06:06 -0500535 {
536 log<level::ERR>(
537 fmt::format(
538 "Failed creating event log for fault {} due to error {}",
539 faultName, e.what())
540 .c_str());
541 }
542}
543
Brandon Wyman18a24d92022-04-19 22:48:34 +0000544void PSUManager::syncHistory()
545{
546 log<level::INFO>("Synchronize INPUT_HISTORY");
547
548 if (!syncHistoryGPIO)
549 {
550 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
551 }
552 if (syncHistoryGPIO)
553 {
554 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
555 syncHistoryGPIO->toggleLowHigh(delay);
556 for (auto& psu : psus)
557 {
558 psu->clearSyncHistoryRequired();
559 }
560 }
561
562 log<level::INFO>("Synchronize INPUT_HISTORY completed");
563}
564
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500565void PSUManager::analyze()
566{
Brandon Wyman18a24d92022-04-19 22:48:34 +0000567 auto syncHistoryRequired =
568 std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
569 return psu->isSyncHistoryRequired();
570 });
571 if (syncHistoryRequired)
572 {
573 syncHistory();
574 }
575
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500576 for (auto& psu : psus)
577 {
578 psu->analyze();
579 }
580
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000581 std::map<std::string, std::string> additionalData;
582
583 auto notPresentCount = decltype(psus.size())(
584 std::count_if(psus.begin(), psus.end(),
585 [](const auto& psu) { return !psu->isPresent(); }));
586
587 auto hasVINUVFaultCount = decltype(psus.size())(
Brandon Wyman9bc5f762022-07-12 20:47:03 +0000588 std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
589 return (psu->isPresent() && psu->hasVINUVFault());
590 }));
Adriana Kobylake5b1e082022-03-02 15:37:32 +0000591
592 // The PSU D-Bus objects may not be available yet, so ignore if all
593 // PSUs are not present or the number of PSUs is still 0.
594 if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) &&
595 (psus.size() != notPresentCount) && (psus.size() != 0))
596 {
597 // Brownout: All PSUs report an AC failure: At least one PSU reports
598 // AC loss VIN fault and the rest either report AC loss VIN fault as
599 // well or are not present.
600 additionalData["NOT_PRESENT_COUNT"] = std::to_string(notPresentCount);
601 additionalData["VIN_FAULT_COUNT"] = std::to_string(hasVINUVFaultCount);
602 setBrownout(additionalData);
603 }
604 else
605 {
606 // Brownout condition is not present or has been cleared
607 clearBrownout();
608 }
609
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600610 if (powerOn)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500611 {
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600612 for (auto& psu : psus)
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500613 {
Brandon Wymanec0b8dc2021-10-08 21:49:43 +0000614 additionalData.clear();
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000615
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600616 if (!psu->isFaultLogged() && !psu->isPresent())
617 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000618 std::map<std::string, std::string> requiredPSUsData;
619 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500620 if (!requiredPSUsPresent && isRequiredPSU(*psu))
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000621 {
Brandon Wymanda369c72021-10-08 18:43:30 +0000622 additionalData.merge(requiredPSUsData);
Adriana Kobylakf2ba1462021-06-24 15:16:17 +0000623 // Create error for power supply missing.
624 additionalData["CALLOUT_INVENTORY_PATH"] =
625 psu->getInventoryPath();
626 additionalData["CALLOUT_PRIORITY"] = "H";
627 createError(
628 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
629 additionalData);
630 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600631 psu->setFaultLogged();
632 }
633 else if (!psu->isFaultLogged() && psu->isFaulted())
634 {
Brandon Wyman786b6f42021-10-12 20:21:41 +0000635 // Add STATUS_WORD and STATUS_MFR last response, in padded
636 // hexadecimal format.
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600637 additionalData["STATUS_WORD"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000638 fmt::format("{:#04x}", psu->getStatusWord());
Jay Meyer10d94052020-11-30 14:41:21 -0600639 additionalData["STATUS_MFR"] =
Brandon Wyman786b6f42021-10-12 20:21:41 +0000640 fmt::format("{:#02x}", psu->getMFRFault());
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600641 // If there are faults being reported, they possibly could be
642 // related to a bug in the firmware version running on the power
643 // supply. Capture that data into the error as well.
644 additionalData["FW_VERSION"] = psu->getFWVersion();
645
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000646 if (psu->hasCommFault())
647 {
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000648 additionalData["STATUS_CML"] =
649 fmt::format("{:#02x}", psu->getStatusCML());
Brandon Wymanb85b9dd2021-10-19 21:25:17 +0000650 /* Attempts to communicate with the power supply have
651 * reached there limit. Create an error. */
652 additionalData["CALLOUT_DEVICE_PATH"] =
653 psu->getDevicePath();
654
655 createError(
656 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
657 additionalData);
658
659 psu->setFaultLogged();
660 }
661 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600662 {
Brandon Wymanf07bc792021-10-12 19:00:35 +0000663 // Include STATUS_INPUT for input faults.
664 additionalData["STATUS_INPUT"] =
665 fmt::format("{:#02x}", psu->getStatusInput());
666
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600667 /* The power supply location might be needed if the input
668 * fault is due to a problem with the power supply itself.
669 * Include the inventory path with a call out priority of
670 * low.
671 */
672 additionalData["CALLOUT_INVENTORY_PATH"] =
673 psu->getInventoryPath();
674 additionalData["CALLOUT_PRIORITY"] = "L";
675 createError("xyz.openbmc_project.Power.PowerSupply.Error."
676 "InputFault",
677 additionalData);
678 psu->setFaultLogged();
679 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000680 else if (psu->hasPSKillFault())
681 {
682 createError(
683 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
684 additionalData);
685 psu->setFaultLogged();
686 }
Brandon Wyman6710ba22021-10-27 17:39:31 +0000687 else if (psu->hasVoutOVFault())
688 {
689 // Include STATUS_VOUT for Vout faults.
690 additionalData["STATUS_VOUT"] =
691 fmt::format("{:#02x}", psu->getStatusVout());
692
693 additionalData["CALLOUT_INVENTORY_PATH"] =
694 psu->getInventoryPath();
695
696 createError(
697 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
698 additionalData);
699
700 psu->setFaultLogged();
701 }
Brandon Wymanb10b3be2021-11-09 22:12:15 +0000702 else if (psu->hasIoutOCFault())
703 {
704 // Include STATUS_IOUT for Iout faults.
705 additionalData["STATUS_IOUT"] =
706 fmt::format("{:#02x}", psu->getStatusIout());
707
708 createError(
709 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
710 additionalData);
711
712 psu->setFaultLogged();
713 }
Brandon Wyman39ea02b2021-11-23 23:22:23 +0000714 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
715 psu->hasPSCS12VFault())
Brandon Wyman2cf46942021-10-28 19:09:16 +0000716 {
717 // Include STATUS_VOUT for Vout faults.
718 additionalData["STATUS_VOUT"] =
719 fmt::format("{:#02x}", psu->getStatusVout());
720
721 additionalData["CALLOUT_INVENTORY_PATH"] =
722 psu->getInventoryPath();
723
724 createError(
725 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
726 additionalData);
727
728 psu->setFaultLogged();
729 }
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000730 // A fan fault should have priority over a temperature fault,
731 // since a failed fan may lead to a temperature problem.
Jim Wrightaca86d02022-06-10 12:01:39 -0500732 // Only process if not in power fault window.
733 else if (psu->hasFanFault() && !powerFaultOccurring)
Brandon Wyman7ee4d7e2021-11-19 20:48:23 +0000734 {
735 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
736 additionalData["STATUS_TEMPERATURE"] =
737 fmt::format("{:#02x}", psu->getStatusTemperature());
738 additionalData["STATUS_FANS_1_2"] =
739 fmt::format("{:#02x}", psu->getStatusFans12());
740
741 additionalData["CALLOUT_INVENTORY_PATH"] =
742 psu->getInventoryPath();
743
744 createError(
745 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
746 additionalData);
747
748 psu->setFaultLogged();
749 }
Brandon Wyman96893a42021-11-05 19:56:57 +0000750 else if (psu->hasTempFault())
751 {
752 // Include STATUS_TEMPERATURE for temperature faults.
753 additionalData["STATUS_TEMPERATURE"] =
754 fmt::format("{:#02x}", psu->getStatusTemperature());
755
756 additionalData["CALLOUT_INVENTORY_PATH"] =
757 psu->getInventoryPath();
758
759 createError(
760 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
761 additionalData);
762
763 psu->setFaultLogged();
764 }
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600765 else if (psu->hasMFRFault())
766 {
767 /* This can represent a variety of faults that result in
768 * calling out the power supply for replacement: Output
769 * OverCurrent, Output Under Voltage, and potentially other
770 * faults.
771 *
772 * Also plan on putting specific fault in AdditionalData,
773 * along with register names and register values
774 * (STATUS_WORD, STATUS_MFR, etc.).*/
775
776 additionalData["CALLOUT_INVENTORY_PATH"] =
777 psu->getInventoryPath();
778
779 createError(
780 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
Brandon Wyman52e54e82020-10-08 14:44:58 -0500781 additionalData);
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500782
Brandon Wyman3180f4d2020-12-08 17:53:46 -0600783 psu->setFaultLogged();
784 }
Jim Wrightaca86d02022-06-10 12:01:39 -0500785 // Only process if not in power fault window.
786 else if (psu->hasPgoodFault() && !powerFaultOccurring)
Brandon Wyman2916ea52021-11-06 03:31:18 +0000787 {
788 /* POWER_GOOD# is not low, or OFF is on */
789 additionalData["CALLOUT_INVENTORY_PATH"] =
790 psu->getInventoryPath();
791
792 createError(
793 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
794 additionalData);
795
796 psu->setFaultLogged();
797 }
Brandon Wyman4176d6b2020-10-07 17:41:06 -0500798 }
Brandon Wyman63ea78b2020-09-24 16:49:09 -0500799 }
800 }
801}
802
Brandon Wyman64e97752022-06-03 23:50:13 +0000803void PSUManager::updateMissingPSUs()
804{
805 if (supportedConfigs.empty() || psus.empty())
806 {
807 return;
808 }
809
810 // Power supplies default to missing. If the power supply is present,
811 // the PowerSupply object will update the inventory Present property to
812 // true. If we have less than the required number of power supplies, and
813 // this power supply is missing, update the inventory Present property
814 // to false to indicate required power supply is missing. Avoid
815 // indicating power supply missing if not required.
816
817 auto presentCount =
818 std::count_if(psus.begin(), psus.end(),
819 [](const auto& psu) { return psu->isPresent(); });
820
821 for (const auto& config : supportedConfigs)
822 {
823 for (const auto& psu : psus)
824 {
825 auto psuModel = psu->getModelName();
826 auto psuShortName = psu->getShortName();
827 auto psuInventoryPath = psu->getInventoryPath();
828 auto relativeInvPath =
829 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
830 auto psuPresent = psu->isPresent();
831 auto presProperty = false;
832 auto propReadFail = false;
833
834 try
835 {
836 presProperty = getPresence(bus, psuInventoryPath);
837 propReadFail = false;
838 }
Patrick Williams7354ce62022-07-22 19:26:56 -0500839 catch (const sdbusplus::exception_t& e)
Brandon Wyman64e97752022-06-03 23:50:13 +0000840 {
841 propReadFail = true;
842 // Relying on property change or interface added to retry.
843 // Log an informational trace to the journal.
844 log<level::INFO>(
845 fmt::format("D-Bus property {} access failure exception",
846 psuInventoryPath)
847 .c_str());
848 }
849
850 if (psuModel.empty())
851 {
852 if (!propReadFail && (presProperty != psuPresent))
853 {
854 // We already have this property, and it is not false
855 // set Present to false
856 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
857 }
858 continue;
859 }
860
861 if (config.first != psuModel)
862 {
863 continue;
864 }
865
866 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
867 {
868 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
869 }
870 }
871 }
872}
873
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000874void PSUManager::validateConfig()
875{
Adriana Kobylakb23e4432022-04-01 14:22:47 +0000876 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000877 {
878 return;
879 }
880
Brandon Wyman9666ddf2022-04-27 21:53:14 +0000881 for (const auto& psu : psus)
882 {
883 if ((psu->hasInputFault() || psu->hasVINUVFault()))
884 {
885 // Do not try to validate if input voltage fault present.
886 validationTimer->restartOnce(validationTimeout);
887 return;
888 }
889 }
890
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000891 std::map<std::string, std::string> additionalData;
892 auto supported = hasRequiredPSUs(additionalData);
893 if (supported)
894 {
895 runValidateConfig = false;
896 return;
897 }
898
899 // Validation failed, create an error log.
900 // Return without setting the runValidateConfig flag to false because
901 // it may be that an additional supported configuration interface is
902 // added and we need to validate it to see if it matches this system.
903 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
904 additionalData);
905}
906
907bool PSUManager::hasRequiredPSUs(
908 std::map<std::string, std::string>& additionalData)
909{
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000910 std::string model{};
Adriana Kobylak523704d2021-09-21 15:55:41 +0000911 if (!validateModelName(model, additionalData))
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000912 {
Adriana Kobylak523704d2021-09-21 15:55:41 +0000913 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000914 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000915
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000916 auto presentCount =
917 std::count_if(psus.begin(), psus.end(),
918 [](const auto& psu) { return psu->isPresent(); });
919
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000920 // Validate the supported configurations. A system may support more than one
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000921 // power supply model configuration. Since all configurations need to be
922 // checked, the additional data would contain only the information of the
923 // last configuration that did not match.
924 std::map<std::string, std::string> tmpAdditionalData;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000925 for (const auto& config : supportedConfigs)
926 {
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000927 if (config.first != model)
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000928 {
929 continue;
930 }
Brandon Wyman64e97752022-06-03 23:50:13 +0000931
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000932 if (presentCount != config.second.powerSupplyCount)
933 {
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000934 tmpAdditionalData.clear();
935 tmpAdditionalData["EXPECTED_COUNT"] =
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000936 std::to_string(config.second.powerSupplyCount);
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000937 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000938 continue;
939 }
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000940
941 bool voltageValidated = true;
942 for (const auto& psu : psus)
943 {
944 if (!psu->isPresent())
945 {
946 // Only present PSUs report a valid input voltage
947 continue;
948 }
949
950 double actualInputVoltage;
951 int inputVoltage;
952 psu->getInputVoltage(actualInputVoltage, inputVoltage);
953
954 if (std::find(config.second.inputVoltage.begin(),
955 config.second.inputVoltage.end(),
956 inputVoltage) == config.second.inputVoltage.end())
957 {
958 tmpAdditionalData.clear();
959 tmpAdditionalData["ACTUAL_VOLTAGE"] =
960 std::to_string(actualInputVoltage);
961 for (const auto& voltage : config.second.inputVoltage)
962 {
963 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
964 std::to_string(voltage) + " ";
965 }
966 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
967 psu->getInventoryPath();
968
969 voltageValidated = false;
970 break;
971 }
972 }
973 if (!voltageValidated)
974 {
975 continue;
976 }
977
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000978 return true;
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000979 }
Adriana Kobylak70e7f932021-06-10 18:53:56 +0000980
Adriana Kobylak4175ffb2021-08-02 14:51:05 +0000981 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
Adriana Kobylak4d9aaf92021-06-30 15:27:42 +0000982 return false;
Adriana Kobylak8f16fb52021-03-31 15:50:15 +0000983}
984
Shawn McCarney9252b7e2022-06-10 12:47:38 -0500985unsigned int PSUManager::getRequiredPSUCount()
986{
987 unsigned int requiredCount{0};
988
989 // Verify we have the supported configuration and PSU information
990 if (!supportedConfigs.empty() && !psus.empty())
991 {
992 // Find PSU models. They should all be the same.
993 std::set<std::string> models{};
994 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
995 if (!psu->getModelName().empty())
996 {
997 models.insert(psu->getModelName());
998 }
999 });
1000
1001 // If exactly one model was found, find corresponding configuration
1002 if (models.size() == 1)
1003 {
1004 const std::string& model = *(models.begin());
1005 auto it = supportedConfigs.find(model);
1006 if (it != supportedConfigs.end())
1007 {
1008 requiredCount = it->second.powerSupplyCount;
1009 }
1010 }
1011 }
1012
1013 return requiredCount;
1014}
1015
1016bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1017{
1018 // Get required number of PSUs; if not found, we don't know if PSU required
1019 unsigned int requiredCount = getRequiredPSUCount();
1020 if (requiredCount == 0)
1021 {
1022 return false;
1023 }
1024
1025 // If total PSU count <= the required count, all PSUs are required
1026 if (psus.size() <= requiredCount)
1027 {
1028 return true;
1029 }
1030
1031 // We don't currently get information from EntityManager about which PSUs
1032 // are required, so we have to do some guesswork. First check if this PSU
1033 // is present. If so, assume it is required.
1034 if (psu.isPresent())
1035 {
1036 return true;
1037 }
1038
1039 // This PSU is not present. Count the number of other PSUs that are
1040 // present. If enough other PSUs are present, assume the specified PSU is
1041 // not required.
1042 unsigned int psuCount =
1043 std::count_if(psus.begin(), psus.end(),
1044 [](const auto& psu) { return psu->isPresent(); });
1045 if (psuCount >= requiredCount)
1046 {
1047 return false;
1048 }
1049
1050 // Check if this PSU was previously present. If so, assume it is required.
1051 // We know it was previously present if it has a non-empty model name.
1052 if (!psu.getModelName().empty())
1053 {
1054 return true;
1055 }
1056
1057 // This PSU was never present. Count the number of other PSUs that were
1058 // previously present. If including those PSUs is enough, assume the
1059 // specified PSU is not required.
1060 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1061 return (!psu->isPresent() && !psu->getModelName().empty());
1062 });
1063 if (psuCount >= requiredCount)
1064 {
1065 return false;
1066 }
1067
1068 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1069 // that were never present. PSU inventory paths typically end with the PSU
1070 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1071 std::vector<std::string> sortedPaths;
1072 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1073 if (!psu->isPresent() && psu->getModelName().empty())
1074 {
1075 sortedPaths.push_back(psu->getInventoryPath());
1076 }
1077 });
1078 std::sort(sortedPaths.begin(), sortedPaths.end());
1079
1080 // Check if specified PSU is close enough to start of list to be required
1081 for (const auto& path : sortedPaths)
1082 {
1083 if (path == psu.getInventoryPath())
1084 {
1085 return true;
1086 }
1087 if (++psuCount >= requiredCount)
1088 {
1089 break;
1090 }
1091 }
1092
1093 // PSU was not close to start of sorted list; assume not required
1094 return false;
1095}
1096
Adriana Kobylak523704d2021-09-21 15:55:41 +00001097bool PSUManager::validateModelName(
1098 std::string& model, std::map<std::string, std::string>& additionalData)
1099{
1100 // Check that all PSUs have the same model name. Initialize the model
1101 // variable with the first PSU name found, then use it as a base to compare
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001102 // against the rest of the PSUs and get its inventory path to use as callout
1103 // if needed.
Adriana Kobylak523704d2021-09-21 15:55:41 +00001104 model.clear();
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001105 std::string modelInventoryPath{};
Adriana Kobylak523704d2021-09-21 15:55:41 +00001106 for (const auto& psu : psus)
1107 {
1108 auto psuModel = psu->getModelName();
1109 if (psuModel.empty())
1110 {
1111 continue;
1112 }
1113 if (model.empty())
1114 {
1115 model = psuModel;
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001116 modelInventoryPath = psu->getInventoryPath();
Adriana Kobylak523704d2021-09-21 15:55:41 +00001117 continue;
1118 }
1119 if (psuModel != model)
1120 {
Adriana Kobylakb70eae92022-01-20 22:09:56 +00001121 if (supportedConfigs.find(model) != supportedConfigs.end())
1122 {
1123 // The base model is supported, callout the mismatched PSU. The
1124 // mismatched PSU may or may not be supported.
1125 additionalData["EXPECTED_MODEL"] = model;
1126 additionalData["ACTUAL_MODEL"] = psuModel;
1127 additionalData["CALLOUT_INVENTORY_PATH"] =
1128 psu->getInventoryPath();
1129 }
1130 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1131 {
1132 // The base model is not supported, but the mismatched PSU is,
1133 // callout the base PSU.
1134 additionalData["EXPECTED_MODEL"] = psuModel;
1135 additionalData["ACTUAL_MODEL"] = model;
1136 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1137 }
1138 else
1139 {
1140 // The base model and the mismatched PSU are not supported or
1141 // could not be found in the supported configuration, callout
1142 // the mismatched PSU.
1143 additionalData["EXPECTED_MODEL"] = model;
1144 additionalData["ACTUAL_MODEL"] = psuModel;
1145 additionalData["CALLOUT_INVENTORY_PATH"] =
1146 psu->getInventoryPath();
1147 }
Adriana Kobylak523704d2021-09-21 15:55:41 +00001148 model.clear();
1149 return false;
1150 }
1151 }
1152 return true;
1153}
1154
Adriana Kobylakc0a07582021-10-13 15:52:25 +00001155void PSUManager::setPowerConfigGPIO()
1156{
1157 if (!powerConfigGPIO)
1158 {
1159 return;
1160 }
1161
1162 std::string model{};
1163 std::map<std::string, std::string> additionalData;
1164 if (!validateModelName(model, additionalData))
1165 {
1166 return;
1167 }
1168
1169 auto config = supportedConfigs.find(model);
1170 if (config != supportedConfigs.end())
1171 {
1172 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1173 // if the supported configuration indicates that this system model
1174 // expects the maximum number of power supplies (full load set to true).
1175 // Else, set it to high (1), this is the default.
1176 auto powerConfigValue =
1177 (config->second.powerConfigFullLoad == true ? 0 : 1);
1178 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1179 powerConfigGPIO->write(powerConfigValue, flags);
1180 }
1181}
1182
Adriana Kobylake5b1e082022-03-02 15:37:32 +00001183void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData)
1184{
1185 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
1186 server::PowerSystemInputs::Status::Fault);
1187 if (!brownoutLogged)
1188 {
1189 if (powerOn)
1190 {
1191 createError(
1192 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
1193 additionalData);
1194 brownoutLogged = true;
1195 }
1196 }
1197}
1198
1199void PSUManager::clearBrownout()
1200{
1201 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator::
1202 server::PowerSystemInputs::Status::Good);
1203 brownoutLogged = false;
1204}
1205
Brandon Wyman63ea78b2020-09-24 16:49:09 -05001206} // namespace phosphor::power::manager