split readFRUContents() into FruUtils module
Splits the readFRUContents() method into its own module. This utility
code can then be more easily tested without extra dependencies.
Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I1b8fa7179d7d0092e0236e8586f99c45121eca84
diff --git a/src/FruUtils.cpp b/src/FruUtils.cpp
new file mode 100644
index 0000000..ed8ab5a
--- /dev/null
+++ b/src/FruUtils.cpp
@@ -0,0 +1,228 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+/// \file FruUtils.cpp
+
+#include "FruUtils.hpp"
+
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <set>
+#include <string>
+#include <vector>
+
+extern "C"
+{
+// Include for I2C_SMBUS_BLOCK_MAX
+#include <linux/i2c.h>
+}
+
+static constexpr bool DEBUG = false;
+constexpr size_t fruVersion = 1; // Current FRU spec version number is 1
+
+bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
+{
+ // ipmi spec format version number is currently at 1, verify it
+ if (blockData[0] != fruVersion)
+ {
+ if (DEBUG)
+ {
+ std::cerr << "FRU spec version " << (int)(blockData[0])
+ << " not supported. Supported version is "
+ << (int)(fruVersion) << "\n";
+ }
+ return false;
+ }
+
+ // verify pad is set to 0
+ if (blockData[6] != 0x0)
+ {
+ if (DEBUG)
+ {
+ std::cerr << "PAD value in header is non zero, value is "
+ << (int)(blockData[6]) << "\n";
+ }
+ return false;
+ }
+
+ // verify offsets are 0, or don't point to another offset
+ std::set<uint8_t> foundOffsets;
+ for (int ii = 1; ii < 6; ii++)
+ {
+ if (blockData[ii] == 0)
+ {
+ continue;
+ }
+ auto inserted = foundOffsets.insert(blockData[ii]);
+ if (!inserted.second)
+ {
+ return false;
+ }
+ }
+
+ // validate checksum
+ size_t sum = 0;
+ for (int jj = 0; jj < 7; jj++)
+ {
+ sum += blockData[jj];
+ }
+ sum = (256 - sum) & 0xFF;
+
+ if (sum != blockData[7])
+ {
+ if (DEBUG)
+ {
+ std::cerr << "Checksum " << (int)(blockData[7])
+ << " is invalid. calculated checksum is " << (int)(sum)
+ << "\n";
+ }
+ return false;
+ }
+ return true;
+}
+
+std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
+ ReadBlockFunc readBlock,
+ const std::string& errorHelp)
+{
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+
+ if (readBlock(flag, file, address, 0x0, 0x8, blockData.data()) < 0)
+ {
+ std::cerr << "failed to read " << errorHelp << "\n";
+ return {};
+ }
+
+ // check the header checksum
+ if (!validateHeader(blockData))
+ {
+ if (DEBUG)
+ {
+ std::cerr << "Illegal header " << errorHelp << "\n";
+ }
+
+ return {};
+ }
+
+ std::vector<uint8_t> device;
+ device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
+
+ bool hasMultiRecords = false;
+ size_t fruLength = fruBlockSize; // At least FRU header is present
+ for (fruAreas area = fruAreas::fruAreaInternal;
+ area <= fruAreas::fruAreaMultirecord; ++area)
+ {
+ // Offset value can be 255.
+ unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
+ if (areaOffset == 0)
+ {
+ continue;
+ }
+
+ // MultiRecords are different. area is not tracking section, it's
+ // walking the common header.
+ if (area == fruAreas::fruAreaMultirecord)
+ {
+ hasMultiRecords = true;
+ break;
+ }
+
+ areaOffset *= fruBlockSize;
+
+ if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
+ 0x2, blockData.data()) < 0)
+ {
+ std::cerr << "failed to read " << errorHelp << "\n";
+ return {};
+ }
+
+ // Ignore data type (blockData is already unsigned).
+ size_t length = blockData[1] * fruBlockSize;
+ areaOffset += length;
+ fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
+ }
+
+ if (hasMultiRecords)
+ {
+ // device[area count] is the index to the last area because the 0th
+ // entry is not an offset in the common header.
+ unsigned int areaOffset =
+ device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
+ areaOffset *= fruBlockSize;
+
+ // the multi-area record header is 5 bytes long.
+ constexpr size_t multiRecordHeaderSize = 5;
+ constexpr uint8_t multiRecordEndOfListMask = 0x80;
+
+ // Sanity hard-limit to 64KB.
+ while (areaOffset < std::numeric_limits<uint16_t>::max())
+ {
+ // In multi-area, the area offset points to the 0th record, each
+ // record has 3 bytes of the header we care about.
+ if (readBlock(flag, file, address,
+ static_cast<uint16_t>(areaOffset), 0x3,
+ blockData.data()) < 0)
+ {
+ std::cerr << "failed to read " << errorHelp << "\n";
+ return {};
+ }
+
+ // Ok, let's check the record length, which is in bytes (unsigned,
+ // up to 255, so blockData should hold uint8_t not char)
+ size_t recordLength = blockData[2];
+ areaOffset += (recordLength + multiRecordHeaderSize);
+ fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
+
+ // If this is the end of the list bail.
+ if ((blockData[1] & multiRecordEndOfListMask))
+ {
+ break;
+ }
+ }
+ }
+
+ // You already copied these first 8 bytes (the ipmi fru header size)
+ fruLength -= std::min(fruBlockSize, fruLength);
+
+ int readOffset = fruBlockSize;
+
+ while (fruLength > 0)
+ {
+ size_t requestLength =
+ std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
+
+ if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
+ static_cast<uint8_t>(requestLength),
+ blockData.data()) < 0)
+ {
+ std::cerr << "failed to read " << errorHelp << "\n";
+ return {};
+ }
+
+ device.insert(device.end(), blockData.begin(),
+ blockData.begin() + requestLength);
+
+ readOffset += requestLength;
+ fruLength -= std::min(requestLength, fruLength);
+ }
+
+ return device;
+}
+
+unsigned int getHeaderAreaFieldOffset(fruAreas area)
+{
+ return static_cast<unsigned int>(area) + 1;
+}