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         try
139         {
140             chassisType = std::stoi(value);
141         }
142         catch (std::exception& e)
143         {
144             log<level::ERR>("Could not parse chassis type",
145                             entry("VALUE=%s", value.c_str()),
146                             entry("ERROR=%s", e.what()));
147             chassisType = 0;
148         }
149     }
150     data.emplace_back(chassisType);
151 }
152 
153 /**
154  * @brief Read property value from inventory and append to the FRU area data
155  *
156  * @param[in] key key to search for in the property inventory data
157  * @param[in] propMap map of property values
158  * @param[in,out] data FRU area data to be appended
159  */
160 void appendData(const Property& key, const PropertyMap& propMap,
161                 FruAreaData& data)
162 {
163     auto iter = propMap.find(key);
164     if (iter != propMap.end())
165     {
166         auto value = iter->second;
167         // If starts with 0x or 0X remove them
168         // ex: 0x123a just take 123a
169         if ((value.compare(0, 2, "0x")) == 0 ||
170             (value.compare(0, 2, "0X") == 0))
171         {
172             value.erase(0, 2);
173         }
174 
175         // 5 bits for length
176         // if length is greater then 31(2^5) bytes then trim the data to 31
177         // bytess.
178         auto valueLength = (value.length() > maxRecordAttributeValue)
179                                ? maxRecordAttributeValue
180                                : value.length();
181         // 2 bits for type
182         // Set the type to ascii
183         uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
184 
185         data.emplace_back(typeLength);
186         std::copy(value.begin(), value.begin() + valueLength,
187                   std::back_inserter(data));
188     }
189     else
190     {
191         // set 0 size
192         data.emplace_back(typeLengthByteNull);
193     }
194 }
195 
196 /**
197  * @brief Appends Build Date
198  *
199  * @param[in] propMap map of property values
200  * @param[in/out] data FRU area to add the manfufacture date
201  */
202 void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
203 {
204     // MFG Date/Time
205     auto iter = propMap.find(buildDate);
206     if ((iter != propMap.end()) && (iter->second.size() > 0))
207     {
208         tm time = {};
209         strptime(iter->second.c_str(), "%F - %H:%M:%S", &time);
210         time_t raw = mktime(&time);
211 
212         // From FRU Spec:
213         // "Mfg. Date / Time
214         // Number of minutes from 0:00 hrs 1/1/96.
215         // LSbyte first (little endian)
216         // 00_00_00h = unspecified."
217         if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
218         {
219             raw -= secs_from_1970_1996;
220             raw /= secs_per_min;
221             uint8_t fru_raw[3];
222             fru_raw[0] = raw & 0xFF;
223             fru_raw[1] = (raw >> 8) & 0xFF;
224             fru_raw[2] = (raw >> 16) & 0xFF;
225             std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
226             return;
227         }
228         std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
229                      static_cast<unsigned int>(raw));
230     }
231     // Blank date
232     data.emplace_back(0);
233     data.emplace_back(0);
234     data.emplace_back(0);
235 }
236 
237 /**
238  * @brief Builds a section of the common header
239  *
240  * @param[in] infoAreaSize size of the FRU area to write
241  * @param[in] offset Current offset for data in overall record
242  * @param[in/out] data Common Header section data container
243  */
244 void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
245                               FruAreaData& data)
246 {
247     // Check if data for internal use section populated
248     if (infoAreaSize == 0)
249     {
250         // Indicate record not present
251         data.emplace_back(recordNotPresent);
252     }
253     else
254     {
255         // offset should be multiple of 8.
256         auto remainder = offset % recordUnitOfMeasurement;
257         // add the padding bytes in the offset so that offset
258         // will be multiple of 8 byte.
259         offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
260         // Place data to define offset to area data section
261         data.emplace_back(offset / recordUnitOfMeasurement);
262 
263         offset += infoAreaSize;
264     }
265 }
266 
267 /**
268  * @brief Builds the Chassis info area data section
269  *
270  * @param[in] propMap map of properties for chassis info area
271  * @return FruAreaData container with chassis info area
272  */
273 FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
274 {
275     FruAreaData fruAreaData;
276     if (!propMap.empty())
277     {
278         // Set formatting data that goes at the beginning of the record
279         preFormatProcessing(false, fruAreaData);
280 
281         // chassis type
282         appendChassisType(propMap, fruAreaData);
283 
284         // Chasiss part number, in config.yaml it is configured as model
285         appendData(model, propMap, fruAreaData);
286 
287         // Board serial number
288         appendData(serialNumber, propMap, fruAreaData);
289 
290         // Indicate End of Custom Fields
291         fruAreaData.emplace_back(endOfCustomFields);
292 
293         // Complete record data formatting
294         postFormatProcessing(fruAreaData);
295     }
296     return fruAreaData;
297 }
298 
299 /**
300  * @brief Builds the Board info area data section
301  *
302  * @param[in] propMap map of properties for board info area
303  * @return FruAreaData container with board info area
304  */
305 FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
306 {
307     FruAreaData fruAreaData;
308     if (!propMap.empty())
309     {
310         preFormatProcessing(true, fruAreaData);
311 
312         // Manufacturing date
313         appendMfgDate(propMap, fruAreaData);
314 
315         // manufacturer
316         appendData(manufacturer, propMap, fruAreaData);
317 
318         // Product name/Pretty name
319         appendData(prettyName, propMap, fruAreaData);
320 
321         // Board serial number
322         appendData(serialNumber, propMap, fruAreaData);
323 
324         // Board part number
325         appendData(partNumber, propMap, fruAreaData);
326 
327         // FRU File ID - Empty
328         fruAreaData.emplace_back(typeLengthByteNull);
329 
330         // Empty FRU File ID bytes
331         fruAreaData.emplace_back(recordNotPresent);
332 
333         // End of custom fields
334         fruAreaData.emplace_back(endOfCustomFields);
335 
336         postFormatProcessing(fruAreaData);
337     }
338     return fruAreaData;
339 }
340 
341 /**
342  * @brief Builds the Product info area data section
343  *
344  * @param[in] propMap map of FRU properties for Board info area
345  * @return FruAreaData container with product info area data
346  */
347 FruAreaData buildProductInfoArea(const PropertyMap& propMap)
348 {
349     FruAreaData fruAreaData;
350     if (!propMap.empty())
351     {
352         // Set formatting data that goes at the beginning of the record
353         preFormatProcessing(true, fruAreaData);
354 
355         // manufacturer
356         appendData(manufacturer, propMap, fruAreaData);
357 
358         // Product name/Pretty name
359         appendData(prettyName, propMap, fruAreaData);
360 
361         // Product part/model number
362         appendData(model, propMap, fruAreaData);
363 
364         // Product version
365         appendData(version, propMap, fruAreaData);
366 
367         // Serial Number
368         appendData(serialNumber, propMap, fruAreaData);
369 
370         // Add Asset Tag
371         fruAreaData.emplace_back(recordNotPresent);
372 
373         // FRU File ID - Empty
374         fruAreaData.emplace_back(typeLengthByteNull);
375 
376         // Empty FRU File ID bytes
377         fruAreaData.emplace_back(recordNotPresent);
378 
379         // End of custom fields
380         fruAreaData.emplace_back(endOfCustomFields);
381 
382         postFormatProcessing(fruAreaData);
383     }
384     return fruAreaData;
385 }
386 
387 FruAreaData buildFruAreaData(const FruInventoryData& inventory)
388 {
389     FruAreaData combFruArea{};
390     // Now build common header with data for this FRU Inv Record
391     // Use this variable to increment size of header as we go along to determine
392     // offset for the subsequent area offsets
393     uint16_t curDataOffset = commonHeaderFormatSize;
394     // First byte is id for version of FRU Info Storage Spec used
395     combFruArea.emplace_back(specVersion);
396 
397     // 2nd byte is offset to internal use data
398     combFruArea.emplace_back(recordNotPresent);
399 
400     // 3rd byte is offset to chassis data
401     FruAreaData chassisArea;
402     auto chassisIt = inventory.find(chassis);
403     if (chassisIt != inventory.end())
404     {
405         chassisArea = std::move(buildChassisInfoArea(chassisIt->second));
406     }
407     // update the offset to chassis data.
408     buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
409 
410     // 4th byte is offset to board data
411     FruAreaData boardArea;
412     auto boardIt = inventory.find(board);
413     if (boardIt != inventory.end())
414     {
415         boardArea = std::move(buildBoardInfoArea(boardIt->second));
416     }
417     // update the offset to the board data.
418     buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
419 
420     // 5th byte is offset to product data
421     FruAreaData prodArea;
422     auto prodIt = inventory.find(product);
423     if (prodIt != inventory.end())
424     {
425         prodArea = std::move(buildProductInfoArea(prodIt->second));
426     }
427     // update the offset to the product data.
428     buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
429 
430     // 6th byte is offset to multirecord data
431     combFruArea.emplace_back(recordNotPresent);
432 
433     // 7th byte is PAD
434     combFruArea.emplace_back(recordNotPresent);
435 
436     // 8th (Final byte of Header Format) is the checksum
437     appendDataChecksum(combFruArea);
438 
439     // Combine everything into one full IPMI FRU specification Record
440     // add chassis use area data
441     combFruArea.insert(combFruArea.end(), chassisArea.begin(),
442                        chassisArea.end());
443 
444     // add board area data
445     combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
446 
447     // add product use area data
448     combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
449 
450     // If area is smaller than the minimum size, pad it. This enables ipmitool
451     // to update the FRU blob with values longer than the original payload.
452     if (combFruArea.size() < fruMinSize)
453     {
454         combFruArea.resize(fruMinSize, fruPadValue);
455     }
456 
457     return combFruArea;
458 }
459 
460 } // namespace fru
461 } // namespace ipmi
462