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