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