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