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