| #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); |
| } |