buffer: Implement "readErrorLogs"
This is the API that will be called by the main to read all of the error
logs. Found a cornercase in wraparoundRead, so added a unit test to
guard it as well.
Tested:
Unit tested and tested on a real HW (with the following changes)
Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: I94cd3a2f53ee7da2c145b751c1c3b5989f4c5b23
diff --git a/include/buffer.hpp b/include/buffer.hpp
index d42b843..d676165 100644
--- a/include/buffer.hpp
+++ b/include/buffer.hpp
@@ -136,6 +136,14 @@
* * @return entry header and entry pair read from buffer
*/
virtual EntryPair readEntry(size_t relativeOffset) = 0;
+
+ /**
+ * Read the buffer - this API should be used instead of individual functions
+ * above
+ *
+ * @return vector of EntryPair which consists of entry header and entry
+ */
+ virtual std::vector<EntryPair> readErrorLogs() = 0;
};
/**
@@ -158,6 +166,7 @@
const uint32_t length) override;
struct QueueEntryHeader readEntryHeader(size_t relativeOffset) override;
EntryPair readEntry(size_t relativeOffset) override;
+ std::vector<EntryPair> readErrorLogs() override;
private:
/** @brief The Error log queue starts after the UE region, which is where
diff --git a/src/buffer.cpp b/src/buffer.cpp
index 836049f..278f8cf 100644
--- a/src/buffer.cpp
+++ b/src/buffer.cpp
@@ -78,7 +78,7 @@
{
size_t headerSize = sizeof(struct CircularBufferHeader);
std::vector<uint8_t> bytesRead =
- dataInterface->read(/* offset */ 0, headerSize);
+ dataInterface->read(/*offset=*/0, headerSize);
if (bytesRead.size() != headerSize)
{
@@ -166,6 +166,12 @@
bytesRead.size(), numBytesToReadTillQueueEnd));
}
size_t updatedReadPtr = relativeOffset + numBytesToReadTillQueueEnd;
+ if (updatedReadPtr == queueSize)
+ {
+ // If we read all the way up to the end of the queue, we need to
+ // manually wrap the updateReadPtr around to 0
+ updatedReadPtr = 0;
+ }
// If there are any more bytes to be read beyond the buffer, wrap around and
// read from the beginning of the buffer (offset by the queueOffset)
@@ -226,4 +232,71 @@
return {entryHeader, entry};
}
+std::vector<EntryPair> BufferImpl::readErrorLogs()
+{
+ // Reading the buffer header will update the cachedBufferHeader
+ readBufferHeader();
+
+ const size_t queueSize =
+ boost::endian::little_to_native(cachedBufferHeader.queueSize);
+ size_t currentBiosWritePtr =
+ boost::endian::little_to_native(cachedBufferHeader.biosWritePtr);
+ if (currentBiosWritePtr > queueSize)
+ {
+ throw std::runtime_error(fmt::format(
+ "[readErrorLogs] currentBiosWritePtr was '{}' which was bigger "
+ "than queueSize '{}'",
+ currentBiosWritePtr, queueSize));
+ }
+ size_t currentReadPtr =
+ boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
+ if (currentReadPtr > queueSize)
+ {
+ throw std::runtime_error(fmt::format(
+ "[readErrorLogs] currentReadPtr was '{}' which was bigger "
+ "than queueSize '{}'",
+ currentReadPtr, queueSize));
+ }
+
+ size_t bytesToRead;
+ if (currentBiosWritePtr == currentReadPtr)
+ {
+ // No new payload was detected, return an empty vector gracefully
+ return {};
+ }
+
+ if (currentBiosWritePtr > currentReadPtr)
+ {
+ // Simply subtract in this case
+ bytesToRead = currentBiosWritePtr - currentReadPtr;
+ }
+ else
+ {
+ // Calculate the bytes to the "end" (QueueSize - ReadPtr) +
+ // bytes to read from the "beginning" (0 + WritePtr)
+ bytesToRead = (queueSize - currentReadPtr) + currentBiosWritePtr;
+ }
+
+ size_t byteRead = 0;
+ std::vector<EntryPair> entryPairs;
+ while (byteRead < bytesToRead)
+ {
+ EntryPair entryPair = readEntry(currentReadPtr);
+ byteRead += sizeof(struct QueueEntryHeader) + entryPair.second.size();
+ entryPairs.push_back(entryPair);
+
+ // Note: readEntry() will update cachedBufferHeader.bmcReadPtr
+ currentReadPtr =
+ boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr);
+ }
+ if (currentBiosWritePtr != currentReadPtr)
+ {
+ throw std::runtime_error(fmt::format(
+ "[readErrorLogs] biosWritePtr '{}' and bmcReaddPtr '{}' "
+ "are not identical after reading through all the logs",
+ currentBiosWritePtr, currentReadPtr));
+ }
+ return entryPairs;
+}
+
} // namespace bios_bmc_smm_error_logger
diff --git a/test/buffer_test.cpp b/test/buffer_test.cpp
index 6f886f0..577e322 100644
--- a/test/buffer_test.cpp
+++ b/test/buffer_test.cpp
@@ -210,6 +210,10 @@
protected:
BufferWraparoundReadTest()
{
+ initializeFuncMock();
+ }
+ void initializeFuncMock()
+ {
// Initialize the memory and the cachedBufferHeader
InSequence s;
EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
@@ -221,11 +225,7 @@
write(0, ElementsAreArray(emptyArray)))
.WillOnce(Return(testProposedBufferSize));
- uint8_t* testInitializationHeaderPtr =
- reinterpret_cast<uint8_t*>(&testInitializationHeader);
- EXPECT_CALL(*dataInterfaceMockPtr,
- write(0, ElementsAreArray(testInitializationHeaderPtr,
- bufferHeaderSize)))
+ EXPECT_CALL(*dataInterfaceMockPtr, write(0, _))
.WillOnce(Return(bufferHeaderSize));
EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
testQueueSize, testUeRegionSize,
@@ -234,6 +234,9 @@
static constexpr size_t expectedWriteSize = 2;
static constexpr uint8_t expectedBmcReadPtrOffset = 0x20;
static constexpr size_t expectedqueueOffset = 0x30 + testUeRegionSize;
+
+ uint8_t* testInitializationHeaderPtr =
+ reinterpret_cast<uint8_t*>(&testInitializationHeader);
};
TEST_F(BufferWraparoundReadTest, ParamsTooBigFail)
@@ -383,6 +386,35 @@
testBytesLeft);
}
+TEST_F(BufferWraparoundReadTest, WrapAroundCornerCasePass)
+{
+ InSequence s;
+ size_t testBytesLeft = 0;
+ size_t testLength = 4;
+ size_t testOffset = testQueueSize - (testLength - testBytesLeft);
+
+ // Read to the very end of the queue
+ std::vector<std::uint8_t> testBytes{4, 3, 2, 1};
+ EXPECT_CALL(*dataInterfaceMockPtr,
+ read(testOffset + expectedqueueOffset, testLength))
+ .WillOnce(Return(testBytes));
+
+ // Call to updateReadPtr is triggered, since we read to the very end of the
+ // buffer, update the readPtr up around to 0
+ const std::vector<uint8_t> expectedReadPtr{0x0, 0x0};
+ EXPECT_CALL(*dataInterfaceMockPtr, write(expectedBmcReadPtrOffset,
+ ElementsAreArray(expectedReadPtr)))
+ .WillOnce(Return(expectedWriteSize));
+
+ EXPECT_THAT(bufferImpl->wraparoundRead(testOffset, testLength),
+ ElementsAreArray(testBytes));
+ struct CircularBufferHeader cachedBufferHeader =
+ bufferImpl->getCachedBufferHeader();
+ // The bmcReadPtr should have been updated to reflect the wraparound
+ EXPECT_EQ(boost::endian::little_to_native(cachedBufferHeader.bmcReadPtr),
+ 0);
+}
+
class BufferEntryTest : public BufferWraparoundReadTest
{
protected:
@@ -500,5 +532,166 @@
testEntrySize - 1);
}
+class BufferReadErrorLogsTest : public BufferEntryTest
+{
+ protected:
+ BufferReadErrorLogsTest() = default;
+
+ uint8_t* testEntryHeaderPtr = reinterpret_cast<uint8_t*>(&testEntryHeader);
+ size_t entryAndHeaderSize = entryHeaderSize + testEntrySize;
+};
+
+TEST_F(BufferReadErrorLogsTest, PtrsTooBigFail)
+{
+ InSequence s;
+ // Set the biosWritePtr too big
+ testInitializationHeader.biosWritePtr =
+ boost::endian::native_to_little((testQueueSize + 1));
+ initializeFuncMock();
+
+ EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+ .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+ testInitializationHeaderPtr +
+ bufferHeaderSize)));
+ EXPECT_THROW(
+ try {
+ bufferImpl->readErrorLogs();
+ } catch (const std::runtime_error& e) {
+ EXPECT_STREQ(e.what(),
+ "[readErrorLogs] currentBiosWritePtr was '257' "
+ "which was bigger than queueSize '256'");
+ throw;
+ },
+ std::runtime_error);
+
+ // Reset the biosWritePtr and set the bmcReadPtr too big
+ testInitializationHeader.biosWritePtr = 0;
+ initializeFuncMock();
+ testInitializationHeader.bmcReadPtr =
+ boost::endian::native_to_little((testQueueSize + 1));
+ initializeFuncMock();
+
+ EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+ .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+ testInitializationHeaderPtr +
+ bufferHeaderSize)));
+ EXPECT_THROW(
+ try {
+ bufferImpl->readErrorLogs();
+ } catch (const std::runtime_error& e) {
+ EXPECT_STREQ(e.what(), "[readErrorLogs] currentReadPtr was '257' "
+ "which was bigger than queueSize '256'");
+ throw;
+ },
+ std::runtime_error);
+}
+
+TEST_F(BufferReadErrorLogsTest, IdenticalPtrsPass)
+{
+ EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+ .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+ testInitializationHeaderPtr +
+ bufferHeaderSize)));
+ EXPECT_NO_THROW(bufferImpl->readErrorLogs());
+}
+
+TEST_F(BufferReadErrorLogsTest, NoWraparoundPass)
+{
+ InSequence s;
+ // Set the biosWritePtr to 1 entryHeader + entry size
+ testInitializationHeader.biosWritePtr =
+ boost::endian::native_to_little((entryAndHeaderSize));
+ initializeFuncMock();
+ EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+ .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+ testInitializationHeaderPtr +
+ bufferHeaderSize)));
+ std::vector<uint8_t> testEntryHeaderVector(
+ testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+ std::vector<uint8_t> testEntryVector(testEntrySize);
+ wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
+ wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
+
+ std::vector<EntryPair> entryPairs;
+ EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
+
+ // Check that we only read one entryPair and that the content is correct
+ EXPECT_EQ(entryPairs.size(), 1);
+ EXPECT_EQ(entryPairs[0].first, testEntryHeader);
+ EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
+}
+
+TEST_F(BufferReadErrorLogsTest, WraparoundMultiplEntryPass)
+{
+ InSequence s;
+ // Set the bmcReadPtr to 1 entryHeader + entry size from the "end" exactly
+ uint32_t entryAndHeaderSizeAwayFromEnd = testQueueSize - entryAndHeaderSize;
+ testInitializationHeader.bmcReadPtr =
+ boost::endian::native_to_little(entryAndHeaderSizeAwayFromEnd);
+ // Set the biosWritePtr to 1 entryHeader + entry size from the "beginning"
+ testInitializationHeader.biosWritePtr = entryAndHeaderSize;
+ initializeFuncMock();
+ EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+ .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+ testInitializationHeaderPtr +
+ bufferHeaderSize)));
+
+ std::vector<uint8_t> testEntryHeaderVector(
+ testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+ std::vector<uint8_t> testEntryVector(testEntrySize);
+ wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd,
+ testEntryHeaderVector);
+ wraparoundReadMock(/*relativeOffset=*/entryAndHeaderSizeAwayFromEnd +
+ entryHeaderSize,
+ testEntryVector);
+ wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize,
+ testEntryHeaderVector);
+ wraparoundReadMock(/*relativeOffset=*/0 + entryAndHeaderSize +
+ entryHeaderSize,
+ testEntryVector);
+
+ std::vector<EntryPair> entryPairs;
+ EXPECT_NO_THROW(entryPairs = bufferImpl->readErrorLogs());
+
+ // Check that we only read one entryPair and that the content is correct
+ EXPECT_EQ(entryPairs.size(), 2);
+ EXPECT_EQ(entryPairs[0].first, testEntryHeader);
+ EXPECT_EQ(entryPairs[1].first, testEntryHeader);
+ EXPECT_THAT(entryPairs[0].second, ElementsAreArray(testEntryVector));
+ EXPECT_THAT(entryPairs[1].second, ElementsAreArray(testEntryVector));
+}
+
+TEST_F(BufferReadErrorLogsTest, WraparoundMismatchingPtrsFail)
+{
+ InSequence s;
+ testInitializationHeader.bmcReadPtr = boost::endian::native_to_little(0);
+ // Make the biosWritePtr intentially 1 smaller than expected
+ testInitializationHeader.biosWritePtr =
+ boost::endian::native_to_little(entryAndHeaderSize - 1);
+ initializeFuncMock();
+ EXPECT_CALL(*dataInterfaceMockPtr, read(0, bufferHeaderSize))
+ .WillOnce(Return(std::vector<uint8_t>(testInitializationHeaderPtr,
+ testInitializationHeaderPtr +
+ bufferHeaderSize)));
+
+ std::vector<uint8_t> testEntryHeaderVector(
+ testEntryHeaderPtr, testEntryHeaderPtr + entryHeaderSize);
+ std::vector<uint8_t> testEntryVector(testEntrySize);
+ wraparoundReadMock(/*relativeOffset=*/0, testEntryHeaderVector);
+ wraparoundReadMock(/*relativeOffset=*/0 + entryHeaderSize, testEntryVector);
+
+ EXPECT_THROW(
+ try {
+ bufferImpl->readErrorLogs();
+ } catch (const std::runtime_error& e) {
+ EXPECT_STREQ(
+ e.what(),
+ "[readErrorLogs] biosWritePtr '37' and bmcReaddPtr '38' "
+ "are not identical after reading through all the logs");
+ throw;
+ },
+ std::runtime_error);
+}
+
} // namespace
} // namespace bios_bmc_smm_error_logger