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