blob: 50c4b904b68b6665d2b2453a7d2d6d9c8163969e [file] [log] [blame]
#include "config.h"
#include "manager.hpp"
#include "common_utility.hpp"
#include "editor_impl.hpp"
#include "ibm_vpd_utils.hpp"
#include "ipz_parser.hpp"
#include "parser_factory.hpp"
#include "reader_impl.hpp"
#include "vpd_exceptions.hpp"
#include <phosphor-logging/elog-errors.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <filesystem>
using namespace openpower::vpd::constants;
using namespace openpower::vpd::inventory;
using namespace openpower::vpd::manager::editor;
using namespace openpower::vpd::manager::reader;
using namespace std;
using namespace openpower::vpd::parser;
using namespace openpower::vpd::parser::factory;
using namespace openpower::vpd::ipz::parser;
using namespace openpower::vpd::exceptions;
using namespace phosphor::logging;
namespace openpower
{
namespace vpd
{
namespace manager
{
Manager::Manager(std::shared_ptr<boost::asio::io_context>& ioCon,
std::shared_ptr<sdbusplus::asio::dbus_interface>& iFace,
std::shared_ptr<sdbusplus::asio::connection>& conn) :
ioContext(ioCon),
interface(iFace), conn(conn)
{
interface->register_method(
"WriteKeyword",
[this](const sdbusplus::message::object_path& path,
const std::string& recordName, const std::string& keyword,
const Binary& value) {
this->writeKeyword(path, recordName, keyword, value);
});
interface->register_method(
"GetFRUsByUnexpandedLocationCode",
[this](const std::string& locationCode,
const uint16_t nodeNumber) -> inventory::ListOfPaths {
return this->getFRUsByUnexpandedLocationCode(locationCode,
nodeNumber);
});
interface->register_method(
"GetFRUsByExpandedLocationCode",
[this](const std::string& locationCode) -> inventory::ListOfPaths {
return this->getFRUsByExpandedLocationCode(locationCode);
});
interface->register_method(
"GetExpandedLocationCode",
[this](const std::string& locationCode,
const uint16_t nodeNumber) -> std::string {
return this->getExpandedLocationCode(locationCode, nodeNumber);
});
interface->register_method("PerformVPDRecollection",
[this]() { this->performVPDRecollection(); });
interface->register_method(
"deleteFRUVPD", [this](const sdbusplus::message::object_path& path) {
this->deleteFRUVPD(path);
});
interface->register_method(
"CollectFRUVPD", [this](const sdbusplus::message::object_path& path) {
this->collectFRUVPD(path);
});
sd_bus_default(&sdBus);
initManager();
}
void Manager::initManager()
{
try
{
processJSON();
restoreSystemVpd();
listenHostState();
listenAssetTag();
// Create an instance of the BIOS handler
biosHandler = std::make_shared<BiosHandler>(conn, *this);
// instantiate gpioMonitor class
gpioMon = std::make_shared<GpioMonitor>(jsonFile, ioContext);
}
catch (const std::exception& e)
{
std::cerr << e.what() << "\n";
}
}
/**
* @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.
*/
static 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& keywordInfo : kwdListForRecord)
{
const auto& keyword = get<0>(keywordInfo);
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);
const auto& defaultValue = get<1>(keywordInfo);
if (Binary(busValue.begin(), busValue.end()) !=
defaultValue)
{
if (Binary(kwdValue.begin(), kwdValue.end()) ==
defaultValue)
{
// 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));
}
}
}
}
}
}
}
void Manager::restoreSystemVpd()
{
std::cout << "Attempting system VPD restore" << std::endl;
ParserInterface* parser = nullptr;
try
{
auto vpdVector = getVpdDataInVector(jsonFile, systemVpdFilePath);
uint32_t vpdStartOffset = 0;
const auto& inventoryPath =
jsonFile["frus"][systemVpdFilePath][0]["inventoryPath"]
.get_ref<const nlohmann::json::string_t&>();
parser = ParserFactory::getParser(vpdVector, (pimPath + inventoryPath),
systemVpdFilePath, vpdStartOffset);
auto parseResult = parser->parse();
if (auto pVal = std::get_if<Store>(&parseResult))
{
// map to hold all the keywords whose value is blank and
// needs to be updated at standby.
std::vector<RestoredEeproms> blankSystemVpdProperties{};
getListOfBlankSystemVpd(pVal->getVpdMap(), SYSTEM_OBJECT,
blankSystemVpdProperties);
// if system VPD restore is required, update the
// EEPROM
for (const auto& item : blankSystemVpdProperties)
{
std::cout << "Restoring keyword: " << std::get<2>(item)
<< std::endl;
writeKeyword(std::get<0>(item), std::get<1>(item),
std::get<2>(item), std::get<3>(item));
}
}
else
{
std::cerr << "Not a valid format to restore system VPD"
<< std::endl;
}
}
catch (const std::exception& e)
{
std::cerr << "Failed to restore system VPD due to exception: "
<< e.what() << std::endl;
}
// release the parser object
ParserFactory::freeParser(parser);
}
void Manager::listenHostState()
{
static std::shared_ptr<sdbusplus::bus::match_t> hostState =
std::make_shared<sdbusplus::bus::match_t>(
*conn,
sdbusplus::bus::match::rules::propertiesChanged(
"/xyz/openbmc_project/state/host0",
"xyz.openbmc_project.State.Host"),
[this](sdbusplus::message_t& msg) { hostStateCallBack(msg); });
}
void Manager::checkEssentialFrus()
{
for (const auto& invPath : essentialFrus)
{
const auto res = readBusProperty(invPath, invItemIntf, "Present");
// implies the essential FRU is missing. Log PEL.
if (res == "false")
{
auto rc = sd_bus_call_method_async(
sdBus, NULL, loggerService, loggerObjectPath,
loggerCreateInterface, "Create", NULL, NULL, "ssa{ss}",
errIntfForEssentialFru,
"xyz.openbmc_project.Logging.Entry.Level.Warning", 2,
"DESCRIPTION", "Essential fru missing from the system.",
"CALLOUT_INVENTORY_PATH", (pimPath + invPath).c_str());
if (rc < 0)
{
log<level::ERR>("Error calling sd_bus_call_method_async",
entry("RC=%d", rc),
entry("MSG=%s", strerror(-rc)));
}
}
}
}
void Manager::hostStateCallBack(sdbusplus::message_t& msg)
{
if (msg.is_method_error())
{
std::cerr << "Error in reading signal " << std::endl;
}
Path object;
PropertyMap propMap;
msg.read(object, propMap);
const auto itr = propMap.find("CurrentHostState");
if (itr != propMap.end())
{
if (auto hostState = std::get_if<std::string>(&(itr->second)))
{
// implies system is moving from standby to power on state
if (*hostState == "xyz.openbmc_project.State.Host.HostState."
"TransitioningToRunning")
{
// detect if essential frus are present in the system.
checkEssentialFrus();
// check and perfrom recollection for FRUs replaceable at
// standby.
performVPDRecollection();
return;
}
}
}
}
void Manager::listenAssetTag()
{
static std::shared_ptr<sdbusplus::bus::match_t> assetMatcher =
std::make_shared<sdbusplus::bus::match_t>(
*conn,
sdbusplus::bus::match::rules::propertiesChanged(
"/xyz/openbmc_project/inventory/system",
"xyz.openbmc_project.Inventory.Decorator.AssetTag"),
[this](sdbusplus::message_t& msg) { assetTagCallback(msg); });
}
void Manager::assetTagCallback(sdbusplus::message_t& msg)
{
if (msg.is_method_error())
{
std::cerr << "Error in reading signal " << std::endl;
}
Path object;
PropertyMap propMap;
msg.read(object, propMap);
const auto itr = propMap.find("AssetTag");
if (itr != propMap.end())
{
if (auto assetTag = std::get_if<std::string>(&(itr->second)))
{
// Call Notify to persist the AssetTag
inventory::ObjectMap objectMap = {
{std::string{"/system"},
{{"xyz.openbmc_project.Inventory.Decorator.AssetTag",
{{"AssetTag", *assetTag}}}}}};
common::utility::callPIM(std::move(objectMap));
}
else
{
std::cerr << "Failed to read asset tag" << std::endl;
}
}
}
void Manager::processJSON()
{
std::ifstream json(INVENTORY_JSON_SYM_LINK, std::ios::binary);
if (!json)
{
throw std::runtime_error("json file not found");
}
jsonFile = nlohmann::json::parse(json);
if (jsonFile.find("frus") == jsonFile.end())
{
throw std::runtime_error("frus group not found in json");
}
const nlohmann::json& groupFRUS =
jsonFile["frus"].get_ref<const nlohmann::json::object_t&>();
for (const auto& itemFRUS : groupFRUS.items())
{
const std::vector<nlohmann::json>& groupEEPROM =
itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
for (const auto& itemEEPROM : groupEEPROM)
{
bool isMotherboard = false;
std::string redundantPath;
if (itemEEPROM["extraInterfaces"].find(
"xyz.openbmc_project.Inventory.Item.Board.Motherboard") !=
itemEEPROM["extraInterfaces"].end())
{
isMotherboard = true;
}
if (itemEEPROM.find("redundantEeprom") != itemEEPROM.end())
{
redundantPath = itemEEPROM["redundantEeprom"]
.get_ref<const nlohmann::json::string_t&>();
}
frus.emplace(
itemEEPROM["inventoryPath"]
.get_ref<const nlohmann::json::string_t&>(),
std::make_tuple(itemFRUS.key(), redundantPath, isMotherboard));
if (itemEEPROM["extraInterfaces"].find(IBM_LOCATION_CODE_INF) !=
itemEEPROM["extraInterfaces"].end())
{
fruLocationCode.emplace(
itemEEPROM["extraInterfaces"][IBM_LOCATION_CODE_INF]
["LocationCode"]
.get_ref<const nlohmann::json::string_t&>(),
itemEEPROM["inventoryPath"]
.get_ref<const nlohmann::json::string_t&>());
}
if (itemEEPROM.value("replaceableAtStandby", false))
{
replaceableFrus.emplace_back(itemFRUS.key());
}
if (itemEEPROM.value("essentialFru", false))
{
essentialFrus.emplace_back(itemEEPROM["inventoryPath"]);
}
}
}
}
void Manager::writeKeyword(const sdbusplus::message::object_path& path,
const std::string& recordName,
const std::string& keyword, const Binary& value)
{
try
{
std::string objPath{path};
// Strip any inventory prefix in path
if (objPath.find(INVENTORY_PATH) == 0)
{
objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1);
}
if (frus.find(objPath) == frus.end())
{
throw std::runtime_error("Inventory path not found");
}
inventory::Path vpdFilePath = std::get<0>(frus.find(objPath)->second);
// instantiate editor class to update the data
EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword, objPath);
uint32_t offset = 0;
// Setup offset, if any
for (const auto& item : jsonFile["frus"][vpdFilePath])
{
if (item.find("offset") != item.end())
{
offset = item["offset"];
break;
}
}
edit.updateKeyword(value, offset, true);
// If we have a redundant EEPROM to update, then update just the EEPROM,
// not the cache since that is already done when we updated the primary
if (!std::get<1>(frus.find(objPath)->second).empty())
{
EditorImpl edit(std::get<1>(frus.find(objPath)->second), jsonFile,
recordName, keyword, objPath);
edit.updateKeyword(value, offset, false);
}
// if it is a motehrboard FRU need to check for location expansion
if (std::get<2>(frus.find(objPath)->second))
{
if (recordName == "VCEN" && (keyword == "FC" || keyword == "SE"))
{
edit.expandLocationCode("fcs");
}
else if (recordName == "VSYS" &&
(keyword == "TM" || keyword == "SE"))
{
edit.expandLocationCode("mts");
}
}
return;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
ListOfPaths
Manager::getFRUsByUnexpandedLocationCode(const LocationCode& locationCode,
const NodeNumber nodeNumber)
{
ReaderImpl read;
return read.getFrusAtLocation(locationCode, nodeNumber, fruLocationCode);
}
ListOfPaths
Manager::getFRUsByExpandedLocationCode(const LocationCode& locationCode)
{
ReaderImpl read;
return read.getFRUsByExpandedLocationCode(locationCode, fruLocationCode);
}
LocationCode Manager::getExpandedLocationCode(const LocationCode& locationCode,
const NodeNumber nodeNumber)
{
ReaderImpl read;
return read.getExpandedLocationCode(locationCode, nodeNumber,
fruLocationCode);
}
void Manager::performVPDRecollection()
{
// get list of FRUs replaceable at standby
for (const auto& item : replaceableFrus)
{
const vector<nlohmann::json>& groupEEPROM = jsonFile["frus"][item];
const nlohmann::json& singleFru = groupEEPROM[0];
const string& inventoryPath =
singleFru["inventoryPath"]
.get_ref<const nlohmann::json::string_t&>();
bool prePostActionRequired = false;
if ((jsonFile["frus"][item].at(0)).find("preAction") !=
jsonFile["frus"][item].at(0).end())
{
try
{
if (!executePreAction(jsonFile, item))
{
// if the FRU has preAction defined then its execution
// should pass to ensure bind/unbind of data.
// preAction execution failed. should not call
// bind/unbind.
log<level::ERR>(
"Pre-Action execution failed for the FRU",
entry("ERROR=%s",
("Inventory path: " + inventoryPath).c_str()));
continue;
}
}
catch (const GpioException& e)
{
log<level::ERR>(e.what());
PelAdditionalData additionalData{};
additionalData.emplace("DESCRIPTION", e.what());
createPEL(additionalData, PelSeverity::WARNING,
errIntfForGpioError, sdBus);
continue;
}
prePostActionRequired = true;
}
// unbind, bind the driver to trigger parser.
triggerVpdCollection(singleFru, inventoryPath);
// this check is added to avoid file system expensive call in case not
// required.
if (prePostActionRequired)
{
// Check if device showed up (test for file)
if (!filesystem::exists(item))
{
try
{
// If not, then take failure postAction
executePostFailAction(jsonFile, item);
}
catch (const GpioException& e)
{
PelAdditionalData additionalData{};
additionalData.emplace("DESCRIPTION", e.what());
createPEL(additionalData, PelSeverity::WARNING,
errIntfForGpioError, sdBus);
}
}
else
{
// bind the LED driver
string chipAddr = singleFru.value("pcaChipAddress", "");
cout << "performVPDRecollection: Executing driver binding for "
"chip "
"address - "
<< chipAddr << endl;
executeCmd(createBindUnbindDriverCmnd(chipAddr, "i2c",
"leds-pca955x", "/bind"));
}
}
}
}
void Manager::collectFRUVPD(const sdbusplus::message::object_path& path)
{
std::cout << "Manager called to collect vpd for fru: " << std::string{path}
<< std::endl;
using InvalidArgument =
sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
using Argument = xyz::openbmc_project::Common::InvalidArgument;
std::string objPath{path};
// Strip any inventory prefix in path
if (objPath.find(INVENTORY_PATH) == 0)
{
objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1);
}
// if path not found in Json.
if (frus.find(objPath) == frus.end())
{
elog<InvalidArgument>(Argument::ARGUMENT_NAME("Object Path"),
Argument::ARGUMENT_VALUE(objPath.c_str()));
}
inventory::Path vpdFilePath = std::get<0>(frus.find(objPath)->second);
const std::vector<nlohmann::json>& groupEEPROM =
jsonFile["frus"][vpdFilePath].get_ref<const nlohmann::json::array_t&>();
nlohmann::json singleFru{};
for (const auto& item : groupEEPROM)
{
if (item["inventoryPath"] == objPath)
{
// this is the inventory we are looking for
singleFru = item;
break;
}
}
// check if the device qualifies for CM.
if (singleFru.value("concurrentlyMaintainable", false))
{
bool prePostActionRequired = false;
if ((jsonFile["frus"][vpdFilePath].at(0)).find("preAction") !=
jsonFile["frus"][vpdFilePath].at(0).end())
{
if (!executePreAction(jsonFile, vpdFilePath))
{
// if the FRU has preAction defined then its execution should
// pass to ensure bind/unbind of data.
// preAction execution failed. should not call bind/unbind.
log<level::ERR>("Pre-Action execution failed for the FRU");
return;
}
prePostActionRequired = true;
}
// unbind, bind the driver to trigger parser.
triggerVpdCollection(singleFru, objPath);
// this check is added to avoid file system expensive call in case not
// required.
if (prePostActionRequired)
{
// Check if device showed up (test for file)
if (!filesystem::exists(vpdFilePath))
{
try
{
// If not, then take failure postAction
executePostFailAction(jsonFile, vpdFilePath);
}
catch (const GpioException& e)
{
PelAdditionalData additionalData{};
additionalData.emplace("DESCRIPTION", e.what());
createPEL(additionalData, PelSeverity::WARNING,
errIntfForGpioError, sdBus);
}
}
else
{
// bind the LED driver
string chipAddr = jsonFile["frus"][vpdFilePath].at(0).value(
"pcaChipAddress", "");
cout << "Executing driver binding for chip address - "
<< chipAddr << endl;
executeCmd(createBindUnbindDriverCmnd(chipAddr, "i2c",
"leds-pca955x", "/bind"));
}
}
return;
}
else
{
elog<InvalidArgument>(Argument::ARGUMENT_NAME("Object Path"),
Argument::ARGUMENT_VALUE(objPath.c_str()));
}
}
void Manager::triggerVpdCollection(const nlohmann::json& singleFru,
const std::string& path)
{
if ((singleFru.find("devAddress") == singleFru.end()) ||
(singleFru.find("driverType") == singleFru.end()) ||
(singleFru.find("busType") == singleFru.end()))
{
// The FRUs is marked for collection but missing mandatory
// fields for collection. Log error and return.
log<level::ERR>(
"Collection Failed as mandatory field missing in Json",
entry("ERROR=%s", ("Recollection failed for " + (path)).c_str()));
return;
}
string deviceAddress = singleFru["devAddress"];
const string& driverType = singleFru["driverType"];
const string& busType = singleFru["busType"];
// devTreeStatus flag is present in json as false to mention
// that the EEPROM is not mentioned in device tree. If this flag
// is absent consider the value to be true, i.e EEPROM is
// mentioned in device tree
if (!singleFru.value("devTreeStatus", true))
{
auto pos = deviceAddress.find('-');
if (pos != string::npos)
{
string busNum = deviceAddress.substr(0, pos);
deviceAddress = "0x" + deviceAddress.substr(pos + 1, string::npos);
string deleteDevice = "echo" + deviceAddress + " > /sys/bus/" +
busType + "/devices/" + busType + "-" +
busNum + "/delete_device";
executeCmd(deleteDevice);
string addDevice = "echo" + driverType + " " + deviceAddress +
" > /sys/bus/" + busType + "/devices/" +
busType + "-" + busNum + "/new_device";
executeCmd(addDevice);
}
else
{
const string& inventoryPath =
singleFru["inventoryPath"]
.get_ref<const nlohmann::json::string_t&>();
log<level::ERR>(
"Wrong format of device address in Json",
entry("ERROR=%s",
("Recollection failed for " + inventoryPath).c_str()));
}
}
else
{
executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
driverType, "/unbind"));
executeCmd(createBindUnbindDriverCmnd(deviceAddress, busType,
driverType, "/bind"));
}
}
void Manager::deleteFRUVPD(const sdbusplus::message::object_path& path)
{
std::cout << "Manager called to delete vpd for fru: " << std::string{path}
<< std::endl;
using InvalidArgument =
sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
using Argument = xyz::openbmc_project::Common::InvalidArgument;
std::string objPath{path};
// Strip any inventory prefix in path
if (objPath.find(INVENTORY_PATH) == 0)
{
objPath = objPath.substr(sizeof(INVENTORY_PATH) - 1);
}
// if path not found in Json.
if (frus.find(objPath) == frus.end())
{
elog<InvalidArgument>(Argument::ARGUMENT_NAME("Object Path"),
Argument::ARGUMENT_VALUE(objPath.c_str()));
}
inventory::Path& vpdFilePath = std::get<0>(frus.find(objPath)->second);
string chipAddress =
jsonFile["frus"][vpdFilePath].at(0).value("pcaChipAddress", "");
// Unbind the LED driver for this FRU
cout << "Unbinding device- " << chipAddress << endl;
executeCmd(createBindUnbindDriverCmnd(chipAddress, "i2c", "leds-pca955x",
"/unbind"));
// if the FRU is not present then log error
if (readBusProperty(objPath, "xyz.openbmc_project.Inventory.Item",
"Present") == "false")
{
elog<InvalidArgument>(Argument::ARGUMENT_NAME("FRU not preset"),
Argument::ARGUMENT_VALUE(objPath.c_str()));
}
else
{
// Set present property of FRU as false as it has been removed.
// CC data for FRU is also removed as
// a) FRU is not there so CC does not make sense.
// b) Sensors dependent on Panel uses CC data.
inventory::InterfaceMap interfaces{
{"xyz.openbmc_project.Inventory.Item", {{"Present", false}}},
{"com.ibm.ipzvpd.VINI", {{"CC", Binary{}}}}};
inventory::ObjectMap objectMap;
objectMap.emplace(objPath, move(interfaces));
common::utility::callPIM(move(objectMap));
}
}
} // namespace manager
} // namespace vpd
} // namespace openpower