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