1 #include "config.h" 2 3 #include "manager.hpp" 4 5 #include "constants.hpp" 6 #include "exceptions.hpp" 7 #include "logger.hpp" 8 #include "parser.hpp" 9 #include "parser_factory.hpp" 10 #include "parser_interface.hpp" 11 #include "single_fab.hpp" 12 #include "types.hpp" 13 #include "utility/dbus_utility.hpp" 14 #include "utility/json_utility.hpp" 15 #include "utility/vpd_specific_utility.hpp" 16 17 #include <boost/asio/steady_timer.hpp> 18 #include <sdbusplus/bus/match.hpp> 19 #include <sdbusplus/message.hpp> 20 21 namespace vpd 22 { 23 Manager::Manager( 24 const std::shared_ptr<boost::asio::io_context>& ioCon, 25 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iFace, 26 const std::shared_ptr<sdbusplus::asio::connection>& asioConnection) : 27 m_ioContext(ioCon), m_interface(iFace), m_asioConnection(asioConnection) 28 { 29 #ifdef IBM_SYSTEM 30 if (!dbusUtility::isChassisPowerOn()) 31 { 32 SingleFab l_singleFab; 33 const int& l_rc = l_singleFab.singleFabImOverride(); 34 35 if (l_rc == constants::FAILURE) 36 { 37 throw std::runtime_error( 38 std::string(__FUNCTION__) + 39 " : Found an invalid system configuration. Needs manual intervention. BMC is being quiesced."); 40 } 41 } 42 #endif 43 44 try 45 { 46 #ifdef IBM_SYSTEM 47 if (dbusUtility::isChassisPowerOn()) 48 { 49 // At power on, less number of FRU(s) needs collection. we can scale 50 // down the threads to reduce CPU utilization. 51 m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT, 52 constants::VALUE_1); 53 } 54 else 55 { 56 // Initialize with default configuration 57 m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT); 58 } 59 60 // Set up minimal things that is needed before bus name is claimed. 61 m_worker->performInitialSetup(); 62 63 const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 64 if (!m_worker->getSysCfgJsonObj().empty() && 65 jsonUtility::isBackupAndRestoreRequired(l_sysCfgJsonObj)) 66 { 67 try 68 { 69 m_backupAndRestoreObj = 70 std::make_shared<BackupAndRestore>(l_sysCfgJsonObj); 71 } 72 catch (const std::exception& l_ex) 73 { 74 logging::logMessage( 75 "Back up and restore instantiation failed. {" + 76 std::string(l_ex.what()) + "}"); 77 78 EventLogger::createSyncPel( 79 EventLogger::getErrorType(l_ex), 80 types::SeverityType::Warning, __FILE__, __FUNCTION__, 0, 81 EventLogger::getErrorMsg(l_ex), std::nullopt, std::nullopt, 82 std::nullopt, std::nullopt); 83 } 84 } 85 86 // set callback to detect any asset tag change 87 registerAssetTagChangeCallback(); 88 89 // set async timer to detect if system VPD is published on D-Bus. 90 SetTimerToDetectSVPDOnDbus(); 91 92 // set async timer to detect if VPD collection is done. 93 SetTimerToDetectVpdCollectionStatus(); 94 95 // Instantiate GpioMonitor class 96 m_gpioMonitor = std::make_shared<GpioMonitor>( 97 m_worker->getSysCfgJsonObj(), m_worker, m_ioContext); 98 99 #endif 100 // set callback to detect host state change. 101 registerHostStateChangeCallback(); 102 103 // For backward compatibility. Should be depricated. 104 iFace->register_method( 105 "WriteKeyword", 106 [this](const sdbusplus::message::object_path i_path, 107 const std::string i_recordName, const std::string i_keyword, 108 const types::BinaryVector i_value) -> int { 109 return this->updateKeyword( 110 i_path, std::make_tuple(i_recordName, i_keyword, i_value)); 111 }); 112 113 // Register methods under com.ibm.VPD.Manager interface 114 iFace->register_method( 115 "UpdateKeyword", 116 [this](const types::Path i_vpdPath, 117 const types::WriteVpdParams i_paramsToWriteData) -> int { 118 return this->updateKeyword(i_vpdPath, i_paramsToWriteData); 119 }); 120 121 iFace->register_method( 122 "WriteKeywordOnHardware", 123 [this](const types::Path i_fruPath, 124 const types::WriteVpdParams i_paramsToWriteData) -> int { 125 return this->updateKeywordOnHardware(i_fruPath, 126 i_paramsToWriteData); 127 }); 128 129 iFace->register_method( 130 "ReadKeyword", 131 [this](const types::Path i_fruPath, 132 const types::ReadVpdParams i_paramsToReadData) 133 -> types::DbusVariantType { 134 return this->readKeyword(i_fruPath, i_paramsToReadData); 135 }); 136 137 iFace->register_method( 138 "CollectFRUVPD", 139 [this](const sdbusplus::message::object_path& i_dbusObjPath) { 140 this->collectSingleFruVpd(i_dbusObjPath); 141 }); 142 143 iFace->register_method( 144 "deleteFRUVPD", 145 [this](const sdbusplus::message::object_path& i_dbusObjPath) { 146 this->deleteSingleFruVpd(i_dbusObjPath); 147 }); 148 149 iFace->register_method( 150 "GetExpandedLocationCode", 151 [this](const std::string& i_unexpandedLocationCode, 152 uint16_t& i_nodeNumber) -> std::string { 153 return this->getExpandedLocationCode(i_unexpandedLocationCode, 154 i_nodeNumber); 155 }); 156 157 iFace->register_method("GetFRUsByExpandedLocationCode", 158 [this](const std::string& i_expandedLocationCode) 159 -> types::ListOfPaths { 160 return this->getFrusByExpandedLocationCode( 161 i_expandedLocationCode); 162 }); 163 164 iFace->register_method( 165 "GetFRUsByUnexpandedLocationCode", 166 [this](const std::string& i_unexpandedLocationCode, 167 uint16_t& i_nodeNumber) -> types::ListOfPaths { 168 return this->getFrusByUnexpandedLocationCode( 169 i_unexpandedLocationCode, i_nodeNumber); 170 }); 171 172 iFace->register_method( 173 "GetHardwarePath", 174 [this](const sdbusplus::message::object_path& i_dbusObjPath) 175 -> std::string { return this->getHwPath(i_dbusObjPath); }); 176 177 iFace->register_method("PerformVPDRecollection", [this]() { 178 this->performVpdRecollection(); 179 }); 180 181 // Indicates FRU VPD collection for the system has not started. 182 iFace->register_property_rw<std::string>( 183 "CollectionStatus", sdbusplus::vtable::property_::emits_change, 184 [this](const std::string l_currStatus, const auto&) { 185 m_vpdCollectionStatus = l_currStatus; 186 return 0; 187 }, 188 [this](const auto&) { return m_vpdCollectionStatus; }); 189 } 190 catch (const std::exception& e) 191 { 192 logging::logMessage( 193 "Manager class instantiation failed. " + std::string(e.what())); 194 195 vpd::EventLogger::createSyncPel( 196 vpd::EventLogger::getErrorType(e), vpd::types::SeverityType::Error, 197 __FILE__, __FUNCTION__, 0, vpd::EventLogger::getErrorMsg(e), 198 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 199 } 200 } 201 202 #ifdef IBM_SYSTEM 203 void Manager::registerAssetTagChangeCallback() 204 { 205 static std::shared_ptr<sdbusplus::bus::match_t> l_assetMatch = 206 std::make_shared<sdbusplus::bus::match_t>( 207 *m_asioConnection, 208 sdbusplus::bus::match::rules::propertiesChanged( 209 constants::systemInvPath, constants::assetTagInf), 210 [this](sdbusplus::message_t& l_msg) { 211 processAssetTagChangeCallback(l_msg); 212 }); 213 } 214 215 void Manager::processAssetTagChangeCallback(sdbusplus::message_t& i_msg) 216 { 217 try 218 { 219 if (i_msg.is_method_error()) 220 { 221 throw std::runtime_error( 222 "Error reading callback msg for asset tag."); 223 } 224 225 std::string l_objectPath; 226 types::PropertyMap l_propMap; 227 i_msg.read(l_objectPath, l_propMap); 228 229 const auto& l_itrToAssetTag = l_propMap.find("AssetTag"); 230 if (l_itrToAssetTag != l_propMap.end()) 231 { 232 if (auto l_assetTag = 233 std::get_if<std::string>(&(l_itrToAssetTag->second))) 234 { 235 // Call Notify to persist the AssetTag 236 types::ObjectMap l_objectMap = { 237 {sdbusplus::message::object_path(constants::systemInvPath), 238 {{constants::assetTagInf, {{"AssetTag", *l_assetTag}}}}}}; 239 240 // Notify PIM 241 if (!dbusUtility::callPIM(move(l_objectMap))) 242 { 243 throw std::runtime_error( 244 "Call to PIM failed for asset tag update."); 245 } 246 } 247 } 248 else 249 { 250 throw std::runtime_error( 251 "Could not find asset tag in callback message."); 252 } 253 } 254 catch (const std::exception& l_ex) 255 { 256 // TODO: Log PEL with below description. 257 logging::logMessage("Asset tag callback update failed with error: " + 258 std::string(l_ex.what())); 259 } 260 } 261 262 void Manager::SetTimerToDetectSVPDOnDbus() 263 { 264 try 265 { 266 static boost::asio::steady_timer timer(*m_ioContext); 267 268 // timer for 2 seconds 269 auto asyncCancelled = timer.expires_after(std::chrono::seconds(2)); 270 271 (asyncCancelled == 0) ? logging::logMessage("Timer started") 272 : logging::logMessage("Timer re-started"); 273 274 timer.async_wait([this](const boost::system::error_code& ec) { 275 if (ec == boost::asio::error::operation_aborted) 276 { 277 throw std::runtime_error( 278 std::string(__FUNCTION__) + 279 ": Timer to detect system VPD collection status was aborted."); 280 } 281 282 if (ec) 283 { 284 throw std::runtime_error( 285 std::string(__FUNCTION__) + 286 ": Timer to detect System VPD collection failed"); 287 } 288 289 if (m_worker->isSystemVPDOnDBus()) 290 { 291 // cancel the timer 292 timer.cancel(); 293 294 // Triggering FRU VPD collection. Setting status to "In 295 // Progress". 296 m_interface->set_property("CollectionStatus", 297 std::string("InProgress")); 298 m_worker->collectFrusFromJson(); 299 } 300 }); 301 } 302 catch (const std::exception& l_ex) 303 { 304 EventLogger::createAsyncPel( 305 EventLogger::getErrorType(l_ex), types::SeverityType::Critical, 306 __FILE__, __FUNCTION__, 0, 307 std::string("Collection for FRUs failed with reason:") + 308 EventLogger::getErrorMsg(l_ex), 309 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 310 } 311 } 312 313 void Manager::SetTimerToDetectVpdCollectionStatus() 314 { 315 // Keeping max retry for 2 minutes. TODO: Make it configurable based on 316 // system type. 317 static constexpr auto MAX_RETRY = 12; 318 319 static boost::asio::steady_timer l_timer(*m_ioContext); 320 static uint8_t l_timerRetry = 0; 321 322 auto l_asyncCancelled = l_timer.expires_after(std::chrono::seconds(10)); 323 324 (l_asyncCancelled == 0) 325 ? logging::logMessage("Collection Timer started") 326 : logging::logMessage("Collection Timer re-started"); 327 328 l_timer.async_wait([this](const boost::system::error_code& ec) { 329 if (ec == boost::asio::error::operation_aborted) 330 { 331 throw std::runtime_error( 332 "Timer to detect thread collection status was aborted"); 333 } 334 335 if (ec) 336 { 337 throw std::runtime_error( 338 "Timer to detect thread collection failed"); 339 } 340 341 if (m_worker->isAllFruCollectionDone()) 342 { 343 // cancel the timer 344 l_timer.cancel(); 345 processFailedEeproms(); 346 347 // update VPD for powerVS system. 348 ConfigurePowerVsSystem(); 349 350 m_interface->set_property("CollectionStatus", 351 std::string("Completed")); 352 353 if (m_backupAndRestoreObj) 354 { 355 m_backupAndRestoreObj->backupAndRestore(); 356 } 357 } 358 else 359 { 360 auto l_threadCount = m_worker->getActiveThreadCount(); 361 if (l_timerRetry == MAX_RETRY) 362 { 363 l_timer.cancel(); 364 logging::logMessage("Taking too long. Active thread = " + 365 std::to_string(l_threadCount)); 366 } 367 else 368 { 369 l_timerRetry++; 370 logging::logMessage("Collection is in progress for [" + 371 std::to_string(l_threadCount) + "] FRUs."); 372 373 SetTimerToDetectVpdCollectionStatus(); 374 } 375 } 376 }); 377 } 378 379 void Manager::checkAndUpdatePowerVsVpd( 380 const nlohmann::json& i_powerVsJsonObj, 381 std::vector<std::string>& o_failedPathList) 382 { 383 for (const auto& [l_fruPath, l_recJson] : i_powerVsJsonObj.items()) 384 { 385 nlohmann::json l_sysCfgJsonObj{}; 386 if (m_worker.get() != nullptr) 387 { 388 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 389 } 390 391 // The utility method will handle emty JSON case. No explicit 392 // handling required here. 393 auto l_inventoryPath = jsonUtility::getInventoryObjPathFromJson( 394 l_sysCfgJsonObj, l_fruPath); 395 396 // Mark it as failed if inventory path not found in JSON. 397 if (l_inventoryPath.empty()) 398 { 399 o_failedPathList.push_back(l_fruPath); 400 continue; 401 } 402 403 // check if the FRU is present 404 if (!dbusUtility::isInventoryPresent(l_inventoryPath)) 405 { 406 logging::logMessage( 407 "Inventory not present, skip updating part number. Path: " + 408 l_inventoryPath); 409 continue; 410 } 411 412 // check if the FRU needs CCIN check before updating PN. 413 if (l_recJson.contains("CCIN")) 414 { 415 const auto& l_ccinFromDbus = 416 vpdSpecificUtility::getCcinFromDbus(l_inventoryPath); 417 418 // Not an ideal situation as CCIN can't be empty. 419 if (l_ccinFromDbus.empty()) 420 { 421 o_failedPathList.push_back(l_fruPath); 422 continue; 423 } 424 425 std::vector<std::string> l_ccinListFromJson = l_recJson["CCIN"]; 426 427 if (find(l_ccinListFromJson.begin(), l_ccinListFromJson.end(), 428 l_ccinFromDbus) == l_ccinListFromJson.end()) 429 { 430 // Don't update PN in this case. 431 continue; 432 } 433 } 434 435 for (const auto& [l_recordName, l_kwdJson] : l_recJson.items()) 436 { 437 // Record name can't be CCIN, skip processing as it is there for PN 438 // update based on CCIN check. 439 if (l_recordName == constants::kwdCCIN) 440 { 441 continue; 442 } 443 444 for (const auto& [l_kwdName, l_kwdValue] : l_kwdJson.items()) 445 { 446 // Is value of type array. 447 if (!l_kwdValue.is_array()) 448 { 449 o_failedPathList.push_back(l_fruPath); 450 continue; 451 } 452 453 // Get current FRU Part number. 454 auto l_retVal = dbusUtility::readDbusProperty( 455 constants::pimServiceName, l_inventoryPath, 456 constants::viniInf, constants::kwdFN); 457 458 auto l_ptrToFn = std::get_if<types::BinaryVector>(&l_retVal); 459 460 if (!l_ptrToFn) 461 { 462 o_failedPathList.push_back(l_fruPath); 463 continue; 464 } 465 466 types::BinaryVector l_binaryKwdValue = 467 l_kwdValue.get<types::BinaryVector>(); 468 if (l_binaryKwdValue == (*l_ptrToFn)) 469 { 470 continue; 471 } 472 473 // Update part number only if required. 474 if (updateKeyword( 475 l_fruPath, 476 std::make_tuple(l_recordName, l_kwdName, l_kwdValue)) == 477 constants::FAILURE) 478 { 479 o_failedPathList.push_back(l_fruPath); 480 continue; 481 } 482 483 // update the Asset interface Spare part number explicitly. 484 if (!dbusUtility::callPIM(types::ObjectMap{ 485 {l_inventoryPath, 486 {{constants::assetInf, 487 {{"SparePartNumber", 488 std::string(l_binaryKwdValue.begin(), 489 l_binaryKwdValue.end())}}}}}})) 490 { 491 logging::logMessage( 492 "Updating Spare Part Number under Asset interface failed for path [" + 493 l_inventoryPath + "]"); 494 } 495 496 // Just needed for logging. 497 std::string l_initialPartNum((*l_ptrToFn).begin(), 498 (*l_ptrToFn).end()); 499 std::string l_finalPartNum(l_binaryKwdValue.begin(), 500 l_binaryKwdValue.end()); 501 logging::logMessage( 502 "FRU Part number updated for path [" + l_inventoryPath + 503 "]" + "From [" + l_initialPartNum + "]" + " to [" + 504 l_finalPartNum + "]"); 505 } 506 } 507 } 508 } 509 510 void Manager::ConfigurePowerVsSystem() 511 { 512 std::vector<std::string> l_failedPathList; 513 try 514 { 515 types::BinaryVector l_imValue = dbusUtility::getImFromDbus(); 516 if (l_imValue.empty()) 517 { 518 throw DbusException("Invalid IM value read from Dbus"); 519 } 520 521 if (!vpdSpecificUtility::isPowerVsConfiguration(l_imValue)) 522 { 523 // TODO: Should booting be blocked in case of some 524 // misconfigurations? 525 return; 526 } 527 528 const nlohmann::json& l_powerVsJsonObj = 529 jsonUtility::getPowerVsJson(l_imValue); 530 531 if (l_powerVsJsonObj.empty()) 532 { 533 throw std::runtime_error("PowerVS Json not found"); 534 } 535 536 checkAndUpdatePowerVsVpd(l_powerVsJsonObj, l_failedPathList); 537 538 if (!l_failedPathList.empty()) 539 { 540 throw std::runtime_error( 541 "Part number update failed for following paths: "); 542 } 543 } 544 catch (const std::exception& l_ex) 545 { 546 // TODO log appropriate PEL 547 } 548 } 549 550 void Manager::processFailedEeproms() 551 { 552 if (m_worker.get() != nullptr) 553 { 554 // TODO: 555 // - iterate through list of EEPROMs for which thread creation has 556 // failed 557 // - For each failed EEPROM, trigger VPD collection 558 m_worker->getFailedEepromPaths().clear(); 559 } 560 } 561 #endif 562 563 int Manager::updateKeyword(const types::Path i_vpdPath, 564 const types::WriteVpdParams i_paramsToWriteData) 565 { 566 if (i_vpdPath.empty()) 567 { 568 logging::logMessage("Given VPD path is empty."); 569 return -1; 570 } 571 572 types::Path l_fruPath; 573 nlohmann::json l_sysCfgJsonObj{}; 574 575 if (m_worker.get() != nullptr) 576 { 577 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 578 579 // Get the EEPROM path 580 if (!l_sysCfgJsonObj.empty()) 581 { 582 l_fruPath = 583 jsonUtility::getFruPathFromJson(l_sysCfgJsonObj, i_vpdPath); 584 } 585 } 586 587 if (l_fruPath.empty()) 588 { 589 l_fruPath = i_vpdPath; 590 } 591 592 try 593 { 594 std::shared_ptr<Parser> l_parserObj = 595 std::make_shared<Parser>(l_fruPath, l_sysCfgJsonObj); 596 auto l_rc = l_parserObj->updateVpdKeyword(i_paramsToWriteData); 597 598 if (l_rc != constants::FAILURE && m_backupAndRestoreObj) 599 { 600 if (m_backupAndRestoreObj->updateKeywordOnPrimaryOrBackupPath( 601 l_fruPath, i_paramsToWriteData) < constants::VALUE_0) 602 { 603 logging::logMessage( 604 "Write success, but backup and restore failed for file[" + 605 l_fruPath + "]"); 606 } 607 } 608 return l_rc; 609 } 610 catch (const std::exception& l_exception) 611 { 612 // TODO:: error log needed 613 logging::logMessage("Update keyword failed for file[" + i_vpdPath + 614 "], reason: " + std::string(l_exception.what())); 615 return -1; 616 } 617 } 618 619 int Manager::updateKeywordOnHardware( 620 const types::Path i_fruPath, 621 const types::WriteVpdParams i_paramsToWriteData) noexcept 622 { 623 try 624 { 625 if (i_fruPath.empty()) 626 { 627 throw std::runtime_error("Given FRU path is empty"); 628 } 629 630 nlohmann::json l_sysCfgJsonObj{}; 631 632 if (m_worker.get() != nullptr) 633 { 634 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 635 } 636 637 std::shared_ptr<Parser> l_parserObj = 638 std::make_shared<Parser>(i_fruPath, l_sysCfgJsonObj); 639 return l_parserObj->updateVpdKeywordOnHardware(i_paramsToWriteData); 640 } 641 catch (const std::exception& l_exception) 642 { 643 EventLogger::createAsyncPel( 644 types::ErrorType::InvalidEeprom, types::SeverityType::Informational, 645 __FILE__, __FUNCTION__, 0, 646 "Update keyword on hardware failed for file[" + i_fruPath + 647 "], reason: " + std::string(l_exception.what()), 648 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 649 650 return constants::FAILURE; 651 } 652 } 653 654 types::DbusVariantType Manager::readKeyword( 655 const types::Path i_fruPath, const types::ReadVpdParams i_paramsToReadData) 656 { 657 try 658 { 659 nlohmann::json l_jsonObj{}; 660 661 if (m_worker.get() != nullptr) 662 { 663 l_jsonObj = m_worker->getSysCfgJsonObj(); 664 } 665 666 std::error_code ec; 667 668 // Check if given path is filesystem path 669 if (!std::filesystem::exists(i_fruPath, ec) && (ec)) 670 { 671 throw std::runtime_error( 672 "Given file path " + i_fruPath + " not found."); 673 } 674 675 logging::logMessage("Performing VPD read on " + i_fruPath); 676 677 std::shared_ptr<vpd::Parser> l_parserObj = 678 std::make_shared<vpd::Parser>(i_fruPath, l_jsonObj); 679 680 std::shared_ptr<vpd::ParserInterface> l_vpdParserInstance = 681 l_parserObj->getVpdParserInstance(); 682 683 return ( 684 l_vpdParserInstance->readKeywordFromHardware(i_paramsToReadData)); 685 } 686 catch (const std::exception& e) 687 { 688 logging::logMessage( 689 e.what() + std::string(". VPD manager read operation failed for ") + 690 i_fruPath); 691 throw types::DeviceError::ReadFailure(); 692 } 693 } 694 695 void Manager::collectSingleFruVpd( 696 const sdbusplus::message::object_path& i_dbusObjPath) 697 { 698 try 699 { 700 if (m_vpdCollectionStatus != "Completed") 701 { 702 logging::logMessage( 703 "Currently VPD CollectionStatus is not completed. Cannot perform single FRU VPD collection for " + 704 std::string(i_dbusObjPath)); 705 return; 706 } 707 708 // Get system config JSON object from worker class 709 nlohmann::json l_sysCfgJsonObj{}; 710 711 if (m_worker.get() != nullptr) 712 { 713 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 714 } 715 716 // Check if system config JSON is present 717 if (l_sysCfgJsonObj.empty()) 718 { 719 logging::logMessage( 720 "System config JSON object not present. Single FRU VPD collection is not performed for " + 721 std::string(i_dbusObjPath)); 722 return; 723 } 724 725 // Get FRU path for the given D-bus object path from JSON 726 const std::string& l_fruPath = 727 jsonUtility::getFruPathFromJson(l_sysCfgJsonObj, i_dbusObjPath); 728 729 if (l_fruPath.empty()) 730 { 731 logging::logMessage( 732 "D-bus object path not present in JSON. Single FRU VPD collection is not performed for " + 733 std::string(i_dbusObjPath)); 734 return; 735 } 736 737 // Check if host is up and running 738 if (dbusUtility::isHostRunning()) 739 { 740 if (!jsonUtility::isFruReplaceableAtRuntime(l_sysCfgJsonObj, 741 l_fruPath)) 742 { 743 logging::logMessage( 744 "Given FRU is not replaceable at host runtime. Single FRU VPD collection is not performed for " + 745 std::string(i_dbusObjPath)); 746 return; 747 } 748 } 749 else if (dbusUtility::isBMCReady()) 750 { 751 if (!jsonUtility::isFruReplaceableAtStandby(l_sysCfgJsonObj, 752 l_fruPath) && 753 (!jsonUtility::isFruReplaceableAtRuntime(l_sysCfgJsonObj, 754 l_fruPath))) 755 { 756 logging::logMessage( 757 "Given FRU is neither replaceable at standby nor replaceable at runtime. Single FRU VPD collection is not performed for " + 758 std::string(i_dbusObjPath)); 759 return; 760 } 761 } 762 763 // Set CollectionStatus as InProgress. Since it's an intermediate state 764 // D-bus set-property call is good enough to update the status. 765 const std::string& l_collStatusProp = "CollectionStatus"; 766 767 if (!dbusUtility::writeDbusProperty( 768 jsonUtility::getServiceName(l_sysCfgJsonObj, 769 std::string(i_dbusObjPath)), 770 std::string(i_dbusObjPath), constants::vpdCollectionInterface, 771 l_collStatusProp, 772 types::DbusVariantType{constants::vpdCollectionInProgress})) 773 { 774 logging::logMessage( 775 "Unable to set CollectionStatus as InProgress for " + 776 std::string(i_dbusObjPath) + 777 ". Continue single FRU VPD collection."); 778 } 779 780 // Parse VPD 781 types::VPDMapVariant l_parsedVpd = m_worker->parseVpdFile(l_fruPath); 782 783 // If l_parsedVpd is pointing to std::monostate 784 if (l_parsedVpd.index() == 0) 785 { 786 throw std::runtime_error( 787 "VPD parsing failed for " + std::string(i_dbusObjPath)); 788 } 789 790 // Get D-bus object map from worker class 791 types::ObjectMap l_dbusObjectMap; 792 m_worker->populateDbus(l_parsedVpd, l_dbusObjectMap, l_fruPath); 793 794 if (l_dbusObjectMap.empty()) 795 { 796 throw std::runtime_error( 797 "Failed to create D-bus object map. Single FRU VPD collection failed for " + 798 std::string(i_dbusObjPath)); 799 } 800 801 // Call PIM's Notify method 802 if (!dbusUtility::callPIM(move(l_dbusObjectMap))) 803 { 804 throw std::runtime_error( 805 "Notify PIM failed. Single FRU VPD collection failed for " + 806 std::string(i_dbusObjPath)); 807 } 808 } 809 catch (const std::exception& l_error) 810 { 811 // Notify FRU's VPD CollectionStatus as Failure 812 if (!dbusUtility::notifyFRUCollectionStatus( 813 std::string(i_dbusObjPath), constants::vpdCollectionFailure)) 814 { 815 logging::logMessage( 816 "Call to PIM Notify method failed to update Collection status as Failure for " + 817 std::string(i_dbusObjPath)); 818 } 819 820 // TODO: Log PEL 821 logging::logMessage(std::string(l_error.what())); 822 } 823 } 824 825 void Manager::deleteSingleFruVpd( 826 const sdbusplus::message::object_path& i_dbusObjPath) 827 { 828 try 829 { 830 if (std::string(i_dbusObjPath).empty()) 831 { 832 throw std::runtime_error( 833 "Given DBus object path is empty. Aborting FRU VPD deletion."); 834 } 835 836 if (m_worker.get() == nullptr) 837 { 838 throw std::runtime_error( 839 "Worker object not found, can't perform FRU VPD deletion for: " + 840 std::string(i_dbusObjPath)); 841 } 842 843 m_worker->deleteFruVpd(std::string(i_dbusObjPath)); 844 } 845 catch (const std::exception& l_ex) 846 { 847 // TODO: Log PEL 848 logging::logMessage(l_ex.what()); 849 } 850 } 851 852 bool Manager::isValidUnexpandedLocationCode( 853 const std::string& i_unexpandedLocationCode) 854 { 855 if ((i_unexpandedLocationCode.length() < 856 constants::UNEXP_LOCATION_CODE_MIN_LENGTH) || 857 ((i_unexpandedLocationCode.compare(0, 4, "Ufcs") != 858 constants::STR_CMP_SUCCESS) && 859 (i_unexpandedLocationCode.compare(0, 4, "Umts") != 860 constants::STR_CMP_SUCCESS)) || 861 ((i_unexpandedLocationCode.length() > 862 constants::UNEXP_LOCATION_CODE_MIN_LENGTH) && 863 (i_unexpandedLocationCode.find("-") != 4))) 864 { 865 return false; 866 } 867 868 return true; 869 } 870 871 std::string Manager::getExpandedLocationCode( 872 const std::string& i_unexpandedLocationCode, 873 [[maybe_unused]] const uint16_t i_nodeNumber) 874 { 875 if (!isValidUnexpandedLocationCode(i_unexpandedLocationCode)) 876 { 877 phosphor::logging::elog<types::DbusInvalidArgument>( 878 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 879 types::InvalidArgument::ARGUMENT_VALUE( 880 i_unexpandedLocationCode.c_str())); 881 } 882 883 const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 884 if (!l_sysCfgJsonObj.contains("frus")) 885 { 886 logging::logMessage("Missing frus tag in system config JSON"); 887 } 888 889 const nlohmann::json& l_listOfFrus = 890 l_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>(); 891 892 for (const auto& l_frus : l_listOfFrus.items()) 893 { 894 for (const auto& l_aFru : l_frus.value()) 895 { 896 if (l_aFru["extraInterfaces"].contains( 897 constants::locationCodeInf) && 898 l_aFru["extraInterfaces"][constants::locationCodeInf].value( 899 "LocationCode", "") == i_unexpandedLocationCode) 900 { 901 return std::get<std::string>(dbusUtility::readDbusProperty( 902 l_aFru["serviceName"], l_aFru["inventoryPath"], 903 constants::locationCodeInf, "LocationCode")); 904 } 905 } 906 } 907 phosphor::logging::elog<types::DbusInvalidArgument>( 908 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 909 types::InvalidArgument::ARGUMENT_VALUE( 910 i_unexpandedLocationCode.c_str())); 911 } 912 913 types::ListOfPaths Manager::getFrusByUnexpandedLocationCode( 914 const std::string& i_unexpandedLocationCode, 915 [[maybe_unused]] const uint16_t i_nodeNumber) 916 { 917 types::ListOfPaths l_inventoryPaths; 918 919 if (!isValidUnexpandedLocationCode(i_unexpandedLocationCode)) 920 { 921 phosphor::logging::elog<types::DbusInvalidArgument>( 922 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 923 types::InvalidArgument::ARGUMENT_VALUE( 924 i_unexpandedLocationCode.c_str())); 925 } 926 927 const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 928 if (!l_sysCfgJsonObj.contains("frus")) 929 { 930 logging::logMessage("Missing frus tag in system config JSON"); 931 } 932 933 const nlohmann::json& l_listOfFrus = 934 l_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>(); 935 936 for (const auto& l_frus : l_listOfFrus.items()) 937 { 938 for (const auto& l_aFru : l_frus.value()) 939 { 940 if (l_aFru["extraInterfaces"].contains( 941 constants::locationCodeInf) && 942 l_aFru["extraInterfaces"][constants::locationCodeInf].value( 943 "LocationCode", "") == i_unexpandedLocationCode) 944 { 945 l_inventoryPaths.push_back( 946 l_aFru.at("inventoryPath") 947 .get_ref<const nlohmann::json::string_t&>()); 948 } 949 } 950 } 951 952 if (l_inventoryPaths.empty()) 953 { 954 phosphor::logging::elog<types::DbusInvalidArgument>( 955 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 956 types::InvalidArgument::ARGUMENT_VALUE( 957 i_unexpandedLocationCode.c_str())); 958 } 959 960 return l_inventoryPaths; 961 } 962 963 std::string Manager::getHwPath( 964 const sdbusplus::message::object_path& i_dbusObjPath) 965 { 966 // Dummy code to supress unused variable warning. To be removed. 967 logging::logMessage(std::string(i_dbusObjPath)); 968 969 return std::string{}; 970 } 971 972 std::tuple<std::string, uint16_t> Manager::getUnexpandedLocationCode( 973 const std::string& i_expandedLocationCode) 974 { 975 /** 976 * Location code should always start with U and fulfil minimum length 977 * criteria. 978 */ 979 if (i_expandedLocationCode[0] != 'U' || 980 i_expandedLocationCode.length() < 981 constants::EXP_LOCATION_CODE_MIN_LENGTH) 982 { 983 phosphor::logging::elog<types::DbusInvalidArgument>( 984 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 985 types::InvalidArgument::ARGUMENT_VALUE( 986 i_expandedLocationCode.c_str())); 987 } 988 989 std::string l_fcKwd; 990 991 auto l_fcKwdValue = dbusUtility::readDbusProperty( 992 "xyz.openbmc_project.Inventory.Manager", 993 "/xyz/openbmc_project/inventory/system/chassis/motherboard", 994 "com.ibm.ipzvpd.VCEN", "FC"); 995 996 if (auto l_kwdValue = std::get_if<types::BinaryVector>(&l_fcKwdValue)) 997 { 998 l_fcKwd.assign(l_kwdValue->begin(), l_kwdValue->end()); 999 } 1000 1001 // Get the first part of expanded location code to check for FC or TM. 1002 std::string l_firstKwd = i_expandedLocationCode.substr(1, 4); 1003 1004 std::string l_unexpandedLocationCode{}; 1005 uint16_t l_nodeNummber = constants::INVALID_NODE_NUMBER; 1006 1007 // Check if this value matches the value of FC keyword. 1008 if (l_fcKwd.substr(0, 4) == l_firstKwd) 1009 { 1010 /** 1011 * Period(.) should be there in expanded location code to seggregate 1012 * FC, node number and SE values. 1013 */ 1014 size_t l_nodeStartPos = i_expandedLocationCode.find('.'); 1015 if (l_nodeStartPos == std::string::npos) 1016 { 1017 phosphor::logging::elog<types::DbusInvalidArgument>( 1018 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 1019 types::InvalidArgument::ARGUMENT_VALUE( 1020 i_expandedLocationCode.c_str())); 1021 } 1022 1023 size_t l_nodeEndPos = 1024 i_expandedLocationCode.find('.', l_nodeStartPos + 1); 1025 if (l_nodeEndPos == std::string::npos) 1026 { 1027 phosphor::logging::elog<types::DbusInvalidArgument>( 1028 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 1029 types::InvalidArgument::ARGUMENT_VALUE( 1030 i_expandedLocationCode.c_str())); 1031 } 1032 1033 // Skip 3 bytes for '.ND' 1034 l_nodeNummber = std::stoi(i_expandedLocationCode.substr( 1035 l_nodeStartPos + 3, (l_nodeEndPos - l_nodeStartPos - 3))); 1036 1037 /** 1038 * Confirm if there are other details apart FC, node number and SE 1039 * in location code 1040 */ 1041 if (i_expandedLocationCode.length() > 1042 constants::EXP_LOCATION_CODE_MIN_LENGTH) 1043 { 1044 l_unexpandedLocationCode = 1045 i_expandedLocationCode[0] + std::string("fcs") + 1046 i_expandedLocationCode.substr( 1047 l_nodeEndPos + 1 + constants::SE_KWD_LENGTH, 1048 std::string::npos); 1049 } 1050 else 1051 { 1052 l_unexpandedLocationCode = "Ufcs"; 1053 } 1054 } 1055 else 1056 { 1057 std::string l_tmKwd; 1058 // Read TM keyword value. 1059 auto l_tmKwdValue = dbusUtility::readDbusProperty( 1060 "xyz.openbmc_project.Inventory.Manager", 1061 "/xyz/openbmc_project/inventory/system/chassis/motherboard", 1062 "com.ibm.ipzvpd.VSYS", "TM"); 1063 1064 if (auto l_kwdValue = std::get_if<types::BinaryVector>(&l_tmKwdValue)) 1065 { 1066 l_tmKwd.assign(l_kwdValue->begin(), l_kwdValue->end()); 1067 } 1068 1069 // Check if the substr matches to TM keyword value. 1070 if (l_tmKwd.substr(0, 4) == l_firstKwd) 1071 { 1072 /** 1073 * System location code will not have node number and any other 1074 * details. 1075 */ 1076 l_unexpandedLocationCode = "Umts"; 1077 } 1078 // The given location code is neither "fcs" or "mts". 1079 else 1080 { 1081 phosphor::logging::elog<types::DbusInvalidArgument>( 1082 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"), 1083 types::InvalidArgument::ARGUMENT_VALUE( 1084 i_expandedLocationCode.c_str())); 1085 } 1086 } 1087 1088 return std::make_tuple(l_unexpandedLocationCode, l_nodeNummber); 1089 } 1090 1091 types::ListOfPaths Manager::getFrusByExpandedLocationCode( 1092 const std::string& i_expandedLocationCode) 1093 { 1094 std::tuple<std::string, uint16_t> l_locationAndNodePair = 1095 getUnexpandedLocationCode(i_expandedLocationCode); 1096 1097 return getFrusByUnexpandedLocationCode(std::get<0>(l_locationAndNodePair), 1098 std::get<1>(l_locationAndNodePair)); 1099 } 1100 1101 void Manager::registerHostStateChangeCallback() 1102 { 1103 static std::shared_ptr<sdbusplus::bus::match_t> l_hostState = 1104 std::make_shared<sdbusplus::bus::match_t>( 1105 *m_asioConnection, 1106 sdbusplus::bus::match::rules::propertiesChanged( 1107 constants::hostObjectPath, constants::hostInterface), 1108 [this](sdbusplus::message_t& i_msg) { 1109 hostStateChangeCallBack(i_msg); 1110 }); 1111 } 1112 1113 void Manager::hostStateChangeCallBack(sdbusplus::message_t& i_msg) 1114 { 1115 try 1116 { 1117 if (i_msg.is_method_error()) 1118 { 1119 throw std::runtime_error( 1120 "Error reading callback message for host state"); 1121 } 1122 1123 std::string l_objectPath; 1124 types::PropertyMap l_propMap; 1125 i_msg.read(l_objectPath, l_propMap); 1126 1127 const auto l_itr = l_propMap.find("CurrentHostState"); 1128 1129 if (l_itr == l_propMap.end()) 1130 { 1131 throw std::runtime_error( 1132 "CurrentHostState field is missing in callback message"); 1133 } 1134 1135 if (auto l_hostState = std::get_if<std::string>(&(l_itr->second))) 1136 { 1137 // implies system is moving from standby to power on state 1138 if (*l_hostState == "xyz.openbmc_project.State.Host.HostState." 1139 "TransitioningToRunning") 1140 { 1141 // TODO: check for all the essential FRUs in the system. 1142 1143 // Perform recollection. 1144 performVpdRecollection(); 1145 return; 1146 } 1147 } 1148 else 1149 { 1150 throw std::runtime_error( 1151 "Invalid type recieved in variant for host state."); 1152 } 1153 } 1154 catch (const std::exception& l_ex) 1155 { 1156 // TODO: Log PEL. 1157 logging::logMessage(l_ex.what()); 1158 } 1159 } 1160 1161 void Manager::performVpdRecollection() 1162 { 1163 try 1164 { 1165 if (m_worker.get() != nullptr) 1166 { 1167 nlohmann::json l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); 1168 1169 // Check if system config JSON is present 1170 if (l_sysCfgJsonObj.empty()) 1171 { 1172 throw std::runtime_error( 1173 "System config json object is empty, can't process recollection."); 1174 } 1175 1176 const auto& l_frusReplaceableAtStandby = 1177 jsonUtility::getListOfFrusReplaceableAtStandby(l_sysCfgJsonObj); 1178 1179 for (const auto& l_fruInventoryPath : l_frusReplaceableAtStandby) 1180 { 1181 // ToDo: Add some logic/trace to know the flow to 1182 // collectSingleFruVpd has been directed via 1183 // performVpdRecollection. 1184 collectSingleFruVpd(l_fruInventoryPath); 1185 } 1186 return; 1187 } 1188 1189 throw std::runtime_error( 1190 "Worker object not found can't process recollection"); 1191 } 1192 catch (const std::exception& l_ex) 1193 { 1194 // TODO Log PEL 1195 logging::logMessage( 1196 "VPD recollection failed with error: " + std::string(l_ex.what())); 1197 } 1198 } 1199 } // namespace vpd 1200