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