/src/phosphor-host-ipmid/ipmi_fru_info_area.cpp
Line | Count | Source |
1 | | #include "ipmi_fru_info_area.hpp" |
2 | | |
3 | | #include <ipmid/utils.hpp> |
4 | | #include <phosphor-logging/elog.hpp> |
5 | | #include <phosphor-logging/lg2.hpp> |
6 | | |
7 | | #include <algorithm> |
8 | | #include <ctime> |
9 | | #include <iomanip> |
10 | | #include <map> |
11 | | #include <numeric> |
12 | | #include <sstream> |
13 | | |
14 | | namespace ipmi |
15 | | { |
16 | | namespace fru |
17 | | { |
18 | | using namespace phosphor::logging; |
19 | | |
20 | | // Property variables |
21 | | static constexpr auto partNumber = "Part Number"; |
22 | | static constexpr auto serialNumber = "Serial Number"; |
23 | | static constexpr auto manufacturer = "Manufacturer"; |
24 | | static constexpr auto buildDate = "Mfg Date"; |
25 | | static constexpr auto modelNumber = "Model Number"; |
26 | | static constexpr auto prettyName = "Name"; |
27 | | static constexpr auto version = "Version"; |
28 | | static constexpr auto type = "Type"; |
29 | | |
30 | | // Board info areas |
31 | | static constexpr auto board = "Board"; |
32 | | static constexpr auto chassis = "Chassis"; |
33 | | static constexpr auto product = "Product"; |
34 | | |
35 | | static constexpr auto specVersion = 0x1; |
36 | | static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes |
37 | | static constexpr auto checksumSize = 0x1; // size in bytes |
38 | | static constexpr auto recordNotPresent = 0x0; |
39 | | static constexpr auto englishLanguageCode = 0x0; |
40 | | static constexpr auto typeLengthByteNull = 0x0; |
41 | | static constexpr auto endOfCustomFields = 0xC1; |
42 | | static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes |
43 | | static constexpr auto areaSizeOffset = 0x1; |
44 | | static constexpr uint8_t typeASCII = 0xC0; |
45 | | static constexpr auto maxRecordAttributeValue = 0x3F; |
46 | | |
47 | | static constexpr auto secs_from_1970_1996 = 820454400; |
48 | | static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length |
49 | | static constexpr auto secs_per_min = 60; |
50 | | static constexpr auto secsToMaxMfgdate = |
51 | | secs_from_1970_1996 + secs_per_min * maxMfgDateValue; |
52 | | |
53 | | // Minimum size of resulting FRU blob. |
54 | | // This is also the theoretical maximum size according to the spec: |
55 | | // 8 bytes header + 5 areas at 0xff*8 bytes max each |
56 | | // 8 + 5*0xff*8 = 0x27e0 |
57 | | static constexpr auto fruMinSize = 0x27E0; |
58 | | |
59 | | // Value to use for padding. |
60 | | // Using 0xff to match the default (blank) value in a physical EEPROM. |
61 | | static constexpr auto fruPadValue = 0xff; |
62 | | |
63 | | /** |
64 | | * @brief Format Beginning of Individual IPMI FRU Data Section |
65 | | * |
66 | | * @param[in] langCode Language code |
67 | | * @param[in/out] data FRU area data |
68 | | */ |
69 | | void preFormatProcessing(bool langCode, FruAreaData& data) |
70 | 1.13k | { |
71 | | // Add id for version of FRU Info Storage Spec used |
72 | 1.13k | data.emplace_back(specVersion); |
73 | | |
74 | | // Add Data Size - 0 as a placeholder, can edit after the data is finalized |
75 | 1.13k | data.emplace_back(typeLengthByteNull); |
76 | | |
77 | 1.13k | if (langCode) |
78 | 758 | { |
79 | 758 | data.emplace_back(englishLanguageCode); |
80 | 758 | } |
81 | 1.13k | } |
82 | | |
83 | | /** |
84 | | * @brief Append checksum of the FRU area data |
85 | | * |
86 | | * @param[in/out] data FRU area data |
87 | | */ |
88 | | void appendDataChecksum(FruAreaData& data) |
89 | 1.97k | { |
90 | 1.97k | uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0); |
91 | | // Push the Zero checksum as the last byte of this data |
92 | | // This appears to be a simple summation of all the bytes |
93 | 1.97k | data.emplace_back(-checksumVal); |
94 | 1.97k | } |
95 | | |
96 | | /** |
97 | | * @brief Append padding bytes for the FRU area data |
98 | | * |
99 | | * @param[in/out] data FRU area data |
100 | | */ |
101 | | void padData(FruAreaData& data) |
102 | 1.13k | { |
103 | 1.13k | uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement; |
104 | 1.13k | if (pad) |
105 | 1.00k | { |
106 | 1.00k | data.resize((data.size() + recordUnitOfMeasurement - pad)); |
107 | 1.00k | } |
108 | 1.13k | } |
109 | | |
110 | | /** |
111 | | * @brief Format End of Individual IPMI FRU Data Section |
112 | | * |
113 | | * @param[in/out] fruAreaData FRU area info data |
114 | | */ |
115 | | void postFormatProcessing(FruAreaData& data) |
116 | 1.13k | { |
117 | | // This area needs to be padded to a multiple of 8 bytes (after checksum) |
118 | 1.13k | padData(data); |
119 | | |
120 | | // Set size of data info area |
121 | 1.13k | data.at(areaSizeOffset) = |
122 | 1.13k | (data.size() + checksumSize) / (recordUnitOfMeasurement); |
123 | | |
124 | | // Finally add area checksum |
125 | 1.13k | appendDataChecksum(data); |
126 | 1.13k | } |
127 | | |
128 | | /** |
129 | | * @brief Read chassis type property value from inventory and append to the FRU |
130 | | * area data. |
131 | | * |
132 | | * @param[in] propMap map of property values |
133 | | * @param[in,out] data FRU area data to be appended |
134 | | */ |
135 | | void appendChassisType(const PropertyMap& propMap, FruAreaData& data) |
136 | 375 | { |
137 | 375 | uint8_t chassisType = 0; // Not specified |
138 | 375 | auto iter = propMap.find(type); |
139 | 375 | if (iter != propMap.end()) |
140 | 205 | { |
141 | 205 | auto value = iter->second; |
142 | 205 | if (!ipmi::tryParse(value, chassisType)) |
143 | 183 | { |
144 | 183 | lg2::error("Could not parse chassis type, value: {VALUE}", "VALUE", |
145 | 183 | value); |
146 | 183 | chassisType = 0; |
147 | 183 | } |
148 | 205 | } |
149 | 375 | data.emplace_back(chassisType); |
150 | 375 | } |
151 | | |
152 | | /** |
153 | | * @brief Read property value from inventory and append to the FRU area data |
154 | | * |
155 | | * @param[in] key key to search for in the property inventory data |
156 | | * @param[in] propMap map of property values |
157 | | * @param[in,out] data FRU area data to be appended |
158 | | */ |
159 | | void appendData(const Property& key, const PropertyMap& propMap, |
160 | | FruAreaData& data) |
161 | 4.11k | { |
162 | 4.11k | auto iter = propMap.find(key); |
163 | 4.11k | if (iter != propMap.end()) |
164 | 1.78k | { |
165 | 1.78k | auto value = iter->second; |
166 | | // If starts with 0x or 0X remove them |
167 | | // ex: 0x123a just take 123a |
168 | 1.78k | if ((value.compare(0, 2, "0x")) == 0 || |
169 | 1.74k | (value.compare(0, 2, "0X") == 0)) |
170 | 100 | { |
171 | 100 | value.erase(0, 2); |
172 | 100 | } |
173 | | |
174 | | // 6 bits for length as per FRU spec v1.0 |
175 | | // if length is greater then 63(2^6) bytes then trim the data to 63 |
176 | | // bytes. |
177 | 1.78k | auto valueLength = (value.length() > maxRecordAttributeValue) |
178 | 1.78k | ? maxRecordAttributeValue |
179 | 1.78k | : value.length(); |
180 | | // 2 bits for type |
181 | | // Set the type to ascii |
182 | 1.78k | uint8_t typeLength = valueLength | ipmi::fru::typeASCII; |
183 | | |
184 | 1.78k | data.emplace_back(typeLength); |
185 | 1.78k | std::copy(value.begin(), value.begin() + valueLength, |
186 | 1.78k | std::back_inserter(data)); |
187 | 1.78k | } |
188 | 2.33k | else |
189 | 2.33k | { |
190 | | // set 0 size |
191 | 2.33k | data.emplace_back(typeLengthByteNull); |
192 | 2.33k | } |
193 | 4.11k | } |
194 | | |
195 | | std::time_t timeStringToRaw(const std::string& input) |
196 | 286 | { |
197 | | // TODO: For non-US region timestamps, pass in region information for the |
198 | | // FRU to avoid the month/day swap. |
199 | | // 2017-02-24 - 13:59:00, Tue Nov 20 23:08:00 2018, 20251224T064200Z |
200 | 286 | static const std::vector<std::string> patterns = { |
201 | 286 | "%Y-%m-%d - %H:%M:%S", "%a %b %d %H:%M:%S %Y", "%Y%m%dT%H%M%SZ"}; |
202 | | |
203 | 286 | std::tm time = {}; |
204 | | |
205 | 286 | for (const auto& pattern : patterns) |
206 | 719 | { |
207 | 719 | std::istringstream timeStream(input); |
208 | 719 | timeStream >> std::get_time(&time, pattern.c_str()); |
209 | 719 | if (!timeStream.fail()) |
210 | 154 | { |
211 | 154 | break; |
212 | 154 | } |
213 | 719 | } |
214 | | |
215 | 286 | return timegm(&time); |
216 | 286 | } |
217 | | |
218 | | /** |
219 | | * @brief Appends Build Date |
220 | | * |
221 | | * @param[in] propMap map of property values |
222 | | * @param[in/out] data FRU area to add the manfufacture date |
223 | | */ |
224 | | void appendMfgDate(const PropertyMap& propMap, FruAreaData& data) |
225 | 425 | { |
226 | | // MFG Date/Time |
227 | 425 | auto iter = propMap.find(buildDate); |
228 | 425 | if ((iter != propMap.end()) && (iter->second.size() > 0)) |
229 | 286 | { |
230 | 286 | std::time_t raw = timeStringToRaw(iter->second); |
231 | | |
232 | | // From FRU Spec: |
233 | | // "Mfg. Date / Time |
234 | | // Number of minutes from 0:00 hrs 1/1/96. |
235 | | // LSbyte first (little endian) |
236 | | // 00_00_00h = unspecified." |
237 | 286 | if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate)) |
238 | 46 | { |
239 | 46 | raw -= secs_from_1970_1996; |
240 | 46 | raw /= secs_per_min; |
241 | 46 | uint8_t fru_raw[3]; |
242 | 46 | fru_raw[0] = raw & 0xFF; |
243 | 46 | fru_raw[1] = (raw >> 8) & 0xFF; |
244 | 46 | fru_raw[2] = (raw >> 16) & 0xFF; |
245 | 46 | std::copy(fru_raw, fru_raw + 3, std::back_inserter(data)); |
246 | 46 | return; |
247 | 46 | } |
248 | 240 | std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n", |
249 | 240 | static_cast<unsigned int>(raw)); |
250 | 240 | } |
251 | | // Blank date |
252 | 379 | data.emplace_back(0); |
253 | 379 | data.emplace_back(0); |
254 | 379 | data.emplace_back(0); |
255 | 379 | } |
256 | | |
257 | | /** |
258 | | * @brief Builds a section of the common header |
259 | | * |
260 | | * @param[in] infoAreaSize size of the FRU area to write |
261 | | * @param[in] offset Current offset for data in overall record |
262 | | * @param[in/out] data Common Header section data container |
263 | | */ |
264 | | void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset, |
265 | | FruAreaData& data) |
266 | 2.53k | { |
267 | | // Check if data for internal use section populated |
268 | 2.53k | if (infoAreaSize == 0) |
269 | 1.40k | { |
270 | | // Indicate record not present |
271 | 1.40k | data.emplace_back(recordNotPresent); |
272 | 1.40k | } |
273 | 1.13k | else |
274 | 1.13k | { |
275 | | // offset should be multiple of 8. |
276 | 1.13k | auto remainder = offset % recordUnitOfMeasurement; |
277 | | // add the padding bytes in the offset so that offset |
278 | | // will be multiple of 8 byte. |
279 | 1.13k | offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0; |
280 | | // Place data to define offset to area data section |
281 | 1.13k | data.emplace_back(offset / recordUnitOfMeasurement); |
282 | | |
283 | 1.13k | offset += infoAreaSize; |
284 | 1.13k | } |
285 | 2.53k | } |
286 | | |
287 | | /** |
288 | | * @brief Builds the Chassis info area data section |
289 | | * |
290 | | * @param[in] propMap map of properties for chassis info area |
291 | | * @return FruAreaData container with chassis info area |
292 | | */ |
293 | | FruAreaData buildChassisInfoArea(const PropertyMap& propMap) |
294 | 375 | { |
295 | 375 | FruAreaData fruAreaData; |
296 | 375 | if (!propMap.empty()) |
297 | 375 | { |
298 | | // Set formatting data that goes at the beginning of the record |
299 | 375 | preFormatProcessing(false, fruAreaData); |
300 | | |
301 | | // chassis type |
302 | 375 | appendChassisType(propMap, fruAreaData); |
303 | | |
304 | | // Chasiss part number, in config.yaml it is configured as model |
305 | 375 | appendData(modelNumber, propMap, fruAreaData); |
306 | | |
307 | | // Board serial number |
308 | 375 | appendData(serialNumber, propMap, fruAreaData); |
309 | | |
310 | | // Indicate End of Custom Fields |
311 | 375 | fruAreaData.emplace_back(endOfCustomFields); |
312 | | |
313 | | // Complete record data formatting |
314 | 375 | postFormatProcessing(fruAreaData); |
315 | 375 | } |
316 | 375 | return fruAreaData; |
317 | 375 | } |
318 | | |
319 | | /** |
320 | | * @brief Builds the Board info area data section |
321 | | * |
322 | | * @param[in] propMap map of properties for board info area |
323 | | * @return FruAreaData container with board info area |
324 | | */ |
325 | | FruAreaData buildBoardInfoArea(const PropertyMap& propMap) |
326 | 425 | { |
327 | 425 | FruAreaData fruAreaData; |
328 | 425 | if (!propMap.empty()) |
329 | 425 | { |
330 | 425 | preFormatProcessing(true, fruAreaData); |
331 | | |
332 | | // Manufacturing date |
333 | 425 | appendMfgDate(propMap, fruAreaData); |
334 | | |
335 | | // manufacturer |
336 | 425 | appendData(manufacturer, propMap, fruAreaData); |
337 | | |
338 | | // Product name/Pretty name |
339 | 425 | appendData(prettyName, propMap, fruAreaData); |
340 | | |
341 | | // Board serial number |
342 | 425 | appendData(serialNumber, propMap, fruAreaData); |
343 | | |
344 | | // Board part number |
345 | 425 | appendData(partNumber, propMap, fruAreaData); |
346 | | |
347 | | // FRU File ID - Empty |
348 | 425 | fruAreaData.emplace_back(typeLengthByteNull); |
349 | | |
350 | | // Empty FRU File ID bytes |
351 | 425 | fruAreaData.emplace_back(recordNotPresent); |
352 | | |
353 | | // End of custom fields |
354 | 425 | fruAreaData.emplace_back(endOfCustomFields); |
355 | | |
356 | 425 | postFormatProcessing(fruAreaData); |
357 | 425 | } |
358 | 425 | return fruAreaData; |
359 | 425 | } |
360 | | |
361 | | /** |
362 | | * @brief Builds the Product info area data section |
363 | | * |
364 | | * @param[in] propMap map of FRU properties for Board info area |
365 | | * @return FruAreaData container with product info area data |
366 | | */ |
367 | | FruAreaData buildProductInfoArea(const PropertyMap& propMap) |
368 | 333 | { |
369 | 333 | FruAreaData fruAreaData; |
370 | 333 | if (!propMap.empty()) |
371 | 333 | { |
372 | | // Set formatting data that goes at the beginning of the record |
373 | 333 | preFormatProcessing(true, fruAreaData); |
374 | | |
375 | | // manufacturer |
376 | 333 | appendData(manufacturer, propMap, fruAreaData); |
377 | | |
378 | | // Product name/Pretty name |
379 | 333 | appendData(prettyName, propMap, fruAreaData); |
380 | | |
381 | | // Product part/model number |
382 | 333 | appendData(modelNumber, propMap, fruAreaData); |
383 | | |
384 | | // Product version |
385 | 333 | appendData(version, propMap, fruAreaData); |
386 | | |
387 | | // Serial Number |
388 | 333 | appendData(serialNumber, propMap, fruAreaData); |
389 | | |
390 | | // Add Asset Tag |
391 | 333 | fruAreaData.emplace_back(recordNotPresent); |
392 | | |
393 | | // FRU File ID - Empty |
394 | 333 | fruAreaData.emplace_back(typeLengthByteNull); |
395 | | |
396 | | // Empty FRU File ID bytes |
397 | 333 | fruAreaData.emplace_back(recordNotPresent); |
398 | | |
399 | | // End of custom fields |
400 | 333 | fruAreaData.emplace_back(endOfCustomFields); |
401 | | |
402 | 333 | postFormatProcessing(fruAreaData); |
403 | 333 | } |
404 | 333 | return fruAreaData; |
405 | 333 | } |
406 | | |
407 | | FruAreaData buildFruAreaData(const FruInventoryData& inventory) |
408 | 845 | { |
409 | 845 | FruAreaData combFruArea{}; |
410 | | // Now build common header with data for this FRU Inv Record |
411 | | // Use this variable to increment size of header as we go along to determine |
412 | | // offset for the subsequent area offsets |
413 | 845 | uint16_t curDataOffset = commonHeaderFormatSize; |
414 | | // First byte is id for version of FRU Info Storage Spec used |
415 | 845 | combFruArea.emplace_back(specVersion); |
416 | | |
417 | | // 2nd byte is offset to internal use data |
418 | 845 | combFruArea.emplace_back(recordNotPresent); |
419 | | |
420 | | // 3rd byte is offset to chassis data |
421 | 845 | FruAreaData chassisArea; |
422 | 845 | auto chassisIt = inventory.find(chassis); |
423 | 845 | if (chassisIt != inventory.end()) |
424 | 375 | { |
425 | 375 | chassisArea = buildChassisInfoArea(chassisIt->second); |
426 | 375 | } |
427 | | // update the offset to chassis data. |
428 | 845 | buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea); |
429 | | |
430 | | // 4th byte is offset to board data |
431 | 845 | FruAreaData boardArea; |
432 | 845 | auto boardIt = inventory.find(board); |
433 | 845 | if (boardIt != inventory.end()) |
434 | 425 | { |
435 | 425 | boardArea = buildBoardInfoArea(boardIt->second); |
436 | 425 | } |
437 | | // update the offset to the board data. |
438 | 845 | buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea); |
439 | | |
440 | | // 5th byte is offset to product data |
441 | 845 | FruAreaData prodArea; |
442 | 845 | auto prodIt = inventory.find(product); |
443 | 845 | if (prodIt != inventory.end()) |
444 | 333 | { |
445 | 333 | prodArea = buildProductInfoArea(prodIt->second); |
446 | 333 | } |
447 | | // update the offset to the product data. |
448 | 845 | buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea); |
449 | | |
450 | | // 6th byte is offset to multirecord data |
451 | 845 | combFruArea.emplace_back(recordNotPresent); |
452 | | |
453 | | // 7th byte is PAD |
454 | 845 | combFruArea.emplace_back(recordNotPresent); |
455 | | |
456 | | // 8th (Final byte of Header Format) is the checksum |
457 | 845 | appendDataChecksum(combFruArea); |
458 | | |
459 | | // Combine everything into one full IPMI FRU specification Record |
460 | | // add chassis use area data |
461 | 845 | combFruArea.insert(combFruArea.end(), chassisArea.begin(), |
462 | 845 | chassisArea.end()); |
463 | | |
464 | | // add board area data |
465 | 845 | combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end()); |
466 | | |
467 | | // add product use area data |
468 | 845 | combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end()); |
469 | | |
470 | | // If area is smaller than the minimum size, pad it. This enables ipmitool |
471 | | // to update the FRU blob with values longer than the original payload. |
472 | 845 | if (combFruArea.size() < fruMinSize) |
473 | 845 | { |
474 | 845 | combFruArea.resize(fruMinSize, fruPadValue); |
475 | 845 | } |
476 | | |
477 | 845 | return combFruArea; |
478 | 845 | } |
479 | | |
480 | | } // namespace fru |
481 | | } // namespace ipmi |