blob: 47979e90671e96c66ca4018caf3d042633932c92 [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>
8namespace ipmi
9{
10namespace fru
11{
12using namespace phosphor::logging;
13
Patrick Venture0b02be92018-08-31 11:55:55 -070014// Property variables
15static constexpr auto partNumber = "PartNumber";
16static constexpr auto serialNumber = "SerialNumber";
17static constexpr auto manufacturer = "Manufacturer";
18static constexpr auto buildDate = "BuildDate";
19static constexpr auto model = "Model";
20static constexpr auto prettyName = "PrettyName";
21static constexpr auto version = "Version";
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050022
Patrick Venture0b02be92018-08-31 11:55:55 -070023// Board info areas
24static constexpr auto board = "Board";
25static constexpr auto chassis = "Chassis";
26static constexpr auto product = "Product";
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050027
Patrick Venture0b02be92018-08-31 11:55:55 -070028static constexpr auto specVersion = 0x1;
29static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes
30static constexpr auto checksumSize = 0x1; // size in bytes
31static constexpr auto recordNotPresent = 0x0;
32static constexpr auto englishLanguageCode = 0x0;
33static constexpr auto typeLengthByteNull = 0x0;
34static constexpr auto endOfCustomFields = 0xC1;
35static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes
36static constexpr auto manufacturingDateSize = 0x3;
37static constexpr auto areaSizeOffset = 0x1;
38static constexpr uint8_t typeASCII = 0xC0;
39static constexpr auto maxRecordAttributeValue = 0x1F;
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050040
Andres Oportus7ebd2462018-04-09 10:35:21 -070041static constexpr auto secs_from_1970_1996 = 820454400;
Patrick Venture0b02be92018-08-31 11:55:55 -070042static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length
Andres Oportus7ebd2462018-04-09 10:35:21 -070043static constexpr auto secs_per_min = 60;
Patrick Venture0b02be92018-08-31 11:55:55 -070044static constexpr auto secsToMaxMfgdate =
45 secs_from_1970_1996 + secs_per_min * maxMfgDateValue;
Andres Oportus7ebd2462018-04-09 10:35:21 -070046
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050047/**
48 * @brief Format Beginning of Individual IPMI FRU Data Section
49 *
50 * @param[in] langCode Language code
51 * @param[in/out] data FRU area data
52 */
53void preFormatProcessing(bool langCode, FruAreaData& data)
54{
Patrick Venture0b02be92018-08-31 11:55:55 -070055 // Add id for version of FRU Info Storage Spec used
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050056 data.emplace_back(specVersion);
57
Patrick Venture0b02be92018-08-31 11:55:55 -070058 // Add Data Size - 0 as a placeholder, can edit after the data is finalized
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050059 data.emplace_back(typeLengthByteNull);
60
61 if (langCode)
62 {
63 data.emplace_back(englishLanguageCode);
64 }
65}
66
67/**
68 * @brief Append checksum of the FRU area data
69 *
70 * @param[in/out] data FRU area data
71 */
72void appendDataChecksum(FruAreaData& data)
73{
74 uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0);
75 // Push the Zero checksum as the last byte of this data
76 // This appears to be a simple summation of all the bytes
77 data.emplace_back(-checksumVal);
78}
79
80/**
81 * @brief Append padding bytes for the FRU area data
82 *
83 * @param[in/out] data FRU area data
84 */
85void padData(FruAreaData& data)
86{
Ratan Gupta3e6a7692018-02-07 16:10:16 +053087 uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement;
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050088 if (pad)
89 {
Ratan Gupta3e6a7692018-02-07 16:10:16 +053090 data.resize((data.size() + recordUnitOfMeasurement - pad));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050091 }
92}
93
94/**
95 * @brief Format End of Individual IPMI FRU Data Section
96 *
97 * @param[in/out] fruAreaData FRU area info data
98 */
99void postFormatProcessing(FruAreaData& data)
100{
Patrick Venture0b02be92018-08-31 11:55:55 -0700101 // This area needs to be padded to a multiple of 8 bytes (after checksum)
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500102 padData(data);
103
Patrick Venture0b02be92018-08-31 11:55:55 -0700104 // Set size of data info area
105 data.at(areaSizeOffset) =
106 (data.size() + checksumSize) / (recordUnitOfMeasurement);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500107
Patrick Venture0b02be92018-08-31 11:55:55 -0700108 // Finally add area checksum
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500109 appendDataChecksum(data);
110}
111
112/**
113 * @brief Read property value from inventory and append to the FRU area data
114 *
115 * @param[in] key key to search for in the property inventory data
116 * @param[in] propMap map of property values
117 * @param[in,out] data FRU area data to be appended
118 */
119void appendData(const Property& key, const PropertyMap& propMap,
120 FruAreaData& data)
121{
122 auto iter = propMap.find(key);
123 if (iter != propMap.end())
124 {
125 auto value = iter->second;
Patrick Venture0b02be92018-08-31 11:55:55 -0700126 // If starts with 0x or 0X remove them
127 // ex: 0x123a just take 123a
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500128 if ((value.compare(0, 2, "0x")) == 0 ||
Patrick Venture0b02be92018-08-31 11:55:55 -0700129 (value.compare(0, 2, "0X") == 0))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500130 {
131 value.erase(0, 2);
132 }
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530133
134 // 5 bits for length
Patrick Venture0b02be92018-08-31 11:55:55 -0700135 // if length is greater then 31(2^5) bytes then trim the data to 31
136 // bytess.
137 auto valueLength = (value.length() > maxRecordAttributeValue)
138 ? maxRecordAttributeValue
139 : value.length();
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530140 // 2 bits for type
141 // Set the type to ascii
142 uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
143
144 data.emplace_back(typeLength);
Patrick Venture0b02be92018-08-31 11:55:55 -0700145 std::copy(value.begin(), value.begin() + valueLength,
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530146 std::back_inserter(data));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500147 }
148 else
149 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700150 // set 0 size
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500151 data.emplace_back(typeLengthByteNull);
152 }
153}
154
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500155/**
156 * @brief Appends Build Date
157 *
158 * @param[in] propMap map of property values
159 * @param[in/out] data FRU area to add the manfufacture date
160 */
161void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
162{
Patrick Venture0b02be92018-08-31 11:55:55 -0700163 // MFG Date/Time
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500164 auto iter = propMap.find(buildDate);
Nagaraju Gorugantib898cde2018-07-10 00:47:43 -0500165 if ((iter != propMap.end()) && (iter->second.size() > 0))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500166 {
Andres Oportus7ebd2462018-04-09 10:35:21 -0700167 tm time = {};
168 strptime(iter->second.c_str(), "%F - %H:%M:%S", &time);
169 time_t raw = mktime(&time);
170
171 // From FRU Spec:
172 // "Mfg. Date / Time
173 // Number of minutes from 0:00 hrs 1/1/96.
174 // LSbyte first (little endian)
175 // 00_00_00h = unspecified."
Nagaraju Gorugantib898cde2018-07-10 00:47:43 -0500176 if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500177 {
Andres Oportus7ebd2462018-04-09 10:35:21 -0700178 raw -= secs_from_1970_1996;
179 raw /= secs_per_min;
180 uint8_t fru_raw[3];
181 fru_raw[0] = raw & 0xFF;
182 fru_raw[1] = (raw >> 8) & 0xFF;
183 fru_raw[2] = (raw >> 16) & 0xFF;
184 std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500185 return;
186 }
Andres Oportus7ebd2462018-04-09 10:35:21 -0700187 fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
188 static_cast<unsigned int>(raw));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500189 }
Patrick Venture0b02be92018-08-31 11:55:55 -0700190 // Blank date
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500191 data.emplace_back(0);
192 data.emplace_back(0);
193 data.emplace_back(0);
194}
195
196/**
197 * @brief Builds a section of the common header
198 *
199 * @param[in] infoAreaSize size of the FRU area to write
200 * @param[in] offset Current offset for data in overall record
201 * @param[in/out] data Common Header section data container
202 */
Patrick Venture0b02be92018-08-31 11:55:55 -0700203void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
204 FruAreaData& data)
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500205{
Patrick Venture0b02be92018-08-31 11:55:55 -0700206 // Check if data for internal use section populated
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500207 if (infoAreaSize == 0)
208 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700209 // Indicate record not present
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500210 data.emplace_back(recordNotPresent);
211 }
212 else
213 {
Ratan Gupta2f66f002018-01-31 21:26:25 +0530214 // offset should be multiple of 8.
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530215 auto remainder = offset % recordUnitOfMeasurement;
Ratan Gupta2f66f002018-01-31 21:26:25 +0530216 // add the padding bytes in the offset so that offset
217 // will be multiple of 8 byte.
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530218 offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
Patrick Venture0b02be92018-08-31 11:55:55 -0700219 // Place data to define offset to area data section
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530220 data.emplace_back(offset / recordUnitOfMeasurement);
Ratan Gupta2f66f002018-01-31 21:26:25 +0530221
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500222 offset += infoAreaSize;
223 }
224}
225
226/**
227 * @brief Builds the Chassis info area data section
228 *
229 * @param[in] propMap map of properties for chassis info area
230 * @return FruAreaData container with chassis info area
231 */
232FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
233{
234 FruAreaData fruAreaData;
235 if (!propMap.empty())
236 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700237 // Set formatting data that goes at the beginning of the record
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500238 preFormatProcessing(false, fruAreaData);
239
Patrick Venture0b02be92018-08-31 11:55:55 -0700240 // chassis type
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500241 fruAreaData.emplace_back(0);
242
Patrick Venture0b02be92018-08-31 11:55:55 -0700243 // Chasiss part number, in config.yaml it is configured as model
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500244 appendData(model, propMap, fruAreaData);
245
Patrick Venture0b02be92018-08-31 11:55:55 -0700246 // Board serial number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500247 appendData(serialNumber, propMap, fruAreaData);
248
Patrick Venture0b02be92018-08-31 11:55:55 -0700249 // Indicate End of Custom Fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500250 fruAreaData.emplace_back(endOfCustomFields);
251
Patrick Venture0b02be92018-08-31 11:55:55 -0700252 // Complete record data formatting
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500253 postFormatProcessing(fruAreaData);
254 }
255 return fruAreaData;
256}
257
258/**
259 * @brief Builds the Board info area data section
260 *
261 * @param[in] propMap map of properties for board info area
262 * @return FruAreaData container with board info area
263 */
264FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
265{
266 FruAreaData fruAreaData;
267 if (!propMap.empty())
268 {
269 preFormatProcessing(true, fruAreaData);
270
Patrick Venture0b02be92018-08-31 11:55:55 -0700271 // Manufacturing date
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500272 appendMfgDate(propMap, fruAreaData);
273
Patrick Venture0b02be92018-08-31 11:55:55 -0700274 // manufacturer
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500275 appendData(manufacturer, propMap, fruAreaData);
276
Patrick Venture0b02be92018-08-31 11:55:55 -0700277 // Product name/Pretty name
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500278 appendData(prettyName, propMap, fruAreaData);
279
Patrick Venture0b02be92018-08-31 11:55:55 -0700280 // Board serial number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500281 appendData(serialNumber, propMap, fruAreaData);
282
Patrick Venture0b02be92018-08-31 11:55:55 -0700283 // Board part number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500284 appendData(partNumber, propMap, fruAreaData);
285
Patrick Venture0b02be92018-08-31 11:55:55 -0700286 // FRU File ID - Empty
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500287 fruAreaData.emplace_back(typeLengthByteNull);
288
289 // Empty FRU File ID bytes
290 fruAreaData.emplace_back(recordNotPresent);
291
Patrick Venture0b02be92018-08-31 11:55:55 -0700292 // End of custom fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500293 fruAreaData.emplace_back(endOfCustomFields);
294
295 postFormatProcessing(fruAreaData);
296 }
297 return fruAreaData;
298}
299
300/**
301 * @brief Builds the Product info area data section
302 *
303 * @param[in] propMap map of FRU properties for Board info area
304 * @return FruAreaData container with product info area data
305 */
306FruAreaData buildProductInfoArea(const PropertyMap& propMap)
307{
308 FruAreaData fruAreaData;
309 if (!propMap.empty())
310 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700311 // Set formatting data that goes at the beginning of the record
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500312 preFormatProcessing(true, fruAreaData);
313
Patrick Venture0b02be92018-08-31 11:55:55 -0700314 // manufacturer
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500315 appendData(manufacturer, propMap, fruAreaData);
316
Patrick Venture0b02be92018-08-31 11:55:55 -0700317 // Product name/Pretty name
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500318 appendData(prettyName, propMap, fruAreaData);
319
Patrick Venture0b02be92018-08-31 11:55:55 -0700320 // Product part/model number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500321 appendData(model, propMap, fruAreaData);
322
Patrick Venture0b02be92018-08-31 11:55:55 -0700323 // Product version
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500324 appendData(version, propMap, fruAreaData);
325
Patrick Venture0b02be92018-08-31 11:55:55 -0700326 // Serial Number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500327 appendData(serialNumber, propMap, fruAreaData);
328
Patrick Venture0b02be92018-08-31 11:55:55 -0700329 // Add Asset Tag
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500330 fruAreaData.emplace_back(recordNotPresent);
331
Patrick Venture0b02be92018-08-31 11:55:55 -0700332 // FRU File ID - Empty
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500333 fruAreaData.emplace_back(typeLengthByteNull);
334
335 // Empty FRU File ID bytes
336 fruAreaData.emplace_back(recordNotPresent);
337
Patrick Venture0b02be92018-08-31 11:55:55 -0700338 // End of custom fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500339 fruAreaData.emplace_back(endOfCustomFields);
340
341 postFormatProcessing(fruAreaData);
342 }
343 return fruAreaData;
344}
345
346FruAreaData buildFruAreaData(const FruInventoryData& inventory)
347{
Ratan Gupta2f66f002018-01-31 21:26:25 +0530348 FruAreaData combFruArea{};
Patrick Venture0b02be92018-08-31 11:55:55 -0700349 // Now build common header with data for this FRU Inv Record
350 // Use this variable to increment size of header as we go along to determine
351 // offset for the subsequent area offsets
Ratan Gupta2f66f002018-01-31 21:26:25 +0530352 uint16_t curDataOffset = commonHeaderFormatSize;
Patrick Venture0b02be92018-08-31 11:55:55 -0700353 // First byte is id for version of FRU Info Storage Spec used
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500354 combFruArea.emplace_back(specVersion);
355
Patrick Venture0b02be92018-08-31 11:55:55 -0700356 // 2nd byte is offset to internal use data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500357 combFruArea.emplace_back(recordNotPresent);
358
Patrick Venture0b02be92018-08-31 11:55:55 -0700359 // 3rd byte is offset to chassis data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500360 FruAreaData chassisArea;
361 auto chassisIt = inventory.find(chassis);
362 if (chassisIt != inventory.end())
363 {
364 chassisArea = std::move(buildChassisInfoArea(chassisIt->second));
365 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530366 // update the offset to chassis data.
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500367 buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
368
Patrick Venture0b02be92018-08-31 11:55:55 -0700369 // 4th byte is offset to board data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500370 FruAreaData boardArea;
371 auto boardIt = inventory.find(board);
372 if (boardIt != inventory.end())
373 {
374 boardArea = std::move(buildBoardInfoArea(boardIt->second));
375 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530376 // update the offset to the board data.
377 buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500378
Patrick Venture0b02be92018-08-31 11:55:55 -0700379 // 5th byte is offset to product data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500380 FruAreaData prodArea;
381 auto prodIt = inventory.find(product);
382 if (prodIt != inventory.end())
383 {
384 prodArea = std::move(buildProductInfoArea(prodIt->second));
385 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530386 // update the offset to the product data.
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500387 buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
388
Patrick Venture0b02be92018-08-31 11:55:55 -0700389 // 6th byte is offset to multirecord data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500390 combFruArea.emplace_back(recordNotPresent);
391
Patrick Venture0b02be92018-08-31 11:55:55 -0700392 // 7th byte is PAD
Ratan Gupta2f66f002018-01-31 21:26:25 +0530393 combFruArea.emplace_back(recordNotPresent);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500394
Patrick Venture0b02be92018-08-31 11:55:55 -0700395 // 8th (Final byte of Header Format) is the checksum
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500396 appendDataChecksum(combFruArea);
397
Patrick Venture0b02be92018-08-31 11:55:55 -0700398 // Combine everything into one full IPMI FRU specification Record
399 // add chassis use area data
400 combFruArea.insert(combFruArea.end(), chassisArea.begin(),
401 chassisArea.end());
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500402
Patrick Venture0b02be92018-08-31 11:55:55 -0700403 // add board area data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500404 combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
405
Patrick Venture0b02be92018-08-31 11:55:55 -0700406 // add product use area data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500407 combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
408
409 return combFruArea;
410}
411
Patrick Venture0b02be92018-08-31 11:55:55 -0700412} // namespace fru
413} // namespace ipmi