#include "config.h" #include "defines.hpp" #include "ibm_vpd_type_check.hpp" #include "keyword_vpd_parser.hpp" #include "parser.hpp" #include "utils.hpp" #include #include #include #include #include #include #include using namespace std; using namespace openpower::vpd; using namespace CLI; using namespace vpd::keyword::parser; using namespace vpdFormat; /** @brief Encodes a keyword for D-Bus. */ static string encodeKeyword(const string& kw, const string& encoding) { if (encoding == "MAC") { string res{}; size_t first = kw[0]; res += toHex(first >> 4); res += toHex(first & 0x0f); for (size_t i = 1; i < kw.size(); ++i) { res += ":"; res += toHex(kw[i] >> 4); res += toHex(kw[i] & 0x0f); } return res; } else if (encoding == "DATE") { // Date, represent as // -- : string res{}; static constexpr uint8_t skipPrefix = 3; auto strItr = kw.begin(); advance(strItr, skipPrefix); for_each(strItr, kw.end(), [&res](size_t c) { res += c; }); res.insert(BD_YEAR_END, 1, '-'); res.insert(BD_MONTH_END, 1, '-'); res.insert(BD_DAY_END, 1, ' '); res.insert(BD_HOUR_END, 1, ':'); return res; } else // default to string encoding { return string(kw.begin(), kw.end()); } } /** @brief Reads a property from the inventory manager given object path, * intreface and property. */ static auto readBusProperty(const string& obj, const string& inf, const string& prop) { string propVal{}; static constexpr auto OBJ_PREFIX = "/xyz/openbmc_project/inventory"; string object = OBJ_PREFIX + obj; auto bus = sdbusplus::bus::new_default(); auto properties = bus.new_method_call( "xyz.openbmc_project.Inventory.Manager", object.c_str(), "org.freedesktop.DBus.Properties", "Get"); properties.append(inf); properties.append(prop); auto result = bus.call(properties); if (!result.is_method_error()) { variant val; result.read(val); if (auto pVal = get_if(&val)) { propVal.assign(reinterpret_cast(pVal->data()), pVal->size()); } } return propVal; } /** * @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 ND1 can be placed in the JSON expanded.replace(idx, 3, fc.substr(0, 4) + ".ND1." + 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 (std::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) { std::vector vec(kwVal.second.begin(), kwVal.second.end()); auto kw = kwVal.first; if (kw[0] == '#') { kw = std::string("PD_") + kw[1]; } 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 constexpr (std::is_same::value) { if (busProp == "LocationCode" && inf == "com.ibm.ipzvpd.Location") { auto prop = expandLocationCode( itr.value().get(), vpdMap, isSystemVpd); props.emplace(busProp, prop); } else { props.emplace(busProp, itr.value().get()); } } else { props.emplace(busProp, itr.value().get()); } } 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 (std::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 (std::is_same::value) { if (!kw.empty() && vpdMap.count(kw)) { auto prop = string(vpdMap.at(kw).begin(), vpdMap.at(kw).end()); auto encoded = encodeKeyword(prop, encoding); props.emplace(busProp, encoded); } } } } interfaces.emplace(inf, move(props)); } } /** * @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(const T& vpdMap, nlohmann::json& js, const string& filePath, const string& preIntrStr) { inventory::InterfaceMap interfaces; inventory::ObjectMap objects; inventory::PropertyMap prop; for (const auto& item : js["frus"][filePath]) { const auto& objectPath = item["inventoryPath"]; sdbusplus::message::object_path object(objectPath); auto isSystemVpd = item.value("isSystemVpd", false); // 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 (std::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, preIntrStr + record.first, interfaces); } } else if constexpr (std::is_same::value) { populateFruSpecificInterfaces(vpdMap, preIntrStr, interfaces); } if (js.find("commonInterfaces") != js.end()) { populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, isSystemVpd); } } // 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); } objects.emplace(move(object), move(interfaces)); } // Notify PIM inventory::callPIM(move(objects)); } int main(int argc, char** argv) { int rc = 0; try { using json = nlohmann::json; App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store " "in DBUS"}; string file{}; app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") ->required() ->check(ExistingFile); CLI11_PARSE(app, argc, argv); // Make sure that the file path we get is for a supported EEPROM ifstream inventoryJson(INVENTORY_JSON); auto js = json::parse(inventoryJson); if ((js.find("frus") == js.end()) || (js["frus"].find(file) == js["frus"].end())) { cout << "Device path not in JSON, ignoring" << std::endl; return 0; } // Open the file in binary mode ifstream vpdFile(file, ios::binary); // Read the content of the binary file into a vector Binary vpdVector((istreambuf_iterator(vpdFile)), istreambuf_iterator()); vpdType type = vpdTypeCheck(vpdVector); switch (type) { case IPZ_VPD: { // Invoking IPZ Vpd Parser auto vpdStore = parse(move(vpdVector)); const Parsed& vpdMap = vpdStore.getVpdMap(); string preIntrStr = "com.ibm.ipzvpd."; // Write it to the inventory populateDbus(vpdMap, js, file, preIntrStr); } break; case KEYWORD_VPD: { // Creating Keyword Vpd Parser Object KeywordVpdParser parserObj(move(vpdVector)); // Invoking KW Vpd Parser const auto& kwValMap = parserObj.parseKwVpd(); string preIntrStr = "com.ibm.kwvpd.KWVPD"; populateDbus(kwValMap, js, file, preIntrStr); } break; default: throw std::runtime_error("Invalid VPD format"); } } catch (exception& e) { cerr << e.what() << "\n"; rc = -1; } return rc; }