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