/* | |
// 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 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() | |
{ | |
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 found 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; | |
std::string buildType; | |
} MetaRevision; | |
// Support both 2 solutions: | |
// 1.Current solution 2.7.0-dev-533-g14dc00e79-5e7d997 | |
// openbmcTag 2.7.0-dev | |
// BuildNo 533 | |
// openbmcHash g14dc00e79 | |
// MetaHasg 5e7d997 | |
// | |
// 2.New solution whtref-0.1-45-023125-a1295e-release | |
// IdStr whtref | |
// Major 0 | |
// Minor 1 | |
// buildNo 45 | |
// openbmcHash 023125 | |
// MetaHash a1295e | |
// BuildType release/CI/<devloperId> | |
std::optional<MetaRevision> convertIntelVersion(std::string& s) | |
{ | |
std::smatch results; | |
MetaRevision rev; | |
std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-(\\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]; | |
rev.buildType = "release"; | |
std::string versionString = | |
rev.platform + ":" + std::to_string(rev.major) + ":" + | |
std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + | |
":" + rev.openbmcHash + ":" + rev.metaHash + ":" + | |
rev.buildType; | |
phosphor::logging::log<phosphor::logging::level::INFO>( | |
versionString.c_str()); | |
return rev; | |
} | |
} | |
constexpr size_t matchedIntel = 8; | |
std::regex pattern2( | |
"(\\w+?)-(\\d+?).(\\d+?)-(\\d+?)-(\\w+?)-(\\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[5]; | |
rev.metaHash = results[6]; | |
rev.buildType = results[7]; | |
std::string versionString = | |
rev.platform + ":" + std::to_string(rev.major) + ":" + | |
std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + | |
":" + rev.openbmcHash + ":" + rev.metaHash + ":" + | |
rev.buildType; | |
phosphor::logging::log<phosphor::logging::level::INFO>( | |
versionString.c_str()); | |
return rev; | |
} | |
} | |
return std::nullopt; | |
} | |
} // namespace | |
auto ipmiAppGetDeviceId() -> 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 | |
> | |
{ | |
std::optional<MetaRevision> rev; | |
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) | |
{ | |
try | |
{ | |
auto version = getActiveSoftwareVersionInfo(); | |
rev = convertIntelVersion(version); | |
} | |
catch (const std::exception& e) | |
{ | |
Log::log<Log::level::ERR>(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; | |
devId.aux = revision.buildNo; | |
} | |
// 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> | |
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp, | |
ipmi::app::cmdGetDeviceId, ipmi::Privilege::User, | |
ipmiAppGetDeviceId); | |
return; | |
} | |
} // namespace ipmi |