blob: d78c07af3b6054faf48ecc82289877a832170b61 [file] [log] [blame]
/**
* Copyright © 2019 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src.hpp"
#include "device_callouts.hpp"
#include "json_utils.hpp"
#include "paths.hpp"
#include "pel_values.hpp"
#ifdef PELTOOL
#include <Python.h>
#include <nlohmann/json.hpp>
#include <sstream>
#endif
#include <fmt/format.h>
#include <phosphor-logging/log.hpp>
namespace openpower
{
namespace pels
{
namespace pv = openpower::pels::pel_values;
namespace rg = openpower::pels::message;
using namespace phosphor::logging;
using namespace std::string_literals;
constexpr size_t ccinSize = 4;
#ifdef PELTOOL
using orderedJSON = nlohmann::ordered_json;
void pyDecRef(PyObject* pyObj)
{
Py_XDECREF(pyObj);
}
/**
* @brief Returns a JSON string to append to SRC section.
*
* The returning string will contain a JSON object, but without
* the outer {}. If the input JSON isn't a JSON object (dict), then
* one will be created with the input added to a 'SRC Details' key.
*
* @param[in] json - The JSON to convert to a string
*
* @return std::string - The JSON string
*/
std::string prettyJSON(const orderedJSON& json)
{
orderedJSON output;
if (!json.is_object())
{
output["SRC Details"] = json;
}
else
{
for (const auto& [key, value] : json.items())
{
output["SRC Details"][key] = value;
}
}
// Let nlohmann do the pretty printing.
std::stringstream stream;
stream << std::setw(4) << output;
auto jsonString = stream.str();
// Now it looks like:
// {
// "Key": "Value",
// ...
// }
// Replace the { and the following newline, and the } and its
// preceeding newline.
jsonString.erase(0, 2);
auto pos = jsonString.find_last_of('}');
jsonString.erase(pos - 1);
return jsonString;
}
/**
* @brief Call Python modules to parse the data into a JSON string
*
* The module to call is based on the Creator Subsystem ID under the namespace
* "srcparsers". For example: "srcparsers.xsrc.xsrc" where "x" is the Creator
* Subsystem ID in ASCII lowercase.
*
* All modules must provide the following:
* Function: parseSRCToJson
* Argument list:
* 1. (str) ASCII string (Hex Word 1)
* 2. (str) Hex Word 2
* 3. (str) Hex Word 3
* 4. (str) Hex Word 4
* 5. (str) Hex Word 5
* 6. (str) Hex Word 6
* 7. (str) Hex Word 7
* 8. (str) Hex Word 8
* 9. (str) Hex Word 9
*-Return data:
* 1. (str) JSON string
*
* @param[in] hexwords - Vector of strings of Hexwords 1-9
* @param[in] creatorID - The creatorID from the Private Header section
* @return std::optional<std::string> - The JSON string if it could be created,
* else std::nullopt
*/
std::optional<std::string> getPythonJSON(std::vector<std::string>& hexwords,
uint8_t creatorID)
{
PyObject *pName, *pModule, *eType, *eValue, *eTraceback;
std::string pErrStr;
std::string module = getNumberString("%c", tolower(creatorID)) + "src";
pName = PyUnicode_FromString(
std::string("srcparsers." + module + "." + module).c_str());
std::unique_ptr<PyObject, decltype(&pyDecRef)> modNamePtr(pName, &pyDecRef);
pModule = PyImport_Import(pName);
if (pModule == NULL)
{
pErrStr = "No error string found";
PyErr_Fetch(&eType, &eValue, &eTraceback);
if (eType)
{
Py_XDECREF(eType);
}
if (eTraceback)
{
Py_XDECREF(eTraceback);
}
if (eValue)
{
PyObject* pStr = PyObject_Str(eValue);
Py_XDECREF(eValue);
if (pStr)
{
pErrStr = PyUnicode_AsUTF8(pStr);
Py_XDECREF(pStr);
}
}
}
else
{
std::unique_ptr<PyObject, decltype(&pyDecRef)> modPtr(pModule,
&pyDecRef);
std::string funcToCall = "parseSRCToJson";
PyObject* pKey = PyUnicode_FromString(funcToCall.c_str());
std::unique_ptr<PyObject, decltype(&pyDecRef)> keyPtr(pKey, &pyDecRef);
PyObject* pDict = PyModule_GetDict(pModule);
Py_INCREF(pDict);
if (!PyDict_Contains(pDict, pKey))
{
Py_DECREF(pDict);
log<level::ERR>(
"Python module error",
entry("ERROR=%s",
std::string(funcToCall + " function missing").c_str()),
entry("SRC=%s", hexwords.front().c_str()),
entry("PARSER_MODULE=%s", module.c_str()));
return std::nullopt;
}
PyObject* pFunc = PyDict_GetItemString(pDict, funcToCall.c_str());
Py_DECREF(pDict);
Py_INCREF(pFunc);
if (PyCallable_Check(pFunc))
{
PyObject* pArgs = PyTuple_New(9);
std::unique_ptr<PyObject, decltype(&pyDecRef)> argPtr(pArgs,
&pyDecRef);
for (size_t i = 0; i < 9; i++)
{
std::string arg{"00000000"};
if (i < hexwords.size())
{
arg = hexwords[i];
}
PyTuple_SetItem(pArgs, i, Py_BuildValue("s", arg.c_str()));
}
PyObject* pResult = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pFunc);
if (pResult)
{
std::unique_ptr<PyObject, decltype(&pyDecRef)> resPtr(
pResult, &pyDecRef);
PyObject* pBytes = PyUnicode_AsEncodedString(pResult, "utf-8",
"~E~");
std::unique_ptr<PyObject, decltype(&pyDecRef)> pyBytePtr(
pBytes, &pyDecRef);
const char* output = PyBytes_AS_STRING(pBytes);
try
{
orderedJSON json = orderedJSON::parse(output);
if ((json.is_object() && !json.empty()) ||
(json.is_array() && json.size() > 0) ||
(json.is_string() && json != ""))
{
return prettyJSON(json);
}
}
catch (const std::exception& e)
{
log<level::ERR>("Bad JSON from parser",
entry("ERROR=%s", e.what()),
entry("SRC=%s", hexwords.front().c_str()),
entry("PARSER_MODULE=%s", module.c_str()));
return std::nullopt;
}
}
else
{
pErrStr = "No error string found";
PyErr_Fetch(&eType, &eValue, &eTraceback);
if (eType)
{
Py_XDECREF(eType);
}
if (eTraceback)
{
Py_XDECREF(eTraceback);
}
if (eValue)
{
PyObject* pStr = PyObject_Str(eValue);
Py_XDECREF(eValue);
if (pStr)
{
pErrStr = PyUnicode_AsUTF8(pStr);
Py_XDECREF(pStr);
}
}
}
}
}
if (!pErrStr.empty())
{
log<level::DEBUG>("Python exception thrown by parser",
entry("ERROR=%s", pErrStr.c_str()),
entry("SRC=%s", hexwords.front().c_str()),
entry("PARSER_MODULE=%s", module.c_str()));
}
return std::nullopt;
}
#endif
void SRC::unflatten(Stream& stream)
{
stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
_reserved2B >> _size;
for (auto& word : _hexData)
{
stream >> word;
}
_asciiString = std::make_unique<src::AsciiString>(stream);
if (hasAdditionalSections())
{
// The callouts section is currently the only extra subsection type
_callouts = std::make_unique<src::Callouts>(stream);
}
}
void SRC::flatten(Stream& stream) const
{
stream << _header << _version << _flags << _reserved1B << _wordCount
<< _reserved2B << _size;
for (auto& word : _hexData)
{
stream << word;
}
_asciiString->flatten(stream);
if (_callouts)
{
_callouts->flatten(stream);
}
}
SRC::SRC(Stream& pel)
{
try
{
unflatten(pel);
validate();
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Cannot unflatten SRC: {}", e.what()).c_str());
_valid = false;
}
}
SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
const nlohmann::json& jsonCallouts, const DataInterfaceBase& dataIface)
{
_header.id = static_cast<uint16_t>(SectionID::primarySRC);
_header.version = srcSectionVersion;
_header.subType = srcSectionSubtype;
_header.componentID = regEntry.componentID;
_version = srcVersion;
_flags = 0;
_reserved1B = 0;
_wordCount = numSRCHexDataWords + 1;
_reserved2B = 0;
// There are multiple fields encoded in the hex data words.
std::for_each(_hexData.begin(), _hexData.end(),
[](auto& word) { word = 0; });
// Hex Word 2 Nibbles:
// MIGVEPFF
// M: Partition dump status = 0
// I: System boot state = TODO
// G: Partition Boot type = 0
// V: BMC dump status
// E: Platform boot mode = 0 (side = temporary, speed = fast)
// P: Platform dump status
// FF: SRC format, set below
setProgressCode(dataIface);
setDumpStatus(dataIface);
setBMCFormat();
setBMCPosition();
setMotherboardCCIN(dataIface);
if (regEntry.src.deconfigFlag)
{
setErrorStatusFlag(ErrorStatusFlags::deconfigured);
}
// Fill in the last 4 words from the AdditionalData property contents.
setUserDefinedHexWords(regEntry, additionalData);
_asciiString = std::make_unique<src::AsciiString>(regEntry);
// Check for additional data - PEL_SUBSYSTEM
auto ss = additionalData.getValue("PEL_SUBSYSTEM");
if (ss)
{
auto eventSubsystem = std::stoul(*ss, NULL, 16);
std::string subsystem = pv::getValue(eventSubsystem,
pel_values::subsystemValues);
if (subsystem == "invalid")
{
log<level::WARNING>(
fmt::format("SRC: Invalid SubSystem value:{:#X}",
eventSubsystem)
.c_str());
}
else
{
_asciiString->setByte(2, eventSubsystem);
}
}
addCallouts(regEntry, additionalData, jsonCallouts, dataIface);
_size = baseSRCSize;
_size += _callouts ? _callouts->flattenedSize() : 0;
_header.size = Section::flattenedSize() + _size;
_valid = true;
}
void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
const AdditionalData& ad)
{
if (!regEntry.src.hexwordADFields)
{
return;
}
// Save the AdditionalData value corresponding to the first element of
// adName tuple into _hexData[wordNum].
for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
{
// Can only set words 6 - 9
if (!isUserDefinedWord(wordNum))
{
std::string msg = "SRC user data word out of range: " +
std::to_string(wordNum);
addDebugData(msg);
continue;
}
auto value = ad.getValue(std::get<0>(adName));
if (value)
{
_hexData[getWordIndexFromWordNum(wordNum)] =
std::strtoul(value.value().c_str(), nullptr, 0);
}
else
{
std::string msg = "Source for user data SRC word not found: " +
std::get<0>(adName);
addDebugData(msg);
}
}
}
void SRC::setMotherboardCCIN(const DataInterfaceBase& dataIface)
{
uint32_t ccin = 0;
auto ccinString = dataIface.getMotherboardCCIN();
try
{
if (ccinString.size() == ccinSize)
{
ccin = std::stoi(ccinString, 0, 16);
}
}
catch (const std::exception& e)
{
log<level::WARNING>("Could not convert motherboard CCIN to a number",
entry("CCIN=%s", ccinString.c_str()));
return;
}
// Set the first 2 bytes
_hexData[1] |= ccin << 16;
}
void SRC::validate()
{
bool failed = false;
if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
(header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
{
log<level::ERR>(
fmt::format("Invalid SRC section ID: {0:#x}", header().id).c_str());
failed = true;
}
// Check the version in the SRC, not in the header
if (_version != srcVersion)
{
log<level::ERR>(
fmt::format("Invalid SRC version: {0:#x}", header().version)
.c_str());
failed = true;
}
_valid = failed ? false : true;
}
bool SRC::isBMCSRC() const
{
auto as = asciiString();
if (as.length() >= 2)
{
uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
return (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
errorType == static_cast<uint8_t>(SRCType::powerError));
}
return false;
}
bool SRC::isHostbootSRC() const
{
auto as = asciiString();
if (as.length() >= 2)
{
uint8_t errorType = strtoul(as.substr(0, 2).c_str(), nullptr, 16);
return errorType == static_cast<uint8_t>(SRCType::hostbootError);
}
return false;
}
std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
DetailLevel type,
bool toCache) const
{
const std::string jsonIndent(indentLevel, 0x20);
std::string errorOut;
if (isBMCSRC())
{
auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
rg::LookupType::reasonCode, toCache);
if (entry)
{
errorOut.append(jsonIndent + "\"Error Details\": {\n");
auto errorMsg = getErrorMessage(*entry);
if (errorMsg)
{
if (type == DetailLevel::message)
{
return errorMsg.value();
}
else
{
jsonInsert(errorOut, "Message", errorMsg.value(), 2);
}
}
if (entry->src.hexwordADFields)
{
std::map<size_t, std::tuple<std::string, std::string>>
adFields = entry->src.hexwordADFields.value();
for (const auto& hexwordMap : adFields)
{
auto srcValue = getNumberString(
"0x%X",
_hexData[getWordIndexFromWordNum(hexwordMap.first)]);
auto srcKey = std::get<0>(hexwordMap.second);
auto srcDesc = std::get<1>(hexwordMap.second);
// Only include this hex word in the error details if the
// description exists.
if (!srcDesc.empty())
{
std::vector<std::string> valueDescr;
valueDescr.push_back(srcValue);
valueDescr.push_back(srcDesc);
jsonInsertArray(errorOut, srcKey, valueDescr, 2);
}
}
}
errorOut.erase(errorOut.size() - 2);
errorOut.append("\n");
errorOut.append(jsonIndent + "},\n");
return errorOut;
}
}
return std::nullopt;
}
std::optional<std::string>
SRC::getErrorMessage(const message::Entry& regEntry) const
{
try
{
if (regEntry.doc.messageArgSources)
{
std::vector<uint32_t> argSourceVals;
std::string message;
const auto& argValues = regEntry.doc.messageArgSources.value();
for (size_t i = 0; i < argValues.size(); ++i)
{
argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
argValues[i].back() - '0')]);
}
auto it = std::begin(regEntry.doc.message);
auto it_end = std::end(regEntry.doc.message);
while (it != it_end)
{
if (*it == '%')
{
++it;
size_t wordIndex = *it - '0';
if (isdigit(*it) && wordIndex >= 1 &&
static_cast<uint16_t>(wordIndex) <=
argSourceVals.size())
{
message.append(getNumberString(
"0x%08X", argSourceVals[wordIndex - 1]));
}
else
{
message.append("%" + std::string(1, *it));
}
}
else
{
message.push_back(*it);
}
++it;
}
return message;
}
else
{
return regEntry.doc.message;
}
}
catch (const std::exception& e)
{
log<level::ERR>("Cannot get error message from registry entry",
entry("ERROR=%s", e.what()));
}
return std::nullopt;
}
std::optional<std::string> SRC::getCallouts() const
{
if (!_callouts)
{
return std::nullopt;
}
std::string printOut;
const std::string jsonIndent(indentLevel, 0x20);
const auto& callout = _callouts->callouts();
const auto& compDescrp = pv::failingComponentType;
printOut.append(jsonIndent + "\"Callout Section\": {\n");
jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
for (auto& entry : callout)
{
printOut.append("{\n");
if (entry->fruIdentity())
{
jsonInsert(
printOut, "FRU Type",
compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
jsonInsert(printOut, "Priority",
pv::getValue(entry->priority(),
pel_values::calloutPriorityValues),
3);
if (!entry->locationCode().empty())
{
jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
}
if (entry->fruIdentity()->getPN().has_value())
{
jsonInsert(printOut, "Part Number",
entry->fruIdentity()->getPN().value(), 3);
}
if (entry->fruIdentity()->getMaintProc().has_value())
{
jsonInsert(printOut, "Procedure",
entry->fruIdentity()->getMaintProc().value(), 3);
if (pv::procedureDesc.find(
entry->fruIdentity()->getMaintProc().value()) !=
pv::procedureDesc.end())
{
jsonInsert(
printOut, "Description",
pv::procedureDesc.at(
entry->fruIdentity()->getMaintProc().value()),
3);
}
}
if (entry->fruIdentity()->getCCIN().has_value())
{
jsonInsert(printOut, "CCIN",
entry->fruIdentity()->getCCIN().value(), 3);
}
if (entry->fruIdentity()->getSN().has_value())
{
jsonInsert(printOut, "Serial Number",
entry->fruIdentity()->getSN().value(), 3);
}
}
if (entry->pceIdentity())
{
const auto& pceIdentMtms = entry->pceIdentity()->mtms();
if (!pceIdentMtms.machineTypeAndModel().empty())
{
jsonInsert(printOut, "PCE MTMS",
pceIdentMtms.machineTypeAndModel() + "_" +
pceIdentMtms.machineSerialNumber(),
3);
}
if (!entry->pceIdentity()->enclosureName().empty())
{
jsonInsert(printOut, "PCE Name",
entry->pceIdentity()->enclosureName(), 3);
}
}
if (entry->mru())
{
const auto& mruCallouts = entry->mru()->mrus();
std::string mruId;
for (auto& element : mruCallouts)
{
if (!mruId.empty())
{
mruId.append(", " + getNumberString("%08X", element.id));
}
else
{
mruId.append(getNumberString("%08X", element.id));
}
}
jsonInsert(printOut, "MRU Id", mruId, 3);
}
printOut.erase(printOut.size() - 2);
printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
};
printOut.erase(printOut.size() - 2);
printOut.append("]\n" + jsonIndent + "}");
return printOut;
}
std::optional<std::string> SRC::getJSON(message::Registry& registry,
const std::vector<std::string>& plugins
[[maybe_unused]],
uint8_t creatorID) const
{
std::string ps;
std::vector<std::string> hexwords;
jsonInsert(ps, pv::sectionVer, getNumberString("%d", _header.version), 1);
jsonInsert(ps, pv::subSection, getNumberString("%d", _header.subType), 1);
jsonInsert(ps, pv::createdBy,
getComponentName(_header.componentID, creatorID), 1);
jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
1);
jsonInsert(ps, "Virtual Progress SRC",
pv::boolString.at(_flags & virtualProgressSRC), 1);
jsonInsert(ps, "I5/OS Service Event Bit",
pv::boolString.at(_flags & i5OSServiceEventBit), 1);
jsonInsert(ps, "Hypervisor Dump Initiated",
pv::boolString.at(_flags & hypDumpInit), 1);
if (isBMCSRC())
{
std::string ccinString;
uint32_t ccin = _hexData[1] >> 16;
if (ccin)
{
ccinString = getNumberString("%04X", ccin);
}
// The PEL spec calls it a backplane, so call it that here.
jsonInsert(ps, "Backplane CCIN", ccinString, 1);
jsonInsert(ps, "Terminate FW Error",
pv::boolString.at(
_hexData[3] &
static_cast<uint32_t>(ErrorStatusFlags::terminateFwErr)),
1);
}
if (isBMCSRC() || isHostbootSRC())
{
jsonInsert(ps, "Deconfigured",
pv::boolString.at(
_hexData[3] &
static_cast<uint32_t>(ErrorStatusFlags::deconfigured)),
1);
jsonInsert(
ps, "Guarded",
pv::boolString.at(_hexData[3] &
static_cast<uint32_t>(ErrorStatusFlags::guarded)),
1);
}
auto errorDetails = getErrorDetails(registry, DetailLevel::json, true);
if (errorDetails)
{
ps.append(errorDetails.value());
}
jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
1);
std::string refcode = asciiString();
hexwords.push_back(refcode);
std::string extRefcode;
size_t pos = refcode.find(0x20);
if (pos != std::string::npos)
{
size_t nextPos = refcode.find_first_not_of(0x20, pos);
if (nextPos != std::string::npos)
{
extRefcode = trimEnd(refcode.substr(nextPos));
}
refcode.erase(pos);
}
jsonInsert(ps, "Reference Code", refcode, 1);
if (!extRefcode.empty())
{
jsonInsert(ps, "Extended Reference Code", extRefcode, 1);
}
for (size_t i = 2; i <= _wordCount; i++)
{
std::string tmpWord =
getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]);
jsonInsert(ps, "Hex Word " + std::to_string(i), tmpWord, 1);
hexwords.push_back(tmpWord);
}
auto calloutJson = getCallouts();
if (calloutJson)
{
ps.append(calloutJson.value());
ps.append(",\n");
}
std::string subsystem = getNumberString("%c", tolower(creatorID));
bool srcDetailExists = false;
#ifdef PELTOOL
if (std::find(plugins.begin(), plugins.end(), subsystem + "src") !=
plugins.end())
{
auto pyJson = getPythonJSON(hexwords, creatorID);
if (pyJson)
{
ps.append(pyJson.value());
srcDetailExists = true;
}
}
#endif
if (!srcDetailExists)
{
ps.erase(ps.size() - 2);
}
return ps;
}
void SRC::addCallouts(const message::Entry& regEntry,
const AdditionalData& additionalData,
const nlohmann::json& jsonCallouts,
const DataInterfaceBase& dataIface)
{
auto registryCallouts = getRegistryCallouts(regEntry, additionalData,
dataIface);
auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
auto priority = additionalData.getValue("CALLOUT_PRIORITY");
std::optional<CalloutPriority> calloutPriority;
// Only H, M or L priority values.
if (priority && !(*priority).empty())
{
uint8_t p = (*priority)[0];
if (p == 'H' || p == 'M' || p == 'L')
{
calloutPriority = static_cast<CalloutPriority>(p);
}
}
// If the first registry callout says to use the passed in inventory
// path to get the location code for a symbolic FRU callout with a
// trusted location code, then do not add the inventory path as a
// normal FRU callout.
bool useInvForSymbolicFRULocCode =
!registryCallouts.empty() && registryCallouts[0].useInventoryLocCode &&
!registryCallouts[0].symbolicFRUTrusted.empty();
if (item && !useInvForSymbolicFRULocCode)
{
addInventoryCallout(*item, calloutPriority, std::nullopt, dataIface);
}
addDevicePathCallouts(additionalData, dataIface);
addRegistryCallouts(registryCallouts, dataIface,
(useInvForSymbolicFRULocCode) ? item : std::nullopt);
if (!jsonCallouts.empty())
{
addJSONCallouts(jsonCallouts, dataIface);
}
}
void SRC::addInventoryCallout(const std::string& inventoryPath,
const std::optional<CalloutPriority>& priority,
const std::optional<std::string>& locationCode,
const DataInterfaceBase& dataIface,
const std::vector<src::MRU::MRUCallout>& mrus)
{
std::string locCode;
std::string fn;
std::string ccin;
std::string sn;
std::unique_ptr<src::Callout> callout;
try
{
// Use the passed in location code if there otherwise look it up
if (locationCode)
{
locCode = *locationCode;
}
else
{
locCode = dataIface.getLocationCode(inventoryPath);
}
try
{
dataIface.getHWCalloutFields(inventoryPath, fn, ccin, sn);
CalloutPriority p = priority ? priority.value()
: CalloutPriority::high;
callout = std::make_unique<src::Callout>(p, locCode, fn, ccin, sn,
mrus);
}
catch (const sdbusplus::exception_t& e)
{
std::string msg = "No VPD found for " + inventoryPath + ": " +
e.what();
addDebugData(msg);
// Just create the callout with empty FRU fields
callout = std::make_unique<src::Callout>(
CalloutPriority::high, locCode, fn, ccin, sn, mrus);
}
}
catch (const sdbusplus::exception_t& e)
{
std::string msg = "Could not get location code for " + inventoryPath +
": " + e.what();
addDebugData(msg);
// Don't add a callout in this case, because:
// 1) With how the inventory is primed, there is no case where
// a location code is expected to be missing. This implies
// the caller is passing in something invalid.
// 2) The addDebugData call above will put the passed in path into
// a user data section that can be seen by development for debug.
// 3) Even if we wanted to do a 'no_vpd_for_fru' sort of maint.
// procedure, we don't have a good way to indicate to the user
// anything about the intended callout (they won't see user data).
// 4) Creating a new standalone event log for this problem isn't
// possible from inside a PEL section.
}
if (callout)
{
createCalloutsObject();
_callouts->addCallout(std::move(callout));
}
}
std::vector<message::RegistryCallout>
SRC::getRegistryCallouts(const message::Entry& regEntry,
const AdditionalData& additionalData,
const DataInterfaceBase& dataIface)
{
std::vector<message::RegistryCallout> registryCallouts;
if (regEntry.callouts)
{
std::vector<std::string> systemNames;
try
{
systemNames = dataIface.getSystemNames();
}
catch (const std::exception& e)
{
// Compatible interface not available yet
}
try
{
registryCallouts = message::Registry::getCallouts(
regEntry.callouts.value(), systemNames, additionalData);
}
catch (const std::exception& e)
{
addDebugData(fmt::format(
"Error parsing PEL message registry callout JSON: {}",
e.what()));
}
}
return registryCallouts;
}
void SRC::addRegistryCallouts(
const std::vector<message::RegistryCallout>& callouts,
const DataInterfaceBase& dataIface,
std::optional<std::string> trustedSymbolicFRUInvPath)
{
try
{
for (const auto& callout : callouts)
{
addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
// Only the first callout gets the inventory path
if (trustedSymbolicFRUInvPath)
{
trustedSymbolicFRUInvPath = std::nullopt;
}
}
}
catch (const std::exception& e)
{
std::string msg = "Error parsing PEL message registry callout JSON: "s +
e.what();
addDebugData(msg);
}
}
void SRC::addRegistryCallout(
const message::RegistryCallout& regCallout,
const DataInterfaceBase& dataIface,
const std::optional<std::string>& trustedSymbolicFRUInvPath)
{
std::unique_ptr<src::Callout> callout;
auto locCode = regCallout.locCode;
if (!locCode.empty())
{
try
{
locCode = dataIface.expandLocationCode(locCode, 0);
}
catch (const std::exception& e)
{
auto msg = "Unable to expand location code " + locCode + ": " +
e.what();
addDebugData(msg);
return;
}
}
// Via the PEL values table, get the priority enum.
// The schema will have validated the priority was a valid value.
auto priorityIt = pv::findByName(regCallout.priority,
pv::calloutPriorityValues);
assert(priorityIt != pv::calloutPriorityValues.end());
auto priority =
static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
if (!regCallout.procedure.empty())
{
// Procedure callout
callout = std::make_unique<src::Callout>(priority,
regCallout.procedure);
}
else if (!regCallout.symbolicFRU.empty())
{
// Symbolic FRU callout
callout = std::make_unique<src::Callout>(
priority, regCallout.symbolicFRU, locCode, false);
}
else if (!regCallout.symbolicFRUTrusted.empty())
{
// Symbolic FRU with trusted location code callout
// Use the location code from the inventory path if there is one.
if (trustedSymbolicFRUInvPath)
{
try
{
locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
}
catch (const std::exception& e)
{
addDebugData(
fmt::format("Could not get location code for {}: {}",
*trustedSymbolicFRUInvPath, e.what()));
locCode.clear();
}
}
// The registry wants it to be trusted, but that requires a valid
// location code for it to actually be.
callout = std::make_unique<src::Callout>(
priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
}
else
{
// A hardware callout
std::vector<std::string> inventoryPaths;
try
{
// Get the inventory item from the unexpanded location code
inventoryPaths =
dataIface.getInventoryFromLocCode(regCallout.locCode, 0, false);
}
catch (const std::exception& e)
{
std::string msg =
"Unable to get inventory path from location code: " + locCode +
": " + e.what();
addDebugData(msg);
return;
}
// Just use first path returned since they all point to the same FRU.
addInventoryCallout(inventoryPaths[0], priority, locCode, dataIface);
}
if (callout)
{
createCalloutsObject();
_callouts->addCallout(std::move(callout));
}
}
void SRC::addDevicePathCallouts(const AdditionalData& additionalData,
const DataInterfaceBase& dataIface)
{
std::vector<device_callouts::Callout> callouts;
auto i2cBus = additionalData.getValue("CALLOUT_IIC_BUS");
auto i2cAddr = additionalData.getValue("CALLOUT_IIC_ADDR");
auto devPath = additionalData.getValue("CALLOUT_DEVICE_PATH");
// A device callout contains either:
// * CALLOUT_ERRNO, CALLOUT_DEVICE_PATH
// * CALLOUT_ERRNO, CALLOUT_IIC_BUS, CALLOUT_IIC_ADDR
// We don't care about the errno.
if (devPath)
{
try
{
callouts = device_callouts::getCallouts(*devPath,
dataIface.getSystemNames());
}
catch (const std::exception& e)
{
addDebugData(e.what());
callouts.clear();
}
}
else if (i2cBus && i2cAddr)
{
size_t bus;
uint8_t address;
try
{
// If /dev/i2c- is prepended, remove it
if (i2cBus->find("/dev/i2c-") != std::string::npos)
{
*i2cBus = i2cBus->substr(9);
}
bus = stoul(*i2cBus, nullptr, 0);
address = stoul(*i2cAddr, nullptr, 0);
}
catch (const std::exception& e)
{
std::string msg = "Invalid CALLOUT_IIC_BUS " + *i2cBus +
" or CALLOUT_IIC_ADDR " + *i2cAddr +
" in AdditionalData property";
addDebugData(msg);
return;
}
try
{
callouts = device_callouts::getI2CCallouts(
bus, address, dataIface.getSystemNames());
}
catch (const std::exception& e)
{
addDebugData(e.what());
callouts.clear();
}
}
for (const auto& callout : callouts)
{
// The priority shouldn't be invalid, but check just in case.
CalloutPriority priority = CalloutPriority::high;
if (!callout.priority.empty())
{
auto p = pel_values::findByValue(
static_cast<uint32_t>(callout.priority[0]),
pel_values::calloutPriorityValues);
if (p != pel_values::calloutPriorityValues.end())
{
priority = static_cast<CalloutPriority>(callout.priority[0]);
}
else
{
std::string msg =
"Invalid priority found in dev callout JSON: " +
callout.priority[0];
addDebugData(msg);
}
}
std::optional<std::string> locCode;
try
{
locCode = dataIface.expandLocationCode(callout.locationCode, 0);
}
catch (const std::exception& e)
{
auto msg = fmt::format("Unable to expand location code {}: {}",
callout.locationCode, e.what());
addDebugData(msg);
}
try
{
auto inventoryPaths = dataIface.getInventoryFromLocCode(
callout.locationCode, 0, false);
// Just use first path returned since they all
// point to the same FRU.
addInventoryCallout(inventoryPaths[0], priority, locCode,
dataIface);
}
catch (const std::exception& e)
{
std::string msg =
"Unable to get inventory path from location code: " +
callout.locationCode + ": " + e.what();
addDebugData(msg);
}
// Until the code is there to convert these MRU value strings to
// the official MRU values in the callout objects, just store
// the MRU name in the debug UserData section.
if (!callout.mru.empty())
{
std::string msg = "MRU: " + callout.mru;
addDebugData(msg);
}
// getCallouts() may have generated some debug data it stored
// in a callout object. Save it as well.
if (!callout.debug.empty())
{
addDebugData(callout.debug);
}
}
}
void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
const DataInterfaceBase& dataIface)
{
if (jsonCallouts.empty())
{
return;
}
if (!jsonCallouts.is_array())
{
addDebugData("Callout JSON isn't an array");
return;
}
for (const auto& callout : jsonCallouts)
{
try
{
addJSONCallout(callout, dataIface);
}
catch (const std::exception& e)
{
addDebugData(fmt::format(
"Failed extracting callout data from JSON: {}", e.what()));
}
}
}
void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
const DataInterfaceBase& dataIface)
{
auto priority = getPriorityFromJSON(jsonCallout);
std::string locCode;
std::string unexpandedLocCode;
std::unique_ptr<src::Callout> callout;
// Expand the location code if it's there
if (jsonCallout.contains("LocationCode"))
{
unexpandedLocCode = jsonCallout.at("LocationCode").get<std::string>();
try
{
locCode = dataIface.expandLocationCode(unexpandedLocCode, 0);
}
catch (const std::exception& e)
{
addDebugData(fmt::format("Unable to expand location code {}: {}",
unexpandedLocCode, e.what()));
// Use the value from the JSON so at least there's something
locCode = unexpandedLocCode;
}
}
// Create either a procedure, symbolic FRU, or normal FRU callout.
if (jsonCallout.contains("Procedure"))
{
auto procedure = jsonCallout.at("Procedure").get<std::string>();
// If it's the registry name instead of the raw name, convert.
if (pv::maintenanceProcedures.find(procedure) !=
pv::maintenanceProcedures.end())
{
procedure = pv::maintenanceProcedures.at(procedure);
}
callout = std::make_unique<src::Callout>(
static_cast<CalloutPriority>(priority), procedure,
src::CalloutValueType::raw);
}
else if (jsonCallout.contains("SymbolicFRU"))
{
auto fru = jsonCallout.at("SymbolicFRU").get<std::string>();
// If it's the registry name instead of the raw name, convert.
if (pv::symbolicFRUs.find(fru) != pv::symbolicFRUs.end())
{
fru = pv::symbolicFRUs.at(fru);
}
bool trusted = false;
if (jsonCallout.contains("TrustedLocationCode") && !locCode.empty())
{
trusted = jsonCallout.at("TrustedLocationCode").get<bool>();
}
callout = std::make_unique<src::Callout>(
static_cast<CalloutPriority>(priority), fru,
src::CalloutValueType::raw, locCode, trusted);
}
else
{
// A hardware FRU
std::string inventoryPath;
std::vector<src::MRU::MRUCallout> mrus;
if (jsonCallout.contains("InventoryPath"))
{
inventoryPath = jsonCallout.at("InventoryPath").get<std::string>();
}
else
{
if (unexpandedLocCode.empty())
{
throw std::runtime_error{"JSON callout needs either an "
"inventory path or location code"};
}
try
{
auto inventoryPaths = dataIface.getInventoryFromLocCode(
unexpandedLocCode, 0, false);
// Just use first path returned since they all
// point to the same FRU.
inventoryPath = inventoryPaths[0];
}
catch (const std::exception& e)
{
throw std::runtime_error{
fmt::format("Unable to get inventory path from "
"location code: {}: {}",
unexpandedLocCode, e.what())};
}
}
if (jsonCallout.contains("MRUs"))
{
mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
}
// If the location code was also passed in, use that here too
// so addInventoryCallout doesn't have to look it up.
std::optional<std::string> lc;
if (!locCode.empty())
{
lc = locCode;
}
addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
if (jsonCallout.contains("Deconfigured"))
{
if (jsonCallout.at("Deconfigured").get<bool>())
{
setErrorStatusFlag(ErrorStatusFlags::deconfigured);
}
}
if (jsonCallout.contains("Guarded"))
{
if (jsonCallout.at("Guarded").get<bool>())
{
setErrorStatusFlag(ErrorStatusFlags::guarded);
}
}
}
if (callout)
{
createCalloutsObject();
_callouts->addCallout(std::move(callout));
}
}
CalloutPriority SRC::getPriorityFromJSON(const nlohmann::json& json)
{
// Looks like:
// {
// "Priority": "H"
// }
auto p = json.at("Priority").get<std::string>();
if (p.empty())
{
throw std::runtime_error{"Priority field in callout is empty"};
}
auto priority = static_cast<CalloutPriority>(p.front());
// Validate it
auto priorityIt = pv::findByValue(static_cast<uint32_t>(priority),
pv::calloutPriorityValues);
if (priorityIt == pv::calloutPriorityValues.end())
{
throw std::runtime_error{
fmt::format("Invalid priority '{}' found in JSON callout", p)};
}
return priority;
}
std::vector<src::MRU::MRUCallout>
SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
{
std::vector<src::MRU::MRUCallout> mrus;
// Looks like:
// [
// {
// "ID": 100,
// "Priority": "H"
// }
// ]
if (!mruJSON.is_array())
{
addDebugData("MRU callout JSON is not an array");
return mrus;
}
for (const auto& mruCallout : mruJSON)
{
try
{
auto priority = getPriorityFromJSON(mruCallout);
auto id = mruCallout.at("ID").get<uint32_t>();
src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
mrus.push_back(std::move(mru));
}
catch (const std::exception& e)
{
addDebugData(fmt::format("Invalid MRU entry in JSON: {}: {}",
mruCallout.dump(), e.what()));
}
}
return mrus;
}
void SRC::setDumpStatus(const DataInterfaceBase& dataIface)
{
std::vector<bool> dumpStatus{false, false, false};
try
{
std::vector<std::string> dumpType = {"bmc/entry", "resource/entry",
"system/entry"};
dumpStatus = dataIface.checkDumpStatus(dumpType);
// For bmc - set bit 0 of nibble [4-7] bits of byte-1 SP dump
// For resource - set bit 2 of nibble [4-7] bits of byte-2 Hypervisor
// For system - set bit 1 of nibble [4-7] bits of byte-2 HW dump
_hexData[0] |= ((dumpStatus[0] << 19) | (dumpStatus[1] << 9) |
(dumpStatus[2] << 10));
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Checking dump status failed: {}", e.what()).c_str());
}
}
std::vector<uint8_t> SRC::getSrcStruct()
{
std::vector<uint8_t> data;
Stream stream{data};
//------ Ref section 4.3 in PEL doc---
//------ SRC Structure 40 bytes-------
// Byte-0 | Byte-1 | Byte-2 | Byte-3 |
// -----------------------------------
// 02 | 08 | 00 | 09 | ==> Header
// 00 | 00 | 00 | 48 | ==> Header
// 00 | 00 | 00 | 00 | ==> Hex data word-2
// 00 | 00 | 00 | 00 | ==> Hex data word-3
// 00 | 00 | 00 | 00 | ==> Hex data word-4
// 20 | 00 | 00 | 00 | ==> Hex data word-5
// 00 | 00 | 00 | 00 | ==> Hex data word-6
// 00 | 00 | 00 | 00 | ==> Hex data word-7
// 00 | 00 | 00 | 00 | ==> Hex data word-8
// 00 | 00 | 00 | 00 | ==> Hex data word-9
// -----------------------------------
// ASCII string - 8 bytes |
// -----------------------------------
// ASCII space NULL - 24 bytes |
// -----------------------------------
//_size = Base SRC struct: 8 byte header + hex data section + ASCII string
uint8_t flags = (_flags | postOPPanel);
stream << _version << flags << _reserved1B << _wordCount << _reserved2B
<< _size;
for (auto& word : _hexData)
{
stream << word;
}
_asciiString->flatten(stream);
return data;
}
void SRC::setProgressCode(const DataInterfaceBase& dataIface)
{
std::vector<uint8_t> progressSRC;
try
{
progressSRC = dataIface.getRawProgressSRC();
}
catch (const std::exception& e)
{
log<level::ERR>(
fmt::format("Error getting progress code: {}", e.what()).c_str());
return;
}
_hexData[2] = getProgressCode(progressSRC);
}
uint32_t SRC::getProgressCode(std::vector<uint8_t>& rawProgressSRC)
{
uint32_t progressCode = 0;
// A valid progress SRC is at least 72 bytes
if (rawProgressSRC.size() < 72)
{
return progressCode;
}
try
{
// The ASCII string field in progress SRCs starts at offset 40.
// Take the first 8 characters to put in the uint32:
// "CC009189" -> 0xCC009189
Stream stream{rawProgressSRC, 40};
src::AsciiString aString{stream};
auto progressCodeString = aString.get().substr(0, 8);
if (std::all_of(progressCodeString.begin(), progressCodeString.end(),
[](char c) {
return std::isxdigit(static_cast<unsigned char>(c));
}))
{
progressCode = std::stoul(progressCodeString, nullptr, 16);
}
}
catch (const std::exception& e)
{}
return progressCode;
}
} // namespace pels
} // namespace openpower