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