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