1 /* 2 // Copyright (c) 2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #include <byteswap.h> 17 18 #include <appcommands.hpp> 19 #include <ipmid/api.hpp> 20 #include <ipmid/utils.hpp> 21 #include <nlohmann/json.hpp> 22 #include <phosphor-logging/log.hpp> 23 #include <types.hpp> 24 25 #include <fstream> 26 #include <regex> 27 28 namespace ipmi 29 { 30 31 static void registerAPPFunctions() __attribute__((constructor)); 32 33 static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC"; 34 static constexpr const char* softwareVerIntf = 35 "xyz.openbmc_project.Software.Version"; 36 static constexpr const char* softwareActivationIntf = 37 "xyz.openbmc_project.Software.Activation"; 38 static constexpr const char* associationIntf = 39 "xyz.openbmc_project.Association"; 40 static constexpr const char* softwareFunctionalPath = 41 "/xyz/openbmc_project/software/functional"; 42 43 static constexpr const char* currentBmcStateProp = "CurrentBMCState"; 44 static constexpr const char* bmcStateReadyStr = 45 "xyz.openbmc_project.State.BMC.BMCState.Ready"; 46 47 static std::unique_ptr<sdbusplus::bus::match_t> bmcStateChangedSignal; 48 static uint8_t bmcDeviceBusy = true; 49 50 int initBMCDeviceState(ipmi::Context::ptr ctx) 51 { 52 DbusObjectInfo objInfo; 53 boost::system::error_code ec = ipmi::getDbusObject(ctx, bmcStateIntf, "/", 54 "bmc0", objInfo); 55 if (ec) 56 { 57 phosphor::logging::log<phosphor::logging::level::ERR>( 58 "initBMCDeviceState: Failed to perform GetSubTree action", 59 phosphor::logging::entry("ERROR=%s", ec.message().c_str()), 60 phosphor::logging::entry("INTERFACE=%s", bmcStateIntf)); 61 return -1; 62 } 63 64 std::string bmcState; 65 ec = ipmi::getDbusProperty(ctx, objInfo.second, objInfo.first, bmcStateIntf, 66 currentBmcStateProp, bmcState); 67 if (ec) 68 { 69 phosphor::logging::log<phosphor::logging::level::ERR>( 70 "initBMCDeviceState: Failed to get CurrentBMCState property", 71 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 72 return -1; 73 } 74 75 bmcDeviceBusy = (bmcState != bmcStateReadyStr); 76 77 phosphor::logging::log<phosphor::logging::level::INFO>( 78 "BMC device state updated"); 79 80 // BMC state may change runtime while doing firmware udpate. 81 // Register for property change signal to update state. 82 bmcStateChangedSignal = std::make_unique<sdbusplus::bus::match_t>( 83 *(ctx->bus), 84 sdbusplus::bus::match::rules::propertiesChanged(objInfo.first, 85 bmcStateIntf), 86 [](sdbusplus::message_t& msg) { 87 std::map<std::string, ipmi::DbusVariant> props; 88 std::vector<std::string> inVal; 89 std::string iface; 90 try 91 { 92 msg.read(iface, props, inVal); 93 } 94 catch (const std::exception& e) 95 { 96 phosphor::logging::log<phosphor::logging::level::ERR>( 97 "Exception caught in Get CurrentBMCState"); 98 return; 99 } 100 101 auto it = props.find(currentBmcStateProp); 102 if (it != props.end()) 103 { 104 std::string* state = std::get_if<std::string>(&it->second); 105 if (state) 106 { 107 bmcDeviceBusy = (*state != bmcStateReadyStr); 108 phosphor::logging::log<phosphor::logging::level::INFO>( 109 "BMC device state updated"); 110 } 111 } 112 }); 113 114 return 0; 115 } 116 117 /** 118 * @brief Returns the functional firmware version information. 119 * 120 * It reads the active firmware versions by checking functional 121 * endpoints association and matching the input version purpose string. 122 * ctx[in] - ipmi context. 123 * reqVersionPurpose[in] - Version purpose which need to be read. 124 * version[out] - Output Version string. 125 * 126 * @return Returns '0' on success and '-1' on failure. 127 * 128 */ 129 int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx, 130 const std::string& reqVersionPurpose, 131 std::string& version) 132 { 133 std::vector<std::string> activeEndPoints; 134 boost::system::error_code ec = ipmi::getDbusProperty( 135 ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf, 136 "endpoints", activeEndPoints); 137 if (ec) 138 { 139 phosphor::logging::log<phosphor::logging::level::ERR>( 140 "Failed to get Active firmware version endpoints."); 141 return -1; 142 } 143 144 for (auto& activeEndPoint : activeEndPoints) 145 { 146 std::string serviceName; 147 ec = ipmi::getService(ctx, softwareActivationIntf, activeEndPoint, 148 serviceName); 149 if (ec) 150 { 151 phosphor::logging::log<phosphor::logging::level::ERR>( 152 "Failed to perform getService.", 153 phosphor::logging::entry("OBJPATH=%s", activeEndPoint.c_str())); 154 continue; 155 } 156 157 PropertyMap propMap; 158 ec = ipmi::getAllDbusProperties(ctx, serviceName, activeEndPoint, 159 softwareVerIntf, propMap); 160 if (ec) 161 { 162 phosphor::logging::log<phosphor::logging::level::ERR>( 163 "Failed to perform GetAll on Version interface.", 164 phosphor::logging::entry("SERVICE=%s", serviceName.c_str()), 165 phosphor::logging::entry("PATH=%s", activeEndPoint.c_str())); 166 continue; 167 } 168 169 std::string* purposeProp = 170 std::get_if<std::string>(&propMap["Purpose"]); 171 std::string* versionProp = 172 std::get_if<std::string>(&propMap["Version"]); 173 if (!purposeProp || !versionProp) 174 { 175 phosphor::logging::log<phosphor::logging::level::ERR>( 176 "Failed to get version or purpose property"); 177 continue; 178 } 179 180 // Check for requested version information and return if found. 181 if (*purposeProp == reqVersionPurpose) 182 { 183 version = *versionProp; 184 return 0; 185 } 186 } 187 188 phosphor::logging::log<phosphor::logging::level::INFO>( 189 "Failed to find version information.", 190 phosphor::logging::entry("PURPOSE=%s", reqVersionPurpose.c_str())); 191 return -1; 192 } 193 194 // Support both 2 solutions: 195 // 1.Current solution 2.7.0-dev-533-g14dc00e79-5e7d997 196 // openbmcTag 2.7.0-dev 197 // BuildNo 533 198 // openbmcHash 14dc00e79 199 // MetaHasg 5e7d997 200 // 201 // 2.New solution wht-0.2-3-gab3500-38384ac or wht-2000.2.3-gab3500-38384ac 202 // IdStr wht 203 // Major 0 204 // Minor 2 205 // buildNo 3 206 // MetaHash ab3500 207 // openbmcHash 38384ac 208 std::optional<MetaRevision> convertIntelVersion(std::string& s) 209 { 210 std::smatch results; 211 MetaRevision rev; 212 std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)"); 213 constexpr size_t matchedPhosphor = 6; 214 if (std::regex_match(s, results, pattern1)) 215 { 216 if (results.size() == matchedPhosphor) 217 { 218 rev.platform = "whtref"; 219 rev.major = static_cast<uint8_t>(std::stoi(results[1])); 220 rev.minor = static_cast<uint8_t>(std::stoi(results[2])); 221 rev.buildNo = static_cast<uint32_t>(std::stoi(results[3])); 222 rev.openbmcHash = results[4]; 223 rev.metaHash = results[5]; 224 std::string versionString = 225 rev.platform + ":" + std::to_string(rev.major) + ":" + 226 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + 227 ":" + rev.openbmcHash + ":" + rev.metaHash; 228 phosphor::logging::log<phosphor::logging::level::INFO>( 229 "Get BMC version", 230 phosphor::logging::entry("VERSION=%s", versionString.c_str())); 231 return rev; 232 } 233 } 234 constexpr size_t matchedIntel = 7; 235 std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)[-.](\\d+?)-g(\\w+?)-(\\w+?)"); 236 if (std::regex_match(s, results, pattern2)) 237 { 238 if (results.size() == matchedIntel) 239 { 240 rev.platform = results[1]; 241 std::string majorVer = results[2].str(); 242 // Take only the last two digits of the major version 243 rev.major = static_cast<uint8_t>( 244 std::stoi(majorVer.substr(majorVer.size() - 2))); 245 rev.minor = static_cast<uint8_t>(std::stoi(results[3])); 246 rev.buildNo = static_cast<uint32_t>(std::stoi(results[4])); 247 rev.openbmcHash = results[6]; 248 rev.metaHash = results[5]; 249 std::string versionString = 250 rev.platform + ":" + std::to_string(rev.major) + ":" + 251 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + 252 ":" + rev.openbmcHash + ":" + rev.metaHash; 253 phosphor::logging::log<phosphor::logging::level::INFO>( 254 "Get BMC version", 255 phosphor::logging::entry("VERSION=%s", versionString.c_str())); 256 return rev; 257 } 258 } 259 260 return std::nullopt; 261 } 262 263 RspType<uint8_t, // Device ID 264 uint8_t, // Device Revision 265 uint7_t, // Firmware Revision Major 266 bool, // Device available(0=NormalMode,1=DeviceFirmware) 267 uint8_t, // Firmware Revision minor 268 uint8_t, // IPMI version 269 uint8_t, // Additional device support 270 uint24_t, // MFG ID 271 uint16_t, // Product ID 272 uint32_t // AUX info 273 > 274 ipmiAppGetDeviceId(ipmi::Context::ptr ctx) 275 { 276 static struct 277 { 278 uint8_t id; 279 uint8_t revision; 280 uint7_t fwMajor; 281 bool devBusy; 282 uint8_t fwMinor; 283 uint8_t ipmiVer = 2; 284 uint8_t addnDevSupport; 285 uint24_t manufId; 286 uint16_t prodId; 287 uint32_t aux; 288 } devId; 289 static bool fwVerInitialized = false; 290 static bool devIdInitialized = false; 291 static bool bmcStateInitialized = false; 292 const char* filename = "/usr/share/ipmi-providers/dev_id.json"; 293 const char* prodIdFilename = "/var/cache/private/prodID"; 294 if (!fwVerInitialized) 295 { 296 std::string versionString; 297 if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC, 298 versionString)) 299 { 300 std::optional<MetaRevision> rev = 301 convertIntelVersion(versionString); 302 if (rev.has_value()) 303 { 304 MetaRevision revision = rev.value(); 305 devId.fwMajor = static_cast<uint7_t>(revision.major); 306 307 revision.minor = (revision.minor > 99 ? 99 : revision.minor); 308 devId.fwMinor = revision.minor % 10 + 309 (revision.minor / 10) * 16; 310 try 311 { 312 uint32_t hash = std::stoul(revision.metaHash, 0, 16); 313 hash = bswap_32(hash); 314 devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00); 315 fwVerInitialized = true; 316 } 317 catch (const std::exception& e) 318 { 319 phosphor::logging::log<phosphor::logging::level::ERR>( 320 "Failed to convert git hash", 321 phosphor::logging::entry("ERROR=%s", e.what())); 322 } 323 } 324 } 325 } 326 327 if (!devIdInitialized) 328 { 329 std::ifstream devIdFile(filename); 330 if (devIdFile.is_open()) 331 { 332 auto data = nlohmann::json::parse(devIdFile, nullptr, false); 333 if (!data.is_discarded()) 334 { 335 devId.id = data.value("id", 0); 336 devId.revision = data.value("revision", 0); 337 devId.addnDevSupport = data.value("addn_dev_support", 0); 338 devId.manufId = data.value("manuf_id", 0); 339 } 340 else 341 { 342 phosphor::logging::log<phosphor::logging::level::ERR>( 343 "Device ID JSON parser failure"); 344 return ipmi::responseUnspecifiedError(); 345 } 346 } 347 else 348 { 349 phosphor::logging::log<phosphor::logging::level::ERR>( 350 "Device ID file not found"); 351 return ipmi::responseUnspecifiedError(); 352 } 353 354 // Determine the Product ID. Using the DBus system is painfully slow at 355 // boot time. Avoid using DBus to get the Product ID. The Product ID is 356 // stored in a non-volatile file now. The /usr/bin/checkFru.sh script, 357 // run during bootup, will populate the productIdFile. 358 std::fstream prodIdFile(prodIdFilename); 359 if (prodIdFile.is_open()) 360 { 361 std::string id = "0x00"; 362 char* end; 363 prodIdFile.getline(&id[0], id.size() + 1); 364 devId.prodId = std::strtol(&id[0], &end, 0); 365 devIdInitialized = true; 366 } 367 else 368 { 369 // For any exception send out platform id as 0, 370 // and make sure to re-query the device id. 371 devIdInitialized = false; 372 devId.prodId = 0; 373 } 374 } 375 376 if (!bmcStateInitialized) 377 { 378 if (!initBMCDeviceState(ctx)) 379 { 380 bmcStateInitialized = true; 381 } 382 } 383 384 return ipmi::responseSuccess(devId.id, devId.revision, devId.fwMajor, 385 bmcDeviceBusy, devId.fwMinor, devId.ipmiVer, 386 devId.addnDevSupport, devId.manufId, 387 devId.prodId, devId.aux); 388 } 389 390 static void registerAPPFunctions(void) 391 { 392 // <Get Device ID> 393 registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User, 394 ipmiAppGetDeviceId); 395 } 396 397 } // namespace ipmi 398