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