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