/* // 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 #include #include #include #include #include #include #include #include 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 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( "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( "initBMCDeviceState: Failed to get CurrentBMCState property", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return -1; } bmcDeviceBusy = (bmcState != bmcStateReadyStr); phosphor::logging::log( "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( *(ctx->bus), sdbusplus::bus::match::rules::propertiesChanged(objInfo.first, bmcStateIntf), [](sdbusplus::message_t& msg) { std::map props; std::vector inVal; std::string iface; try { msg.read(iface, props, inVal); } catch (const std::exception& e) { phosphor::logging::log( "Exception caught in Get CurrentBMCState"); return; } auto it = props.find(currentBmcStateProp); if (it != props.end()) { std::string* state = std::get_if(&it->second); if (state) { bmcDeviceBusy = (*state != bmcStateReadyStr); phosphor::logging::log( "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 activeEndPoints; boost::system::error_code ec = ipmi::getDbusProperty( ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf, "endpoints", activeEndPoints); if (ec) { phosphor::logging::log( "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( "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( "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(&propMap["Purpose"]); std::string* versionProp = std::get_if(&propMap["Version"]); if (!purposeProp || !versionProp) { phosphor::logging::log( "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( "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 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(std::stoi(results[1])); rev.minor = static_cast(std::stoi(results[2])); rev.buildNo = static_cast(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( "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( std::stoi(majorVer.substr(majorVer.size() - 2))); rev.minor = static_cast(std::stoi(results[3])); rev.buildNo = static_cast(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( "Get BMC version", phosphor::logging::entry("VERSION=%s", versionString.c_str())); return rev; } } return std::nullopt; } RspType 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 rev = convertIntelVersion(versionString); if (rev.has_value()) { MetaRevision revision = rev.value(); devId.fwMajor = static_cast(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( "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( "Device ID JSON parser failure"); return ipmi::responseUnspecifiedError(); } } else { phosphor::logging::log( "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) { // registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User, ipmiAppGetDeviceId); } } // namespace ipmi