buffer: Implement "initialize" and add unit tests
Add buffer headers and the initialization process
Tested: Unit Tested
Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: Iaf3c26ce01f7109000266cdbc7efa77988eae73b
diff --git a/include/buffer.hpp b/include/buffer.hpp
new file mode 100644
index 0000000..972884e
--- /dev/null
+++ b/include/buffer.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include "pci_handler.hpp"
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+namespace bios_bmc_smm_error_logger
+{
+
+struct CircularBufferHeader
+{
+ uint32_t bmcInterfaceVersion; // Offset 0x0
+ uint32_t biosInterfaceVersion; // Offset 0x4
+ std::array<uint32_t, 4> magicNumber; // Offset 0x8
+ uint16_t queueSize; // Offset 0x18
+ uint16_t ueRegionSize; // Offset 0x1a
+ uint32_t bmcFlags; // Offset 0x1c
+ uint16_t bmcReadPtr; // Offset 0x20
+ std::array<uint8_t, 6> padding1; // Offset 0x22
+ uint32_t biosFlags; // Offset 0x28
+ uint16_t biosWritePtr; // Offset 0x2c
+ std::array<uint8_t, 2> padding2; // Offset 0x2e
+ // UE reserved region: Offset 0x30
+ // Error log queue: Offset 0x30 + UE reserved region
+
+ bool operator==(const CircularBufferHeader& other) const
+ {
+ return this->bmcInterfaceVersion == other.bmcInterfaceVersion &&
+ this->biosInterfaceVersion == other.biosInterfaceVersion &&
+ this->magicNumber == other.magicNumber &&
+ this->queueSize == other.queueSize &&
+ this->ueRegionSize == other.ueRegionSize &&
+ this->bmcFlags == other.bmcFlags &&
+ this->bmcReadPtr == other.bmcReadPtr &&
+ /* Skip comparing padding1 */
+ this->biosFlags == other.biosFlags &&
+ this->biosWritePtr == other.biosWritePtr;
+ /* Skip comparing padding2 */
+ }
+} __attribute__((__packed__));
+
+/**
+ * An interface class for the buffer helper APIs
+ */
+class BufferInterface
+{
+ public:
+ virtual ~BufferInterface() = default;
+
+ /**
+ * Zero out the buffer first before populating the header
+ *
+ * @param[in] bmcInterfaceVersion - Used to initialize the header
+ * @param[in] queueSize - Used to initialize the header
+ * @param[in] ueRegionSize - Used to initialize the header
+ * @param[in] magicNumber - Used to initialize the header
+ * @return true if successful
+ */
+ virtual void initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
+ uint16_t ueRegionSize,
+ const std::array<uint32_t, 4>& magicNumber) = 0;
+};
+
+/**
+ * Buffer implementation class
+ */
+class BufferImpl : public BufferInterface
+{
+ public:
+ /** @brief Constructor for BufferImpl
+ * @param[in] dataInterface - DataInterface for this object
+ */
+ explicit BufferImpl(std::unique_ptr<DataInterface> dataInterface);
+ void initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
+ uint16_t ueRegionSize,
+ const std::array<uint32_t, 4>& magicNumber) override;
+
+ private:
+ std::unique_ptr<DataInterface> dataInterface;
+};
+
+} // namespace bios_bmc_smm_error_logger
diff --git a/src/buffer.cpp b/src/buffer.cpp
new file mode 100644
index 0000000..a0491eb
--- /dev/null
+++ b/src/buffer.cpp
@@ -0,0 +1,55 @@
+#include "buffer.hpp"
+
+#include "pci_handler.hpp"
+
+#include <fmt/format.h>
+
+#include <array>
+#include <cstdint>
+#include <memory>
+#include <span>
+#include <vector>
+
+namespace bios_bmc_smm_error_logger
+{
+
+BufferImpl::BufferImpl(std::unique_ptr<DataInterface> dataInterface) :
+ dataInterface(std::move(dataInterface)){};
+
+void BufferImpl::initialize(uint32_t bmcInterfaceVersion, uint16_t queueSize,
+ uint16_t ueRegionSize,
+ const std::array<uint32_t, 4>& magicNumber)
+{
+ // Initialize the whole buffer with 0x00
+ const size_t memoryRegionSize = dataInterface->getMemoryRegionSize();
+ const std::vector<uint8_t> emptyVector(memoryRegionSize, 0);
+ size_t byteWritten = dataInterface->write(0, emptyVector);
+ if (byteWritten != memoryRegionSize)
+ {
+ throw std::runtime_error(
+ fmt::format("Buffer initialization only erased '{}'", byteWritten));
+ }
+
+ // Create an initial buffer header and write to it
+ struct CircularBufferHeader initializationHeader = {};
+ initializationHeader.bmcInterfaceVersion = bmcInterfaceVersion;
+ initializationHeader.queueSize = queueSize;
+ initializationHeader.ueRegionSize = ueRegionSize;
+ initializationHeader.magicNumber = magicNumber;
+
+ uint8_t* initializationHeaderPtr =
+ reinterpret_cast<uint8_t*>(&initializationHeader);
+ size_t initializationHeaderSize = sizeof(initializationHeader);
+ byteWritten = dataInterface->write(
+ 0, std::span<const uint8_t>(initializationHeaderPtr,
+ initializationHeaderPtr +
+ initializationHeaderSize));
+ if (byteWritten != initializationHeaderSize)
+ {
+ throw std::runtime_error(fmt::format(
+ "Buffer initialization buffer header write only wrote '{}'",
+ byteWritten));
+ }
+}
+
+} // namespace bios_bmc_smm_error_logger
diff --git a/src/meson.build b/src/meson.build
index c7f76bd..fd0ead4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -24,6 +24,7 @@
bios_bmc_smm_error_logger_lib = static_library(
'bios_bmc_smm_error_logger',
'pci_handler.cpp',
+ 'buffer.cpp',
implicit_include_directories: false,
dependencies: bios_bmc_smm_error_logger_pre)
diff --git a/test/buffer_test.cpp b/test/buffer_test.cpp
new file mode 100644
index 0000000..ee03163
--- /dev/null
+++ b/test/buffer_test.cpp
@@ -0,0 +1,114 @@
+#include "buffer.hpp"
+#include "data_interface_mock.hpp"
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace bios_bmc_smm_error_logger
+{
+namespace
+{
+
+using ::testing::_;
+using ::testing::ElementsAreArray;
+using ::testing::InSequence;
+using ::testing::Return;
+
+class BufferTest : public ::testing::Test
+{
+ protected:
+ BufferTest() :
+ dataInterfaceMock(std::make_unique<DataInterfaceMock>()),
+ dataInterfaceMockPtr(dataInterfaceMock.get())
+ {
+ bufferImpl = std::make_unique<BufferImpl>(std::move(dataInterfaceMock));
+ testInitializationHeader.bmcInterfaceVersion = testBmcInterfaceVersion;
+ testInitializationHeader.queueSize = testQueueSize;
+ testInitializationHeader.ueRegionSize = testUeRegionSize;
+ testInitializationHeader.magicNumber = testMagicNumber;
+ }
+ ~BufferTest() override = default;
+
+ // CircularBufferHeader size is 0x30, ensure the test region is bigger
+ static constexpr size_t testRegionSize = 0x200;
+ static constexpr uint32_t testBmcInterfaceVersion = 123;
+ static constexpr uint16_t testQueueSize = 0x100;
+ static constexpr uint16_t testUeRegionSize = 0x50;
+ static constexpr std::array<uint32_t, 4> testMagicNumber = {
+ 0x12345678, 0x22345678, 0x32345678, 0x42345678};
+ struct CircularBufferHeader testInitializationHeader
+ {};
+
+ std::unique_ptr<DataInterfaceMock> dataInterfaceMock;
+ DataInterfaceMock* dataInterfaceMockPtr;
+
+ std::unique_ptr<BufferImpl> bufferImpl;
+};
+
+TEST_F(BufferTest, BufferInitializeEraseFail)
+{
+ InSequence s;
+
+ EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
+ .WillOnce(Return(testRegionSize));
+ const std::vector<uint8_t> emptyArray(testRegionSize, 0);
+ // Return a smaller write than the intended testRegionSize to test the error
+ EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
+ .WillOnce(Return(testRegionSize - 1));
+ EXPECT_THROW(
+ try {
+ bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
+ testUeRegionSize, testMagicNumber);
+ } catch (const std::runtime_error& e) {
+ EXPECT_STREQ(e.what(), "Buffer initialization only erased '511'");
+ throw;
+ },
+ std::runtime_error);
+
+ EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
+ .WillOnce(Return(testRegionSize));
+ EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
+ .WillOnce(Return(testRegionSize));
+ // Return a smaller write than the intended initializationHeader to test the
+ // error
+ EXPECT_CALL(*dataInterfaceMockPtr, write(0, _)).WillOnce(Return(0));
+ EXPECT_THROW(
+ try {
+ bufferImpl->initialize(testBmcInterfaceVersion, testQueueSize,
+ testUeRegionSize, testMagicNumber);
+ } catch (const std::runtime_error& e) {
+ EXPECT_STREQ(
+ e.what(),
+ "Buffer initialization buffer header write only wrote '0'");
+ throw;
+ },
+ std::runtime_error);
+}
+
+TEST_F(BufferTest, BufferInitializePass)
+{
+ InSequence s;
+ EXPECT_CALL(*dataInterfaceMockPtr, getMemoryRegionSize())
+ .WillOnce(Return(testRegionSize));
+ const std::vector<uint8_t> emptyArray(testRegionSize, 0);
+ EXPECT_CALL(*dataInterfaceMockPtr, write(0, ElementsAreArray(emptyArray)))
+ .WillOnce(Return(testRegionSize));
+
+ uint8_t* testInitializationHeaderPtr =
+ reinterpret_cast<uint8_t*>(&testInitializationHeader);
+ size_t initializationHeaderSize = sizeof(testInitializationHeader);
+ EXPECT_CALL(*dataInterfaceMockPtr,
+ write(0, ElementsAreArray(testInitializationHeaderPtr,
+ initializationHeaderSize)))
+ .WillOnce(Return(initializationHeaderSize));
+ EXPECT_NO_THROW(bufferImpl->initialize(testBmcInterfaceVersion,
+ testQueueSize, testUeRegionSize,
+ testMagicNumber));
+}
+
+} // namespace
+} // namespace bios_bmc_smm_error_logger
diff --git a/test/include/data_interface_mock.hpp b/test/include/data_interface_mock.hpp
new file mode 100644
index 0000000..942103e
--- /dev/null
+++ b/test/include/data_interface_mock.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include "data_interface.hpp"
+
+#include <cstdint>
+#include <span>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+namespace bios_bmc_smm_error_logger
+{
+
+class DataInterfaceMock : public DataInterface
+{
+ public:
+ MOCK_METHOD(std::vector<uint8_t>, read,
+ (const uint32_t offset, const uint32_t length), (override));
+ MOCK_METHOD(uint32_t, write,
+ (const uint32_t offset, const std::span<const uint8_t> bytes),
+ (override));
+ MOCK_METHOD(uint32_t, getMemoryRegionSize, (), (override));
+};
+
+} // namespace bios_bmc_smm_error_logger
diff --git a/test/meson.build b/test/meson.build
index 081fcd7..6f72d9f 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -17,13 +17,19 @@
endif
endif
+test_dep = declare_dependency(
+ include_directories: include_directories('include'),
+ dependencies: [bios_bmc_smm_error_logger_dep, gtest, gmock, rde_dep]
+)
+
gtests = [
'pci_handler',
'rde_dictionary_manager',
+ 'buffer',
]
foreach t : gtests
test(t, executable(t.underscorify(), t + '_test.cpp',
build_by_default: false,
implicit_include_directories: false,
- dependencies: [bios_bmc_smm_error_logger_dep, gtest, gmock, rde_dep]))
+ dependencies: test_dep))
endforeach