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