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