| /* | |
| // 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 |