1 #include "config.h"
2
3 #include "psu_manager.hpp"
4
5 #include "utility.hpp"
6
7 #include <fmt/format.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10
11 #include <xyz/openbmc_project/State/Chassis/server.hpp>
12
13 #include <algorithm>
14 #include <regex>
15 #include <set>
16
17 using namespace phosphor::logging;
18
19 namespace phosphor::power::manager
20 {
21 constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor";
22 constexpr auto objectManagerObjPath =
23 "/xyz/openbmc_project/power/power_supplies";
24 constexpr auto powerSystemsInputsObjPath =
25 "/xyz/openbmc_project/power/power_supplies/chassis0/psus";
26
27 constexpr auto IBMCFFPSInterface =
28 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
29 constexpr auto i2cBusProp = "I2CBus";
30 constexpr auto i2cAddressProp = "I2CAddress";
31 constexpr auto psuNameProp = "Name";
32 constexpr auto presLineName = "NamedPresenceGpio";
33
34 constexpr auto supportedConfIntf =
35 "xyz.openbmc_project.Configuration.SupportedConfiguration";
36
37 const auto deviceDirPath = "/sys/bus/i2c/devices/";
38 const auto driverDirName = "/driver";
39
40 constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
41
PSUManager(sdbusplus::bus_t & bus,const sdeventplus::Event & e)42 PSUManager::PSUManager(sdbusplus::bus_t& bus, const sdeventplus::Event& e) :
43 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath),
44 objectManager(bus, objectManagerObjPath),
45 sensorsObjManager(bus, "/xyz/openbmc_project/sensors")
46 {
47 // Subscribe to InterfacesAdded before doing a property read, otherwise
48 // the interface could be created after the read attempt but before the
49 // match is created.
50 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
51 bus,
52 sdbusplus::bus::match::rules::interfacesAdded() +
53 sdbusplus::bus::match::rules::sender(
54 "xyz.openbmc_project.EntityManager"),
55 std::bind(&PSUManager::entityManagerIfaceAdded, this,
56 std::placeholders::_1));
57 getPSUConfiguration();
58 getSystemProperties();
59
60 // Request the bus name before the analyze() function, which is the one that
61 // determines the brownout condition and sets the status d-bus property.
62 bus.request_name(managerBusName);
63
64 using namespace sdeventplus;
65 auto interval = std::chrono::milliseconds(1000);
66 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
67 e, std::bind(&PSUManager::analyze, this), interval);
68
69 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
70 e, std::bind(&PSUManager::validateConfig, this));
71
72 try
73 {
74 powerConfigGPIO = createGPIO("power-config-full-load");
75 }
76 catch (const std::exception& e)
77 {
78 // Ignore error, GPIO may not be implemented in this system.
79 powerConfigGPIO = nullptr;
80 }
81
82 // Subscribe to power state changes
83 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
84 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
85 bus,
86 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
87 POWER_IFACE),
88 [this](auto& msg) { this->powerStateChanged(msg); });
89
90 initialize();
91 }
92
initialize()93 void PSUManager::initialize()
94 {
95 try
96 {
97 // pgood is the latest read of the chassis pgood
98 int pgood = 0;
99 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH,
100 powerService, bus, pgood);
101
102 // state is the latest requested power on / off transition
103 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH,
104 POWER_IFACE, "getPowerState");
105 auto reply = bus.call(method);
106 int state = 0;
107 reply.read(state);
108
109 if (state)
110 {
111 // Monitor PSUs anytime state is on
112 powerOn = true;
113 // In the power fault window if pgood is off
114 powerFaultOccurring = !pgood;
115 validationTimer->restartOnce(validationTimeout);
116 }
117 else
118 {
119 // Power is off
120 powerOn = false;
121 powerFaultOccurring = false;
122 runValidateConfig = true;
123 }
124 }
125 catch (const std::exception& e)
126 {
127 log<level::INFO>(
128 fmt::format(
129 "Failed to get power state, assuming it is off, error {}",
130 e.what())
131 .c_str());
132 powerOn = false;
133 powerFaultOccurring = false;
134 runValidateConfig = true;
135 }
136
137 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
138 clearFaults();
139 updateMissingPSUs();
140 setPowerConfigGPIO();
141
142 log<level::INFO>(
143 fmt::format("initialize: power on: {}, power fault occurring: {}",
144 powerOn, powerFaultOccurring)
145 .c_str());
146 }
147
getPSUConfiguration()148 void PSUManager::getPSUConfiguration()
149 {
150 using namespace phosphor::power::util;
151 auto depth = 0;
152 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth);
153
154 psus.clear();
155
156 // I should get a map of objects back.
157 // Each object will have a path, a service, and an interface.
158 // The interface should match the one passed into this function.
159 for (const auto& [path, services] : objects)
160 {
161 auto service = services.begin()->first;
162
163 if (path.empty() || service.empty())
164 {
165 continue;
166 }
167
168 // For each object in the array of objects, I want to get properties
169 // from the service, path, and interface.
170 auto properties = getAllProperties(bus, path, IBMCFFPSInterface,
171 service);
172
173 getPSUProperties(properties);
174 }
175
176 if (psus.empty())
177 {
178 // Interface or properties not found. Let the Interfaces Added callback
179 // process the information once the interfaces are added to D-Bus.
180 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
181 }
182 }
183
getPSUProperties(util::DbusPropertyMap & properties)184 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties)
185 {
186 // From passed in properties, I want to get: I2CBus, I2CAddress,
187 // and Name. Create a power supply object, using Name to build the inventory
188 // path.
189 const auto basePSUInvPath =
190 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply";
191 uint64_t* i2cbus = nullptr;
192 uint64_t* i2caddr = nullptr;
193 std::string* psuname = nullptr;
194 std::string* preslineptr = nullptr;
195
196 for (const auto& property : properties)
197 {
198 try
199 {
200 if (property.first == i2cBusProp)
201 {
202 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]);
203 }
204 else if (property.first == i2cAddressProp)
205 {
206 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]);
207 }
208 else if (property.first == psuNameProp)
209 {
210 psuname = std::get_if<std::string>(&properties[psuNameProp]);
211 }
212 else if (property.first == presLineName)
213 {
214 preslineptr =
215 std::get_if<std::string>(&properties[presLineName]);
216 }
217 }
218 catch (const std::exception& e)
219 {}
220 }
221
222 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty()))
223 {
224 std::string invpath = basePSUInvPath;
225 invpath.push_back(psuname->back());
226 std::string presline = "";
227
228 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str());
229
230 if (nullptr != preslineptr)
231 {
232 presline = *preslineptr;
233 }
234
235 auto invMatch = std::find_if(psus.begin(), psus.end(),
236 [&invpath](auto& psu) {
237 return psu->getInventoryPath() == invpath;
238 });
239 if (invMatch != psus.end())
240 {
241 // This power supply has the same inventory path as the one with
242 // information just added to D-Bus.
243 // Changes to GPIO line name unlikely, so skip checking.
244 // Changes to the I2C bus and address unlikely, as that would
245 // require corresponding device tree updates.
246 // Return out to avoid duplicate object creation.
247 return;
248 }
249
250 buildDriverName(*i2cbus, *i2caddr);
251 log<level::DEBUG>(
252 fmt::format("make PowerSupply bus: {} addr: {} presline: {}",
253 *i2cbus, *i2caddr, presline)
254 .c_str());
255 auto psu = std::make_unique<PowerSupply>(
256 bus, invpath, *i2cbus, *i2caddr, driverName, presline,
257 std::bind(
258 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn),
259 this));
260 psus.emplace_back(std::move(psu));
261
262 // Subscribe to power supply presence changes
263 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>(
264 bus,
265 sdbusplus::bus::match::rules::propertiesChanged(invpath,
266 INVENTORY_IFACE),
267 [this](auto& msg) { this->presenceChanged(msg); });
268 presenceMatches.emplace_back(std::move(presenceMatch));
269 }
270
271 if (psus.empty())
272 {
273 log<level::INFO>(fmt::format("No power supplies to monitor").c_str());
274 }
275 else
276 {
277 populateDriverName();
278 }
279 }
280
populateSysProperties(const util::DbusPropertyMap & properties)281 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties)
282 {
283 try
284 {
285 auto propIt = properties.find("SupportedType");
286 if (propIt == properties.end())
287 {
288 return;
289 }
290 const std::string* type = std::get_if<std::string>(&(propIt->second));
291 if ((type == nullptr) || (*type != "PowerSupply"))
292 {
293 return;
294 }
295
296 propIt = properties.find("SupportedModel");
297 if (propIt == properties.end())
298 {
299 return;
300 }
301 const std::string* model = std::get_if<std::string>(&(propIt->second));
302 if (model == nullptr)
303 {
304 return;
305 }
306
307 sys_properties sys;
308 propIt = properties.find("RedundantCount");
309 if (propIt != properties.end())
310 {
311 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second));
312 if (count != nullptr)
313 {
314 sys.powerSupplyCount = *count;
315 }
316 }
317 propIt = properties.find("InputVoltage");
318 if (propIt != properties.end())
319 {
320 const std::vector<uint64_t>* voltage =
321 std::get_if<std::vector<uint64_t>>(&(propIt->second));
322 if (voltage != nullptr)
323 {
324 sys.inputVoltage = *voltage;
325 }
326 }
327
328 // The PowerConfigFullLoad is an optional property, default it to false
329 // since that's the default value of the power-config-full-load GPIO.
330 sys.powerConfigFullLoad = false;
331 propIt = properties.find("PowerConfigFullLoad");
332 if (propIt != properties.end())
333 {
334 const bool* fullLoad = std::get_if<bool>(&(propIt->second));
335 if (fullLoad != nullptr)
336 {
337 sys.powerConfigFullLoad = *fullLoad;
338 }
339 }
340
341 supportedConfigs.emplace(*model, sys);
342 }
343 catch (const std::exception& e)
344 {}
345 }
346
getSystemProperties()347 void PSUManager::getSystemProperties()
348 {
349 try
350 {
351 util::DbusSubtree subtree = util::getSubTree(bus, INVENTORY_OBJ_PATH,
352 supportedConfIntf, 0);
353 if (subtree.empty())
354 {
355 throw std::runtime_error("Supported Configuration Not Found");
356 }
357
358 for (const auto& [objPath, services] : subtree)
359 {
360 std::string service = services.begin()->first;
361 if (objPath.empty() || service.empty())
362 {
363 continue;
364 }
365 auto properties = util::getAllProperties(
366 bus, objPath, supportedConfIntf, service);
367 populateSysProperties(properties);
368 }
369 }
370 catch (const std::exception& e)
371 {
372 // Interface or property not found. Let the Interfaces Added callback
373 // process the information once the interfaces are added to D-Bus.
374 }
375 }
376
entityManagerIfaceAdded(sdbusplus::message_t & msg)377 void PSUManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
378 {
379 try
380 {
381 sdbusplus::message::object_path objPath;
382 std::map<std::string, std::map<std::string, util::DbusVariant>>
383 interfaces;
384 msg.read(objPath, interfaces);
385
386 auto itIntf = interfaces.find(supportedConfIntf);
387 if (itIntf != interfaces.cend())
388 {
389 populateSysProperties(itIntf->second);
390 updateMissingPSUs();
391 }
392
393 itIntf = interfaces.find(IBMCFFPSInterface);
394 if (itIntf != interfaces.cend())
395 {
396 log<level::INFO>(
397 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface)
398 .c_str());
399 getPSUProperties(itIntf->second);
400 updateMissingPSUs();
401 }
402
403 // Call to validate the psu configuration if the power is on and both
404 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been
405 // processed
406 if (powerOn && !psus.empty() && !supportedConfigs.empty())
407 {
408 validationTimer->restartOnce(validationTimeout);
409 }
410 }
411 catch (const std::exception& e)
412 {
413 // Ignore, the property may be of a different type than expected.
414 }
415 }
416
powerStateChanged(sdbusplus::message_t & msg)417 void PSUManager::powerStateChanged(sdbusplus::message_t& msg)
418 {
419 std::string msgSensor;
420 std::map<std::string, std::variant<int>> msgData;
421 msg.read(msgSensor, msgData);
422
423 // Check if it was the state property that changed.
424 auto valPropMap = msgData.find("state");
425 if (valPropMap != msgData.end())
426 {
427 int state = std::get<int>(valPropMap->second);
428 if (state)
429 {
430 // Power on requested
431 powerOn = true;
432 powerFaultOccurring = false;
433 validationTimer->restartOnce(validationTimeout);
434 clearFaults();
435 syncHistory();
436 setPowerConfigGPIO();
437 setInputVoltageRating();
438 }
439 else
440 {
441 // Power off requested
442 powerOn = false;
443 powerFaultOccurring = false;
444 runValidateConfig = true;
445 }
446 }
447
448 // Check if it was the pgood property that changed.
449 valPropMap = msgData.find("pgood");
450 if (valPropMap != msgData.end())
451 {
452 int pgood = std::get<int>(valPropMap->second);
453 if (!pgood)
454 {
455 // Chassis power good has turned off
456 if (powerOn)
457 {
458 // pgood is off but state is on, in power fault window
459 powerFaultOccurring = true;
460 }
461 }
462 }
463 log<level::INFO>(
464 fmt::format(
465 "powerStateChanged: power on: {}, power fault occurring: {}",
466 powerOn, powerFaultOccurring)
467 .c_str());
468 }
469
presenceChanged(sdbusplus::message_t & msg)470 void PSUManager::presenceChanged(sdbusplus::message_t& msg)
471 {
472 std::string msgSensor;
473 std::map<std::string, std::variant<uint32_t, bool>> msgData;
474 msg.read(msgSensor, msgData);
475
476 // Check if it was the Present property that changed.
477 auto valPropMap = msgData.find(PRESENT_PROP);
478 if (valPropMap != msgData.end())
479 {
480 if (std::get<bool>(valPropMap->second))
481 {
482 // A PSU became present, force the PSU validation to run.
483 runValidateConfig = true;
484 validationTimer->restartOnce(validationTimeout);
485 }
486 }
487 }
488
setPowerSupplyError(const std::string & psuErrorString)489 void PSUManager::setPowerSupplyError(const std::string& psuErrorString)
490 {
491 using namespace sdbusplus::xyz::openbmc_project;
492 constexpr auto method = "setPowerSupplyError";
493
494 try
495 {
496 // Call D-Bus method to inform pseq of PSU error
497 auto methodMsg = bus.new_method_call(
498 powerService.c_str(), POWER_OBJ_PATH, POWER_IFACE, method);
499 methodMsg.append(psuErrorString);
500 auto callReply = bus.call(methodMsg);
501 }
502 catch (const std::exception& e)
503 {
504 log<level::INFO>(
505 fmt::format("Failed calling setPowerSupplyError due to error {}",
506 e.what())
507 .c_str());
508 }
509 }
510
createError(const std::string & faultName,std::map<std::string,std::string> & additionalData)511 void PSUManager::createError(const std::string& faultName,
512 std::map<std::string, std::string>& additionalData)
513 {
514 using namespace sdbusplus::xyz::openbmc_project;
515 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
516 constexpr auto loggingCreateInterface =
517 "xyz.openbmc_project.Logging.Create";
518
519 try
520 {
521 additionalData["_PID"] = std::to_string(getpid());
522
523 auto service = util::getService(loggingObjectPath,
524 loggingCreateInterface, bus);
525
526 if (service.empty())
527 {
528 log<level::ERR>("Unable to get logging manager service");
529 return;
530 }
531
532 auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
533 loggingCreateInterface, "Create");
534
535 auto level = Logging::server::Entry::Level::Error;
536 method.append(faultName, level, additionalData);
537
538 auto reply = bus.call(method);
539 setPowerSupplyError(faultName);
540 }
541 catch (const std::exception& e)
542 {
543 log<level::ERR>(
544 fmt::format(
545 "Failed creating event log for fault {} due to error {}",
546 faultName, e.what())
547 .c_str());
548 }
549 }
550
syncHistory()551 void PSUManager::syncHistory()
552 {
553 if (driverName != ACBEL_FSG032_DD_NAME)
554 {
555 if (!syncHistoryGPIO)
556 {
557 try
558 {
559 syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
560 }
561 catch (const std::exception& e)
562 {
563 // Not an error, system just hasn't implemented the synch gpio
564 log<level::INFO>("No synchronization GPIO found");
565 syncHistoryGPIO = nullptr;
566 }
567 }
568 if (syncHistoryGPIO)
569 {
570 const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
571 log<level::INFO>("Synchronize INPUT_HISTORY");
572 syncHistoryGPIO->toggleLowHigh(delay);
573 log<level::INFO>("Synchronize INPUT_HISTORY completed");
574 }
575 }
576
577 // Always clear synch history required after calling this function
578 for (auto& psu : psus)
579 {
580 psu->clearSyncHistoryRequired();
581 }
582 }
583
analyze()584 void PSUManager::analyze()
585 {
586 auto syncHistoryRequired = std::any_of(
587 psus.begin(), psus.end(),
588 [](const auto& psu) { return psu->isSyncHistoryRequired(); });
589 if (syncHistoryRequired)
590 {
591 syncHistory();
592 }
593
594 for (auto& psu : psus)
595 {
596 psu->analyze();
597 }
598
599 analyzeBrownout();
600
601 // Only perform individual PSU analysis if power is on and a brownout has
602 // not already been logged
603 if (powerOn && !brownoutLogged)
604 {
605 for (auto& psu : psus)
606 {
607 std::map<std::string, std::string> additionalData;
608
609 if (!psu->isFaultLogged() && !psu->isPresent() &&
610 !validationTimer->isEnabled())
611 {
612 std::map<std::string, std::string> requiredPSUsData;
613 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
614 if (!requiredPSUsPresent && isRequiredPSU(*psu))
615 {
616 additionalData.merge(requiredPSUsData);
617 // Create error for power supply missing.
618 additionalData["CALLOUT_INVENTORY_PATH"] =
619 psu->getInventoryPath();
620 additionalData["CALLOUT_PRIORITY"] = "H";
621 createError(
622 "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
623 additionalData);
624 }
625 psu->setFaultLogged();
626 }
627 else if (!psu->isFaultLogged() && psu->isFaulted())
628 {
629 // Add STATUS_WORD and STATUS_MFR last response, in padded
630 // hexadecimal format.
631 additionalData["STATUS_WORD"] =
632 fmt::format("{:#04x}", psu->getStatusWord());
633 additionalData["STATUS_MFR"] = fmt::format("{:#02x}",
634 psu->getMFRFault());
635 // If there are faults being reported, they possibly could be
636 // related to a bug in the firmware version running on the power
637 // supply. Capture that data into the error as well.
638 additionalData["FW_VERSION"] = psu->getFWVersion();
639
640 if (psu->hasCommFault())
641 {
642 additionalData["STATUS_CML"] =
643 fmt::format("{:#02x}", psu->getStatusCML());
644 /* Attempts to communicate with the power supply have
645 * reached there limit. Create an error. */
646 additionalData["CALLOUT_DEVICE_PATH"] =
647 psu->getDevicePath();
648
649 createError(
650 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
651 additionalData);
652
653 psu->setFaultLogged();
654 }
655 else if ((psu->hasInputFault() || psu->hasVINUVFault()))
656 {
657 // Include STATUS_INPUT for input faults.
658 additionalData["STATUS_INPUT"] =
659 fmt::format("{:#02x}", psu->getStatusInput());
660
661 /* The power supply location might be needed if the input
662 * fault is due to a problem with the power supply itself.
663 * Include the inventory path with a call out priority of
664 * low.
665 */
666 additionalData["CALLOUT_INVENTORY_PATH"] =
667 psu->getInventoryPath();
668 additionalData["CALLOUT_PRIORITY"] = "L";
669 createError("xyz.openbmc_project.Power.PowerSupply.Error."
670 "InputFault",
671 additionalData);
672 psu->setFaultLogged();
673 }
674 else if (psu->hasPSKillFault())
675 {
676 createError(
677 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
678 additionalData);
679 psu->setFaultLogged();
680 }
681 else if (psu->hasVoutOVFault())
682 {
683 // Include STATUS_VOUT for Vout faults.
684 additionalData["STATUS_VOUT"] =
685 fmt::format("{:#02x}", psu->getStatusVout());
686
687 additionalData["CALLOUT_INVENTORY_PATH"] =
688 psu->getInventoryPath();
689
690 createError(
691 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
692 additionalData);
693
694 psu->setFaultLogged();
695 }
696 else if (psu->hasIoutOCFault())
697 {
698 // Include STATUS_IOUT for Iout faults.
699 additionalData["STATUS_IOUT"] =
700 fmt::format("{:#02x}", psu->getStatusIout());
701
702 createError(
703 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
704 additionalData);
705
706 psu->setFaultLogged();
707 }
708 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
709 psu->hasPSCS12VFault())
710 {
711 // Include STATUS_VOUT for Vout faults.
712 additionalData["STATUS_VOUT"] =
713 fmt::format("{:#02x}", psu->getStatusVout());
714
715 additionalData["CALLOUT_INVENTORY_PATH"] =
716 psu->getInventoryPath();
717
718 createError(
719 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
720 additionalData);
721
722 psu->setFaultLogged();
723 }
724 // A fan fault should have priority over a temperature fault,
725 // since a failed fan may lead to a temperature problem.
726 // Only process if not in power fault window.
727 else if (psu->hasFanFault() && !powerFaultOccurring)
728 {
729 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
730 additionalData["STATUS_TEMPERATURE"] =
731 fmt::format("{:#02x}", psu->getStatusTemperature());
732 additionalData["STATUS_FANS_1_2"] =
733 fmt::format("{:#02x}", psu->getStatusFans12());
734
735 additionalData["CALLOUT_INVENTORY_PATH"] =
736 psu->getInventoryPath();
737
738 createError(
739 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
740 additionalData);
741
742 psu->setFaultLogged();
743 }
744 else if (psu->hasTempFault())
745 {
746 // Include STATUS_TEMPERATURE for temperature faults.
747 additionalData["STATUS_TEMPERATURE"] =
748 fmt::format("{:#02x}", psu->getStatusTemperature());
749
750 additionalData["CALLOUT_INVENTORY_PATH"] =
751 psu->getInventoryPath();
752
753 createError(
754 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
755 additionalData);
756
757 psu->setFaultLogged();
758 }
759 else if (psu->hasMFRFault())
760 {
761 /* This can represent a variety of faults that result in
762 * calling out the power supply for replacement: Output
763 * OverCurrent, Output Under Voltage, and potentially other
764 * faults.
765 *
766 * Also plan on putting specific fault in AdditionalData,
767 * along with register names and register values
768 * (STATUS_WORD, STATUS_MFR, etc.).*/
769
770 additionalData["CALLOUT_INVENTORY_PATH"] =
771 psu->getInventoryPath();
772
773 createError(
774 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
775 additionalData);
776
777 psu->setFaultLogged();
778 }
779 // Only process if not in power fault window.
780 else if (psu->hasPgoodFault() && !powerFaultOccurring)
781 {
782 /* POWER_GOOD# is not low, or OFF is on */
783 additionalData["CALLOUT_INVENTORY_PATH"] =
784 psu->getInventoryPath();
785
786 createError(
787 "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
788 additionalData);
789
790 psu->setFaultLogged();
791 }
792 }
793 }
794 }
795 }
796
analyzeBrownout()797 void PSUManager::analyzeBrownout()
798 {
799 // Count number of power supplies failing
800 size_t presentCount = 0;
801 size_t notPresentCount = 0;
802 size_t acFailedCount = 0;
803 size_t pgoodFailedCount = 0;
804 for (const auto& psu : psus)
805 {
806 if (psu->isPresent())
807 {
808 ++presentCount;
809 if (psu->hasACFault())
810 {
811 ++acFailedCount;
812 }
813 else if (psu->hasPgoodFault())
814 {
815 ++pgoodFailedCount;
816 }
817 }
818 else
819 {
820 ++notPresentCount;
821 }
822 }
823
824 // Only issue brownout failure if chassis pgood has failed, it has not
825 // already been logged, at least one PSU has seen an AC fail, and all
826 // present PSUs have an AC or pgood failure. Note an AC fail is only set if
827 // at least one PSU is present.
828 if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
829 (presentCount == (acFailedCount + pgoodFailedCount)))
830 {
831 // Indicate that the system is in a brownout condition by creating an
832 // error log and setting the PowerSystemInputs status property to Fault.
833 powerSystemInputs.status(
834 sdbusplus::xyz::openbmc_project::State::Decorator::server::
835 PowerSystemInputs::Status::Fault);
836
837 std::map<std::string, std::string> additionalData;
838 additionalData.emplace("NOT_PRESENT_COUNT",
839 std::to_string(notPresentCount));
840 additionalData.emplace("VIN_FAULT_COUNT",
841 std::to_string(acFailedCount));
842 additionalData.emplace("PGOOD_FAULT_COUNT",
843 std::to_string(pgoodFailedCount));
844 log<level::INFO>(
845 fmt::format(
846 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}",
847 notPresentCount, acFailedCount, pgoodFailedCount)
848 .c_str());
849
850 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
851 additionalData);
852 brownoutLogged = true;
853 }
854 else
855 {
856 // If a brownout was previously logged but at least one PSU is not
857 // currently in AC fault, determine if the brownout condition can be
858 // cleared
859 if (brownoutLogged && (acFailedCount < presentCount))
860 {
861 // Chassis only recognizes the PowerSystemInputs change when it is
862 // off
863 try
864 {
865 using PowerState = sdbusplus::xyz::openbmc_project::State::
866 server::Chassis::PowerState;
867 PowerState currentPowerState;
868 util::getProperty<PowerState>(
869 "xyz.openbmc_project.State.Chassis", "CurrentPowerState",
870 "/xyz/openbmc_project/state/chassis0",
871 "xyz.openbmc_project.State.Chassis0", bus,
872 currentPowerState);
873
874 if (currentPowerState == PowerState::Off)
875 {
876 // Indicate that the system is no longer in a brownout
877 // condition by setting the PowerSystemInputs status
878 // property to Good.
879 log<level::INFO>(
880 fmt::format(
881 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}",
882 notPresentCount, acFailedCount, pgoodFailedCount)
883 .c_str());
884 powerSystemInputs.status(
885 sdbusplus::xyz::openbmc_project::State::Decorator::
886 server::PowerSystemInputs::Status::Good);
887 brownoutLogged = false;
888 }
889 }
890 catch (const std::exception& e)
891 {
892 log<level::ERR>(
893 fmt::format("Error trying to clear brownout, error: {}",
894 e.what())
895 .c_str());
896 }
897 }
898 }
899 }
900
updateMissingPSUs()901 void PSUManager::updateMissingPSUs()
902 {
903 if (supportedConfigs.empty() || psus.empty())
904 {
905 return;
906 }
907
908 // Power supplies default to missing. If the power supply is present,
909 // the PowerSupply object will update the inventory Present property to
910 // true. If we have less than the required number of power supplies, and
911 // this power supply is missing, update the inventory Present property
912 // to false to indicate required power supply is missing. Avoid
913 // indicating power supply missing if not required.
914
915 auto presentCount =
916 std::count_if(psus.begin(), psus.end(),
917 [](const auto& psu) { return psu->isPresent(); });
918
919 for (const auto& config : supportedConfigs)
920 {
921 for (const auto& psu : psus)
922 {
923 auto psuModel = psu->getModelName();
924 auto psuShortName = psu->getShortName();
925 auto psuInventoryPath = psu->getInventoryPath();
926 auto relativeInvPath =
927 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
928 auto psuPresent = psu->isPresent();
929 auto presProperty = false;
930 auto propReadFail = false;
931
932 try
933 {
934 presProperty = getPresence(bus, psuInventoryPath);
935 propReadFail = false;
936 }
937 catch (const sdbusplus::exception_t& e)
938 {
939 propReadFail = true;
940 // Relying on property change or interface added to retry.
941 // Log an informational trace to the journal.
942 log<level::INFO>(
943 fmt::format("D-Bus property {} access failure exception",
944 psuInventoryPath)
945 .c_str());
946 }
947
948 if (psuModel.empty())
949 {
950 if (!propReadFail && (presProperty != psuPresent))
951 {
952 // We already have this property, and it is not false
953 // set Present to false
954 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
955 }
956 continue;
957 }
958
959 if (config.first != psuModel)
960 {
961 continue;
962 }
963
964 if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
965 {
966 setPresence(bus, relativeInvPath, psuPresent, psuShortName);
967 }
968 }
969 }
970 }
971
validateConfig()972 void PSUManager::validateConfig()
973 {
974 if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
975 {
976 return;
977 }
978
979 for (const auto& psu : psus)
980 {
981 if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
982 {
983 // Do not try to validate if input voltage fault present.
984 validationTimer->restartOnce(validationTimeout);
985 return;
986 }
987 }
988
989 std::map<std::string, std::string> additionalData;
990 auto supported = hasRequiredPSUs(additionalData);
991 if (supported)
992 {
993 runValidateConfig = false;
994 double actualVoltage;
995 int inputVoltage;
996 int previousInputVoltage = 0;
997 bool voltageMismatch = false;
998
999 for (const auto& psu : psus)
1000 {
1001 if (!psu->isPresent())
1002 {
1003 // Only present PSUs report a valid input voltage
1004 continue;
1005 }
1006 psu->getInputVoltage(actualVoltage, inputVoltage);
1007 if (previousInputVoltage && inputVoltage &&
1008 (previousInputVoltage != inputVoltage))
1009 {
1010 additionalData["EXPECTED_VOLTAGE"] =
1011 std::to_string(previousInputVoltage);
1012 additionalData["ACTUAL_VOLTAGE"] =
1013 std::to_string(actualVoltage);
1014 voltageMismatch = true;
1015 }
1016 if (!previousInputVoltage && inputVoltage)
1017 {
1018 previousInputVoltage = inputVoltage;
1019 }
1020 }
1021 if (!voltageMismatch)
1022 {
1023 return;
1024 }
1025 }
1026
1027 // Validation failed, create an error log.
1028 // Return without setting the runValidateConfig flag to false because
1029 // it may be that an additional supported configuration interface is
1030 // added and we need to validate it to see if it matches this system.
1031 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
1032 additionalData);
1033 }
1034
hasRequiredPSUs(std::map<std::string,std::string> & additionalData)1035 bool PSUManager::hasRequiredPSUs(
1036 std::map<std::string, std::string>& additionalData)
1037 {
1038 std::string model{};
1039 if (!validateModelName(model, additionalData))
1040 {
1041 return false;
1042 }
1043
1044 auto presentCount =
1045 std::count_if(psus.begin(), psus.end(),
1046 [](const auto& psu) { return psu->isPresent(); });
1047
1048 // Validate the supported configurations. A system may support more than one
1049 // power supply model configuration. Since all configurations need to be
1050 // checked, the additional data would contain only the information of the
1051 // last configuration that did not match.
1052 std::map<std::string, std::string> tmpAdditionalData;
1053 for (const auto& config : supportedConfigs)
1054 {
1055 if (config.first != model)
1056 {
1057 continue;
1058 }
1059
1060 // Number of power supplies present should equal or exceed the expected
1061 // count
1062 if (presentCount < config.second.powerSupplyCount)
1063 {
1064 tmpAdditionalData.clear();
1065 tmpAdditionalData["EXPECTED_COUNT"] =
1066 std::to_string(config.second.powerSupplyCount);
1067 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount);
1068 continue;
1069 }
1070
1071 bool voltageValidated = true;
1072 for (const auto& psu : psus)
1073 {
1074 if (!psu->isPresent())
1075 {
1076 // Only present PSUs report a valid input voltage
1077 continue;
1078 }
1079
1080 double actualInputVoltage;
1081 int inputVoltage;
1082 psu->getInputVoltage(actualInputVoltage, inputVoltage);
1083
1084 if (std::find(config.second.inputVoltage.begin(),
1085 config.second.inputVoltage.end(),
1086 inputVoltage) == config.second.inputVoltage.end())
1087 {
1088 tmpAdditionalData.clear();
1089 tmpAdditionalData["ACTUAL_VOLTAGE"] =
1090 std::to_string(actualInputVoltage);
1091 for (const auto& voltage : config.second.inputVoltage)
1092 {
1093 tmpAdditionalData["EXPECTED_VOLTAGE"] +=
1094 std::to_string(voltage) + " ";
1095 }
1096 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] =
1097 psu->getInventoryPath();
1098
1099 voltageValidated = false;
1100 break;
1101 }
1102 }
1103 if (!voltageValidated)
1104 {
1105 continue;
1106 }
1107
1108 return true;
1109 }
1110
1111 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end());
1112 return false;
1113 }
1114
getRequiredPSUCount()1115 unsigned int PSUManager::getRequiredPSUCount()
1116 {
1117 unsigned int requiredCount{0};
1118
1119 // Verify we have the supported configuration and PSU information
1120 if (!supportedConfigs.empty() && !psus.empty())
1121 {
1122 // Find PSU models. They should all be the same.
1123 std::set<std::string> models{};
1124 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) {
1125 if (!psu->getModelName().empty())
1126 {
1127 models.insert(psu->getModelName());
1128 }
1129 });
1130
1131 // If exactly one model was found, find corresponding configuration
1132 if (models.size() == 1)
1133 {
1134 const std::string& model = *(models.begin());
1135 auto it = supportedConfigs.find(model);
1136 if (it != supportedConfigs.end())
1137 {
1138 requiredCount = it->second.powerSupplyCount;
1139 }
1140 }
1141 }
1142
1143 return requiredCount;
1144 }
1145
isRequiredPSU(const PowerSupply & psu)1146 bool PSUManager::isRequiredPSU(const PowerSupply& psu)
1147 {
1148 // Get required number of PSUs; if not found, we don't know if PSU required
1149 unsigned int requiredCount = getRequiredPSUCount();
1150 if (requiredCount == 0)
1151 {
1152 return false;
1153 }
1154
1155 // If total PSU count <= the required count, all PSUs are required
1156 if (psus.size() <= requiredCount)
1157 {
1158 return true;
1159 }
1160
1161 // We don't currently get information from EntityManager about which PSUs
1162 // are required, so we have to do some guesswork. First check if this PSU
1163 // is present. If so, assume it is required.
1164 if (psu.isPresent())
1165 {
1166 return true;
1167 }
1168
1169 // This PSU is not present. Count the number of other PSUs that are
1170 // present. If enough other PSUs are present, assume the specified PSU is
1171 // not required.
1172 unsigned int psuCount =
1173 std::count_if(psus.begin(), psus.end(),
1174 [](const auto& psu) { return psu->isPresent(); });
1175 if (psuCount >= requiredCount)
1176 {
1177 return false;
1178 }
1179
1180 // Check if this PSU was previously present. If so, assume it is required.
1181 // We know it was previously present if it has a non-empty model name.
1182 if (!psu.getModelName().empty())
1183 {
1184 return true;
1185 }
1186
1187 // This PSU was never present. Count the number of other PSUs that were
1188 // previously present. If including those PSUs is enough, assume the
1189 // specified PSU is not required.
1190 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) {
1191 return (!psu->isPresent() && !psu->getModelName().empty());
1192 });
1193 if (psuCount >= requiredCount)
1194 {
1195 return false;
1196 }
1197
1198 // We still haven't found enough PSUs. Sort the inventory paths of PSUs
1199 // that were never present. PSU inventory paths typically end with the PSU
1200 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required.
1201 std::vector<std::string> sortedPaths;
1202 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) {
1203 if (!psu->isPresent() && psu->getModelName().empty())
1204 {
1205 sortedPaths.push_back(psu->getInventoryPath());
1206 }
1207 });
1208 std::sort(sortedPaths.begin(), sortedPaths.end());
1209
1210 // Check if specified PSU is close enough to start of list to be required
1211 for (const auto& path : sortedPaths)
1212 {
1213 if (path == psu.getInventoryPath())
1214 {
1215 return true;
1216 }
1217 if (++psuCount >= requiredCount)
1218 {
1219 break;
1220 }
1221 }
1222
1223 // PSU was not close to start of sorted list; assume not required
1224 return false;
1225 }
1226
validateModelName(std::string & model,std::map<std::string,std::string> & additionalData)1227 bool PSUManager::validateModelName(
1228 std::string& model, std::map<std::string, std::string>& additionalData)
1229 {
1230 // Check that all PSUs have the same model name. Initialize the model
1231 // variable with the first PSU name found, then use it as a base to compare
1232 // against the rest of the PSUs and get its inventory path to use as callout
1233 // if needed.
1234 model.clear();
1235 std::string modelInventoryPath{};
1236 for (const auto& psu : psus)
1237 {
1238 auto psuModel = psu->getModelName();
1239 if (psuModel.empty())
1240 {
1241 continue;
1242 }
1243 if (model.empty())
1244 {
1245 model = psuModel;
1246 modelInventoryPath = psu->getInventoryPath();
1247 continue;
1248 }
1249 if (psuModel != model)
1250 {
1251 if (supportedConfigs.find(model) != supportedConfigs.end())
1252 {
1253 // The base model is supported, callout the mismatched PSU. The
1254 // mismatched PSU may or may not be supported.
1255 additionalData["EXPECTED_MODEL"] = model;
1256 additionalData["ACTUAL_MODEL"] = psuModel;
1257 additionalData["CALLOUT_INVENTORY_PATH"] =
1258 psu->getInventoryPath();
1259 }
1260 else if (supportedConfigs.find(psuModel) != supportedConfigs.end())
1261 {
1262 // The base model is not supported, but the mismatched PSU is,
1263 // callout the base PSU.
1264 additionalData["EXPECTED_MODEL"] = psuModel;
1265 additionalData["ACTUAL_MODEL"] = model;
1266 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath;
1267 }
1268 else
1269 {
1270 // The base model and the mismatched PSU are not supported or
1271 // could not be found in the supported configuration, callout
1272 // the mismatched PSU.
1273 additionalData["EXPECTED_MODEL"] = model;
1274 additionalData["ACTUAL_MODEL"] = psuModel;
1275 additionalData["CALLOUT_INVENTORY_PATH"] =
1276 psu->getInventoryPath();
1277 }
1278 model.clear();
1279 return false;
1280 }
1281 }
1282 return true;
1283 }
1284
setPowerConfigGPIO()1285 void PSUManager::setPowerConfigGPIO()
1286 {
1287 if (!powerConfigGPIO)
1288 {
1289 return;
1290 }
1291
1292 std::string model{};
1293 std::map<std::string, std::string> additionalData;
1294 if (!validateModelName(model, additionalData))
1295 {
1296 return;
1297 }
1298
1299 auto config = supportedConfigs.find(model);
1300 if (config != supportedConfigs.end())
1301 {
1302 // The power-config-full-load is an open drain GPIO. Set it to low (0)
1303 // if the supported configuration indicates that this system model
1304 // expects the maximum number of power supplies (full load set to true).
1305 // Else, set it to high (1), this is the default.
1306 auto powerConfigValue =
1307 (config->second.powerConfigFullLoad == true ? 0 : 1);
1308 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN;
1309 powerConfigGPIO->write(powerConfigValue, flags);
1310 }
1311 }
1312
buildDriverName(uint64_t i2cbus,uint64_t i2caddr)1313 void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr)
1314 {
1315 namespace fs = std::filesystem;
1316 std::stringstream ss;
1317 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
1318 std::string symLinkPath = deviceDirPath + std::to_string(i2cbus) + "-" +
1319 ss.str() + driverDirName;
1320 try
1321 {
1322 fs::path linkStrPath = fs::read_symlink(symLinkPath);
1323 driverName = linkStrPath.filename();
1324 }
1325 catch (const std::exception& e)
1326 {
1327 log<level::ERR>(fmt::format("Failed to find device driver {}, error {}",
1328 symLinkPath, e.what())
1329 .c_str());
1330 }
1331 }
1332
populateDriverName()1333 void PSUManager::populateDriverName()
1334 {
1335 std::string driverName;
1336 // Search in PSUs for driver name
1337 std::for_each(psus.begin(), psus.end(), [&driverName](auto& psu) {
1338 if (!psu->getDriverName().empty())
1339 {
1340 driverName = psu->getDriverName();
1341 }
1342 });
1343 // Assign driver name to all PSUs
1344 std::for_each(psus.begin(), psus.end(),
1345 [=](auto& psu) { psu->setDriverName(driverName); });
1346 }
1347 } // namespace phosphor::power::manager
1348