blob: ebf95bed57545511fc8623325abd302d04f3b465 [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 "xyz/openbmc_project/Common/error.hpp"
#include <fstream>
#include <ipmid/api.hpp>
#include <ipmid/utils.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <regex>
#include <xyz/openbmc_project/Software/Activation/server.hpp>
#include <xyz/openbmc_project/Software/Version/server.hpp>
#include <xyz/openbmc_project/State/BMC/server.hpp>
namespace ipmi
{
static void registerAPPFunctions() __attribute__((constructor));
namespace Log = phosphor::logging;
namespace Error = sdbusplus::xyz::openbmc_project::Common::Error;
using Version = sdbusplus::xyz::openbmc_project::Software::server::Version;
using Activation =
sdbusplus::xyz::openbmc_project::Software::server::Activation;
using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC;
constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
constexpr auto bmc_state_property = "CurrentBMCState";
namespace
{
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";
bool getCurrentBmcState()
{
sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
// Get the Inventory object implementing the BMC interface
ipmi::DbusObjectInfo bmcObject =
ipmi::getDbusObject(bus, bmc_state_interface);
auto variant =
ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,
bmc_state_interface, bmc_state_property);
return std::holds_alternative<std::string>(variant) &&
BMC::convertBMCStateFromString(std::get<std::string>(variant)) ==
BMC::BMCState::Ready;
}
bool getCurrentBmcStateWithFallback(const bool fallbackAvailability)
{
try
{
return getCurrentBmcState();
}
catch (...)
{
// Nothing provided the BMC interface, therefore return whatever was
// configured as the default.
return fallbackAvailability;
}
}
/**
* @brief Returns the Version info from primary software 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 software object.
*
*/
std::string getActiveSoftwareVersionInfo()
{
auto busp = getSdBus();
std::string revision{};
ipmi::ObjectTree objectTree;
try
{
objectTree =
ipmi::getAllDbusObjects(*busp, softwareRoot, redundancyIntf);
}
catch (sdbusplus::exception::SdBusError& e)
{
Log::log<Log::level::ERR>("Failed to fetch redundancy object from dbus",
Log::entry("INTERFACE=%s", redundancyIntf),
Log::entry("ERRMSG=%s", e.what()));
}
auto objectFound = false;
for (auto& softObject : objectTree)
{
auto service =
ipmi::getService(*busp, redundancyIntf, softObject.first);
auto objValueTree =
ipmi::getManagedObjects(*busp, 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)
{
Log::log<Log::level::ERR>(e.what());
}
}
}
if (!objectFound)
{
Log::log<Log::level::ERR>("Could not find an BMC software object");
}
return revision;
}
typedef struct
{
std::string platform;
uint8_t major;
uint8_t minor;
uint32_t buildNo;
std::string openbmcHash;
std::string metaHash;
} MetaRevision;
// 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
// 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;
Log::log<Log::level::INFO>(
"Get BMC version",
Log::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];
rev.major = static_cast<uint8_t>(std::stoi(results[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;
Log::log<Log::level::INFO>(
"Get BMC version",
Log::entry("VERSION=%s", versionString.c_str()));
return rev;
}
}
return std::nullopt;
}
} // namespace
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()
{
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);
if (!dev_id_initialized)
{
std::optional<MetaRevision> rev;
try
{
auto version = getActiveSoftwareVersionInfo();
rev = convertIntelVersion(version);
}
catch (const std::exception& e)
{
Log::log<Log::level::ERR>("Failed to get active version info",
Log::entry("ERROR=%s", e.what()));
}
if (rev.has_value())
{
// 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.
MetaRevision revision = rev.value();
devId.fw[0] = revision.major & ipmiDevIdFw1Mask;
revision.minor = (revision.minor > 99 ? 99 : revision.minor);
devId.fw[1] = revision.minor % 10 + (revision.minor / 10) * 16;
try
{
uint32_t hash = std::stoul(revision.metaHash, 0, 16);
hash = ((hash & 0xff000000) >> 24) |
((hash & 0x00FF0000) >> 8) | ((hash & 0x0000FF00) << 8) |
((hash & 0xFF) << 24);
devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
}
catch (const std::exception& e)
{
Log::log<Log::level::ERR>("Failed to convert git hash",
Log::entry("ERROR=%s", e.what()));
}
}
// 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);
try
{
auto busp = getSdBus();
const ipmi::DbusObjectInfo& object = ipmi::getDbusObject(
*busp, "xyz.openbmc_project.Inventory.Item.Board",
"/xyz/openbmc_project/inventory/system/board/",
"Baseboard");
const ipmi::Value& propValue = ipmi::getDbusProperty(
*busp, object.second, object.first,
"xyz.openbmc_project.Inventory.Item.Board",
"ProductId");
devId.prodId =
static_cast<uint8_t>(std::get<uint64_t>(propValue));
}
catch (std::exception& e)
{
devId.prodId = data.value("prod_id", 0);
}
// 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
{
Log::log<Log::level::ERR>("Device ID JSON parser failure");
return ipmi::responseUnspecifiedError();
}
}
else
{
Log::log<Log::level::ERR>("Device ID file not found");
return ipmi::responseUnspecifiedError();
}
}
// Set availability to the actual current BMC state
devId.fw[0] &= ipmiDevIdFw1Mask;
if (!getCurrentBmcStateWithFallback(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);
}
static void registerAPPFunctions(void)
{
Log::log<Log::level::INFO>("Registering App commands");
// <Get Device ID>
registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
ipmiAppGetDeviceId);
}
} // namespace ipmi