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