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 sensorsObjManager(bus, "/xyz/openbmc_project/sensors") 46 { 47 // Subscribe to InterfacesAdded before doing a property read, otherwise 48 // the interface could be created after the read attempt but before the 49 // match is created. 50 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>( 51 bus, 52 sdbusplus::bus::match::rules::interfacesAdded() + 53 sdbusplus::bus::match::rules::sender( 54 "xyz.openbmc_project.EntityManager"), 55 std::bind(&PSUManager::entityManagerIfaceAdded, this, 56 std::placeholders::_1)); 57 getPSUConfiguration(); 58 getSystemProperties(); 59 60 // Request the bus name before the analyze() function, which is the one that 61 // determines the brownout condition and sets the status d-bus property. 62 bus.request_name(managerBusName); 63 64 using namespace sdeventplus; 65 auto interval = std::chrono::milliseconds(1000); 66 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 67 e, std::bind(&PSUManager::analyze, this), interval); 68 69 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 70 e, std::bind(&PSUManager::validateConfig, this)); 71 72 try 73 { 74 powerConfigGPIO = createGPIO("power-config-full-load"); 75 } 76 catch (const std::exception& e) 77 { 78 // Ignore error, GPIO may not be implemented in this system. 79 powerConfigGPIO = nullptr; 80 } 81 82 // Subscribe to power state changes 83 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus); 84 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>( 85 bus, 86 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH, 87 POWER_IFACE), 88 [this](auto& msg) { this->powerStateChanged(msg); }); 89 90 initialize(); 91 } 92 93 void PSUManager::initialize() 94 { 95 try 96 { 97 // pgood is the latest read of the chassis pgood 98 int pgood = 0; 99 util::getProperty<int>(POWER_IFACE, "pgood", POWER_OBJ_PATH, 100 powerService, bus, pgood); 101 102 // state is the latest requested power on / off transition 103 auto method = bus.new_method_call(powerService.c_str(), POWER_OBJ_PATH, 104 POWER_IFACE, "getPowerState"); 105 auto reply = bus.call(method); 106 int state = 0; 107 reply.read(state); 108 109 if (state) 110 { 111 // Monitor PSUs anytime state is on 112 powerOn = true; 113 // In the power fault window if pgood is off 114 powerFaultOccurring = !pgood; 115 validationTimer->restartOnce(validationTimeout); 116 } 117 else 118 { 119 // Power is off 120 powerOn = false; 121 powerFaultOccurring = false; 122 runValidateConfig = true; 123 } 124 } 125 catch (const std::exception& e) 126 { 127 log<level::INFO>( 128 fmt::format( 129 "Failed to get power state, assuming it is off, error {}", 130 e.what()) 131 .c_str()); 132 powerOn = false; 133 powerFaultOccurring = false; 134 runValidateConfig = true; 135 } 136 137 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY); 138 clearFaults(); 139 updateMissingPSUs(); 140 setPowerConfigGPIO(); 141 142 log<level::INFO>( 143 fmt::format("initialize: power on: {}, power fault occurring: {}", 144 powerOn, powerFaultOccurring) 145 .c_str()); 146 } 147 148 void PSUManager::getPSUConfiguration() 149 { 150 using namespace phosphor::power::util; 151 auto depth = 0; 152 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 153 154 psus.clear(); 155 156 // I should get a map of objects back. 157 // Each object will have a path, a service, and an interface. 158 // The interface should match the one passed into this function. 159 for (const auto& [path, services] : objects) 160 { 161 auto service = services.begin()->first; 162 163 if (path.empty() || service.empty()) 164 { 165 continue; 166 } 167 168 // For each object in the array of objects, I want to get properties 169 // from the service, path, and interface. 170 auto properties = getAllProperties(bus, path, IBMCFFPSInterface, 171 service); 172 173 getPSUProperties(properties); 174 } 175 176 if (psus.empty()) 177 { 178 // Interface or properties not found. Let the Interfaces Added callback 179 // process the information once the interfaces are added to D-Bus. 180 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 181 } 182 } 183 184 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties) 185 { 186 // From passed in properties, I want to get: I2CBus, I2CAddress, 187 // and Name. Create a power supply object, using Name to build the inventory 188 // path. 189 const auto basePSUInvPath = 190 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply"; 191 uint64_t* i2cbus = nullptr; 192 uint64_t* i2caddr = nullptr; 193 std::string* psuname = nullptr; 194 std::string* preslineptr = nullptr; 195 196 for (const auto& property : properties) 197 { 198 try 199 { 200 if (property.first == i2cBusProp) 201 { 202 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]); 203 } 204 else if (property.first == i2cAddressProp) 205 { 206 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]); 207 } 208 else if (property.first == psuNameProp) 209 { 210 psuname = std::get_if<std::string>(&properties[psuNameProp]); 211 } 212 else if (property.first == presLineName) 213 { 214 preslineptr = 215 std::get_if<std::string>(&properties[presLineName]); 216 } 217 } 218 catch (const std::exception& e) 219 {} 220 } 221 222 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty())) 223 { 224 std::string invpath = basePSUInvPath; 225 invpath.push_back(psuname->back()); 226 std::string presline = ""; 227 228 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str()); 229 230 if (nullptr != preslineptr) 231 { 232 presline = *preslineptr; 233 } 234 235 auto invMatch = std::find_if(psus.begin(), psus.end(), 236 [&invpath](auto& psu) { 237 return psu->getInventoryPath() == invpath; 238 }); 239 if (invMatch != psus.end()) 240 { 241 // This power supply has the same inventory path as the one with 242 // information just added to D-Bus. 243 // Changes to GPIO line name unlikely, so skip checking. 244 // Changes to the I2C bus and address unlikely, as that would 245 // require corresponding device tree updates. 246 // Return out to avoid duplicate object creation. 247 return; 248 } 249 250 buildDriverName(*i2cbus, *i2caddr); 251 log<level::DEBUG>( 252 fmt::format("make PowerSupply bus: {} addr: {} presline: {}", 253 *i2cbus, *i2caddr, presline) 254 .c_str()); 255 auto psu = std::make_unique<PowerSupply>( 256 bus, invpath, *i2cbus, *i2caddr, driverName, presline, 257 std::bind( 258 std::mem_fn(&phosphor::power::manager::PSUManager::isPowerOn), 259 this)); 260 psus.emplace_back(std::move(psu)); 261 262 // Subscribe to power supply presence changes 263 auto presenceMatch = std::make_unique<sdbusplus::bus::match_t>( 264 bus, 265 sdbusplus::bus::match::rules::propertiesChanged(invpath, 266 INVENTORY_IFACE), 267 [this](auto& msg) { this->presenceChanged(msg); }); 268 presenceMatches.emplace_back(std::move(presenceMatch)); 269 } 270 271 if (psus.empty()) 272 { 273 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 274 } 275 } 276 277 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties) 278 { 279 try 280 { 281 auto propIt = properties.find("SupportedType"); 282 if (propIt == properties.end()) 283 { 284 return; 285 } 286 const std::string* type = std::get_if<std::string>(&(propIt->second)); 287 if ((type == nullptr) || (*type != "PowerSupply")) 288 { 289 return; 290 } 291 292 propIt = properties.find("SupportedModel"); 293 if (propIt == properties.end()) 294 { 295 return; 296 } 297 const std::string* model = std::get_if<std::string>(&(propIt->second)); 298 if (model == nullptr) 299 { 300 return; 301 } 302 303 sys_properties sys; 304 propIt = properties.find("RedundantCount"); 305 if (propIt != properties.end()) 306 { 307 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second)); 308 if (count != nullptr) 309 { 310 sys.powerSupplyCount = *count; 311 } 312 } 313 propIt = properties.find("InputVoltage"); 314 if (propIt != properties.end()) 315 { 316 const std::vector<uint64_t>* voltage = 317 std::get_if<std::vector<uint64_t>>(&(propIt->second)); 318 if (voltage != nullptr) 319 { 320 sys.inputVoltage = *voltage; 321 } 322 } 323 324 // The PowerConfigFullLoad is an optional property, default it to false 325 // since that's the default value of the power-config-full-load GPIO. 326 sys.powerConfigFullLoad = false; 327 propIt = properties.find("PowerConfigFullLoad"); 328 if (propIt != properties.end()) 329 { 330 const bool* fullLoad = std::get_if<bool>(&(propIt->second)); 331 if (fullLoad != nullptr) 332 { 333 sys.powerConfigFullLoad = *fullLoad; 334 } 335 } 336 337 supportedConfigs.emplace(*model, sys); 338 } 339 catch (const std::exception& e) 340 {} 341 } 342 343 void PSUManager::getSystemProperties() 344 { 345 try 346 { 347 util::DbusSubtree subtree = util::getSubTree(bus, INVENTORY_OBJ_PATH, 348 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 = util::getService(loggingObjectPath, 520 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 if (driverName != ACBEL_FSG032_DD_NAME) 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 log<level::INFO>("Synchronize INPUT_HISTORY"); 559 syncHistoryGPIO->toggleLowHigh(delay); 560 for (auto& psu : psus) 561 { 562 psu->clearSyncHistoryRequired(); 563 } 564 log<level::INFO>("Synchronize INPUT_HISTORY completed"); 565 } 566 } 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