1 #include "config.h" 2 3 #include "ibm_handler.hpp" 4 5 #include "listener.hpp" 6 #include "parser.hpp" 7 8 #include <utility/common_utility.hpp> 9 #include <utility/dbus_utility.hpp> 10 #include <utility/json_utility.hpp> 11 #include <utility/vpd_specific_utility.hpp> 12 13 namespace vpd 14 { 15 IbmHandler::IbmHandler( 16 std::shared_ptr<Worker>& o_worker, 17 std::shared_ptr<BackupAndRestore>& o_backupAndRestoreObj, 18 const std::shared_ptr<sdbusplus::asio::dbus_interface>& i_iFace, 19 const std::shared_ptr<sdbusplus::asio::dbus_interface>& i_progressiFace, 20 const std::shared_ptr<boost::asio::io_context>& i_ioCon, 21 const std::shared_ptr<sdbusplus::asio::connection>& i_asioConnection) : 22 m_worker(o_worker), m_backupAndRestoreObj(o_backupAndRestoreObj), 23 m_interface(i_iFace), m_progressInterface(i_progressiFace), 24 m_ioContext(i_ioCon), m_asioConnection(i_asioConnection) 25 { 26 if (dbusUtility::isChassisPowerOn()) 27 { 28 // At power on, less number of FRU(s) needs collection. we can scale 29 // down the threads to reduce CPU utilization. 30 m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT, 31 constants::VALUE_1); 32 } 33 else 34 { 35 // Initialize with default configuration 36 m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT); 37 } 38 39 // Set up minimal things that is needed before bus name is claimed. 40 performInitialSetup(); 41 uint16_t l_errCode = 0; 42 43 if (!m_sysCfgJsonObj.empty() && 44 jsonUtility::isBackupAndRestoreRequired(m_sysCfgJsonObj, l_errCode)) 45 { 46 try 47 { 48 m_backupAndRestoreObj = 49 std::make_shared<BackupAndRestore>(m_sysCfgJsonObj); 50 } 51 catch (const std::exception& l_ex) 52 { 53 logging::logMessage("Back up and restore instantiation failed. {" + 54 std::string(l_ex.what()) + "}"); 55 56 EventLogger::createSyncPel( 57 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 58 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), 59 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 60 } 61 } 62 else if (l_errCode) 63 { 64 logging::logMessage( 65 "Failed to check if backup & restore required. Error : " + 66 vpdSpecificUtility::getErrCodeMsg(l_errCode)); 67 } 68 69 // Instantiate Listener object 70 m_eventListener = std::make_shared<Listener>(m_worker, m_asioConnection); 71 m_eventListener->registerAssetTagChangeCallback(); 72 m_eventListener->registerHostStateChangeCallback(); 73 m_eventListener->registerPresenceChangeCallback(); 74 75 // Instantiate GpioMonitor class 76 m_gpioMonitor = 77 std::make_shared<GpioMonitor>(m_sysCfgJsonObj, m_worker, m_ioContext); 78 } 79 80 void IbmHandler::SetTimerToDetectVpdCollectionStatus() 81 { 82 // Keeping max retry for 2 minutes. TODO: Make it configurable based on 83 // system type. 84 static constexpr auto MAX_RETRY = 12; 85 86 static boost::asio::steady_timer l_timer(*m_ioContext); 87 static uint8_t l_timerRetry = 0; 88 89 auto l_asyncCancelled = l_timer.expires_after(std::chrono::seconds(10)); 90 91 (l_asyncCancelled == 0) 92 ? logging::logMessage("Collection Timer started") 93 : logging::logMessage("Collection Timer re-started"); 94 95 l_timer.async_wait([this](const boost::system::error_code& ec) { 96 if (ec == boost::asio::error::operation_aborted) 97 { 98 throw std::runtime_error( 99 "Timer to detect thread collection status was aborted"); 100 } 101 102 if (ec) 103 { 104 throw std::runtime_error( 105 "Timer to detect thread collection failed"); 106 } 107 108 if (m_worker->isAllFruCollectionDone()) 109 { 110 // cancel the timer 111 l_timer.cancel(); 112 processFailedEeproms(); 113 114 // update VPD for powerVS system. 115 ConfigurePowerVsSystem(); 116 117 std::cout << "m_worker->isSystemVPDOnDBus() completed" << std::endl; 118 m_progressInterface->set_property( 119 "Status", std::string(constants::vpdCollectionCompleted)); 120 121 if (m_backupAndRestoreObj) 122 { 123 m_backupAndRestoreObj->backupAndRestore(); 124 } 125 126 if (m_eventListener) 127 { 128 m_eventListener->registerCorrPropCallBack(); 129 } 130 } 131 else 132 { 133 auto l_threadCount = m_worker->getActiveThreadCount(); 134 if (l_timerRetry == MAX_RETRY) 135 { 136 l_timer.cancel(); 137 logging::logMessage("Taking too long. Active thread = " + 138 std::to_string(l_threadCount)); 139 } 140 else 141 { 142 l_timerRetry++; 143 logging::logMessage("Collection is in progress for [" + 144 std::to_string(l_threadCount) + "] FRUs."); 145 146 SetTimerToDetectVpdCollectionStatus(); 147 } 148 } 149 }); 150 } 151 152 void IbmHandler::checkAndUpdatePowerVsVpd( 153 const nlohmann::json& i_powerVsJsonObj, 154 std::vector<std::string>& o_failedPathList) 155 { 156 for (const auto& [l_fruPath, l_recJson] : i_powerVsJsonObj.items()) 157 { 158 nlohmann::json l_sysCfgJsonObj{}; 159 if (m_worker.get() != nullptr) 160 { 161 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 162 } 163 164 // The utility method will handle emty JSON case. No explicit 165 // handling required here. 166 uint16_t l_errCode = 0; 167 auto l_inventoryPath = jsonUtility::getInventoryObjPathFromJson( 168 l_sysCfgJsonObj, l_fruPath, l_errCode); 169 170 // Mark it as failed if inventory path not found in JSON. 171 if (l_inventoryPath.empty()) 172 { 173 if (l_errCode) 174 { 175 logging::logMessage( 176 "Failed to get inventory object path from JSON for FRU [" + 177 l_fruPath + "], error : " + 178 vpdSpecificUtility::getErrCodeMsg(l_errCode)); 179 } 180 181 o_failedPathList.push_back(l_fruPath); 182 continue; 183 } 184 185 // check if the FRU is present 186 if (!dbusUtility::isInventoryPresent(l_inventoryPath)) 187 { 188 logging::logMessage( 189 "Inventory not present, skip updating part number. Path: " + 190 l_inventoryPath); 191 continue; 192 } 193 194 // check if the FRU needs CCIN check before updating PN. 195 if (l_recJson.contains("CCIN")) 196 { 197 const auto& l_ccinFromDbus = 198 vpdSpecificUtility::getCcinFromDbus(l_inventoryPath); 199 200 // Not an ideal situation as CCIN can't be empty. 201 if (l_ccinFromDbus.empty()) 202 { 203 o_failedPathList.push_back(l_fruPath); 204 continue; 205 } 206 207 std::vector<std::string> l_ccinListFromJson = l_recJson["CCIN"]; 208 209 if (find(l_ccinListFromJson.begin(), l_ccinListFromJson.end(), 210 l_ccinFromDbus) == l_ccinListFromJson.end()) 211 { 212 // Don't update PN in this case. 213 continue; 214 } 215 } 216 217 for (const auto& [l_recordName, l_kwdJson] : l_recJson.items()) 218 { 219 // Record name can't be CCIN, skip processing as it is there for PN 220 // update based on CCIN check. 221 if (l_recordName == constants::kwdCCIN) 222 { 223 continue; 224 } 225 226 for (const auto& [l_kwdName, l_kwdValue] : l_kwdJson.items()) 227 { 228 // Is value of type array. 229 if (!l_kwdValue.is_array()) 230 { 231 o_failedPathList.push_back(l_fruPath); 232 continue; 233 } 234 235 // Get current FRU Part number. 236 auto l_retVal = dbusUtility::readDbusProperty( 237 constants::pimServiceName, l_inventoryPath, 238 constants::viniInf, constants::kwdFN); 239 240 auto l_ptrToFn = std::get_if<types::BinaryVector>(&l_retVal); 241 242 if (!l_ptrToFn) 243 { 244 o_failedPathList.push_back(l_fruPath); 245 continue; 246 } 247 248 types::BinaryVector l_binaryKwdValue = 249 l_kwdValue.get<types::BinaryVector>(); 250 if (l_binaryKwdValue == (*l_ptrToFn)) 251 { 252 continue; 253 } 254 255 // Update part number only if required. 256 std::shared_ptr<Parser> l_parserObj = 257 std::make_shared<Parser>(l_fruPath, l_sysCfgJsonObj); 258 if (l_parserObj->updateVpdKeyword(std::make_tuple( 259 l_recordName, l_kwdName, l_binaryKwdValue)) == 260 constants::FAILURE) 261 { 262 o_failedPathList.push_back(l_fruPath); 263 continue; 264 } 265 266 // update the Asset interface Spare part number explicitly. 267 if (!dbusUtility::callPIM(types::ObjectMap{ 268 {l_inventoryPath, 269 {{constants::assetInf, 270 {{"SparePartNumber", 271 std::string(l_binaryKwdValue.begin(), 272 l_binaryKwdValue.end())}}}}}})) 273 { 274 logging::logMessage( 275 "Updating Spare Part Number under Asset interface failed for path [" + 276 l_inventoryPath + "]"); 277 } 278 279 // Just needed for logging. 280 std::string l_initialPartNum((*l_ptrToFn).begin(), 281 (*l_ptrToFn).end()); 282 std::string l_finalPartNum(l_binaryKwdValue.begin(), 283 l_binaryKwdValue.end()); 284 logging::logMessage( 285 "FRU Part number updated for path [" + l_inventoryPath + 286 "]" + "From [" + l_initialPartNum + "]" + " to [" + 287 l_finalPartNum + "]"); 288 } 289 } 290 } 291 } 292 293 void IbmHandler::ConfigurePowerVsSystem() 294 { 295 std::vector<std::string> l_failedPathList; 296 try 297 { 298 types::BinaryVector l_imValue = dbusUtility::getImFromDbus(); 299 if (l_imValue.empty()) 300 { 301 throw DbusException("Invalid IM value read from Dbus"); 302 } 303 304 if (!vpdSpecificUtility::isPowerVsConfiguration(l_imValue)) 305 { 306 // TODO: Should booting be blocked in case of some 307 // misconfigurations? 308 return; 309 } 310 311 uint16_t l_errCode = 0; 312 const nlohmann::json& l_powerVsJsonObj = 313 jsonUtility::getPowerVsJson(l_imValue, l_errCode); 314 315 if (l_powerVsJsonObj.empty()) 316 { 317 throw std::runtime_error( 318 "PowerVS Json not found. Error : " + 319 vpdSpecificUtility::getErrCodeMsg(l_errCode)); 320 } 321 322 checkAndUpdatePowerVsVpd(l_powerVsJsonObj, l_failedPathList); 323 324 if (!l_failedPathList.empty()) 325 { 326 throw std::runtime_error( 327 "Part number update failed for following paths: "); 328 } 329 } 330 catch (const std::exception& l_ex) 331 { 332 // TODO log appropriate PEL 333 } 334 } 335 336 void IbmHandler::processFailedEeproms() 337 { 338 if (m_worker.get() != nullptr) 339 { 340 // TODO: 341 // - iterate through list of EEPROMs for which thread creation has 342 // failed 343 // - For each failed EEPROM, trigger VPD collection 344 m_worker->getFailedEepromPaths().clear(); 345 } 346 } 347 348 void IbmHandler::primeSystemBlueprint() 349 { 350 if (m_sysCfgJsonObj.empty()) 351 { 352 return; 353 } 354 355 const nlohmann::json& l_listOfFrus = 356 m_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>(); 357 358 for (const auto& l_itemFRUS : l_listOfFrus.items()) 359 { 360 const std::string& l_vpdFilePath = l_itemFRUS.key(); 361 362 if (l_vpdFilePath == SYSTEM_VPD_FILE_PATH) 363 { 364 continue; 365 } 366 367 // Prime the inventry for FRUs which 368 // are not present/processing had some error. 369 if (m_worker.get() != nullptr && 370 !m_worker->primeInventory(l_vpdFilePath)) 371 { 372 logging::logMessage( 373 "Priming of inventory failed for FRU " + l_vpdFilePath); 374 } 375 } 376 } 377 378 void IbmHandler::enableMuxChips() 379 { 380 if (m_sysCfgJsonObj.empty()) 381 { 382 // config JSON should not be empty at this point of execution. 383 throw std::runtime_error("Config JSON is empty. Can't enable muxes"); 384 return; 385 } 386 387 if (!m_sysCfgJsonObj.contains("muxes")) 388 { 389 logging::logMessage("No mux defined for the system in config JSON"); 390 return; 391 } 392 393 // iterate over each MUX detail and enable them. 394 for (const auto& item : m_sysCfgJsonObj["muxes"]) 395 { 396 if (item.contains("holdidlepath")) 397 { 398 std::string cmd = "echo 0 > "; 399 cmd += item["holdidlepath"]; 400 401 logging::logMessage("Enabling mux with command = " + cmd); 402 403 commonUtility::executeCmd(cmd); 404 continue; 405 } 406 407 logging::logMessage( 408 "Mux Entry does not have hold idle path. Can't enable the mux"); 409 } 410 } 411 412 void IbmHandler::performInitialSetup() 413 { 414 try 415 { 416 if (m_worker.get() == nullptr) 417 { 418 throw std::runtime_error( 419 "Worker object not found. Can't perform initial setup."); 420 } 421 422 m_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 423 if (!dbusUtility::isChassisPowerOn()) 424 { 425 m_worker->setDeviceTreeAndJson(); 426 427 // Since the above function setDeviceTreeAndJson can change the json 428 // which is used, we would need to reacquire the json object again 429 // here. 430 m_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 431 432 if (isPrimingRequired()) 433 { 434 primeSystemBlueprint(); 435 } 436 } 437 438 // Enable all mux which are used for connecting to the i2c on the 439 // pcie slots for pcie cards. These are not enabled by kernel due to 440 // an issue seen with Castello cards, where the i2c line hangs on a 441 // probe. 442 enableMuxChips(); 443 444 // Nothing needs to be done. Service restarted or BMC re-booted for 445 // some reason at system power on. 446 } 447 catch (const std::exception& l_ex) 448 { 449 m_worker->setCollectionStatusProperty(SYSTEM_VPD_FILE_PATH, 450 constants::vpdCollectionFailed); 451 // Any issue in system's inital set up is handled in this catch. Error 452 // will not propogate to manager. 453 EventLogger::createSyncPel( 454 EventLogger::getErrorType(l_ex), types::SeverityType::Critical, 455 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), 456 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 457 } 458 } 459 460 bool IbmHandler::isPrimingRequired() const noexcept 461 { 462 try 463 { 464 // get all object paths under PIM 465 const auto l_objectPaths = dbusUtility::GetSubTreePaths( 466 constants::systemInvPath, 0, 467 std::vector<std::string>{constants::vpdCollectionInterface}); 468 469 const nlohmann::json& l_listOfFrus = 470 m_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>(); 471 472 size_t l_invPathCount = 0; 473 474 for (const auto& l_itemFRUS : l_listOfFrus.items()) 475 { 476 for (const auto& l_Fru : l_itemFRUS.value()) 477 { 478 if (l_Fru.contains("ccin") || (l_Fru.contains("noprime") && 479 l_Fru.value("noprime", false))) 480 { 481 continue; 482 } 483 484 l_invPathCount += 1; 485 } 486 } 487 return ((l_objectPaths.size() >= l_invPathCount) ? false : true); 488 } 489 catch (const std::exception& l_ex) 490 { 491 logging::logMessage( 492 "Error while checking is priming required or not, error: " + 493 std::string(l_ex.what())); 494 } 495 496 // In case of any error, perform priming, as it's unclear whether priming is 497 // required. 498 return true; 499 } 500 501 void IbmHandler::collectAllFruVpd() 502 { 503 // Setting status to "InProgress", before trigeering VPD collection. 504 m_progressInterface->set_property( 505 "Status", std::string(constants::vpdCollectionInProgress)); 506 m_worker->collectFrusFromJson(); 507 SetTimerToDetectVpdCollectionStatus(); 508 } 509 } // namespace vpd 510