blob: ae4d1be6866a2f3374245dfbe6709daca68832b2 [file] [log] [blame]
#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 <assert.h>
#include <ctype.h>
#include <CLI/CLI.hpp>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <cstdarg>
#include <exception>
#include <filesystem>
#include <fstream>
#include <gpiod.hpp>
#include <iostream>
#include <iterator>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <regex>
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<std::string, std::vector<std::string>>
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<string> val;
result.read(val);
if (auto pVal = get_if<string>(&val))
{
powerState = *pVal;
}
}
cout << "Power state is: " << powerState << endl;
return powerState;
}
/**
* @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 <typename T>
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<T, KeywordVpdMap>::value)
{
if (get_if<Binary>(&kwVal.second))
{
Binary vec(get_if<Binary>(&kwVal.second)->begin(),
get_if<Binary>(&kwVal.second)->end());
prop.emplace(move(kw), move(vec));
}
else
{
if (kw == "MemorySizeInKB")
{
inventory::PropertyMap memProp;
auto memVal = get_if<size_t>(&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 <typename T>
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<bool>());
}
else if (itr.value().is_string())
{
if (busProp == "LocationCode" && inf == IBM_LOCATION_CODE_INF)
{
std::string prop;
if constexpr (is_same<T, Parsed>::value)
{
// TODO deprecate the com.ibm interface later
prop = expandLocationCode(itr.value().get<string>(),
vpdMap, isSystemVpd);
}
else if constexpr (is_same<T, KeywordVpdMap>::value)
{
// Send empty Parsed object to expandLocationCode api.
prop = expandLocationCode(itr.value().get<string>(),
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<string>());
}
}
else if (itr.value().is_array())
{
try
{
props.emplace(busProp, itr.value().get<Binary>());
}
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<T, Parsed>::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<T, KeywordVpdMap>::value)
{
if (!kw.empty() && vpdMap.count(kw))
{
auto kwValue = get_if<Binary>(&vpdMap.at(kw));
auto uintValue = get_if<size_t>(&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<size_t>());
}
}
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].find("extraInterfaces") !=
js["frus"][file].end()))
{
if (js["frus"][file]["extraInterfaces"].find(
"xyz.openbmc_project.Inventory.Item.PCIeDevice") !=
js["frus"][file]["extraInterfaces"].end())
{
isThisPCIeDev = true;
}
}
}
if (isThisPCIeDev)
{
// Collect SystemType to know if it is PASS1 planar.
auto bus = sdbusplus::bus::new_default();
auto properties = bus.new_method_call(
INVENTORY_MANAGER_SERVICE,
"/xyz/openbmc_project/inventory/system/chassis/motherboard",
"org.freedesktop.DBus.Properties", "Get");
properties.append("com.ibm.ipzvpd.VINI");
properties.append("HW");
auto result = bus.call(properties);
inventory::Value val;
result.read(val);
if (auto pVal = get_if<Binary>(&val))
{
auto hwVersion = *pVal;
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<uintmax_t>(65504));
Binary vpdVector;
vpdVector.resize(maxVPDSize);
ifstream vpdFile;
vpdFile.open(file, ios::binary);
vpdFile.seekg(offset, ios_base::cur);
vpdFile.read(reinterpret_cast<char*>(&vpdVector[0]), maxVPDSize);
vpdVector.resize(vpdFile.gcount());
resetEEPROMPointer(js, file, vpdFile);
return vpdVector;
}
/** This API will be called at the end of VPD collection to perform any post
* actions.
*
* @param[in] json - json object
* @param[in] file - eeprom file path
*/
static void postFailAction(const nlohmann::json& json, const string& file)
{
if ((json["frus"][file].at(0)).find("postActionFail") ==
json["frus"][file].at(0).end())
{
return;
}
uint8_t pinValue = 0;
string pinName;
for (const auto& postAction :
(json["frus"][file].at(0))["postActionFail"].items())
{
if (postAction.key() == "pin")
{
pinName = postAction.value();
}
else if (postAction.key() == "value")
{
// Get the value to set
pinValue = postAction.value();
}
}
cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl;
try
{
gpiod::line outputLine = gpiod::find_line(pinName);
if (!outputLine)
{
cout << "Couldn't find output line:" << pinName
<< " on GPIO. Skipping...\n";
return;
}
outputLine.request(
{"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0},
pinValue);
}
catch (const system_error&)
{
cerr << "Failed to set post-action GPIO" << endl;
}
}
/**
* @brief This sets the appropriate value to presence GPIO based on device
* attached or not on expander GPIO, which enables that FRU's VPD 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("presence") !=
json["frus"][file].at(0).end())
{
if (((json["frus"][file].at(0)["presence"]).find("pin") !=
json["frus"][file].at(0)["presence"].end()) &&
((json["frus"][file].at(0)["presence"]).find("value") !=
json["frus"][file].at(0)["presence"].end()))
{
string presPinName = json["frus"][file].at(0)["presence"]["pin"];
Byte presPinValue = json["frus"][file].at(0)["presence"]["value"];
try
{
gpiod::line presenceLine = gpiod::find_line(presPinName);
if (!presenceLine)
{
cerr << "couldn't find presence line:" << presPinName
<< "\n";
return;
}
presenceLine.request({"Read the presence line",
gpiod::line_request::DIRECTION_INPUT, 0});
Byte gpioData = presenceLine.get_value();
if (gpioData != presPinValue)
{
return;
}
}
catch (system_error&)
{
cerr << "Failed to get the presence GPIO for - " << presPinName
<< endl;
return;
}
}
}
if ((json["frus"][file].at(0)).find("preAction") !=
json["frus"][file].at(0).end())
{
if (((json["frus"][file].at(0)["preAction"]).find("pin") !=
json["frus"][file].at(0)["preAction"].end()) &&
((json["frus"][file].at(0)["preAction"]).find("value") !=
json["frus"][file].at(0)["preAction"].end()))
{
string pinName = json["frus"][file].at(0)["preAction"]["pin"];
// Get the value to set
Byte pinValue = json["frus"][file].at(0)["preAction"]["value"];
cout << "Setting GPIO: " << pinName << " to " << (int)pinValue
<< endl;
try
{
gpiod::line outputLine = gpiod::find_line(pinName);
if (!outputLine)
{
cout << "Couldn't find output line:" << pinName
<< " on GPIO. Skipping...\n";
return;
}
outputLine.request({"FRU pre-action",
::gpiod::line_request::DIRECTION_OUTPUT, 0},
pinValue);
}
catch (system_error&)
{
cerr << "Failed to set pre-action for GPIO - " << pinName
<< endl;
return;
}
}
}
// Now bind the device
if (json["frus"][file].at(0).find("devAddress") !=
json["frus"][file].at(0).end())
{
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))
{
cout << "EEPROM " << file << " does not exist. Take failure action"
<< endl;
// If not, then take failure postAction
postFailAction(json, file);
}
}
/**
* @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 <typename T>
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;
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<T, Parsed>::value)
{
for (auto& lC : eI.value().items())
{
auto propVal = expandLocationCode(
lC.value().get<string>(), 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<string>());
}
}
// 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<level::INFO>("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"}};
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<string> 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<sdbusplus::message::object_path>(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<RestoredEeproms>& 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<string> 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 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 <typename T>
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<T, Parsed>::value)
{
ccinFromVpd = getKwVal(vpdMap, "VINI", "CC");
transform(ccinFromVpd.begin(), ccinFromVpd.end(), ccinFromVpd.begin(),
::toupper);
if (isSystemVpd)
{
std::vector<std::string> 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<level::ERR>("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);
}
}
}
}
if (isSystemVpd)
{
string systemJsonName{};
if constexpr (is_same<T, Parsed>::value)
{
// pick the right system json
systemJsonName = getSystemsJson(vpdMap);
}
fs::path target = systemJsonName;
fs::path link = 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<string> 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<T, Parsed>::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<T, KeywordVpdMap>::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<T, Parsed>::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);
}
inventory::PropertyMap presProp;
presProp.emplace("Present", true);
insertOrMerge(interfaces, invItemIntf, move(presProp));
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<T, Parsed>::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<std::string> 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<KeywordVpdMap, Store> parseResult;
parseResult = parser->parse();
if (auto pVal = get_if<Store>(&parseResult))
{
// map to hold all the keywords whose value is blank and
// needs to be updated at standby.
vector<RestoredEeproms> 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<level::ERR>("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
// 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))
{
if ("xyz.openbmc_project.State.Chassis.PowerState.On" ==
getPowerState())
{
cout << "This VPD cannot be read when power is ON" << endl;
return 0;
}
}
try
{
vpdVector = getVpdDataInVector(js, file);
ParserInterface* parser = ParserFactory::getParser(vpdVector);
variant<KeywordVpdMap, Store> parseResult;
parseResult = parser->parse();
if (auto pVal = get_if<Store>(&parseResult))
{
populateDbus(pVal->getVpdMap(), js, file);
}
else if (auto pVal = get_if<KeywordVpdMap>(&parseResult))
{
populateDbus(*pVal, js, file);
}
// release the parser object
ParserFactory::freeParser(parser);
}
catch (const exception& e)
{
postFailAction(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
{
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;
}