#include "config.h" #include "common_utility.hpp" #include "defines.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; // Map to hold record, kwd pair which can be re-stored at standby. // The list of keywords for VSYS record is as per the S0 system. Should // be updated for another type of systems static const std::unordered_map> svpdKwdMap{{"VSYS", {"BR", "TM", "SE", "SU", "RB", "WN"}}, {"VCEN", {"FC", "SE"}}, {"LXR0", {"LX"}}}; /** * @brief Returns the power state for chassis0 */ static auto getPowerState() { // TODO: How do we handle multiple chassis? string powerState{}; auto bus = sdbusplus::bus::new_default(); auto properties = bus.new_method_call("xyz.openbmc_project.State.Chassis", "/xyz/openbmc_project/state/chassis0", "org.freedesktop.DBus.Properties", "Get"); properties.append("xyz.openbmc_project.State.Chassis"); properties.append("CurrentPowerState"); auto result = bus.call(properties); if (!result.is_method_error()) { variant val; result.read(val); if (auto pVal = get_if(&val)) { powerState = *pVal; } } cout << "Power state is: " << powerState << endl; return powerState; } /** * @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"; } 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) { 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 (get_if(&kwVal.second)) { Binary vec(get_if(&kwVal.second)->begin(), get_if(&kwVal.second)->end()); prop.emplace(move(kw), move(vec)); } else { if (kw == "MemorySizeInKB") { inventory::PropertyMap memProp; auto memVal = get_if(&kwVal.second); if (memVal) { memProp.emplace(move(kw), ((*memVal) * CONVERT_MB_TO_KB)); interfaces.emplace( "xyz.openbmc_project.Inventory.Item.Dimm", move(memProp)); } else { cerr << "MemorySizeInKB value not found in vpd map\n"; } } } } 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)) { auto kwValue = get_if(&vpdMap.at(kw)); auto uintValue = get_if(&vpdMap.at(kw)); if (kwValue) { auto prop = string((*kwValue).begin(), (*kwValue).end()); auto encoded = encodeKeyword(prop, encoding); props.emplace(busProp, encoded); } else if (uintValue) { props.emplace(busProp, *uintValue); } } } } 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)); } } /*API to reset EEPROM pointer to a safe position to avoid VPD corruption. * Currently do reset only for DIMM VPD.*/ static void resetEEPROMPointer(const nlohmann::json& js, const string& file, ifstream& vpdFile) { for (const auto& item : js["frus"][file]) { if (item.find("extraInterfaces") != item.end()) { if (item["extraInterfaces"].find( "xyz.openbmc_project.Inventory.Item.Dimm") != item["extraInterfaces"].end()) { // moves the EEPROM pointer to 2048 'th byte. vpdFile.seekg(2047, std::ios::beg); // Read that byte and discard - to affirm the move // operation. char ch; vpdFile.read(&ch, sizeof(ch)); } return; } } } /** * @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); } static Binary getVpdDataInVector(const nlohmann::json& js, const string& file) { uint32_t offset = 0; // check if offset present? for (const auto& item : js["frus"][file]) { if (item.find("offset") != item.end()) { offset = item["offset"]; } } // TODO: Figure out a better way to get max possible VPD size. auto maxVPDSize = std::min(std::filesystem::file_size(file), static_cast(65504)); Binary vpdVector; vpdVector.resize(maxVPDSize); ifstream vpdFile; vpdFile.open(file, ios::binary); vpdFile.seekg(offset, ios_base::cur); vpdFile.read(reinterpret_cast(&vpdVector[0]), maxVPDSize); vpdVector.resize(vpdFile.gcount()); resetEEPROMPointer(js, file, vpdFile); return vpdVector; } /** 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; } 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", ""); cout << "Binding device " << bind << endl; string bindCmd = string("echo \"") + bind + string("\" > /sys/bus/i2c/drivers/at24/bind"); cout << bindCmd << endl; executeCmd(bindCmd); // Check if device showed up (test for file) if (!fs::exists(file)) { cerr << "EEPROM " << file << " does not exist. Take failure action" << endl; // If not, then take failure postAction executePostFailAction(json, file); } } else { // missing required informations cerr << "VPD inventory JSON missing basic informations of preAction " "for this FRU : [" << file << "]. Executing executePostFailAction." << endl; // Take failure postAction executePostFailAction(json, file); return; } } } /** * @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 false for Enabled) * * @param[in] object - The inventory D-Bus obejct without the inventory prefix. * @param[inout] 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", false); 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 is owned by // some other component and not by vpd. if (!itemEEPROM.value("synthesized", false)) { 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().find("Inventory.Item.") != string::npos) { 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)); } } } 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"}}; 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); 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 API to call VPD manager to write VPD to EEPROM. * @param[in] Object path. * @param[in] record to be updated. * @param[in] keyword to be updated. * @param[in] keyword data to be updated */ void updateHardware(const string& objectName, const string& recName, const string& kwdName, const Binary& data) { try { auto bus = sdbusplus::bus::new_default(); auto properties = bus.new_method_call(BUSNAME, OBJPATH, IFACE, "WriteKeyword"); properties.append( static_cast(objectName)); properties.append(recName); properties.append(kwdName); properties.append(data); bus.call(properties); } catch (const sdbusplus::exception::exception& e) { std::string what = "VPDManager WriteKeyword api failed for inventory path " + objectName; what += " record " + recName; what += " keyword " + kwdName; what += " with bus error = " + std::string(e.what()); // map to hold additional data in case of logging pel PelAdditionalData additionalData{}; additionalData.emplace("CALLOUT_INVENTORY_PATH", objectName); additionalData.emplace("DESCRIPTION", what); createPEL(additionalData, PelSeverity::WARNING, errIntfForBusFailure); } } /** * @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. */ 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& keyword : kwdListForRecord) { 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); if (busValue.find_first_not_of(' ') != string::npos) { if (kwdValue.find_first_not_of(' ') == string::npos) { // 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)); } } } } } } } /** * @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 */ void restoreSystemVPD(Parsed& vpdMap, const string& objectPath) { 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& keyword : kwdListForRecord) { 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); if (busValue.find_first_not_of(' ') != string::npos) { if (kwdValue.find_first_not_of(' ') != string::npos) { // both the data are present, check for mismatch if (busValue != kwdValue) { string errMsg = "VPD data mismatch on cache " "and hardware for record: "; errMsg += (*it).first; errMsg += " and keyword: "; errMsg += keyword; // data mismatch PelAdditionalData additionalData; additionalData.emplace("CALLOUT_INVENTORY_PATH", objectPath); additionalData.emplace("DESCRIPTION", errMsg); createPEL(additionalData, PelSeverity::WARNING, errIntfForInvalidVPD); } } else { // implies hardware data is blank // update the map Binary busData(busValue.begin(), busValue.end()); // update the map as well, so that cache data is not // updated as blank while populating VPD map on Dbus // in populateDBus Api kwdValue = busValue; } } else if (kwdValue.find_first_not_of(' ') == string::npos) { string errMsg = "VPD is blank on both cache and " "hardware for record: "; errMsg += (*it).first; errMsg += " and keyword: "; errMsg += keyword; errMsg += ". SSR need to update hardware VPD."; // both the data are blanks, log PEL PelAdditionalData additionalData; additionalData.emplace("CALLOUT_INVENTORY_PATH", objectPath); additionalData.emplace("DESCRIPTION", errMsg); // log PEL TODO: Block IPL createPEL(additionalData, PelSeverity::ERROR, errIntfForBlankSystemVPD); continue; } } } } } } /** * @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 begining const regex pattern("^0+(?!$)"); for (auto& i : i2cReg) { i = regex_replace(i, pattern, ""); } 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 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) { std::vector interfaces = {motherBoardInterface}; // call mapper to check for object path creation MapperResponse subTree = getObjectSubtreeForInterfaces(pimPath, 0, interfaces); string mboardPath = js["frus"][filePath].at(0).value("inventoryPath", ""); // 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); } else { log("No object path found"); } } 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); } } } } 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(); } 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 donot 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))) { 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{}; // 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 = {}; // severity for PEL PelSeverity pelSeverity = PelSeverity::WARNING; try { App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store " "in DBUS"}; app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") ->required(); CLI11_PARSE(app, argc, argv); // PEL severity should be ERROR in case of any system VPD failure if (file == systemVpdFilePath) { pelSeverity = PelSeverity::ERROR; } 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); if ((js["frus"].find(file) != js["frus"].end()) && (file == systemVpdFilePath)) { // We need manager service active to process restoring of // system VPD on hardware. So in case any system restore is // required, update hardware in the second trigger of parser // code for system vpd file path. std::vector interfaces{motherBoardInterface}; // call mapper to check for object path creation MapperResponse subTree = getObjectSubtreeForInterfaces(pimPath, 0, interfaces); string mboardPath = js["frus"][file].at(0).value("inventoryPath", ""); // Attempt system VPD restore if we have a motherboard // object in the inventory. if ((subTree.size() != 0) && (subTree.find(pimPath + mboardPath) != subTree.end())) { vpdVector = getVpdDataInVector(js, file); ParserInterface* parser = ParserFactory::getParser(vpdVector); variant parseResult; parseResult = parser->parse(); if (auto pVal = get_if(&parseResult)) { // map to hold all the keywords whose value is blank and // needs to be updated at standby. vector blankSystemVpdProperties{}; getListOfBlankSystemVpd(pVal->getVpdMap(), mboardPath, blankSystemVpdProperties); // if system VPD restore is required, update the // EEPROM for (const auto& item : blankSystemVpdProperties) { updateHardware(get<0>(item), get<1>(item), get<2>(item), get<3>(item)); } } else { std::cout << "Not a valid format to restore system VPD" << std::endl; } // release the parser object ParserFactory::freeParser(parser); } else { log("No object path found"); } return 0; } } if (file.empty()) { cerr << "The EEPROM path <" << file << "> is not valid."; return 0; } if (js["frus"].find(file) == js["frus"].end()) { return 0; } if (!fs::exists(file)) { cout << "Device path: " << file << " does not exist. Spurious udev event? Exiting." << endl; return 0; } 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()) { cout << "This VPD cannot be read when power is ON" << endl; return 0; } } // Check if this VPD should be recollected at all if (!needsRecollection(js, file)) { cout << "Skip VPD recollection for: " << file << endl; return 0; } try { vpdVector = getVpdDataInVector(js, file); ParserInterface* parser = ParserFactory::getParser(vpdVector); variant parseResult; parseResult = parser->parse(); if (auto pVal = get_if(&parseResult)) { populateDbus(pVal->getVpdMap(), js, file); } else if (auto pVal = get_if(&parseResult)) { populateDbus(*pVal, js, file); } // release the parser object ParserFactory::freeParser(parser); } catch (const exception& e) { executePostFailAction(js, file); throw; } } catch (const VpdJsonException& ex) { additionalData.emplace("JSON_PATH", ex.getJsonPath()); additionalData.emplace("DESCRIPTION", ex.what()); createPEL(additionalData, pelSeverity, errIntfForJsonFailure); 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); dumpBadVpd(file, vpdVector); cerr << ex.what() << "\n"; rc = -1; } catch (const VpdDataException& ex) { if (isThisPcieOnPass1planar(js, file)) { cout << "Pcie_device [" << file << "]'s VPD is not valid on PASS1 planar.Ignoring.\n"; rc = 0; } else if (!(isPresent(js, file).value_or(true))) { 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); rc = -1; } } catch (const exception& e) { dumpBadVpd(file, vpdVector); cerr << e.what() << "\n"; rc = -1; } return rc; }