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