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