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/include/FruUtils.hpp b/include/FruUtils.hpp
new file mode 100644
index 0000000..4417ece
--- /dev/null
+++ b/include/FruUtils.hpp
@@ -0,0 +1,71 @@
+/*
+// 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.hpp
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <vector>
+
+extern "C"
+{
+// Include for I2C_SMBUS_BLOCK_MAX
+#include <linux/i2c.h>
+}
+
+enum class fruAreas
+{
+ fruAreaInternal = 0,
+ fruAreaChassis,
+ fruAreaBoard,
+ fruAreaProduct,
+ fruAreaMultirecord
+};
+inline fruAreas operator++(fruAreas& x)
+{
+ return x = static_cast<fruAreas>(std::underlying_type<fruAreas>::type(x) +
+ 1);
+}
+
+inline constexpr size_t fruBlockSize =
+ 8; // FRU areas are measured in 8-byte blocks
+
+using ReadBlockFunc =
+ std::function<int64_t(int flag, int file, uint16_t address, uint16_t offset,
+ uint8_t length, uint8_t* outBuf)>;
+
+/// \brief Read and validate FRU contents.
+/// \param flag the flag required for raw i2c
+/// \param file the open file handle
+/// \param address the i2c device address
+/// \param readBlock a read method
+/// \param errorHelp and a helper string for failures
+/// \return the FRU contents from the file
+std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
+ ReadBlockFunc readBlock,
+ const std::string& errorHelp);
+
+/// \brief Validate an IPMI FRU common header
+/// \param blockData the bytes comprising the common header
+/// \return true if valid
+bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
+
+/// \brief Get offset for a common header area
+/// \param area - the area
+/// \return the field offset
+unsigned int getHeaderAreaFieldOffset(fruAreas area);
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 53d3118..2552242 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -15,6 +15,7 @@
*/
/// \file FruDevice.cpp
+#include "FruUtils.hpp"
#include "Utils.hpp"
#include <errno.h>
@@ -61,8 +62,6 @@
constexpr size_t MAX_FRU_SIZE = 512;
constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
constexpr size_t busTimeoutSeconds = 5;
-constexpr size_t fruBlockSize = 8; // FRU areas are measured in 8-byte blocks
-constexpr size_t fruVersion = 1; // Current FRU spec version number is 1
constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
@@ -78,20 +77,6 @@
resErr
};
-enum class fruAreas
-{
- fruAreaInternal = 0,
- fruAreaChassis,
- fruAreaBoard,
- fruAreaProduct,
- fruAreaMultirecord
-};
-inline fruAreas operator++(fruAreas& x)
-{
- return x = static_cast<fruAreas>(std::underlying_type<fruAreas>::type(x) +
- 1);
-}
-
static const std::vector<std::string> FRU_AREA_NAMES = {
"INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
@@ -128,15 +113,10 @@
static const std::string FRU_CUSTOM_FIELD_NAME = "INFO_AM";
-static inline unsigned int getHeaderAreaFieldOffset(fruAreas area)
-{
- return static_cast<unsigned int>(area) + 1;
-}
static inline const std::string& getFruAreaName(fruAreas area)
{
return FRU_AREA_NAMES[static_cast<unsigned int>(area)];
}
-bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
std::vector<uint8_t>::const_iterator end);
bool updateFRUProperty(
@@ -146,140 +126,6 @@
std::pair<size_t, size_t>,
std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap);
-using ReadBlockFunc =
- std::function<int64_t(int flag, int file, uint16_t address, uint16_t offset,
- uint8_t length, uint8_t* outBuf)>;
-
-// Read and validate FRU contents, given the flag required for raw i2c, the open
-// file handle, a read method, and a helper string for failures.
-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;
-}
-
// Given a bus/address, produce the path in sysfs for an eeprom.
static std::string getEepromPath(size_t bus, size_t address)
{
@@ -449,67 +295,6 @@
file, address, reinterpret_cast<uint8_t*>(&offset), 2, buf, len);
}
-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;
-}
-
// TODO: This code is very similar to the non-eeprom version and can be merged
// with some tweaks.
static std::vector<uint8_t> processEeprom(int bus, int address)
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;
+}
diff --git a/src/meson.build b/src/meson.build
index 4966ed2..8a9811b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -26,6 +26,7 @@
'fru-device',
'FruDevice.cpp',
'Utils.cpp',
+ 'FruUtils.cpp',
cpp_args: cpp_args_fd,
dependencies: [
boost,