#include "config.h" #include "common_utility.hpp" #include "defines.hpp" #include "editor_impl.hpp" #include "ibm_vpd_utils.hpp" #include "ipz_parser.hpp" #include "keyword_vpd_parser.hpp" #include "memory_vpd_parser.hpp" #include "parser_factory.hpp" #include "vpd_exceptions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace openpower::vpd; using namespace CLI; using namespace vpd::keyword::parser; using namespace openpower::vpd::constants; namespace fs = filesystem; using json = nlohmann::json; using namespace openpower::vpd::parser::factory; using namespace openpower::vpd::inventory; using namespace openpower::vpd::memory::parser; using namespace openpower::vpd::parser::interface; using namespace openpower::vpd::exceptions; using namespace phosphor::logging; using namespace openpower::vpd::manager::editor; /** * @brief API declaration, Populate Dbus. * * This method invokes all the populateInterface functions * and notifies PIM about dbus object. * * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the * input. * @param[in] js - Inventory json object * @param[in] filePath - Path of the vpd file * @param[in] preIntrStr - Interface string */ template static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath); /** * @brief Returns the BMC state */ static auto getBMCState() { std::string bmcState; try { auto bus = sdbusplus::bus::new_default(); auto properties = bus.new_method_call( "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0", "org.freedesktop.DBus.Properties", "Get"); properties.append("xyz.openbmc_project.State.BMC"); properties.append("CurrentBMCState"); auto result = bus.call(properties); std::variant val; result.read(val); if (auto pVal = std::get_if(&val)) { bmcState = *pVal; } } catch (const sdbusplus::exception::SdBusError& e) { // Ignore any error std::cerr << "Failed to get BMC state: " << e.what() << "\n"; // Since we failed set to not ready state bmcState = "xyz.openbmc_project.State.BMC.BMCState.NotReady"; } return bmcState; } /** * @brief Check if the FRU is in the cache * * Checks if the FRU associated with the supplied D-Bus object path is already * on D-Bus. This can be used to test if a VPD collection is required for this * FRU. It uses the "xyz.openbmc_project.Inventory.Item, Present" property to * determine the presence of a FRU in the cache. * * @param objectPath - The D-Bus object path without the PIM prefix. * @return true if the object exists on D-Bus, false otherwise. */ static auto isFruInVpdCache(const std::string& objectPath) { try { auto bus = sdbusplus::bus::new_default(); auto invPath = std::string{pimPath} + objectPath; auto props = bus.new_method_call( "xyz.openbmc_project.Inventory.Manager", invPath.c_str(), "org.freedesktop.DBus.Properties", "Get"); props.append("xyz.openbmc_project.Inventory.Item"); props.append("Present"); auto result = bus.call(props); std::variant present; result.read(present); if (auto pVal = std::get_if(&present)) { return *pVal; } return false; } catch (const sdbusplus::exception::SdBusError& e) { std::cout << "FRU: " << objectPath << " not in D-Bus\n"; // Assume not present in case of an error return false; } } /** * @brief Check if VPD recollection is needed for the given EEPROM * * Not all FRUs can be swapped at BMC ready state. This function does the * following: * -- Check if the FRU is marked as "pluggableAtStandby" OR * "concurrentlyMaintainable". If so, return true. * -- Check if we are at BMC NotReady state. If we are, then return true. * -- Else check if the FRU is not present in the VPD cache (to cover for VPD * force collection). If not found in the cache, return true. * -- Else return false. * * @param js - JSON Object. * @param filePath - The EEPROM file. * @return true if collection should be attempted, false otherwise. */ static auto needsRecollection(const nlohmann::json& js, const string& filePath) { if (js["frus"][filePath].at(0).value("pluggableAtStandby", false) || js["frus"][filePath].at(0).value("concurrentlyMaintainable", false)) { return true; } if (getBMCState() == "xyz.openbmc_project.State.BMC.BMCState.NotReady") { return true; } if (!isFruInVpdCache(js["frus"][filePath].at(0).value("inventoryPath", ""))) { return true; } return false; } /** * @brief Expands location codes */ static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap, bool isSystemVpd) { auto expanded{unexpanded}; static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard"; static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN"; static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS"; size_t idx = expanded.find("fcs"); try { if (idx != string::npos) { string fc{}; string se{}; if (isSystemVpd) { const auto& fcData = vpdMap.at("VCEN").at("FC"); const auto& seData = vpdMap.at("VCEN").at("SE"); fc = string(fcData.data(), fcData.size()); se = string(seData.data(), seData.size()); } else { fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC"); se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE"); } // TODO: See if ND0 can be placed in the JSON expanded.replace(idx, 3, fc.substr(0, 4) + ".ND0." + se); } else { idx = expanded.find("mts"); if (idx != string::npos) { string mt{}; string se{}; if (isSystemVpd) { const auto& mtData = vpdMap.at("VSYS").at("TM"); const auto& seData = vpdMap.at("VSYS").at("SE"); mt = string(mtData.data(), mtData.size()); se = string(seData.data(), seData.size()); } else { mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM"); se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE"); } replace(mt.begin(), mt.end(), '-', '.'); expanded.replace(idx, 3, mt + "." + se); } } } catch (const exception& e) { std::cerr << "Failed to expand location code with exception: " << e.what() << "\n"; } return expanded; } /** * @brief Populate FRU specific interfaces. * * This is a common method which handles both * ipz and keyword specific interfaces thus, * reducing the code redundancy. * @param[in] map - Reference to the innermost keyword-value map. * @param[in] preIntrStr - Reference to the interface string. * @param[out] interfaces - Reference to interface map. */ template static void populateFruSpecificInterfaces(const T& map, const string& preIntrStr, inventory::InterfaceMap& interfaces) { inventory::PropertyMap prop; for (const auto& kwVal : map) { auto kw = kwVal.first; if (kw[0] == '#') { kw = string("PD_") + kw[1]; } else if (isdigit(kw[0])) { kw = string("N_") + kw; } if constexpr (is_same::value) { if (auto keywordValue = get_if(&kwVal.second)) { Binary vec((*keywordValue).begin(), (*keywordValue).end()); prop.emplace(move(kw), move(vec)); } else if (auto keywordValue = get_if(&kwVal.second)) { Binary vec((*keywordValue).begin(), (*keywordValue).end()); prop.emplace(move(kw), move(vec)); } else if (auto keywordValue = get_if(&kwVal.second)) { if (kw == "MemorySizeInKB") { inventory::PropertyMap memProp; memProp.emplace(move(kw), ((*keywordValue))); interfaces.emplace( "xyz.openbmc_project.Inventory.Item.Dimm", move(memProp)); } else { std::cerr << "Unknown Keyword[" << kw << "] found "; } } else { std::cerr << "Unknown Variant found "; } } else { Binary vec(kwVal.second.begin(), kwVal.second.end()); prop.emplace(move(kw), move(vec)); } } interfaces.emplace(preIntrStr, move(prop)); } /** * @brief Populate Interfaces. * * This method populates common and extra interfaces to dbus. * @param[in] js - json object * @param[out] interfaces - Reference to interface map * @param[in] vpdMap - Reference to the parsed vpd map. * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD. */ template static void populateInterfaces(const nlohmann::json& js, inventory::InterfaceMap& interfaces, const T& vpdMap, bool isSystemVpd) { for (const auto& ifs : js.items()) { string inf = ifs.key(); inventory::PropertyMap props; for (const auto& itr : ifs.value().items()) { const string& busProp = itr.key(); if (itr.value().is_boolean()) { props.emplace(busProp, itr.value().get()); } else if (itr.value().is_string()) { if (busProp == "LocationCode" && inf == IBM_LOCATION_CODE_INF) { std::string prop; if constexpr (is_same::value) { // TODO deprecate the com.ibm interface later prop = expandLocationCode(itr.value().get(), vpdMap, isSystemVpd); } else if constexpr (is_same::value) { // Send empty Parsed object to expandLocationCode api. prop = expandLocationCode(itr.value().get(), Parsed{}, false); } props.emplace(busProp, prop); interfaces.emplace(XYZ_LOCATION_CODE_INF, props); interfaces.emplace(IBM_LOCATION_CODE_INF, props); } else { props.emplace(busProp, itr.value().get()); } } else if (itr.value().is_array()) { try { props.emplace(busProp, itr.value().get()); } catch (const nlohmann::detail::type_error& e) { std::cerr << "Type exception: " << e.what() << "\n"; // Ignore any type errors } } else if (itr.value().is_object()) { const string& rec = itr.value().value("recordName", ""); const string& kw = itr.value().value("keywordName", ""); const string& encoding = itr.value().value("encoding", ""); if constexpr (is_same::value) { if (!rec.empty() && !kw.empty() && vpdMap.count(rec) && vpdMap.at(rec).count(kw)) { auto encoded = encodeKeyword(vpdMap.at(rec).at(kw), encoding); props.emplace(busProp, encoded); } } else if constexpr (is_same::value) { if (!kw.empty() && vpdMap.count(kw)) { if (auto kwValue = get_if(&vpdMap.at(kw))) { auto prop = string((*kwValue).begin(), (*kwValue).end()); auto encoded = encodeKeyword(prop, encoding); props.emplace(busProp, encoded); } else if (auto kwValue = get_if(&vpdMap.at(kw))) { auto prop = string((*kwValue).begin(), (*kwValue).end()); auto encoded = encodeKeyword(prop, encoding); props.emplace(busProp, encoded); } else if (auto uintValue = get_if(&vpdMap.at(kw))) { props.emplace(busProp, *uintValue); } else { std::cerr << " Unknown Keyword [" << kw << "] Encountered"; } } } } else if (itr.value().is_number()) { // For now assume the value is a size_t. In the future it would // be nice to come up with a way to get the type from the JSON. props.emplace(busProp, itr.value().get()); } } insertOrMerge(interfaces, inf, move(props)); } } /** * @brief This API checks if this FRU is pcie_devices. If yes then it further * checks whether it is PASS1 planar. */ static bool isThisPcieOnPass1planar(const nlohmann::json& js, const string& file) { auto isThisPCIeDev = false; auto isPASS1 = false; // Check if it is a PCIE device if (js["frus"].find(file) != js["frus"].end()) { if ((js["frus"][file].at(0).find("extraInterfaces") != js["frus"][file].at(0).end())) { if (js["frus"][file].at(0)["extraInterfaces"].find( "xyz.openbmc_project.Inventory.Item.PCIeDevice") != js["frus"][file].at(0)["extraInterfaces"].end()) { isThisPCIeDev = true; } } } if (isThisPCIeDev) { // Collect HW version and SystemType to know if it is PASS1 planar. auto bus = sdbusplus::bus::new_default(); auto property1 = bus.new_method_call( INVENTORY_MANAGER_SERVICE, "/xyz/openbmc_project/inventory/system/chassis/motherboard", "org.freedesktop.DBus.Properties", "Get"); property1.append("com.ibm.ipzvpd.VINI"); property1.append("HW"); auto result1 = bus.call(property1); inventory::Value hwVal; result1.read(hwVal); // SystemType auto property2 = bus.new_method_call( INVENTORY_MANAGER_SERVICE, "/xyz/openbmc_project/inventory/system/chassis/motherboard", "org.freedesktop.DBus.Properties", "Get"); property2.append("com.ibm.ipzvpd.VSBP"); property2.append("IM"); auto result2 = bus.call(property2); inventory::Value imVal; result2.read(imVal); auto pVal1 = get_if(&hwVal); auto pVal2 = get_if(&imVal); if (pVal1 && pVal2) { auto hwVersion = *pVal1; auto systemType = *pVal2; // IM kw for Everest Binary everestSystem{80, 00, 48, 00}; if (systemType == everestSystem) { if (hwVersion[1] < 21) { isPASS1 = true; } } else if (hwVersion[1] < 2) { isPASS1 = true; } } } return (isThisPCIeDev && isPASS1); } /** Performs any pre-action needed to get the FRU setup for collection. * * @param[in] json - json object * @param[in] file - eeprom file path */ static void preAction(const nlohmann::json& json, const string& file) { if ((json["frus"][file].at(0)).find("preAction") == json["frus"][file].at(0).end()) { return; } try { if (executePreAction(json, file)) { if (json["frus"][file].at(0).find("devAddress") != json["frus"][file].at(0).end()) { // Now bind the device string bind = json["frus"][file].at(0).value("devAddress", ""); std::cout << "Binding device " << bind << std::endl; string bindCmd = string("echo \"") + bind + string("\" > /sys/bus/i2c/drivers/at24/bind"); std::cout << bindCmd << std::endl; executeCmd(bindCmd); // Check if device showed up (test for file) if (!fs::exists(file)) { std::cerr << "EEPROM " << file << " does not exist. Take failure action" << std::endl; // If not, then take failure postAction executePostFailAction(json, file); } } else { // missing required information std::cerr << "VPD inventory JSON missing basic information of " "preAction " "for this FRU : [" << file << "]. Executing executePostFailAction." << std::endl; // Take failure postAction executePostFailAction(json, file); return; } } else { // If the FRU is not there, clear the VINI/CCIN data. // Entity manager probes for this keyword to look for this // FRU, now if the data is persistent on BMC and FRU is // removed this can lead to ambiguity. Hence clearing this // Keyword if FRU is absent. const auto& invPath = json["frus"][file].at(0).value("inventoryPath", ""); if (!invPath.empty()) { inventory::ObjectMap pimObjMap{ {invPath, {{"com.ibm.ipzvpd.VINI", {{"CC", Binary{}}}}}}}; common::utility::callPIM(move(pimObjMap)); } else { throw std::runtime_error("Path empty in Json"); } } } catch (const GpioException& e) { PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", e.what()); createPEL(additionalData, PelSeverity::WARNING, errIntfForGpioError, nullptr); } } /** * @brief Fills the Decorator.AssetTag property into the interfaces map * * This function should only be called in cases where we did not find a JSON * symlink. A missing symlink in /var/lib will be considered as a factory reset * and this function will be used to default the AssetTag property. * * @param interfaces A possibly pre-populated map of inetrfaces to properties. * @param vpdMap A VPD map of the system VPD data. */ static void fillAssetTag(inventory::InterfaceMap& interfaces, const Parsed& vpdMap) { // Read the system serial number and MTM // Default asset tag is Server-MTM-System Serial inventory::Interface assetIntf{ "xyz.openbmc_project.Inventory.Decorator.AssetTag"}; inventory::PropertyMap assetTagProps; std::string defaultAssetTag = std::string{"Server-"} + getKwVal(vpdMap, "VSYS", "TM") + std::string{"-"} + getKwVal(vpdMap, "VSYS", "SE"); assetTagProps.emplace("AssetTag", defaultAssetTag); insertOrMerge(interfaces, assetIntf, std::move(assetTagProps)); } /** * @brief Set certain one time properties in the inventory * Use this function to insert the Functional and Enabled properties into the * inventory map. This function first checks if the object in question already * has these properties hosted on D-Bus, if the property is already there, it is * not modified, hence the name "one time". If the property is not already * present, it will be added to the map with a suitable default value (true for * Functional and Enabled) * * @param[in] object - The inventory D-Bus object without the inventory prefix. * @param[in,out] interfaces - Reference to a map of inventory interfaces to * which the properties will be attached. */ static void setOneTimeProperties(const std::string& object, inventory::InterfaceMap& interfaces) { auto bus = sdbusplus::bus::new_default(); auto objectPath = INVENTORY_PATH + object; auto prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager", objectPath.c_str(), "org.freedesktop.DBus.Properties", "Get"); prop.append("xyz.openbmc_project.State.Decorator.OperationalStatus"); prop.append("Functional"); try { auto result = bus.call(prop); } catch (const sdbusplus::exception::SdBusError& e) { // Treat as property unavailable inventory::PropertyMap prop; prop.emplace("Functional", true); interfaces.emplace( "xyz.openbmc_project.State.Decorator.OperationalStatus", move(prop)); } prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager", objectPath.c_str(), "org.freedesktop.DBus.Properties", "Get"); prop.append("xyz.openbmc_project.Object.Enable"); prop.append("Enabled"); try { auto result = bus.call(prop); } catch (const sdbusplus::exception::SdBusError& e) { // Treat as property unavailable inventory::PropertyMap prop; prop.emplace("Enabled", true); interfaces.emplace("xyz.openbmc_project.Object.Enable", move(prop)); } } /** * @brief Prime the Inventory * Prime the inventory by populating only the location code, * type interface and the inventory object for the frus * which are not system vpd fru. * * @param[in] jsObject - Reference to vpd inventory json object * @param[in] vpdMap - Reference to the parsed vpd map * * @returns Map of items in extraInterface. */ template inventory::ObjectMap primeInventory(const nlohmann::json& jsObject, const T& vpdMap) { inventory::ObjectMap objects; for (auto& itemFRUS : jsObject["frus"].items()) { for (auto& itemEEPROM : itemFRUS.value()) { // Take pre actions if needed if (itemEEPROM.find("preAction") != itemEEPROM.end()) { preAction(jsObject, itemFRUS.key()); } inventory::InterfaceMap interfaces; inventory::Object object(itemEEPROM.at("inventoryPath")); if ((itemFRUS.key() != systemVpdFilePath) && !itemEEPROM.value("noprime", false)) { inventory::PropertyMap presProp; // Do not populate Present property for frus whose // synthesized=true. synthesized=true says the fru VPD is // synthesized and owned by a separate component. // In some cases, the FRU has its own VPD, but still a separate // application handles the FRU's presence. So VPD parser skips // populating Present property by checking the JSON flag, // "handlePresence". if (!itemEEPROM.value("synthesized", false)) { if (itemEEPROM.value("handlePresence", true)) { presProp.emplace("Present", false); interfaces.emplace("xyz.openbmc_project.Inventory.Item", presProp); } } setOneTimeProperties(object, interfaces); if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end()) { for (const auto& eI : itemEEPROM["extraInterfaces"].items()) { inventory::PropertyMap props; if (eI.key() == IBM_LOCATION_CODE_INF) { if constexpr (std::is_same::value) { for (auto& lC : eI.value().items()) { auto propVal = expandLocationCode( lC.value().get(), vpdMap, true); props.emplace(move(lC.key()), move(propVal)); interfaces.emplace(XYZ_LOCATION_CODE_INF, props); interfaces.emplace(move(eI.key()), move(props)); } } } else if (eI.key() == "xyz.openbmc_project.Inventory.Item") { for (auto& val : eI.value().items()) { if (val.key() == "PrettyName") { presProp.emplace(val.key(), val.value().get()); } } // Use insert_or_assign here as we may already have // inserted the present property only earlier in // this function under this same interface. interfaces.insert_or_assign(eI.key(), move(presProp)); } else { interfaces.emplace(move(eI.key()), move(props)); } } } objects.emplace(move(object), move(interfaces)); } } } return objects; } /** * @brief This API executes command to set environment variable * And then reboot the system * @param[in] key -env key to set new value * @param[in] value -value to set. */ void setEnvAndReboot(const string& key, const string& value) { // set env and reboot and break. executeCmd("/sbin/fw_setenv", key, value); log("Rebooting BMC to pick up new device tree"); // make dbus call to reboot auto bus = sdbusplus::bus::new_default_system(); auto method = bus.new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Reboot"); bus.call_noreply(method); } /* * @brief This API checks for env var fitconfig. * If not initialised OR updated as per the current system type, * update this env var and reboot the system. * * @param[in] systemType IM kwd in vpd tells about which system type it is. * */ void setDevTreeEnv(const string& systemType) { // Init with default dtb string newDeviceTree = "conf-aspeed-bmc-ibm-rainier-p1.dtb"; static const deviceTreeMap deviceTreeSystemTypeMap = { {RAINIER_2U, "conf-aspeed-bmc-ibm-rainier-p1.dtb"}, {RAINIER_2U_V2, "conf-aspeed-bmc-ibm-rainier.dtb"}, {RAINIER_4U, "conf-aspeed-bmc-ibm-rainier-4u-p1.dtb"}, {RAINIER_4U_V2, "conf-aspeed-bmc-ibm-rainier-4u.dtb"}, {RAINIER_1S4U, "conf-aspeed-bmc-ibm-rainier-1s4u.dtb"}, {EVEREST, "conf-aspeed-bmc-ibm-everest.dtb"}, {EVEREST_V2, "conf-aspeed-bmc-ibm-everest.dtb"}, {BONNELL, "conf-aspeed-bmc-ibm-bonnell.dtb"}}; if (deviceTreeSystemTypeMap.find(systemType) != deviceTreeSystemTypeMap.end()) { newDeviceTree = deviceTreeSystemTypeMap.at(systemType); } else { // System type not supported string err = "This System type not found/supported in dtb table " + systemType + ".Please check the HW and IM keywords in the system " "VPD.Breaking..."; // map to hold additional data in case of logging pel PelAdditionalData additionalData{}; additionalData.emplace("DESCRIPTION", err); createPEL(additionalData, PelSeverity::WARNING, errIntfForInvalidSystemType, nullptr); exit(-1); } string readVarValue; bool envVarFound = false; vector output = executeCmd("/sbin/fw_printenv"); for (const auto& entry : output) { size_t pos = entry.find("="); string key = entry.substr(0, pos); if (key != "fitconfig") { continue; } envVarFound = true; if (pos + 1 < entry.size()) { readVarValue = entry.substr(pos + 1); if (readVarValue.find(newDeviceTree) != string::npos) { // fitconfig is Updated. No action needed break; } } // set env and reboot and break. setEnvAndReboot(key, newDeviceTree); exit(0); } // check If env var Not found if (!envVarFound) { setEnvAndReboot("fitconfig", newDeviceTree); } } /** * @brief Parse the given EEPROM file. * * @param[in] vpdFilePath - Path of EEPROM file * @param[in] js- Reference to vpd inventory json object * @return Parsed VPD map */ std::variant parseVpdFile(const std::string& vpdFilePath, const nlohmann::json& js) { uint32_t vpdStartOffset = 0; for (const auto& item : js["frus"][vpdFilePath]) { if (item.find("offset") != item.end()) { vpdStartOffset = item["offset"]; break; } } Binary vpdVector = getVpdDataInVector(js, vpdFilePath); ParserInterface* parser = ParserFactory::getParser( vpdVector, (pimPath + js["frus"][vpdFilePath][0]["inventoryPath"] .get_ref()), vpdFilePath, vpdStartOffset); auto parseResult = parser->parse(); // release the parser object ParserFactory::freeParser(parser); return parseResult; } /* * @brief This API retrieves the hardware backup in map * * @param[in] systemVpdBackupPath - The path that backs up the system VPD. * @param[in] backupVpdInvPath - FRU inventory path. * @param[in] js - JSON object. * @param[out] backupVpdMap - An IPZ VPD map containing the parsed backup VPD. * * */ void getBackupVpdInMap(const string& systemVpdBackupPath, const string& backupVpdInvPath, const nlohmann::json& js, Parsed& backupVpdMap) { PelAdditionalData additionalData{}; if (!fs::exists(systemVpdBackupPath)) { string errorMsg = "Device path "; errorMsg += systemVpdBackupPath; errorMsg += " does not exist"; additionalData.emplace("DESCRIPTION", errorMsg); additionalData.emplace("CALLOUT_INVENTORY_PATH", INVENTORY_PATH + backupVpdInvPath); createPEL(additionalData, PelSeverity::ERROR, errIntfForStreamFail, nullptr); } else { auto backupVpdParsedResult = parseVpdFile(systemVpdBackupPath, js); if (auto pVal = get_if(&backupVpdParsedResult)) { backupVpdMap = pVal->getVpdMap(); } else { std::cerr << "Invalid format of VPD in back up. Restore aborted." << std::endl; } } } void updateVpdDataOnHw(const std::string& vpdFilePath, nlohmann::json& js, const std::string& recName, const std::string& kwName, const Binary& kwdData) { const std::string& fruInvPath = js["frus"][vpdFilePath][0]["inventoryPath"] .get_ref(); EditorImpl edit(vpdFilePath, js, recName, kwName, fruInvPath); uint32_t offset = 0; // Setup offset, if any for (const auto& item : js["frus"][vpdFilePath]) { if (item.find("offset") != item.end()) { offset = item["offset"]; break; } } // update keyword data on to EEPROM file // Note: Updating keyword data on cache is // handled via PIM Notify call hence passing // the updCache flag value as false here. edit.updateKeyword(kwdData, offset, false); } /** * @brief API to check if we need to restore system VPD * This functionality is only applicable for IPZ VPD data. * @param[in] vpdMap - IPZ vpd map * @param[in] objectPath - Object path for the FRU * @param[in] js - JSON Object * @param[in] isBackupOnCache - Denotes whether the backup is on cache/hardware */ void restoreSystemVPD(Parsed& vpdMap, const string& objectPath, nlohmann::json& js, bool isBackupOnCache = true) { std::string systemVpdBackupPath{}; std::string backupVpdInvPath{}; Parsed backupVpdMap{}; if (!isBackupOnCache) { // Get the value of systemvpdBackupPath field from json systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value( "systemVpdBackupPath", ""); backupVpdInvPath = js["frus"][systemVpdBackupPath][0]["inventoryPath"] .get_ref(); getBackupVpdInMap(systemVpdBackupPath, backupVpdInvPath, js, backupVpdMap); if (backupVpdMap.empty()) { std::cerr << "Backup VPD map is empty" << std::endl; return; } } for (const auto& systemRecKwdPair : svpdKwdMap) { const string& recordName = systemRecKwdPair.first; auto it = vpdMap.find(recordName); // 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 keywordName = get<0>(keywordInfo); DbusPropertyMap& kwdValMap = it->second; auto iterator = kwdValMap.find(keywordName); if (iterator != kwdValMap.end()) { string& kwdValue = iterator->second; std::string backupValue{}; const auto& defaultValue = get<1>(keywordInfo); const auto& backupVpdRecName = get<4>(keywordInfo); const auto& backupVpdKwName = get<5>(keywordInfo); // If the 'isBackupOnCache' flag is false, we need // to backup the systemVPD on the specified fru's eeprom // path or restore it from the specified fru's eeprom path. if (isBackupOnCache) { // check bus data backupValue = readBusProperty( objectPath, ipzVpdInf + recordName, keywordName); } else { backupValue = getKwVal(backupVpdMap, backupVpdRecName, backupVpdKwName); if (backupValue.empty()) { string errorMsg{}; if (backupVpdMap.find(backupVpdRecName) == backupVpdMap.end()) { errorMsg = backupVpdRecName + " Record does not exist in " "the EEPROM file "; } else { errorMsg = backupVpdKwName + " Keyword not found or empty."; } errorMsg += systemVpdBackupPath; PelAdditionalData additionalData; additionalData.emplace("DESCRIPTION", errorMsg); createPEL(additionalData, PelSeverity::ERROR, errIntfForInvalidVPD, nullptr); continue; } } Binary backupDataInBinary(backupValue.begin(), backupValue.end()); Binary kwdDataInBinary(kwdValue.begin(), kwdValue.end()); if (backupDataInBinary != defaultValue) { if (kwdDataInBinary != defaultValue) { // both the data are present, check for mismatch if (backupValue != kwdValue) { string errMsg = "Mismatch found between backup " "and primary VPD for record: "; errMsg += (*it).first; errMsg += " and keyword: "; errMsg += keywordName; std::ostringstream busStream; for (uint16_t byte : backupValue) { busStream << std::setfill('0') << std::setw(2) << std::hex << "0x" << byte << " "; } std::ostringstream vpdStream; for (uint16_t byte : kwdValue) { vpdStream << std::setfill('0') << std::setw(2) << std::hex << "0x" << byte << " "; } // data mismatch PelAdditionalData additionalData; additionalData.emplace("DESCRIPTION", errMsg); additionalData.emplace( "Value read from Backup: ", busStream.str()); additionalData.emplace( "Value read from Primary: ", vpdStream.str()); createPEL(additionalData, PelSeverity::WARNING, errIntfForVPDMismatch, nullptr); if (!isBackupOnCache) { // Backing up or restoring from a hardware // path does not requires copying the backup // data to the VPD map, as this will result // in a mismatch between the primary VPD and // its cache. continue; } } else { // both the backup and primary data is // non-default and same. Nothing needs to be // done. continue; } } // If the backup is on the cache we need to copy the // backup data to the VPD map to ensure there is no // mismatch b/n them. So if backup data is not default, // then irrespective of primary data(default or other // than backup), copy the backup data to vpd map as we // don't need to change the backup data in either case // in the process of restoring system vpd. kwdValue = backupValue; // If the backup data is on the base panel the restoring // of Backup VPD on to the system backplane VPD // file is done here not through the VPD manager code // path. This is to have the logic of restoring data on // to the cache & hardware in the same code path. if (!isBackupOnCache) { // copy backup VPD on to system backplane // EEPROM file. updateVpdDataOnHw(systemVpdFilePath, js, recordName, keywordName, backupDataInBinary); } } else if (kwdDataInBinary == defaultValue && get<2>(keywordInfo)) // Check isPELRequired is true { string errMsg = "Found default value on both backup " "and primary VPD for record: "; errMsg += (*it).first; errMsg += " and keyword: "; errMsg += keywordName; errMsg += ". Update primary VPD."; // mfg default on both backup and primary, log PEL PelAdditionalData additionalData; additionalData.emplace("DESCRIPTION", errMsg); createPEL(additionalData, PelSeverity::ERROR, errIntfForVPDDefault, nullptr); continue; } else if ((kwdDataInBinary != defaultValue) && (!isBackupOnCache)) { // update primary VPD on to backup VPD file updateVpdDataOnHw(systemVpdBackupPath, js, backupVpdRecName, backupVpdKwName, kwdDataInBinary); // copy primary VPD to backup VPD to publish on // DBus backupVpdMap.find(backupVpdRecName) ->second.find(backupVpdKwName) ->second = kwdValue; } } } } } } /** * @brief This checks for is this FRU a processor * And if yes, then checks for is this primary * * @param[in] js- vpd json to get the information about this FRU * @param[in] filePath- FRU vpd * * @return true/false */ bool isThisPrimaryProcessor(nlohmann::json& js, const string& filePath) { bool isProcessor = false; bool isPrimary = false; for (const auto& item : js["frus"][filePath]) { if (item.find("extraInterfaces") != item.end()) { for (const auto& eI : item["extraInterfaces"].items()) { if (eI.key().find("Inventory.Item.Cpu") != string::npos) { isProcessor = true; } } } if (isProcessor) { string cpuType = item.value("cpuType", ""); if (cpuType == "primary") { isPrimary = true; } } } return (isProcessor && isPrimary); } /** * @brief This finds DIMM vpd in vpd json and enables them by binding the device * driver * @param[in] js- vpd json to iterate through and take action if it is DIMM */ void doEnableAllDimms(nlohmann::json& js) { // iterate over each fru for (const auto& eachFru : js["frus"].items()) { // skip the driver binding if eeprom already exists if (fs::exists(eachFru.key())) { continue; } for (const auto& eachInventory : eachFru.value()) { if (eachInventory.find("extraInterfaces") != eachInventory.end()) { for (const auto& eI : eachInventory["extraInterfaces"].items()) { if (eI.key().find("Inventory.Item.Dimm") != string::npos) { string dimmVpd = eachFru.key(); // fetch it from // "/sys/bus/i2c/drivers/at24/414-0050/eeprom" regex matchPatern("([0-9]+-[0-9]{4})"); smatch matchFound; if (regex_search(dimmVpd, matchFound, matchPatern)) { vector i2cReg; boost::split(i2cReg, matchFound.str(0), boost::is_any_of("-")); // remove 0s from beginning const regex pattern("^0+(?!$)"); for (auto& i : i2cReg) { i = regex_replace(i, pattern, ""); } // For ISDIMM which uses ee1004 driver // the below is done size_t stringFound = dimmVpd.find("ee1004"); if (stringFound != string::npos) { // echo ee1004 0x50 > // /sys/bus/i2c/devices/i2c-110/new_device string cmnd = "echo ee1004 0x" + i2cReg[1] + " > /sys/bus/i2c/devices/i2c-" + i2cReg[0] + "/new_device"; executeCmd(cmnd); } else if (i2cReg.size() == 2) { // echo 24c32 0x50 > // /sys/bus/i2c/devices/i2c-16/new_device string cmnd = "echo 24c32 0x" + i2cReg[1] + " > /sys/bus/i2c/devices/i2c-" + i2cReg[0] + "/new_device"; executeCmd(cmnd); } } } } } } } } /** * @brief Check if the given CPU is an IO only chip. * The CPU is termed as IO, whose all of the cores are bad and can never be * used. Those CPU chips can be used for IO purpose like connecting PCIe devices * etc., The CPU whose every cores are bad, can be identified from the CP00 * record's PG keyword, only if all of the 8 EQs' value equals 0xE7F9FF. (1EQ * has 4 cores grouped together by sharing its cache memory.) * @param [in] pgKeyword - PG Keyword of CPU. * @return true if the given cpu is an IO, false otherwise. */ static bool isCPUIOGoodOnly(const string& pgKeyword) { const unsigned char io[] = {0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF}; // EQ0 index (in PG keyword) starts at 97 (with offset starting from 0). // Each EQ carries 3 bytes of data. Totally there are 8 EQs. If all EQs' // value equals 0xE7F9FF, then the cpu has no good cores and its treated as // IO. if (memcmp(io, pgKeyword.data() + 97, 24) == 0) { return true; } // The CPU is not an IO return false; } /** * @brief Function to bring MUX out of idle state * * This finds All the MUX defined in the system json and enables * them by setting the holdidle parameter to 0. * @param[in] js- system json to iterate through and take action */ void doEnableAllMuxChips(const nlohmann::json& js) { // Do we have the mandatory "muxes" section? if (js.find("muxes") != js.end()) { std::cout << "Enabling all the MUX on the system " << std::endl; // iterate over each MUX detail and enable them for (const auto& item : js["muxes"]) { if (item.find("holdidlepath") != item.end()) { const std::string& holdidle = item["holdidlepath"]; std::cout << "Setting holdidle state for " << holdidle << "to 0 " << std::endl; string cmd = "echo 0 > " + holdidle; executeCmd(cmd); } } std::cout << "Completed enabling all the MUX on the system " << std::endl; } else { std::cout << "No MUX was defined for the system" << std::endl; } } /** * @brief Populate Dbus. * This method invokes all the populateInterface functions * and notifies PIM about dbus object. * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the * input. * @param[in] js - Inventory json object * @param[in] filePath - Path of the vpd file * @param[in] preIntrStr - Interface string */ template static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath) { inventory::InterfaceMap interfaces; inventory::ObjectMap objects; inventory::PropertyMap prop; string ccinFromVpd; bool isSystemVpd = (filePath == systemVpdFilePath); if constexpr (is_same::value) { ccinFromVpd = getKwVal(vpdMap, "VINI", "CC"); transform(ccinFromVpd.begin(), ccinFromVpd.end(), ccinFromVpd.begin(), ::toupper); if (isSystemVpd) { string mboardPath = js["frus"][filePath].at(0).value("inventoryPath", ""); // Get the value of systemvpdBackupPath field from json const std::string& systemVpdBackupPath = js["frus"][filePath].at(0).value("systemVpdBackupPath", ""); if (systemVpdBackupPath.empty()) { std::vector interfaces = {motherBoardInterface}; // call mapper to check for object path creation MapperResponse subTree = getObjectSubtreeForInterfaces(pimPath, 0, interfaces); // Attempt system VPD restore if we have a motherboard // object in the inventory. if ((subTree.size() != 0) && (subTree.find(pimPath + mboardPath) != subTree.end())) { restoreSystemVPD(vpdMap, mboardPath, js); } else { log("No object path found"); } } else { restoreSystemVPD(vpdMap, mboardPath, js, false); } } else { // check if it is processor vpd. auto isPrimaryCpu = isThisPrimaryProcessor(js, filePath); if (isPrimaryCpu) { auto ddVersion = getKwVal(vpdMap, "CRP0", "DD"); auto chipVersion = atoi(ddVersion.substr(1, 2).c_str()); if (chipVersion >= 2) { doEnableAllDimms(js); // Sleep for a few seconds to let the DIMM parses start using namespace std::chrono_literals; std::this_thread::sleep_for(5s); } } } } auto processFactoryReset = false; if (isSystemVpd) { string systemJsonName{}; if constexpr (is_same::value) { // pick the right system json systemJsonName = getSystemsJson(vpdMap); } fs::path target = systemJsonName; fs::path link = INVENTORY_JSON_SYM_LINK; // If the symlink does not exist, we treat that as a factory reset processFactoryReset = !fs::exists(INVENTORY_JSON_SYM_LINK); // Create the directory for hosting the symlink fs::create_directories(VPD_FILES_PATH); // unlink the symlink previously created (if any) remove(INVENTORY_JSON_SYM_LINK); // create a new symlink based on the system fs::create_symlink(target, link); // Reloading the json ifstream inventoryJson(link); js = json::parse(inventoryJson); inventoryJson.close(); // enable the muxes again here to cover the case where during first boot // after reset, system would have come up with default JSON // configuration and have skipped enabling mux at the beginning. // Default config JSON does not have mux entries. doEnableAllMuxChips(js); } for (const auto& item : js["frus"][filePath]) { const auto& objectPath = item["inventoryPath"]; sdbusplus::message::object_path object(objectPath); vector ccinList; if (item.find("ccin") != item.end()) { for (const auto& cc : item["ccin"]) { string ccin = cc; transform(ccin.begin(), ccin.end(), ccin.begin(), ::toupper); ccinList.push_back(ccin); } } if (!ccinFromVpd.empty() && !ccinList.empty() && (find(ccinList.begin(), ccinList.end(), ccinFromVpd) == ccinList.end())) { continue; } if ((isSystemVpd) || (item.value("noprime", false))) { // Populate one time properties for the system VPD and its sub-frus // and for other non-primeable frus. // For the remaining FRUs, this will get handled as a part of // priming the inventory. setOneTimeProperties(objectPath, interfaces); } // Populate the VPD keywords and the common interfaces only if we // are asked to inherit that data from the VPD, else only add the // extraInterfaces. if (item.value("inherit", true)) { if constexpr (is_same::value) { // Each record in the VPD becomes an interface and all // keyword within the record are properties under that // interface. for (const auto& record : vpdMap) { populateFruSpecificInterfaces( record.second, ipzVpdInf + record.first, interfaces); } } else if constexpr (is_same::value) { populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces); } if (js.find("commonInterfaces") != js.end()) { populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, isSystemVpd); } } else { // Check if we have been asked to inherit specific record(s) if constexpr (is_same::value) { if (item.find("copyRecords") != item.end()) { for (const auto& record : item["copyRecords"]) { const string& recordName = record; if (vpdMap.find(recordName) != vpdMap.end()) { populateFruSpecificInterfaces( vpdMap.at(recordName), ipzVpdInf + recordName, interfaces); } } } } } // Populate interfaces and properties that are common to every FRU // and additional interface that might be defined on a per-FRU // basis. if (item.find("extraInterfaces") != item.end()) { populateInterfaces(item["extraInterfaces"], interfaces, vpdMap, isSystemVpd); if constexpr (is_same::value) { if (item["extraInterfaces"].find( "xyz.openbmc_project.Inventory.Item.Cpu") != item["extraInterfaces"].end()) { if (isCPUIOGoodOnly(getKwVal(vpdMap, "CP00", "PG"))) { interfaces[invItemIntf]["PrettyName"] = "IO Module"; } } } } // embedded property(true or false) says whether the subfru is embedded // into the parent fru (or) not. VPD sets Present property only for // embedded frus. If the subfru is not an embedded FRU, the subfru may // or may not be physically present. Those non embedded frus will always // have Present=false irrespective of its physical presence or absence. // Eg: nvme drive in nvme slot is not an embedded FRU. So don't set // Present to true for such sub frus. // Eg: ethernet port is embedded into bmc card. So set Present to true // for such sub frus. Also do not populate present property for embedded // subfru which is synthesized. Currently there is no subfru which are // both embedded and synthesized. But still the case is handled here. if ((item.value("embedded", true)) && (!item.value("synthesized", false))) { // Check if its required to handle presence for this FRU. if (item.value("handlePresence", true)) { inventory::PropertyMap presProp; presProp.emplace("Present", true); insertOrMerge(interfaces, invItemIntf, move(presProp)); } } if constexpr (is_same::value) { // Restore asset tag, if needed if (processFactoryReset && objectPath == "/system") { fillAssetTag(interfaces, vpdMap); } } objects.emplace(move(object), move(interfaces)); } if (isSystemVpd) { inventory::ObjectMap primeObject = primeInventory(js, vpdMap); objects.insert(primeObject.begin(), primeObject.end()); // set the U-boot environment variable for device-tree if constexpr (is_same::value) { setDevTreeEnv(fs::path(getSystemsJson(vpdMap)).filename()); } } // Notify PIM common::utility::callPIM(move(objects)); } int main(int argc, char** argv) { int rc = 0; json js{}; Binary vpdVector{}; string file{}; string driver{}; // map to hold additional data in case of logging pel PelAdditionalData additionalData{}; // this is needed to hold base fru inventory path in case there is ECC or // vpd exception while parsing the file std::string baseFruInventoryPath = {}; // It holds the backup EEPROM file path for the system backplane's critical // data std::string systemVpdBackupPath{}; // It holds the inventory path of backup EEPROM file std::string backupVpdInvPath{}; bool isSystemVpd = false; // severity for PEL PelSeverity pelSeverity = PelSeverity::WARNING; try { App app{"ibm-read-vpd - App to read IPZ/Jedec format VPD, parse it and " "store it in DBUS"}; app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") ->required(); app.add_option("--driver", driver, "Driver used by kernel (at24,at25,ee1004)") ->required(); CLI11_PARSE(app, argc, argv); // PEL severity should be ERROR in case of any system VPD failure if (file == systemVpdFilePath) { pelSeverity = PelSeverity::ERROR; isSystemVpd = true; } // Check if input file is not empty. if ((file.empty()) || (driver.empty())) { std::cerr << "Encountered empty input parameter file [" << file << "] driver [" << driver << "]" << std::endl; return 0; } // Check if currently supported driver or not if ((driver != at24driver) && (driver != at25driver) && (driver != ee1004driver)) { std::cerr << "The driver [" << driver << "] is not supported." << std::endl; return 0; } auto jsonToParse = INVENTORY_JSON_DEFAULT; // If the symlink exists, it means it has been setup for us, switch the // path if (fs::exists(INVENTORY_JSON_SYM_LINK)) { jsonToParse = INVENTORY_JSON_SYM_LINK; } // Make sure that the file path we get is for a supported EEPROM ifstream inventoryJson(jsonToParse); if (!inventoryJson) { throw(VpdJsonException("Failed to access Json path", jsonToParse)); } try { js = json::parse(inventoryJson); } catch (const json::parse_error& ex) { throw(VpdJsonException("Json parsing failed", jsonToParse)); } // Do we have the mandatory "frus" section? if (js.find("frus") == js.end()) { throw(VpdJsonException("FRUs section not found in JSON", jsonToParse)); } // Check if it's a udev path - patterned as(/ahb/ahb:apb/ahb:apb:bus@) if (file.find("/ahb:apb") != string::npos) { // Translate udev path to a generic /sys/bus/.. file path. udevToGenericPath(file, driver); if ((js["frus"].find(file) != js["frus"].end()) && (file == systemVpdFilePath)) { std::cout << "We have already collected system VPD, skipping." << std::endl; return 0; } } // 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. // To run it only once have kept it under System vpd check. // we need to run this on all BMC reboots so kept here if (file == systemVpdFilePath) { doEnableAllMuxChips(js); } if (file.empty()) { std::cerr << "The EEPROM path <" << file << "> is not valid."; return 0; } if (js["frus"].find(file) == js["frus"].end()) { std::cerr << "The EEPROM path [" << file << "] is not found in the json." << std::endl; return 0; } if (!fs::exists(file)) { std::cout << "Device path: " << file << " does not exist. Spurious udev event? Exiting." << std::endl; return 0; } // In case of system VPD it will already be filled, Don't have to // overwrite that. if (baseFruInventoryPath.empty()) { baseFruInventoryPath = js["frus"][file][0]["inventoryPath"]; } // Check if we can read the VPD file based on the power state // We skip reading VPD when the power is ON in two scenarios: // 1) The eeprom we are trying to read is that of the system VPD and the // JSON symlink is already setup (the symlink's existence tells us we // are not coming out of a factory reset) // 2) The JSON tells us that the FRU EEPROM cannot be // read when we are powered ON. if (js["frus"][file].at(0).value("powerOffOnly", false) || (file == systemVpdFilePath && fs::exists(INVENTORY_JSON_SYM_LINK))) { if ("xyz.openbmc_project.State.Chassis.PowerState.On" == getPowerState()) { std::cout << "This VPD cannot be read when power is ON" << std::endl; return 0; } } // Check if this VPD should be recollected at all if (!needsRecollection(js, file)) { std::cout << "Skip VPD recollection for: " << file << std::endl; return 0; } try { variant parseResult; parseResult = parseVpdFile(file, js); if (isSystemVpd) { // Get the value of systemVpdBackupPath field from json systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value( "systemVpdBackupPath", ""); if (!systemVpdBackupPath.empty()) { backupVpdInvPath = js["frus"][systemVpdBackupPath][0]["inventoryPath"] .get_ref(); } } if (auto pVal = get_if(&parseResult)) { populateDbus(pVal->getVpdMap(), js, file); } else if (auto pVal = get_if(&parseResult)) { populateDbus(*pVal, js, file); } } catch (const exception& e) { if (!systemVpdBackupPath.empty()) { file = systemVpdBackupPath; baseFruInventoryPath = backupVpdInvPath; } executePostFailAction(js, file); throw; } } catch (const VpdJsonException& ex) { additionalData.emplace("JSON_PATH", ex.getJsonPath()); additionalData.emplace("DESCRIPTION", ex.what()); createPEL(additionalData, pelSeverity, errIntfForJsonFailure, nullptr); std::cerr << ex.what() << "\n"; rc = -1; } catch (const VpdEccException& ex) { additionalData.emplace("DESCRIPTION", "ECC check failed"); additionalData.emplace("CALLOUT_INVENTORY_PATH", INVENTORY_PATH + baseFruInventoryPath); createPEL(additionalData, pelSeverity, errIntfForEccCheckFail, nullptr); if (systemVpdBackupPath.empty()) { dumpBadVpd(file, vpdVector); } std::cerr << ex.what() << "\n"; rc = -1; } catch (const VpdDataException& ex) { if (isThisPcieOnPass1planar(js, file)) { std::cout << "Pcie_device [" << file << "]'s VPD is not valid on PASS1 planar.Ignoring.\n"; rc = 0; } else if (!(isPresent(js, file).value_or(true))) { std::cout << "FRU at: " << file << " is not detected present. Ignore parser error.\n"; rc = 0; } else { string errorMsg = "VPD file is either empty or invalid. Parser failed for ["; errorMsg += file; errorMsg += "], with error = " + std::string(ex.what()); additionalData.emplace("DESCRIPTION", errorMsg); additionalData.emplace("CALLOUT_INVENTORY_PATH", INVENTORY_PATH + baseFruInventoryPath); createPEL(additionalData, pelSeverity, errIntfForInvalidVPD, nullptr); rc = -1; } } catch (const exception& e) { dumpBadVpd(file, vpdVector); std::cerr << e.what() << "\n"; rc = -1; } return rc; }