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