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