blob: e10eee51433bd2ddb05e2d3c26339687a26d1bb7 [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";
Oskar Senft40f59e22018-12-04 22:43:19 -050023static constexpr auto type = "Type";
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050024
Patrick Venture0b02be92018-08-31 11:55:55 -070025// Board info areas
26static constexpr auto board = "Board";
27static constexpr auto chassis = "Chassis";
28static constexpr auto product = "Product";
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050029
Patrick Venture0b02be92018-08-31 11:55:55 -070030static constexpr auto specVersion = 0x1;
31static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes
32static constexpr auto checksumSize = 0x1; // size in bytes
33static constexpr auto recordNotPresent = 0x0;
34static constexpr auto englishLanguageCode = 0x0;
35static constexpr auto typeLengthByteNull = 0x0;
36static constexpr auto endOfCustomFields = 0xC1;
37static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes
38static constexpr auto manufacturingDateSize = 0x3;
39static constexpr auto areaSizeOffset = 0x1;
40static constexpr uint8_t typeASCII = 0xC0;
41static constexpr auto maxRecordAttributeValue = 0x1F;
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050042
Andres Oportus7ebd2462018-04-09 10:35:21 -070043static constexpr auto secs_from_1970_1996 = 820454400;
Patrick Venture0b02be92018-08-31 11:55:55 -070044static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length
Andres Oportus7ebd2462018-04-09 10:35:21 -070045static constexpr auto secs_per_min = 60;
Patrick Venture0b02be92018-08-31 11:55:55 -070046static constexpr auto secsToMaxMfgdate =
47 secs_from_1970_1996 + secs_per_min * maxMfgDateValue;
Andres Oportus7ebd2462018-04-09 10:35:21 -070048
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050049/**
50 * @brief Format Beginning of Individual IPMI FRU Data Section
51 *
52 * @param[in] langCode Language code
53 * @param[in/out] data FRU area data
54 */
55void preFormatProcessing(bool langCode, FruAreaData& data)
56{
Patrick Venture0b02be92018-08-31 11:55:55 -070057 // Add id for version of FRU Info Storage Spec used
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050058 data.emplace_back(specVersion);
59
Patrick Venture0b02be92018-08-31 11:55:55 -070060 // Add Data Size - 0 as a placeholder, can edit after the data is finalized
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050061 data.emplace_back(typeLengthByteNull);
62
63 if (langCode)
64 {
65 data.emplace_back(englishLanguageCode);
66 }
67}
68
69/**
70 * @brief Append checksum of the FRU area data
71 *
72 * @param[in/out] data FRU area data
73 */
74void appendDataChecksum(FruAreaData& data)
75{
76 uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0);
77 // Push the Zero checksum as the last byte of this data
78 // This appears to be a simple summation of all the bytes
79 data.emplace_back(-checksumVal);
80}
81
82/**
83 * @brief Append padding bytes for the FRU area data
84 *
85 * @param[in/out] data FRU area data
86 */
87void padData(FruAreaData& data)
88{
Ratan Gupta3e6a7692018-02-07 16:10:16 +053089 uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement;
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050090 if (pad)
91 {
Ratan Gupta3e6a7692018-02-07 16:10:16 +053092 data.resize((data.size() + recordUnitOfMeasurement - pad));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -050093 }
94}
95
96/**
97 * @brief Format End of Individual IPMI FRU Data Section
98 *
99 * @param[in/out] fruAreaData FRU area info data
100 */
101void postFormatProcessing(FruAreaData& data)
102{
Patrick Venture0b02be92018-08-31 11:55:55 -0700103 // This area needs to be padded to a multiple of 8 bytes (after checksum)
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500104 padData(data);
105
Patrick Venture0b02be92018-08-31 11:55:55 -0700106 // Set size of data info area
107 data.at(areaSizeOffset) =
108 (data.size() + checksumSize) / (recordUnitOfMeasurement);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500109
Patrick Venture0b02be92018-08-31 11:55:55 -0700110 // Finally add area checksum
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500111 appendDataChecksum(data);
112}
113
114/**
Oskar Senft40f59e22018-12-04 22:43:19 -0500115 * @brief Read chassis type property value from inventory and append to the FRU
116 * area data.
117 *
118 * @param[in] propMap map of property values
119 * @param[in,out] data FRU area data to be appended
120 */
121void appendChassisType(const PropertyMap& propMap, FruAreaData& data)
122{
123 uint8_t chassisType = 0; // Not specified
124 auto iter = propMap.find(type);
125 if (iter != propMap.end())
126 {
127 auto value = iter->second;
128 chassisType = std::stoi(value);
129 }
130 data.emplace_back(chassisType);
131}
132
133/**
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500134 * @brief Read property value from inventory and append to the FRU area data
135 *
136 * @param[in] key key to search for in the property inventory data
137 * @param[in] propMap map of property values
138 * @param[in,out] data FRU area data to be appended
139 */
140void appendData(const Property& key, const PropertyMap& propMap,
141 FruAreaData& data)
142{
143 auto iter = propMap.find(key);
144 if (iter != propMap.end())
145 {
146 auto value = iter->second;
Patrick Venture0b02be92018-08-31 11:55:55 -0700147 // If starts with 0x or 0X remove them
148 // ex: 0x123a just take 123a
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500149 if ((value.compare(0, 2, "0x")) == 0 ||
Patrick Venture0b02be92018-08-31 11:55:55 -0700150 (value.compare(0, 2, "0X") == 0))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500151 {
152 value.erase(0, 2);
153 }
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530154
155 // 5 bits for length
Patrick Venture0b02be92018-08-31 11:55:55 -0700156 // if length is greater then 31(2^5) bytes then trim the data to 31
157 // bytess.
158 auto valueLength = (value.length() > maxRecordAttributeValue)
159 ? maxRecordAttributeValue
160 : value.length();
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530161 // 2 bits for type
162 // Set the type to ascii
163 uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
164
165 data.emplace_back(typeLength);
Patrick Venture0b02be92018-08-31 11:55:55 -0700166 std::copy(value.begin(), value.begin() + valueLength,
Ratan Gupta6edfc5c2018-01-31 21:41:45 +0530167 std::back_inserter(data));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500168 }
169 else
170 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700171 // set 0 size
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500172 data.emplace_back(typeLengthByteNull);
173 }
174}
175
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500176/**
177 * @brief Appends Build Date
178 *
179 * @param[in] propMap map of property values
180 * @param[in/out] data FRU area to add the manfufacture date
181 */
182void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
183{
Patrick Venture0b02be92018-08-31 11:55:55 -0700184 // MFG Date/Time
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500185 auto iter = propMap.find(buildDate);
Nagaraju Gorugantib898cde2018-07-10 00:47:43 -0500186 if ((iter != propMap.end()) && (iter->second.size() > 0))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500187 {
Andres Oportus7ebd2462018-04-09 10:35:21 -0700188 tm time = {};
189 strptime(iter->second.c_str(), "%F - %H:%M:%S", &time);
190 time_t raw = mktime(&time);
191
192 // From FRU Spec:
193 // "Mfg. Date / Time
194 // Number of minutes from 0:00 hrs 1/1/96.
195 // LSbyte first (little endian)
196 // 00_00_00h = unspecified."
Nagaraju Gorugantib898cde2018-07-10 00:47:43 -0500197 if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500198 {
Andres Oportus7ebd2462018-04-09 10:35:21 -0700199 raw -= secs_from_1970_1996;
200 raw /= secs_per_min;
201 uint8_t fru_raw[3];
202 fru_raw[0] = raw & 0xFF;
203 fru_raw[1] = (raw >> 8) & 0xFF;
204 fru_raw[2] = (raw >> 16) & 0xFF;
205 std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500206 return;
207 }
Patrick Ventureb51bf9c2018-09-10 15:53:14 -0700208 std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
209 static_cast<unsigned int>(raw));
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500210 }
Patrick Venture0b02be92018-08-31 11:55:55 -0700211 // Blank date
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500212 data.emplace_back(0);
213 data.emplace_back(0);
214 data.emplace_back(0);
215}
216
217/**
218 * @brief Builds a section of the common header
219 *
220 * @param[in] infoAreaSize size of the FRU area to write
221 * @param[in] offset Current offset for data in overall record
222 * @param[in/out] data Common Header section data container
223 */
Patrick Venture0b02be92018-08-31 11:55:55 -0700224void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
225 FruAreaData& data)
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500226{
Patrick Venture0b02be92018-08-31 11:55:55 -0700227 // Check if data for internal use section populated
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500228 if (infoAreaSize == 0)
229 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700230 // Indicate record not present
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500231 data.emplace_back(recordNotPresent);
232 }
233 else
234 {
Ratan Gupta2f66f002018-01-31 21:26:25 +0530235 // offset should be multiple of 8.
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530236 auto remainder = offset % recordUnitOfMeasurement;
Ratan Gupta2f66f002018-01-31 21:26:25 +0530237 // add the padding bytes in the offset so that offset
238 // will be multiple of 8 byte.
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530239 offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
Patrick Venture0b02be92018-08-31 11:55:55 -0700240 // Place data to define offset to area data section
Ratan Gupta3e6a7692018-02-07 16:10:16 +0530241 data.emplace_back(offset / recordUnitOfMeasurement);
Ratan Gupta2f66f002018-01-31 21:26:25 +0530242
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500243 offset += infoAreaSize;
244 }
245}
246
247/**
248 * @brief Builds the Chassis info area data section
249 *
250 * @param[in] propMap map of properties for chassis info area
251 * @return FruAreaData container with chassis info area
252 */
253FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
254{
255 FruAreaData fruAreaData;
256 if (!propMap.empty())
257 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700258 // Set formatting data that goes at the beginning of the record
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500259 preFormatProcessing(false, fruAreaData);
260
Patrick Venture0b02be92018-08-31 11:55:55 -0700261 // chassis type
Oskar Senft40f59e22018-12-04 22:43:19 -0500262 appendChassisType(propMap, fruAreaData);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500263
Patrick Venture0b02be92018-08-31 11:55:55 -0700264 // Chasiss part number, in config.yaml it is configured as model
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500265 appendData(model, propMap, fruAreaData);
266
Patrick Venture0b02be92018-08-31 11:55:55 -0700267 // Board serial number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500268 appendData(serialNumber, propMap, fruAreaData);
269
Patrick Venture0b02be92018-08-31 11:55:55 -0700270 // Indicate End of Custom Fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500271 fruAreaData.emplace_back(endOfCustomFields);
272
Patrick Venture0b02be92018-08-31 11:55:55 -0700273 // Complete record data formatting
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500274 postFormatProcessing(fruAreaData);
275 }
276 return fruAreaData;
277}
278
279/**
280 * @brief Builds the Board info area data section
281 *
282 * @param[in] propMap map of properties for board info area
283 * @return FruAreaData container with board info area
284 */
285FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
286{
287 FruAreaData fruAreaData;
288 if (!propMap.empty())
289 {
290 preFormatProcessing(true, fruAreaData);
291
Patrick Venture0b02be92018-08-31 11:55:55 -0700292 // Manufacturing date
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500293 appendMfgDate(propMap, fruAreaData);
294
Patrick Venture0b02be92018-08-31 11:55:55 -0700295 // manufacturer
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500296 appendData(manufacturer, propMap, fruAreaData);
297
Patrick Venture0b02be92018-08-31 11:55:55 -0700298 // Product name/Pretty name
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500299 appendData(prettyName, propMap, fruAreaData);
300
Patrick Venture0b02be92018-08-31 11:55:55 -0700301 // Board serial number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500302 appendData(serialNumber, propMap, fruAreaData);
303
Patrick Venture0b02be92018-08-31 11:55:55 -0700304 // Board part number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500305 appendData(partNumber, propMap, fruAreaData);
306
Patrick Venture0b02be92018-08-31 11:55:55 -0700307 // FRU File ID - Empty
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500308 fruAreaData.emplace_back(typeLengthByteNull);
309
310 // Empty FRU File ID bytes
311 fruAreaData.emplace_back(recordNotPresent);
312
Patrick Venture0b02be92018-08-31 11:55:55 -0700313 // End of custom fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500314 fruAreaData.emplace_back(endOfCustomFields);
315
316 postFormatProcessing(fruAreaData);
317 }
318 return fruAreaData;
319}
320
321/**
322 * @brief Builds the Product info area data section
323 *
324 * @param[in] propMap map of FRU properties for Board info area
325 * @return FruAreaData container with product info area data
326 */
327FruAreaData buildProductInfoArea(const PropertyMap& propMap)
328{
329 FruAreaData fruAreaData;
330 if (!propMap.empty())
331 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700332 // Set formatting data that goes at the beginning of the record
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500333 preFormatProcessing(true, fruAreaData);
334
Patrick Venture0b02be92018-08-31 11:55:55 -0700335 // manufacturer
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500336 appendData(manufacturer, propMap, fruAreaData);
337
Patrick Venture0b02be92018-08-31 11:55:55 -0700338 // Product name/Pretty name
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500339 appendData(prettyName, propMap, fruAreaData);
340
Patrick Venture0b02be92018-08-31 11:55:55 -0700341 // Product part/model number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500342 appendData(model, propMap, fruAreaData);
343
Patrick Venture0b02be92018-08-31 11:55:55 -0700344 // Product version
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500345 appendData(version, propMap, fruAreaData);
346
Patrick Venture0b02be92018-08-31 11:55:55 -0700347 // Serial Number
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500348 appendData(serialNumber, propMap, fruAreaData);
349
Patrick Venture0b02be92018-08-31 11:55:55 -0700350 // Add Asset Tag
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500351 fruAreaData.emplace_back(recordNotPresent);
352
Patrick Venture0b02be92018-08-31 11:55:55 -0700353 // FRU File ID - Empty
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500354 fruAreaData.emplace_back(typeLengthByteNull);
355
356 // Empty FRU File ID bytes
357 fruAreaData.emplace_back(recordNotPresent);
358
Patrick Venture0b02be92018-08-31 11:55:55 -0700359 // End of custom fields
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500360 fruAreaData.emplace_back(endOfCustomFields);
361
362 postFormatProcessing(fruAreaData);
363 }
364 return fruAreaData;
365}
366
367FruAreaData buildFruAreaData(const FruInventoryData& inventory)
368{
Ratan Gupta2f66f002018-01-31 21:26:25 +0530369 FruAreaData combFruArea{};
Patrick Venture0b02be92018-08-31 11:55:55 -0700370 // Now build common header with data for this FRU Inv Record
371 // Use this variable to increment size of header as we go along to determine
372 // offset for the subsequent area offsets
Ratan Gupta2f66f002018-01-31 21:26:25 +0530373 uint16_t curDataOffset = commonHeaderFormatSize;
Patrick Venture0b02be92018-08-31 11:55:55 -0700374 // First byte is id for version of FRU Info Storage Spec used
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500375 combFruArea.emplace_back(specVersion);
376
Patrick Venture0b02be92018-08-31 11:55:55 -0700377 // 2nd byte is offset to internal use data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500378 combFruArea.emplace_back(recordNotPresent);
379
Patrick Venture0b02be92018-08-31 11:55:55 -0700380 // 3rd byte is offset to chassis data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500381 FruAreaData chassisArea;
382 auto chassisIt = inventory.find(chassis);
383 if (chassisIt != inventory.end())
384 {
385 chassisArea = std::move(buildChassisInfoArea(chassisIt->second));
386 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530387 // update the offset to chassis data.
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500388 buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
389
Patrick Venture0b02be92018-08-31 11:55:55 -0700390 // 4th byte is offset to board data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500391 FruAreaData boardArea;
392 auto boardIt = inventory.find(board);
393 if (boardIt != inventory.end())
394 {
395 boardArea = std::move(buildBoardInfoArea(boardIt->second));
396 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530397 // update the offset to the board data.
398 buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500399
Patrick Venture0b02be92018-08-31 11:55:55 -0700400 // 5th byte is offset to product data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500401 FruAreaData prodArea;
402 auto prodIt = inventory.find(product);
403 if (prodIt != inventory.end())
404 {
405 prodArea = std::move(buildProductInfoArea(prodIt->second));
406 }
Ratan Gupta2f66f002018-01-31 21:26:25 +0530407 // update the offset to the product data.
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500408 buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
409
Patrick Venture0b02be92018-08-31 11:55:55 -0700410 // 6th byte is offset to multirecord data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500411 combFruArea.emplace_back(recordNotPresent);
412
Patrick Venture0b02be92018-08-31 11:55:55 -0700413 // 7th byte is PAD
Ratan Gupta2f66f002018-01-31 21:26:25 +0530414 combFruArea.emplace_back(recordNotPresent);
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500415
Patrick Venture0b02be92018-08-31 11:55:55 -0700416 // 8th (Final byte of Header Format) is the checksum
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500417 appendDataChecksum(combFruArea);
418
Patrick Venture0b02be92018-08-31 11:55:55 -0700419 // Combine everything into one full IPMI FRU specification Record
420 // add chassis use area data
421 combFruArea.insert(combFruArea.end(), chassisArea.begin(),
422 chassisArea.end());
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500423
Patrick Venture0b02be92018-08-31 11:55:55 -0700424 // add board area data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500425 combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
426
Patrick Venture0b02be92018-08-31 11:55:55 -0700427 // add product use area data
Marri Devender Rao7d9157e2017-07-01 16:11:40 -0500428 combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
429
430 return combFruArea;
431}
432
Patrick Venture0b02be92018-08-31 11:55:55 -0700433} // namespace fru
434} // namespace ipmi