blob: 8f00e543b4499d831b38a1fc7e482d10399897d1 [file] [log] [blame]
/*
// Copyright (c) 2019 Intel 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 <byteswap.h>
#include <appcommands.hpp>
#include <ipmid/api.hpp>
#include <ipmid/utils.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <types.hpp>
#include <fstream>
#include <regex>
namespace ipmi
{
static void registerAPPFunctions() __attribute__((constructor));
static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC";
static constexpr const char* softwareVerIntf =
"xyz.openbmc_project.Software.Version";
static constexpr const char* softwareActivationIntf =
"xyz.openbmc_project.Software.Activation";
static constexpr const char* associationIntf =
"xyz.openbmc_project.Association";
static constexpr const char* softwareFunctionalPath =
"/xyz/openbmc_project/software/functional";
static constexpr const char* currentBmcStateProp = "CurrentBMCState";
static constexpr const char* bmcStateReadyStr =
"xyz.openbmc_project.State.BMC.BMCState.Ready";
static std::unique_ptr<sdbusplus::bus::match_t> bmcStateChangedSignal;
static uint8_t bmcDeviceBusy = true;
int initBMCDeviceState(ipmi::Context::ptr ctx)
{
DbusObjectInfo objInfo;
boost::system::error_code ec = ipmi::getDbusObject(ctx, bmcStateIntf, "/",
"bmc0", objInfo);
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"initBMCDeviceState: Failed to perform GetSubTree action",
phosphor::logging::entry("ERROR=%s", ec.message().c_str()),
phosphor::logging::entry("INTERFACE=%s", bmcStateIntf));
return -1;
}
std::string bmcState;
ec = ipmi::getDbusProperty(ctx, objInfo.second, objInfo.first, bmcStateIntf,
currentBmcStateProp, bmcState);
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"initBMCDeviceState: Failed to get CurrentBMCState property",
phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
return -1;
}
bmcDeviceBusy = (bmcState != bmcStateReadyStr);
phosphor::logging::log<phosphor::logging::level::INFO>(
"BMC device state updated");
// BMC state may change runtime while doing firmware udpate.
// Register for property change signal to update state.
bmcStateChangedSignal = std::make_unique<sdbusplus::bus::match_t>(
*(ctx->bus),
sdbusplus::bus::match::rules::propertiesChanged(objInfo.first,
bmcStateIntf),
[](sdbusplus::message_t& msg) {
std::map<std::string, ipmi::DbusVariant> props;
std::vector<std::string> inVal;
std::string iface;
try
{
msg.read(iface, props, inVal);
}
catch (const std::exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Exception caught in Get CurrentBMCState");
return;
}
auto it = props.find(currentBmcStateProp);
if (it != props.end())
{
std::string* state = std::get_if<std::string>(&it->second);
if (state)
{
bmcDeviceBusy = (*state != bmcStateReadyStr);
phosphor::logging::log<phosphor::logging::level::INFO>(
"BMC device state updated");
}
}
});
return 0;
}
/**
* @brief Returns the functional firmware version information.
*
* It reads the active firmware versions by checking functional
* endpoints association and matching the input version purpose string.
* ctx[in] - ipmi context.
* reqVersionPurpose[in] - Version purpose which need to be read.
* version[out] - Output Version string.
*
* @return Returns '0' on success and '-1' on failure.
*
*/
int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx,
const std::string& reqVersionPurpose,
std::string& version)
{
std::vector<std::string> activeEndPoints;
boost::system::error_code ec = ipmi::getDbusProperty(
ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf,
"endpoints", activeEndPoints);
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to get Active firmware version endpoints.");
return -1;
}
for (auto& activeEndPoint : activeEndPoints)
{
std::string serviceName;
ec = ipmi::getService(ctx, softwareActivationIntf, activeEndPoint,
serviceName);
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to perform getService.",
phosphor::logging::entry("OBJPATH=%s", activeEndPoint.c_str()));
continue;
}
PropertyMap propMap;
ec = ipmi::getAllDbusProperties(ctx, serviceName, activeEndPoint,
softwareVerIntf, propMap);
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to perform GetAll on Version interface.",
phosphor::logging::entry("SERVICE=%s", serviceName.c_str()),
phosphor::logging::entry("PATH=%s", activeEndPoint.c_str()));
continue;
}
std::string* purposeProp =
std::get_if<std::string>(&propMap["Purpose"]);
std::string* versionProp =
std::get_if<std::string>(&propMap["Version"]);
if (!purposeProp || !versionProp)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to get version or purpose property");
continue;
}
// Check for requested version information and return if found.
if (*purposeProp == reqVersionPurpose)
{
version = *versionProp;
return 0;
}
}
phosphor::logging::log<phosphor::logging::level::INFO>(
"Failed to find version information.",
phosphor::logging::entry("PURPOSE=%s", reqVersionPurpose.c_str()));
return -1;
}
// Support both 2 solutions:
// 1.Current solution 2.7.0-dev-533-g14dc00e79-5e7d997
// openbmcTag 2.7.0-dev
// BuildNo 533
// openbmcHash 14dc00e79
// MetaHasg 5e7d997
//
// 2.New solution wht-0.2-3-gab3500-38384ac or wht-2000.2.3-gab3500-38384ac
// IdStr wht
// Major 0
// Minor 2
// buildNo 3
// MetaHash ab3500
// openbmcHash 38384ac
std::optional<MetaRevision> convertIntelVersion(std::string& s)
{
std::smatch results;
MetaRevision rev;
std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)");
constexpr size_t matchedPhosphor = 6;
if (std::regex_match(s, results, pattern1))
{
if (results.size() == matchedPhosphor)
{
rev.platform = "whtref";
rev.major = static_cast<uint8_t>(std::stoi(results[1]));
rev.minor = static_cast<uint8_t>(std::stoi(results[2]));
rev.buildNo = static_cast<uint32_t>(std::stoi(results[3]));
rev.openbmcHash = results[4];
rev.metaHash = results[5];
std::string versionString =
rev.platform + ":" + std::to_string(rev.major) + ":" +
std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
":" + rev.openbmcHash + ":" + rev.metaHash;
phosphor::logging::log<phosphor::logging::level::INFO>(
"Get BMC version",
phosphor::logging::entry("VERSION=%s", versionString.c_str()));
return rev;
}
}
constexpr size_t matchedIntel = 7;
std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)[-.](\\d+?)-g(\\w+?)-(\\w+?)");
if (std::regex_match(s, results, pattern2))
{
if (results.size() == matchedIntel)
{
rev.platform = results[1];
std::string majorVer = results[2].str();
// Take only the last two digits of the major version
rev.major = static_cast<uint8_t>(
std::stoi(majorVer.substr(majorVer.size() - 2)));
rev.minor = static_cast<uint8_t>(std::stoi(results[3]));
rev.buildNo = static_cast<uint32_t>(std::stoi(results[4]));
rev.openbmcHash = results[6];
rev.metaHash = results[5];
std::string versionString =
rev.platform + ":" + std::to_string(rev.major) + ":" +
std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
":" + rev.openbmcHash + ":" + rev.metaHash;
phosphor::logging::log<phosphor::logging::level::INFO>(
"Get BMC version",
phosphor::logging::entry("VERSION=%s", versionString.c_str()));
return rev;
}
}
return std::nullopt;
}
RspType<uint8_t, // Device ID
uint8_t, // Device Revision
uint7_t, // Firmware Revision Major
bool, // Device available(0=NormalMode,1=DeviceFirmware)
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(ipmi::Context::ptr ctx)
{
static struct
{
uint8_t id;
uint8_t revision;
uint7_t fwMajor;
bool devBusy;
uint8_t fwMinor;
uint8_t ipmiVer = 2;
uint8_t addnDevSupport;
uint24_t manufId;
uint16_t prodId;
uint32_t aux;
} devId;
static bool fwVerInitialized = false;
static bool devIdInitialized = false;
static bool bmcStateInitialized = false;
const char* filename = "/usr/share/ipmi-providers/dev_id.json";
const char* prodIdFilename = "/var/cache/private/prodID";
if (!fwVerInitialized)
{
std::string versionString;
if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC,
versionString))
{
std::optional<MetaRevision> rev =
convertIntelVersion(versionString);
if (rev.has_value())
{
MetaRevision revision = rev.value();
devId.fwMajor = static_cast<uint7_t>(revision.major);
revision.minor = (revision.minor > 99 ? 99 : revision.minor);
devId.fwMinor = revision.minor % 10 +
(revision.minor / 10) * 16;
try
{
uint32_t hash = std::stoul(revision.metaHash, 0, 16);
hash = bswap_32(hash);
devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
fwVerInitialized = true;
}
catch (const std::exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to convert git hash",
phosphor::logging::entry("ERROR=%s", e.what()));
}
}
}
}
if (!devIdInitialized)
{
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);
}
else
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Device ID JSON parser failure");
return ipmi::responseUnspecifiedError();
}
}
else
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Device ID file not found");
return ipmi::responseUnspecifiedError();
}
// Determine the Product ID. Using the DBus system is painfully slow at
// boot time. Avoid using DBus to get the Product ID. The Product ID is
// stored in a non-volatile file now. The /usr/bin/checkFru.sh script,
// run during bootup, will populate the productIdFile.
std::fstream prodIdFile(prodIdFilename);
if (prodIdFile.is_open())
{
std::string id = "0x00";
char* end;
prodIdFile.getline(&id[0], id.size() + 1);
devId.prodId = std::strtol(&id[0], &end, 0);
devIdInitialized = true;
}
else
{
// For any exception send out platform id as 0,
// and make sure to re-query the device id.
devIdInitialized = false;
devId.prodId = 0;
}
}
if (!bmcStateInitialized)
{
if (!initBMCDeviceState(ctx))
{
bmcStateInitialized = true;
}
}
return ipmi::responseSuccess(devId.id, devId.revision, devId.fwMajor,
bmcDeviceBusy, devId.fwMinor, devId.ipmiVer,
devId.addnDevSupport, devId.manufId,
devId.prodId, devId.aux);
}
static void registerAPPFunctions(void)
{
// <Get Device ID>
registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
ipmiAppGetDeviceId);
}
} // namespace ipmi