1 /* 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 */ 16 /// \file fru_utils.cpp 17 18 #include "fru_utils.hpp" 19 20 #include <array> 21 #include <cstddef> 22 #include <cstdint> 23 #include <filesystem> 24 #include <iostream> 25 #include <numeric> 26 #include <set> 27 #include <string> 28 #include <vector> 29 30 extern "C" 31 { 32 // Include for I2C_SMBUS_BLOCK_MAX 33 #include <linux/i2c.h> 34 } 35 36 static constexpr bool debug = false; 37 constexpr size_t fruVersion = 1; // Current FRU spec version number is 1 38 39 std::tm intelEpoch(void) 40 { 41 std::tm val = {}; 42 val.tm_year = 1996 - 1900; 43 val.tm_mday = 1; 44 return val; 45 } 46 47 char sixBitToChar(uint8_t val) 48 { 49 return static_cast<char>((val & 0x3f) + ' '); 50 } 51 52 char bcdPlusToChar(uint8_t val) 53 { 54 val &= 0xf; 55 return (val < 10) ? static_cast<char>(val + '0') : bcdHighChars[val - 10]; 56 } 57 58 enum 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 */ 73 std::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; 79 unsigned int i = 0; 80 81 /* we need at least one byte to decode the type/len header */ 82 if (iter == end) 83 { 84 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: 110 { 111 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; 120 } 121 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 171 bool 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. 175 if ((lang != 0U) && lang != 25) 176 { 177 std::cerr << "Warning: languages other than English is not " 178 "supported\n"; 179 // Return language flag as non english 180 return false; 181 } 182 return true; 183 } 184 185 /* 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 */ 194 bool verifyOffset(const std::vector<uint8_t>& fruBytes, fruAreas currentArea, 195 uint8_t len) 196 { 197 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 259 resCodes 260 formatIPMIFRU(const std::vector<uint8_t>& fruBytes, 261 boost::container::flat_map<std::string, std::string>& result) 262 { 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 272 const std::vector<std::string>* fruAreaFieldNames = nullptr; 273 274 // Don't parse Internal and Multirecord areas 275 for (fruAreas area = fruAreas::fruAreaChassis; 276 area <= fruAreas::fruAreaProduct; ++area) 277 { 278 size_t offset = *(fruBytes.begin() + getHeaderAreaFieldOffset(area)); 279 if (offset == 0) 280 { 281 continue; 282 } 283 offset *= fruBlockSize; 284 std::vector<uint8_t>::const_iterator fruBytesIter = fruBytes.begin() + 285 offset; 286 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 307 size_t fruAreaSize = *fruBytesIter * fruBlockSize; 308 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; 339 fruAreaFieldNames = &chassisFruAreas; 340 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); 355 timeValue += static_cast<long>(minutes) * 60; 356 fruTime = *std::gmtime(&timeValue); 357 358 // Tue Nov 20 23:08:00 2018 359 std::array<char, 32> timeString = {}; 360 auto bytes = std::strftime(timeString.data(), timeString.size(), 361 "%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 368 result["BOARD_MANUFACTURE_DATE"] = 369 std::string_view(timeString.data(), bytes); 370 fruBytesIter += 3; 371 fruAreaFieldNames = &boardFruAreas; 372 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; 381 fruAreaFieldNames = &productFruAreas; 382 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; 392 DecodeState state = DecodeState::ok; 393 do 394 { 395 auto res = decodeFRUData(fruBytesIter, fruBytesIterEndArea, 396 isLangEng); 397 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 } 405 else 406 { 407 name = 408 std::string(getFruAreaName(area)) + "_" + 409 fruCustomFieldName + 410 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; 450 if (c != 0U) 451 { 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 464 uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter, 465 std::vector<uint8_t>::const_iterator end) 466 { 467 constexpr int checksumMod = 256; 468 uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0)); 469 return (checksumMod - sum) % checksumMod; 470 } 471 472 uint8_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 480 unsigned 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; 492 size_t checksumLoc = 0; 493 if (mod == 0U) 494 { 495 traverseFRUAreaIndex += (fruBlockSize); 496 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - 1); 497 } 498 else 499 { 500 traverseFRUAreaIndex += (fruBlockSize - mod); 501 checksumLoc = fruAreaEndOfFieldsOffset + (fruBlockSize - mod - 1); 502 } 503 504 size_t newFRUAreaLen = 505 (traverseFRUAreaIndex / fruBlockSize) + 506 static_cast<unsigned long>((traverseFRUAreaIndex % fruBlockSize) != 0); 507 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 519 ssize_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 } 527 return fruFieldTypeLenValue & typeLenMask; 528 } 529 530 bool 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 { 535 if (debug) 536 { 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 { 547 if (debug) 548 { 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 { 580 if (debug) 581 { 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 591 bool findFRUHeader(FRUReader& reader, const std::string& errorHelp, 592 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData, 593 off_t& baseOffset) 594 { 595 if (reader.read(baseOffset, 0x8, blockData.data()) < 0) 596 { 597 std::cerr << "failed to read " << errorHelp << " base offset " 598 << baseOffset << "\n"; 599 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. 609 if (baseOffset != 0) 610 { 611 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; 623 return findFRUHeader(reader, errorHelp, blockData, baseOffset); 624 } 625 626 if (debug) 627 { 628 std::cerr << "Illegal header " << errorHelp << " base offset " 629 << baseOffset << "\n"; 630 } 631 632 return false; 633 } 634 635 std::vector<uint8_t> readFRUContents(FRUReader& reader, 636 const std::string& errorHelp) 637 { 638 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 639 off_t baseOffset = 0x0; 640 641 if (!findFRUHeader(reader, errorHelp, blockData, baseOffset)) 642 { 643 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 651 unsigned int prevOffset = 0; 652 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 662 /* 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 675 // 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 685 if (reader.read(baseOffset + areaOffset, 0x2, blockData.data()) < 0) 686 { 687 std::cerr << "failed to read " << errorHelp << " base offset " 688 << baseOffset << "\n"; 689 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. 715 if (reader.read(baseOffset + areaOffset, 0x3, blockData.data()) < 0) 716 { 717 std::cerr << "failed to read " << errorHelp << " base offset " 718 << baseOffset << "\n"; 719 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. 729 if ((blockData[1] & multiRecordEndOfListMask) != 0) 730 { 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 746 if (reader.read(baseOffset + readOffset, requestLength, 747 blockData.data()) < 0) 748 { 749 std::cerr << "failed to read " << errorHelp << " base offset " 750 << baseOffset << "\n"; 751 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 764 unsigned int getHeaderAreaFieldOffset(fruAreas area) 765 { 766 return static_cast<unsigned int>(area) + 1; 767 } 768 769 std::vector<uint8_t>& getFRUInfo(const uint16_t& bus, const uint8_t& address) 770 { 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 } 785 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 791 bool findFruAreaLocationAndField(std::vector<uint8_t>& fruData, 792 const std::string& propertyName, 793 struct FruArea& fruAreaParams) 794 { 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; 840 size_t fruDataIter = fruAreaParams.start + offset; 841 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 } 892 893 // Copy the FRU Area fields and properties into restFRUAreaFieldsData vector. 894 // Return true for success and false for failure. 895 896 bool 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 } 942 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 946 std::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 { 952 int highest = -1; 953 bool found = false; 954 955 for (const auto& busIface : dbusInterfaceMap) 956 { 957 std::string path = busIface.second->get_object_path(); 958 if (std::regex_match(path, std::regex(productName + "(_\\d+|)$"))) 959 { 960 // 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 } 986 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 991 std::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 } 1033 1034 bool 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 } 1049