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