FruUtils: Add support for finding FRU at an offset
This change adds findFRUHeader which can detect non-IPMI headers to
identify the offset for IPMI FRU data. An implementation for "$TYAN$"
has been added. The new findFRUHeader is now used by readFRUContents
to generically read IPMI FRU both at offset 0 and at a offset as
determined by findFRUHeader.
Signed-off-by: Oskar Senft <osk@google.com>
Change-Id: I4325aac7737db59aaa11359ad7a0d575957f16e7
diff --git a/include/FruUtils.hpp b/include/FruUtils.hpp
index 74f80e4..906b77e 100644
--- a/include/FruUtils.hpp
+++ b/include/FruUtils.hpp
@@ -126,6 +126,23 @@
ssize_t getFieldLength(uint8_t fruFieldTypeLenValue);
+/// \brief Find a FRU header.
+/// \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
+/// \param blockData buffer to return the last read block
+/// \param baseOffset the offset to start the search at;
+/// set to 0 to perform search;
+/// returns the offset at which a header was found
+/// \return whether a header was found
+bool findFRUHeader(int flag, int file, uint16_t address,
+ const ReadBlockFunc& readBlock,
+ const std::string& errorHelp,
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
+ uint16_t& baseOffset);
+
/// \brief Read and validate FRU contents.
/// \param flag the flag required for raw i2c
/// \param file the open file handle
diff --git a/src/FruUtils.cpp b/src/FruUtils.cpp
index b8b1905..940f8c8 100644
--- a/src/FruUtils.cpp
+++ b/src/FruUtils.cpp
@@ -586,26 +586,61 @@
return true;
}
+bool findFRUHeader(int flag, int file, uint16_t address,
+ const ReadBlockFunc& readBlock,
+ const std::string& errorHelp,
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
+ uint16_t& baseOffset)
+{
+ if (readBlock(flag, file, address, baseOffset, 0x8, blockData.data()) < 0)
+ {
+ std::cerr << "failed to read " << errorHelp << " base offset "
+ << baseOffset << "\n";
+ return false;
+ }
+
+ // check the header checksum
+ if (validateHeader(blockData))
+ {
+ return true;
+ }
+
+ // only continue the search if we just looked at 0x0.
+ if (baseOffset != 0) {
+ return false;
+ }
+
+ // now check for special cases where the IPMI data is at an offset
+
+ // check if blockData starts with tyanHeader
+ const std::vector<uint8_t> tyanHeader = {'$', 'T', 'Y', 'A', 'N', '$'};
+ if (blockData.size() >= tyanHeader.size() &&
+ std::equal(tyanHeader.begin(), tyanHeader.end(), blockData.begin()))
+ {
+ // look for the FRU header at offset 0x6000
+ baseOffset = 0x6000;
+ return findFRUHeader(flag, file, address, readBlock, errorHelp,
+ blockData, baseOffset);
+ }
+
+ if (debug)
+ {
+ std::cerr << "Illegal header " << errorHelp << " base offset "
+ << baseOffset << "\n";
+ }
+
+ return false;
+}
+
std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
const ReadBlockFunc& readBlock,
const std::string& errorHelp)
{
std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ uint16_t baseOffset = 0x0;
- 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";
- }
-
+ if (!findFRUHeader(flag, file, address, readBlock, errorHelp,
+ blockData, baseOffset)) {
return {};
}
@@ -648,10 +683,12 @@
areaOffset *= fruBlockSize;
- if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
+ if (readBlock(flag, file, address,
+ baseOffset + static_cast<uint16_t>(areaOffset),
0x2, blockData.data()) < 0)
{
- std::cerr << "failed to read " << errorHelp << "\n";
+ std::cerr << "failed to read " << errorHelp << " base offset "
+ << baseOffset << "\n";
return {};
}
@@ -679,10 +716,11 @@
// 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,
+ baseOffset + static_cast<uint16_t>(areaOffset), 0x3,
blockData.data()) < 0)
{
- std::cerr << "failed to read " << errorHelp << "\n";
+ std::cerr << "failed to read " << errorHelp << " base offset "
+ << baseOffset << "\n";
return {};
}
@@ -710,11 +748,13 @@
size_t requestLength =
std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
- if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
+ if (readBlock(flag, file, address,
+ baseOffset + static_cast<uint16_t>(readOffset),
static_cast<uint8_t>(requestLength),
blockData.data()) < 0)
{
- std::cerr << "failed to read " << errorHelp << "\n";
+ std::cerr << "failed to read " << errorHelp << " base offset "
+ << baseOffset << "\n";
return {};
}
diff --git a/test/test_fru-utils.cpp b/test/test_fru-utils.cpp
index d2f0e36..2984094 100644
--- a/test/test_fru-utils.cpp
+++ b/test/test_fru-utils.cpp
@@ -1,6 +1,8 @@
#include "FruUtils.hpp"
#include <array>
+#include<algorithm>
+#include<iterator>
#include "gtest/gtest.h"
@@ -142,3 +144,101 @@
EXPECT_EQ(calculateChecksum(data), 255);
}
+
+int64_t getDataTempl(
+ const std::vector<uint8_t>& data,
+ [[maybe_unused]] int flag,
+ [[maybe_unused]] int file,
+ [[maybe_unused]] uint16_t address,
+ uint16_t offset, uint8_t length, uint8_t* outBuf)
+{
+ if (offset >= data.size())
+ {
+ return 0;
+ }
+
+ uint16_t idx;
+ for (idx = offset;
+ idx < data.size() && idx < offset + length;
+ ++idx, ++outBuf)
+ {
+ *outBuf = data[idx];
+ }
+
+ return idx - offset;
+}
+
+TEST(FindFRUHeaderTest, InvalidHeader)
+{
+ const std::vector<uint8_t> data = {255, 16};
+ uint16_t offset = 0;
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ auto getData = [&data](auto fl, auto fi, auto a, auto o, auto l,
+ auto* b) { return getDataTempl(data, fl, fi, a, o, l, b); };
+
+ EXPECT_FALSE(findFRUHeader(0, 0, 0, getData, "error", blockData, offset));
+}
+
+TEST(FindFRUHeaderTest, NoData)
+{
+ const std::vector<uint8_t> data = {};
+ uint16_t offset = 0;
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ auto getData = [&data](auto fl, auto fi, auto a, auto o, auto l,
+ auto* b) { return getDataTempl(data, fl, fi, a, o, l, b); };
+
+ EXPECT_FALSE(findFRUHeader(0, 0, 0, getData, "error", blockData, offset));
+}
+
+TEST(FindFRUHeaderTest, ValidHeader)
+{
+ const std::vector<uint8_t> data = {
+ 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5};
+ uint16_t offset = 0;
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ auto getData = [&data](auto fl, auto fi, auto a, auto o, auto l,
+ auto* b) { return getDataTempl(data, fl, fi, a, o, l, b); };
+
+ EXPECT_TRUE(findFRUHeader(0, 0, 0, getData, "error", blockData, offset));
+ EXPECT_EQ(0, offset);
+}
+
+TEST(FindFRUHeaderTest, TyanInvalidHeader)
+{
+ std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
+ data.resize(0x6000 + I2C_SMBUS_BLOCK_MAX);
+ uint16_t offset = 0;
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ auto getData = [&data](auto fl, auto fi, auto a, auto o, auto l,
+ auto* b) { return getDataTempl(data, fl, fi, a, o, l, b); };
+
+ EXPECT_FALSE(findFRUHeader(0, 0, 0, getData, "error", blockData, offset));
+}
+
+TEST(FindFRUHeaderTest, TyanNoData)
+{
+ const std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
+ uint16_t offset = 0;
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ auto getData = [&data](auto fl, auto fi, auto a, auto o, auto l,
+ auto* b) { return getDataTempl(data, fl, fi, a, o, l, b); };
+
+ EXPECT_FALSE(findFRUHeader(0, 0, 0, getData, "error", blockData, offset));
+}
+
+TEST(FindFRUHeaderTest, TyanValidHeader)
+{
+ std::vector<uint8_t> data = {'$', 'T', 'Y', 'A', 'N', '$', 0, 0};
+ data.resize(0x6000);
+ constexpr std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> fruHeader = {
+ 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0xf5};
+ copy(fruHeader.begin(), fruHeader.end(), back_inserter(data));
+
+ uint16_t offset = 0;
+ std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
+ auto getData = [&data](auto fl, auto fi, auto a, auto o, auto l,
+ auto* b) { return getDataTempl(data, fl, fi, a, o, l, b); };
+
+ EXPECT_TRUE(findFRUHeader(0, 0, 0, getData, "error", blockData, offset));
+ EXPECT_EQ(0x6000, offset);
+}