Alexander Amelkin | eeef3b9 | 2020-09-26 04:10:36 +0300 | [diff] [blame] | 1 | From 605210dca3d6803839a8bcea4b5b83866c01f2da Mon Sep 17 00:00:00 2001 |
| 2 | From: Alexander Amelkin <alexander@amelkin.msk.ru> |
| 3 | Date: Wed, 11 Jul 2018 13:16:01 +0300 |
| 4 | Subject: [PATCH] Fix version parsing, update AUX revision info |
| 5 | |
| 6 | AUX Revision info was always taken from the dev_id.json |
| 7 | file if it exists, overriding the value calculated from the |
| 8 | active firmware version string. Also, when AUX info was |
| 9 | calculated, it only properly parsed the dirtyness of the |
| 10 | build. |
| 11 | |
| 12 | With this commit the AUX info calculation will properly parse |
| 13 | the git hash part and will include it as higher 3 bytes of |
| 14 | the AUX info. For officially released versions the lower byte |
| 15 | will be zero. |
| 16 | |
| 17 | For development versions, bits [7:1] of the fourth byte will |
| 18 | all be 1 as an indicator of non-release branch. For unofficial |
| 19 | builds from release branches those bits will contain a number |
| 20 | from 1 to 126 indicating a patch level since the release tag. |
| 21 | |
| 22 | In any case the bit 0 of byte 4 is a dirtyness indicator. |
| 23 | If the sources used to build the firmware were modified compared |
| 24 | to the git hash, this bit will be 1. |
| 25 | |
| 26 | WARNING: For the AUX decoding from version string to work |
| 27 | properly, the dev_id.json file must NOT contain |
| 28 | the `aux` property. |
| 29 | |
| 30 | Resolves SRV-775 |
| 31 | End-user-impact: Version info is properly represented in the |
| 32 | AUX Revision Info fields in response to the |
| 33 | IPMI Get Device ID command (ipmitool mc info) |
| 34 | Signed-off-by: Alexander Amelkin <a.filippov@yadro.com> |
| 35 | |
| 36 | --- |
| 37 | apphandler.cpp | 228 +++++++++++++++++++++++++++++++++++-------------- |
| 38 | 1 file changed, 165 insertions(+), 63 deletions(-) |
| 39 | |
| 40 | diff --git a/apphandler.cpp b/apphandler.cpp |
| 41 | index 90818a9..93e3b6c 100644 |
| 42 | --- a/apphandler.cpp |
| 43 | +++ b/apphandler.cpp |
| 44 | @@ -1,4 +1,5 @@ |
| 45 | #include <arpa/inet.h> |
| 46 | +#include <endian.h> |
| 47 | #include <fcntl.h> |
| 48 | #include <limits.h> |
| 49 | #include <linux/i2c-dev.h> |
| 50 | @@ -459,33 +460,112 @@ ipmi::RspType<uint8_t, // acpiSystemPowerState |
| 51 | return ipmi::responseSuccess(sysAcpiState, devAcpiState); |
| 52 | } |
| 53 | |
| 54 | +static |
| 55 | +std::vector<std::string> |
| 56 | +tokenize(std::string const& str, |
| 57 | + char const token[]) |
| 58 | +{ |
| 59 | + std::vector<std::string> results; |
| 60 | + std::string::size_type j = 0; |
| 61 | + while (j < str.length()) |
| 62 | + { |
| 63 | + std::string::size_type k = str.find_first_of(token, j); |
| 64 | + if (k == std::string::npos) |
| 65 | + k = str.length(); |
| 66 | + results.push_back(str.substr(j, k-j)); |
| 67 | + j = k + 1; |
| 68 | + } |
| 69 | + return results; |
| 70 | +} |
| 71 | + |
| 72 | typedef struct |
| 73 | { |
| 74 | - char major; |
| 75 | - char minor; |
| 76 | - uint16_t d[2]; |
| 77 | + uint8_t major; |
| 78 | + uint8_t minor; |
| 79 | + union { |
| 80 | + uint8_t aux[4]; // Individual bytes in IPMI big-endian order |
| 81 | + uint32_t aux32; // use htobe32() on writes to aux32 |
| 82 | + }; |
| 83 | } Revision; |
| 84 | |
| 85 | -/* Currently supports the vx.x-x-[-x] and v1.x.x-x-[-x] format. It will */ |
| 86 | -/* return -1 if not in those formats, this routine knows how to parse */ |
| 87 | +/* Currently supports the following formats. It will return -1 if not in */ |
| 88 | +/* those formats: */ |
| 89 | +/* */ |
| 90 | +/* Format 1: */ |
| 91 | /* version = v0.6-19-gf363f61-dirty */ |
| 92 | -/* ^ ^ ^^ ^ */ |
| 93 | -/* | | |----------|-- additional details */ |
| 94 | -/* | |---------------- Minor */ |
| 95 | -/* |------------------ Major */ |
| 96 | -/* and version = v1.99.10-113-g65edf7d-r3-0-g9e4f715 */ |
| 97 | -/* ^ ^ ^^ ^ */ |
| 98 | -/* | | |--|---------- additional details */ |
| 99 | -/* | |---------------- Minor */ |
| 100 | -/* |------------------ Major */ |
| 101 | -/* Additional details : If the option group exists it will force Auxiliary */ |
| 102 | -/* Firmware Revision Information 4th byte to 1 indicating the build was */ |
| 103 | -/* derived with additional edits */ |
| 104 | +/* ^ ^ ^^^^^^ ^^^^^ */ |
| 105 | +/* | | | | */ |
| 106 | +/* | | | `-- AUX dirty flag */ |
| 107 | +/* | | `---------- AUX commit hash */ |
| 108 | +/* | `---------------- Minor */ |
| 109 | +/* `------------------ Major */ |
| 110 | +/* */ |
| 111 | +/* Format 2: */ |
| 112 | +/* version = v1.99.10-113-g65edf7d-r3-0-g9e4f715-dirty */ |
| 113 | +/* ^ ^^ ^^^^^^ ^^^^^ */ |
| 114 | +/* | | | .-----------------' */ |
| 115 | +/* | | | `- AUX dirty flag */ |
| 116 | +/* | | `----- AUX commit hash */ |
| 117 | +/* | `---------------- Minor */ |
| 118 | +/* `------------------ Major */ |
| 119 | +/* */ |
| 120 | +/* version = v2.09-dev-794-g196400c89-some-branch-name-dirty */ |
| 121 | +/* ^ ^^ ^^^^^^ ^^^^^ */ |
| 122 | +/* | | | .-----------------------' */ |
| 123 | +/* | | | `- AUX dirty flag */ |
| 124 | +/* | | `---- AUX commit hash */ |
| 125 | +/* | `---------------- Minor */ |
| 126 | +/* `------------------ Major */ |
| 127 | +/* */ |
| 128 | +/* Format 3 (YADRO Releases): */ |
| 129 | +/* version = v1.0rcf2817p7-rc2-unofficial-dirty */ |
| 130 | +/* ^ ^ ^^^^^^ ^^ .----------^^^^^ */ |
| 131 | +/* | | | | `- AUX dirty flag */ |
| 132 | +/* | | | `------- AUX patch level (1-126), optional */ |
| 133 | +/* | | `-------------- AUX release number */ |
| 134 | +/* | `---------------- Minor */ |
| 135 | +/* `------------------ Major */ |
| 136 | +/* */ |
| 137 | +static |
| 138 | int convertVersion(std::string s, Revision& rev) |
| 139 | { |
| 140 | - std::string token; |
| 141 | - uint16_t commits; |
| 142 | + std::vector<std::string> tokens; |
| 143 | + bool has_release = false; // version string is of "release" format 3 |
| 144 | + bool dirty = false; |
| 145 | |
| 146 | + constexpr int TOKEN_MAJOR = 0; |
| 147 | + constexpr int TOKEN_MINOR = 1; |
| 148 | + // These are for "release" format 3 |
| 149 | + constexpr int TOKEN_MINOR_HASH = 1; |
| 150 | + constexpr int TOKEN_MINOR_PATCH = 2; |
| 151 | + // For non-release formats 1 and 2 |
| 152 | + constexpr int TOKEN_HASH = 3; // Search for git hash starting from this |
| 153 | + |
| 154 | + // Hash info is in the higher 24 bits of AUX F/W Revision Info |
| 155 | + constexpr int AUX_HASH_SHIFT = 8; |
| 156 | + constexpr int AUX_HASH_LEN = 6; |
| 157 | + |
| 158 | + // Non-release indicator is byte 3 (bits 7..1 of AUX F/W Revision Info) |
| 159 | + constexpr int AUX_NON_REL_BYTE = 3; |
| 160 | + constexpr int AUX_NON_REL_SHIFT = 1; |
| 161 | + constexpr uint8_t AUX_NON_REL_VALUE = UINT8_MAX >> AUX_NON_REL_SHIFT; |
| 162 | + |
| 163 | + // Release patch level occupies the same bits as the non-release indicator |
| 164 | + constexpr int AUX_PATCH_BYTE = AUX_NON_REL_BYTE; |
| 165 | + constexpr int AUX_PATCH_SHIFT = AUX_NON_REL_SHIFT; |
| 166 | + constexpr int AUX_MAX_PATCH = AUX_NON_REL_VALUE - 1; |
| 167 | + |
| 168 | + // The least significant bit of byte 3 is the dirty flag |
| 169 | + constexpr int AUX_DIRTY_BYTE = 3; |
| 170 | + constexpr int AUX_DIRTY_SHIFT = 0; |
| 171 | + |
| 172 | + // Use base-16 to convert decimals to BCD |
| 173 | + constexpr int BCD_BASE = 16; |
| 174 | + |
| 175 | + // First of all clear the revision |
| 176 | + rev = {0}; |
| 177 | + |
| 178 | + // Cut off the optional 'v' at the beginning |
| 179 | auto location = s.find_first_of('v'); |
| 180 | if (location != std::string::npos) |
| 181 | { |
| 182 | @@ -494,64 +576,77 @@ int convertVersion(std::string s, Revision& rev) |
| 183 | |
| 184 | if (!s.empty()) |
| 185 | { |
| 186 | - location = s.find_first_of("."); |
| 187 | - if (location != std::string::npos) |
| 188 | + int hash = 0; |
| 189 | + |
| 190 | + if (s.find("dirty") != std::string::npos) |
| 191 | { |
| 192 | - rev.major = |
| 193 | - static_cast<char>(std::stoi(s.substr(0, location), 0, 16)); |
| 194 | - token = s.substr(location + 1); |
| 195 | + dirty = true; |
| 196 | } |
| 197 | |
| 198 | - if (!token.empty()) |
| 199 | + tokens = tokenize(s, ".-"); |
| 200 | + |
| 201 | + if (!tokens.empty()) |
| 202 | { |
| 203 | - location = token.find_first_of(".-"); |
| 204 | - if (location != std::string::npos) |
| 205 | + rev.major = std::stoi(tokens[TOKEN_MAJOR], 0, BCD_BASE); |
| 206 | + } |
| 207 | + |
| 208 | + if (tokens.size() > TOKEN_MINOR) |
| 209 | + { |
| 210 | + rev.minor = std::stoi(tokens[TOKEN_MINOR], 0, BCD_BASE); |
| 211 | + |
| 212 | + // Minor version token may also contain release/patchlevel info |
| 213 | + std::vector<std::string> minortok; |
| 214 | + |
| 215 | + minortok = tokenize(tokens[TOKEN_MINOR], "rp"); |
| 216 | + |
| 217 | + if (minortok.size() > TOKEN_MINOR_HASH) |
| 218 | { |
| 219 | - rev.minor = static_cast<char>( |
| 220 | - std::stoi(token.substr(0, location), 0, 16)); |
| 221 | - token = token.substr(location + 1); |
| 222 | + // hash is plain hex |
| 223 | + hash= std::stoi(minortok[TOKEN_MINOR_HASH], 0, 16); |
| 224 | + has_release = true; |
| 225 | + } |
| 226 | + |
| 227 | + if (minortok.size() > TOKEN_MINOR_PATCH) |
| 228 | + { |
| 229 | + // Patch level is encoded as binary, not BCD. |
| 230 | + // That is to allow for a wider range. |
| 231 | + int pl = std::stoi(minortok[TOKEN_MINOR_PATCH], 0, 10); |
| 232 | + uint8_t patchlevel = (pl > AUX_MAX_PATCH) |
| 233 | + ? AUX_MAX_PATCH |
| 234 | + : pl; |
| 235 | + rev.aux[AUX_PATCH_BYTE] = patchlevel << AUX_PATCH_SHIFT; |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | - // Capture the number of commits on top of the minor tag. |
| 240 | - // I'm using BE format like the ipmi spec asked for |
| 241 | - location = token.find_first_of(".-"); |
| 242 | - if (!token.empty()) |
| 243 | + // If it's not a "release" format 3, then search for |
| 244 | + // letter 'g' indicating the position of a git hash |
| 245 | + // in the version string |
| 246 | + if (!has_release && tokens.size() > TOKEN_HASH) |
| 247 | { |
| 248 | - commits = std::stoi(token.substr(0, location), 0, 16); |
| 249 | - rev.d[0] = (commits >> 8) | (commits << 8); |
| 250 | - |
| 251 | - // commit number we skip |
| 252 | - location = token.find_first_of(".-"); |
| 253 | - if (location != std::string::npos) |
| 254 | + std::string hashstr; |
| 255 | + for (size_t i = TOKEN_HASH; i < tokens.size(); ++i) |
| 256 | { |
| 257 | - token = token.substr(location + 1); |
| 258 | + // Find the first token that looks like a git hash. |
| 259 | + // We think here that anything starting with a 'g' is a match. |
| 260 | + if ('g' == tokens[i][0]) |
| 261 | + { |
| 262 | + // Cut off the 'g', take only the first AUX_HASH_LEN digits |
| 263 | + hashstr = tokens[i].substr(1, AUX_HASH_LEN); |
| 264 | + break; |
| 265 | + } |
| 266 | } |
| 267 | - } |
| 268 | - else |
| 269 | - { |
| 270 | - rev.d[0] = 0; |
| 271 | - } |
| 272 | |
| 273 | - if (location != std::string::npos) |
| 274 | - { |
| 275 | - token = token.substr(location + 1); |
| 276 | + // Hash is plain hex |
| 277 | + hash = std::stoi(hashstr, 0, 16); |
| 278 | + rev.aux[AUX_NON_REL_BYTE] |= AUX_NON_REL_VALUE << AUX_NON_REL_SHIFT; |
| 279 | } |
| 280 | + rev.aux32 |= htobe32(hash << AUX_HASH_SHIFT); |
| 281 | + rev.aux[AUX_DIRTY_BYTE] |= dirty << AUX_DIRTY_SHIFT; |
| 282 | |
| 283 | - // Any value of the optional parameter forces it to 1 |
| 284 | - location = token.find_first_of(".-"); |
| 285 | - if (location != std::string::npos) |
| 286 | - { |
| 287 | - token = token.substr(location + 1); |
| 288 | - } |
| 289 | - commits = (!token.empty()) ? 1 : 0; |
| 290 | - |
| 291 | - // We do this operation to get this displayed in least significant bytes |
| 292 | - // of ipmitool device id command. |
| 293 | - rev.d[1] = (commits >> 8) | (commits << 8); |
| 294 | + return 0; |
| 295 | } |
| 296 | |
| 297 | - return 0; |
| 298 | + return -1; |
| 299 | } |
| 300 | |
| 301 | /* @brief: Implement the Get Device ID IPMI command per the IPMI spec |
| 302 | @@ -623,7 +718,7 @@ ipmi::RspType<uint8_t, // Device ID |
| 303 | |
| 304 | rev.minor = (rev.minor > 99 ? 99 : rev.minor); |
| 305 | devId.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16; |
| 306 | - std::memcpy(&devId.aux, rev.d, 4); |
| 307 | + std::memcpy(&devId.aux, rev.aux, 4); |
| 308 | } |
| 309 | |
| 310 | // IPMI Spec version 2.0 |
| 311 | @@ -640,7 +735,14 @@ ipmi::RspType<uint8_t, // Device ID |
| 312 | devId.addnDevSupport = data.value("addn_dev_support", 0); |
| 313 | devId.manufId = data.value("manuf_id", 0); |
| 314 | devId.prodId = data.value("prod_id", 0); |
| 315 | - devId.aux = data.value("aux", 0); |
| 316 | + |
| 317 | + // Use the AUX data from the file only for overriding |
| 318 | + // the data obtained from version string. |
| 319 | + if (data.contains("aux")) |
| 320 | + { |
| 321 | + // AUX F/W Revision Info is MSB first (big-endian) |
| 322 | + devId.aux = htobe32(data.value("aux", 0)); |
| 323 | + } |
| 324 | |
| 325 | // Set the availablitity of the BMC. |
| 326 | defaultActivationSetting = data.value("availability", true); |
| 327 | -- |
| 328 | 2.28.0 |
| 329 | |