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