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