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