blob: 949d5468a3ae9ce4a7dd27776023a7b462499694 [file] [log] [blame]
Patrick Venture0b02be92018-08-31 11:55:55 -07001#include "ipmi_fru_info_area.hpp"
2
Marri Devender Rao7d9157e2017-07-01 16:11:40 -05003#include <algorithm>
Patrick Venture0b02be92018-08-31 11:55:55 -07004#include <ctime>
Marri Devender Rao7d9157e2017-07-01 16:11:40 -05005#include <map>
6#include <numeric>
Marri Devender Rao7d9157e2017-07-01 16:11:40 -05007#include <phosphor-logging/elog.hpp>
Patrick Ventureb51bf9c2018-09-10 15:53:14 -07008
Marri Devender Rao7d9157e2017-07-01 16:11:40 -05009namespace ipmi
10{
11namespace fru
12{
13using namespace phosphor::logging;
14
Patrick Venture0b02be92018-08-31 11:55:55 -070015// Property variables
16static constexpr auto partNumber = "PartNumber";
17static constexpr auto serialNumber = "SerialNumber";
18static constexpr auto manufacturer = "Manufacturer";
19static constexpr auto buildDate = "BuildDate";
20static constexpr auto model = "Model";
21static constexpr auto prettyName = "PrettyName";
22static constexpr auto version = "Version";
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050023
Patrick Venture0b02be92018-08-31 11:55:55 -070024// Board info areas
25static constexpr auto board = "Board";
26static constexpr auto chassis = "Chassis";
27static constexpr auto product = "Product";
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050028
Patrick Venture0b02be92018-08-31 11:55:55 -070029static constexpr auto specVersion = 0x1;
30static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes
31static constexpr auto checksumSize = 0x1; // size in bytes
32static constexpr auto recordNotPresent = 0x0;
33static constexpr auto englishLanguageCode = 0x0;
34static constexpr auto typeLengthByteNull = 0x0;
35static constexpr auto endOfCustomFields = 0xC1;
36static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes
37static constexpr auto manufacturingDateSize = 0x3;
38static constexpr auto areaSizeOffset = 0x1;
39static constexpr uint8_t typeASCII = 0xC0;
40static constexpr auto maxRecordAttributeValue = 0x1F;
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050041
Andres Oportus7ebd2462018-04-09 10:35:21 -070042static constexpr auto secs_from_1970_1996 = 820454400;
Patrick Venture0b02be92018-08-31 11:55:55 -070043static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length
Andres Oportus7ebd2462018-04-09 10:35:21 -070044static constexpr auto secs_per_min = 60;
Patrick Venture0b02be92018-08-31 11:55:55 -070045static constexpr auto secsToMaxMfgdate =
46 secs_from_1970_1996 + secs_per_min * maxMfgDateValue;
Andres Oportus7ebd2462018-04-09 10:35:21 -070047
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050048/**
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 */
54void preFormatProcessing(bool langCode, FruAreaData& data)
55{
Patrick Venture0b02be92018-08-31 11:55:55 -070056 // Add id for version of FRU Info Storage Spec used
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050057 data.emplace_back(specVersion);
58
Patrick Venture0b02be92018-08-31 11:55:55 -070059 // Add Data Size - 0 as a placeholder, can edit after the data is finalized
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050060 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 */
73void 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 */
86void padData(FruAreaData& data)
87{
Ratan Gupta3e6a7692018-02-07 16:10:16 +053088 uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement;
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050089 if (pad)
90 {
Ratan Gupta3e6a7692018-02-07 16:10:16 +053091 data.resize((data.size() + recordUnitOfMeasurement - pad));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050092 }
93}
94
95/**
96 * @brief Format End of Individual IPMI FRU Data Section
97 *
98 * @param[in/out] fruAreaData FRU area info data
99 */
100void postFormatProcessing(FruAreaData& data)
101{
Patrick Venture0b02be92018-08-31 11:55:55 -0700102 // This area needs to be padded to a multiple of 8 bytes (after checksum)
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500103 padData(data);
104
Patrick Venture0b02be92018-08-31 11:55:55 -0700105 // Set size of data info area
106 data.at(areaSizeOffset) =
107 (data.size() + checksumSize) / (recordUnitOfMeasurement);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500108
Patrick Venture0b02be92018-08-31 11:55:55 -0700109 // Finally add area checksum
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500110 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 */
120void 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;
Patrick Venture0b02be92018-08-31 11:55:55 -0700127 // If starts with 0x or 0X remove them
128 // ex: 0x123a just take 123a
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500129 if ((value.compare(0, 2, "0x")) == 0 ||
Patrick Venture0b02be92018-08-31 11:55:55 -0700130 (value.compare(0, 2, "0X") == 0))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500131 {
132 value.erase(0, 2);
133 }
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530134
135 // 5 bits for length
Patrick Venture0b02be92018-08-31 11:55:55 -0700136 // 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();
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530141 // 2 bits for type
142 // Set the type to ascii
143 uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
144
145 data.emplace_back(typeLength);
Patrick Venture0b02be92018-08-31 11:55:55 -0700146 std::copy(value.begin(), value.begin() + valueLength,
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530147 std::back_inserter(data));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500148 }
149 else
150 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700151 // set 0 size
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500152 data.emplace_back(typeLengthByteNull);
153 }
154}
155
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500156/**
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 */
162void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
163{
Patrick Venture0b02be92018-08-31 11:55:55 -0700164 // MFG Date/Time
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500165 auto iter = propMap.find(buildDate);
Nagaraju Gorugantib898cde2018-07-10 00:47:43 -0500166 if ((iter != propMap.end()) && (iter->second.size() > 0))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500167 {
Andres Oportus7ebd2462018-04-09 10:35:21 -0700168 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."
Nagaraju Gorugantib898cde2018-07-10 00:47:43 -0500177 if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500178 {
Andres Oportus7ebd2462018-04-09 10:35:21 -0700179 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));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500186 return;
187 }
Patrick Ventureb51bf9c2018-09-10 15:53:14 -0700188 std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
189 static_cast<unsigned int>(raw));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500190 }
Patrick Venture0b02be92018-08-31 11:55:55 -0700191 // Blank date
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500192 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 */
Patrick Venture0b02be92018-08-31 11:55:55 -0700204void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
205 FruAreaData& data)
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500206{
Patrick Venture0b02be92018-08-31 11:55:55 -0700207 // Check if data for internal use section populated
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500208 if (infoAreaSize == 0)
209 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700210 // Indicate record not present
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500211 data.emplace_back(recordNotPresent);
212 }
213 else
214 {
Ratan Gupta2f66f002018-01-31 21:26:25 +0530215 // offset should be multiple of 8.
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530216 auto remainder = offset % recordUnitOfMeasurement;
Ratan Gupta2f66f002018-01-31 21:26:25 +0530217 // add the padding bytes in the offset so that offset
218 // will be multiple of 8 byte.
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530219 offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
Patrick Venture0b02be92018-08-31 11:55:55 -0700220 // Place data to define offset to area data section
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530221 data.emplace_back(offset / recordUnitOfMeasurement);
Ratan Gupta2f66f002018-01-31 21:26:25 +0530222
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500223 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 */
233FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
234{
235 FruAreaData fruAreaData;
236 if (!propMap.empty())
237 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700238 // Set formatting data that goes at the beginning of the record
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500239 preFormatProcessing(false, fruAreaData);
240
Patrick Venture0b02be92018-08-31 11:55:55 -0700241 // chassis type
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500242 fruAreaData.emplace_back(0);
243
Patrick Venture0b02be92018-08-31 11:55:55 -0700244 // Chasiss part number, in config.yaml it is configured as model
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500245 appendData(model, propMap, fruAreaData);
246
Patrick Venture0b02be92018-08-31 11:55:55 -0700247 // Board serial number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500248 appendData(serialNumber, propMap, fruAreaData);
249
Patrick Venture0b02be92018-08-31 11:55:55 -0700250 // Indicate End of Custom Fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500251 fruAreaData.emplace_back(endOfCustomFields);
252
Patrick Venture0b02be92018-08-31 11:55:55 -0700253 // Complete record data formatting
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500254 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 */
265FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
266{
267 FruAreaData fruAreaData;
268 if (!propMap.empty())
269 {
270 preFormatProcessing(true, fruAreaData);
271
Patrick Venture0b02be92018-08-31 11:55:55 -0700272 // Manufacturing date
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500273 appendMfgDate(propMap, fruAreaData);
274
Patrick Venture0b02be92018-08-31 11:55:55 -0700275 // manufacturer
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500276 appendData(manufacturer, propMap, fruAreaData);
277
Patrick Venture0b02be92018-08-31 11:55:55 -0700278 // Product name/Pretty name
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500279 appendData(prettyName, propMap, fruAreaData);
280
Patrick Venture0b02be92018-08-31 11:55:55 -0700281 // Board serial number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500282 appendData(serialNumber, propMap, fruAreaData);
283
Patrick Venture0b02be92018-08-31 11:55:55 -0700284 // Board part number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500285 appendData(partNumber, propMap, fruAreaData);
286
Patrick Venture0b02be92018-08-31 11:55:55 -0700287 // FRU File ID - Empty
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500288 fruAreaData.emplace_back(typeLengthByteNull);
289
290 // Empty FRU File ID bytes
291 fruAreaData.emplace_back(recordNotPresent);
292
Patrick Venture0b02be92018-08-31 11:55:55 -0700293 // End of custom fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500294 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 */
307FruAreaData buildProductInfoArea(const PropertyMap& propMap)
308{
309 FruAreaData fruAreaData;
310 if (!propMap.empty())
311 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700312 // Set formatting data that goes at the beginning of the record
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500313 preFormatProcessing(true, fruAreaData);
314
Patrick Venture0b02be92018-08-31 11:55:55 -0700315 // manufacturer
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500316 appendData(manufacturer, propMap, fruAreaData);
317
Patrick Venture0b02be92018-08-31 11:55:55 -0700318 // Product name/Pretty name
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500319 appendData(prettyName, propMap, fruAreaData);
320
Patrick Venture0b02be92018-08-31 11:55:55 -0700321 // Product part/model number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500322 appendData(model, propMap, fruAreaData);
323
Patrick Venture0b02be92018-08-31 11:55:55 -0700324 // Product version
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500325 appendData(version, propMap, fruAreaData);
326
Patrick Venture0b02be92018-08-31 11:55:55 -0700327 // Serial Number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500328 appendData(serialNumber, propMap, fruAreaData);
329
Patrick Venture0b02be92018-08-31 11:55:55 -0700330 // Add Asset Tag
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500331 fruAreaData.emplace_back(recordNotPresent);
332
Patrick Venture0b02be92018-08-31 11:55:55 -0700333 // FRU File ID - Empty
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500334 fruAreaData.emplace_back(typeLengthByteNull);
335
336 // Empty FRU File ID bytes
337 fruAreaData.emplace_back(recordNotPresent);
338
Patrick Venture0b02be92018-08-31 11:55:55 -0700339 // End of custom fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500340 fruAreaData.emplace_back(endOfCustomFields);
341
342 postFormatProcessing(fruAreaData);
343 }
344 return fruAreaData;
345}
346
347FruAreaData buildFruAreaData(const FruInventoryData& inventory)
348{
Ratan Gupta2f66f002018-01-31 21:26:25 +0530349 FruAreaData combFruArea{};
Patrick Venture0b02be92018-08-31 11:55:55 -0700350 // 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
Ratan Gupta2f66f002018-01-31 21:26:25 +0530353 uint16_t curDataOffset = commonHeaderFormatSize;
Patrick Venture0b02be92018-08-31 11:55:55 -0700354 // First byte is id for version of FRU Info Storage Spec used
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500355 combFruArea.emplace_back(specVersion);
356
Patrick Venture0b02be92018-08-31 11:55:55 -0700357 // 2nd byte is offset to internal use data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500358 combFruArea.emplace_back(recordNotPresent);
359
Patrick Venture0b02be92018-08-31 11:55:55 -0700360 // 3rd byte is offset to chassis data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500361 FruAreaData chassisArea;
362 auto chassisIt = inventory.find(chassis);
363 if (chassisIt != inventory.end())
364 {
365 chassisArea = std::move(buildChassisInfoArea(chassisIt->second));
366 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530367 // update the offset to chassis data.
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500368 buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
369
Patrick Venture0b02be92018-08-31 11:55:55 -0700370 // 4th byte is offset to board data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500371 FruAreaData boardArea;
372 auto boardIt = inventory.find(board);
373 if (boardIt != inventory.end())
374 {
375 boardArea = std::move(buildBoardInfoArea(boardIt->second));
376 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530377 // update the offset to the board data.
378 buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500379
Patrick Venture0b02be92018-08-31 11:55:55 -0700380 // 5th byte is offset to product data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500381 FruAreaData prodArea;
382 auto prodIt = inventory.find(product);
383 if (prodIt != inventory.end())
384 {
385 prodArea = std::move(buildProductInfoArea(prodIt->second));
386 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530387 // update the offset to the product data.
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500388 buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
389
Patrick Venture0b02be92018-08-31 11:55:55 -0700390 // 6th byte is offset to multirecord data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500391 combFruArea.emplace_back(recordNotPresent);
392
Patrick Venture0b02be92018-08-31 11:55:55 -0700393 // 7th byte is PAD
Ratan Gupta2f66f002018-01-31 21:26:25 +0530394 combFruArea.emplace_back(recordNotPresent);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500395
Patrick Venture0b02be92018-08-31 11:55:55 -0700396 // 8th (Final byte of Header Format) is the checksum
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500397 appendDataChecksum(combFruArea);
398
Patrick Venture0b02be92018-08-31 11:55:55 -0700399 // 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());
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500403
Patrick Venture0b02be92018-08-31 11:55:55 -0700404 // add board area data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500405 combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
406
Patrick Venture0b02be92018-08-31 11:55:55 -0700407 // add product use area data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500408 combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
409
410 return combFruArea;
411}
412
Patrick Venture0b02be92018-08-31 11:55:55 -0700413} // namespace fru
414} // namespace ipmi