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