1 #include "psu_manager.hpp" 2 3 #include "utility.hpp" 4 5 #include <fmt/format.h> 6 #include <sys/types.h> 7 #include <unistd.h> 8 9 #include <regex> 10 11 using namespace phosphor::logging; 12 13 namespace phosphor::power::manager 14 { 15 16 constexpr auto IBMCFFPSInterface = 17 "xyz.openbmc_project.Configuration.IBMCFFPSConnector"; 18 constexpr auto i2cBusProp = "I2CBus"; 19 constexpr auto i2cAddressProp = "I2CAddress"; 20 constexpr auto psuNameProp = "Name"; 21 constexpr auto presLineName = "NamedPresenceGpio"; 22 23 constexpr auto supportedConfIntf = 24 "xyz.openbmc_project.Configuration.SupportedConfiguration"; 25 26 PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e) : 27 bus(bus) 28 { 29 // Subscribe to InterfacesAdded before doing a property read, otherwise 30 // the interface could be created after the read attempt but before the 31 // match is created. 32 entityManagerIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>( 33 bus, 34 sdbusplus::bus::match::rules::interfacesAdded() + 35 sdbusplus::bus::match::rules::sender( 36 "xyz.openbmc_project.EntityManager"), 37 std::bind(&PSUManager::entityManagerIfaceAdded, this, 38 std::placeholders::_1)); 39 getPSUConfiguration(); 40 getSystemProperties(); 41 42 using namespace sdeventplus; 43 auto interval = std::chrono::milliseconds(1000); 44 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 45 e, std::bind(&PSUManager::analyze, this), interval); 46 47 validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 48 e, std::bind(&PSUManager::validateConfig, this)); 49 50 try 51 { 52 powerConfigGPIO = createGPIO("power-config-full-load"); 53 } 54 catch (const std::exception& e) 55 { 56 // Ignore error, GPIO may not be implemented in this system. 57 powerConfigGPIO = nullptr; 58 } 59 60 // Subscribe to power state changes 61 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus); 62 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>( 63 bus, 64 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH, 65 POWER_IFACE), 66 [this](auto& msg) { this->powerStateChanged(msg); }); 67 68 initialize(); 69 } 70 71 void PSUManager::getPSUConfiguration() 72 { 73 using namespace phosphor::power::util; 74 auto depth = 0; 75 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 76 77 psus.clear(); 78 79 // I should get a map of objects back. 80 // Each object will have a path, a service, and an interface. 81 // The interface should match the one passed into this function. 82 for (const auto& [path, services] : objects) 83 { 84 auto service = services.begin()->first; 85 86 if (path.empty() || service.empty()) 87 { 88 continue; 89 } 90 91 // For each object in the array of objects, I want to get properties 92 // from the service, path, and interface. 93 auto properties = 94 getAllProperties(bus, path, IBMCFFPSInterface, service); 95 96 getPSUProperties(properties); 97 } 98 99 if (psus.empty()) 100 { 101 // Interface or properties not found. Let the Interfaces Added callback 102 // process the information once the interfaces are added to D-Bus. 103 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 104 } 105 } 106 107 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties) 108 { 109 // From passed in properties, I want to get: I2CBus, I2CAddress, 110 // and Name. Create a power supply object, using Name to build the inventory 111 // path. 112 const auto basePSUInvPath = 113 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply"; 114 uint64_t* i2cbus = nullptr; 115 uint64_t* i2caddr = nullptr; 116 std::string* psuname = nullptr; 117 std::string* preslineptr = nullptr; 118 119 for (const auto& property : properties) 120 { 121 try 122 { 123 if (property.first == i2cBusProp) 124 { 125 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]); 126 } 127 else if (property.first == i2cAddressProp) 128 { 129 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]); 130 } 131 else if (property.first == psuNameProp) 132 { 133 psuname = std::get_if<std::string>(&properties[psuNameProp]); 134 } 135 else if (property.first == presLineName) 136 { 137 preslineptr = 138 std::get_if<std::string>(&properties[presLineName]); 139 } 140 } 141 catch (const std::exception& e) 142 {} 143 } 144 145 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty())) 146 { 147 std::string invpath = basePSUInvPath; 148 invpath.push_back(psuname->back()); 149 std::string presline = ""; 150 151 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str()); 152 153 if (nullptr != preslineptr) 154 { 155 presline = *preslineptr; 156 } 157 158 auto invMatch = 159 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) { 160 return psu->getInventoryPath() == invpath; 161 }); 162 if (invMatch != psus.end()) 163 { 164 // This power supply has the same inventory path as the one with 165 // information just added to D-Bus. 166 // Changes to GPIO line name unlikely, so skip checking. 167 // Changes to the I2C bus and address unlikely, as that would 168 // require corresponding device tree updates. 169 // Return out to avoid duplicate object creation. 170 return; 171 } 172 173 log<level::DEBUG>( 174 fmt::format("make PowerSupply bus: {} addr: {} presline: {}", 175 *i2cbus, *i2caddr, presline) 176 .c_str()); 177 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus, 178 *i2caddr, presline); 179 psus.emplace_back(std::move(psu)); 180 } 181 182 if (psus.empty()) 183 { 184 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 185 } 186 } 187 188 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties) 189 { 190 try 191 { 192 auto propIt = properties.find("SupportedType"); 193 if (propIt == properties.end()) 194 { 195 return; 196 } 197 const std::string* type = std::get_if<std::string>(&(propIt->second)); 198 if ((type == nullptr) || (*type != "PowerSupply")) 199 { 200 return; 201 } 202 203 propIt = properties.find("SupportedModel"); 204 if (propIt == properties.end()) 205 { 206 return; 207 } 208 const std::string* model = std::get_if<std::string>(&(propIt->second)); 209 if (model == nullptr) 210 { 211 return; 212 } 213 214 sys_properties sys; 215 propIt = properties.find("RedundantCount"); 216 if (propIt != properties.end()) 217 { 218 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second)); 219 if (count != nullptr) 220 { 221 sys.powerSupplyCount = *count; 222 } 223 } 224 propIt = properties.find("InputVoltage"); 225 if (propIt != properties.end()) 226 { 227 const std::vector<uint64_t>* voltage = 228 std::get_if<std::vector<uint64_t>>(&(propIt->second)); 229 if (voltage != nullptr) 230 { 231 sys.inputVoltage = *voltage; 232 } 233 } 234 235 // The PowerConfigFullLoad is an optional property, default it to false 236 // since that's the default value of the power-config-full-load GPIO. 237 sys.powerConfigFullLoad = false; 238 propIt = properties.find("PowerConfigFullLoad"); 239 if (propIt != properties.end()) 240 { 241 const bool* fullLoad = std::get_if<bool>(&(propIt->second)); 242 if (fullLoad != nullptr) 243 { 244 sys.powerConfigFullLoad = *fullLoad; 245 } 246 } 247 248 supportedConfigs.emplace(*model, sys); 249 } 250 catch (const std::exception& e) 251 {} 252 } 253 254 void PSUManager::getSystemProperties() 255 { 256 257 try 258 { 259 util::DbusSubtree subtree = 260 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0); 261 if (subtree.empty()) 262 { 263 throw std::runtime_error("Supported Configuration Not Found"); 264 } 265 266 for (const auto& [objPath, services] : subtree) 267 { 268 std::string service = services.begin()->first; 269 if (objPath.empty() || service.empty()) 270 { 271 continue; 272 } 273 auto properties = util::getAllProperties( 274 bus, objPath, supportedConfIntf, service); 275 populateSysProperties(properties); 276 } 277 } 278 catch (const std::exception& e) 279 { 280 // Interface or property not found. Let the Interfaces Added callback 281 // process the information once the interfaces are added to D-Bus. 282 } 283 } 284 285 void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg) 286 { 287 try 288 { 289 sdbusplus::message::object_path objPath; 290 std::map<std::string, std::map<std::string, util::DbusVariant>> 291 interfaces; 292 msg.read(objPath, interfaces); 293 294 auto itIntf = interfaces.find(supportedConfIntf); 295 if (itIntf != interfaces.cend()) 296 { 297 populateSysProperties(itIntf->second); 298 } 299 300 itIntf = interfaces.find(IBMCFFPSInterface); 301 if (itIntf != interfaces.cend()) 302 { 303 log<level::INFO>( 304 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface) 305 .c_str()); 306 getPSUProperties(itIntf->second); 307 } 308 309 // Call to validate the psu configuration if the power is on and both 310 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been 311 // processed 312 if (powerOn && !psus.empty() && !supportedConfigs.empty()) 313 { 314 validationTimer->restartOnce(validationTimeout); 315 } 316 } 317 catch (const std::exception& e) 318 { 319 // Ignore, the property may be of a different type than expected. 320 } 321 } 322 323 void PSUManager::powerStateChanged(sdbusplus::message::message& msg) 324 { 325 int32_t state = 0; 326 std::string msgSensor; 327 std::map<std::string, std::variant<int32_t>> msgData; 328 msg.read(msgSensor, msgData); 329 330 // Check if it was the Present property that changed. 331 auto valPropMap = msgData.find("state"); 332 if (valPropMap != msgData.end()) 333 { 334 state = std::get<int32_t>(valPropMap->second); 335 336 // Power is on when state=1. Clear faults. 337 if (state) 338 { 339 powerOn = true; 340 validationTimer->restartOnce(validationTimeout); 341 clearFaults(); 342 setPowerConfigGPIO(); 343 } 344 else 345 { 346 powerOn = false; 347 runValidateConfig = true; 348 } 349 } 350 } 351 352 void PSUManager::createError(const std::string& faultName, 353 std::map<std::string, std::string>& additionalData) 354 { 355 using namespace sdbusplus::xyz::openbmc_project; 356 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging"; 357 constexpr auto loggingCreateInterface = 358 "xyz.openbmc_project.Logging.Create"; 359 360 try 361 { 362 additionalData["_PID"] = std::to_string(getpid()); 363 364 auto service = 365 util::getService(loggingObjectPath, loggingCreateInterface, bus); 366 367 if (service.empty()) 368 { 369 log<level::ERR>("Unable to get logging manager service"); 370 return; 371 } 372 373 auto method = bus.new_method_call(service.c_str(), loggingObjectPath, 374 loggingCreateInterface, "Create"); 375 376 auto level = Logging::server::Entry::Level::Error; 377 method.append(faultName, level, additionalData); 378 379 auto reply = bus.call(method); 380 } 381 catch (const std::exception& e) 382 { 383 log<level::ERR>( 384 fmt::format( 385 "Failed creating event log for fault {} due to error {}", 386 faultName, e.what()) 387 .c_str()); 388 } 389 } 390 391 void PSUManager::analyze() 392 { 393 for (auto& psu : psus) 394 { 395 psu->analyze(); 396 } 397 398 if (powerOn) 399 { 400 std::map<std::string, std::string> additionalData; 401 402 for (auto& psu : psus) 403 { 404 additionalData.clear(); 405 // TODO: Fault priorities #918 406 if (!psu->isFaultLogged() && !psu->isPresent()) 407 { 408 std::map<std::string, std::string> requiredPSUsData; 409 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData); 410 if (!requiredPSUsPresent) 411 { 412 additionalData.merge(requiredPSUsData); 413 // Create error for power supply missing. 414 additionalData["CALLOUT_INVENTORY_PATH"] = 415 psu->getInventoryPath(); 416 additionalData["CALLOUT_PRIORITY"] = "H"; 417 createError( 418 "xyz.openbmc_project.Power.PowerSupply.Error.Missing", 419 additionalData); 420 } 421 psu->setFaultLogged(); 422 } 423 else if (!psu->isFaultLogged() && psu->isFaulted()) 424 { 425 // Add STATUS_WORD and STATUS_MFR last response, in padded 426 // hexadecimal format. 427 additionalData["STATUS_WORD"] = 428 fmt::format("{:#04x}", psu->getStatusWord()); 429 additionalData["STATUS_MFR"] = 430 fmt::format("{:#02x}", psu->getMFRFault()); 431 // If there are faults being reported, they possibly could be 432 // related to a bug in the firmware version running on the power 433 // supply. Capture that data into the error as well. 434 additionalData["FW_VERSION"] = psu->getFWVersion(); 435 436 if (psu->hasCommFault()) 437 { 438 additionalData["STATUS_CML"] = 439 fmt::format("{:#02x}", psu->getStatusCML()); 440 /* Attempts to communicate with the power supply have 441 * reached there limit. Create an error. */ 442 additionalData["CALLOUT_DEVICE_PATH"] = 443 psu->getDevicePath(); 444 445 createError( 446 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault", 447 additionalData); 448 449 psu->setFaultLogged(); 450 } 451 else if ((psu->hasInputFault() || psu->hasVINUVFault())) 452 { 453 // Include STATUS_INPUT for input faults. 454 additionalData["STATUS_INPUT"] = 455 fmt::format("{:#02x}", psu->getStatusInput()); 456 457 /* The power supply location might be needed if the input 458 * fault is due to a problem with the power supply itself. 459 * Include the inventory path with a call out priority of 460 * low. 461 */ 462 additionalData["CALLOUT_INVENTORY_PATH"] = 463 psu->getInventoryPath(); 464 additionalData["CALLOUT_PRIORITY"] = "L"; 465 createError("xyz.openbmc_project.Power.PowerSupply.Error." 466 "InputFault", 467 additionalData); 468 psu->setFaultLogged(); 469 } 470 else if (psu->hasVoutOVFault()) 471 { 472 // Include STATUS_VOUT for Vout faults. 473 additionalData["STATUS_VOUT"] = 474 fmt::format("{:#02x}", psu->getStatusVout()); 475 476 additionalData["CALLOUT_INVENTORY_PATH"] = 477 psu->getInventoryPath(); 478 479 createError( 480 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 481 additionalData); 482 483 psu->setFaultLogged(); 484 } 485 else if (psu->hasIoutOCFault()) 486 { 487 // Include STATUS_IOUT for Iout faults. 488 additionalData["STATUS_IOUT"] = 489 fmt::format("{:#02x}", psu->getStatusIout()); 490 491 createError( 492 "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault", 493 additionalData); 494 495 psu->setFaultLogged(); 496 } 497 else if (psu->hasVoutUVFault()) 498 { 499 // Include STATUS_VOUT for Vout faults. 500 additionalData["STATUS_VOUT"] = 501 fmt::format("{:#02x}", psu->getStatusVout()); 502 503 additionalData["CALLOUT_INVENTORY_PATH"] = 504 psu->getInventoryPath(); 505 506 createError( 507 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 508 additionalData); 509 510 psu->setFaultLogged(); 511 } 512 // A fan fault should have priority over a temperature fault, 513 // since a failed fan may lead to a temperature problem. 514 else if (psu->hasFanFault()) 515 { 516 // Include STATUS_TEMPERATURE and STATUS_FANS_1_2 517 additionalData["STATUS_TEMPERATURE"] = 518 fmt::format("{:#02x}", psu->getStatusTemperature()); 519 additionalData["STATUS_FANS_1_2"] = 520 fmt::format("{:#02x}", psu->getStatusFans12()); 521 522 additionalData["CALLOUT_INVENTORY_PATH"] = 523 psu->getInventoryPath(); 524 525 createError( 526 "xyz.openbmc_project.Power.PowerSupply.Error.FanFault", 527 additionalData); 528 529 psu->setFaultLogged(); 530 } 531 else if (psu->hasTempFault()) 532 { 533 // Include STATUS_TEMPERATURE for temperature faults. 534 additionalData["STATUS_TEMPERATURE"] = 535 fmt::format("{:#02x}", psu->getStatusTemperature()); 536 537 additionalData["CALLOUT_INVENTORY_PATH"] = 538 psu->getInventoryPath(); 539 540 createError( 541 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 542 additionalData); 543 544 psu->setFaultLogged(); 545 } 546 else if (psu->hasMFRFault()) 547 { 548 /* This can represent a variety of faults that result in 549 * calling out the power supply for replacement: Output 550 * OverCurrent, Output Under Voltage, and potentially other 551 * faults. 552 * 553 * Also plan on putting specific fault in AdditionalData, 554 * along with register names and register values 555 * (STATUS_WORD, STATUS_MFR, etc.).*/ 556 557 additionalData["CALLOUT_INVENTORY_PATH"] = 558 psu->getInventoryPath(); 559 560 createError( 561 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 562 additionalData); 563 564 psu->setFaultLogged(); 565 } 566 else if (psu->hasPgoodFault()) 567 { 568 /* POWER_GOOD# is not low, or OFF is on */ 569 additionalData["CALLOUT_INVENTORY_PATH"] = 570 psu->getInventoryPath(); 571 572 createError( 573 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 574 additionalData); 575 576 psu->setFaultLogged(); 577 } 578 } 579 } 580 } 581 } 582 583 void PSUManager::validateConfig() 584 { 585 if (!runValidateConfig || supportedConfigs.empty()) 586 { 587 return; 588 } 589 590 std::map<std::string, std::string> additionalData; 591 auto supported = hasRequiredPSUs(additionalData); 592 if (supported) 593 { 594 runValidateConfig = false; 595 return; 596 } 597 598 // Validation failed, create an error log. 599 // Return without setting the runValidateConfig flag to false because 600 // it may be that an additional supported configuration interface is 601 // added and we need to validate it to see if it matches this system. 602 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported", 603 additionalData); 604 } 605 606 bool PSUManager::hasRequiredPSUs( 607 std::map<std::string, std::string>& additionalData) 608 { 609 std::string model{}; 610 if (!validateModelName(model, additionalData)) 611 { 612 return false; 613 } 614 615 auto presentCount = 616 std::count_if(psus.begin(), psus.end(), 617 [](const auto& psu) { return psu->isPresent(); }); 618 619 // Validate the supported configurations. A system may support more than one 620 // power supply model configuration. Since all configurations need to be 621 // checked, the additional data would contain only the information of the 622 // last configuration that did not match. 623 std::map<std::string, std::string> tmpAdditionalData; 624 for (const auto& config : supportedConfigs) 625 { 626 if (config.first != model) 627 { 628 continue; 629 } 630 if (presentCount != config.second.powerSupplyCount) 631 { 632 tmpAdditionalData.clear(); 633 tmpAdditionalData["EXPECTED_COUNT"] = 634 std::to_string(config.second.powerSupplyCount); 635 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount); 636 continue; 637 } 638 639 bool voltageValidated = true; 640 for (const auto& psu : psus) 641 { 642 if (!psu->isPresent()) 643 { 644 // Only present PSUs report a valid input voltage 645 continue; 646 } 647 648 double actualInputVoltage; 649 int inputVoltage; 650 psu->getInputVoltage(actualInputVoltage, inputVoltage); 651 652 if (std::find(config.second.inputVoltage.begin(), 653 config.second.inputVoltage.end(), 654 inputVoltage) == config.second.inputVoltage.end()) 655 { 656 tmpAdditionalData.clear(); 657 tmpAdditionalData["ACTUAL_VOLTAGE"] = 658 std::to_string(actualInputVoltage); 659 for (const auto& voltage : config.second.inputVoltage) 660 { 661 tmpAdditionalData["EXPECTED_VOLTAGE"] += 662 std::to_string(voltage) + " "; 663 } 664 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] = 665 psu->getInventoryPath(); 666 667 voltageValidated = false; 668 break; 669 } 670 } 671 if (!voltageValidated) 672 { 673 continue; 674 } 675 676 return true; 677 } 678 679 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end()); 680 return false; 681 } 682 683 bool PSUManager::validateModelName( 684 std::string& model, std::map<std::string, std::string>& additionalData) 685 { 686 // Check that all PSUs have the same model name. Initialize the model 687 // variable with the first PSU name found, then use it as a base to compare 688 // against the rest of the PSUs. 689 model.clear(); 690 for (const auto& psu : psus) 691 { 692 auto psuModel = psu->getModelName(); 693 if (psuModel.empty()) 694 { 695 continue; 696 } 697 if (model.empty()) 698 { 699 model = psuModel; 700 continue; 701 } 702 if (psuModel != model) 703 { 704 additionalData["EXPECTED_MODEL"] = model; 705 additionalData["ACTUAL_MODEL"] = psuModel; 706 additionalData["CALLOUT_INVENTORY_PATH"] = psu->getInventoryPath(); 707 model.clear(); 708 return false; 709 } 710 } 711 return true; 712 } 713 714 void PSUManager::setPowerConfigGPIO() 715 { 716 if (!powerConfigGPIO) 717 { 718 return; 719 } 720 721 std::string model{}; 722 std::map<std::string, std::string> additionalData; 723 if (!validateModelName(model, additionalData)) 724 { 725 return; 726 } 727 728 auto config = supportedConfigs.find(model); 729 if (config != supportedConfigs.end()) 730 { 731 // The power-config-full-load is an open drain GPIO. Set it to low (0) 732 // if the supported configuration indicates that this system model 733 // expects the maximum number of power supplies (full load set to true). 734 // Else, set it to high (1), this is the default. 735 auto powerConfigValue = 736 (config->second.powerConfigFullLoad == true ? 0 : 1); 737 auto flags = gpiod::line_request::FLAG_OPEN_DRAIN; 738 powerConfigGPIO->write(powerConfigValue, flags); 739 } 740 } 741 742 } // namespace phosphor::power::manager 743