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