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 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 rev.major = static_cast<uint8_t>(std::stoi(results[2])); 242 rev.minor = static_cast<uint8_t>(std::stoi(results[3])); 243 rev.buildNo = static_cast<uint32_t>(std::stoi(results[4])); 244 rev.openbmcHash = results[6]; 245 rev.metaHash = results[5]; 246 std::string versionString = 247 rev.platform + ":" + std::to_string(rev.major) + ":" + 248 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + 249 ":" + rev.openbmcHash + ":" + rev.metaHash; 250 phosphor::logging::log<phosphor::logging::level::INFO>( 251 "Get BMC version", 252 phosphor::logging::entry("VERSION=%s", versionString.c_str())); 253 return rev; 254 } 255 } 256 257 return std::nullopt; 258 } 259 260 RspType<uint8_t, // Device ID 261 uint8_t, // Device Revision 262 uint7_t, // Firmware Revision Major 263 bool, // Device available(0=NormalMode,1=DeviceFirmware) 264 uint8_t, // Firmware Revision minor 265 uint8_t, // IPMI version 266 uint8_t, // Additional device support 267 uint24_t, // MFG ID 268 uint16_t, // Product ID 269 uint32_t // AUX info 270 > 271 ipmiAppGetDeviceId(ipmi::Context::ptr ctx) 272 { 273 static struct 274 { 275 uint8_t id; 276 uint8_t revision; 277 uint7_t fwMajor; 278 bool devBusy; 279 uint8_t fwMinor; 280 uint8_t ipmiVer = 2; 281 uint8_t addnDevSupport; 282 uint24_t manufId; 283 uint16_t prodId; 284 uint32_t aux; 285 } devId; 286 static bool fwVerInitialized = false; 287 static bool devIdInitialized = false; 288 static bool bmcStateInitialized = false; 289 const char* filename = "/usr/share/ipmi-providers/dev_id.json"; 290 const char* prodIdFilename = "/var/cache/private/prodID"; 291 if (!fwVerInitialized) 292 { 293 std::string versionString; 294 if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC, 295 versionString)) 296 { 297 std::optional<MetaRevision> rev = 298 convertIntelVersion(versionString); 299 if (rev.has_value()) 300 { 301 MetaRevision revision = rev.value(); 302 devId.fwMajor = static_cast<uint7_t>(revision.major); 303 304 revision.minor = (revision.minor > 99 ? 99 : revision.minor); 305 devId.fwMinor = revision.minor % 10 + 306 (revision.minor / 10) * 16; 307 try 308 { 309 uint32_t hash = std::stoul(revision.metaHash, 0, 16); 310 hash = bswap_32(hash); 311 devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00); 312 fwVerInitialized = true; 313 } 314 catch (const std::exception& e) 315 { 316 phosphor::logging::log<phosphor::logging::level::ERR>( 317 "Failed to convert git hash", 318 phosphor::logging::entry("ERROR=%s", e.what())); 319 } 320 } 321 } 322 } 323 324 if (!devIdInitialized) 325 { 326 std::ifstream devIdFile(filename); 327 if (devIdFile.is_open()) 328 { 329 auto data = nlohmann::json::parse(devIdFile, nullptr, false); 330 if (!data.is_discarded()) 331 { 332 devId.id = data.value("id", 0); 333 devId.revision = data.value("revision", 0); 334 devId.addnDevSupport = data.value("addn_dev_support", 0); 335 devId.manufId = data.value("manuf_id", 0); 336 } 337 else 338 { 339 phosphor::logging::log<phosphor::logging::level::ERR>( 340 "Device ID JSON parser failure"); 341 return ipmi::responseUnspecifiedError(); 342 } 343 } 344 else 345 { 346 phosphor::logging::log<phosphor::logging::level::ERR>( 347 "Device ID file not found"); 348 return ipmi::responseUnspecifiedError(); 349 } 350 351 // Determine the Product ID. Using the DBus system is painfully slow at 352 // boot time. Avoid using DBus to get the Product ID. The Product ID is 353 // stored in a non-volatile file now. The /usr/bin/checkFru.sh script, 354 // run during bootup, will populate the productIdFile. 355 std::fstream prodIdFile(prodIdFilename); 356 if (prodIdFile.is_open()) 357 { 358 std::string id = "0x00"; 359 char* end; 360 prodIdFile.getline(&id[0], id.size() + 1); 361 devId.prodId = std::strtol(&id[0], &end, 0); 362 devIdInitialized = true; 363 } 364 else 365 { 366 // For any exception send out platform id as 0, 367 // and make sure to re-query the device id. 368 devIdInitialized = false; 369 devId.prodId = 0; 370 } 371 } 372 373 if (!bmcStateInitialized) 374 { 375 if (!initBMCDeviceState(ctx)) 376 { 377 bmcStateInitialized = true; 378 } 379 } 380 381 return ipmi::responseSuccess(devId.id, devId.revision, devId.fwMajor, 382 bmcDeviceBusy, devId.fwMinor, devId.ipmiVer, 383 devId.addnDevSupport, devId.manufId, 384 devId.prodId, devId.aux); 385 } 386 387 static void registerAPPFunctions(void) 388 { 389 // <Get Device ID> 390 registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User, 391 ipmiAppGetDeviceId); 392 } 393 394 } // namespace ipmi 395