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