blob: 808179f33548745be9835942207cd357c731e6a0 [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{
Vijay Khemka06d1b4a2021-02-09 18:39:11 +0000197 unsigned int fruBytesSize = fruBytes.size();
198
199 // check if Fru data has at least 8 byte header
200 if (fruBytesSize <= fruBlockSize)
201 {
202 std::cerr << "Error: trying to parse empty FRU\n";
203 return false;
204 }
205
206 // Check range of passed currentArea value
207 if (currentArea > fruAreas::fruAreaMultirecord)
208 {
209 std::cerr << "Error: Fru area is out of range\n";
210 return false;
211 }
212
213 unsigned int currentAreaIndex = getHeaderAreaFieldOffset(currentArea);
214 if (currentAreaIndex > fruBytesSize)
215 {
216 std::cerr << "Error: Fru area index is out of range\n";
217 return false;
218 }
219
220 unsigned int start = fruBytes[currentAreaIndex];
221 unsigned int end = start + len;
222
223 /* Verify each offset within the range of start and end */
224 for (fruAreas area = fruAreas::fruAreaInternal;
225 area <= fruAreas::fruAreaMultirecord; ++area)
226 {
227 // skip the current offset
228 if (area == currentArea)
229 {
230 continue;
231 }
232
233 unsigned int areaIndex = getHeaderAreaFieldOffset(area);
234 if (areaIndex > fruBytesSize)
235 {
236 std::cerr << "Error: Fru area index is out of range\n";
237 return false;
238 }
239
240 unsigned int areaOffset = fruBytes[areaIndex];
241 // if areaOffset is 0 means this area is not available so skip
242 if (areaOffset == 0)
243 {
244 continue;
245 }
246
247 // check for overlapping of current offset with given areaoffset
248 if (areaOffset == start || (areaOffset > start && areaOffset < end))
249 {
250 std::cerr << getFruAreaName(currentArea)
251 << " offset is overlapping with " << getFruAreaName(area)
252 << " offset\n";
253 return false;
254 }
255 }
256 return true;
257}
258
Michael Shen0961b112022-02-22 11:06:33 +0800259resCodes
260 formatIPMIFRU(const std::vector<uint8_t>& fruBytes,
261 boost::container::flat_map<std::string, std::string>& result)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530262{
263 resCodes ret = resCodes::resOK;
264 if (fruBytes.size() <= fruBlockSize)
265 {
266 std::cerr << "Error: trying to parse empty FRU \n";
267 return resCodes::resErr;
268 }
269 result["Common_Format_Version"] =
270 std::to_string(static_cast<int>(*fruBytes.begin()));
271
Ed Tanous3013fb42022-07-09 08:27:06 -0700272 const std::vector<std::string>* fruAreaFieldNames = nullptr;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530273
274 // Don't parse Internal and Multirecord areas
275 for (fruAreas area = fruAreas::fruAreaChassis;
276 area <= fruAreas::fruAreaProduct; ++area)
277 {
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530278 size_t offset = *(fruBytes.begin() + getHeaderAreaFieldOffset(area));
279 if (offset == 0)
280 {
281 continue;
282 }
283 offset *= fruBlockSize;
Patrick Williamsdf190612023-05-10 07:51:34 -0500284 std::vector<uint8_t>::const_iterator fruBytesIter = fruBytes.begin() +
285 offset;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530286 if (fruBytesIter + fruBlockSize >= fruBytes.end())
287 {
288 std::cerr << "Not enough data to parse \n";
289 return resCodes::resErr;
290 }
291 // check for format version 1
292 if (*fruBytesIter != 0x01)
293 {
294 std::cerr << "Unexpected version " << *fruBytesIter << "\n";
295 return resCodes::resErr;
296 }
297 ++fruBytesIter;
298
299 /* Verify other area offset for overlap with current area by passing
300 * length of current area offset pointed by *fruBytesIter
301 */
302 if (!verifyOffset(fruBytes, area, *fruBytesIter))
303 {
304 return resCodes::resErr;
305 }
306
Andrew Jeffery65ed6642021-08-02 22:32:23 +0930307 size_t fruAreaSize = *fruBytesIter * fruBlockSize;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530308 std::vector<uint8_t>::const_iterator fruBytesIterEndArea =
309 fruBytes.begin() + offset + fruAreaSize - 1;
310 ++fruBytesIter;
311
312 uint8_t fruComputedChecksum =
313 calculateChecksum(fruBytes.begin() + offset, fruBytesIterEndArea);
314 if (fruComputedChecksum != *fruBytesIterEndArea)
315 {
316 std::stringstream ss;
317 ss << std::hex << std::setfill('0');
318 ss << "Checksum error in FRU area " << getFruAreaName(area) << "\n";
319 ss << "\tComputed checksum: 0x" << std::setw(2)
320 << static_cast<int>(fruComputedChecksum) << "\n";
321 ss << "\tThe read checksum: 0x" << std::setw(2)
322 << static_cast<int>(*fruBytesIterEndArea) << "\n";
323 std::cerr << ss.str();
324 ret = resCodes::resWarn;
325 }
326
327 /* Set default language flag to true as Chassis Fru area are always
328 * encoded in English defined in Section 10 of Fru specification
329 */
330
331 bool isLangEng = true;
332 switch (area)
333 {
334 case fruAreas::fruAreaChassis:
335 {
336 result["CHASSIS_TYPE"] =
337 std::to_string(static_cast<int>(*fruBytesIter));
338 fruBytesIter += 1;
Ed Tanous07d467b2021-02-23 14:48:37 -0800339 fruAreaFieldNames = &chassisFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530340 break;
341 }
342 case fruAreas::fruAreaBoard:
343 {
344 uint8_t lang = *fruBytesIter;
345 result["BOARD_LANGUAGE_CODE"] =
346 std::to_string(static_cast<int>(lang));
347 isLangEng = checkLangEng(lang);
348 fruBytesIter += 1;
349
350 unsigned int minutes = *fruBytesIter |
351 *(fruBytesIter + 1) << 8 |
352 *(fruBytesIter + 2) << 16;
353 std::tm fruTime = intelEpoch();
354 std::time_t timeValue = std::mktime(&fruTime);
Ed Tanous3013fb42022-07-09 08:27:06 -0700355 timeValue += static_cast<long>(minutes) * 60;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530356 fruTime = *std::gmtime(&timeValue);
357
358 // Tue Nov 20 23:08:00 2018
Ed Tanous3013fb42022-07-09 08:27:06 -0700359 std::array<char, 32> timeString = {};
360 auto bytes = std::strftime(timeString.data(), timeString.size(),
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530361 "%Y-%m-%d - %H:%M:%S", &fruTime);
362 if (bytes == 0)
363 {
364 std::cerr << "invalid time string encountered\n";
365 return resCodes::resErr;
366 }
367
Ed Tanous3013fb42022-07-09 08:27:06 -0700368 result["BOARD_MANUFACTURE_DATE"] =
369 std::string_view(timeString.data(), bytes);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530370 fruBytesIter += 3;
Ed Tanous07d467b2021-02-23 14:48:37 -0800371 fruAreaFieldNames = &boardFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530372 break;
373 }
374 case fruAreas::fruAreaProduct:
375 {
376 uint8_t lang = *fruBytesIter;
377 result["PRODUCT_LANGUAGE_CODE"] =
378 std::to_string(static_cast<int>(lang));
379 isLangEng = checkLangEng(lang);
380 fruBytesIter += 1;
Ed Tanous07d467b2021-02-23 14:48:37 -0800381 fruAreaFieldNames = &productFruAreas;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530382 break;
383 }
384 default:
385 {
386 std::cerr << "Internal error: unexpected FRU area index: "
387 << static_cast<int>(area) << " \n";
388 return resCodes::resErr;
389 }
390 }
391 size_t fieldIndex = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700392 DecodeState state = DecodeState::ok;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530393 do
394 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500395 auto res = decodeFRUData(fruBytesIter, fruBytesIterEndArea,
396 isLangEng);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530397 state = res.first;
398 std::string value = res.second;
399 std::string name;
400 if (fieldIndex < fruAreaFieldNames->size())
401 {
402 name = std::string(getFruAreaName(area)) + "_" +
403 fruAreaFieldNames->at(fieldIndex);
404 }
Scron-Chang77987122021-03-30 20:53:52 +0800405 else
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530406 {
407 name =
408 std::string(getFruAreaName(area)) + "_" +
Ed Tanous07d467b2021-02-23 14:48:37 -0800409 fruCustomFieldName +
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530410 std::to_string(fieldIndex - fruAreaFieldNames->size() + 1);
411 }
412
413 if (state == DecodeState::ok)
414 {
415 // Strip non null characters from the end
416 value.erase(std::find_if(value.rbegin(), value.rend(),
417 [](char ch) { return ch != 0; })
418 .base(),
419 value.end());
420
421 result[name] = std::move(value);
422 ++fieldIndex;
423 }
424 else if (state == DecodeState::err)
425 {
426 std::cerr << "Error while parsing " << name << "\n";
427 ret = resCodes::resWarn;
428 // Cancel decoding if failed to parse any of mandatory
429 // fields
430 if (fieldIndex < fruAreaFieldNames->size())
431 {
432 std::cerr << "Failed to parse mandatory field \n";
433 return resCodes::resErr;
434 }
435 }
436 else
437 {
438 if (fieldIndex < fruAreaFieldNames->size())
439 {
440 std::cerr << "Mandatory fields absent in FRU area "
441 << getFruAreaName(area) << " after " << name
442 << "\n";
443 ret = resCodes::resWarn;
444 }
445 }
446 } while (state == DecodeState::ok);
447 for (; fruBytesIter < fruBytesIterEndArea; fruBytesIter++)
448 {
449 uint8_t c = *fruBytesIter;
Ed Tanous3013fb42022-07-09 08:27:06 -0700450 if (c != 0U)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530451 {
452 std::cerr << "Non-zero byte after EndOfFields in FRU area "
453 << getFruAreaName(area) << "\n";
454 ret = resCodes::resWarn;
455 break;
456 }
457 }
458 }
459
460 return ret;
461}
462
463// Calculate new checksum for fru info area
464uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
465 std::vector<uint8_t>::const_iterator end)
466{
467 constexpr int checksumMod = 256;
Andrew Jeffery499e7aa2021-08-02 22:18:22 +0930468 uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
469 return (checksumMod - sum) % checksumMod;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530470}
471
472uint8_t calculateChecksum(std::vector<uint8_t>& fruAreaData)
473{
474 return calculateChecksum(fruAreaData.begin(), fruAreaData.end());
475}
476
477// Update new fru area length &
478// Update checksum at new checksum location
479// Return the offset of the area checksum byte
480unsigned int updateFRUAreaLenAndChecksum(std::vector<uint8_t>& fruData,
481 size_t fruAreaStart,
482 size_t fruAreaEndOfFieldsOffset,
483 size_t fruAreaEndOffset)
484{
485 size_t traverseFRUAreaIndex = fruAreaEndOfFieldsOffset - fruAreaStart;
486
487 // fill zeros for any remaining unused space
488 std::fill(fruData.begin() + fruAreaEndOfFieldsOffset,
489 fruData.begin() + fruAreaEndOffset, 0);
490
491 size_t mod = traverseFRUAreaIndex % fruBlockSize;
Ed Tanous3013fb42022-07-09 08:27:06 -0700492 size_t checksumLoc = 0;
493 if (mod == 0U)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530494 {
495 traverseFRUAreaIndex += (fruBlockSize);
496 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - 1);
497 }
498 else
499 {
500 traverseFRUAreaIndex += (fruBlockSize - mod);
501 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - mod - 1);
502 }
503
Ed Tanous3013fb42022-07-09 08:27:06 -0700504 size_t newFRUAreaLen =
505 (traverseFRUAreaIndex / fruBlockSize) +
506 static_cast<unsigned long>((traverseFRUAreaIndex % fruBlockSize) != 0);
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530507 size_t fruAreaLengthLoc = fruAreaStart + 1;
508 fruData[fruAreaLengthLoc] = static_cast<uint8_t>(newFRUAreaLen);
509
510 // Calculate new checksum
511 std::vector<uint8_t> finalFRUData;
512 std::copy_n(fruData.begin() + fruAreaStart, checksumLoc - fruAreaStart,
513 std::back_inserter(finalFRUData));
514
515 fruData[checksumLoc] = calculateChecksum(finalFRUData);
516 return checksumLoc;
517}
518
519ssize_t getFieldLength(uint8_t fruFieldTypeLenValue)
520{
521 constexpr uint8_t typeLenMask = 0x3F;
522 constexpr uint8_t endOfFields = 0xC1;
523 if (fruFieldTypeLenValue == endOfFields)
524 {
525 return -1;
526 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800527 return fruFieldTypeLenValue & typeLenMask;
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530528}
529
530bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
531{
532 // ipmi spec format version number is currently at 1, verify it
533 if (blockData[0] != fruVersion)
534 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800535 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530536 {
537 std::cerr << "FRU spec version " << (int)(blockData[0])
538 << " not supported. Supported version is "
539 << (int)(fruVersion) << "\n";
540 }
541 return false;
542 }
543
544 // verify pad is set to 0
545 if (blockData[6] != 0x0)
546 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800547 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530548 {
549 std::cerr << "PAD value in header is non zero, value is "
550 << (int)(blockData[6]) << "\n";
551 }
552 return false;
553 }
554
555 // verify offsets are 0, or don't point to another offset
556 std::set<uint8_t> foundOffsets;
557 for (int ii = 1; ii < 6; ii++)
558 {
559 if (blockData[ii] == 0)
560 {
561 continue;
562 }
563 auto inserted = foundOffsets.insert(blockData[ii]);
564 if (!inserted.second)
565 {
566 return false;
567 }
568 }
569
570 // validate checksum
571 size_t sum = 0;
572 for (int jj = 0; jj < 7; jj++)
573 {
574 sum += blockData[jj];
575 }
576 sum = (256 - sum) & 0xFF;
577
578 if (sum != blockData[7])
579 {
Ed Tanous07d467b2021-02-23 14:48:37 -0800580 if (debug)
Kumar Thangavelc8dc4af2021-01-12 10:36:38 +0530581 {
582 std::cerr << "Checksum " << (int)(blockData[7])
583 << " is invalid. calculated checksum is " << (int)(sum)
584 << "\n";
585 }
586 return false;
587 }
588 return true;
589}
590
Zev Weiss309c0b12022-02-25 01:44:12 +0000591bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
Oskar Senftbd4075f2021-10-05 23:42:43 -0400592 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
Zev Weiss1525e852022-03-22 22:27:43 +0000593 off_t& baseOffset)
Oskar Senftbd4075f2021-10-05 23:42:43 -0400594{
Zev Weiss309c0b12022-02-25 01:44:12 +0000595 if (reader.read(baseOffset, 0x8, blockData.data()) < 0)
Oskar Senftbd4075f2021-10-05 23:42:43 -0400596 {
597 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030598 << baseOffset << "\n";
Oskar Senftbd4075f2021-10-05 23:42:43 -0400599 return false;
600 }
601
602 // check the header checksum
603 if (validateHeader(blockData))
604 {
605 return true;
606 }
607
608 // only continue the search if we just looked at 0x0.
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030609 if (baseOffset != 0)
610 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400611 return false;
612 }
613
614 // now check for special cases where the IPMI data is at an offset
615
616 // check if blockData starts with tyanHeader
617 const std::vector<uint8_t> tyanHeader = {'$', 'T', 'Y', 'A', 'N', '$'};
618 if (blockData.size() >= tyanHeader.size() &&
619 std::equal(tyanHeader.begin(), tyanHeader.end(), blockData.begin()))
620 {
621 // look for the FRU header at offset 0x6000
622 baseOffset = 0x6000;
Zev Weiss309c0b12022-02-25 01:44:12 +0000623 return findFRUHeader(reader, errorHelp, blockData, baseOffset);
Oskar Senftbd4075f2021-10-05 23:42:43 -0400624 }
625
626 if (debug)
627 {
628 std::cerr << "Illegal header " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030629 << baseOffset << "\n";
Oskar Senftbd4075f2021-10-05 23:42:43 -0400630 }
631
632 return false;
633}
634
Zev Weiss309c0b12022-02-25 01:44:12 +0000635std::vector<uint8_t> readFRUContents(FRUReader& reader,
Patrick Ventureab296412020-12-30 13:39:37 -0800636 const std::string& errorHelp)
637{
Ed Tanous3013fb42022-07-09 08:27:06 -0700638 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};
Zev Weiss1525e852022-03-22 22:27:43 +0000639 off_t baseOffset = 0x0;
Patrick Ventureab296412020-12-30 13:39:37 -0800640
Zev Weiss309c0b12022-02-25 01:44:12 +0000641 if (!findFRUHeader(reader, errorHelp, blockData, baseOffset))
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030642 {
Patrick Ventureab296412020-12-30 13:39:37 -0800643 return {};
644 }
645
646 std::vector<uint8_t> device;
647 device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
648
649 bool hasMultiRecords = false;
650 size_t fruLength = fruBlockSize; // At least FRU header is present
Vijay Khemka7792e392021-01-25 13:03:56 -0800651 unsigned int prevOffset = 0;
Patrick Ventureab296412020-12-30 13:39:37 -0800652 for (fruAreas area = fruAreas::fruAreaInternal;
653 area <= fruAreas::fruAreaMultirecord; ++area)
654 {
655 // Offset value can be 255.
656 unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
657 if (areaOffset == 0)
658 {
659 continue;
660 }
661
Vijay Khemka7792e392021-01-25 13:03:56 -0800662 /* Check for offset order, as per Section 17 of FRU specification, FRU
663 * information areas are required to be in order in FRU data layout
664 * which means all offset value should be in increasing order or can be
665 * 0 if that area is not present
666 */
667 if (areaOffset <= prevOffset)
668 {
669 std::cerr << "Fru area offsets are not in required order as per "
670 "Section 17 of Fru specification\n";
671 return {};
672 }
673 prevOffset = areaOffset;
674
Patrick Ventureab296412020-12-30 13:39:37 -0800675 // MultiRecords are different. area is not tracking section, it's
676 // walking the common header.
677 if (area == fruAreas::fruAreaMultirecord)
678 {
679 hasMultiRecords = true;
680 break;
681 }
682
683 areaOffset *= fruBlockSize;
684
Zev Weiss309c0b12022-02-25 01:44:12 +0000685 if (reader.read(baseOffset + areaOffset, 0x2, blockData.data()) < 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800686 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400687 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030688 << baseOffset << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800689 return {};
690 }
691
692 // Ignore data type (blockData is already unsigned).
693 size_t length = blockData[1] * fruBlockSize;
694 areaOffset += length;
695 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
696 }
697
698 if (hasMultiRecords)
699 {
700 // device[area count] is the index to the last area because the 0th
701 // entry is not an offset in the common header.
702 unsigned int areaOffset =
703 device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
704 areaOffset *= fruBlockSize;
705
706 // the multi-area record header is 5 bytes long.
707 constexpr size_t multiRecordHeaderSize = 5;
708 constexpr uint8_t multiRecordEndOfListMask = 0x80;
709
710 // Sanity hard-limit to 64KB.
711 while (areaOffset < std::numeric_limits<uint16_t>::max())
712 {
713 // In multi-area, the area offset points to the 0th record, each
714 // record has 3 bytes of the header we care about.
Zev Weiss309c0b12022-02-25 01:44:12 +0000715 if (reader.read(baseOffset + areaOffset, 0x3, blockData.data()) < 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800716 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400717 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030718 << baseOffset << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800719 return {};
720 }
721
722 // Ok, let's check the record length, which is in bytes (unsigned,
723 // up to 255, so blockData should hold uint8_t not char)
724 size_t recordLength = blockData[2];
725 areaOffset += (recordLength + multiRecordHeaderSize);
726 fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
727
728 // If this is the end of the list bail.
Ed Tanous3013fb42022-07-09 08:27:06 -0700729 if ((blockData[1] & multiRecordEndOfListMask) != 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800730 {
731 break;
732 }
733 }
734 }
735
736 // You already copied these first 8 bytes (the ipmi fru header size)
737 fruLength -= std::min(fruBlockSize, fruLength);
738
739 int readOffset = fruBlockSize;
740
741 while (fruLength > 0)
742 {
743 size_t requestLength =
744 std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
745
Zev Weiss309c0b12022-02-25 01:44:12 +0000746 if (reader.read(baseOffset + readOffset, requestLength,
747 blockData.data()) < 0)
Patrick Ventureab296412020-12-30 13:39:37 -0800748 {
Oskar Senftbd4075f2021-10-05 23:42:43 -0400749 std::cerr << "failed to read " << errorHelp << " base offset "
Andrew Jefferyf8ae2ba2022-03-25 15:13:55 +1030750 << baseOffset << "\n";
Patrick Ventureab296412020-12-30 13:39:37 -0800751 return {};
752 }
753
754 device.insert(device.end(), blockData.begin(),
755 blockData.begin() + requestLength);
756
757 readOffset += requestLength;
758 fruLength -= std::min(requestLength, fruLength);
759 }
760
761 return device;
762}
763
764unsigned int getHeaderAreaFieldOffset(fruAreas area)
765{
766 return static_cast<unsigned int>(area) + 1;
767}
Kumar Thangavel7135f3d2022-02-04 12:14:24 +0530768
krishnar4213ee212022-11-11 15:39:30 +0530769std::vector<uint8_t>& getFRUInfo(const uint16_t& bus, const uint8_t& address)
Kumar Thangavel7135f3d2022-02-04 12:14:24 +0530770{
771 auto deviceMap = busMap.find(bus);
772 if (deviceMap == busMap.end())
773 {
774 throw std::invalid_argument("Invalid Bus.");
775 }
776 auto device = deviceMap->second->find(address);
777 if (device == deviceMap->second->end())
778 {
779 throw std::invalid_argument("Invalid Address.");
780 }
781 std::vector<uint8_t>& ret = device->second;
782
783 return ret;
784}
Kumar Thangavelbdfc5ec2022-08-29 22:23:00 +0530785
786// Iterate FruArea Names and find start and size of the fru area that contains
787// the propertyName and the field start location for the property. fruAreaParams
788// struct values fruAreaStart, fruAreaSize, fruAreaEnd, fieldLoc values gets
789// updated/returned if successful.
790
791bool findFruAreaLocationAndField(std::vector<uint8_t>& fruData,
792 const std::string& propertyName,
Kumar Thangavel51b557b2022-09-13 13:40:47 +0530793 struct FruArea& fruAreaParams)
Kumar Thangavelbdfc5ec2022-08-29 22:23:00 +0530794{
795 const std::vector<std::string>* fruAreaFieldNames = nullptr;
796
797 uint8_t fruAreaOffsetFieldValue = 0;
798 size_t offset = 0;
799 std::string areaName = propertyName.substr(0, propertyName.find('_'));
800 std::string propertyNamePrefix = areaName + "_";
801 auto it = std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
802 if (it == fruAreaNames.end())
803 {
804 std::cerr << "Can't parse area name for property " << propertyName
805 << " \n";
806 return false;
807 }
808 fruAreas fruAreaToUpdate = static_cast<fruAreas>(it - fruAreaNames.begin());
809 fruAreaOffsetFieldValue =
810 fruData[getHeaderAreaFieldOffset(fruAreaToUpdate)];
811 switch (fruAreaToUpdate)
812 {
813 case fruAreas::fruAreaChassis:
814 offset = 3; // chassis part number offset. Skip fixed first 3 bytes
815 fruAreaFieldNames = &chassisFruAreas;
816 break;
817 case fruAreas::fruAreaBoard:
818 offset = 6; // board manufacturer offset. Skip fixed first 6 bytes
819 fruAreaFieldNames = &boardFruAreas;
820 break;
821 case fruAreas::fruAreaProduct:
822 // Manufacturer name offset. Skip fixed first 3 product fru bytes
823 // i.e. version, area length and language code
824 offset = 3;
825 fruAreaFieldNames = &productFruAreas;
826 break;
827 default:
828 std::cerr << "Invalid PropertyName " << propertyName << " \n";
829 return false;
830 }
831 if (fruAreaOffsetFieldValue == 0)
832 {
833 std::cerr << "FRU Area for " << propertyName << " not present \n";
834 return false;
835 }
836
837 fruAreaParams.start = fruAreaOffsetFieldValue * fruBlockSize;
838 fruAreaParams.size = fruData[fruAreaParams.start + 1] * fruBlockSize;
839 fruAreaParams.end = fruAreaParams.start + fruAreaParams.size;
Kumar Thangavel51b557b2022-09-13 13:40:47 +0530840 size_t fruDataIter = fruAreaParams.start + offset;
Kumar Thangavelbdfc5ec2022-08-29 22:23:00 +0530841 size_t skipToFRUUpdateField = 0;
842 ssize_t fieldLength = 0;
843
844 bool found = false;
845 for (const auto& field : *fruAreaFieldNames)
846 {
847 skipToFRUUpdateField++;
848 if (propertyName == propertyNamePrefix + field)
849 {
850 found = true;
851 break;
852 }
853 }
854 if (!found)
855 {
856 std::size_t pos = propertyName.find(fruCustomFieldName);
857 if (pos == std::string::npos)
858 {
859 std::cerr << "PropertyName doesn't exist in FRU Area Vectors: "
860 << propertyName << "\n";
861 return false;
862 }
863 std::string fieldNumStr =
864 propertyName.substr(pos + fruCustomFieldName.length());
865 size_t fieldNum = std::stoi(fieldNumStr);
866 if (fieldNum == 0)
867 {
868 std::cerr << "PropertyName not recognized: " << propertyName
869 << "\n";
870 return false;
871 }
872 skipToFRUUpdateField += fieldNum;
873 }
874
875 for (size_t i = 1; i < skipToFRUUpdateField; i++)
876 {
877 if (fruDataIter < fruData.size())
878 {
879 fieldLength = getFieldLength(fruData[fruDataIter]);
880
881 if (fieldLength < 0)
882 {
883 break;
884 }
885 fruDataIter += 1 + fieldLength;
886 }
887 }
888 fruAreaParams.updateFieldLoc = fruDataIter;
889
890 return true;
891}
Kumar Thangavel51b557b2022-09-13 13:40:47 +0530892
893// Copy the FRU Area fields and properties into restFRUAreaFieldsData vector.
894// Return true for success and false for failure.
895
896bool copyRestFRUArea(std::vector<uint8_t>& fruData,
897 const std::string& propertyName,
898 struct FruArea& fruAreaParams,
899 std::vector<uint8_t>& restFRUAreaFieldsData)
900{
901 size_t fieldLoc = fruAreaParams.updateFieldLoc;
902 size_t start = fruAreaParams.start;
903 size_t fruAreaSize = fruAreaParams.size;
904
905 // Push post update fru field bytes to a vector
906 ssize_t fieldLength = getFieldLength(fruData[fieldLoc]);
907 if (fieldLength < 0)
908 {
909 std::cerr << "Property " << propertyName << " not present \n";
910 return false;
911 }
912
913 size_t fruDataIter = 0;
914 fruDataIter = fieldLoc;
915 fruDataIter += 1 + fieldLength;
916 size_t restFRUFieldsLoc = fruDataIter;
917 size_t endOfFieldsLoc = 0;
918
919 if (fruDataIter < fruData.size())
920 {
921 while ((fieldLength = getFieldLength(fruData[fruDataIter])) >= 0)
922 {
923 if (fruDataIter >= (start + fruAreaSize))
924 {
925 fruDataIter = start + fruAreaSize;
926 break;
927 }
928 fruDataIter += 1 + fieldLength;
929 }
930 endOfFieldsLoc = fruDataIter;
931 }
932
933 std::copy_n(fruData.begin() + restFRUFieldsLoc,
934 endOfFieldsLoc - restFRUFieldsLoc + 1,
935 std::back_inserter(restFRUAreaFieldsData));
936
937 fruAreaParams.restFieldsLoc = restFRUFieldsLoc;
938 fruAreaParams.restFieldsEnd = endOfFieldsLoc;
939
940 return true;
941}
Kumar Thangaveld79d0252022-08-24 14:26:01 +0530942
943// Get all device dbus path and match path with product name using
944// regular expression and find the device index for all devices.
945
946std::optional<int> findIndexForFRU(
947 boost::container::flat_map<
948 std::pair<size_t, size_t>,
949 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap,
950 std::string& productName)
951{
Kumar Thangaveld79d0252022-08-24 14:26:01 +0530952 int highest = -1;
953 bool found = false;
954
Patrick Williamsdf190612023-05-10 07:51:34 -0500955 for (const auto& busIface : dbusInterfaceMap)
Kumar Thangaveld79d0252022-08-24 14:26:01 +0530956 {
957 std::string path = busIface.second->get_object_path();
958 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$")))
959 {
Kumar Thangaveld79d0252022-08-24 14:26:01 +0530960 // Check if the match named has extra information.
961 found = true;
962 std::smatch baseMatch;
963
964 bool match = std::regex_match(path, baseMatch,
965 std::regex(productName + "_(\\d+)$"));
966 if (match)
967 {
968 if (baseMatch.size() == 2)
969 {
970 std::ssub_match baseSubMatch = baseMatch[1];
971 std::string base = baseSubMatch.str();
972
973 int value = std::stoi(base);
974 highest = (value > highest) ? value : highest;
975 }
976 }
977 }
978 } // end searching objects
979
980 if (!found)
981 {
982 return std::nullopt;
983 }
984 return highest;
985}
Kumar Thangavel9f2162a2022-08-10 18:05:20 +0530986
987// This function does format fru data as per IPMI format and find the
988// productName in the formatted fru data, get that productName and return
989// productName if found or return NULL.
990
991std::optional<std::string> getProductName(
992 std::vector<uint8_t>& device,
993 boost::container::flat_map<std::string, std::string>& formattedFRU,
994 uint32_t bus, uint32_t address, size_t& unknownBusObjectCount)
995{
996 std::string productName;
997
998 resCodes res = formatIPMIFRU(device, formattedFRU);
999 if (res == resCodes::resErr)
1000 {
1001 std::cerr << "failed to parse FRU for device at bus " << bus
1002 << " address " << address << "\n";
1003 return std::nullopt;
1004 }
1005 if (res == resCodes::resWarn)
1006 {
1007 std::cerr << "Warnings while parsing FRU for device at bus " << bus
1008 << " address " << address << "\n";
1009 }
1010
1011 auto productNameFind = formattedFRU.find("BOARD_PRODUCT_NAME");
1012 // Not found under Board section or an empty string.
1013 if (productNameFind == formattedFRU.end() ||
1014 productNameFind->second.empty())
1015 {
1016 productNameFind = formattedFRU.find("PRODUCT_PRODUCT_NAME");
1017 }
1018 // Found under Product section and not an empty string.
1019 if (productNameFind != formattedFRU.end() &&
1020 !productNameFind->second.empty())
1021 {
1022 productName = productNameFind->second;
1023 std::regex illegalObject("[^A-Za-z0-9_]");
1024 productName = std::regex_replace(productName, illegalObject, "_");
1025 }
1026 else
1027 {
1028 productName = "UNKNOWN" + std::to_string(unknownBusObjectCount);
1029 unknownBusObjectCount++;
1030 }
1031 return productName;
1032}
Kumar Thangavel9d6f5902022-08-26 16:52:14 +05301033
1034bool getFruData(std::vector<uint8_t>& fruData, uint32_t bus, uint32_t address)
1035{
1036 try
1037 {
1038 fruData = getFRUInfo(static_cast<uint16_t>(bus),
1039 static_cast<uint8_t>(address));
1040 }
1041 catch (const std::invalid_argument& e)
1042 {
1043 std::cerr << "Failure getting FRU Info" << e.what() << "\n";
1044 return false;
1045 }
1046
1047 return !fruData.empty();
1048}