blob: e030ac83c85479d1fe5151e8c8e717e299d8bcba [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
30extern "C"
31{
32// Include for I2C_SMBUS_BLOCK_MAX
33#include <linux/i2c.h>
34}
35
Ed Tanous07d467b2021-02-23 14:48:37 -080036static constexpr bool debug = false;
Patrick Ventureab296412020-12-30 13:39:37 -080037constexpr size_t fruVersion = 1; // Current FRU spec version number is 1
38
Ed Tanous07d467b2021-02-23 14:48:37 -080039std::tm intelEpoch(void)
Vijay Khemka06d1b4a2021-02-09 18:39:11 +000040{
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053041 std::tm val = {};
42 val.tm_year = 1996 - 1900;
Scron-Chang9e5a6752021-03-16 10:51:50 +080043 val.tm_mday = 1;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053044 return val;
Vijay Khemka06d1b4a2021-02-09 18:39:11 +000045}
46
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053047char sixBitToChar(uint8_t val)
Patrick Ventureab296412020-12-30 13:39:37 -080048{
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053049 return static_cast<char>((val & 0x3f) + ' ');
50}
51
52char bcdPlusToChar(uint8_t val)
53{
54 val &= 0xf;
55 return (val < 10) ? static_cast<char>(val + '0') : bcdHighChars[val - 10];
56}
57
58enum FRUDataEncoding
59{
60 binary = 0x0,
61 bcdPlus = 0x1,
62 sixBitASCII = 0x2,
63 languageDependent = 0x3,
64};
65
66/* Decode FRU data into a std::string, given an input iterator and end. If the
67 * state returned is fruDataOk, then the resulting string is the decoded FRU
68 * data. The input iterator is advanced past the data consumed.
69 *
70 * On fruDataErr, we have lost synchronisation with the length bytes, so the
71 * iterator is no longer usable.
72 */
73std::pair<DecodeState, std::string>
74 decodeFRUData(std::vector<uint8_t>::const_iterator& iter,
75 const std::vector<uint8_t>::const_iterator& end,
76 bool isLangEng)
77{
78 std::string value;
Ed Tanous3013fb42022-07-09 08:27:06 -070079 unsigned int i = 0;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053080
81 /* we need at least one byte to decode the type/len header */
82 if (iter == end)
Patrick Ventureab296412020-12-30 13:39:37 -080083 {
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +053084 std::cerr << "Truncated FRU data\n";
85 return make_pair(DecodeState::err, value);
86 }
87
88 uint8_t c = *(iter++);
89
90 /* 0xc1 is the end marker */
91 if (c == 0xc1)
92 {
93 return make_pair(DecodeState::end, value);
94 }
95
96 /* decode type/len byte */
97 uint8_t type = static_cast<uint8_t>(c >> 6);
98 uint8_t len = static_cast<uint8_t>(c & 0x3f);
99
100 /* we should have at least len bytes of data available overall */
101 if (iter + len > end)
102 {
103 std::cerr << "FRU data field extends past end of FRU area data\n";
104 return make_pair(DecodeState::err, value);
105 }
106
107 switch (type)
108 {
109 case FRUDataEncoding::binary:
Patrick Ventureab296412020-12-30 13:39:37 -0800110 {
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530111 std::stringstream ss;
112 ss << std::hex << std::setfill('0');
113 for (i = 0; i < len; i++, iter++)
114 {
115 uint8_t val = static_cast<uint8_t>(*iter);
116 ss << std::setw(2) << static_cast<int>(val);
117 }
118 value = ss.str();
119 break;
Patrick Ventureab296412020-12-30 13:39:37 -0800120 }
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530121 case FRUDataEncoding::languageDependent:
122 /* For language-code dependent encodings, assume 8-bit ASCII */
123 value = std::string(iter, iter + len);
124 iter += len;
125
126 /* English text is encoded in 8-bit ASCII + Latin 1. All other
127 * languages are required to use 2-byte unicode. FruDevice does not
128 * handle unicode.
129 */
130 if (!isLangEng)
131 {
132 std::cerr << "Error: Non english string is not supported \n";
133 return make_pair(DecodeState::err, value);
134 }
135
136 break;
137
138 case FRUDataEncoding::bcdPlus:
139 value = std::string();
140 for (i = 0; i < len; i++, iter++)
141 {
142 uint8_t val = *iter;
143 value.push_back(bcdPlusToChar(val >> 4));
144 value.push_back(bcdPlusToChar(val & 0xf));
145 }
146 break;
147
148 case FRUDataEncoding::sixBitASCII:
149 {
150 unsigned int accum = 0;
151 unsigned int accumBitLen = 0;
152 value = std::string();
153 for (i = 0; i < len; i++, iter++)
154 {
155 accum |= *iter << accumBitLen;
156 accumBitLen += 8;
157 while (accumBitLen >= 6)
158 {
159 value.push_back(sixBitToChar(accum & 0x3f));
160 accum >>= 6;
161 accumBitLen -= 6;
162 }
163 }
164 }
165 break;
166 }
167
168 return make_pair(DecodeState::ok, value);
169}
170
171bool checkLangEng(uint8_t lang)
172{
173 // If Lang is not English then the encoding is defined as 2-byte UNICODE,
174 // but we don't support that.
Ed Tanous3013fb42022-07-09 08:27:06 -0700175 if ((lang != 0U) && lang != 25)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530176 {
177 std::cerr << "Warning: languages other than English is not "
178 "supported\n";
179 // Return language flag as non english
Patrick Ventureab296412020-12-30 13:39:37 -0800180 return false;
181 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800182 return true;
Patrick Ventureab296412020-12-30 13:39:37 -0800183}
184
Vijay Khemka06d1b4a2021-02-09 18:39:11 +0000185/* This function verifies for other offsets to check if they are not
186 * falling under other field area
187 *
188 * fruBytes: Start of Fru data
189 * currentArea: Index of current area offset to be compared against all area
190 * offset and it is a multiple of 8 bytes as per specification
191 * len: Length of current area space and it is a multiple of 8 bytes
192 * as per specification
193 */
194bool verifyOffset(const std::vector<uint8_t>& fruBytes, fruAreas currentArea,
195 uint8_t len)
196{
197
198 unsigned int fruBytesSize = fruBytes.size();
199
200 // check if Fru data has at least 8 byte header
201 if (fruBytesSize <= fruBlockSize)
202 {
203 std::cerr << "Error: trying to parse empty FRU\n";
204 return false;
205 }
206
207 // Check range of passed currentArea value
208 if (currentArea > fruAreas::fruAreaMultirecord)
209 {
210 std::cerr << "Error: Fru area is out of range\n";
211 return false;
212 }
213
214 unsigned int currentAreaIndex = getHeaderAreaFieldOffset(currentArea);
215 if (currentAreaIndex > fruBytesSize)
216 {
217 std::cerr << "Error: Fru area index is out of range\n";
218 return false;
219 }
220
221 unsigned int start = fruBytes[currentAreaIndex];
222 unsigned int end = start + len;
223
224 /* Verify each offset within the range of start and end */
225 for (fruAreas area = fruAreas::fruAreaInternal;
226 area <= fruAreas::fruAreaMultirecord; ++area)
227 {
228 // skip the current offset
229 if (area == currentArea)
230 {
231 continue;
232 }
233
234 unsigned int areaIndex = getHeaderAreaFieldOffset(area);
235 if (areaIndex > fruBytesSize)
236 {
237 std::cerr << "Error: Fru area index is out of range\n";
238 return false;
239 }
240
241 unsigned int areaOffset = fruBytes[areaIndex];
242 // if areaOffset is 0 means this area is not available so skip
243 if (areaOffset == 0)
244 {
245 continue;
246 }
247
248 // check for overlapping of current offset with given areaoffset
249 if (areaOffset == start || (areaOffset > start && areaOffset < end))
250 {
251 std::cerr << getFruAreaName(currentArea)
252 << " offset is overlapping with " << getFruAreaName(area)
253 << " offset\n";
254 return false;
255 }
256 }
257 return true;
258}
259
Michael Shen0961b112022-02-22 11:06:33 +0800260resCodes
261 formatIPMIFRU(const std::vector<uint8_t>& fruBytes,
262 boost::container::flat_map<std::string, std::string>& result)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530263{
264 resCodes ret = resCodes::resOK;
265 if (fruBytes.size() <= fruBlockSize)
266 {
267 std::cerr << "Error: trying to parse empty FRU \n";
268 return resCodes::resErr;
269 }
270 result["Common_Format_Version"] =
271 std::to_string(static_cast<int>(*fruBytes.begin()));
272
Ed Tanous3013fb42022-07-09 08:27:06 -0700273 const std::vector<std::string>* fruAreaFieldNames = nullptr;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530274
275 // Don't parse Internal and Multirecord areas
276 for (fruAreas area = fruAreas::fruAreaChassis;
277 area <= fruAreas::fruAreaProduct; ++area)
278 {
279
280 size_t offset = *(fruBytes.begin() + getHeaderAreaFieldOffset(area));
281 if (offset == 0)
282 {
283 continue;
284 }
285 offset *= fruBlockSize;
286 std::vector<uint8_t>::const_iterator fruBytesIter =
287 fruBytes.begin() + offset;
288 if (fruBytesIter + fruBlockSize >= fruBytes.end())
289 {
290 std::cerr << "Not enough data to parse \n";
291 return resCodes::resErr;
292 }
293 // check for format version 1
294 if (*fruBytesIter != 0x01)
295 {
296 std::cerr << "Unexpected version " << *fruBytesIter << "\n";
297 return resCodes::resErr;
298 }
299 ++fruBytesIter;
300
301 /* Verify other area offset for overlap with current area by passing
302 * length of current area offset pointed by *fruBytesIter
303 */
304 if (!verifyOffset(fruBytes, area, *fruBytesIter))
305 {
306 return resCodes::resErr;
307 }
308
Andrew Jeffery65ed6642021-08-02 22:32:23 +0930309 size_t fruAreaSize = *fruBytesIter * fruBlockSize;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530310 std::vector<uint8_t>::const_iterator fruBytesIterEndArea =
311 fruBytes.begin() + offset + fruAreaSize - 1;
312 ++fruBytesIter;
313
314 uint8_t fruComputedChecksum =
315 calculateChecksum(fruBytes.begin() + offset, fruBytesIterEndArea);
316 if (fruComputedChecksum != *fruBytesIterEndArea)
317 {
318 std::stringstream ss;
319 ss << std::hex << std::setfill('0');
320 ss << "Checksum error in FRU area " << getFruAreaName(area) << "\n";
321 ss << "\tComputed checksum: 0x" << std::setw(2)
322 << static_cast<int>(fruComputedChecksum) << "\n";
323 ss << "\tThe read checksum: 0x" << std::setw(2)
324 << static_cast<int>(*fruBytesIterEndArea) << "\n";
325 std::cerr << ss.str();
326 ret = resCodes::resWarn;
327 }
328
329 /* Set default language flag to true as Chassis Fru area are always
330 * encoded in English defined in Section 10 of Fru specification
331 */
332
333 bool isLangEng = true;
334 switch (area)
335 {
336 case fruAreas::fruAreaChassis:
337 {
338 result["CHASSIS_TYPE"] =
339 std::to_string(static_cast<int>(*fruBytesIter));
340 fruBytesIter += 1;
Ed Tanous07d467b2021-02-23 14:48:37 -0800341 fruAreaFieldNames = &chassisFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530342 break;
343 }
344 case fruAreas::fruAreaBoard:
345 {
346 uint8_t lang = *fruBytesIter;
347 result["BOARD_LANGUAGE_CODE"] =
348 std::to_string(static_cast<int>(lang));
349 isLangEng = checkLangEng(lang);
350 fruBytesIter += 1;
351
352 unsigned int minutes = *fruBytesIter |
353 *(fruBytesIter + 1) << 8 |
354 *(fruBytesIter + 2) << 16;
355 std::tm fruTime = intelEpoch();
356 std::time_t timeValue = std::mktime(&fruTime);
Ed Tanous3013fb42022-07-09 08:27:06 -0700357 timeValue += static_cast<long>(minutes) * 60;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530358 fruTime = *std::gmtime(&timeValue);
359
360 // Tue Nov 20 23:08:00 2018
Ed Tanous3013fb42022-07-09 08:27:06 -0700361 std::array<char, 32> timeString = {};
362 auto bytes = std::strftime(timeString.data(), timeString.size(),
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530363 "%Y-%m-%d - %H:%M:%S", &fruTime);
364 if (bytes == 0)
365 {
366 std::cerr << "invalid time string encountered\n";
367 return resCodes::resErr;
368 }
369
Ed Tanous3013fb42022-07-09 08:27:06 -0700370 result["BOARD_MANUFACTURE_DATE"] =
371 std::string_view(timeString.data(), bytes);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530372 fruBytesIter += 3;
Ed Tanous07d467b2021-02-23 14:48:37 -0800373 fruAreaFieldNames = &boardFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530374 break;
375 }
376 case fruAreas::fruAreaProduct:
377 {
378 uint8_t lang = *fruBytesIter;
379 result["PRODUCT_LANGUAGE_CODE"] =
380 std::to_string(static_cast<int>(lang));
381 isLangEng = checkLangEng(lang);
382 fruBytesIter += 1;
Ed Tanous07d467b2021-02-23 14:48:37 -0800383 fruAreaFieldNames = &productFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530384 break;
385 }
386 default:
387 {
388 std::cerr << "Internal error: unexpected FRU area index: "
389 << static_cast<int>(area) << " \n";
390 return resCodes::resErr;
391 }
392 }
393 size_t fieldIndex = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700394 DecodeState state = DecodeState::ok;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530395 do
396 {
397 auto res =
398 decodeFRUData(fruBytesIter, fruBytesIterEndArea, isLangEng);
399 state = res.first;
400 std::string value = res.second;
401 std::string name;
402 if (fieldIndex < fruAreaFieldNames->size())
403 {
404 name = std::string(getFruAreaName(area)) + "_" +
405 fruAreaFieldNames->at(fieldIndex);
406 }
Scron-Chang77987122021-03-30 20:53:52 +0800407 else
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530408 {
409 name =
410 std::string(getFruAreaName(area)) + "_" +
Ed Tanous07d467b2021-02-23 14:48:37 -0800411 fruCustomFieldName +
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530412 std::to_string(fieldIndex - fruAreaFieldNames->size() + 1);
413 }
414
415 if (state == DecodeState::ok)
416 {
417 // Strip non null characters from the end
418 value.erase(std::find_if(value.rbegin(), value.rend(),
419 [](char ch) { return ch != 0; })
420 .base(),
421 value.end());
422
423 result[name] = std::move(value);
424 ++fieldIndex;
425 }
426 else if (state == DecodeState::err)
427 {
428 std::cerr << "Error while parsing " << name << "\n";
429 ret = resCodes::resWarn;
430 // Cancel decoding if failed to parse any of mandatory
431 // fields
432 if (fieldIndex < fruAreaFieldNames->size())
433 {
434 std::cerr << "Failed to parse mandatory field \n";
435 return resCodes::resErr;
436 }
437 }
438 else
439 {
440 if (fieldIndex < fruAreaFieldNames->size())
441 {
442 std::cerr << "Mandatory fields absent in FRU area "
443 << getFruAreaName(area) << " after " << name
444 << "\n";
445 ret = resCodes::resWarn;
446 }
447 }
448 } while (state == DecodeState::ok);
449 for (; fruBytesIter < fruBytesIterEndArea; fruBytesIter++)
450 {
451 uint8_t c = *fruBytesIter;
Ed Tanous3013fb42022-07-09 08:27:06 -0700452 if (c != 0U)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530453 {
454 std::cerr << "Non-zero byte after EndOfFields in FRU area "
455 << getFruAreaName(area) << "\n";
456 ret = resCodes::resWarn;
457 break;
458 }
459 }
460 }
461
462 return ret;
463}
464
465// Calculate new checksum for fru info area
466uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
467 std::vector<uint8_t>::const_iterator end)
468{
469 constexpr int checksumMod = 256;
Andrew Jeffery499e7aa2021-08-02 22:18:22 +0930470 uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
471 return (checksumMod - sum) % checksumMod;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530472}
473
474uint8_t calculateChecksum(std::vector<uint8_t>& fruAreaData)
475{
476 return calculateChecksum(fruAreaData.begin(), fruAreaData.end());
477}
478
479// Update new fru area length &
480// Update checksum at new checksum location
481// Return the offset of the area checksum byte
482unsigned int updateFRUAreaLenAndChecksum(std::vector<uint8_t>& fruData,
483 size_t fruAreaStart,
484 size_t fruAreaEndOfFieldsOffset,
485 size_t fruAreaEndOffset)
486{
487 size_t traverseFRUAreaIndex = fruAreaEndOfFieldsOffset - fruAreaStart;
488
489 // fill zeros for any remaining unused space
490 std::fill(fruData.begin() + fruAreaEndOfFieldsOffset,
491 fruData.begin() + fruAreaEndOffset, 0);
492
493 size_t mod = traverseFRUAreaIndex % fruBlockSize;
Ed Tanous3013fb42022-07-09 08:27:06 -0700494 size_t checksumLoc = 0;
495 if (mod == 0U)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530496 {
497 traverseFRUAreaIndex += (fruBlockSize);
498 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - 1);
499 }
500 else
501 {
502 traverseFRUAreaIndex += (fruBlockSize - mod);
503 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - mod - 1);
504 }
505
Ed Tanous3013fb42022-07-09 08:27:06 -0700506 size_t newFRUAreaLen =
507 (traverseFRUAreaIndex / fruBlockSize) +
508 static_cast<unsigned long>((traverseFRUAreaIndex % fruBlockSize) != 0);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530509 size_t fruAreaLengthLoc = fruAreaStart + 1;
510 fruData[fruAreaLengthLoc] = static_cast<uint8_t>(newFRUAreaLen);
511
512 // Calculate new checksum
513 std::vector<uint8_t> finalFRUData;
514 std::copy_n(fruData.begin() + fruAreaStart, checksumLoc - fruAreaStart,
515 std::back_inserter(finalFRUData));
516
517 fruData[checksumLoc] = calculateChecksum(finalFRUData);
518 return checksumLoc;
519}
520
521ssize_t getFieldLength(uint8_t fruFieldTypeLenValue)
522{
523 constexpr uint8_t typeLenMask = 0x3F;
524 constexpr uint8_t endOfFields = 0xC1;
525 if (fruFieldTypeLenValue == endOfFields)
526 {
527 return -1;
528 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800529 return fruFieldTypeLenValue & typeLenMask;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530530}
531
532bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
533{
534 // ipmi spec format version number is currently at 1, verify it
535 if (blockData[0] != fruVersion)
536 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800537 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530538 {
539 std::cerr << "FRU spec version " << (int)(blockData[0])
540 << " not supported. Supported version is "
541 << (int)(fruVersion) << "\n";
542 }
543 return false;
544 }
545
546 // verify pad is set to 0
547 if (blockData[6] != 0x0)
548 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800549 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530550 {
551 std::cerr << "PAD value in header is non zero, value is "
552 << (int)(blockData[6]) << "\n";
553 }
554 return false;
555 }
556
557 // verify offsets are 0, or don't point to another offset
558 std::set<uint8_t> foundOffsets;
559 for (int ii = 1; ii < 6; ii++)
560 {
561 if (blockData[ii] == 0)
562 {
563 continue;
564 }
565 auto inserted = foundOffsets.insert(blockData[ii]);
566 if (!inserted.second)
567 {
568 return false;
569 }
570 }
571
572 // validate checksum
573 size_t sum = 0;
574 for (int jj = 0; jj < 7; jj++)
575 {
576 sum += blockData[jj];
577 }
578 sum = (256 - sum) & 0xFF;
579
580 if (sum != blockData[7])
581 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800582 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530583 {
584 std::cerr << "Checksum " << (int)(blockData[7])
585 << " is invalid. calculated checksum is " << (int)(sum)
586 << "\n";
587 }
588 return false;
589 }
590 return true;
591}
592
Zev Weiss309c0b12022-02-25 01:44:12 +0000593bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
Oskar Senftbd4075f2021-10-05 23:42:43 -0400594 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
Zev Weiss1525e852022-03-22 22:27:43 +0000595 off_t& baseOffset)
Oskar Senftbd4075f2021-10-05 23:42:43 -0400596{
Zev Weiss309c0b12022-02-25 01:44:12 +0000597 if (reader.read(baseOffset, 0x8, blockData.data()) < 0)
Oskar Senftbd4075f2021-10-05 23:42:43 -0400598 {
599 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030600 << baseOffset << "\n";
Oskar Senftbd4075f2021-10-05 23:42:43 -0400601 return false;
602 }
603
604 // check the header checksum
605 if (validateHeader(blockData))
606 {
607 return true;
608 }
609
610 // only continue the search if we just looked at 0x0.
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030611 if (baseOffset != 0)
612 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400613 return false;
614 }
615
616 // now check for special cases where the IPMI data is at an offset
617
618 // check if blockData starts with tyanHeader
619 const std::vector<uint8_t> tyanHeader = {'$', 'T', 'Y', 'A', 'N', '$'};
620 if (blockData.size() >= tyanHeader.size() &&
621 std::equal(tyanHeader.begin(), tyanHeader.end(), blockData.begin()))
622 {
623 // look for the FRU header at offset 0x6000
624 baseOffset = 0x6000;
Zev Weiss309c0b12022-02-25 01:44:12 +0000625 return findFRUHeader(reader, errorHelp, blockData, baseOffset);
Oskar Senftbd4075f2021-10-05 23:42:43 -0400626 }
627
628 if (debug)
629 {
630 std::cerr << "Illegal header " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030631 << baseOffset << "\n";
Oskar Senftbd4075f2021-10-05 23:42:43 -0400632 }
633
634 return false;
635}
636
Zev Weiss309c0b12022-02-25 01:44:12 +0000637std::vector<uint8_t> readFRUContents(FRUReader& reader,
Patrick Ventureab296412020-12-30 13:39:37 -0800638 const std::string& errorHelp)
639{
Ed Tanous3013fb42022-07-09 08:27:06 -0700640 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
Zev Weiss1525e852022-03-22 22:27:43 +0000641 off_t baseOffset = 0x0;
Patrick Ventureab296412020-12-30 13:39:37 -0800642
Zev Weiss309c0b12022-02-25 01:44:12 +0000643 if (!findFRUHeader(reader, errorHelp, blockData, baseOffset))
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030644 {
Patrick Ventureab296412020-12-30 13:39:37 -0800645 return {};
646 }
647
648 std::vector<uint8_t> device;
649 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
650
651 bool hasMultiRecords = false;
652 size_t fruLength = fruBlockSize; // At least FRU header is present
Vijay Khemka7792e392021-01-25 13:03:56 -0800653 unsigned int prevOffset = 0;
Patrick Ventureab296412020-12-30 13:39:37 -0800654 for (fruAreas area = fruAreas::fruAreaInternal;
655 area <= fruAreas::fruAreaMultirecord; ++area)
656 {
657 // Offset value can be 255.
658 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
659 if (areaOffset == 0)
660 {
661 continue;
662 }
663
Vijay Khemka7792e392021-01-25 13:03:56 -0800664 /* Check for offset order, as per Section 17 of FRU specification, FRU
665 * information areas are required to be in order in FRU data layout
666 * which means all offset value should be in increasing order or can be
667 * 0 if that area is not present
668 */
669 if (areaOffset <= prevOffset)
670 {
671 std::cerr << "Fru area offsets are not in required order as per "
672 "Section 17 of Fru specification\n";
673 return {};
674 }
675 prevOffset = areaOffset;
676
Patrick Ventureab296412020-12-30 13:39:37 -0800677 // MultiRecords are different. area is not tracking section, it's
678 // walking the common header.
679 if (area == fruAreas::fruAreaMultirecord)
680 {
681 hasMultiRecords = true;
682 break;
683 }
684
685 areaOffset *= fruBlockSize;
686
Zev Weiss309c0b12022-02-25 01:44:12 +0000687 if (reader.read(baseOffset + areaOffset, 0x2, blockData.data()) < 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800688 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400689 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030690 << baseOffset << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800691 return {};
692 }
693
694 // Ignore data type (blockData is already unsigned).
695 size_t length = blockData[1] * fruBlockSize;
696 areaOffset += length;
697 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
698 }
699
700 if (hasMultiRecords)
701 {
702 // device[area count] is the index to the last area because the 0th
703 // entry is not an offset in the common header.
704 unsigned int areaOffset =
705 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
706 areaOffset *= fruBlockSize;
707
708 // the multi-area record header is 5 bytes long.
709 constexpr size_t multiRecordHeaderSize = 5;
710 constexpr uint8_t multiRecordEndOfListMask = 0x80;
711
712 // Sanity hard-limit to 64KB.
713 while (areaOffset < std::numeric_limits<uint16_t>::max())
714 {
715 // In multi-area, the area offset points to the 0th record, each
716 // record has 3 bytes of the header we care about.
Zev Weiss309c0b12022-02-25 01:44:12 +0000717 if (reader.read(baseOffset + areaOffset, 0x3, blockData.data()) < 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800718 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400719 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030720 << baseOffset << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800721 return {};
722 }
723
724 // Ok, let's check the record length, which is in bytes (unsigned,
725 // up to 255, so blockData should hold uint8_t not char)
726 size_t recordLength = blockData[2];
727 areaOffset += (recordLength + multiRecordHeaderSize);
728 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
729
730 // If this is the end of the list bail.
Ed Tanous3013fb42022-07-09 08:27:06 -0700731 if ((blockData[1] & multiRecordEndOfListMask) != 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800732 {
733 break;
734 }
735 }
736 }
737
738 // You already copied these first 8 bytes (the ipmi fru header size)
739 fruLength -= std::min(fruBlockSize, fruLength);
740
741 int readOffset = fruBlockSize;
742
743 while (fruLength > 0)
744 {
745 size_t requestLength =
746 std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
747
Zev Weiss309c0b12022-02-25 01:44:12 +0000748 if (reader.read(baseOffset + readOffset, requestLength,
749 blockData.data()) < 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800750 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400751 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030752 << baseOffset << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800753 return {};
754 }
755
756 device.insert(device.end(), blockData.begin(),
757 blockData.begin() + requestLength);
758
759 readOffset += requestLength;
760 fruLength -= std::min(requestLength, fruLength);
761 }
762
763 return device;
764}
765
766unsigned int getHeaderAreaFieldOffset(fruAreas area)
767{
768 return static_cast<unsigned int>(area) + 1;
769}
Kumar Thangavel7135f3d2022-02-04 12:14:24 +0530770
771std::vector<uint8_t>& getFRUInfo(const uint8_t& bus, const uint8_t& address)
772{
773 auto deviceMap = busMap.find(bus);
774 if (deviceMap == busMap.end())
775 {
776 throw std::invalid_argument("Invalid Bus.");
777 }
778 auto device = deviceMap->second->find(address);
779 if (device == deviceMap->second->end())
780 {
781 throw std::invalid_argument("Invalid Address.");
782 }
783 std::vector<uint8_t>& ret = device->second;
784
785 return ret;
786}
Kumar Thangavelbdfc5ec2022-08-29 22:23:00 +0530787
788// Iterate FruArea Names and find start and size of the fru area that contains
789// the propertyName and the field start location for the property. fruAreaParams
790// struct values fruAreaStart, fruAreaSize, fruAreaEnd, fieldLoc values gets
791// updated/returned if successful.
792
793bool findFruAreaLocationAndField(std::vector<uint8_t>& fruData,
794 const std::string& propertyName,
795 struct FruArea& fruAreaParams,
796 size_t& fruDataIter)
797{
798 const std::vector<std::string>* fruAreaFieldNames = nullptr;
799
800 uint8_t fruAreaOffsetFieldValue = 0;
801 size_t offset = 0;
802 std::string areaName = propertyName.substr(0, propertyName.find('_'));
803 std::string propertyNamePrefix = areaName + "_";
804 auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
805 if (it == fruAreaNames.end())
806 {
807 std::cerr << "Can't parse area name for property " << propertyName
808 << " \n";
809 return false;
810 }
811 fruAreas fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
812 fruAreaOffsetFieldValue =
813 fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
814 switch (fruAreaToUpdate)
815 {
816 case fruAreas::fruAreaChassis:
817 offset = 3; // chassis part number offset. Skip fixed first 3 bytes
818 fruAreaFieldNames = &chassisFruAreas;
819 break;
820 case fruAreas::fruAreaBoard:
821 offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
822 fruAreaFieldNames = &boardFruAreas;
823 break;
824 case fruAreas::fruAreaProduct:
825 // Manufacturer name offset. Skip fixed first 3 product fru bytes
826 // i.e. version, area length and language code
827 offset = 3;
828 fruAreaFieldNames = &productFruAreas;
829 break;
830 default:
831 std::cerr << "Invalid PropertyName " << propertyName << " \n";
832 return false;
833 }
834 if (fruAreaOffsetFieldValue == 0)
835 {
836 std::cerr << "FRU Area for " << propertyName << " not present \n";
837 return false;
838 }
839
840 fruAreaParams.start = fruAreaOffsetFieldValue * fruBlockSize;
841 fruAreaParams.size = fruData[fruAreaParams.start + 1] * fruBlockSize;
842 fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
843 fruDataIter = fruAreaParams.start + offset;
844 size_t skipToFRUUpdateField = 0;
845 ssize_t fieldLength = 0;
846
847 bool found = false;
848 for (const auto& field : *fruAreaFieldNames)
849 {
850 skipToFRUUpdateField++;
851 if (propertyName == propertyNamePrefix + field)
852 {
853 found = true;
854 break;
855 }
856 }
857 if (!found)
858 {
859 std::size_t pos = propertyName.find(fruCustomFieldName);
860 if (pos == std::string::npos)
861 {
862 std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
863 << propertyName << "\n";
864 return false;
865 }
866 std::string fieldNumStr =
867 propertyName.substr(pos + fruCustomFieldName.length());
868 size_t fieldNum = std::stoi(fieldNumStr);
869 if (fieldNum == 0)
870 {
871 std::cerr << "PropertyName not recognized: " << propertyName
872 << "\n";
873 return false;
874 }
875 skipToFRUUpdateField += fieldNum;
876 }
877
878 for (size_t i = 1; i < skipToFRUUpdateField; i++)
879 {
880 if (fruDataIter < fruData.size())
881 {
882 fieldLength = getFieldLength(fruData[fruDataIter]);
883
884 if (fieldLength < 0)
885 {
886 break;
887 }
888 fruDataIter += 1 + fieldLength;
889 }
890 }
891 fruAreaParams.updateFieldLoc = fruDataIter;
892
893 return true;
894}