blob: 05b7dc5a7357e1c52425a4a62841495731e4d393 [file] [log] [blame]
Patrick Ventureab296412020-12-30 13:39:37 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
Brad Bishope45d8c72022-05-25 15:12:53 -040016/// \file fru_utils.cpp
Patrick Ventureab296412020-12-30 13:39:37 -080017
Brad Bishope45d8c72022-05-25 15:12:53 -040018#include "fru_utils.hpp"
Patrick Ventureab296412020-12-30 13:39:37 -080019
20#include <array>
Ed Tanous3013fb42022-07-09 08:27:06 -070021#include <cstddef>
Patrick Ventureab296412020-12-30 13:39:37 -080022#include <cstdint>
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053023#include <filesystem>
Patrick Ventureab296412020-12-30 13:39:37 -080024#include <iostream>
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053025#include <numeric>
Patrick Ventureab296412020-12-30 13:39:37 -080026#include <set>
27#include <string>
28#include <vector>
29
Ed Tanous07d467b2021-02-23 14:48:37 -080030static constexpr bool debug = false;
Patrick Ventureab296412020-12-30 13:39:37 -080031constexpr size_t fruVersion = 1; // Current FRU spec version number is 1
32
Ed Tanous07d467b2021-02-23 14:48:37 -080033std::tm intelEpoch(void)
Vijay Khemka06d1b4a2021-02-09 18:39:11 +000034{
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053035 std::tm val = {};
36 val.tm_year = 1996 - 1900;
Scron-Chang9e5a6752021-03-16 10:51:50 +080037 val.tm_mday = 1;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053038 return val;
Vijay Khemka06d1b4a2021-02-09 18:39:11 +000039}
40
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053041char sixBitToChar(uint8_t val)
Patrick Ventureab296412020-12-30 13:39:37 -080042{
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053043 return static_cast<char>((val & 0x3f) + ' ');
44}
45
46char bcdPlusToChar(uint8_t val)
47{
48 val &= 0xf;
49 return (val < 10) ? static_cast<char>(val + '0') : bcdHighChars[val - 10];
50}
51
52enum FRUDataEncoding
53{
54 binary = 0x0,
55 bcdPlus = 0x1,
56 sixBitASCII = 0x2,
57 languageDependent = 0x3,
58};
59
60/* Decode FRU data into a std::string, given an input iterator and end. If the
61 * state returned is fruDataOk, then the resulting string is the decoded FRU
62 * data. The input iterator is advanced past the data consumed.
63 *
64 * On fruDataErr, we have lost synchronisation with the length bytes, so the
65 * iterator is no longer usable.
66 */
67std::pair<DecodeState, std::string>
68 decodeFRUData(std::vector<uint8_t>::const_iterator& iter,
69 const std::vector<uint8_t>::const_iterator& end,
70 bool isLangEng)
71{
72 std::string value;
Ed Tanous3013fb42022-07-09 08:27:06 -070073 unsigned int i = 0;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053074
75 /* we need at least one byte to decode the type/len header */
76 if (iter == end)
Patrick Ventureab296412020-12-30 13:39:37 -080077 {
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053078 std::cerr << "Truncated FRU data\n";
79 return make_pair(DecodeState::err, value);
80 }
81
82 uint8_t c = *(iter++);
83
84 /* 0xc1 is the end marker */
85 if (c == 0xc1)
86 {
87 return make_pair(DecodeState::end, value);
88 }
89
90 /* decode type/len byte */
91 uint8_t type = static_cast<uint8_t>(c >> 6);
92 uint8_t len = static_cast<uint8_t>(c & 0x3f);
93
94 /* we should have at least len bytes of data available overall */
95 if (iter + len > end)
96 {
97 std::cerr << "FRU data field extends past end of FRU area data\n";
98 return make_pair(DecodeState::err, value);
99 }
100
101 switch (type)
102 {
103 case FRUDataEncoding::binary:
Patrick Ventureab296412020-12-30 13:39:37 -0800104 {
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530105 std::stringstream ss;
106 ss << std::hex << std::setfill('0');
107 for (i = 0; i < len; i++, iter++)
108 {
109 uint8_t val = static_cast<uint8_t>(*iter);
110 ss << std::setw(2) << static_cast<int>(val);
111 }
112 value = ss.str();
113 break;
Patrick Ventureab296412020-12-30 13:39:37 -0800114 }
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530115 case FRUDataEncoding::languageDependent:
116 /* For language-code dependent encodings, assume 8-bit ASCII */
117 value = std::string(iter, iter + len);
118 iter += len;
119
120 /* English text is encoded in 8-bit ASCII + Latin 1. All other
121 * languages are required to use 2-byte unicode. FruDevice does not
122 * handle unicode.
123 */
124 if (!isLangEng)
125 {
126 std::cerr << "Error: Non english string is not supported \n";
127 return make_pair(DecodeState::err, value);
128 }
129
130 break;
131
132 case FRUDataEncoding::bcdPlus:
133 value = std::string();
134 for (i = 0; i < len; i++, iter++)
135 {
136 uint8_t val = *iter;
137 value.push_back(bcdPlusToChar(val >> 4));
138 value.push_back(bcdPlusToChar(val & 0xf));
139 }
140 break;
141
142 case FRUDataEncoding::sixBitASCII:
143 {
144 unsigned int accum = 0;
145 unsigned int accumBitLen = 0;
146 value = std::string();
147 for (i = 0; i < len; i++, iter++)
148 {
149 accum |= *iter << accumBitLen;
150 accumBitLen += 8;
151 while (accumBitLen >= 6)
152 {
153 value.push_back(sixBitToChar(accum & 0x3f));
154 accum >>= 6;
155 accumBitLen -= 6;
156 }
157 }
158 }
159 break;
160 }
161
162 return make_pair(DecodeState::ok, value);
163}
164
165bool checkLangEng(uint8_t lang)
166{
167 // If Lang is not English then the encoding is defined as 2-byte UNICODE,
168 // but we don't support that.
Ed Tanous3013fb42022-07-09 08:27:06 -0700169 if ((lang != 0U) && lang != 25)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530170 {
171 std::cerr << "Warning: languages other than English is not "
172 "supported\n";
173 // Return language flag as non english
Patrick Ventureab296412020-12-30 13:39:37 -0800174 return false;
175 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800176 return true;
Patrick Ventureab296412020-12-30 13:39:37 -0800177}
178
Vijay Khemka06d1b4a2021-02-09 18:39:11 +0000179/* This function verifies for other offsets to check if they are not
180 * falling under other field area
181 *
182 * fruBytes: Start of Fru data
183 * currentArea: Index of current area offset to be compared against all area
184 * offset and it is a multiple of 8 bytes as per specification
185 * len: Length of current area space and it is a multiple of 8 bytes
186 * as per specification
187 */
188bool verifyOffset(const std::vector<uint8_t>& fruBytes, fruAreas currentArea,
189 uint8_t len)
190{
191
192 unsigned int fruBytesSize = fruBytes.size();
193
194 // check if Fru data has at least 8 byte header
195 if (fruBytesSize <= fruBlockSize)
196 {
197 std::cerr << "Error: trying to parse empty FRU\n";
198 return false;
199 }
200
201 // Check range of passed currentArea value
202 if (currentArea > fruAreas::fruAreaMultirecord)
203 {
204 std::cerr << "Error: Fru area is out of range\n";
205 return false;
206 }
207
208 unsigned int currentAreaIndex = getHeaderAreaFieldOffset(currentArea);
209 if (currentAreaIndex > fruBytesSize)
210 {
211 std::cerr << "Error: Fru area index is out of range\n";
212 return false;
213 }
214
215 unsigned int start = fruBytes[currentAreaIndex];
216 unsigned int end = start + len;
217
218 /* Verify each offset within the range of start and end */
219 for (fruAreas area = fruAreas::fruAreaInternal;
220 area <= fruAreas::fruAreaMultirecord; ++area)
221 {
222 // skip the current offset
223 if (area == currentArea)
224 {
225 continue;
226 }
227
228 unsigned int areaIndex = getHeaderAreaFieldOffset(area);
229 if (areaIndex > fruBytesSize)
230 {
231 std::cerr << "Error: Fru area index is out of range\n";
232 return false;
233 }
234
235 unsigned int areaOffset = fruBytes[areaIndex];
236 // if areaOffset is 0 means this area is not available so skip
237 if (areaOffset == 0)
238 {
239 continue;
240 }
241
242 // check for overlapping of current offset with given areaoffset
243 if (areaOffset == start || (areaOffset > start && areaOffset < end))
244 {
245 std::cerr << getFruAreaName(currentArea)
246 << " offset is overlapping with " << getFruAreaName(area)
247 << " offset\n";
248 return false;
249 }
250 }
251 return true;
252}
253
Michael Shen0961b112022-02-22 11:06:33 +0800254resCodes
255 formatIPMIFRU(const std::vector<uint8_t>& fruBytes,
256 boost::container::flat_map<std::string, std::string>& result)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530257{
258 resCodes ret = resCodes::resOK;
259 if (fruBytes.size() <= fruBlockSize)
260 {
261 std::cerr << "Error: trying to parse empty FRU \n";
262 return resCodes::resErr;
263 }
264 result["Common_Format_Version"] =
265 std::to_string(static_cast<int>(*fruBytes.begin()));
266
Ed Tanous3013fb42022-07-09 08:27:06 -0700267 const std::vector<std::string>* fruAreaFieldNames = nullptr;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530268
269 // Don't parse Internal and Multirecord areas
270 for (fruAreas area = fruAreas::fruAreaChassis;
271 area <= fruAreas::fruAreaProduct; ++area)
272 {
273
274 size_t offset = *(fruBytes.begin() + getHeaderAreaFieldOffset(area));
275 if (offset == 0)
276 {
277 continue;
278 }
279 offset *= fruBlockSize;
280 std::vector<uint8_t>::const_iterator fruBytesIter =
281 fruBytes.begin() + offset;
282 if (fruBytesIter + fruBlockSize >= fruBytes.end())
283 {
284 std::cerr << "Not enough data to parse \n";
285 return resCodes::resErr;
286 }
287 // check for format version 1
288 if (*fruBytesIter != 0x01)
289 {
290 std::cerr << "Unexpected version " << *fruBytesIter << "\n";
291 return resCodes::resErr;
292 }
293 ++fruBytesIter;
294
295 /* Verify other area offset for overlap with current area by passing
296 * length of current area offset pointed by *fruBytesIter
297 */
298 if (!verifyOffset(fruBytes, area, *fruBytesIter))
299 {
300 return resCodes::resErr;
301 }
302
Andrew Jeffery65ed6642021-08-02 22:32:23 +0930303 size_t fruAreaSize = *fruBytesIter * fruBlockSize;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530304 std::vector<uint8_t>::const_iterator fruBytesIterEndArea =
305 fruBytes.begin() + offset + fruAreaSize - 1;
306 ++fruBytesIter;
307
308 uint8_t fruComputedChecksum =
309 calculateChecksum(fruBytes.begin() + offset, fruBytesIterEndArea);
310 if (fruComputedChecksum != *fruBytesIterEndArea)
311 {
312 std::stringstream ss;
313 ss << std::hex << std::setfill('0');
314 ss << "Checksum error in FRU area " << getFruAreaName(area) << "\n";
315 ss << "\tComputed checksum: 0x" << std::setw(2)
316 << static_cast<int>(fruComputedChecksum) << "\n";
317 ss << "\tThe read checksum: 0x" << std::setw(2)
318 << static_cast<int>(*fruBytesIterEndArea) << "\n";
319 std::cerr << ss.str();
320 ret = resCodes::resWarn;
321 }
322
323 /* Set default language flag to true as Chassis Fru area are always
324 * encoded in English defined in Section 10 of Fru specification
325 */
326
327 bool isLangEng = true;
328 switch (area)
329 {
330 case fruAreas::fruAreaChassis:
331 {
332 result["CHASSIS_TYPE"] =
333 std::to_string(static_cast<int>(*fruBytesIter));
334 fruBytesIter += 1;
Ed Tanous07d467b2021-02-23 14:48:37 -0800335 fruAreaFieldNames = &chassisFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530336 break;
337 }
338 case fruAreas::fruAreaBoard:
339 {
340 uint8_t lang = *fruBytesIter;
341 result["BOARD_LANGUAGE_CODE"] =
342 std::to_string(static_cast<int>(lang));
343 isLangEng = checkLangEng(lang);
344 fruBytesIter += 1;
345
346 unsigned int minutes = *fruBytesIter |
347 *(fruBytesIter + 1) << 8 |
348 *(fruBytesIter + 2) << 16;
349 std::tm fruTime = intelEpoch();
350 std::time_t timeValue = std::mktime(&fruTime);
Ed Tanous3013fb42022-07-09 08:27:06 -0700351 timeValue += static_cast<long>(minutes) * 60;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530352 fruTime = *std::gmtime(&timeValue);
353
354 // Tue Nov 20 23:08:00 2018
Ed Tanous3013fb42022-07-09 08:27:06 -0700355 std::array<char, 32> timeString = {};
356 auto bytes = std::strftime(timeString.data(), timeString.size(),
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530357 "%Y-%m-%d - %H:%M:%S", &fruTime);
358 if (bytes == 0)
359 {
360 std::cerr << "invalid time string encountered\n";
361 return resCodes::resErr;
362 }
363
Ed Tanous3013fb42022-07-09 08:27:06 -0700364 result["BOARD_MANUFACTURE_DATE"] =
365 std::string_view(timeString.data(), bytes);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530366 fruBytesIter += 3;
Ed Tanous07d467b2021-02-23 14:48:37 -0800367 fruAreaFieldNames = &boardFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530368 break;
369 }
370 case fruAreas::fruAreaProduct:
371 {
372 uint8_t lang = *fruBytesIter;
373 result["PRODUCT_LANGUAGE_CODE"] =
374 std::to_string(static_cast<int>(lang));
375 isLangEng = checkLangEng(lang);
376 fruBytesIter += 1;
Ed Tanous07d467b2021-02-23 14:48:37 -0800377 fruAreaFieldNames = &productFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530378 break;
379 }
380 default:
381 {
382 std::cerr << "Internal error: unexpected FRU area index: "
383 << static_cast<int>(area) << " \n";
384 return resCodes::resErr;
385 }
386 }
387 size_t fieldIndex = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700388 DecodeState state = DecodeState::ok;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530389 do
390 {
391 auto res =
392 decodeFRUData(fruBytesIter, fruBytesIterEndArea, isLangEng);
393 state = res.first;
394 std::string value = res.second;
395 std::string name;
396 if (fieldIndex < fruAreaFieldNames->size())
397 {
398 name = std::string(getFruAreaName(area)) + "_" +
399 fruAreaFieldNames->at(fieldIndex);
400 }
Scron-Chang77987122021-03-30 20:53:52 +0800401 else
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530402 {
403 name =
404 std::string(getFruAreaName(area)) + "_" +
Ed Tanous07d467b2021-02-23 14:48:37 -0800405 fruCustomFieldName +
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530406 std::to_string(fieldIndex - fruAreaFieldNames->size() + 1);
407 }
408
409 if (state == DecodeState::ok)
410 {
411 // Strip non null characters from the end
412 value.erase(std::find_if(value.rbegin(), value.rend(),
413 [](char ch) { return ch != 0; })
414 .base(),
415 value.end());
416
417 result[name] = std::move(value);
418 ++fieldIndex;
419 }
420 else if (state == DecodeState::err)
421 {
422 std::cerr << "Error while parsing " << name << "\n";
423 ret = resCodes::resWarn;
424 // Cancel decoding if failed to parse any of mandatory
425 // fields
426 if (fieldIndex < fruAreaFieldNames->size())
427 {
428 std::cerr << "Failed to parse mandatory field \n";
429 return resCodes::resErr;
430 }
431 }
432 else
433 {
434 if (fieldIndex < fruAreaFieldNames->size())
435 {
436 std::cerr << "Mandatory fields absent in FRU area "
437 << getFruAreaName(area) << " after " << name
438 << "\n";
439 ret = resCodes::resWarn;
440 }
441 }
442 } while (state == DecodeState::ok);
443 for (; fruBytesIter < fruBytesIterEndArea; fruBytesIter++)
444 {
445 uint8_t c = *fruBytesIter;
Ed Tanous3013fb42022-07-09 08:27:06 -0700446 if (c != 0U)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530447 {
448 std::cerr << "Non-zero byte after EndOfFields in FRU area "
449 << getFruAreaName(area) << "\n";
450 ret = resCodes::resWarn;
451 break;
452 }
453 }
454 }
455
456 return ret;
457}
458
459// Calculate new checksum for fru info area
460uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
461 std::vector<uint8_t>::const_iterator end)
462{
463 constexpr int checksumMod = 256;
Andrew Jeffery499e7aa2021-08-02 22:18:22 +0930464 uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
465 return (checksumMod - sum) % checksumMod;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530466}
467
468uint8_t calculateChecksum(std::vector<uint8_t>& fruAreaData)
469{
470 return calculateChecksum(fruAreaData.begin(), fruAreaData.end());
471}
472
473// Update new fru area length &
474// Update checksum at new checksum location
475// Return the offset of the area checksum byte
476unsigned int updateFRUAreaLenAndChecksum(std::vector<uint8_t>& fruData,
477 size_t fruAreaStart,
478 size_t fruAreaEndOfFieldsOffset,
479 size_t fruAreaEndOffset)
480{
481 size_t traverseFRUAreaIndex = fruAreaEndOfFieldsOffset - fruAreaStart;
482
483 // fill zeros for any remaining unused space
484 std::fill(fruData.begin() + fruAreaEndOfFieldsOffset,
485 fruData.begin() + fruAreaEndOffset, 0);
486
487 size_t mod = traverseFRUAreaIndex % fruBlockSize;
Ed Tanous3013fb42022-07-09 08:27:06 -0700488 size_t checksumLoc = 0;
489 if (mod == 0U)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530490 {
491 traverseFRUAreaIndex += (fruBlockSize);
492 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - 1);
493 }
494 else
495 {
496 traverseFRUAreaIndex += (fruBlockSize - mod);
497 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - mod - 1);
498 }
499
Ed Tanous3013fb42022-07-09 08:27:06 -0700500 size_t newFRUAreaLen =
501 (traverseFRUAreaIndex / fruBlockSize) +
502 static_cast<unsigned long>((traverseFRUAreaIndex % fruBlockSize) != 0);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530503 size_t fruAreaLengthLoc = fruAreaStart + 1;
504 fruData[fruAreaLengthLoc] = static_cast<uint8_t>(newFRUAreaLen);
505
506 // Calculate new checksum
507 std::vector<uint8_t> finalFRUData;
508 std::copy_n(fruData.begin() + fruAreaStart, checksumLoc - fruAreaStart,
509 std::back_inserter(finalFRUData));
510
511 fruData[checksumLoc] = calculateChecksum(finalFRUData);
512 return checksumLoc;
513}
514
515ssize_t getFieldLength(uint8_t fruFieldTypeLenValue)
516{
517 constexpr uint8_t typeLenMask = 0x3F;
518 constexpr uint8_t endOfFields = 0xC1;
519 if (fruFieldTypeLenValue == endOfFields)
520 {
521 return -1;
522 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800523 return fruFieldTypeLenValue & typeLenMask;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530524}
525
Zev Weisse6c86b72022-02-26 00:23:13 +0000526bool validateIPMIHeader(const std::vector<uint8_t>& hdr)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530527{
Zev Weisse6c86b72022-02-26 00:23:13 +0000528 if (hdr.size() < 8)
529 {
530 return false;
531 }
532
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530533 // ipmi spec format version number is currently at 1, verify it
Zev Weisse6c86b72022-02-26 00:23:13 +0000534 if (hdr[0] != fruVersion)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530535 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800536 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530537 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000538 std::cerr << "FRU spec version " << (int)(hdr[0])
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530539 << " not supported. Supported version is "
540 << (int)(fruVersion) << "\n";
541 }
542 return false;
543 }
544
545 // verify pad is set to 0
Zev Weisse6c86b72022-02-26 00:23:13 +0000546 if (hdr[6] != 0x0)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530547 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800548 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530549 {
550 std::cerr << "PAD value in header is non zero, value is "
Zev Weisse6c86b72022-02-26 00:23:13 +0000551 << (int)(hdr[6]) << "\n";
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530552 }
553 return false;
554 }
555
556 // verify offsets are 0, or don't point to another offset
557 std::set<uint8_t> foundOffsets;
558 for (int ii = 1; ii < 6; ii++)
559 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000560 if (hdr[ii] == 0)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530561 {
562 continue;
563 }
Zev Weisse6c86b72022-02-26 00:23:13 +0000564 auto inserted = foundOffsets.insert(hdr[ii]);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530565 if (!inserted.second)
566 {
567 return false;
568 }
569 }
570
571 // validate checksum
572 size_t sum = 0;
573 for (int jj = 0; jj < 7; jj++)
574 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000575 sum += hdr[jj];
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530576 }
577 sum = (256 - sum) & 0xFF;
578
Zev Weisse6c86b72022-02-26 00:23:13 +0000579 if (sum != hdr[7])
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530580 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800581 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530582 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000583 std::cerr << "Checksum " << (int)(hdr[7])
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530584 << " is invalid. calculated checksum is " << (int)(sum)
585 << "\n";
586 }
587 return false;
588 }
589 return true;
590}
591
Zev Weisse6c86b72022-02-26 00:23:13 +0000592static std::vector<uint8_t> readIPMIFRUContents(BaseFRUReader& reader,
593 const std::string& errorHelp)
Oskar Senftbd4075f2021-10-05 23:42:43 -0400594{
Zev Weisse6c86b72022-02-26 00:23:13 +0000595 std::vector<uint8_t> device(8);
596 int64_t ret = reader.read(0, device.size(), device.data());
597
598 if (ret != static_cast<int64_t>(device.size()))
Oskar Senftbd4075f2021-10-05 23:42:43 -0400599 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000600 if (ret < 0)
601 {
602 std::cerr << "failed to read " << errorHelp << "\n";
603 }
Patrick Ventureab296412020-12-30 13:39:37 -0800604 return {};
605 }
606
Zev Weisse6c86b72022-02-26 00:23:13 +0000607 if (!validateIPMIHeader(device))
608 {
609 return {};
610 }
Patrick Ventureab296412020-12-30 13:39:37 -0800611
612 bool hasMultiRecords = false;
613 size_t fruLength = fruBlockSize; // At least FRU header is present
Vijay Khemka7792e392021-01-25 13:03:56 -0800614 unsigned int prevOffset = 0;
Patrick Ventureab296412020-12-30 13:39:37 -0800615 for (fruAreas area = fruAreas::fruAreaInternal;
616 area <= fruAreas::fruAreaMultirecord; ++area)
617 {
618 // Offset value can be 255.
619 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
620 if (areaOffset == 0)
621 {
622 continue;
623 }
624
Vijay Khemka7792e392021-01-25 13:03:56 -0800625 /* Check for offset order, as per Section 17 of FRU specification, FRU
626 * information areas are required to be in order in FRU data layout
627 * which means all offset value should be in increasing order or can be
628 * 0 if that area is not present
629 */
630 if (areaOffset <= prevOffset)
631 {
632 std::cerr << "Fru area offsets are not in required order as per "
633 "Section 17 of Fru specification\n";
634 return {};
635 }
636 prevOffset = areaOffset;
637
Patrick Ventureab296412020-12-30 13:39:37 -0800638 // MultiRecords are different. area is not tracking section, it's
639 // walking the common header.
640 if (area == fruAreas::fruAreaMultirecord)
641 {
642 hasMultiRecords = true;
643 break;
644 }
645
646 areaOffset *= fruBlockSize;
647
Zev Weisse6c86b72022-02-26 00:23:13 +0000648 std::array<uint8_t, 2> areaData{};
649 if (reader.read(areaOffset, areaData.size(), areaData.data()) !=
650 static_cast<ssize_t>(areaData.size()))
Patrick Ventureab296412020-12-30 13:39:37 -0800651 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000652 std::cerr << "failed to read " << errorHelp << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800653 return {};
654 }
655
Zev Weisse6c86b72022-02-26 00:23:13 +0000656 // Ignore data type (areaData is already unsigned).
657 size_t length = areaData[1] * fruBlockSize;
Patrick Ventureab296412020-12-30 13:39:37 -0800658 areaOffset += length;
659 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
660 }
661
662 if (hasMultiRecords)
663 {
664 // device[area count] is the index to the last area because the 0th
665 // entry is not an offset in the common header.
666 unsigned int areaOffset =
667 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
668 areaOffset *= fruBlockSize;
669
670 // the multi-area record header is 5 bytes long.
671 constexpr size_t multiRecordHeaderSize = 5;
672 constexpr uint8_t multiRecordEndOfListMask = 0x80;
673
674 // Sanity hard-limit to 64KB.
675 while (areaOffset < std::numeric_limits<uint16_t>::max())
676 {
677 // In multi-area, the area offset points to the 0th record, each
678 // record has 3 bytes of the header we care about.
Zev Weisse6c86b72022-02-26 00:23:13 +0000679 std::array<uint8_t, 3> areaData{};
680 if (reader.read(areaOffset, areaData.size(), areaData.data()) !=
681 static_cast<ssize_t>(areaData.size()))
Patrick Ventureab296412020-12-30 13:39:37 -0800682 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000683 std::cerr << "failed to read " << errorHelp << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800684 return {};
685 }
686
687 // Ok, let's check the record length, which is in bytes (unsigned,
Zev Weisse6c86b72022-02-26 00:23:13 +0000688 // up to 255, so areaData should hold uint8_t not char)
689 size_t recordLength = areaData[2];
Patrick Ventureab296412020-12-30 13:39:37 -0800690 areaOffset += (recordLength + multiRecordHeaderSize);
691 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
692
693 // If this is the end of the list bail.
Zev Weisse6c86b72022-02-26 00:23:13 +0000694 if ((areaData[1] & multiRecordEndOfListMask) != 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800695 {
696 break;
697 }
698 }
699 }
700
701 // You already copied these first 8 bytes (the ipmi fru header size)
702 fruLength -= std::min(fruBlockSize, fruLength);
Zev Weisse6c86b72022-02-26 00:23:13 +0000703 device.resize(device.size() + fruLength);
Patrick Ventureab296412020-12-30 13:39:37 -0800704
Zev Weisse6c86b72022-02-26 00:23:13 +0000705 int64_t nread = reader.read(static_cast<uint16_t>(fruBlockSize),
706 static_cast<uint8_t>(fruLength),
707 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
708 device.data() + (device.size() - fruLength));
709 if (nread != static_cast<int64_t>(fruLength))
Patrick Ventureab296412020-12-30 13:39:37 -0800710 {
Zev Weisse6c86b72022-02-26 00:23:13 +0000711 std::cerr << "failed to read " << errorHelp << "\n";
712 return {};
Patrick Ventureab296412020-12-30 13:39:37 -0800713 }
714
715 return device;
716}
717
Zev Weisse6c86b72022-02-26 00:23:13 +0000718static std::vector<uint8_t> readTyanFRUContents(BaseFRUReader& reader,
719 const std::string& errorHelp)
720{
721 const std::array<uint8_t, 6> tyanMagic = {'$', 'T', 'Y', 'A', 'N', '$'};
722 std::array<uint8_t, tyanMagic.size()> magicBytes{};
723
724 if (reader.read(0, magicBytes.size(), magicBytes.data()) !=
725 magicBytes.size() ||
726 magicBytes != tyanMagic)
727 {
728 return {};
729 }
730
731 OffsetFRUReader tyanReader(reader, 0x6000);
732
733 return readIPMIFRUContents(tyanReader, errorHelp);
734}
735
736std::vector<uint8_t> readFRUContents(BaseFRUReader& reader,
737 const std::string& errorHelp)
738{
739 using FRUParser = std::vector<uint8_t> (*)(BaseFRUReader&, const std::string&);
740 static constexpr auto fruParsers = std::to_array<FRUParser>({
741 readIPMIFRUContents,
742 readTyanFRUContents,
743 });
744
745 for (const auto& parser : fruParsers)
746 {
747 std::vector<uint8_t> data = parser(reader, errorHelp);
748 if (!data.empty())
749 {
750 return data;
751 }
752 }
753
754 return {};
755}
756
Patrick Ventureab296412020-12-30 13:39:37 -0800757unsigned int getHeaderAreaFieldOffset(fruAreas area)
758{
759 return static_cast<unsigned int>(area) + 1;
760}
Kumar Thangavel7135f3d2022-02-04 12:14:24 +0530761
762std::vector<uint8_t>& getFRUInfo(const uint8_t& bus, const uint8_t& address)
763{
764 auto deviceMap = busMap.find(bus);
765 if (deviceMap == busMap.end())
766 {
767 throw std::invalid_argument("Invalid Bus.");
768 }
769 auto device = deviceMap->second->find(address);
770 if (device == deviceMap->second->end())
771 {
772 throw std::invalid_argument("Invalid Address.");
773 }
774 std::vector<uint8_t>& ret = device->second;
775
776 return ret;
777}
Kumar Thangavelbdfc5ec2022-08-29 22:23:00 +0530778
779// Iterate FruArea Names and find start and size of the fru area that contains
780// the propertyName and the field start location for the property. fruAreaParams
781// struct values fruAreaStart, fruAreaSize, fruAreaEnd, fieldLoc values gets
782// updated/returned if successful.
783
784bool findFruAreaLocationAndField(std::vector<uint8_t>& fruData,
785 const std::string& propertyName,
786 struct FruArea& fruAreaParams,
787 size_t& fruDataIter)
788{
789 const std::vector<std::string>* fruAreaFieldNames = nullptr;
790
791 uint8_t fruAreaOffsetFieldValue = 0;
792 size_t offset = 0;
793 std::string areaName = propertyName.substr(0, propertyName.find('_'));
794 std::string propertyNamePrefix = areaName + "_";
795 auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
796 if (it == fruAreaNames.end())
797 {
798 std::cerr << "Can't parse area name for property " << propertyName
799 << " \n";
800 return false;
801 }
802 fruAreas fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
803 fruAreaOffsetFieldValue =
804 fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
805 switch (fruAreaToUpdate)
806 {
807 case fruAreas::fruAreaChassis:
808 offset = 3; // chassis part number offset. Skip fixed first 3 bytes
809 fruAreaFieldNames = &chassisFruAreas;
810 break;
811 case fruAreas::fruAreaBoard:
812 offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
813 fruAreaFieldNames = &boardFruAreas;
814 break;
815 case fruAreas::fruAreaProduct:
816 // Manufacturer name offset. Skip fixed first 3 product fru bytes
817 // i.e. version, area length and language code
818 offset = 3;
819 fruAreaFieldNames = &productFruAreas;
820 break;
821 default:
822 std::cerr << "Invalid PropertyName " << propertyName << " \n";
823 return false;
824 }
825 if (fruAreaOffsetFieldValue == 0)
826 {
827 std::cerr << "FRU Area for " << propertyName << " not present \n";
828 return false;
829 }
830
831 fruAreaParams.start = fruAreaOffsetFieldValue * fruBlockSize;
832 fruAreaParams.size = fruData[fruAreaParams.start + 1] * fruBlockSize;
833 fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
834 fruDataIter = fruAreaParams.start + offset;
835 size_t skipToFRUUpdateField = 0;
836 ssize_t fieldLength = 0;
837
838 bool found = false;
839 for (const auto& field : *fruAreaFieldNames)
840 {
841 skipToFRUUpdateField++;
842 if (propertyName == propertyNamePrefix + field)
843 {
844 found = true;
845 break;
846 }
847 }
848 if (!found)
849 {
850 std::size_t pos = propertyName.find(fruCustomFieldName);
851 if (pos == std::string::npos)
852 {
853 std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
854 << propertyName << "\n";
855 return false;
856 }
857 std::string fieldNumStr =
858 propertyName.substr(pos + fruCustomFieldName.length());
859 size_t fieldNum = std::stoi(fieldNumStr);
860 if (fieldNum == 0)
861 {
862 std::cerr << "PropertyName not recognized: " << propertyName
863 << "\n";
864 return false;
865 }
866 skipToFRUUpdateField += fieldNum;
867 }
868
869 for (size_t i = 1; i < skipToFRUUpdateField; i++)
870 {
871 if (fruDataIter < fruData.size())
872 {
873 fieldLength = getFieldLength(fruData[fruDataIter]);
874
875 if (fieldLength < 0)
876 {
877 break;
878 }
879 fruDataIter += 1 + fieldLength;
880 }
881 }
882 fruAreaParams.updateFieldLoc = fruDataIter;
883
884 return true;
885}