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