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 "xyz/openbmc_project/Common/error.hpp" 17 18 #include <byteswap.h> 19 20 #include <appcommands.hpp> 21 #include <ipmid/api.hpp> 22 #include <ipmid/utils.hpp> 23 #include <nlohmann/json.hpp> 24 #include <phosphor-logging/elog-errors.hpp> 25 #include <phosphor-logging/lg2.hpp> 26 #include <phosphor-logging/log.hpp> 27 #include <types.hpp> 28 29 #include <fstream> 30 #include <regex> 31 32 using namespace phosphor::logging; 33 using namespace sdbusplus::error::xyz::openbmc_project::common; 34 35 namespace ipmi 36 { 37 38 static void registerAPPFunctions() __attribute__((constructor)); 39 40 static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC"; 41 static constexpr const char* softwareVerIntf = 42 "xyz.openbmc_project.Software.Version"; 43 static constexpr const char* softwareActivationIntf = 44 "xyz.openbmc_project.Software.Activation"; 45 static constexpr const char* associationIntf = 46 "xyz.openbmc_project.Association"; 47 static constexpr const char* softwareFunctionalPath = 48 "/xyz/openbmc_project/software/functional"; 49 50 static constexpr const char* currentBmcStateProp = "CurrentBMCState"; 51 static constexpr const char* bmcStateReadyStr = 52 "xyz.openbmc_project.State.BMC.BMCState.Ready"; 53 54 static std::unique_ptr<sdbusplus::bus::match_t> bmcStateChangedSignal; 55 static uint8_t bmcDeviceBusy = true; 56 57 int initBMCDeviceState(ipmi::Context::ptr ctx) 58 { 59 DbusObjectInfo objInfo; 60 boost::system::error_code ec = 61 ipmi::getDbusObject(ctx, bmcStateIntf, "/", "bmc0", objInfo); 62 if (ec) 63 { 64 phosphor::logging::log<phosphor::logging::level::ERR>( 65 "initBMCDeviceState: Failed to perform GetSubTree action", 66 phosphor::logging::entry("ERROR=%s", ec.message().c_str()), 67 phosphor::logging::entry("INTERFACE=%s", bmcStateIntf)); 68 return -1; 69 } 70 71 // BMC state may change runtime while doing firmware udpate. 72 // Register for property change signal to update state. 73 bmcStateChangedSignal = std::make_unique<sdbusplus::bus::match_t>( 74 *(ctx->bus), 75 sdbusplus::bus::match::rules::propertiesChanged(objInfo.first, 76 bmcStateIntf), 77 [](sdbusplus::message_t& msg) { 78 std::map<std::string, ipmi::DbusVariant> props; 79 std::vector<std::string> inVal; 80 std::string iface; 81 try 82 { 83 msg.read(iface, props, inVal); 84 } 85 catch (const std::exception& e) 86 { 87 phosphor::logging::log<phosphor::logging::level::ERR>( 88 "Exception caught in Get CurrentBMCState"); 89 return; 90 } 91 92 auto it = props.find(currentBmcStateProp); 93 if (it != props.end()) 94 { 95 std::string* state = std::get_if<std::string>(&it->second); 96 if (state) 97 { 98 bmcDeviceBusy = (*state != bmcStateReadyStr); 99 phosphor::logging::log<phosphor::logging::level::INFO>( 100 "BMC device state updated"); 101 } 102 } 103 }); 104 105 std::string bmcState; 106 ec = ipmi::getDbusProperty(ctx, objInfo.second, objInfo.first, bmcStateIntf, 107 currentBmcStateProp, bmcState); 108 if (ec) 109 { 110 phosphor::logging::log<phosphor::logging::level::ERR>( 111 "initBMCDeviceState: Failed to get CurrentBMCState property", 112 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 113 return -1; 114 } 115 116 bmcDeviceBusy = (bmcState != bmcStateReadyStr); 117 118 phosphor::logging::log<phosphor::logging::level::INFO>( 119 "BMC device state updated"); 120 121 return 0; 122 } 123 124 /** 125 * @brief Returns the functional firmware version information. 126 * 127 * It reads the active firmware versions by checking functional 128 * endpoints association and matching the input version purpose string. 129 * ctx[in] - ipmi context. 130 * reqVersionPurpose[in] - Version purpose which need to be read. 131 * version[out] - Output Version string. 132 * 133 * @return Returns '0' on success and '-1' on failure. 134 * 135 */ 136 int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx, 137 const std::string& reqVersionPurpose, 138 std::string& version) 139 { 140 std::vector<std::string> activeEndPoints; 141 boost::system::error_code ec = ipmi::getDbusProperty( 142 ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf, 143 "endpoints", activeEndPoints); 144 if (ec) 145 { 146 phosphor::logging::log<phosphor::logging::level::ERR>( 147 "Failed to get Active firmware version endpoints."); 148 return -1; 149 } 150 151 for (auto& activeEndPoint : activeEndPoints) 152 { 153 std::string serviceName; 154 ec = ipmi::getService(ctx, softwareActivationIntf, activeEndPoint, 155 serviceName); 156 if (ec) 157 { 158 phosphor::logging::log<phosphor::logging::level::ERR>( 159 "Failed to perform getService.", 160 phosphor::logging::entry("OBJPATH=%s", activeEndPoint.c_str())); 161 continue; 162 } 163 164 PropertyMap propMap; 165 ec = ipmi::getAllDbusProperties(ctx, serviceName, activeEndPoint, 166 softwareVerIntf, propMap); 167 if (ec) 168 { 169 phosphor::logging::log<phosphor::logging::level::ERR>( 170 "Failed to perform GetAll on Version interface.", 171 phosphor::logging::entry("SERVICE=%s", serviceName.c_str()), 172 phosphor::logging::entry("PATH=%s", activeEndPoint.c_str())); 173 continue; 174 } 175 176 std::string* purposeProp = 177 std::get_if<std::string>(&propMap["Purpose"]); 178 std::string* versionProp = 179 std::get_if<std::string>(&propMap["Version"]); 180 if (!purposeProp || !versionProp) 181 { 182 phosphor::logging::log<phosphor::logging::level::ERR>( 183 "Failed to get version or purpose property"); 184 continue; 185 } 186 187 // Check for requested version information and return if found. 188 if (*purposeProp == reqVersionPurpose) 189 { 190 version = *versionProp; 191 return 0; 192 } 193 } 194 195 phosphor::logging::log<phosphor::logging::level::INFO>( 196 "Failed to find version information.", 197 phosphor::logging::entry("PURPOSE=%s", reqVersionPurpose.c_str())); 198 return -1; 199 } 200 201 // Support both 2 solutions: 202 // 1.Current solution 2.7.0-dev-533-g14dc00e79-5e7d997 203 // openbmcTag 2.7.0-dev 204 // BuildNo 533 205 // openbmcHash 14dc00e79 206 // MetaHasg 5e7d997 207 // 208 // 2.New solution wht-0.2-3-gab3500-38384ac or wht-2000.2.3-gab3500-38384ac 209 // IdStr wht 210 // Major 0 211 // Minor 2 212 // buildNo 3 213 // MetaHash ab3500 214 // openbmcHash 38384ac 215 std::optional<MetaRevision> convertIntelVersion(std::string& s) 216 { 217 std::smatch results; 218 MetaRevision rev; 219 std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)"); 220 constexpr size_t matchedPhosphor = 6; 221 if (std::regex_match(s, results, pattern1)) 222 { 223 if (results.size() == matchedPhosphor) 224 { 225 rev.platform = "whtref"; 226 rev.major = static_cast<uint8_t>(std::stoi(results[1])); 227 rev.minor = static_cast<uint8_t>(std::stoi(results[2])); 228 rev.buildNo = static_cast<uint32_t>(std::stoi(results[3])); 229 rev.openbmcHash = results[4]; 230 rev.metaHash = results[5]; 231 std::string versionString = 232 rev.platform + ":" + std::to_string(rev.major) + ":" + 233 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + 234 ":" + rev.openbmcHash + ":" + rev.metaHash; 235 phosphor::logging::log<phosphor::logging::level::INFO>( 236 "Get BMC version", 237 phosphor::logging::entry("VERSION=%s", versionString.c_str())); 238 return rev; 239 } 240 } 241 constexpr size_t matchedIntel = 7; 242 std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)[-.](\\d+?)-g(\\w+?)-(\\w+?)"); 243 if (std::regex_match(s, results, pattern2)) 244 { 245 if (results.size() == matchedIntel) 246 { 247 rev.platform = results[1]; 248 std::string majorVer = results[2].str(); 249 // Take only the last two digits of the major version 250 rev.major = static_cast<uint8_t>( 251 std::stoi(majorVer.substr(majorVer.size() - 2))); 252 rev.minor = static_cast<uint8_t>(std::stoi(results[3])); 253 rev.buildNo = static_cast<uint32_t>(std::stoi(results[4])); 254 rev.openbmcHash = results[6]; 255 rev.metaHash = results[5]; 256 std::string versionString = 257 rev.platform + ":" + std::to_string(rev.major) + ":" + 258 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + 259 ":" + rev.openbmcHash + ":" + rev.metaHash; 260 phosphor::logging::log<phosphor::logging::level::INFO>( 261 "Get BMC version", 262 phosphor::logging::entry("VERSION=%s", versionString.c_str())); 263 return rev; 264 } 265 } 266 267 return std::nullopt; 268 } 269 270 static constexpr size_t uuidLength = 16; 271 static std::array<uint8_t, uuidLength> 272 rfc4122ToIpmiConvesrion(std::string rfc4122) 273 { 274 using Argument = xyz::openbmc_project::common::InvalidArgument; 275 // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 276 // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte 277 // order 278 // Ex: 0x2332fc2c40e66298e511f2782395a361 279 constexpr size_t uuidHexLength = (2 * uuidLength); 280 constexpr size_t uuidRfc4122Length = (uuidHexLength + 4); 281 std::array<uint8_t, uuidLength> uuid; 282 if (rfc4122.size() == uuidRfc4122Length) 283 { 284 rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'), 285 rfc4122.end()); 286 } 287 if (rfc4122.size() != uuidHexLength) 288 { 289 elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), 290 Argument::ARGUMENT_VALUE(rfc4122.c_str())); 291 } 292 for (size_t ind = 0; ind < uuidHexLength; ind += 2) 293 { 294 char v[3]; 295 v[0] = rfc4122[ind]; 296 v[1] = rfc4122[ind + 1]; 297 v[2] = 0; 298 size_t err; 299 long b; 300 try 301 { 302 b = std::stoul(v, &err, 16); 303 } 304 catch (const std::exception& e) 305 { 306 elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), 307 Argument::ARGUMENT_VALUE(rfc4122.c_str())); 308 } 309 // check that exactly two ascii bytes were converted 310 if (err != 2) 311 { 312 elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), 313 Argument::ARGUMENT_VALUE(rfc4122.c_str())); 314 } 315 uuid[uuidLength - (ind / 2) - 1] = static_cast<uint8_t>(b); 316 } 317 return uuid; 318 } 319 320 ipmi::RspType<std::array<uint8_t, 16>> 321 ipmiAppGetSystemGuid(ipmi::Context::ptr& ctx) 322 { 323 static constexpr auto uuidInterface = "xyz.openbmc_project.Common.UUID"; 324 static constexpr auto uuidProperty = "UUID"; 325 // Get the Inventory object implementing BMC interface 326 ipmi::DbusObjectInfo objectInfo{}; 327 boost::system::error_code ec = 328 ipmi::getDbusObject(ctx, uuidInterface, objectInfo); 329 330 if (ec.value()) 331 { 332 lg2::error("Failed to locate System UUID object, " 333 "interface: {INTERFACE}, error: {ERROR}", 334 "INTERFACE", uuidInterface, "ERROR", ec.message()); 335 return ipmi::responseUnspecifiedError(); 336 } 337 338 // Read UUID property value from bmcObject 339 // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 340 std::string rfc4122Uuid{}; 341 ec = ipmi::getDbusProperty(ctx, objectInfo.second, objectInfo.first, 342 uuidInterface, uuidProperty, rfc4122Uuid); 343 344 if (ec.value()) 345 { 346 lg2::error("Failed to read System UUID property, " 347 "interface: {INTERFACE}, property: {PROPERTY}, " 348 "error: {ERROR}", 349 "INTERFACE", uuidInterface, "PROPERTY", uuidProperty, 350 "ERROR", ec.message()); 351 return ipmi::responseUnspecifiedError(); 352 } 353 std::array<uint8_t, 16> uuid; 354 try 355 { 356 // convert to IPMI format 357 uuid = rfc4122ToIpmiConvesrion(rfc4122Uuid); 358 } 359 catch (const InvalidArgument& e) 360 { 361 lg2::error("Failed in parsing BMC UUID property, " 362 "interface: {INTERFACE}, property: {PROPERTY}, " 363 "value: {VALUE}, error: {ERROR}", 364 "INTERFACE", uuidInterface, "PROPERTY", uuidProperty, 365 "VALUE", rfc4122Uuid, "ERROR", e); 366 return ipmi::responseUnspecifiedError(); 367 } 368 return ipmi::responseSuccess(uuid); 369 } 370 371 RspType<uint8_t, // Device ID 372 uint8_t, // Device Revision 373 uint7_t, // Firmware Revision Major 374 bool, // Device available(0=NormalMode,1=DeviceFirmware) 375 uint8_t, // Firmware Revision minor 376 uint8_t, // IPMI version 377 uint8_t, // Additional device support 378 uint24_t, // MFG ID 379 uint16_t, // Product ID 380 uint32_t // AUX info 381 > 382 ipmiAppGetDeviceId(ipmi::Context::ptr ctx) 383 { 384 static struct 385 { 386 uint8_t id; 387 uint8_t revision; 388 uint7_t fwMajor; 389 bool devBusy; 390 uint8_t fwMinor; 391 uint8_t ipmiVer = 2; 392 uint8_t addnDevSupport; 393 uint24_t manufId; 394 uint16_t prodId; 395 uint32_t aux; 396 } devId; 397 static bool fwVerInitialized = false; 398 static bool devIdInitialized = false; 399 static bool bmcStateInitialized = false; 400 const char* filename = "/usr/share/ipmi-providers/dev_id.json"; 401 const char* prodIdFilename = "/var/cache/private/prodID"; 402 if (!fwVerInitialized) 403 { 404 std::string versionString; 405 if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC, 406 versionString)) 407 { 408 std::optional<MetaRevision> rev = 409 convertIntelVersion(versionString); 410 if (rev.has_value()) 411 { 412 MetaRevision revision = rev.value(); 413 devId.fwMajor = static_cast<uint7_t>(revision.major); 414 415 revision.minor = (revision.minor > 99 ? 99 : revision.minor); 416 devId.fwMinor = revision.minor % 10 + 417 (revision.minor / 10) * 16; 418 try 419 { 420 uint32_t hash = std::stoul(revision.metaHash, 0, 16); 421 hash = bswap_32(hash); 422 devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00); 423 fwVerInitialized = true; 424 } 425 catch (const std::exception& e) 426 { 427 phosphor::logging::log<phosphor::logging::level::ERR>( 428 "Failed to convert git hash", 429 phosphor::logging::entry("ERROR=%s", e.what())); 430 } 431 } 432 } 433 } 434 435 if (!devIdInitialized) 436 { 437 std::ifstream devIdFile(filename); 438 if (devIdFile.is_open()) 439 { 440 auto data = nlohmann::json::parse(devIdFile, nullptr, false); 441 if (!data.is_discarded()) 442 { 443 devId.id = data.value("id", 0); 444 devId.revision = data.value("revision", 0); 445 devId.addnDevSupport = data.value("addn_dev_support", 0); 446 devId.manufId = data.value("manuf_id", 0); 447 } 448 else 449 { 450 phosphor::logging::log<phosphor::logging::level::ERR>( 451 "Device ID JSON parser failure"); 452 return ipmi::responseUnspecifiedError(); 453 } 454 } 455 else 456 { 457 phosphor::logging::log<phosphor::logging::level::ERR>( 458 "Device ID file not found"); 459 return ipmi::responseUnspecifiedError(); 460 } 461 462 // Determine the Product ID. Using the DBus system is painfully slow at 463 // boot time. Avoid using DBus to get the Product ID. The Product ID is 464 // stored in a non-volatile file now. The /usr/bin/checkFru.sh script, 465 // run during bootup, will populate the productIdFile. 466 std::fstream prodIdFile(prodIdFilename); 467 if (prodIdFile.is_open()) 468 { 469 std::string id = "0x00"; 470 char* end; 471 prodIdFile.getline(&id[0], id.size() + 1); 472 devId.prodId = std::strtol(&id[0], &end, 0); 473 devIdInitialized = true; 474 } 475 else 476 { 477 // For any exception send out platform id as 0, 478 // and make sure to re-query the device id. 479 devIdInitialized = false; 480 devId.prodId = 0; 481 } 482 } 483 484 if (!bmcStateInitialized) 485 { 486 if (!initBMCDeviceState(ctx)) 487 { 488 bmcStateInitialized = true; 489 } 490 } 491 492 return ipmi::responseSuccess( 493 devId.id, devId.revision, devId.fwMajor, bmcDeviceBusy, devId.fwMinor, 494 devId.ipmiVer, devId.addnDevSupport, devId.manufId, devId.prodId, 495 devId.aux); 496 } 497 498 static void registerAPPFunctions(void) 499 { 500 // <Get Device ID> 501 registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User, 502 ipmiAppGetDeviceId); 503 // <Get System GUID> 504 registerHandler(prioOemBase, netFnApp, app::cmdGetSystemGuid, 505 Privilege::User, ipmiAppGetSystemGuid); 506 } 507 508 } // namespace ipmi 509