blob: fbf713b77ab33083fe05282cda566daffd765812 [file] [log] [blame]
#pragma once
#include "config.h"
#include "constants.hpp"
#include "event_logger.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>
#include <typeindex>
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] i_vpdFilePath - file path of the vpd.
*
* @return On success, returns generated file name, otherwise returns empty
* string.
*/
inline std::string generateBadVPDFileName(
const std::string& i_vpdFilePath) noexcept
{
std::string l_badVpdFileName{BAD_VPD_DIR};
try
{
if (i_vpdFilePath.find("i2c") != std::string::npos)
{
l_badVpdFileName += "i2c-";
std::regex l_i2cPattern("(at24/)([0-9]+-[0-9]+)\\/");
std::smatch l_match;
if (std::regex_search(i_vpdFilePath, l_match, l_i2cPattern))
{
l_badVpdFileName += l_match.str(2);
}
}
else if (i_vpdFilePath.find("spi") != std::string::npos)
{
std::regex l_spiPattern("((spi)[0-9]+)(.0)");
std::smatch l_match;
if (std::regex_search(i_vpdFilePath, l_match, l_spiPattern))
{
l_badVpdFileName += l_match.str(1);
}
}
}
catch (const std::exception& l_ex)
{
l_badVpdFileName.clear();
logging::logMessage("Failed to generate bad VPD file name for [" +
i_vpdFilePath + "]. Error: " + l_ex.what());
}
return l_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.
*
*
* @param[in] i_vpdFilePath - vpd file path
* @param[in] i_vpdVector - vpd vector
*
* @return On success returns 0, otherwise returns -1.
*/
inline int dumpBadVpd(const std::string& i_vpdFilePath,
const types::BinaryVector& i_vpdVector) noexcept
{
int l_rc{constants::FAILURE};
try
{
std::filesystem::create_directory(BAD_VPD_DIR);
auto l_badVpdPath = generateBadVPDFileName(i_vpdFilePath);
if (l_badVpdPath.empty())
{
throw std::runtime_error("Failed to generate bad VPD file name");
}
if (std::filesystem::exists(l_badVpdPath))
{
std::error_code l_ec;
std::filesystem::remove(l_badVpdPath, l_ec);
if (l_ec) // error code
{
const std::string l_errorMsg{
"Error removing the existing broken vpd in " +
l_badVpdPath +
". Error code : " + std::to_string(l_ec.value()) +
". Error message : " + l_ec.message()};
throw std::runtime_error(l_errorMsg);
}
}
std::ofstream l_badVpdFileStream(l_badVpdPath, std::ofstream::binary);
if (!l_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.");
}
l_badVpdFileStream.write(
reinterpret_cast<const char*>(i_vpdVector.data()),
i_vpdVector.size());
l_rc = constants::SUCCESS;
}
catch (const std::exception& l_ex)
{
logging::logMessage("Failed to dump bad VPD for [" + i_vpdFilePath +
"]. Error: " + l_ex.what());
}
return l_rc;
}
/**
* @brief An API to read value of a keyword.
*
*
* @param[in] i_kwdValueMap - A map having Kwd value pair.
* @param[in] i_kwd - keyword name.
*
* @return On success returns value of the keyword read from map, otherwise
* returns empty string.
*/
inline std::string getKwVal(const types::IPZKwdValueMap& i_kwdValueMap,
const std::string& i_kwd) noexcept
{
std::string l_kwdValue;
try
{
if (i_kwd.empty())
{
throw std::runtime_error("Invalid parameters");
}
auto l_itrToKwd = i_kwdValueMap.find(i_kwd);
if (l_itrToKwd != i_kwdValueMap.end())
{
l_kwdValue = l_itrToKwd->second;
}
else
{
throw std::runtime_error("Keyword not found");
}
}
catch (const std::exception& l_ex)
{
logging::logMessage("Failed to get value for keyword [" + i_kwd +
"]. Error : " + l_ex.what());
}
return l_kwdValue;
}
/**
* @brief An API to process encoding of a keyword.
*
* @param[in] i_keyword - Keyword to be processed.
* @param[in] i_encoding - Type of encoding.
*
* @return Value after being processed for encoded type.
*/
inline std::string encodeKeyword(const std::string& i_keyword,
const std::string& i_encoding) noexcept
{
// Default value is keyword value
std::string l_result(i_keyword.begin(), i_keyword.end());
try
{
if (i_encoding == "MAC")
{
l_result.clear();
size_t l_firstByte = i_keyword[0];
l_result += commonUtility::toHex(l_firstByte >> 4);
l_result += commonUtility::toHex(l_firstByte & 0x0f);
for (size_t i = 1; i < i_keyword.size(); ++i)
{
l_result += ":";
l_result += commonUtility::toHex(i_keyword[i] >> 4);
l_result += commonUtility::toHex(i_keyword[i] & 0x0f);
}
}
else if (i_encoding == "DATE")
{
// Date, represent as
// <year>-<month>-<day> <hour>:<min>
l_result.clear();
static constexpr uint8_t skipPrefix = 3;
auto strItr = i_keyword.begin();
advance(strItr, skipPrefix);
for_each(strItr, i_keyword.end(),
[&l_result](size_t c) { l_result += c; });
l_result.insert(constants::BD_YEAR_END, 1, '-');
l_result.insert(constants::BD_MONTH_END, 1, '-');
l_result.insert(constants::BD_DAY_END, 1, ' ');
l_result.insert(constants::BD_HOUR_END, 1, ':');
}
}
catch (const std::exception& l_ex)
{
l_result.clear();
logging::logMessage("Failed to encode keyword [" + i_keyword +
"]. Error: " + l_ex.what());
}
return l_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] io_map - Interface map.
* @param[in] i_interface - Interface to be processed.
* @param[in] i_propertyMap - new property map that needs to be emplaced.
*
* @return On success returns 0, otherwise returns -1.
*/
inline int insertOrMerge(types::InterfaceMap& io_map,
const std::string& i_interface,
types::PropertyMap&& i_propertyMap) noexcept
{
int l_rc{constants::FAILURE};
try
{
if (io_map.find(i_interface) != io_map.end())
{
auto& l_prop = io_map.at(i_interface);
std::for_each(i_propertyMap.begin(), i_propertyMap.end(),
[&l_prop](auto l_keyValue) {
l_prop[l_keyValue.first] = l_keyValue.second;
});
}
else
{
io_map.emplace(i_interface, i_propertyMap);
}
l_rc = constants::SUCCESS;
}
catch (const std::exception& l_ex)
{
// ToDo:: Log PEL
logging::logMessage(
"Inserting properties into interface[" + i_interface +
"] map failed, reason: " + std::string(l_ex.what()));
}
return l_rc;
}
/**
* @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);
firstKwdValue = getKwVal(itrToVCEN->second, kwd1);
if (firstKwdValue.empty())
{
throw std::runtime_error(
"Failed to get value for keyword [" + kwd1 + "]");
}
secondKwdValue = getKwVal(itrToVCEN->second, kwd2);
if (secondKwdValue.empty())
{
throw std::runtime_error(
"Failed to get value for keyword [" + kwd2 + "]");
}
}
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.
*
* @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) noexcept
{
bool l_rc{false};
try
{
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")};
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
l_rc = true;
}
}
if (!l_rc)
{
logging::logMessage("No match found for CCIN");
}
}
else
{
logging::logMessage("VPD type not supported. Can't find CCIN");
}
}
catch (const std::exception& l_ex)
{
const std::string l_errMsg{
"Failed to find CCIN in VPD. Error : " + std::string(l_ex.what())};
if (typeid(l_ex) == std::type_index(typeid(DataException)))
{
EventLogger::createSyncPel(
types::ErrorType::InvalidVpdMessage,
types::SeverityType::Informational, __FILE__, __FUNCTION__, 0,
l_errMsg, std::nullopt, std::nullopt, std::nullopt,
std::nullopt);
}
logging::logMessage(l_errMsg);
}
return l_rc;
}
/**
* @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() noexcept
{
bool l_rc{false};
try
{
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)
{
if (l_hwVer->size() != constants::VALUE_2)
{
throw std::runtime_error("Invalid HW keyword length.");
}
if (l_imValue->size() != constants::VALUE_4)
{
throw std::runtime_error("Invalid IM keyword length.");
}
const types::BinaryVector l_everest{80, 00, 48, 00};
const types::BinaryVector l_fuji{96, 00, 32, 00};
if (((*l_imValue) == l_everest) || ((*l_imValue) == l_fuji))
{
if ((*l_hwVer).at(1) < constants::VALUE_21)
{
l_rc = true;
}
}
else if ((*l_hwVer).at(1) < constants::VALUE_2)
{
l_rc = true;
}
}
}
catch (const std::exception& l_ex)
{
logging::logMessage("Failed to check for pass 1 planar. Error: " +
std::string(l_ex.what()));
}
return l_rc;
}
/**
* @brief API to detect if system configuration is that of PowerVS system.
*
* @param[in] i_imValue - IM value of the system.
* @return true if it is PowerVS configuration, false otherwise.
*/
inline bool isPowerVsConfiguration(const types::BinaryVector& i_imValue)
{
if (i_imValue.empty() || i_imValue.size() != constants::VALUE_4)
{
return false;
}
// Should be a 0x5000XX series system.
if (i_imValue.at(0) == constants::HEX_VALUE_50 &&
i_imValue.at(1) == constants::HEX_VALUE_00)
{
std::string l_imagePrefix = dbusUtility::getImagePrefix();
// Check image for 0x500030XX series.
if ((i_imValue.at(2) == constants::HEX_VALUE_30) &&
((l_imagePrefix == constants::powerVsImagePrefix_MY) ||
(l_imagePrefix == constants::powerVsImagePrefix_NY)))
{
logging::logMessage("PowerVS configuration");
return true;
}
// Check image for 0X500010XX series.
if ((i_imValue.at(2) == constants::HEX_VALUE_10) &&
((l_imagePrefix == constants::powerVsImagePrefix_MZ) ||
(l_imagePrefix == constants::powerVsImagePrefix_NZ)))
{
logging::logMessage("PowerVS configuration");
return true;
}
}
return false;
}
/**
* @brief API to get CCIN for a given FRU from DBus.
*
* The API reads the CCIN for a FRU based on its inventory path.
*
* @param[in] i_invObjPath - Inventory path of the FRU.
* @return CCIN of the FRU on success, empty string otherwise.
*/
inline std::string getCcinFromDbus(const std::string& i_invObjPath)
{
try
{
if (i_invObjPath.empty())
{
throw std::runtime_error("Empty EEPROM path, can't read CCIN");
}
const auto& l_retValue = dbusUtility::readDbusProperty(
constants::pimServiceName, i_invObjPath, constants::viniInf,
constants::kwdCCIN);
auto l_ptrCcin = std::get_if<types::BinaryVector>(&l_retValue);
if (!l_ptrCcin || (*l_ptrCcin).size() != constants::VALUE_4)
{
throw DbusException("Invalid CCIN read from Dbus");
}
return std::string((*l_ptrCcin).begin(), (*l_ptrCcin).end());
}
catch (const std::exception& l_ex)
{
logging::logMessage(l_ex.what());
return std::string{};
}
}
} // namespace vpdSpecificUtility
} // namespace vpd