blob: 678f71a96e8546f9edbf103f75037ee23f82f27e [file] [log] [blame]
#include "config.h"
#include <ipmid/api.hpp>
#include <ipmid/types.hpp>
#include <ipmid/utils.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/message/types.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <xyz/openbmc_project/Software/Activation/server.hpp>
#include <xyz/openbmc_project/Software/Version/server.hpp>
#include <xyz/openbmc_project/State/BMC/server.hpp>
#include <algorithm>
#include <array>
#include <charconv>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <memory>
#include <regex>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
constexpr auto bmcStateInterface = "xyz.openbmc_project.State.BMC";
constexpr auto bmcStateProperty = "CurrentBMCState";
static constexpr auto redundancyIntf =
"xyz.openbmc_project.Software.RedundancyPriority";
static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
static constexpr auto activationIntf =
"xyz.openbmc_project.Software.Activation";
static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
void registerNetFnAppFunctions() __attribute__((constructor));
using namespace phosphor::logging;
using namespace sdbusplus::error::xyz::openbmc_project::common;
using Version = sdbusplus::server::xyz::openbmc_project::software::Version;
using Activation =
sdbusplus::server::xyz::openbmc_project::software::Activation;
using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
namespace fs = std::filesystem;
/**
* @brief Returns the Version info from primary s/w object
*
* Get the Version info from the active s/w object which is having high
* "Priority" value(a smaller number is a higher priority) and "Purpose"
* is "BMC" from the list of all s/w objects those are implementing
* RedundancyPriority interface from the given softwareRoot path.
*
* @return On success returns the Version info from primary s/w object.
*
*/
std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx)
{
std::string revision{};
ipmi::ObjectTree objectTree;
try
{
objectTree =
ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error("Failed to fetch redundancy object from dbus, "
"interface: {INTERFACE}, error: {ERROR}",
"INTERFACE", redundancyIntf, "ERROR", e);
elog<InternalFailure>();
}
auto objectFound = false;
for (auto& softObject : objectTree)
{
auto service =
ipmi::getService(*ctx->bus, redundancyIntf, softObject.first);
auto objValueTree =
ipmi::getManagedObjects(*ctx->bus, service, softwareRoot);
auto minPriority = 0xFF;
for (const auto& objIter : objValueTree)
{
try
{
auto& intfMap = objIter.second;
auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
auto& versionProps = intfMap.at(versionIntf);
auto& activationProps = intfMap.at(activationIntf);
auto priority =
std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
auto purpose =
std::get<std::string>(versionProps.at("Purpose"));
auto activation =
std::get<std::string>(activationProps.at("Activation"));
auto version =
std::get<std::string>(versionProps.at("Version"));
if ((Version::convertVersionPurposeFromString(purpose) ==
Version::VersionPurpose::BMC) &&
(Activation::convertActivationsFromString(activation) ==
Activation::Activations::Active))
{
if (priority < minPriority)
{
minPriority = priority;
objectFound = true;
revision = std::move(version);
}
}
}
catch (const std::exception& e)
{
lg2::error("error message: {ERROR}", "ERROR", e);
}
}
}
if (!objectFound)
{
lg2::error("Could not found an BMC software Object");
elog<InternalFailure>();
}
return revision;
}
bool getCurrentBmcStateWithFallback(ipmi::Context::ptr& ctx,
const bool fallbackAvailability)
{
// Get the Inventory object implementing the BMC interface
ipmi::DbusObjectInfo bmcObject{};
boost::system::error_code ec =
ipmi::getDbusObject(ctx, bmcStateInterface, bmcObject);
std::string bmcState{};
if (ec.value())
{
return fallbackAvailability;
}
ec = ipmi::getDbusProperty(ctx, bmcObject.second, bmcObject.first,
bmcStateInterface, bmcStateProperty, bmcState);
if (!ec.value())
{
return fallbackAvailability;
}
return BMC::convertBMCStateFromString(bmcState) == BMC::BMCState::Ready;
}
typedef struct
{
char major;
char minor;
uint8_t aux[4];
} Revision;
/* Use regular expression searching matched pattern X.Y, and convert it to */
/* Major (X) and Minor (Y) version. */
/* Example: */
/* version = 2.14.0-dev */
/* ^ ^ */
/* | |---------------- Minor */
/* |------------------ Major */
/* */
/* Default regex string only tries to match Major and Minor version. */
/* */
/* To match more firmware version info, platforms need to define it own */
/* regex string to match more strings, and assign correct mapping index in */
/* matches array. */
/* */
/* matches[0]: matched index for major ver */
/* matches[1]: matched index for minor ver */
/* matches[2]: matched index for aux[0] (set 0 to skip) */
/* matches[3]: matched index for aux[1] (set 0 to skip) */
/* matches[4]: matched index for aux[2] (set 0 to skip) */
/* matches[5]: matched index for aux[3] (set 0 to skip) */
/* Example: */
/* regex = "([\d]+).([\d]+).([\d]+)-dev-([\d]+)-g([0-9a-fA-F]{2}) */
/* ([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})" */
/* matches = {1,2,5,6,7,8} */
/* version = 2.14.0-dev-750-g37a7c5ad1-dirty */
/* ^ ^ ^ ^ ^ ^ ^ ^ */
/* | | | | | | | | */
/* | | | | | | | |-- Aux byte 3 (0xAD), index 8 */
/* | | | | | | |---- Aux byte 2 (0xC5), index 7 */
/* | | | | | |------ Aux byte 1 (0xA7), index 6 */
/* | | | | |-------- Aux byte 0 (0x37), index 5 */
/* | | | |------------- Not used, index 4 */
/* | | |------------------- Not used, index 3 */
/* | |---------------------- Minor (14), index 2 */
/* |------------------------ Major (2), index 1 */
int convertVersion(std::string s, Revision& rev)
{
static const std::vector<size_t> matches = {
MAJOR_MATCH_INDEX, MINOR_MATCH_INDEX, AUX_0_MATCH_INDEX,
AUX_1_MATCH_INDEX, AUX_2_MATCH_INDEX, AUX_3_MATCH_INDEX};
std::regex fw_regex(FW_VER_REGEX);
std::smatch m;
Revision r = {0};
size_t val;
if (std::regex_search(s, m, fw_regex))
{
if (m.size() < *std::max_element(matches.begin(), matches.end()))
{ // max index higher than match count
return -1;
}
// convert major
{
std::string str = m[matches[0]].str();
const auto& [ptr, ec] =
std::from_chars(str.data(), str.data() + str.size(), val);
if (ec != std::errc() || ptr != str.data() + str.size())
{ // failed to convert major string
return -1;
}
if (val >= 2000)
{ // For the platforms use year as major version, it would expect to
// have major version between 0 - 99. If the major version is
// greater than or equal to 2000, it is treated as a year and
// converted to 0 - 99.
r.major = val % 100;
}
else
{
r.major = val & 0x7F;
}
}
// convert minor
{
std::string str = m[matches[1]].str();
const auto& [ptr, ec] =
std::from_chars(str.data(), str.data() + str.size(), val);
if (ec != std::errc() || ptr != str.data() + str.size())
{ // failed to convert minor string
return -1;
}
r.minor = val & 0xFF;
}
// convert aux bytes
{
size_t i;
for (i = 0; i < 4; i++)
{
if (matches[i + 2] == 0)
{
continue;
}
std::string str = m[matches[i + 2]].str();
const char* cstr = str.c_str();
auto [ptr,
ec] = std::from_chars(cstr, cstr + str.size(), val, 16);
if (ec != std::errc() || ptr != cstr + str.size())
{ // failed to convert aux byte string
break;
}
r.aux[i] = val & 0xFF;
}
if (i != 4)
{ // something wrong durign converting aux bytes
return -1;
}
}
// all matched
rev = r;
return 0;
}
return -1;
}
/* @brief: Implement the Get Device ID IPMI command per the IPMI spec
* @param[in] ctx - shared_ptr to an IPMI context struct
*
* @returns IPMI completion code plus response data
* - Device ID (manufacturer defined)
* - Device revision[4 bits]; reserved[3 bits]; SDR support[1 bit]
* - FW revision major[7 bits] (binary encoded); available[1 bit]
* - FW Revision minor (BCD encoded)
* - IPMI version (0x02 for IPMI 2.0)
* - device support (bitfield of supported options)
* - MFG IANA ID (3 bytes)
* - product ID (2 bytes)
* - AUX info (4 bytes)
*/
ipmi::RspType<uint8_t, // Device ID
uint8_t, // Device Revision
uint8_t, // Firmware Revision Major
uint8_t, // Firmware Revision minor
uint8_t, // IPMI version
uint8_t, // Additional device support
uint24_t, // MFG ID
uint16_t, // Product ID
uint32_t // AUX info
>
ipmiAppGetDeviceId([[maybe_unused]] ipmi::Context::ptr ctx)
{
static struct
{
uint8_t id;
uint8_t revision;
uint8_t fw[2];
uint8_t ipmiVer;
uint8_t addnDevSupport;
uint24_t manufId;
uint16_t prodId;
uint32_t aux;
} devId;
static bool dev_id_initialized = false;
static bool defaultActivationSetting = true;
const char* filename = "/usr/share/ipmi-providers/dev_id.json";
constexpr auto ipmiDevIdStateShift = 7;
constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
static bool haveBMCVersion = false;
if (!haveBMCVersion || !dev_id_initialized)
{
int r = -1;
Revision rev = {0, 0, {0, 0, 0, 0}};
try
{
auto version = getActiveSoftwareVersionInfo(ctx);
r = convertVersion(version, rev);
}
catch (const std::exception& e)
{
lg2::error("error message: {ERROR}", "ERROR", e);
}
if (r >= 0)
{
// bit7 identifies if the device is available
// 0=normal operation
// 1=device firmware, SDR update,
// or self-initialization in progress.
// The availability may change in run time, so mask here
// and initialize later.
devId.fw[0] = rev.major & ipmiDevIdFw1Mask;
rev.minor = (rev.minor > 99 ? 99 : rev.minor);
devId.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16;
std::memcpy(&devId.aux, rev.aux, sizeof(rev.aux));
haveBMCVersion = true;
}
}
if (!dev_id_initialized)
{
// IPMI Spec version 2.0
devId.ipmiVer = 2;
std::ifstream devIdFile(filename);
if (devIdFile.is_open())
{
auto data = nlohmann::json::parse(devIdFile, nullptr, false);
if (!data.is_discarded())
{
devId.id = data.value("id", 0);
devId.revision = data.value("revision", 0);
devId.addnDevSupport = data.value("addn_dev_support", 0);
devId.manufId = data.value("manuf_id", 0);
devId.prodId = data.value("prod_id", 0);
if (!(AUX_0_MATCH_INDEX || AUX_1_MATCH_INDEX ||
AUX_2_MATCH_INDEX || AUX_3_MATCH_INDEX))
{
devId.aux = data.value("aux", 0);
}
if (data.contains("firmware_revision"))
{
const auto& firmwareRevision = data.at("firmware_revision");
if (firmwareRevision.contains("major"))
{
firmwareRevision.at("major").get_to(devId.fw[0]);
}
if (firmwareRevision.contains("minor"))
{
firmwareRevision.at("minor").get_to(devId.fw[1]);
}
}
// Set the availablitity of the BMC.
defaultActivationSetting = data.value("availability", true);
// Don't read the file every time if successful
dev_id_initialized = true;
}
else
{
lg2::error("Device ID JSON parser failure");
return ipmi::responseUnspecifiedError();
}
}
else
{
lg2::error("Device ID file not found");
return ipmi::responseUnspecifiedError();
}
}
// Set availability to the actual current BMC state
devId.fw[0] &= ipmiDevIdFw1Mask;
if (!getCurrentBmcStateWithFallback(ctx, defaultActivationSetting))
{
devId.fw[0] |= (1 << ipmiDevIdStateShift);
}
return ipmi::responseSuccess(
devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
}
void registerNetFnAppFunctions()
{
// OEM libraries should use ipmi::prioOemBase to override default
// implementation of IPMI commands that use ipmi::prioOpenBmcBase
// <Get Device ID>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp,
ipmi::app::cmdGetDeviceId, ipmi::Privilege::User,
ipmiAppGetDeviceId);
}