Add a IPMI blob handler for SMBIOS tables
smbios-mdr has dependencies on intel-ipmi-oem which makes BIOS sending
SMBIOS tables to BMC through VGA shared memory. For platforms without
intel-ipmi-oem, implement a IPMI blob hanler so that BIOS can send
SMBIOS tables through IPMI blob interfaces.
Test:
Unit tests for the IPMI blob handler.
Manual test that transfers SMBIOS tables to BMC through IPMI blob
interfaces on a platform host.
Signed-off-by: Jie Yang <jjy@google.com>
Change-Id: I9bc1ae7e9bfaa793e47e38fa19049f0f69355189
diff --git a/src/smbios-ipmi-blobs/handler.cpp b/src/smbios-ipmi-blobs/handler.cpp
new file mode 100644
index 0000000..51d9d95
--- /dev/null
+++ b/src/smbios-ipmi-blobs/handler.cpp
@@ -0,0 +1,280 @@
+#include "handler.hpp"
+
+#include "mdrv2.hpp"
+#include "smbios_mdrv2.hpp"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <ctime>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+namespace internal
+{
+
+constexpr const char* mdrV2Service = "xyz.openbmc_project.Smbios.MDR_V2";
+constexpr const char* mdrV2Interface = "xyz.openbmc_project.Smbios.MDR_V2";
+
+bool syncSmbiosData()
+{
+ bool status = false;
+ sdbusplus::bus::bus bus =
+ sdbusplus::bus::bus(ipmid_get_sd_bus_connection());
+ sdbusplus::message::message method =
+ bus.new_method_call(mdrV2Service, phosphor::smbios::mdrV2Path,
+ mdrV2Interface, "AgentSynchronizeData");
+
+ try
+ {
+ sdbusplus::message::message reply = bus.call(method);
+ reply.read(status);
+ }
+ catch (sdbusplus::exception_t& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Error Sync data with service",
+ phosphor::logging::entry("ERROR=%s", e.what()),
+ phosphor::logging::entry("SERVICE=%s", mdrV2Service),
+ phosphor::logging::entry("PATH=%s", phosphor::smbios::mdrV2Path));
+ return false;
+ }
+
+ if (!status)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Sync data with service failure");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace internal
+
+bool SmbiosBlobHandler::canHandleBlob(const std::string& path)
+{
+ return path == blobId;
+}
+
+std::vector<std::string> SmbiosBlobHandler::getBlobIds()
+{
+ return std::vector<std::string>(1, blobId);
+}
+
+bool SmbiosBlobHandler::deleteBlob(const std::string& path)
+{
+ return false;
+}
+
+bool SmbiosBlobHandler::stat(const std::string& path, struct BlobMeta* meta)
+{
+ if (!blobPtr || blobPtr->blobId != path)
+ {
+ return false;
+ }
+
+ meta->size = blobPtr->buffer.size();
+ meta->blobState = blobPtr->state;
+ return true;
+}
+
+bool SmbiosBlobHandler::open(uint16_t session, uint16_t flags,
+ const std::string& path)
+{
+ if (flags & blobs::OpenFlags::read)
+ {
+ /* Disable the read operation. */
+ return false;
+ }
+
+ /* The handler only allows one session. If an open blob exists, return
+ * false directly.
+ */
+ if (blobPtr)
+ {
+ return false;
+ }
+ blobPtr = std::make_unique<SmbiosBlob>(session, path, flags);
+ return true;
+}
+
+std::vector<uint8_t> SmbiosBlobHandler::read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize)
+{
+ /* SMBIOS blob handler does not support read. */
+ return std::vector<uint8_t>();
+}
+
+bool SmbiosBlobHandler::write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data)
+{
+ if (!blobPtr || blobPtr->sessionId != session)
+ {
+ return false;
+ }
+
+ if (!(blobPtr->state & blobs::StateFlags::open_write))
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "No open blob to write");
+ return false;
+ }
+
+ /* Is the offset beyond the array? */
+ if (offset >= maxBufferSize)
+ {
+ return false;
+ }
+
+ /* Determine whether all their bytes will fit. */
+ uint32_t remain = maxBufferSize - offset;
+ if (data.size() > remain)
+ {
+ return false;
+ }
+
+ /* Resize the buffer if what we're writing will go over the size */
+ uint32_t newBufferSize = data.size() + offset;
+ if (newBufferSize > blobPtr->buffer.size())
+ {
+ blobPtr->buffer.resize(newBufferSize);
+ }
+
+ std::memcpy(blobPtr->buffer.data() + offset, data.data(), data.size());
+ return true;
+}
+
+bool SmbiosBlobHandler::writeMeta(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data)
+{
+ return false;
+}
+
+bool SmbiosBlobHandler::commit(uint16_t session,
+ const std::vector<uint8_t>& data)
+{
+ if (!data.empty())
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Unexpected data provided to commit call");
+ return false;
+ }
+
+ if (!blobPtr || blobPtr->sessionId != session)
+ {
+ return false;
+ }
+
+ /* If a blob is committing or commited, return true directly. But if last
+ * commit fails, may try to commit again.
+ */
+ if (blobPtr->state &
+ (blobs::StateFlags::committing | blobs::StateFlags::committed))
+ {
+ return true;
+ }
+
+ /* Clear the commit_error bit. */
+ blobPtr->state &= ~blobs::StateFlags::commit_error;
+
+ MDRSMBIOSHeader mdrHdr;
+ mdrHdr.mdrType = mdrTypeII;
+ mdrHdr.timestamp = std::time(nullptr);
+ mdrHdr.dataSize = blobPtr->buffer.size();
+ if (access(smbiosPath, F_OK) == -1)
+ {
+ int flag = mkdir(smbiosPath, S_IRWXU);
+ if (flag != 0)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "create folder failed for writting smbios file");
+ blobPtr->state |= blobs::StateFlags::commit_error;
+ return false;
+ }
+ }
+
+ std::ofstream smbiosFile(mdrType2File,
+ std::ios_base::binary | std::ios_base::trunc);
+ if (!smbiosFile.good())
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Write data from flash error - Open SMBIOS table file failure");
+ blobPtr->state |= blobs::StateFlags::commit_error;
+ return false;
+ }
+
+ smbiosFile.exceptions(std::ofstream::badbit | std::ofstream::failbit);
+ try
+ {
+ smbiosFile.write(reinterpret_cast<char*>(&mdrHdr),
+ sizeof(MDRSMBIOSHeader));
+ smbiosFile.write(reinterpret_cast<char*>(blobPtr->buffer.data()),
+ mdrHdr.dataSize);
+ blobPtr->state |= blobs::StateFlags::committing;
+ }
+ catch (std::ofstream::failure& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "Write data from flash error - write data error",
+ phosphor::logging::entry("ERROR=%s", e.what()));
+ blobPtr->state |= blobs::StateFlags::commit_error;
+ return false;
+ }
+
+ if (!internal::syncSmbiosData())
+ {
+ blobPtr->state &= ~blobs::StateFlags::committing;
+ blobPtr->state |= blobs::StateFlags::commit_error;
+ return false;
+ }
+
+ // Unset committing state and set committed state
+ blobPtr->state &= ~blobs::StateFlags::committing;
+ blobPtr->state |= blobs::StateFlags::committed;
+
+ return true;
+}
+
+bool SmbiosBlobHandler::close(uint16_t session)
+{
+ if (!blobPtr || blobPtr->sessionId != session)
+ {
+ return false;
+ }
+
+ blobPtr = nullptr;
+ return true;
+}
+
+bool SmbiosBlobHandler::stat(uint16_t session, struct BlobMeta* meta)
+{
+ if (!blobPtr || blobPtr->sessionId != session)
+ {
+ return false;
+ }
+
+ meta->size = blobPtr->buffer.size();
+ meta->blobState = blobPtr->state;
+ return true;
+}
+
+bool SmbiosBlobHandler::expire(uint16_t session)
+{
+ return close(session);
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/handler.hpp b/src/smbios-ipmi-blobs/handler.hpp
new file mode 100644
index 0000000..d97618a
--- /dev/null
+++ b/src/smbios-ipmi-blobs/handler.hpp
@@ -0,0 +1,77 @@
+#pragma once
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace blobs
+{
+
+class SmbiosBlobHandler : public GenericBlobInterface
+{
+ public:
+ SmbiosBlobHandler() = default;
+ ~SmbiosBlobHandler() = default;
+ SmbiosBlobHandler(const SmbiosBlobHandler&) = delete;
+ SmbiosBlobHandler& operator=(const SmbiosBlobHandler&) = delete;
+ SmbiosBlobHandler(SmbiosBlobHandler&&) = default;
+ SmbiosBlobHandler& operator=(SmbiosBlobHandler&&) = default;
+
+ struct SmbiosBlob
+ {
+ SmbiosBlob(uint16_t id, const std::string& path, uint16_t flags) :
+ sessionId(id), blobId(path), state(0)
+ {
+ if (flags & blobs::OpenFlags::write)
+ {
+ state |= blobs::StateFlags::open_write;
+ }
+
+ /* Pre-allocate the buffer.capacity() with maxBufferSize */
+ buffer.reserve(maxBufferSize);
+ }
+
+ /* The blob handler session id. */
+ uint16_t sessionId;
+
+ /* The identifier for the blob */
+ std::string blobId;
+
+ /* The current state. */
+ uint16_t state;
+
+ /* The staging buffer. */
+ std::vector<uint8_t> buffer;
+ };
+
+ bool canHandleBlob(const std::string& path) override;
+ std::vector<std::string> getBlobIds() override;
+ bool deleteBlob(const std::string& path) override;
+ bool stat(const std::string& path, struct BlobMeta* meta) override;
+ bool open(uint16_t session, uint16_t flags,
+ const std::string& path) override;
+ std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize) override;
+ bool write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+ bool writeMeta(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+ bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
+ bool close(uint16_t session) override;
+ bool stat(uint16_t session, struct BlobMeta* meta) override;
+ bool expire(uint16_t session) override;
+
+ private:
+ static constexpr char blobId[] = "/smbios";
+
+ /* SMBIOS table storage size */
+ static constexpr uint32_t maxBufferSize = 64 * 1024;
+
+ /* The handler only allows one open blob. */
+ std::unique_ptr<SmbiosBlob> blobPtr = nullptr;
+};
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/main.cpp b/src/smbios-ipmi-blobs/main.cpp
new file mode 100644
index 0000000..5f031dd
--- /dev/null
+++ b/src/smbios-ipmi-blobs/main.cpp
@@ -0,0 +1,10 @@
+#include "handler.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <memory>
+
+extern "C" std::unique_ptr<blobs::GenericBlobInterface> createHandler()
+{
+ return std::make_unique<blobs::SmbiosBlobHandler>();
+}
diff --git a/src/smbios-ipmi-blobs/test/handler_open_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_open_unittest.cpp
new file mode 100644
index 0000000..22223cc
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_open_unittest.cpp
@@ -0,0 +1,32 @@
+#include "handler_unittest.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerOpenTest : public SmbiosBlobHandlerTest
+{};
+
+TEST_F(SmbiosBlobHandlerOpenTest, OpenWithBadFlagsFails)
+{
+ // SMBIOS blob handler disables read flag
+
+ EXPECT_FALSE(handler.open(session, blobs::OpenFlags::read, expectedBlobId));
+}
+
+TEST_F(SmbiosBlobHandlerOpenTest, OpenEverythingSucceeds)
+{
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+}
+
+TEST_F(SmbiosBlobHandlerOpenTest, CannotOpenSameSessionTwice)
+{
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_FALSE(
+ handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_readwrite_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_readwrite_unittest.cpp
new file mode 100644
index 0000000..5cd3abc
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_readwrite_unittest.cpp
@@ -0,0 +1,102 @@
+#include "handler_unittest.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::IsEmpty;
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerReadWriteTest : public SmbiosBlobHandlerTest
+{};
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, InvalidSessionWriteIsRejected)
+{
+ // Verify the handler checks for a valid session.
+
+ std::vector<uint8_t> data = {0x1, 0x2};
+ EXPECT_FALSE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, NoWriteFlagRejected)
+{
+ // Verify the handler checks the write flag;
+
+ EXPECT_TRUE(handler.open(session, 0, expectedBlobId));
+
+ std::vector<uint8_t> data = {0x1, 0x2};
+ EXPECT_FALSE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingTooMuchByOneByteFails)
+{
+ int bytes = handlerMaxBufferSize + 1;
+ std::vector<uint8_t> data(bytes, 0x11);
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_FALSE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingTooMuchByOffsetOfOne)
+{
+ std::vector<uint8_t> data(handlerMaxBufferSize, 0x11);
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_FALSE(handler.write(session, 1, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingOneByteBeyondEndFromOffsetFails)
+{
+ std::vector<uint8_t> data = {0x01, 0x02};
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_FALSE(handler.write(session, handlerMaxBufferSize - 1, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingOneByteAtOffsetBeyondEndFails)
+{
+ std::vector<uint8_t> data = {0x01};
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_FALSE(handler.write(session, handlerMaxBufferSize, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingFullBufferAtOffsetZeroSucceeds)
+{
+ std::vector<uint8_t> data(handlerMaxBufferSize, 0x01);
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_TRUE(handler.write(session, 0, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, WritingOneByteToTheLastOffsetSucceeds)
+{
+ std::vector<uint8_t> data = {0x01};
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_TRUE(handler.write(session, handlerMaxBufferSize - 1, data));
+}
+
+TEST_F(SmbiosBlobHandlerReadWriteTest, ReadAlwaysReturnsEmpty)
+{
+ const uint32_t testOffset = 0;
+ const std::vector<uint8_t> testData = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_TRUE(handler.write(session, testOffset, testData));
+
+ EXPECT_THAT(handler.read(session, testOffset, testData.size()), IsEmpty());
+
+ for (size_t i = 0; i < testData.size(); ++i)
+ {
+ EXPECT_THAT(handler.read(session, i, 1), IsEmpty());
+ }
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_statclose_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_statclose_unittest.cpp
new file mode 100644
index 0000000..b8d32cd
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_statclose_unittest.cpp
@@ -0,0 +1,78 @@
+#include "handler_unittest.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerStatCloseTest : public SmbiosBlobHandlerTest
+{
+ protected:
+ blobs::BlobMeta meta;
+
+ // Initialize expected_meta_ with empty members
+ blobs::BlobMeta expected_meta_session = {};
+ blobs::BlobMeta expected_meta_path = {};
+};
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, InvalidSessionStatIsRejected)
+{
+ EXPECT_FALSE(handler.stat(session, &meta));
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, SessionStatAlwaysInitialReadAndWrite)
+{
+ // Verify the session stat returns the information for a session.
+
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+
+ EXPECT_TRUE(handler.stat(session, &meta));
+ expected_meta_session.blobState = blobs::StateFlags::open_write;
+ EXPECT_EQ(meta, expected_meta_session);
+
+ EXPECT_TRUE(handler.stat(expectedBlobId, &meta));
+ expected_meta_path.blobState = blobs::StateFlags::open_write;
+ EXPECT_EQ(meta, expected_meta_path);
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, AfterWriteMetadataLengthMatches)
+{
+ // Verify that after writes, the length returned matches.
+
+ std::vector<uint8_t> data = {0x01};
+ EXPECT_TRUE(handler.open(session, blobs::OpenFlags::write, expectedBlobId));
+ EXPECT_TRUE(handler.write(session, handlerMaxBufferSize - 1, data));
+
+ // We wrote one byte to the last index, making the length the buffer size.
+ EXPECT_TRUE(handler.stat(session, &meta));
+ expected_meta_session.size = handlerMaxBufferSize;
+ expected_meta_session.blobState = blobs::StateFlags::open_write;
+ EXPECT_EQ(meta, expected_meta_session);
+
+ EXPECT_TRUE(handler.stat(expectedBlobId, &meta));
+ expected_meta_path.size = handlerMaxBufferSize;
+ expected_meta_path.blobState = blobs::StateFlags::open_write;
+ EXPECT_EQ(meta, expected_meta_path);
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, CloseWithInvalidSessionFails)
+{
+ // Verify you cannot close an invalid session.
+
+ EXPECT_FALSE(handler.close(session));
+}
+
+TEST_F(SmbiosBlobHandlerStatCloseTest, CloseWithValidSessionSuccess)
+{
+ // Verify you can close a valid session.
+
+ EXPECT_TRUE(handler.open(session, 0, expectedBlobId));
+
+ EXPECT_TRUE(handler.close(session));
+}
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_unittest.cpp b/src/smbios-ipmi-blobs/test/handler_unittest.cpp
new file mode 100644
index 0000000..9395e23
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_unittest.cpp
@@ -0,0 +1,38 @@
+#include "handler_unittest.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerBasicTest : public SmbiosBlobHandlerTest
+{};
+
+TEST_F(SmbiosBlobHandlerBasicTest, CanHandleBlobChecksNameInvalid)
+{
+ // Verify canHandleBlob checks and returns false on an invalid name.
+
+ EXPECT_FALSE(handler.canHandleBlob("asdf"));
+ EXPECT_FALSE(handler.canHandleBlob("smbios"));
+ EXPECT_FALSE(handler.canHandleBlob("/smbios0"));
+ EXPECT_FALSE(handler.canHandleBlob("/smbios/0"));
+}
+
+TEST_F(SmbiosBlobHandlerBasicTest, CanHandleBlobChecksNameVaild)
+{
+ // Verify canHandleBlob checks and returns true on the valid name.
+
+ EXPECT_TRUE(handler.canHandleBlob(expectedBlobId));
+}
+
+TEST_F(SmbiosBlobHandlerBasicTest, GetblobIdsAsExpected)
+{
+ // Verify getBlobIds returns the expected blob list.
+
+ EXPECT_EQ(handler.getBlobIds(), expectedBlobIdList);
+}
+
+} // namespace blobs
diff --git a/src/smbios-ipmi-blobs/test/handler_unittest.hpp b/src/smbios-ipmi-blobs/test/handler_unittest.hpp
new file mode 100644
index 0000000..0b02a79
--- /dev/null
+++ b/src/smbios-ipmi-blobs/test/handler_unittest.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "handler.hpp"
+
+#include <ipmid/api.h>
+#include <systemd/sd-bus.h>
+
+#include <gtest/gtest.h>
+
+sd_bus* ipmid_get_sd_bus_connection()
+{
+ return nullptr;
+}
+
+namespace blobs
+{
+
+class SmbiosBlobHandlerTest : public ::testing::Test
+{
+ protected:
+ SmbiosBlobHandlerTest() = default;
+
+ SmbiosBlobHandler handler;
+
+ const uint16_t session = 0;
+ const std::string expectedBlobId = "/smbios";
+ const std::vector<std::string> expectedBlobIdList = {"/smbios"};
+ const uint32_t handlerMaxBufferSize = 64 * 1024;
+};
+} // namespace blobs