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 log<level::INFO>("Synchronize INPUT_HISTORY"); 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 syncHistoryGPIO->toggleLowHigh(delay); 560 for (auto& psu : psus) 561 { 562 psu->clearSyncHistoryRequired(); 563 } 564 } 565 566 log<level::INFO>("Synchronize INPUT_HISTORY completed"); 567 } 568 569 void PSUManager::analyze() 570 { 571 auto syncHistoryRequired = std::any_of(psus.begin(), psus.end(), 572 [](const auto& psu) { 573 return psu->isSyncHistoryRequired(); 574 }); 575 if (syncHistoryRequired) 576 { 577 syncHistory(); 578 } 579 580 for (auto& psu : psus) 581 { 582 psu->analyze(); 583 } 584 585 analyzeBrownout(); 586 587 // Only perform individual PSU analysis if power is on and a brownout has 588 // not already been logged 589 if (powerOn && !brownoutLogged) 590 { 591 for (auto& psu : psus) 592 { 593 std::map<std::string, std::string> additionalData; 594 595 if (!psu->isFaultLogged() && !psu->isPresent()) 596 { 597 std::map<std::string, std::string> requiredPSUsData; 598 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData); 599 if (!requiredPSUsPresent && isRequiredPSU(*psu)) 600 { 601 additionalData.merge(requiredPSUsData); 602 // Create error for power supply missing. 603 additionalData["CALLOUT_INVENTORY_PATH"] = 604 psu->getInventoryPath(); 605 additionalData["CALLOUT_PRIORITY"] = "H"; 606 createError( 607 "xyz.openbmc_project.Power.PowerSupply.Error.Missing", 608 additionalData); 609 } 610 psu->setFaultLogged(); 611 } 612 else if (!psu->isFaultLogged() && psu->isFaulted()) 613 { 614 // Add STATUS_WORD and STATUS_MFR last response, in padded 615 // hexadecimal format. 616 additionalData["STATUS_WORD"] = 617 fmt::format("{:#04x}", psu->getStatusWord()); 618 additionalData["STATUS_MFR"] = fmt::format("{:#02x}", 619 psu->getMFRFault()); 620 // If there are faults being reported, they possibly could be 621 // related to a bug in the firmware version running on the power 622 // supply. Capture that data into the error as well. 623 additionalData["FW_VERSION"] = psu->getFWVersion(); 624 625 if (psu->hasCommFault()) 626 { 627 additionalData["STATUS_CML"] = 628 fmt::format("{:#02x}", psu->getStatusCML()); 629 /* Attempts to communicate with the power supply have 630 * reached there limit. Create an error. */ 631 additionalData["CALLOUT_DEVICE_PATH"] = 632 psu->getDevicePath(); 633 634 createError( 635 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault", 636 additionalData); 637 638 psu->setFaultLogged(); 639 } 640 else if ((psu->hasInputFault() || psu->hasVINUVFault())) 641 { 642 // Include STATUS_INPUT for input faults. 643 additionalData["STATUS_INPUT"] = 644 fmt::format("{:#02x}", psu->getStatusInput()); 645 646 /* The power supply location might be needed if the input 647 * fault is due to a problem with the power supply itself. 648 * Include the inventory path with a call out priority of 649 * low. 650 */ 651 additionalData["CALLOUT_INVENTORY_PATH"] = 652 psu->getInventoryPath(); 653 additionalData["CALLOUT_PRIORITY"] = "L"; 654 createError("xyz.openbmc_project.Power.PowerSupply.Error." 655 "InputFault", 656 additionalData); 657 psu->setFaultLogged(); 658 } 659 else if (psu->hasPSKillFault()) 660 { 661 createError( 662 "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault", 663 additionalData); 664 psu->setFaultLogged(); 665 } 666 else if (psu->hasVoutOVFault()) 667 { 668 // Include STATUS_VOUT for Vout faults. 669 additionalData["STATUS_VOUT"] = 670 fmt::format("{:#02x}", psu->getStatusVout()); 671 672 additionalData["CALLOUT_INVENTORY_PATH"] = 673 psu->getInventoryPath(); 674 675 createError( 676 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 677 additionalData); 678 679 psu->setFaultLogged(); 680 } 681 else if (psu->hasIoutOCFault()) 682 { 683 // Include STATUS_IOUT for Iout faults. 684 additionalData["STATUS_IOUT"] = 685 fmt::format("{:#02x}", psu->getStatusIout()); 686 687 createError( 688 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault", 689 additionalData); 690 691 psu->setFaultLogged(); 692 } 693 else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() || 694 psu->hasPSCS12VFault()) 695 { 696 // Include STATUS_VOUT for Vout faults. 697 additionalData["STATUS_VOUT"] = 698 fmt::format("{:#02x}", psu->getStatusVout()); 699 700 additionalData["CALLOUT_INVENTORY_PATH"] = 701 psu->getInventoryPath(); 702 703 createError( 704 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 705 additionalData); 706 707 psu->setFaultLogged(); 708 } 709 // A fan fault should have priority over a temperature fault, 710 // since a failed fan may lead to a temperature problem. 711 // Only process if not in power fault window. 712 else if (psu->hasFanFault() && !powerFaultOccurring) 713 { 714 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2 715 additionalData["STATUS_TEMPERATURE"] = 716 fmt::format("{:#02x}", psu->getStatusTemperature()); 717 additionalData["STATUS_FANS_1_2"] = 718 fmt::format("{:#02x}", psu->getStatusFans12()); 719 720 additionalData["CALLOUT_INVENTORY_PATH"] = 721 psu->getInventoryPath(); 722 723 createError( 724 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault", 725 additionalData); 726 727 psu->setFaultLogged(); 728 } 729 else if (psu->hasTempFault()) 730 { 731 // Include STATUS_TEMPERATURE for temperature faults. 732 additionalData["STATUS_TEMPERATURE"] = 733 fmt::format("{:#02x}", psu->getStatusTemperature()); 734 735 additionalData["CALLOUT_INVENTORY_PATH"] = 736 psu->getInventoryPath(); 737 738 createError( 739 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 740 additionalData); 741 742 psu->setFaultLogged(); 743 } 744 else if (psu->hasMFRFault()) 745 { 746 /* This can represent a variety of faults that result in 747 * calling out the power supply for replacement: Output 748 * OverCurrent, Output Under Voltage, and potentially other 749 * faults. 750 * 751 * Also plan on putting specific fault in AdditionalData, 752 * along with register names and register values 753 * (STATUS_WORD, STATUS_MFR, etc.).*/ 754 755 additionalData["CALLOUT_INVENTORY_PATH"] = 756 psu->getInventoryPath(); 757 758 createError( 759 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 760 additionalData); 761 762 psu->setFaultLogged(); 763 } 764 // Only process if not in power fault window. 765 else if (psu->hasPgoodFault() && !powerFaultOccurring) 766 { 767 /* POWER_GOOD# is not low, or OFF is on */ 768 additionalData["CALLOUT_INVENTORY_PATH"] = 769 psu->getInventoryPath(); 770 771 createError( 772 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 773 additionalData); 774 775 psu->setFaultLogged(); 776 } 777 } 778 } 779 } 780 } 781 782 void PSUManager::analyzeBrownout() 783 { 784 // Count number of power supplies failing 785 size_t presentCount = 0; 786 size_t notPresentCount = 0; 787 size_t acFailedCount = 0; 788 size_t pgoodFailedCount = 0; 789 for (const auto& psu : psus) 790 { 791 if (psu->isPresent()) 792 { 793 ++presentCount; 794 if (psu->hasACFault()) 795 { 796 ++acFailedCount; 797 } 798 else if (psu->hasPgoodFault()) 799 { 800 ++pgoodFailedCount; 801 } 802 } 803 else 804 { 805 ++notPresentCount; 806 } 807 } 808 809 // Only issue brownout failure if chassis pgood has failed, it has not 810 // already been logged, at least one PSU has seen an AC fail, and all 811 // present PSUs have an AC or pgood failure. Note an AC fail is only set if 812 // at least one PSU is present. 813 if (powerFaultOccurring && !brownoutLogged && acFailedCount && 814 (presentCount == (acFailedCount + pgoodFailedCount))) 815 { 816 // Indicate that the system is in a brownout condition by creating an 817 // error log and setting the PowerSystemInputs status property to Fault. 818 powerSystemInputs.status( 819 sdbusplus::xyz::openbmc_project::State::Decorator::server:: 820 PowerSystemInputs::Status::Fault); 821 822 std::map<std::string, std::string> additionalData; 823 additionalData.emplace("NOT_PRESENT_COUNT", 824 std::to_string(notPresentCount)); 825 additionalData.emplace("VIN_FAULT_COUNT", 826 std::to_string(acFailedCount)); 827 additionalData.emplace("PGOOD_FAULT_COUNT", 828 std::to_string(pgoodFailedCount)); 829 log<level::INFO>( 830 fmt::format( 831 "Brownout detected, not present count: {}, AC fault count {}, pgood fault count: {}", 832 notPresentCount, acFailedCount, pgoodFailedCount) 833 .c_str()); 834 835 createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout", 836 additionalData); 837 brownoutLogged = true; 838 } 839 else 840 { 841 // If a brownout was previously logged but at least one PSU is not 842 // currently in AC fault, determine if the brownout condition can be 843 // cleared 844 if (brownoutLogged && (acFailedCount < presentCount)) 845 { 846 // Chassis only recognizes the PowerSystemInputs change when it is 847 // off 848 try 849 { 850 using PowerState = sdbusplus::xyz::openbmc_project::State:: 851 server::Chassis::PowerState; 852 PowerState currentPowerState; 853 util::getProperty<PowerState>( 854 "xyz.openbmc_project.State.Chassis", "CurrentPowerState", 855 "/xyz/openbmc_project/state/chassis0", 856 "xyz.openbmc_project.State.Chassis", bus, 857 currentPowerState); 858 859 if (currentPowerState == PowerState::Off) 860 { 861 // Indicate that the system is no longer in a brownout 862 // condition by setting the PowerSystemInputs status 863 // property to Good. 864 log<level::INFO>( 865 fmt::format( 866 "Brownout cleared, not present count: {}, AC fault count {}, pgood fault count: {}", 867 notPresentCount, acFailedCount, pgoodFailedCount) 868 .c_str()); 869 powerSystemInputs.status( 870 sdbusplus::xyz::openbmc_project::State::Decorator:: 871 server::PowerSystemInputs::Status::Good); 872 brownoutLogged = false; 873 } 874 } 875 catch (const std::exception& e) 876 { 877 log<level::ERR>( 878 fmt::format("Error trying to clear brownout, error: {}", 879 e.what()) 880 .c_str()); 881 } 882 } 883 } 884 } 885 886 void PSUManager::updateMissingPSUs() 887 { 888 if (supportedConfigs.empty() || psus.empty()) 889 { 890 return; 891 } 892 893 // Power supplies default to missing. If the power supply is present, 894 // the PowerSupply object will update the inventory Present property to 895 // true. If we have less than the required number of power supplies, and 896 // this power supply is missing, update the inventory Present property 897 // to false to indicate required power supply is missing. Avoid 898 // indicating power supply missing if not required. 899 900 auto presentCount = 901 std::count_if(psus.begin(), psus.end(), 902 [](const auto& psu) { return psu->isPresent(); }); 903 904 for (const auto& config : supportedConfigs) 905 { 906 for (const auto& psu : psus) 907 { 908 auto psuModel = psu->getModelName(); 909 auto psuShortName = psu->getShortName(); 910 auto psuInventoryPath = psu->getInventoryPath(); 911 auto relativeInvPath = 912 psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH)); 913 auto psuPresent = psu->isPresent(); 914 auto presProperty = false; 915 auto propReadFail = false; 916 917 try 918 { 919 presProperty = getPresence(bus, psuInventoryPath); 920 propReadFail = false; 921 } 922 catch (const sdbusplus::exception_t& e) 923 { 924 propReadFail = true; 925 // Relying on property change or interface added to retry. 926 // Log an informational trace to the journal. 927 log<level::INFO>( 928 fmt::format("D-Bus property {} access failure exception", 929 psuInventoryPath) 930 .c_str()); 931 } 932 933 if (psuModel.empty()) 934 { 935 if (!propReadFail && (presProperty != psuPresent)) 936 { 937 // We already have this property, and it is not false 938 // set Present to false 939 setPresence(bus, relativeInvPath, psuPresent, psuShortName); 940 } 941 continue; 942 } 943 944 if (config.first != psuModel) 945 { 946 continue; 947 } 948 949 if ((presentCount < config.second.powerSupplyCount) && !psuPresent) 950 { 951 setPresence(bus, relativeInvPath, psuPresent, psuShortName); 952 } 953 } 954 } 955 } 956 957 void PSUManager::validateConfig() 958 { 959 if (!runValidateConfig || supportedConfigs.empty() || psus.empty()) 960 { 961 return; 962 } 963 964 for (const auto& psu : psus) 965 { 966 if ((psu->hasInputFault() || psu->hasVINUVFault())) 967 { 968 // Do not try to validate if input voltage fault present. 969 validationTimer->restartOnce(validationTimeout); 970 return; 971 } 972 } 973 974 std::map<std::string, std::string> additionalData; 975 auto supported = hasRequiredPSUs(additionalData); 976 if (supported) 977 { 978 runValidateConfig = false; 979 double actualVoltage; 980 int inputVoltage; 981 int previousInputVoltage = 0; 982 bool voltageMismatch = false; 983 984 for (const auto& psu : psus) 985 { 986 if (!psu->isPresent()) 987 { 988 // Only present PSUs report a valid input voltage 989 continue; 990 } 991 psu->getInputVoltage(actualVoltage, inputVoltage); 992 if (previousInputVoltage && inputVoltage && 993 (previousInputVoltage != inputVoltage)) 994 { 995 additionalData["EXPECTED_VOLTAGE"] = 996 std::to_string(previousInputVoltage); 997 additionalData["ACTUAL_VOLTAGE"] = 998 std::to_string(actualVoltage); 999 voltageMismatch = true; 1000 } 1001 if (!previousInputVoltage && inputVoltage) 1002 { 1003 previousInputVoltage = inputVoltage; 1004 } 1005 } 1006 if (!voltageMismatch) 1007 { 1008 return; 1009 } 1010 } 1011 1012 // Validation failed, create an error log. 1013 // Return without setting the runValidateConfig flag to false because 1014 // it may be that an additional supported configuration interface is 1015 // added and we need to validate it to see if it matches this system. 1016 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported", 1017 additionalData); 1018 } 1019 1020 bool PSUManager::hasRequiredPSUs( 1021 std::map<std::string, std::string>& additionalData) 1022 { 1023 std::string model{}; 1024 if (!validateModelName(model, additionalData)) 1025 { 1026 return false; 1027 } 1028 1029 auto presentCount = 1030 std::count_if(psus.begin(), psus.end(), 1031 [](const auto& psu) { return psu->isPresent(); }); 1032 1033 // Validate the supported configurations. A system may support more than one 1034 // power supply model configuration. Since all configurations need to be 1035 // checked, the additional data would contain only the information of the 1036 // last configuration that did not match. 1037 std::map<std::string, std::string> tmpAdditionalData; 1038 for (const auto& config : supportedConfigs) 1039 { 1040 if (config.first != model) 1041 { 1042 continue; 1043 } 1044 1045 // Number of power supplies present should equal or exceed the expected 1046 // count 1047 if (presentCount < config.second.powerSupplyCount) 1048 { 1049 tmpAdditionalData.clear(); 1050 tmpAdditionalData["EXPECTED_COUNT"] = 1051 std::to_string(config.second.powerSupplyCount); 1052 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount); 1053 continue; 1054 } 1055 1056 bool voltageValidated = true; 1057 for (const auto& psu : psus) 1058 { 1059 if (!psu->isPresent()) 1060 { 1061 // Only present PSUs report a valid input voltage 1062 continue; 1063 } 1064 1065 double actualInputVoltage; 1066 int inputVoltage; 1067 psu->getInputVoltage(actualInputVoltage, inputVoltage); 1068 1069 if (std::find(config.second.inputVoltage.begin(), 1070 config.second.inputVoltage.end(), 1071 inputVoltage) == config.second.inputVoltage.end()) 1072 { 1073 tmpAdditionalData.clear(); 1074 tmpAdditionalData["ACTUAL_VOLTAGE"] = 1075 std::to_string(actualInputVoltage); 1076 for (const auto& voltage : config.second.inputVoltage) 1077 { 1078 tmpAdditionalData["EXPECTED_VOLTAGE"] += 1079 std::to_string(voltage) + " "; 1080 } 1081 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] = 1082 psu->getInventoryPath(); 1083 1084 voltageValidated = false; 1085 break; 1086 } 1087 } 1088 if (!voltageValidated) 1089 { 1090 continue; 1091 } 1092 1093 return true; 1094 } 1095 1096 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end()); 1097 return false; 1098 } 1099 1100 unsigned int PSUManager::getRequiredPSUCount() 1101 { 1102 unsigned int requiredCount{0}; 1103 1104 // Verify we have the supported configuration and PSU information 1105 if (!supportedConfigs.empty() && !psus.empty()) 1106 { 1107 // Find PSU models. They should all be the same. 1108 std::set<std::string> models{}; 1109 std::for_each(psus.begin(), psus.end(), [&models](const auto& psu) { 1110 if (!psu->getModelName().empty()) 1111 { 1112 models.insert(psu->getModelName()); 1113 } 1114 }); 1115 1116 // If exactly one model was found, find corresponding configuration 1117 if (models.size() == 1) 1118 { 1119 const std::string& model = *(models.begin()); 1120 auto it = supportedConfigs.find(model); 1121 if (it != supportedConfigs.end()) 1122 { 1123 requiredCount = it->second.powerSupplyCount; 1124 } 1125 } 1126 } 1127 1128 return requiredCount; 1129 } 1130 1131 bool PSUManager::isRequiredPSU(const PowerSupply& psu) 1132 { 1133 // Get required number of PSUs; if not found, we don't know if PSU required 1134 unsigned int requiredCount = getRequiredPSUCount(); 1135 if (requiredCount == 0) 1136 { 1137 return false; 1138 } 1139 1140 // If total PSU count <= the required count, all PSUs are required 1141 if (psus.size() <= requiredCount) 1142 { 1143 return true; 1144 } 1145 1146 // We don't currently get information from EntityManager about which PSUs 1147 // are required, so we have to do some guesswork. First check if this PSU 1148 // is present. If so, assume it is required. 1149 if (psu.isPresent()) 1150 { 1151 return true; 1152 } 1153 1154 // This PSU is not present. Count the number of other PSUs that are 1155 // present. If enough other PSUs are present, assume the specified PSU is 1156 // not required. 1157 unsigned int psuCount = 1158 std::count_if(psus.begin(), psus.end(), 1159 [](const auto& psu) { return psu->isPresent(); }); 1160 if (psuCount >= requiredCount) 1161 { 1162 return false; 1163 } 1164 1165 // Check if this PSU was previously present. If so, assume it is required. 1166 // We know it was previously present if it has a non-empty model name. 1167 if (!psu.getModelName().empty()) 1168 { 1169 return true; 1170 } 1171 1172 // This PSU was never present. Count the number of other PSUs that were 1173 // previously present. If including those PSUs is enough, assume the 1174 // specified PSU is not required. 1175 psuCount += std::count_if(psus.begin(), psus.end(), [](const auto& psu) { 1176 return (!psu->isPresent() && !psu->getModelName().empty()); 1177 }); 1178 if (psuCount >= requiredCount) 1179 { 1180 return false; 1181 } 1182 1183 // We still haven't found enough PSUs. Sort the inventory paths of PSUs 1184 // that were never present. PSU inventory paths typically end with the PSU 1185 // number (0, 1, 2, ...). Assume that lower-numbered PSUs are required. 1186 std::vector<std::string> sortedPaths; 1187 std::for_each(psus.begin(), psus.end(), [&sortedPaths](const auto& psu) { 1188 if (!psu->isPresent() && psu->getModelName().empty()) 1189 { 1190 sortedPaths.push_back(psu->getInventoryPath()); 1191 } 1192 }); 1193 std::sort(sortedPaths.begin(), sortedPaths.end()); 1194 1195 // Check if specified PSU is close enough to start of list to be required 1196 for (const auto& path : sortedPaths) 1197 { 1198 if (path == psu.getInventoryPath()) 1199 { 1200 return true; 1201 } 1202 if (++psuCount >= requiredCount) 1203 { 1204 break; 1205 } 1206 } 1207 1208 // PSU was not close to start of sorted list; assume not required 1209 return false; 1210 } 1211 1212 bool PSUManager::validateModelName( 1213 std::string& model, std::map<std::string, std::string>& additionalData) 1214 { 1215 // Check that all PSUs have the same model name. Initialize the model 1216 // variable with the first PSU name found, then use it as a base to compare 1217 // against the rest of the PSUs and get its inventory path to use as callout 1218 // if needed. 1219 model.clear(); 1220 std::string modelInventoryPath{}; 1221 for (const auto& psu : psus) 1222 { 1223 auto psuModel = psu->getModelName(); 1224 if (psuModel.empty()) 1225 { 1226 continue; 1227 } 1228 if (model.empty()) 1229 { 1230 model = psuModel; 1231 modelInventoryPath = psu->getInventoryPath(); 1232 continue; 1233 } 1234 if (psuModel != model) 1235 { 1236 if (supportedConfigs.find(model) != supportedConfigs.end()) 1237 { 1238 // The base model is supported, callout the mismatched PSU. The 1239 // mismatched PSU may or may not be supported. 1240 additionalData["EXPECTED_MODEL"] = model; 1241 additionalData["ACTUAL_MODEL"] = psuModel; 1242 additionalData["CALLOUT_INVENTORY_PATH"] = 1243 psu->getInventoryPath(); 1244 } 1245 else if (supportedConfigs.find(psuModel) != supportedConfigs.end()) 1246 { 1247 // The base model is not supported, but the mismatched PSU is, 1248 // callout the base PSU. 1249 additionalData["EXPECTED_MODEL"] = psuModel; 1250 additionalData["ACTUAL_MODEL"] = model; 1251 additionalData["CALLOUT_INVENTORY_PATH"] = modelInventoryPath; 1252 } 1253 else 1254 { 1255 // The base model and the mismatched PSU are not supported or 1256 // could not be found in the supported configuration, callout 1257 // the mismatched PSU. 1258 additionalData["EXPECTED_MODEL"] = model; 1259 additionalData["ACTUAL_MODEL"] = psuModel; 1260 additionalData["CALLOUT_INVENTORY_PATH"] = 1261 psu->getInventoryPath(); 1262 } 1263 model.clear(); 1264 return false; 1265 } 1266 } 1267 return true; 1268 } 1269 1270 void PSUManager::setPowerConfigGPIO() 1271 { 1272 if (!powerConfigGPIO) 1273 { 1274 return; 1275 } 1276 1277 std::string model{}; 1278 std::map<std::string, std::string> additionalData; 1279 if (!validateModelName(model, additionalData)) 1280 { 1281 return; 1282 } 1283 1284 auto config = supportedConfigs.find(model); 1285 if (config != supportedConfigs.end()) 1286 { 1287 // The power-config-full-load is an open drain GPIO. Set it to low (0) 1288 // if the supported configuration indicates that this system model 1289 // expects the maximum number of power supplies (full load set to true). 1290 // Else, set it to high (1), this is the default. 1291 auto powerConfigValue = 1292 (config->second.powerConfigFullLoad == true ? 0 : 1); 1293 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN; 1294 powerConfigGPIO->write(powerConfigValue, flags); 1295 } 1296 } 1297 1298 void PSUManager::buildDriverName(uint64_t i2cbus, uint64_t i2caddr) 1299 { 1300 namespace fs = std::filesystem; 1301 std::stringstream ss; 1302 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr; 1303 std::string symLinkPath = deviceDirPath + std::to_string(i2cbus) + "-" + 1304 ss.str() + driverDirName; 1305 try 1306 { 1307 fs::path linkStrPath = fs::read_symlink(symLinkPath); 1308 driverName = linkStrPath.filename(); 1309 } 1310 catch (const std::exception& e) 1311 { 1312 log<level::ERR>(fmt::format("Failed to find device driver {}, error {}", 1313 symLinkPath, e.what()) 1314 .c_str()); 1315 } 1316 } 1317 } // namespace phosphor::power::manager 1318