#include "config.h" #include "ibm_handler.hpp" #include "parser.hpp" #include #include #include #include namespace vpd { IbmHandler::IbmHandler( std::shared_ptr& o_worker, std::shared_ptr& o_backupAndRestoreObj, const std::shared_ptr& i_iFace, const std::shared_ptr& i_ioCon, const std::shared_ptr& i_asioConnection) : m_worker(o_worker), m_backupAndRestoreObj(o_backupAndRestoreObj), m_interface(i_iFace), m_ioContext(i_ioCon), m_asioConnection(i_asioConnection) { if (dbusUtility::isChassisPowerOn()) { // At power on, less number of FRU(s) needs collection. we can scale // down the threads to reduce CPU utilization. m_worker = std::make_shared(INVENTORY_JSON_DEFAULT, constants::VALUE_1); } else { // Initialize with default configuration m_worker = std::make_shared(INVENTORY_JSON_DEFAULT); } // Set up minimal things that is needed before bus name is claimed. performInitialSetup(); if (!m_sysCfgJsonObj.empty() && jsonUtility::isBackupAndRestoreRequired(m_sysCfgJsonObj)) { try { m_backupAndRestoreObj = std::make_shared(m_sysCfgJsonObj); } catch (const std::exception& l_ex) { logging::logMessage("Back up and restore instantiation failed. {" + std::string(l_ex.what()) + "}"); EventLogger::createSyncPel( EventLogger::getErrorType(l_ex), types::SeverityType::Warning, __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), std::nullopt, std::nullopt, std::nullopt, std::nullopt); } } // callback to detect host state change. registerHostStateChangeCallback(); // set callback to detect any asset tag change registerAssetTagChangeCallback(); // set async timer to detect if system VPD is published on D-Bus. SetTimerToDetectSVPDOnDbus(); // set async timer to detect if VPD collection is done. SetTimerToDetectVpdCollectionStatus(); // Instantiate GpioMonitor class m_gpioMonitor = std::make_shared(m_sysCfgJsonObj, m_worker, m_ioContext); } void IbmHandler::registerAssetTagChangeCallback() { static std::shared_ptr l_assetMatch = std::make_shared( *m_asioConnection, sdbusplus::bus::match::rules::propertiesChanged( constants::systemInvPath, constants::assetTagInf), [this](sdbusplus::message_t& l_msg) { processAssetTagChangeCallback(l_msg); }); } void IbmHandler::processAssetTagChangeCallback(sdbusplus::message_t& i_msg) { try { if (i_msg.is_method_error()) { throw std::runtime_error( "Error reading callback msg for asset tag."); } std::string l_objectPath; types::PropertyMap l_propMap; i_msg.read(l_objectPath, l_propMap); const auto& l_itrToAssetTag = l_propMap.find("AssetTag"); if (l_itrToAssetTag != l_propMap.end()) { if (auto l_assetTag = std::get_if(&(l_itrToAssetTag->second))) { // Call Notify to persist the AssetTag types::ObjectMap l_objectMap = { {sdbusplus::message::object_path(constants::systemInvPath), {{constants::assetTagInf, {{"AssetTag", *l_assetTag}}}}}}; // Notify PIM if (!dbusUtility::callPIM(move(l_objectMap))) { throw std::runtime_error( "Call to PIM failed for asset tag update."); } } } else { throw std::runtime_error( "Could not find asset tag in callback message."); } } catch (const std::exception& l_ex) { // TODO: Log PEL with below description. logging::logMessage("Asset tag callback update failed with error: " + std::string(l_ex.what())); } } void IbmHandler::SetTimerToDetectSVPDOnDbus() { try { static boost::asio::steady_timer timer(*m_ioContext); // timer for 2 seconds auto asyncCancelled = timer.expires_after(std::chrono::seconds(2)); (asyncCancelled == 0) ? logging::logMessage("Timer started") : logging::logMessage("Timer re-started"); timer.async_wait([this](const boost::system::error_code& ec) { if (ec == boost::asio::error::operation_aborted) { throw std::runtime_error( std::string(__FUNCTION__) + ": Timer to detect system VPD collection status was aborted."); } if (ec) { throw std::runtime_error( std::string(__FUNCTION__) + ": Timer to detect System VPD collection failed"); } if (m_worker->isSystemVPDOnDBus()) { // cancel the timer timer.cancel(); // Triggering FRU VPD collection. Setting status to "In // Progress". m_interface->set_property("CollectionStatus", std::string("InProgress")); m_worker->collectFrusFromJson(); } }); } catch (const std::exception& l_ex) { EventLogger::createAsyncPel( EventLogger::getErrorType(l_ex), types::SeverityType::Critical, __FILE__, __FUNCTION__, 0, std::string("Collection for FRUs failed with reason:") + EventLogger::getErrorMsg(l_ex), std::nullopt, std::nullopt, std::nullopt, std::nullopt); } } void IbmHandler::SetTimerToDetectVpdCollectionStatus() { // Keeping max retry for 2 minutes. TODO: Make it configurable based on // system type. static constexpr auto MAX_RETRY = 12; static boost::asio::steady_timer l_timer(*m_ioContext); static uint8_t l_timerRetry = 0; auto l_asyncCancelled = l_timer.expires_after(std::chrono::seconds(10)); (l_asyncCancelled == 0) ? logging::logMessage("Collection Timer started") : logging::logMessage("Collection Timer re-started"); l_timer.async_wait([this](const boost::system::error_code& ec) { if (ec == boost::asio::error::operation_aborted) { throw std::runtime_error( "Timer to detect thread collection status was aborted"); } if (ec) { throw std::runtime_error( "Timer to detect thread collection failed"); } if (m_worker->isAllFruCollectionDone()) { // cancel the timer l_timer.cancel(); processFailedEeproms(); // update VPD for powerVS system. ConfigurePowerVsSystem(); std::cout << "m_worker->isSystemVPDOnDBus() completed" << std::endl; m_interface->set_property("CollectionStatus", std::string("Completed")); if (m_backupAndRestoreObj) { m_backupAndRestoreObj->backupAndRestore(); } } else { auto l_threadCount = m_worker->getActiveThreadCount(); if (l_timerRetry == MAX_RETRY) { l_timer.cancel(); logging::logMessage("Taking too long. Active thread = " + std::to_string(l_threadCount)); } else { l_timerRetry++; logging::logMessage("Collection is in progress for [" + std::to_string(l_threadCount) + "] FRUs."); SetTimerToDetectVpdCollectionStatus(); } } }); } void IbmHandler::checkAndUpdatePowerVsVpd( const nlohmann::json& i_powerVsJsonObj, std::vector& o_failedPathList) { for (const auto& [l_fruPath, l_recJson] : i_powerVsJsonObj.items()) { nlohmann::json l_sysCfgJsonObj{}; if (m_worker.get() != nullptr) { l_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); } // The utility method will handle emty JSON case. No explicit // handling required here. auto l_inventoryPath = jsonUtility::getInventoryObjPathFromJson( l_sysCfgJsonObj, l_fruPath); // Mark it as failed if inventory path not found in JSON. if (l_inventoryPath.empty()) { o_failedPathList.push_back(l_fruPath); continue; } // check if the FRU is present if (!dbusUtility::isInventoryPresent(l_inventoryPath)) { logging::logMessage( "Inventory not present, skip updating part number. Path: " + l_inventoryPath); continue; } // check if the FRU needs CCIN check before updating PN. if (l_recJson.contains("CCIN")) { const auto& l_ccinFromDbus = vpdSpecificUtility::getCcinFromDbus(l_inventoryPath); // Not an ideal situation as CCIN can't be empty. if (l_ccinFromDbus.empty()) { o_failedPathList.push_back(l_fruPath); continue; } std::vector l_ccinListFromJson = l_recJson["CCIN"]; if (find(l_ccinListFromJson.begin(), l_ccinListFromJson.end(), l_ccinFromDbus) == l_ccinListFromJson.end()) { // Don't update PN in this case. continue; } } for (const auto& [l_recordName, l_kwdJson] : l_recJson.items()) { // Record name can't be CCIN, skip processing as it is there for PN // update based on CCIN check. if (l_recordName == constants::kwdCCIN) { continue; } for (const auto& [l_kwdName, l_kwdValue] : l_kwdJson.items()) { // Is value of type array. if (!l_kwdValue.is_array()) { o_failedPathList.push_back(l_fruPath); continue; } // Get current FRU Part number. auto l_retVal = dbusUtility::readDbusProperty( constants::pimServiceName, l_inventoryPath, constants::viniInf, constants::kwdFN); auto l_ptrToFn = std::get_if(&l_retVal); if (!l_ptrToFn) { o_failedPathList.push_back(l_fruPath); continue; } types::BinaryVector l_binaryKwdValue = l_kwdValue.get(); if (l_binaryKwdValue == (*l_ptrToFn)) { continue; } // Update part number only if required. std::shared_ptr l_parserObj = std::make_shared(l_fruPath, l_sysCfgJsonObj); if (l_parserObj->updateVpdKeyword(std::make_tuple( l_recordName, l_kwdName, l_binaryKwdValue)) == constants::FAILURE) { o_failedPathList.push_back(l_fruPath); continue; } // update the Asset interface Spare part number explicitly. if (!dbusUtility::callPIM(types::ObjectMap{ {l_inventoryPath, {{constants::assetInf, {{"SparePartNumber", std::string(l_binaryKwdValue.begin(), l_binaryKwdValue.end())}}}}}})) { logging::logMessage( "Updating Spare Part Number under Asset interface failed for path [" + l_inventoryPath + "]"); } // Just needed for logging. std::string l_initialPartNum((*l_ptrToFn).begin(), (*l_ptrToFn).end()); std::string l_finalPartNum(l_binaryKwdValue.begin(), l_binaryKwdValue.end()); logging::logMessage( "FRU Part number updated for path [" + l_inventoryPath + "]" + "From [" + l_initialPartNum + "]" + " to [" + l_finalPartNum + "]"); } } } } void IbmHandler::ConfigurePowerVsSystem() { std::vector l_failedPathList; try { types::BinaryVector l_imValue = dbusUtility::getImFromDbus(); if (l_imValue.empty()) { throw DbusException("Invalid IM value read from Dbus"); } if (!vpdSpecificUtility::isPowerVsConfiguration(l_imValue)) { // TODO: Should booting be blocked in case of some // misconfigurations? return; } const nlohmann::json& l_powerVsJsonObj = jsonUtility::getPowerVsJson(l_imValue); if (l_powerVsJsonObj.empty()) { throw std::runtime_error("PowerVS Json not found"); } checkAndUpdatePowerVsVpd(l_powerVsJsonObj, l_failedPathList); if (!l_failedPathList.empty()) { throw std::runtime_error( "Part number update failed for following paths: "); } } catch (const std::exception& l_ex) { // TODO log appropriate PEL } } void IbmHandler::processFailedEeproms() { if (m_worker.get() != nullptr) { // TODO: // - iterate through list of EEPROMs for which thread creation has // failed // - For each failed EEPROM, trigger VPD collection m_worker->getFailedEepromPaths().clear(); } } void IbmHandler::registerHostStateChangeCallback() { static std::shared_ptr l_hostState = std::make_shared( *m_asioConnection, sdbusplus::bus::match::rules::propertiesChanged( constants::hostObjectPath, constants::hostInterface), [this](sdbusplus::message_t& i_msg) { hostStateChangeCallBack(i_msg); }); } void IbmHandler::hostStateChangeCallBack(sdbusplus::message_t& i_msg) { try { if (i_msg.is_method_error()) { throw std::runtime_error( "Error reading callback message for host state"); } std::string l_objectPath; types::PropertyMap l_propMap; i_msg.read(l_objectPath, l_propMap); const auto l_itr = l_propMap.find("CurrentHostState"); if (l_itr == l_propMap.end()) { throw std::runtime_error( "CurrentHostState field is missing in callback message"); } if (auto l_hostState = std::get_if(&(l_itr->second))) { // implies system is moving from standby to power on state if (*l_hostState == "xyz.openbmc_project.State.Host.HostState." "TransitioningToRunning") { // TODO: check for all the essential FRUs in the system. if (m_worker.get() != nullptr) { // Perform recollection. m_worker->performVpdRecollection(); } else { logging::logMessage( "Failed to get worker object, Abort re-collection"); } } } else { throw std::runtime_error( "Invalid type recieved in variant for host state."); } } catch (const std::exception& l_ex) { // TODO: Log PEL. logging::logMessage(l_ex.what()); } } void IbmHandler::primeSystemBlueprint() { if (m_sysCfgJsonObj.empty()) { return; } const nlohmann::json& l_listOfFrus = m_sysCfgJsonObj["frus"].get_ref(); for (const auto& l_itemFRUS : l_listOfFrus.items()) { const std::string& l_vpdFilePath = l_itemFRUS.key(); if (l_vpdFilePath == SYSTEM_VPD_FILE_PATH) { continue; } // Prime the inventry for FRUs which // are not present/processing had some error. if (m_worker.get() != nullptr && !m_worker->primeInventory(l_vpdFilePath)) { logging::logMessage( "Priming of inventory failed for FRU " + l_vpdFilePath); } } } void IbmHandler::enableMuxChips() { if (m_sysCfgJsonObj.empty()) { // config JSON should not be empty at this point of execution. throw std::runtime_error("Config JSON is empty. Can't enable muxes"); return; } if (!m_sysCfgJsonObj.contains("muxes")) { logging::logMessage("No mux defined for the system in config JSON"); return; } // iterate over each MUX detail and enable them. for (const auto& item : m_sysCfgJsonObj["muxes"]) { if (item.contains("holdidlepath")) { std::string cmd = "echo 0 > "; cmd += item["holdidlepath"]; logging::logMessage("Enabling mux with command = " + cmd); commonUtility::executeCmd(cmd); continue; } logging::logMessage( "Mux Entry does not have hold idle path. Can't enable the mux"); } } void IbmHandler::performInitialSetup() { try { if (!dbusUtility::isChassisPowerOn()) { logging::logMessage("Chassis is in Off state."); if (m_worker.get() != nullptr) { m_worker->setDeviceTreeAndJson(); // Get the system config JSON object. m_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); } else { throw std::runtime_error( "Worker object not found. Can't set up device tree and Json."); } primeSystemBlueprint(); } else { // get the JSON from worker. m_sysCfgJsonObj = m_worker->getSysCfgJsonObj(); } // Enable all mux which are used for connecting to the i2c on the // pcie slots for pcie cards. These are not enabled by kernel due to // an issue seen with Castello cards, where the i2c line hangs on a // probe. enableMuxChips(); // Nothing needs to be done. Service restarted or BMC re-booted for // some reason at system power on. return; } catch (const std::exception& l_ex) { // Any issue in system's inital set up is handled in this catch. Error // will not propogate to manager. EventLogger::createSyncPel( EventLogger::getErrorType(l_ex), types::SeverityType::Critical, __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), std::nullopt, std::nullopt, std::nullopt, std::nullopt); } } } // namespace vpd