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