1 #include "ipmi_fru_info_area.hpp" 2 3 #include <phosphor-logging/elog.hpp> 4 5 #include <algorithm> 6 #include <ctime> 7 #include <iomanip> 8 #include <map> 9 #include <numeric> 10 #include <sstream> 11 12 namespace ipmi 13 { 14 namespace fru 15 { 16 using namespace phosphor::logging; 17 18 // Property variables 19 static constexpr auto partNumber = "Part Number"; 20 static constexpr auto serialNumber = "Serial Number"; 21 static constexpr auto manufacturer = "Manufacturer"; 22 static constexpr auto buildDate = "Mfg Date"; 23 static constexpr auto modelNumber = "Model Number"; 24 static constexpr auto prettyName = "Name"; 25 static constexpr auto version = "Version"; 26 static constexpr auto type = "Type"; 27 28 // Board info areas 29 static constexpr auto board = "Board"; 30 static constexpr auto chassis = "Chassis"; 31 static constexpr auto product = "Product"; 32 33 static constexpr auto specVersion = 0x1; 34 static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes 35 static constexpr auto checksumSize = 0x1; // size in bytes 36 static constexpr auto recordNotPresent = 0x0; 37 static constexpr auto englishLanguageCode = 0x0; 38 static constexpr auto typeLengthByteNull = 0x0; 39 static constexpr auto endOfCustomFields = 0xC1; 40 static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes 41 static constexpr auto manufacturingDateSize = 0x3; 42 static constexpr auto areaSizeOffset = 0x1; 43 static constexpr uint8_t typeASCII = 0xC0; 44 static constexpr auto maxRecordAttributeValue = 0x3F; 45 46 static constexpr auto secs_from_1970_1996 = 820454400; 47 static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length 48 static constexpr auto secs_per_min = 60; 49 static constexpr auto secsToMaxMfgdate = secs_from_1970_1996 + 50 secs_per_min * maxMfgDateValue; 51 52 // Minimum size of resulting FRU blob. 53 // This is also the theoretical maximum size according to the spec: 54 // 8 bytes header + 5 areas at 0xff*8 bytes max each 55 // 8 + 5*0xff*8 = 0x27e0 56 static constexpr auto fruMinSize = 0x27E0; 57 58 // Value to use for padding. 59 // Using 0xff to match the default (blank) value in a physical EEPROM. 60 static constexpr auto fruPadValue = 0xff; 61 62 /** 63 * @brief Format Beginning of Individual IPMI FRU Data Section 64 * 65 * @param[in] langCode Language code 66 * @param[in/out] data FRU area data 67 */ 68 void preFormatProcessing(bool langCode, FruAreaData& data) 69 { 70 // Add id for version of FRU Info Storage Spec used 71 data.emplace_back(specVersion); 72 73 // Add Data Size - 0 as a placeholder, can edit after the data is finalized 74 data.emplace_back(typeLengthByteNull); 75 76 if (langCode) 77 { 78 data.emplace_back(englishLanguageCode); 79 } 80 } 81 82 /** 83 * @brief Append checksum of the FRU area data 84 * 85 * @param[in/out] data FRU area data 86 */ 87 void appendDataChecksum(FruAreaData& data) 88 { 89 uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0); 90 // Push the Zero checksum as the last byte of this data 91 // This appears to be a simple summation of all the bytes 92 data.emplace_back(-checksumVal); 93 } 94 95 /** 96 * @brief Append padding bytes for the FRU area data 97 * 98 * @param[in/out] data FRU area data 99 */ 100 void padData(FruAreaData& data) 101 { 102 uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement; 103 if (pad) 104 { 105 data.resize((data.size() + recordUnitOfMeasurement - pad)); 106 } 107 } 108 109 /** 110 * @brief Format End of Individual IPMI FRU Data Section 111 * 112 * @param[in/out] fruAreaData FRU area info data 113 */ 114 void postFormatProcessing(FruAreaData& data) 115 { 116 // This area needs to be padded to a multiple of 8 bytes (after checksum) 117 padData(data); 118 119 // Set size of data info area 120 data.at(areaSizeOffset) = (data.size() + checksumSize) / 121 (recordUnitOfMeasurement); 122 123 // Finally add area checksum 124 appendDataChecksum(data); 125 } 126 127 /** 128 * @brief Read chassis type property value from inventory and append to the FRU 129 * area data. 130 * 131 * @param[in] propMap map of property values 132 * @param[in,out] data FRU area data to be appended 133 */ 134 void appendChassisType(const PropertyMap& propMap, FruAreaData& data) 135 { 136 uint8_t chassisType = 0; // Not specified 137 auto iter = propMap.find(type); 138 if (iter != propMap.end()) 139 { 140 auto value = iter->second; 141 try 142 { 143 chassisType = std::stoi(value); 144 } 145 catch (const std::exception& e) 146 { 147 log<level::ERR>("Could not parse chassis type", 148 entry("VALUE=%s", value.c_str()), 149 entry("ERROR=%s", e.what())); 150 chassisType = 0; 151 } 152 } 153 data.emplace_back(chassisType); 154 } 155 156 /** 157 * @brief Read property value from inventory and append to the FRU area data 158 * 159 * @param[in] key key to search for in the property inventory data 160 * @param[in] propMap map of property values 161 * @param[in,out] data FRU area data to be appended 162 */ 163 void appendData(const Property& key, const PropertyMap& propMap, 164 FruAreaData& data) 165 { 166 auto iter = propMap.find(key); 167 if (iter != propMap.end()) 168 { 169 auto value = iter->second; 170 // If starts with 0x or 0X remove them 171 // ex: 0x123a just take 123a 172 if ((value.compare(0, 2, "0x")) == 0 || 173 (value.compare(0, 2, "0X") == 0)) 174 { 175 value.erase(0, 2); 176 } 177 178 // 6 bits for length as per FRU spec v1.0 179 // if length is greater then 63(2^6) bytes then trim the data to 63 180 // bytess. 181 auto valueLength = (value.length() > maxRecordAttributeValue) 182 ? maxRecordAttributeValue 183 : value.length(); 184 // 2 bits for type 185 // Set the type to ascii 186 uint8_t typeLength = valueLength | ipmi::fru::typeASCII; 187 188 data.emplace_back(typeLength); 189 std::copy(value.begin(), value.begin() + valueLength, 190 std::back_inserter(data)); 191 } 192 else 193 { 194 // set 0 size 195 data.emplace_back(typeLengthByteNull); 196 } 197 } 198 199 std::time_t timeStringToRaw(const std::string& input) 200 { 201 // TODO: For non-US region timestamps, pass in region information for the 202 // FRU to avoid the month/day swap. 203 // 2017-02-24 - 13:59:00, Tue Nov 20 23:08:00 2018 204 static const std::vector<std::string> patterns = {"%Y-%m-%d - %H:%M:%S", 205 "%a %b %d %H:%M:%S %Y"}; 206 207 std::tm time = {}; 208 209 for (const auto& pattern : patterns) 210 { 211 std::istringstream timeStream(input); 212 timeStream >> std::get_time(&time, pattern.c_str()); 213 if (!timeStream.fail()) 214 { 215 break; 216 } 217 } 218 219 return timegm(&time); 220 } 221 222 /** 223 * @brief Appends Build Date 224 * 225 * @param[in] propMap map of property values 226 * @param[in/out] data FRU area to add the manfufacture date 227 */ 228 void appendMfgDate(const PropertyMap& propMap, FruAreaData& data) 229 { 230 // MFG Date/Time 231 auto iter = propMap.find(buildDate); 232 if ((iter != propMap.end()) && (iter->second.size() > 0)) 233 { 234 std::time_t raw = timeStringToRaw(iter->second); 235 236 // From FRU Spec: 237 // "Mfg. Date / Time 238 // Number of minutes from 0:00 hrs 1/1/96. 239 // LSbyte first (little endian) 240 // 00_00_00h = unspecified." 241 if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate)) 242 { 243 raw -= secs_from_1970_1996; 244 raw /= secs_per_min; 245 uint8_t fru_raw[3]; 246 fru_raw[0] = raw & 0xFF; 247 fru_raw[1] = (raw >> 8) & 0xFF; 248 fru_raw[2] = (raw >> 16) & 0xFF; 249 std::copy(fru_raw, fru_raw + 3, std::back_inserter(data)); 250 return; 251 } 252 std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n", 253 static_cast<unsigned int>(raw)); 254 } 255 // Blank date 256 data.emplace_back(0); 257 data.emplace_back(0); 258 data.emplace_back(0); 259 } 260 261 /** 262 * @brief Builds a section of the common header 263 * 264 * @param[in] infoAreaSize size of the FRU area to write 265 * @param[in] offset Current offset for data in overall record 266 * @param[in/out] data Common Header section data container 267 */ 268 void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset, 269 FruAreaData& data) 270 { 271 // Check if data for internal use section populated 272 if (infoAreaSize == 0) 273 { 274 // Indicate record not present 275 data.emplace_back(recordNotPresent); 276 } 277 else 278 { 279 // offset should be multiple of 8. 280 auto remainder = offset % recordUnitOfMeasurement; 281 // add the padding bytes in the offset so that offset 282 // will be multiple of 8 byte. 283 offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0; 284 // Place data to define offset to area data section 285 data.emplace_back(offset / recordUnitOfMeasurement); 286 287 offset += infoAreaSize; 288 } 289 } 290 291 /** 292 * @brief Builds the Chassis info area data section 293 * 294 * @param[in] propMap map of properties for chassis info area 295 * @return FruAreaData container with chassis info area 296 */ 297 FruAreaData buildChassisInfoArea(const PropertyMap& propMap) 298 { 299 FruAreaData fruAreaData; 300 if (!propMap.empty()) 301 { 302 // Set formatting data that goes at the beginning of the record 303 preFormatProcessing(false, fruAreaData); 304 305 // chassis type 306 appendChassisType(propMap, fruAreaData); 307 308 // Chasiss part number, in config.yaml it is configured as model 309 appendData(modelNumber, propMap, fruAreaData); 310 311 // Board serial number 312 appendData(serialNumber, propMap, fruAreaData); 313 314 // Indicate End of Custom Fields 315 fruAreaData.emplace_back(endOfCustomFields); 316 317 // Complete record data formatting 318 postFormatProcessing(fruAreaData); 319 } 320 return fruAreaData; 321 } 322 323 /** 324 * @brief Builds the Board info area data section 325 * 326 * @param[in] propMap map of properties for board info area 327 * @return FruAreaData container with board info area 328 */ 329 FruAreaData buildBoardInfoArea(const PropertyMap& propMap) 330 { 331 FruAreaData fruAreaData; 332 if (!propMap.empty()) 333 { 334 preFormatProcessing(true, fruAreaData); 335 336 // Manufacturing date 337 appendMfgDate(propMap, fruAreaData); 338 339 // manufacturer 340 appendData(manufacturer, propMap, fruAreaData); 341 342 // Product name/Pretty name 343 appendData(prettyName, propMap, fruAreaData); 344 345 // Board serial number 346 appendData(serialNumber, propMap, fruAreaData); 347 348 // Board part number 349 appendData(partNumber, propMap, fruAreaData); 350 351 // FRU File ID - Empty 352 fruAreaData.emplace_back(typeLengthByteNull); 353 354 // Empty FRU File ID bytes 355 fruAreaData.emplace_back(recordNotPresent); 356 357 // End of custom fields 358 fruAreaData.emplace_back(endOfCustomFields); 359 360 postFormatProcessing(fruAreaData); 361 } 362 return fruAreaData; 363 } 364 365 /** 366 * @brief Builds the Product info area data section 367 * 368 * @param[in] propMap map of FRU properties for Board info area 369 * @return FruAreaData container with product info area data 370 */ 371 FruAreaData buildProductInfoArea(const PropertyMap& propMap) 372 { 373 FruAreaData fruAreaData; 374 if (!propMap.empty()) 375 { 376 // Set formatting data that goes at the beginning of the record 377 preFormatProcessing(true, fruAreaData); 378 379 // manufacturer 380 appendData(manufacturer, propMap, fruAreaData); 381 382 // Product name/Pretty name 383 appendData(prettyName, propMap, fruAreaData); 384 385 // Product part/model number 386 appendData(modelNumber, propMap, fruAreaData); 387 388 // Product version 389 appendData(version, propMap, fruAreaData); 390 391 // Serial Number 392 appendData(serialNumber, propMap, fruAreaData); 393 394 // Add Asset Tag 395 fruAreaData.emplace_back(recordNotPresent); 396 397 // FRU File ID - Empty 398 fruAreaData.emplace_back(typeLengthByteNull); 399 400 // Empty FRU File ID bytes 401 fruAreaData.emplace_back(recordNotPresent); 402 403 // End of custom fields 404 fruAreaData.emplace_back(endOfCustomFields); 405 406 postFormatProcessing(fruAreaData); 407 } 408 return fruAreaData; 409 } 410 411 FruAreaData buildFruAreaData(const FruInventoryData& inventory) 412 { 413 FruAreaData combFruArea{}; 414 // Now build common header with data for this FRU Inv Record 415 // Use this variable to increment size of header as we go along to determine 416 // offset for the subsequent area offsets 417 uint16_t curDataOffset = commonHeaderFormatSize; 418 // First byte is id for version of FRU Info Storage Spec used 419 combFruArea.emplace_back(specVersion); 420 421 // 2nd byte is offset to internal use data 422 combFruArea.emplace_back(recordNotPresent); 423 424 // 3rd byte is offset to chassis data 425 FruAreaData chassisArea; 426 auto chassisIt = inventory.find(chassis); 427 if (chassisIt != inventory.end()) 428 { 429 chassisArea = buildChassisInfoArea(chassisIt->second); 430 } 431 // update the offset to chassis data. 432 buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea); 433 434 // 4th byte is offset to board data 435 FruAreaData boardArea; 436 auto boardIt = inventory.find(board); 437 if (boardIt != inventory.end()) 438 { 439 boardArea = buildBoardInfoArea(boardIt->second); 440 } 441 // update the offset to the board data. 442 buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea); 443 444 // 5th byte is offset to product data 445 FruAreaData prodArea; 446 auto prodIt = inventory.find(product); 447 if (prodIt != inventory.end()) 448 { 449 prodArea = buildProductInfoArea(prodIt->second); 450 } 451 // update the offset to the product data. 452 buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea); 453 454 // 6th byte is offset to multirecord data 455 combFruArea.emplace_back(recordNotPresent); 456 457 // 7th byte is PAD 458 combFruArea.emplace_back(recordNotPresent); 459 460 // 8th (Final byte of Header Format) is the checksum 461 appendDataChecksum(combFruArea); 462 463 // Combine everything into one full IPMI FRU specification Record 464 // add chassis use area data 465 combFruArea.insert(combFruArea.end(), chassisArea.begin(), 466 chassisArea.end()); 467 468 // add board area data 469 combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end()); 470 471 // add product use area data 472 combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end()); 473 474 // If area is smaller than the minimum size, pad it. This enables ipmitool 475 // to update the FRU blob with values longer than the original payload. 476 if (combFruArea.size() < fruMinSize) 477 { 478 combFruArea.resize(fruMinSize, fruPadValue); 479 } 480 481 return combFruArea; 482 } 483 484 } // namespace fru 485 } // namespace ipmi 486