Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 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 <fstream> |
| 19 | #include <ipmid/api.hpp> |
| 20 | #include <ipmid/utils.hpp> |
| 21 | #include <nlohmann/json.hpp> |
| 22 | #include <phosphor-logging/log.hpp> |
| 23 | #include <regex> |
| 24 | #include <xyz/openbmc_project/Software/Activation/server.hpp> |
| 25 | #include <xyz/openbmc_project/Software/Version/server.hpp> |
| 26 | #include <xyz/openbmc_project/State/BMC/server.hpp> |
| 27 | |
| 28 | namespace ipmi |
| 29 | { |
| 30 | |
| 31 | static void registerAPPFunctions() __attribute__((constructor)); |
| 32 | |
| 33 | namespace Log = phosphor::logging; |
| 34 | namespace Error = sdbusplus::xyz::openbmc_project::Common::Error; |
| 35 | using Version = sdbusplus::xyz::openbmc_project::Software::server::Version; |
| 36 | using Activation = |
| 37 | sdbusplus::xyz::openbmc_project::Software::server::Activation; |
| 38 | using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC; |
| 39 | |
| 40 | constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC"; |
| 41 | constexpr auto bmc_state_property = "CurrentBMCState"; |
| 42 | |
| 43 | namespace |
| 44 | { |
| 45 | static constexpr auto redundancyIntf = |
| 46 | "xyz.openbmc_project.Software.RedundancyPriority"; |
| 47 | static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version"; |
| 48 | static constexpr auto activationIntf = |
| 49 | "xyz.openbmc_project.Software.Activation"; |
| 50 | static constexpr auto softwareRoot = "/xyz/openbmc_project/software"; |
| 51 | |
| 52 | bool getCurrentBmcState() |
| 53 | { |
| 54 | sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; |
| 55 | |
| 56 | // Get the Inventory object implementing the BMC interface |
| 57 | ipmi::DbusObjectInfo bmcObject = |
| 58 | ipmi::getDbusObject(bus, bmc_state_interface); |
| 59 | auto variant = |
| 60 | ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first, |
| 61 | bmc_state_interface, bmc_state_property); |
| 62 | |
| 63 | return std::holds_alternative<std::string>(variant) && |
| 64 | BMC::convertBMCStateFromString(std::get<std::string>(variant)) == |
| 65 | BMC::BMCState::Ready; |
| 66 | } |
| 67 | |
| 68 | bool getCurrentBmcStateWithFallback(const bool fallbackAvailability) |
| 69 | { |
| 70 | try |
| 71 | { |
| 72 | return getCurrentBmcState(); |
| 73 | } |
| 74 | catch (...) |
| 75 | { |
| 76 | // Nothing provided the BMC interface, therefore return whatever was |
| 77 | // configured as the default. |
| 78 | return fallbackAvailability; |
| 79 | } |
| 80 | } |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 81 | |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 82 | /** |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 83 | * @brief Returns the Version info from primary software object |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 84 | * |
| 85 | * Get the Version info from the active s/w object which is having high |
| 86 | * "Priority" value(a smaller number is a higher priority) and "Purpose" |
| 87 | * is "BMC" from the list of all s/w objects those are implementing |
| 88 | * RedundancyPriority interface from the given softwareRoot path. |
| 89 | * |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 90 | * @return On success returns the Version info from primary software object. |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 91 | * |
| 92 | */ |
| 93 | std::string getActiveSoftwareVersionInfo() |
| 94 | { |
| 95 | auto busp = getSdBus(); |
| 96 | |
| 97 | std::string revision{}; |
| 98 | ipmi::ObjectTree objectTree; |
| 99 | try |
| 100 | { |
| 101 | objectTree = |
| 102 | ipmi::getAllDbusObjects(*busp, softwareRoot, redundancyIntf); |
| 103 | } |
| 104 | catch (sdbusplus::exception::SdBusError& e) |
| 105 | { |
| 106 | Log::log<Log::level::ERR>("Failed to fetch redundancy object from dbus", |
| 107 | Log::entry("INTERFACE=%s", redundancyIntf), |
| 108 | Log::entry("ERRMSG=%s", e.what())); |
| 109 | } |
| 110 | |
| 111 | auto objectFound = false; |
| 112 | for (auto& softObject : objectTree) |
| 113 | { |
| 114 | auto service = |
| 115 | ipmi::getService(*busp, redundancyIntf, softObject.first); |
| 116 | auto objValueTree = |
| 117 | ipmi::getManagedObjects(*busp, service, softwareRoot); |
| 118 | |
| 119 | auto minPriority = 0xFF; |
| 120 | for (const auto& objIter : objValueTree) |
| 121 | { |
| 122 | try |
| 123 | { |
| 124 | auto& intfMap = objIter.second; |
| 125 | auto& redundancyPriorityProps = intfMap.at(redundancyIntf); |
| 126 | auto& versionProps = intfMap.at(versionIntf); |
| 127 | auto& activationProps = intfMap.at(activationIntf); |
| 128 | auto priority = |
| 129 | std::get<uint8_t>(redundancyPriorityProps.at("Priority")); |
| 130 | auto purpose = |
| 131 | std::get<std::string>(versionProps.at("Purpose")); |
| 132 | auto activation = |
| 133 | std::get<std::string>(activationProps.at("Activation")); |
| 134 | auto version = |
| 135 | std::get<std::string>(versionProps.at("Version")); |
| 136 | if ((Version::convertVersionPurposeFromString(purpose) == |
| 137 | Version::VersionPurpose::BMC) && |
| 138 | (Activation::convertActivationsFromString(activation) == |
| 139 | Activation::Activations::Active)) |
| 140 | { |
| 141 | if (priority < minPriority) |
| 142 | { |
| 143 | minPriority = priority; |
| 144 | objectFound = true; |
| 145 | revision = std::move(version); |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | catch (const std::exception& e) |
| 150 | { |
| 151 | Log::log<Log::level::ERR>(e.what()); |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | if (!objectFound) |
| 157 | { |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 158 | Log::log<Log::level::ERR>("Could not find an BMC software object"); |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 159 | } |
| 160 | |
| 161 | return revision; |
| 162 | } |
| 163 | |
| 164 | typedef struct |
| 165 | { |
| 166 | std::string platform; |
| 167 | uint8_t major; |
| 168 | uint8_t minor; |
| 169 | uint32_t buildNo; |
| 170 | std::string openbmcHash; |
| 171 | std::string metaHash; |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 172 | } MetaRevision; |
| 173 | |
| 174 | // Support both 2 solutions: |
| 175 | // 1.Current solution 2.7.0-dev-533-g14dc00e79-5e7d997 |
| 176 | // openbmcTag 2.7.0-dev |
| 177 | // BuildNo 533 |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 178 | // openbmcHash 14dc00e79 |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 179 | // MetaHasg 5e7d997 |
| 180 | // |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 181 | // 2.New solution wht-0.2-3-gab3500-38384ac |
| 182 | // IdStr wht |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 183 | // Major 0 |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 184 | // Minor 2 |
| 185 | // buildNo 3 |
| 186 | // MetaHash ab3500 |
| 187 | // openbmcHash 38384ac |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 188 | std::optional<MetaRevision> convertIntelVersion(std::string& s) |
| 189 | { |
| 190 | std::smatch results; |
| 191 | MetaRevision rev; |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 192 | std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)"); |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 193 | constexpr size_t matchedPhosphor = 6; |
| 194 | if (std::regex_match(s, results, pattern1)) |
| 195 | { |
| 196 | if (results.size() == matchedPhosphor) |
| 197 | { |
| 198 | rev.platform = "whtref"; |
| 199 | rev.major = static_cast<uint8_t>(std::stoi(results[1])); |
| 200 | rev.minor = static_cast<uint8_t>(std::stoi(results[2])); |
| 201 | rev.buildNo = static_cast<uint32_t>(std::stoi(results[3])); |
| 202 | rev.openbmcHash = results[4]; |
| 203 | rev.metaHash = results[5]; |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 204 | std::string versionString = |
| 205 | rev.platform + ":" + std::to_string(rev.major) + ":" + |
| 206 | std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 207 | ":" + rev.openbmcHash + ":" + rev.metaHash; |
| 208 | Log::log<Log::level::INFO>( |
| 209 | "Get BMC version", |
| 210 | Log::entry("VERSION=%s", versionString.c_str())); |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 211 | return rev; |
| 212 | } |
| 213 | } |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 214 | constexpr size_t matchedIntel = 7; |
| 215 | std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)-(\\d+?)-g(\\w+?)-(\\w+?)"); |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 216 | if (std::regex_match(s, results, pattern2)) |
| 217 | { |
| 218 | if (results.size() == matchedIntel) |
| 219 | { |
| 220 | rev.platform = results[1]; |
| 221 | rev.major = static_cast<uint8_t>(std::stoi(results[2])); |
| 222 | rev.minor = static_cast<uint8_t>(std::stoi(results[3])); |
| 223 | rev.buildNo = static_cast<uint32_t>(std::stoi(results[4])); |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 224 | rev.openbmcHash = results[6]; |
| 225 | rev.metaHash = results[5]; |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 226 | std::string versionString = |
| 227 | rev.platform + ":" + std::to_string(rev.major) + ":" + |
| 228 | std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) + |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 229 | ":" + rev.openbmcHash + ":" + rev.metaHash; |
| 230 | Log::log<Log::level::INFO>( |
| 231 | "Get BMC version", |
| 232 | Log::entry("VERSION=%s", versionString.c_str())); |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 233 | return rev; |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | return std::nullopt; |
| 238 | } |
| 239 | } // namespace |
| 240 | auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID |
| 241 | uint8_t, // Device Revision |
| 242 | uint8_t, // Firmware Revision Major |
| 243 | uint8_t, // Firmware Revision minor |
| 244 | uint8_t, // IPMI version |
| 245 | uint8_t, // Additional device support |
| 246 | uint24_t, // MFG ID |
| 247 | uint16_t, // Product ID |
| 248 | uint32_t // AUX info |
| 249 | > |
| 250 | { |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 251 | static struct |
| 252 | { |
| 253 | uint8_t id; |
| 254 | uint8_t revision; |
| 255 | uint8_t fw[2]; |
| 256 | uint8_t ipmiVer; |
| 257 | uint8_t addnDevSupport; |
| 258 | uint24_t manufId; |
| 259 | uint16_t prodId; |
| 260 | uint32_t aux; |
| 261 | } devId; |
| 262 | static bool dev_id_initialized = false; |
| 263 | static bool defaultActivationSetting = true; |
| 264 | const char* filename = "/usr/share/ipmi-providers/dev_id.json"; |
| 265 | constexpr auto ipmiDevIdStateShift = 7; |
| 266 | constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift); |
| 267 | |
| 268 | if (!dev_id_initialized) |
| 269 | { |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 270 | std::optional<MetaRevision> rev; |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 271 | try |
| 272 | { |
| 273 | auto version = getActiveSoftwareVersionInfo(); |
| 274 | rev = convertIntelVersion(version); |
| 275 | } |
| 276 | catch (const std::exception& e) |
| 277 | { |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 278 | Log::log<Log::level::ERR>("Failed to get active version info", |
| 279 | Log::entry("ERROR=%s", e.what())); |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 280 | } |
| 281 | |
| 282 | if (rev.has_value()) |
| 283 | { |
| 284 | // bit7 identifies if the device is available |
| 285 | // 0=normal operation |
| 286 | // 1=device firmware, SDR update, |
| 287 | // or self-initialization in progress. |
| 288 | // The availability may change in run time, so mask here |
| 289 | // and initialize later. |
| 290 | MetaRevision revision = rev.value(); |
| 291 | devId.fw[0] = revision.major & ipmiDevIdFw1Mask; |
| 292 | |
| 293 | revision.minor = (revision.minor > 99 ? 99 : revision.minor); |
| 294 | devId.fw[1] = revision.minor % 10 + (revision.minor / 10) * 16; |
Jia, chunhui | 82d178c | 2019-06-27 16:48:14 +0800 | [diff] [blame] | 295 | try |
| 296 | { |
| 297 | uint32_t hash = std::stoul(revision.metaHash, 0, 16); |
| 298 | hash = ((hash & 0xff000000) >> 24) | |
| 299 | ((hash & 0x00FF0000) >> 8) | ((hash & 0x0000FF00) << 8) | |
| 300 | ((hash & 0xFF) << 24); |
| 301 | devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00); |
| 302 | } |
| 303 | catch (const std::exception& e) |
| 304 | { |
| 305 | Log::log<Log::level::ERR>("Failed to convert git hash", |
| 306 | Log::entry("ERROR=%s", e.what())); |
| 307 | } |
Vernon Mauery | ce14e4d | 2019-06-25 11:38:20 -0700 | [diff] [blame] | 308 | } |
| 309 | |
| 310 | // IPMI Spec version 2.0 |
| 311 | devId.ipmiVer = 2; |
| 312 | |
| 313 | std::ifstream devIdFile(filename); |
| 314 | if (devIdFile.is_open()) |
| 315 | { |
| 316 | auto data = nlohmann::json::parse(devIdFile, nullptr, false); |
| 317 | if (!data.is_discarded()) |
| 318 | { |
| 319 | devId.id = data.value("id", 0); |
| 320 | devId.revision = data.value("revision", 0); |
| 321 | devId.addnDevSupport = data.value("addn_dev_support", 0); |
| 322 | devId.manufId = data.value("manuf_id", 0); |
| 323 | |
| 324 | try |
| 325 | { |
| 326 | auto busp = getSdBus(); |
| 327 | const ipmi::DbusObjectInfo& object = ipmi::getDbusObject( |
| 328 | *busp, "xyz.openbmc_project.Inventory.Item.Board", |
| 329 | "/xyz/openbmc_project/inventory/system/board/", |
| 330 | "Baseboard"); |
| 331 | const ipmi::Value& propValue = ipmi::getDbusProperty( |
| 332 | *busp, object.second, object.first, |
| 333 | "xyz.openbmc_project.Inventory.Item.Board", |
| 334 | "ProductId"); |
| 335 | devId.prodId = |
| 336 | static_cast<uint8_t>(std::get<uint64_t>(propValue)); |
| 337 | } |
| 338 | catch (std::exception& e) |
| 339 | { |
| 340 | devId.prodId = data.value("prod_id", 0); |
| 341 | } |
| 342 | |
| 343 | // Set the availablitity of the BMC. |
| 344 | defaultActivationSetting = data.value("availability", true); |
| 345 | |
| 346 | // Don't read the file every time if successful |
| 347 | dev_id_initialized = true; |
| 348 | } |
| 349 | else |
| 350 | { |
| 351 | Log::log<Log::level::ERR>("Device ID JSON parser failure"); |
| 352 | return ipmi::responseUnspecifiedError(); |
| 353 | } |
| 354 | } |
| 355 | else |
| 356 | { |
| 357 | Log::log<Log::level::ERR>("Device ID file not found"); |
| 358 | return ipmi::responseUnspecifiedError(); |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | // Set availability to the actual current BMC state |
| 363 | devId.fw[0] &= ipmiDevIdFw1Mask; |
| 364 | if (!getCurrentBmcStateWithFallback(defaultActivationSetting)) |
| 365 | { |
| 366 | devId.fw[0] |= (1 << ipmiDevIdStateShift); |
| 367 | } |
| 368 | |
| 369 | return ipmi::responseSuccess( |
| 370 | devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer, |
| 371 | devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux); |
| 372 | } |
| 373 | |
| 374 | static void registerAPPFunctions(void) |
| 375 | { |
| 376 | Log::log<Log::level::INFO>("Registering App commands"); |
| 377 | // <Get Device ID> |
| 378 | ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp, |
| 379 | ipmi::app::cmdGetDeviceId, ipmi::Privilege::User, |
| 380 | ipmiAppGetDeviceId); |
| 381 | return; |
| 382 | } |
| 383 | |
| 384 | } // namespace ipmi |