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