blob: 32531056e17a534463c02b20d932cfaedc48e3ac [file] [log] [blame]
#pragma once
#include "config.h"
#include "constants.hpp"
#include "exceptions.hpp"
#include "logger.hpp"
#include "types.hpp"
#include <nlohmann/json.hpp>
#include <utility/common_utility.hpp>
#include <utility/dbus_utility.hpp>
#include <filesystem>
#include <fstream>
#include <regex>
namespace vpd
{
namespace vpdSpecificUtility
{
/**
* @brief API to generate file name for bad VPD.
*
* For i2c eeproms - the pattern of the vpd-name will be
* i2c-<bus-number>-<eeprom-address>.
* For spi eeproms - the pattern of the vpd-name will be spi-<spi-number>.
*
* @param[in] vpdFilePath - file path of the vpd.
* @return Generated file name.
*/
inline std::string generateBadVPDFileName(const std::string& vpdFilePath)
{
std::string badVpdFileName = BAD_VPD_DIR;
if (vpdFilePath.find("i2c") != std::string::npos)
{
badVpdFileName += "i2c-";
std::regex i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
std::smatch match;
if (std::regex_search(vpdFilePath, match, i2cPattern))
{
badVpdFileName += match.str(2);
}
}
else if (vpdFilePath.find("spi") != std::string::npos)
{
std::regex spiPattern("((spi)[0-9]+)(.0)");
std::smatch match;
if (std::regex_search(vpdFilePath, match, spiPattern))
{
badVpdFileName += match.str(1);
}
}
return badVpdFileName;
}
/**
* @brief API which dumps the broken/bad vpd in a directory.
* When the vpd is bad, this API places the bad vpd file inside
* "/tmp/bad-vpd" in BMC, in order to collect bad VPD data as a part of user
* initiated BMC dump.
*
* Note: Throws exception in case of any failure.
*
* @param[in] vpdFilePath - vpd file path
* @param[in] vpdVector - vpd vector
*/
inline void dumpBadVpd(const std::string& vpdFilePath,
const types::BinaryVector& vpdVector)
{
std::filesystem::create_directory(BAD_VPD_DIR);
auto badVpdPath = generateBadVPDFileName(vpdFilePath);
if (std::filesystem::exists(badVpdPath))
{
std::error_code ec;
std::filesystem::remove(badVpdPath, ec);
if (ec) // error code
{
std::string error = "Error removing the existing broken vpd in ";
error += badVpdPath;
error += ". Error code : ";
error += ec.value();
error += ". Error message : ";
error += ec.message();
throw std::runtime_error(error);
}
}
std::ofstream badVpdFileStream(badVpdPath, std::ofstream::binary);
if (badVpdFileStream.is_open())
{
throw std::runtime_error(
"Failed to open bad vpd file path in /tmp/bad-vpd. "
"Unable to dump the broken/bad vpd file.");
}
badVpdFileStream.write(reinterpret_cast<const char*>(vpdVector.data()),
vpdVector.size());
}
/**
* @brief An API to read value of a keyword.
*
* Note: Throws exception. Caller needs to handle.
*
* @param[in] kwdValueMap - A map having Kwd value pair.
* @param[in] kwd - keyword name.
* @param[out] kwdValue - Value of the keyword read from map.
*/
inline void getKwVal(const types::IPZKwdValueMap& kwdValueMap,
const std::string& kwd, std::string& kwdValue)
{
if (kwd.empty())
{
logging::logMessage("Invalid parameters");
throw std::runtime_error("Invalid parameters");
}
auto itrToKwd = kwdValueMap.find(kwd);
if (itrToKwd != kwdValueMap.end())
{
kwdValue = itrToKwd->second;
return;
}
throw std::runtime_error("Keyword not found");
}
/**
* @brief An API to process encoding of a keyword.
*
* @param[in] keyword - Keyword to be processed.
* @param[in] encoding - Type of encoding.
* @return Value after being processed for encoded type.
*/
inline std::string encodeKeyword(const std::string& keyword,
const std::string& encoding)
{
// Default value is keyword value
std::string result(keyword.begin(), keyword.end());
if (encoding == "MAC")
{
result.clear();
size_t firstByte = keyword[0];
result += commonUtility::toHex(firstByte >> 4);
result += commonUtility::toHex(firstByte & 0x0f);
for (size_t i = 1; i < keyword.size(); ++i)
{
result += ":";
result += commonUtility::toHex(keyword[i] >> 4);
result += commonUtility::toHex(keyword[i] & 0x0f);
}
}
else if (encoding == "DATE")
{
// Date, represent as
// <year>-<month>-<day> <hour>:<min>
result.clear();
static constexpr uint8_t skipPrefix = 3;
auto strItr = keyword.begin();
advance(strItr, skipPrefix);
for_each(strItr, keyword.end(), [&result](size_t c) { result += c; });
result.insert(constants::BD_YEAR_END, 1, '-');
result.insert(constants::BD_MONTH_END, 1, '-');
result.insert(constants::BD_DAY_END, 1, ' ');
result.insert(constants::BD_HOUR_END, 1, ':');
}
return result;
}
/**
* @brief Helper function to insert or merge in map.
*
* This method checks in an interface if the given interface exists. If the
* interface key already exists, property map is inserted corresponding to it.
* If the key does'nt exist then given interface and property map pair is newly
* created. If the property present in propertymap already exist in the
* InterfaceMap, then the new property value is ignored.
*
* @param[in,out] map - Interface map.
* @param[in] interface - Interface to be processed.
* @param[in] propertyMap - new property map that needs to be emplaced.
*/
inline void insertOrMerge(types::InterfaceMap& map,
const std::string& interface,
types::PropertyMap&& propertyMap)
{
if (map.find(interface) != map.end())
{
try
{
auto& prop = map.at(interface);
std::for_each(propertyMap.begin(), propertyMap.end(),
[&prop](auto l_keyValue) {
prop[l_keyValue.first] = l_keyValue.second;
});
}
catch (const std::exception& l_ex)
{
// ToDo:: Log PEL
logging::logMessage(
"Inserting properties into interface[" + interface +
"] map is failed, reason: " + std::string(l_ex.what()));
}
}
else
{
map.emplace(interface, propertyMap);
}
}
/**
* @brief API to expand unpanded location code.
*
* Note: The API handles all the exception internally, in case of any error
* unexpanded location code will be returned as it is.
*
* @param[in] unexpandedLocationCode - Unexpanded location code.
* @param[in] parsedVpdMap - Parsed VPD map.
* @return Expanded location code. In case of any error, unexpanded is returned
* as it is.
*/
inline std::string getExpandedLocationCode(
const std::string& unexpandedLocationCode,
const types::VPDMapVariant& parsedVpdMap)
{
auto expanded{unexpandedLocationCode};
try
{
// Expanded location code is formed by combining two keywords
// depending on type in unexpanded one. Second one is always "SE".
std::string kwd1, kwd2{constants::kwdSE};
// interface to search for required keywords;
std::string kwdInterface;
// record which holds the required keywords.
std::string recordName;
auto pos = unexpandedLocationCode.find("fcs");
if (pos != std::string::npos)
{
kwd1 = constants::kwdFC;
kwdInterface = constants::vcenInf;
recordName = constants::recVCEN;
}
else
{
pos = unexpandedLocationCode.find("mts");
if (pos != std::string::npos)
{
kwd1 = constants::kwdTM;
kwdInterface = constants::vsysInf;
recordName = constants::recVSYS;
}
else
{
throw std::runtime_error(
"Error detecting type of unexpanded location code.");
}
}
std::string firstKwdValue, secondKwdValue;
if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap);
ipzVpdMap && (*ipzVpdMap).find(recordName) != (*ipzVpdMap).end())
{
auto itrToVCEN = (*ipzVpdMap).find(recordName);
// The exceptions will be cautght at end.
getKwVal(itrToVCEN->second, kwd1, firstKwdValue);
getKwVal(itrToVCEN->second, kwd2, secondKwdValue);
}
else
{
std::array<const char*, 1> interfaceList = {kwdInterface.c_str()};
types::MapperGetObject mapperRetValue = dbusUtility::getObjectMap(
std::string(constants::systemVpdInvPath), interfaceList);
if (mapperRetValue.empty())
{
throw std::runtime_error("Mapper failed to get service");
}
const std::string& serviceName = std::get<0>(mapperRetValue.at(0));
auto retVal = dbusUtility::readDbusProperty(
serviceName, std::string(constants::systemVpdInvPath),
kwdInterface, kwd1);
if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
{
firstKwdValue.assign(
reinterpret_cast<const char*>(kwdVal->data()),
kwdVal->size());
}
else
{
throw std::runtime_error(
"Failed to read value of " + kwd1 + " from Bus");
}
retVal = dbusUtility::readDbusProperty(
serviceName, std::string(constants::systemVpdInvPath),
kwdInterface, kwd2);
if (auto kwdVal = std::get_if<types::BinaryVector>(&retVal))
{
secondKwdValue.assign(
reinterpret_cast<const char*>(kwdVal->data()),
kwdVal->size());
}
else
{
throw std::runtime_error(
"Failed to read value of " + kwd2 + " from Bus");
}
}
if (unexpandedLocationCode.find("fcs") != std::string::npos)
{
// TODO: See if ND0 can be placed in the JSON
expanded.replace(
pos, 3, firstKwdValue.substr(0, 4) + ".ND0." + secondKwdValue);
}
else
{
replace(firstKwdValue.begin(), firstKwdValue.end(), '-', '.');
expanded.replace(pos, 3, firstKwdValue + "." + secondKwdValue);
}
}
catch (const std::exception& ex)
{
logging::logMessage("Failed to expand location code with exception: " +
std::string(ex.what()));
}
return expanded;
}
/**
* @brief An API to get VPD in a vector.
*
* The vector is required by the respective parser to fill the VPD map.
* Note: API throws exception in case of failure. Caller needs to handle.
*
* @param[in] vpdFilePath - EEPROM path of the FRU.
* @param[out] vpdVector - VPD in vector form.
* @param[in] vpdStartOffset - Offset of VPD data in EEPROM.
*/
inline void getVpdDataInVector(const std::string& vpdFilePath,
types::BinaryVector& vpdVector,
size_t& vpdStartOffset)
{
try
{
std::fstream vpdFileStream;
vpdFileStream.exceptions(
std::ifstream::badbit | std::ifstream::failbit);
vpdFileStream.open(vpdFilePath, std::ios::in | std::ios::binary);
auto vpdSizeToRead = std::min(std::filesystem::file_size(vpdFilePath),
static_cast<uintmax_t>(65504));
vpdVector.resize(vpdSizeToRead);
vpdFileStream.seekg(vpdStartOffset, std::ios_base::beg);
vpdFileStream.read(reinterpret_cast<char*>(&vpdVector[0]),
vpdSizeToRead);
vpdVector.resize(vpdFileStream.gcount());
vpdFileStream.clear(std::ios_base::eofbit);
}
catch (const std::ifstream::failure& fail)
{
std::cerr << "Exception in file handling [" << vpdFilePath
<< "] error : " << fail.what();
throw;
}
}
/**
* @brief An API to get D-bus representation of given VPD keyword.
*
* @param[in] i_keywordName - VPD keyword name.
*
* @return D-bus representation of given keyword.
*/
inline std::string getDbusPropNameForGivenKw(const std::string& i_keywordName)
{
// Check for "#" prefixed VPD keyword.
if ((i_keywordName.size() == vpd::constants::TWO_BYTES) &&
(i_keywordName.at(0) == constants::POUND_KW))
{
// D-bus doesn't support "#". Replace "#" with "PD_" for those "#"
// prefixed keywords.
return (std::string(constants::POUND_KW_PREFIX) +
i_keywordName.substr(1));
}
// Return the keyword name back, if D-bus representation is same as the VPD
// keyword name.
return i_keywordName;
}
/**
* @brief API to find CCIN in parsed VPD map.
*
* Few FRUs need some special handling. To identify those FRUs CCIN are used.
* The API will check from parsed VPD map if the FRU is the one with desired
* CCIN.
*
* @throw std::runtime_error
* @throw DataException
*
* @param[in] i_JsonObject - Any JSON which contains CCIN tag to match.
* @param[in] i_parsedVpdMap - Parsed VPD map.
* @return True if found, false otherwise.
*/
inline bool findCcinInVpd(const nlohmann::json& i_JsonObject,
const types::VPDMapVariant& i_parsedVpdMap)
{
if (i_JsonObject.empty())
{
throw std::runtime_error("Json object is empty. Can't find CCIN");
}
if (auto l_ipzVPDMap = std::get_if<types::IPZVpdMap>(&i_parsedVpdMap))
{
auto l_itrToRec = (*l_ipzVPDMap).find("VINI");
if (l_itrToRec == (*l_ipzVPDMap).end())
{
throw DataException(
"VINI record not found in parsed VPD. Can't find CCIN");
}
std::string l_ccinFromVpd;
vpdSpecificUtility::getKwVal(l_itrToRec->second, "CC", l_ccinFromVpd);
if (l_ccinFromVpd.empty())
{
throw DataException("Empty CCIN value in VPD map. Can't find CCIN");
}
transform(l_ccinFromVpd.begin(), l_ccinFromVpd.end(),
l_ccinFromVpd.begin(), ::toupper);
for (std::string l_ccinValue : i_JsonObject["ccin"])
{
transform(l_ccinValue.begin(), l_ccinValue.end(),
l_ccinValue.begin(), ::toupper);
if (l_ccinValue.compare(l_ccinFromVpd) ==
constants::STR_CMP_SUCCESS)
{
// CCIN found
return true;
}
}
logging::logMessage("No match found for CCIN");
return false;
}
logging::logMessage("VPD type not supported. Can't find CCIN");
return false;
}
/**
* @brief API to reset data of a FRU populated under PIM.
*
* This API resets the data for particular interfaces of a FRU under PIM.
*
* @param[in] i_objectPath - DBus object path of the FRU.
* @param[in] io_interfaceMap - Interface and its properties map.
*/
inline void resetDataUnderPIM(const std::string& i_objectPath,
types::InterfaceMap& io_interfaceMap)
{
try
{
std::array<const char*, 0> l_interfaces;
const types::MapperGetObject& l_getObjectMap =
dbusUtility::getObjectMap(i_objectPath, l_interfaces);
const std::vector<std::string>& l_vpdRelatedInterfaces{
constants::operationalStatusInf, constants::inventoryItemInf,
constants::assetInf};
for (const auto& [l_service, l_interfaceList] : l_getObjectMap)
{
if (l_service.compare(constants::pimServiceName) !=
constants::STR_CMP_SUCCESS)
{
continue;
}
for (const auto& l_interface : l_interfaceList)
{
if ((l_interface.find(constants::ipzVpdInf) !=
std::string::npos) ||
((std::find(l_vpdRelatedInterfaces.begin(),
l_vpdRelatedInterfaces.end(), l_interface)) !=
l_vpdRelatedInterfaces.end()))
{
const types::PropertyMap& l_propertyValueMap =
dbusUtility::getPropertyMap(l_service, i_objectPath,
l_interface);
types::PropertyMap l_propertyMap;
for (const auto& l_aProperty : l_propertyValueMap)
{
const std::string& l_propertyName = l_aProperty.first;
const auto& l_propertyValue = l_aProperty.second;
if (std::holds_alternative<types::BinaryVector>(
l_propertyValue))
{
l_propertyMap.emplace(l_propertyName,
types::BinaryVector{});
}
else if (std::holds_alternative<std::string>(
l_propertyValue))
{
l_propertyMap.emplace(l_propertyName,
std::string{});
}
else if (std::holds_alternative<bool>(l_propertyValue))
{
// ToDo -- Update the functional status property
// to true.
if (l_propertyName.compare("Present") ==
constants::STR_CMP_SUCCESS)
{
l_propertyMap.emplace(l_propertyName, false);
}
}
}
io_interfaceMap.emplace(l_interface,
std::move(l_propertyMap));
}
}
}
}
catch (const std::exception& l_ex)
{
logging::logMessage("Failed to remove VPD for FRU: " + i_objectPath +
" with error: " + std::string(l_ex.what()));
}
}
/**
* @brief API to detect pass1 planar type.
*
* Based on HW version and IM keyword, This API detects is it is a pass1 planar
* or not.
*
* @return True if pass 1 planar, false otherwise.
*/
inline bool isPass1Planar()
{
auto l_retVal = dbusUtility::readDbusProperty(
constants::pimServiceName, constants::systemVpdInvPath,
constants::viniInf, constants::kwdHW);
auto l_hwVer = std::get_if<types::BinaryVector>(&l_retVal);
l_retVal = dbusUtility::readDbusProperty(
constants::pimServiceName, constants::systemInvPath, constants::vsbpInf,
constants::kwdIM);
auto l_imValue = std::get_if<types::BinaryVector>(&l_retVal);
if (l_hwVer && l_imValue)
{
types::BinaryVector everest{80, 00, 48, 00};
types::BinaryVector fuji{96, 00, 32, 00};
if (((*l_imValue) == everest) || ((*l_imValue) == fuji))
{
if ((*l_hwVer).at(1) < constants::VALUE_21)
{
return true;
}
}
else if ((*l_hwVer).at(1) < constants::VALUE_2)
{
return true;
}
}
return false;
}
} // namespace vpdSpecificUtility
} // namespace vpd