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