#include "config.h" #include "manager.hpp" #include "common_utility.hpp" #include "editor_impl.hpp" #include "ibm_vpd_utils.hpp" #include "ipz_parser.hpp" #include "parser_factory.hpp" #include "reader_impl.hpp" #include "vpd_exceptions.hpp" #include #include #include #include using namespace openpower::vpd::constants; using namespace openpower::vpd::inventory; using namespace openpower::vpd::manager::editor; using namespace openpower::vpd::manager::reader; using namespace std; using namespace openpower::vpd::parser; using namespace openpower::vpd::parser::factory; using namespace openpower::vpd::ipz::parser; using namespace openpower::vpd::exceptions; using namespace phosphor::logging; namespace openpower { namespace vpd { namespace manager { Manager::Manager(std::shared_ptr& ioCon, std::shared_ptr& iFace, std::shared_ptr& conn) : ioContext(ioCon), interface(iFace), conn(conn) { interface->register_method( "WriteKeyword", [this](const sdbusplus::message::object_path& path, const std::string& recordName, const std::string& keyword, const Binary& value) { this->writeKeyword(path, recordName, keyword, value); }); interface->register_method( "GetFRUsByUnexpandedLocationCode", [this](const std::string& locationCode, const uint16_t nodeNumber) -> inventory::ListOfPaths { return this->getFRUsByUnexpandedLocationCode(locationCode, nodeNumber); }); interface->register_method( "GetFRUsByExpandedLocationCode", [this](const std::string& locationCode) -> inventory::ListOfPaths { return this->getFRUsByExpandedLocationCode(locationCode); }); interface->register_method( "GetExpandedLocationCode", [this](const std::string& locationCode, const uint16_t nodeNumber) -> std::string { return this->getExpandedLocationCode(locationCode, nodeNumber); }); interface->register_method("PerformVPDRecollection", [this]() { this->performVPDRecollection(); }); interface->register_method( "deleteFRUVPD", [this](const sdbusplus::message::object_path& path) { this->deleteFRUVPD(path); }); interface->register_method( "CollectFRUVPD", [this](const sdbusplus::message::object_path& path) { this->collectFRUVPD(path); }); sd_bus_default(&sdBus); initManager(); } void Manager::initManager() { try { processJSON(); restoreSystemVpd(); listenHostState(); listenAssetTag(); // Create an instance of the BIOS handler biosHandler = std::make_shared(conn, *this); // instantiate gpioMonitor class gpioMon = std::make_shared(jsonFile, ioContext); } catch (const std::exception& e) { std::cerr << e.what() << "\n"; } } /** * @brief An api to get list of blank system VPD properties. * @param[in] vpdMap - IPZ vpd map. * @param[in] objectPath - Object path for the FRU. * @param[out] blankPropertyList - Properties which are blank in System VPD and * needs to be updated as standby. */ static void getListOfBlankSystemVpd(Parsed& vpdMap, const string& objectPath, std::vector& blankPropertyList) { for (const auto& systemRecKwdPair : svpdKwdMap) { auto it = vpdMap.find(systemRecKwdPair.first); // check if record is found in map we got by parser if (it != vpdMap.end()) { const auto& kwdListForRecord = systemRecKwdPair.second; for (const auto& keywordInfo : kwdListForRecord) { const auto& keyword = get<0>(keywordInfo); DbusPropertyMap& kwdValMap = it->second; auto iterator = kwdValMap.find(keyword); if (iterator != kwdValMap.end()) { string& kwdValue = iterator->second; // check bus data const string& recordName = systemRecKwdPair.first; const string& busValue = readBusProperty( objectPath, ipzVpdInf + recordName, keyword); const auto& defaultValue = get<1>(keywordInfo); if (Binary(busValue.begin(), busValue.end()) != defaultValue) { if (Binary(kwdValue.begin(), kwdValue.end()) == defaultValue) { // implies data is blank on EEPROM but not on cache. // So EEPROM vpd update is required. Binary busData(busValue.begin(), busValue.end()); blankPropertyList.push_back(std::make_tuple( objectPath, recordName, keyword, busData)); } } } } } } } void Manager::restoreSystemVpd() { std::cout << "Attempting system VPD restore" << std::endl; ParserInterface* parser = nullptr; try { auto vpdVector = getVpdDataInVector(jsonFile, systemVpdFilePath); uint32_t vpdStartOffset = 0; const auto& inventoryPath = jsonFile["frus"][systemVpdFilePath][0]["inventoryPath"] .get_ref(); parser = ParserFactory::getParser(vpdVector, (pimPath + inventoryPath), systemVpdFilePath, vpdStartOffset); auto parseResult = parser->parse(); if (auto pVal = std::get_if(&parseResult)) { // map to hold all the keywords whose value is blank and // needs to be updated at standby. std::vector blankSystemVpdProperties{}; getListOfBlankSystemVpd(pVal->getVpdMap(), SYSTEM_OBJECT, blankSystemVpdProperties); // if system VPD restore is required, update the // EEPROM for (const auto& item : blankSystemVpdProperties) { std::cout << "Restoring keyword: " << std::get<2>(item) << std::endl; writeKeyword(std::get<0>(item), std::get<1>(item), std::get<2>(item), std::get<3>(item)); } } else { std::cerr << "Not a valid format to restore system VPD" << std::endl; } } catch (const std::exception& e) { std::cerr << "Failed to restore system VPD due to exception: " << e.what() << std::endl; } // release the parser object ParserFactory::freeParser(parser); } void Manager::listenHostState() { static std::shared_ptr hostState = std::make_shared( *conn, sdbusplus::bus::match::rules::propertiesChanged( "/xyz/openbmc_project/state/host0", "xyz.openbmc_project.State.Host"), [this](sdbusplus::message_t& msg) { hostStateCallBack(msg); }); } void Manager::checkEssentialFrus() { for (const auto& invPath : essentialFrus) { const auto res = readBusProperty(invPath, invItemIntf, "Present"); // implies the essential FRU is missing. Log PEL. if (res == "false") { auto rc = sd_bus_call_method_async( sdBus, NULL, loggerService, loggerObjectPath, loggerCreateInterface, "Create", NULL, NULL, "ssa{ss}", errIntfForEssentialFru, "xyz.openbmc_project.Logging.Entry.Level.Warning", 2, "DESCRIPTION", "Essential fru missing from the system.", "CALLOUT_INVENTORY_PATH", (pimPath + invPath).c_str()); if (rc < 0) { log("Error calling sd_bus_call_method_async", entry("RC=%d", rc), entry("MSG=%s", strerror(-rc))); } } } } void Manager::hostStateCallBack(sdbusplus::message_t& msg) { if (msg.is_method_error()) { std::cerr << "Error in reading signal " << std::endl; } Path object; PropertyMap propMap; msg.read(object, propMap); const auto itr = propMap.find("CurrentHostState"); if (itr != propMap.end()) { if (auto hostState = std::get_if(&(itr->second))) { // implies system is moving from standby to power on state if (*hostState == "xyz.openbmc_project.State.Host.HostState." "TransitioningToRunning") { // detect if essential frus are present in the system. checkEssentialFrus(); // check and perform recollection for FRUs replaceable at // standby. performVPDRecollection(); return; } } } } void Manager::listenAssetTag() { static std::shared_ptr assetMatcher = std::make_shared( *conn, sdbusplus::bus::match::rules::propertiesChanged( "/xyz/openbmc_project/inventory/system", "xyz.openbmc_project.Inventory.Decorator.AssetTag"), [this](sdbusplus::message_t& msg) { assetTagCallback(msg); }); } void Manager::assetTagCallback(sdbusplus::message_t& msg) { if (msg.is_method_error()) { std::cerr << "Error in reading signal " << std::endl; } Path object; PropertyMap propMap; msg.read(object, propMap); const auto itr = propMap.find("AssetTag"); if (itr != propMap.end()) { if (auto assetTag = std::get_if(&(itr->second))) { // Call Notify to persist the AssetTag inventory::ObjectMap objectMap = { {std::string{"/system"}, {{"xyz.openbmc_project.Inventory.Decorator.AssetTag", {{"AssetTag", *assetTag}}}}}}; common::utility::callPIM(std::move(objectMap)); } else { std::cerr << "Failed to read asset tag" << std::endl; } } } void Manager::processJSON() { std::ifstream json(INVENTORY_JSON_SYM_LINK, std::ios::binary); if (!json) { throw std::runtime_error("json file not found"); } jsonFile = nlohmann::json::parse(json); if (jsonFile.find("frus") == jsonFile.end()) { throw std::runtime_error("frus group not found in json"); } const nlohmann::json& groupFRUS = jsonFile["frus"].get_ref(); for (const auto& itemFRUS : groupFRUS.items()) { const std::vector& groupEEPROM = itemFRUS.value().get_ref(); for (const auto& itemEEPROM : groupEEPROM) { bool isMotherboard = false; std::string redundantPath; if (itemEEPROM["extraInterfaces"].find( "xyz.openbmc_project.Inventory.Item.Board.Motherboard") != itemEEPROM["extraInterfaces"].end()) { isMotherboard = true; } if (itemEEPROM.find("redundantEeprom") != itemEEPROM.end()) { redundantPath = itemEEPROM["redundantEeprom"] .get_ref(); } frus.emplace( itemEEPROM["inventoryPath"] .get_ref(), std::make_tuple(itemFRUS.key(), redundantPath, isMotherboard)); if (itemEEPROM["extraInterfaces"].find(IBM_LOCATION_CODE_INF) != itemEEPROM["extraInterfaces"].end()) { fruLocationCode.emplace( itemEEPROM["extraInterfaces"][IBM_LOCATION_CODE_INF] ["LocationCode"] .get_ref(), itemEEPROM["inventoryPath"] .get_ref()); } if (itemEEPROM.value("replaceableAtStandby", false)) { replaceableFrus.emplace_back(itemFRUS.key()); } if (itemEEPROM.value("essentialFru", false)) { essentialFrus.emplace_back(itemEEPROM["inventoryPath"]); } } } } void Manager::updateSystemVPDBackUpFRU(const std::string& recordName, const std::string& keyword, const Binary& value) { const std::string& systemVpdBackupPath = jsonFile["frus"][systemVpdFilePath].at(0).value("systemVpdBackupPath", ""); if (!systemVpdBackupPath.empty() && jsonFile["frus"][systemVpdBackupPath].at(0).contains("inventoryPath")) { std::string systemVpdBackupInvPath = jsonFile["frus"][systemVpdBackupPath][0]["inventoryPath"] .get_ref(); const auto& itr = svpdKwdMap.find(recordName); if (itr != svpdKwdMap.end()) { auto systemKwdInfoList = itr->second; const auto& itrToKwd = find_if(systemKwdInfoList.begin(), systemKwdInfoList.end(), [&keyword](const auto& kwdInfo) { return (keyword == std::get<0>(kwdInfo)); }); if (itrToKwd != systemKwdInfoList.end()) { EditorImpl edit(systemVpdBackupPath, jsonFile, std::get<4>(*itrToKwd), std::get<5>(*itrToKwd), systemVpdBackupInvPath); // Setup offset, if any uint32_t offset = 0; if (jsonFile["frus"][systemVpdBackupPath].at(0).contains( "offset")) { offset = jsonFile["frus"][systemVpdBackupPath].at(0).contains( "offset"); } edit.updateKeyword(value, offset, true); } } } else { if (systemVpdBackupPath.empty()) { throw std::runtime_error( "Invalid entry for systemVpdBackupPath in JSON"); } else { throw std::runtime_error( "Inventory path missing for systemVpdBackupPath"); } } } void Manager::writeKeyword(const sdbusplus::message::object_path& path, const std::string& recordName, const std::string& keyword, const Binary& value) { try { std::string objPath{path}; // Strip any inventory prefix in path if (objPath.find(INVENTORY_PATH) == 0) { objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1); } if (frus.find(objPath) == frus.end()) { throw std::runtime_error("Inventory path not found"); } inventory::Path vpdFilePath = std::get<0>(frus.find(objPath)->second); // instantiate editor class to update the data EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword, objPath); uint32_t offset = 0; // Setup offset, if any for (const auto& item : jsonFile["frus"][vpdFilePath]) { if (item.find("offset") != item.end()) { offset = item["offset"]; break; } } edit.updateKeyword(value, offset, true); // If system VPD is being updated and system VPD is marked for back up // on another FRU, update data on back up as well. if (objPath == sdbusplus::message::object_path{SYSTEM_OBJECT} && jsonFile["frus"][systemVpdFilePath].at(0).contains( "systemVpdBackupPath")) { updateSystemVPDBackUpFRU(recordName, keyword, value); } // If we have a redundant EEPROM to update, then update just the EEPROM, // not the cache since that is already done when we updated the primary if (!std::get<1>(frus.find(objPath)->second).empty()) { EditorImpl edit(std::get<1>(frus.find(objPath)->second), jsonFile, recordName, keyword, objPath); edit.updateKeyword(value, offset, false); } // if it is a motehrboard FRU need to check for location expansion if (std::get<2>(frus.find(objPath)->second)) { if (recordName == "VCEN" && (keyword == "FC" || keyword == "SE")) { edit.expandLocationCode("fcs"); } else if (recordName == "VSYS" && (keyword == "TM" || keyword == "SE")) { edit.expandLocationCode("mts"); } } return; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } ListOfPaths Manager::getFRUsByUnexpandedLocationCode( const LocationCode& locationCode, const NodeNumber nodeNumber) { ReaderImpl read; return read.getFrusAtLocation(locationCode, nodeNumber, fruLocationCode); } ListOfPaths Manager::getFRUsByExpandedLocationCode(const LocationCode& locationCode) { ReaderImpl read; return read.getFRUsByExpandedLocationCode(locationCode, fruLocationCode); } LocationCode Manager::getExpandedLocationCode(const LocationCode& locationCode, const NodeNumber nodeNumber) { ReaderImpl read; return read.getExpandedLocationCode(locationCode, nodeNumber, fruLocationCode); } void Manager::performVPDRecollection() { // get list of FRUs replaceable at standby for (const auto& item : replaceableFrus) { const vector& groupEEPROM = jsonFile["frus"][item]; const nlohmann::json& singleFru = groupEEPROM[0]; const string& inventoryPath = singleFru["inventoryPath"] .get_ref(); bool prePostActionRequired = false; if ((jsonFile["frus"][item].at(0)).find("preAction") != jsonFile["frus"][item].at(0).end()) { try { if (!executePreAction(jsonFile, item)) { // if the FRU has preAction defined then its execution // should pass to ensure bind/unbind of data. // preAction execution failed. should not call // bind/unbind. log( "Pre-Action execution failed for the FRU", entry("ERROR=%s", ("Inventory path: " + inventoryPath).c_str())); // As recollection failed delete FRU data. deleteFRUVPD(inventoryPath); continue; } } catch (const GpioException& e) { log(e.what()); PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", e.what()); createPEL(additionalData, PelSeverity::WARNING, errIntfForGpioError, sdBus); // As recollection failed delete FRU data. deleteFRUVPD(inventoryPath); continue; } prePostActionRequired = true; } // unbind, bind the driver to trigger parser. triggerVpdCollection(singleFru, inventoryPath); // this check is added to avoid file system expensive call in case not // required. if (prePostActionRequired) { // The sleep of 1sec is sliced up in 10 retries of 10 milliseconds // each. for (auto retryCounter = VALUE_0; retryCounter <= VALUE_10; retryCounter++) { // sleep for 10 millisecond if (usleep(VALUE_100000) != VALUE_0) { std::cout << "Sleep failed before accessing the file" << std::endl; } // Check if file showed up if (!filesystem::exists(item)) { // Do we need to retry? if (retryCounter < VALUE_10) { continue; } try { // If not, then take failure postAction executePostFailAction(jsonFile, item); // As recollection failed delete FRU data. deleteFRUVPD(inventoryPath); } catch (const GpioException& e) { PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", e.what()); createPEL(additionalData, PelSeverity::WARNING, errIntfForGpioError, sdBus); // As recollection failed delete FRU data. deleteFRUVPD(inventoryPath); } } else { // bind the LED driver string chipAddr = singleFru.value("pcaChipAddress", ""); cout << "performVPDRecollection: Executing driver binding for " "chip " "address - " << chipAddr << endl; executeCmd(createBindUnbindDriverCmnd( chipAddr, "i2c", "leds-pca955x", "/bind")); // File has been found, kill the retry loop. break; } } } } } void Manager::collectFRUVPD(const sdbusplus::message::object_path& path) { std::cout << "Manager called to collect vpd for fru: " << std::string{path} << std::endl; using InvalidArgument = sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; using Argument = xyz::openbmc_project::Common::InvalidArgument; std::string objPath{path}; // Strip any inventory prefix in path if (objPath.find(INVENTORY_PATH) == 0) { objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1); } // if path not found in Json. if (frus.find(objPath) == frus.end()) { elog(Argument::ARGUMENT_NAME("Object Path"), Argument::ARGUMENT_VALUE(objPath.c_str())); } inventory::Path vpdFilePath = std::get<0>(frus.find(objPath)->second); const std::vector& groupEEPROM = jsonFile["frus"][vpdFilePath].get_ref(); nlohmann::json singleFru{}; for (const auto& item : groupEEPROM) { if (item["inventoryPath"] == objPath) { // this is the inventory we are looking for singleFru = item; break; } } // check if the device qualifies for CM. if (singleFru.value("concurrentlyMaintainable", false)) { bool prePostActionRequired = false; if ((jsonFile["frus"][vpdFilePath].at(0)).find("preAction") != jsonFile["frus"][vpdFilePath].at(0).end()) { if (!executePreAction(jsonFile, vpdFilePath)) { // if the FRU has preAction defined then its execution should // pass to ensure bind/unbind of data. // preAction execution failed. should not call bind/unbind. log("Pre-Action execution failed for the FRU"); return; } prePostActionRequired = true; } // unbind, bind the driver to trigger parser. triggerVpdCollection(singleFru, objPath); // this check is added to avoid file system expensive call in case not // required. if (prePostActionRequired) { // Check if device showed up (test for file) if (!filesystem::exists(vpdFilePath)) { try { // If not, then take failure postAction executePostFailAction(jsonFile, vpdFilePath); } catch (const GpioException& e) { PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", e.what()); createPEL(additionalData, PelSeverity::WARNING, errIntfForGpioError, sdBus); } } else { // bind the LED driver string chipAddr = jsonFile["frus"][vpdFilePath].at(0).value( "pcaChipAddress", ""); cout << "Executing driver binding for chip address - " << chipAddr << endl; executeCmd(createBindUnbindDriverCmnd(chipAddr, "i2c", "leds-pca955x", "/bind")); } } return; } else { elog(Argument::ARGUMENT_NAME("Object Path"), Argument::ARGUMENT_VALUE(objPath.c_str())); } } void Manager::triggerVpdCollection(const nlohmann::json& singleFru, const std::string& path) { if ((singleFru.find("devAddress") == singleFru.end()) || (singleFru.find("driverType") == singleFru.end()) || (singleFru.find("busType") == singleFru.end())) { // The FRUs is marked for collection but missing mandatory // fields for collection. Log error and return. log( "Collection Failed as mandatory field missing in Json", entry("ERROR=%s", ("Recollection failed for " + (path)).c_str())); return; } string deviceAddress = singleFru["devAddress"]; const string& driverType = singleFru["driverType"]; const string& busType = singleFru["busType"]; // devTreeStatus flag is present in json as false to mention // that the EEPROM is not mentioned in device tree. If this flag // is absent consider the value to be true, i.e EEPROM is // mentioned in device tree if (!singleFru.value("devTreeStatus", true)) { auto pos = deviceAddress.find('-'); if (pos != string::npos) { string busNum = deviceAddress.substr(0, pos); deviceAddress = "0x" + deviceAddress.substr(pos + 1, string::npos); string deleteDevice = "echo" + deviceAddress + " > /sys/bus/" + busType + "/devices/" + busType + "-" + busNum + "/delete_device"; executeCmd(deleteDevice); string addDevice = "echo" + driverType + " " + deviceAddress + " > /sys/bus/" + busType + "/devices/" + busType + "-" + busNum + "/new_device"; executeCmd(addDevice); } else { const string& inventoryPath = singleFru["inventoryPath"] .get_ref(); log( "Wrong format of device address in Json", entry("ERROR=%s", ("Recollection failed for " + inventoryPath).c_str())); } } else { executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType, driverType, "/unbind")); executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType, driverType, "/bind")); } } void Manager::deleteFRUVPD(const sdbusplus::message::object_path& path) { std::cout << "Manager called to delete vpd for fru: " << std::string{path} << std::endl; using InvalidArgument = sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; using Argument = xyz::openbmc_project::Common::InvalidArgument; std::string objPath{path}; // Strip any inventory prefix in path if (objPath.find(INVENTORY_PATH) == 0) { objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1); } // if path not found in Json. if (frus.find(objPath) == frus.end()) { elog(Argument::ARGUMENT_NAME("Object Path"), Argument::ARGUMENT_VALUE(objPath.c_str())); } inventory::Path& vpdFilePath = std::get<0>(frus.find(objPath)->second); string chipAddress = jsonFile["frus"][vpdFilePath].at(0).value("pcaChipAddress", ""); // if the FRU is present, then unbind the LED driver if any if (readBusProperty(objPath, "xyz.openbmc_project.Inventory.Item", "Present") == "true") { // check if we have cxp-port populated for the given object path. std::vector interfaceList{ "xyz.openbmc_project.State.Decorator.OperationalStatus"}; MapperResponse subTree = getObjectSubtreeForInterfaces( INVENTORY_PATH + objPath, 0, interfaceList); if (subTree.size() != 0) { for (auto [objectPath, serviceInterfaceMap] : subTree) { std::string subTreeObjPath{objectPath}; // Strip any inventory prefix in path if (subTreeObjPath.find(INVENTORY_PATH) == 0) { subTreeObjPath = subTreeObjPath.substr(sizeof(INVENTORY_PATH) - 1); } inventory::ObjectMap objectMap{ {subTreeObjPath, {{"xyz.openbmc_project.State.Decorator.OperationalStatus", {{"Functional", true}}}, {"xyz.openbmc_project.Inventory.Item", {{"Present", false}}}}}}; // objectMap.emplace(objectPath, move(interfaceMap)); common::utility::callPIM(move(objectMap)); } } // Unbind the LED driver for this FRU cout << "Unbinding device- " << chipAddress << endl; executeCmd(createBindUnbindDriverCmnd(chipAddress, "i2c", "leds-pca955x", "/unbind")); inventory::InterfaceMap interfacesPropMap; clearVpdOnRemoval(INVENTORY_PATH + objPath, interfacesPropMap); inventory::ObjectMap objectMap; objectMap.emplace(objPath, move(interfacesPropMap)); common::utility::callPIM(move(objectMap)); } } } // namespace manager } // namespace vpd } // namespace openpower