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