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