1 #include "psu_manager.hpp" 2 3 #include "utility.hpp" 4 5 #include <fmt/format.h> 6 #include <sys/types.h> 7 #include <unistd.h> 8 9 #include <regex> 10 11 using namespace phosphor::logging; 12 13 namespace phosphor::power::manager 14 { 15 constexpr auto managerBusName = "xyz.openbmc_project.Power.PSUMonitor"; 16 constexpr auto objectManagerObjPath = 17 "/xyz/openbmc_project/power/power_supplies"; 18 constexpr auto powerSystemsInputsObjPath = 19 "/xyz/openbmc_project/power/power_supplies/chassis0/psus"; 20 21 constexpr auto IBMCFFPSInterface = 22 "xyz.openbmc_project.Configuration.IBMCFFPSConnector"; 23 constexpr auto i2cBusProp = "I2CBus"; 24 constexpr auto i2cAddressProp = "I2CAddress"; 25 constexpr auto psuNameProp = "Name"; 26 constexpr auto presLineName = "NamedPresenceGpio"; 27 28 constexpr auto supportedConfIntf = 29 "xyz.openbmc_project.Configuration.SupportedConfiguration"; 30 31 PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) : 32 bus(bus), powerSystemInputs(bus, powerSystemsInputsObjPath), 33 objectManager(bus, objectManagerObjPath), 34 historyManager(bus, "/org/open_power/sensors") 35 { 36 // Subscribe to InterfacesAdded before doing a property read, otherwise 37 // the interface could be created after the read attempt but before the 38 // match is created. 39 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>( 40 bus, 41 sdbusplus::bus::match::rules::interfacesAdded() + 42 sdbusplus::bus::match::rules::sender( 43 "xyz.openbmc_project.EntityManager"), 44 std::bind(&PSUManager::entityManagerIfaceAdded, this, 45 std::placeholders::_1)); 46 getPSUConfiguration(); 47 getSystemProperties(); 48 49 // Request the bus name before the analyze() function, which is the one that 50 // determines the brownout condition and sets the status d-bus property. 51 bus.request_name(managerBusName); 52 53 using namespace sdeventplus; 54 auto interval = std::chrono::milliseconds(1000); 55 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 56 e, std::bind(&PSUManager::analyze, this), interval); 57 58 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 59 e, std::bind(&PSUManager::validateConfig, this)); 60 61 try 62 { 63 powerConfigGPIO = createGPIO("power-config-full-load"); 64 } 65 catch (const std::exception& e) 66 { 67 // Ignore error, GPIO may not be implemented in this system. 68 powerConfigGPIO = nullptr; 69 } 70 71 // Subscribe to power state changes 72 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus); 73 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>( 74 bus, 75 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH, 76 POWER_IFACE), 77 [this](auto& msg) { this->powerStateChanged(msg); }); 78 79 initialize(); 80 } 81 82 void PSUManager::getPSUConfiguration() 83 { 84 using namespace phosphor::power::util; 85 auto depth = 0; 86 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 87 88 psus.clear(); 89 90 // I should get a map of objects back. 91 // Each object will have a path, a service, and an interface. 92 // The interface should match the one passed into this function. 93 for (const auto& [path, services] : objects) 94 { 95 auto service = services.begin()->first; 96 97 if (path.empty() || service.empty()) 98 { 99 continue; 100 } 101 102 // For each object in the array of objects, I want to get properties 103 // from the service, path, and interface. 104 auto properties = 105 getAllProperties(bus, path, IBMCFFPSInterface, service); 106 107 getPSUProperties(properties); 108 } 109 110 if (psus.empty()) 111 { 112 // Interface or properties not found. Let the Interfaces Added callback 113 // process the information once the interfaces are added to D-Bus. 114 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 115 } 116 } 117 118 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties) 119 { 120 // From passed in properties, I want to get: I2CBus, I2CAddress, 121 // and Name. Create a power supply object, using Name to build the inventory 122 // path. 123 const auto basePSUInvPath = 124 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply"; 125 uint64_t* i2cbus = nullptr; 126 uint64_t* i2caddr = nullptr; 127 std::string* psuname = nullptr; 128 std::string* preslineptr = nullptr; 129 130 for (const auto& property : properties) 131 { 132 try 133 { 134 if (property.first == i2cBusProp) 135 { 136 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]); 137 } 138 else if (property.first == i2cAddressProp) 139 { 140 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]); 141 } 142 else if (property.first == psuNameProp) 143 { 144 psuname = std::get_if<std::string>(&properties[psuNameProp]); 145 } 146 else if (property.first == presLineName) 147 { 148 preslineptr = 149 std::get_if<std::string>(&properties[presLineName]); 150 } 151 } 152 catch (const std::exception& e) 153 {} 154 } 155 156 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty())) 157 { 158 std::string invpath = basePSUInvPath; 159 invpath.push_back(psuname->back()); 160 std::string presline = ""; 161 162 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str()); 163 164 if (nullptr != preslineptr) 165 { 166 presline = *preslineptr; 167 } 168 169 auto invMatch = 170 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) { 171 return psu->getInventoryPath() == invpath; 172 }); 173 if (invMatch != psus.end()) 174 { 175 // This power supply has the same inventory path as the one with 176 // information just added to D-Bus. 177 // Changes to GPIO line name unlikely, so skip checking. 178 // Changes to the I2C bus and address unlikely, as that would 179 // require corresponding device tree updates. 180 // Return out to avoid duplicate object creation. 181 return; 182 } 183 184 constexpr auto driver = "ibm-cffps"; 185 log<level::DEBUG>( 186 fmt::format( 187 "make PowerSupply bus: {} addr: {} driver: {} presline: {}", 188 *i2cbus, *i2caddr, driver, presline) 189 .c_str()); 190 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus, 191 *i2caddr, driver, presline); 192 psus.emplace_back(std::move(psu)); 193 194 // Subscribe to power supply presence changes 195 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>( 196 bus, 197 sdbusplus::bus::match::rules::propertiesChanged(invpath, 198 INVENTORY_IFACE), 199 [this](auto& msg) { this->presenceChanged(msg); }); 200 presenceMatches.emplace_back(std::move(presenceMatch)); 201 } 202 203 if (psus.empty()) 204 { 205 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 206 } 207 } 208 209 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties) 210 { 211 try 212 { 213 auto propIt = properties.find("SupportedType"); 214 if (propIt == properties.end()) 215 { 216 return; 217 } 218 const std::string* type = std::get_if<std::string>(&(propIt->second)); 219 if ((type == nullptr) || (*type != "PowerSupply")) 220 { 221 return; 222 } 223 224 propIt = properties.find("SupportedModel"); 225 if (propIt == properties.end()) 226 { 227 return; 228 } 229 const std::string* model = std::get_if<std::string>(&(propIt->second)); 230 if (model == nullptr) 231 { 232 return; 233 } 234 235 sys_properties sys; 236 propIt = properties.find("RedundantCount"); 237 if (propIt != properties.end()) 238 { 239 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second)); 240 if (count != nullptr) 241 { 242 sys.powerSupplyCount = *count; 243 } 244 } 245 propIt = properties.find("InputVoltage"); 246 if (propIt != properties.end()) 247 { 248 const std::vector<uint64_t>* voltage = 249 std::get_if<std::vector<uint64_t>>(&(propIt->second)); 250 if (voltage != nullptr) 251 { 252 sys.inputVoltage = *voltage; 253 } 254 } 255 256 // The PowerConfigFullLoad is an optional property, default it to false 257 // since that's the default value of the power-config-full-load GPIO. 258 sys.powerConfigFullLoad = false; 259 propIt = properties.find("PowerConfigFullLoad"); 260 if (propIt != properties.end()) 261 { 262 const bool* fullLoad = std::get_if<bool>(&(propIt->second)); 263 if (fullLoad != nullptr) 264 { 265 sys.powerConfigFullLoad = *fullLoad; 266 } 267 } 268 269 supportedConfigs.emplace(*model, sys); 270 } 271 catch (const std::exception& e) 272 {} 273 } 274 275 void PSUManager::getSystemProperties() 276 { 277 278 try 279 { 280 util::DbusSubtree subtree = 281 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0); 282 if (subtree.empty()) 283 { 284 throw std::runtime_error("Supported Configuration Not Found"); 285 } 286 287 for (const auto& [objPath, services] : subtree) 288 { 289 std::string service = services.begin()->first; 290 if (objPath.empty() || service.empty()) 291 { 292 continue; 293 } 294 auto properties = util::getAllProperties( 295 bus, objPath, supportedConfIntf, service); 296 populateSysProperties(properties); 297 } 298 } 299 catch (const std::exception& e) 300 { 301 // Interface or property not found. Let the Interfaces Added callback 302 // process the information once the interfaces are added to D-Bus. 303 } 304 } 305 306 void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg) 307 { 308 try 309 { 310 sdbusplus::message::object_path objPath; 311 std::map<std::string, std::map<std::string, util::DbusVariant>> 312 interfaces; 313 msg.read(objPath, interfaces); 314 315 auto itIntf = interfaces.find(supportedConfIntf); 316 if (itIntf != interfaces.cend()) 317 { 318 populateSysProperties(itIntf->second); 319 } 320 321 itIntf = interfaces.find(IBMCFFPSInterface); 322 if (itIntf != interfaces.cend()) 323 { 324 log<level::INFO>( 325 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface) 326 .c_str()); 327 getPSUProperties(itIntf->second); 328 } 329 330 // Call to validate the psu configuration if the power is on and both 331 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been 332 // processed 333 if (powerOn && !psus.empty() && !supportedConfigs.empty()) 334 { 335 validationTimer->restartOnce(validationTimeout); 336 } 337 } 338 catch (const std::exception& e) 339 { 340 // Ignore, the property may be of a different type than expected. 341 } 342 } 343 344 void PSUManager::powerStateChanged(sdbusplus::message::message& msg) 345 { 346 int32_t state = 0; 347 std::string msgSensor; 348 std::map<std::string, std::variant<int32_t>> msgData; 349 msg.read(msgSensor, msgData); 350 351 // Check if it was the Present property that changed. 352 auto valPropMap = msgData.find("state"); 353 if (valPropMap != msgData.end()) 354 { 355 state = std::get<int32_t>(valPropMap->second); 356 357 // Power is on when state=1. Clear faults. 358 if (state) 359 { 360 powerOn = true; 361 validationTimer->restartOnce(validationTimeout); 362 clearFaults(); 363 setPowerConfigGPIO(); 364 } 365 else 366 { 367 powerOn = false; 368 runValidateConfig = true; 369 } 370 } 371 } 372 373 void PSUManager::presenceChanged(sdbusplus::message::message& msg) 374 { 375 std::string msgSensor; 376 std::map<std::string, std::variant<uint32_t, bool>> msgData; 377 msg.read(msgSensor, msgData); 378 379 // Check if it was the Present property that changed. 380 auto valPropMap = msgData.find(PRESENT_PROP); 381 if (valPropMap != msgData.end()) 382 { 383 if (std::get<bool>(valPropMap->second)) 384 { 385 // A PSU became present, force the PSU validation to run. 386 runValidateConfig = true; 387 validationTimer->restartOnce(validationTimeout); 388 } 389 } 390 } 391 392 void PSUManager::setPowerSupplyError(const std::string& psuErrorString) 393 { 394 using namespace sdbusplus::xyz::openbmc_project; 395 constexpr auto service = "org.openbmc.control.Power"; 396 constexpr auto objPath = "/org/openbmc/control/power0"; 397 constexpr auto interface = "org.openbmc.control.Power"; 398 constexpr auto method = "setPowerSupplyError"; 399 400 try 401 { 402 // Call D-Bus method to inform pseq of PSU error 403 auto methodMsg = 404 bus.new_method_call(service, objPath, interface, method); 405 methodMsg.append(psuErrorString); 406 auto callReply = bus.call(methodMsg); 407 } 408 catch (const std::exception& e) 409 { 410 log<level::INFO>( 411 fmt::format("Failed calling setPowerSupplyError due to error {}", 412 e.what()) 413 .c_str()); 414 } 415 } 416 417 void PSUManager::createError(const std::string& faultName, 418 std::map<std::string, std::string>& additionalData) 419 { 420 using namespace sdbusplus::xyz::openbmc_project; 421 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging"; 422 constexpr auto loggingCreateInterface = 423 "xyz.openbmc_project.Logging.Create"; 424 425 try 426 { 427 additionalData["_PID"] = std::to_string(getpid()); 428 429 auto service = 430 util::getService(loggingObjectPath, loggingCreateInterface, bus); 431 432 if (service.empty()) 433 { 434 log<level::ERR>("Unable to get logging manager service"); 435 return; 436 } 437 438 auto method = bus.new_method_call(service.c_str(), loggingObjectPath, 439 loggingCreateInterface, "Create"); 440 441 auto level = Logging::server::Entry::Level::Error; 442 method.append(faultName, level, additionalData); 443 444 auto reply = bus.call(method); 445 setPowerSupplyError(faultName); 446 } 447 catch (const std::exception& e) 448 { 449 log<level::ERR>( 450 fmt::format( 451 "Failed creating event log for fault {} due to error {}", 452 faultName, e.what()) 453 .c_str()); 454 } 455 } 456 457 void PSUManager::analyze() 458 { 459 for (auto& psu : psus) 460 { 461 psu->analyze(); 462 } 463 464 std::map<std::string, std::string> additionalData; 465 466 auto notPresentCount = decltype(psus.size())( 467 std::count_if(psus.begin(), psus.end(), 468 [](const auto& psu) { return !psu->isPresent(); })); 469 470 auto hasVINUVFaultCount = decltype(psus.size())( 471 std::count_if(psus.begin(), psus.end(), 472 [](const auto& psu) { return psu->hasVINUVFault(); })); 473 474 // The PSU D-Bus objects may not be available yet, so ignore if all 475 // PSUs are not present or the number of PSUs is still 0. 476 if ((psus.size() == (notPresentCount + hasVINUVFaultCount)) && 477 (psus.size() != notPresentCount) && (psus.size() != 0)) 478 { 479 // Brownout: All PSUs report an AC failure: At least one PSU reports 480 // AC loss VIN fault and the rest either report AC loss VIN fault as 481 // well or are not present. 482 additionalData["NOT_PRESENT_COUNT"] = std::to_string(notPresentCount); 483 additionalData["VIN_FAULT_COUNT"] = std::to_string(hasVINUVFaultCount); 484 setBrownout(additionalData); 485 } 486 else 487 { 488 // Brownout condition is not present or has been cleared 489 clearBrownout(); 490 } 491 492 if (powerOn) 493 { 494 for (auto& psu : psus) 495 { 496 additionalData.clear(); 497 498 if (!psu->isFaultLogged() && !psu->isPresent()) 499 { 500 std::map<std::string, std::string> requiredPSUsData; 501 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData); 502 if (!requiredPSUsPresent) 503 { 504 additionalData.merge(requiredPSUsData); 505 // Create error for power supply missing. 506 additionalData["CALLOUT_INVENTORY_PATH"] = 507 psu->getInventoryPath(); 508 additionalData["CALLOUT_PRIORITY"] = "H"; 509 createError( 510 "xyz.openbmc_project.Power.PowerSupply.Error.Missing", 511 additionalData); 512 } 513 psu->setFaultLogged(); 514 } 515 else if (!psu->isFaultLogged() && psu->isFaulted()) 516 { 517 // Add STATUS_WORD and STATUS_MFR last response, in padded 518 // hexadecimal format. 519 additionalData["STATUS_WORD"] = 520 fmt::format("{:#04x}", psu->getStatusWord()); 521 additionalData["STATUS_MFR"] = 522 fmt::format("{:#02x}", psu->getMFRFault()); 523 // If there are faults being reported, they possibly could be 524 // related to a bug in the firmware version running on the power 525 // supply. Capture that data into the error as well. 526 additionalData["FW_VERSION"] = psu->getFWVersion(); 527 528 if (psu->hasCommFault()) 529 { 530 additionalData["STATUS_CML"] = 531 fmt::format("{:#02x}", psu->getStatusCML()); 532 /* Attempts to communicate with the power supply have 533 * reached there limit. Create an error. */ 534 additionalData["CALLOUT_DEVICE_PATH"] = 535 psu->getDevicePath(); 536 537 createError( 538 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault", 539 additionalData); 540 541 psu->setFaultLogged(); 542 } 543 else if ((psu->hasInputFault() || psu->hasVINUVFault())) 544 { 545 // Include STATUS_INPUT for input faults. 546 additionalData["STATUS_INPUT"] = 547 fmt::format("{:#02x}", psu->getStatusInput()); 548 549 /* The power supply location might be needed if the input 550 * fault is due to a problem with the power supply itself. 551 * Include the inventory path with a call out priority of 552 * low. 553 */ 554 additionalData["CALLOUT_INVENTORY_PATH"] = 555 psu->getInventoryPath(); 556 additionalData["CALLOUT_PRIORITY"] = "L"; 557 createError("xyz.openbmc_project.Power.PowerSupply.Error." 558 "InputFault", 559 additionalData); 560 psu->setFaultLogged(); 561 } 562 else if (psu->hasPSKillFault()) 563 { 564 createError( 565 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault", 566 additionalData); 567 psu->setFaultLogged(); 568 } 569 else if (psu->hasVoutOVFault()) 570 { 571 // Include STATUS_VOUT for Vout faults. 572 additionalData["STATUS_VOUT"] = 573 fmt::format("{:#02x}", psu->getStatusVout()); 574 575 additionalData["CALLOUT_INVENTORY_PATH"] = 576 psu->getInventoryPath(); 577 578 createError( 579 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 580 additionalData); 581 582 psu->setFaultLogged(); 583 } 584 else if (psu->hasIoutOCFault()) 585 { 586 // Include STATUS_IOUT for Iout faults. 587 additionalData["STATUS_IOUT"] = 588 fmt::format("{:#02x}", psu->getStatusIout()); 589 590 createError( 591 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault", 592 additionalData); 593 594 psu->setFaultLogged(); 595 } 596 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() || 597 psu->hasPSCS12VFault()) 598 { 599 // Include STATUS_VOUT for Vout faults. 600 additionalData["STATUS_VOUT"] = 601 fmt::format("{:#02x}", psu->getStatusVout()); 602 603 additionalData["CALLOUT_INVENTORY_PATH"] = 604 psu->getInventoryPath(); 605 606 createError( 607 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 608 additionalData); 609 610 psu->setFaultLogged(); 611 } 612 // A fan fault should have priority over a temperature fault, 613 // since a failed fan may lead to a temperature problem. 614 else if (psu->hasFanFault()) 615 { 616 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2 617 additionalData["STATUS_TEMPERATURE"] = 618 fmt::format("{:#02x}", psu->getStatusTemperature()); 619 additionalData["STATUS_FANS_1_2"] = 620 fmt::format("{:#02x}", psu->getStatusFans12()); 621 622 additionalData["CALLOUT_INVENTORY_PATH"] = 623 psu->getInventoryPath(); 624 625 createError( 626 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault", 627 additionalData); 628 629 psu->setFaultLogged(); 630 } 631 else if (psu->hasTempFault()) 632 { 633 // Include STATUS_TEMPERATURE for temperature faults. 634 additionalData["STATUS_TEMPERATURE"] = 635 fmt::format("{:#02x}", psu->getStatusTemperature()); 636 637 additionalData["CALLOUT_INVENTORY_PATH"] = 638 psu->getInventoryPath(); 639 640 createError( 641 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 642 additionalData); 643 644 psu->setFaultLogged(); 645 } 646 else if (psu->hasMFRFault()) 647 { 648 /* This can represent a variety of faults that result in 649 * calling out the power supply for replacement: Output 650 * OverCurrent, Output Under Voltage, and potentially other 651 * faults. 652 * 653 * Also plan on putting specific fault in AdditionalData, 654 * along with register names and register values 655 * (STATUS_WORD, STATUS_MFR, etc.).*/ 656 657 additionalData["CALLOUT_INVENTORY_PATH"] = 658 psu->getInventoryPath(); 659 660 createError( 661 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 662 additionalData); 663 664 psu->setFaultLogged(); 665 } 666 else if (psu->hasPgoodFault()) 667 { 668 /* POWER_GOOD# is not low, or OFF is on */ 669 additionalData["CALLOUT_INVENTORY_PATH"] = 670 psu->getInventoryPath(); 671 672 createError( 673 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 674 additionalData); 675 676 psu->setFaultLogged(); 677 } 678 } 679 } 680 } 681 } 682 683 void PSUManager::validateConfig() 684 { 685 if (!runValidateConfig || supportedConfigs.empty() || psus.empty()) 686 { 687 return; 688 } 689 690 std::map<std::string, std::string> additionalData; 691 auto supported = hasRequiredPSUs(additionalData); 692 if (supported) 693 { 694 runValidateConfig = false; 695 return; 696 } 697 698 // Validation failed, create an error log. 699 // Return without setting the runValidateConfig flag to false because 700 // it may be that an additional supported configuration interface is 701 // added and we need to validate it to see if it matches this system. 702 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported", 703 additionalData); 704 } 705 706 bool PSUManager::hasRequiredPSUs( 707 std::map<std::string, std::string>& additionalData) 708 { 709 std::string model{}; 710 if (!validateModelName(model, additionalData)) 711 { 712 return false; 713 } 714 715 auto presentCount = 716 std::count_if(psus.begin(), psus.end(), 717 [](const auto& psu) { return psu->isPresent(); }); 718 719 // Validate the supported configurations. A system may support more than one 720 // power supply model configuration. Since all configurations need to be 721 // checked, the additional data would contain only the information of the 722 // last configuration that did not match. 723 std::map<std::string, std::string> tmpAdditionalData; 724 for (const auto& config : supportedConfigs) 725 { 726 if (config.first != model) 727 { 728 continue; 729 } 730 if (presentCount != config.second.powerSupplyCount) 731 { 732 tmpAdditionalData.clear(); 733 tmpAdditionalData["EXPECTED_COUNT"] = 734 std::to_string(config.second.powerSupplyCount); 735 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount); 736 continue; 737 } 738 739 bool voltageValidated = true; 740 for (const auto& psu : psus) 741 { 742 if (!psu->isPresent()) 743 { 744 // Only present PSUs report a valid input voltage 745 continue; 746 } 747 748 double actualInputVoltage; 749 int inputVoltage; 750 psu->getInputVoltage(actualInputVoltage, inputVoltage); 751 752 if (std::find(config.second.inputVoltage.begin(), 753 config.second.inputVoltage.end(), 754 inputVoltage) == config.second.inputVoltage.end()) 755 { 756 tmpAdditionalData.clear(); 757 tmpAdditionalData["ACTUAL_VOLTAGE"] = 758 std::to_string(actualInputVoltage); 759 for (const auto& voltage : config.second.inputVoltage) 760 { 761 tmpAdditionalData["EXPECTED_VOLTAGE"] += 762 std::to_string(voltage) + " "; 763 } 764 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] = 765 psu->getInventoryPath(); 766 767 voltageValidated = false; 768 break; 769 } 770 } 771 if (!voltageValidated) 772 { 773 continue; 774 } 775 776 return true; 777 } 778 779 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end()); 780 return false; 781 } 782 783 bool PSUManager::validateModelName( 784 std::string& model, std::map<std::string, std::string>& additionalData) 785 { 786 // Check that all PSUs have the same model name. Initialize the model 787 // variable with the first PSU name found, then use it as a base to compare 788 // against the rest of the PSUs and get its inventory path to use as callout 789 // if needed. 790 model.clear(); 791 std::string modelInventoryPath{}; 792 for (const auto& psu : psus) 793 { 794 auto psuModel = psu->getModelName(); 795 if (psuModel.empty()) 796 { 797 continue; 798 } 799 if (model.empty()) 800 { 801 model = psuModel; 802 modelInventoryPath = psu->getInventoryPath(); 803 continue; 804 } 805 if (psuModel != model) 806 { 807 if (supportedConfigs.find(model) != supportedConfigs.end()) 808 { 809 // The base model is supported, callout the mismatched PSU. The 810 // mismatched PSU may or may not be supported. 811 additionalData["EXPECTED_MODEL"] = model; 812 additionalData["ACTUAL_MODEL"] = psuModel; 813 additionalData["CALLOUT_INVENTORY_PATH"] = 814 psu->getInventoryPath(); 815 } 816 else if (supportedConfigs.find(psuModel) != supportedConfigs.end()) 817 { 818 // The base model is not supported, but the mismatched PSU is, 819 // callout the base PSU. 820 additionalData["EXPECTED_MODEL"] = psuModel; 821 additionalData["ACTUAL_MODEL"] = model; 822 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath; 823 } 824 else 825 { 826 // The base model and the mismatched PSU are not supported or 827 // could not be found in the supported configuration, callout 828 // the mismatched PSU. 829 additionalData["EXPECTED_MODEL"] = model; 830 additionalData["ACTUAL_MODEL"] = psuModel; 831 additionalData["CALLOUT_INVENTORY_PATH"] = 832 psu->getInventoryPath(); 833 } 834 model.clear(); 835 return false; 836 } 837 } 838 return true; 839 } 840 841 void PSUManager::setPowerConfigGPIO() 842 { 843 if (!powerConfigGPIO) 844 { 845 return; 846 } 847 848 std::string model{}; 849 std::map<std::string, std::string> additionalData; 850 if (!validateModelName(model, additionalData)) 851 { 852 return; 853 } 854 855 auto config = supportedConfigs.find(model); 856 if (config != supportedConfigs.end()) 857 { 858 // The power-config-full-load is an open drain GPIO. Set it to low (0) 859 // if the supported configuration indicates that this system model 860 // expects the maximum number of power supplies (full load set to true). 861 // Else, set it to high (1), this is the default. 862 auto powerConfigValue = 863 (config->second.powerConfigFullLoad == true ? 0 : 1); 864 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN; 865 powerConfigGPIO->write(powerConfigValue, flags); 866 } 867 } 868 869 void PSUManager::setBrownout(std::map<std::string, std::string>& additionalData) 870 { 871 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator:: 872 server::PowerSystemInputs::Status::Fault); 873 if (!brownoutLogged) 874 { 875 if (powerOn) 876 { 877 createError( 878 "xyz.openbmc_project.State.Shutdown.Power.Error.Blackout", 879 additionalData); 880 brownoutLogged = true; 881 } 882 } 883 } 884 885 void PSUManager::clearBrownout() 886 { 887 powerSystemInputs.status(sdbusplus::xyz::openbmc_project::State::Decorator:: 888 server::PowerSystemInputs::Status::Good); 889 brownoutLogged = false; 890 } 891 892 } // namespace phosphor::power::manager 893