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 // Subscribe to power state changes 51 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus); 52 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>( 53 bus, 54 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH, 55 POWER_IFACE), 56 [this](auto& msg) { this->powerStateChanged(msg); }); 57 58 initialize(); 59 } 60 61 void PSUManager::getPSUConfiguration() 62 { 63 using namespace phosphor::power::util; 64 auto depth = 0; 65 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 66 67 psus.clear(); 68 69 // I should get a map of objects back. 70 // Each object will have a path, a service, and an interface. 71 // The interface should match the one passed into this function. 72 for (const auto& [path, services] : objects) 73 { 74 auto service = services.begin()->first; 75 76 if (path.empty() || service.empty()) 77 { 78 continue; 79 } 80 81 // For each object in the array of objects, I want to get properties 82 // from the service, path, and interface. 83 auto properties = 84 getAllProperties(bus, path, IBMCFFPSInterface, service); 85 86 getPSUProperties(properties); 87 } 88 89 if (psus.empty()) 90 { 91 // Interface or properties not found. Let the Interfaces Added callback 92 // process the information once the interfaces are added to D-Bus. 93 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 94 } 95 } 96 97 void PSUManager::getPSUProperties(util::DbusPropertyMap& properties) 98 { 99 // From passed in properties, I want to get: I2CBus, I2CAddress, 100 // and Name. Create a power supply object, using Name to build the inventory 101 // path. 102 const auto basePSUInvPath = 103 "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply"; 104 uint64_t* i2cbus = nullptr; 105 uint64_t* i2caddr = nullptr; 106 std::string* psuname = nullptr; 107 std::string* preslineptr = nullptr; 108 109 for (const auto& property : properties) 110 { 111 try 112 { 113 if (property.first == i2cBusProp) 114 { 115 i2cbus = std::get_if<uint64_t>(&properties[i2cBusProp]); 116 } 117 else if (property.first == i2cAddressProp) 118 { 119 i2caddr = std::get_if<uint64_t>(&properties[i2cAddressProp]); 120 } 121 else if (property.first == psuNameProp) 122 { 123 psuname = std::get_if<std::string>(&properties[psuNameProp]); 124 } 125 else if (property.first == presLineName) 126 { 127 preslineptr = 128 std::get_if<std::string>(&properties[presLineName]); 129 } 130 } 131 catch (const std::exception& e) 132 {} 133 } 134 135 if ((i2cbus) && (i2caddr) && (psuname) && (!psuname->empty())) 136 { 137 std::string invpath = basePSUInvPath; 138 invpath.push_back(psuname->back()); 139 std::string presline = ""; 140 141 log<level::DEBUG>(fmt::format("Inventory Path: {}", invpath).c_str()); 142 143 if (nullptr != preslineptr) 144 { 145 presline = *preslineptr; 146 } 147 148 auto invMatch = 149 std::find_if(psus.begin(), psus.end(), [&invpath](auto& psu) { 150 return psu->getInventoryPath() == invpath; 151 }); 152 if (invMatch != psus.end()) 153 { 154 // This power supply has the same inventory path as the one with 155 // information just added to D-Bus. 156 // Changes to GPIO line name unlikely, so skip checking. 157 // Changes to the I2C bus and address unlikely, as that would 158 // require corresponding device tree updates. 159 // Return out to avoid duplicate object creation. 160 return; 161 } 162 163 log<level::DEBUG>( 164 fmt::format("make PowerSupply bus: {} addr: {} presline: {}", 165 *i2cbus, *i2caddr, presline) 166 .c_str()); 167 auto psu = std::make_unique<PowerSupply>(bus, invpath, *i2cbus, 168 *i2caddr, presline); 169 psus.emplace_back(std::move(psu)); 170 } 171 172 if (psus.empty()) 173 { 174 log<level::INFO>(fmt::format("No power supplies to monitor").c_str()); 175 } 176 } 177 178 void PSUManager::populateSysProperties(const util::DbusPropertyMap& properties) 179 { 180 try 181 { 182 auto propIt = properties.find("SupportedType"); 183 if (propIt == properties.end()) 184 { 185 return; 186 } 187 const std::string* type = std::get_if<std::string>(&(propIt->second)); 188 if ((type == nullptr) || (*type != "PowerSupply")) 189 { 190 return; 191 } 192 193 propIt = properties.find("SupportedModel"); 194 if (propIt == properties.end()) 195 { 196 return; 197 } 198 const std::string* model = std::get_if<std::string>(&(propIt->second)); 199 if (model == nullptr) 200 { 201 return; 202 } 203 204 sys_properties sys; 205 propIt = properties.find("RedundantCount"); 206 if (propIt != properties.end()) 207 { 208 const uint64_t* count = std::get_if<uint64_t>(&(propIt->second)); 209 if (count != nullptr) 210 { 211 sys.powerSupplyCount = *count; 212 } 213 } 214 propIt = properties.find("InputVoltage"); 215 if (propIt != properties.end()) 216 { 217 const std::vector<uint64_t>* voltage = 218 std::get_if<std::vector<uint64_t>>(&(propIt->second)); 219 if (voltage != nullptr) 220 { 221 sys.inputVoltage = *voltage; 222 } 223 } 224 225 supportedConfigs.emplace(*model, sys); 226 } 227 catch (const std::exception& e) 228 {} 229 } 230 231 void PSUManager::getSystemProperties() 232 { 233 234 try 235 { 236 util::DbusSubtree subtree = 237 util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0); 238 if (subtree.empty()) 239 { 240 throw std::runtime_error("Supported Configuration Not Found"); 241 } 242 243 for (const auto& [objPath, services] : subtree) 244 { 245 std::string service = services.begin()->first; 246 if (objPath.empty() || service.empty()) 247 { 248 continue; 249 } 250 auto properties = util::getAllProperties( 251 bus, objPath, supportedConfIntf, service); 252 populateSysProperties(properties); 253 } 254 } 255 catch (const std::exception& e) 256 { 257 // Interface or property not found. Let the Interfaces Added callback 258 // process the information once the interfaces are added to D-Bus. 259 } 260 } 261 262 void PSUManager::entityManagerIfaceAdded(sdbusplus::message::message& msg) 263 { 264 try 265 { 266 sdbusplus::message::object_path objPath; 267 std::map<std::string, std::map<std::string, util::DbusVariant>> 268 interfaces; 269 msg.read(objPath, interfaces); 270 271 auto itIntf = interfaces.find(supportedConfIntf); 272 if (itIntf != interfaces.cend()) 273 { 274 populateSysProperties(itIntf->second); 275 } 276 277 itIntf = interfaces.find(IBMCFFPSInterface); 278 if (itIntf != interfaces.cend()) 279 { 280 log<level::INFO>( 281 fmt::format("InterfacesAdded for: {}", IBMCFFPSInterface) 282 .c_str()); 283 getPSUProperties(itIntf->second); 284 } 285 286 // Call to validate the psu configuration if the power is on and both 287 // the IBMCFFPSConnector and SupportedConfiguration interfaces have been 288 // processed 289 if (powerOn && !psus.empty() && !supportedConfigs.empty()) 290 { 291 validationTimer->restartOnce(validationTimeout); 292 } 293 } 294 catch (const std::exception& e) 295 { 296 // Ignore, the property may be of a different type than expected. 297 } 298 } 299 300 void PSUManager::powerStateChanged(sdbusplus::message::message& msg) 301 { 302 int32_t state = 0; 303 std::string msgSensor; 304 std::map<std::string, std::variant<int32_t>> msgData; 305 msg.read(msgSensor, msgData); 306 307 // Check if it was the Present property that changed. 308 auto valPropMap = msgData.find("state"); 309 if (valPropMap != msgData.end()) 310 { 311 state = std::get<int32_t>(valPropMap->second); 312 313 // Power is on when state=1. Clear faults. 314 if (state) 315 { 316 powerOn = true; 317 validationTimer->restartOnce(validationTimeout); 318 clearFaults(); 319 } 320 else 321 { 322 powerOn = false; 323 runValidateConfig = true; 324 } 325 } 326 } 327 328 void PSUManager::createError(const std::string& faultName, 329 std::map<std::string, std::string>& additionalData) 330 { 331 using namespace sdbusplus::xyz::openbmc_project; 332 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging"; 333 constexpr auto loggingCreateInterface = 334 "xyz.openbmc_project.Logging.Create"; 335 336 try 337 { 338 additionalData["_PID"] = std::to_string(getpid()); 339 340 auto service = 341 util::getService(loggingObjectPath, loggingCreateInterface, bus); 342 343 if (service.empty()) 344 { 345 log<level::ERR>("Unable to get logging manager service"); 346 return; 347 } 348 349 auto method = bus.new_method_call(service.c_str(), loggingObjectPath, 350 loggingCreateInterface, "Create"); 351 352 auto level = Logging::server::Entry::Level::Error; 353 method.append(faultName, level, additionalData); 354 355 auto reply = bus.call(method); 356 } 357 catch (const std::exception& e) 358 { 359 log<level::ERR>( 360 fmt::format( 361 "Failed creating event log for fault {} due to error {}", 362 faultName, e.what()) 363 .c_str()); 364 } 365 } 366 367 void PSUManager::analyze() 368 { 369 for (auto& psu : psus) 370 { 371 psu->analyze(); 372 } 373 374 if (powerOn) 375 { 376 std::map<std::string, std::string> additionalData; 377 378 for (auto& psu : psus) 379 { 380 additionalData.clear(); 381 // TODO: Fault priorities #918 382 if (!psu->isFaultLogged() && !psu->isPresent()) 383 { 384 std::map<std::string, std::string> requiredPSUsData; 385 auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData); 386 if (!requiredPSUsPresent) 387 { 388 additionalData.merge(requiredPSUsData); 389 // Create error for power supply missing. 390 additionalData["CALLOUT_INVENTORY_PATH"] = 391 psu->getInventoryPath(); 392 additionalData["CALLOUT_PRIORITY"] = "H"; 393 createError( 394 "xyz.openbmc_project.Power.PowerSupply.Error.Missing", 395 additionalData); 396 } 397 psu->setFaultLogged(); 398 } 399 else if (!psu->isFaultLogged() && psu->isFaulted()) 400 { 401 // Add STATUS_WORD and STATUS_MFR last response, in padded 402 // hexadecimal format. 403 additionalData["STATUS_WORD"] = 404 fmt::format("{:#04x}", psu->getStatusWord()); 405 additionalData["STATUS_MFR"] = 406 fmt::format("{:#02x}", psu->getMFRFault()); 407 // If there are faults being reported, they possibly could be 408 // related to a bug in the firmware version running on the power 409 // supply. Capture that data into the error as well. 410 additionalData["FW_VERSION"] = psu->getFWVersion(); 411 412 if (psu->hasCommFault()) 413 { 414 additionalData["STATUS_CML"] = 415 fmt::format("{:#02x}", psu->getStatusCML()); 416 /* Attempts to communicate with the power supply have 417 * reached there limit. Create an error. */ 418 additionalData["CALLOUT_DEVICE_PATH"] = 419 psu->getDevicePath(); 420 421 createError( 422 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault", 423 additionalData); 424 425 psu->setFaultLogged(); 426 } 427 else if ((psu->hasInputFault() || psu->hasVINUVFault())) 428 { 429 // Include STATUS_INPUT for input faults. 430 additionalData["STATUS_INPUT"] = 431 fmt::format("{:#02x}", psu->getStatusInput()); 432 433 /* The power supply location might be needed if the input 434 * fault is due to a problem with the power supply itself. 435 * Include the inventory path with a call out priority of 436 * low. 437 */ 438 additionalData["CALLOUT_INVENTORY_PATH"] = 439 psu->getInventoryPath(); 440 additionalData["CALLOUT_PRIORITY"] = "L"; 441 createError("xyz.openbmc_project.Power.PowerSupply.Error." 442 "InputFault", 443 additionalData); 444 psu->setFaultLogged(); 445 } 446 else if (psu->hasVoutOVFault()) 447 { 448 // Include STATUS_VOUT for Vout faults. 449 additionalData["STATUS_VOUT"] = 450 fmt::format("{:#02x}", psu->getStatusVout()); 451 452 additionalData["CALLOUT_INVENTORY_PATH"] = 453 psu->getInventoryPath(); 454 455 createError( 456 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 457 additionalData); 458 459 psu->setFaultLogged(); 460 } 461 else if (psu->hasMFRFault()) 462 { 463 /* This can represent a variety of faults that result in 464 * calling out the power supply for replacement: Output 465 * OverCurrent, Output Under Voltage, and potentially other 466 * faults. 467 * 468 * Also plan on putting specific fault in AdditionalData, 469 * along with register names and register values 470 * (STATUS_WORD, STATUS_MFR, etc.).*/ 471 472 additionalData["CALLOUT_INVENTORY_PATH"] = 473 psu->getInventoryPath(); 474 475 createError( 476 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 477 additionalData); 478 479 psu->setFaultLogged(); 480 } 481 } 482 } 483 } 484 } 485 486 void PSUManager::validateConfig() 487 { 488 if (!runValidateConfig || supportedConfigs.empty()) 489 { 490 return; 491 } 492 493 std::map<std::string, std::string> additionalData; 494 auto supported = hasRequiredPSUs(additionalData); 495 if (supported) 496 { 497 runValidateConfig = false; 498 return; 499 } 500 501 // Validation failed, create an error log. 502 // Return without setting the runValidateConfig flag to false because 503 // it may be that an additional supported configuration interface is 504 // added and we need to validate it to see if it matches this system. 505 createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported", 506 additionalData); 507 } 508 509 bool PSUManager::hasRequiredPSUs( 510 std::map<std::string, std::string>& additionalData) 511 { 512 std::string model{}; 513 if (!validateModelName(model, additionalData)) 514 { 515 return false; 516 } 517 518 auto presentCount = 519 std::count_if(psus.begin(), psus.end(), 520 [](const auto& psu) { return psu->isPresent(); }); 521 522 // Validate the supported configurations. A system may support more than one 523 // power supply model configuration. Since all configurations need to be 524 // checked, the additional data would contain only the information of the 525 // last configuration that did not match. 526 std::map<std::string, std::string> tmpAdditionalData; 527 for (const auto& config : supportedConfigs) 528 { 529 if (config.first != model) 530 { 531 continue; 532 } 533 if (presentCount != config.second.powerSupplyCount) 534 { 535 tmpAdditionalData.clear(); 536 tmpAdditionalData["EXPECTED_COUNT"] = 537 std::to_string(config.second.powerSupplyCount); 538 tmpAdditionalData["ACTUAL_COUNT"] = std::to_string(presentCount); 539 continue; 540 } 541 542 bool voltageValidated = true; 543 for (const auto& psu : psus) 544 { 545 if (!psu->isPresent()) 546 { 547 // Only present PSUs report a valid input voltage 548 continue; 549 } 550 551 double actualInputVoltage; 552 int inputVoltage; 553 psu->getInputVoltage(actualInputVoltage, inputVoltage); 554 555 if (std::find(config.second.inputVoltage.begin(), 556 config.second.inputVoltage.end(), 557 inputVoltage) == config.second.inputVoltage.end()) 558 { 559 tmpAdditionalData.clear(); 560 tmpAdditionalData["ACTUAL_VOLTAGE"] = 561 std::to_string(actualInputVoltage); 562 for (const auto& voltage : config.second.inputVoltage) 563 { 564 tmpAdditionalData["EXPECTED_VOLTAGE"] += 565 std::to_string(voltage) + " "; 566 } 567 tmpAdditionalData["CALLOUT_INVENTORY_PATH"] = 568 psu->getInventoryPath(); 569 570 voltageValidated = false; 571 break; 572 } 573 } 574 if (!voltageValidated) 575 { 576 continue; 577 } 578 579 return true; 580 } 581 582 additionalData.insert(tmpAdditionalData.begin(), tmpAdditionalData.end()); 583 return false; 584 } 585 586 bool PSUManager::validateModelName( 587 std::string& model, std::map<std::string, std::string>& additionalData) 588 { 589 // Check that all PSUs have the same model name. Initialize the model 590 // variable with the first PSU name found, then use it as a base to compare 591 // against the rest of the PSUs. 592 model.clear(); 593 for (const auto& psu : psus) 594 { 595 auto psuModel = psu->getModelName(); 596 if (psuModel.empty()) 597 { 598 continue; 599 } 600 if (model.empty()) 601 { 602 model = psuModel; 603 continue; 604 } 605 if (psuModel != model) 606 { 607 additionalData["EXPECTED_MODEL"] = model; 608 additionalData["ACTUAL_MODEL"] = psuModel; 609 additionalData["CALLOUT_INVENTORY_PATH"] = psu->getInventoryPath(); 610 model.clear(); 611 return false; 612 } 613 } 614 return true; 615 } 616 617 } // namespace phosphor::power::manager 618