firmware: add read/write configuration methods

A data handler may require the host-side client to read or write
configuration information.  Therefore, implement a method for
controlling this in the data handler object.

Change-Id: Id7f8ff54d90cece2e8751773a8696638c2a2ea77
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/data_handler.hpp b/data_handler.hpp
index 79e228f..2df5b15 100644
--- a/data_handler.hpp
+++ b/data_handler.hpp
@@ -21,6 +21,22 @@
      * @return the bytes read
      */
     virtual std::vector<std::uint8_t> copyFrom(std::uint32_t length) = 0;
+
+    /**
+     * set configuration.
+     *
+     * @param[in] configuration - byte vector of data.
+     * @return bool - returns true on success.
+     */
+    virtual bool write(const std::vector<std::uint8_t>& configuration) = 0;
+
+    /**
+     * read configuration.
+     *
+     * @return bytes - whatever bytes are required configuration information for
+     * the mechanism.
+     */
+    virtual std::vector<std::uint8_t> read() = 0;
 };
 
 struct DataHandlerPack
diff --git a/firmware_handler.cpp b/firmware_handler.cpp
index 2ee4155..59ef651 100644
--- a/firmware_handler.cpp
+++ b/firmware_handler.cpp
@@ -304,17 +304,30 @@
     return item->second->imageHandler->write(offset, bytes);
 }
 
+/*
+ * If the active session (image or hash) is over LPC, this allows
+ * configuring it.  This option is only available before you start
+ * writing data for the given item (image or hash).  This will return
+ * false at any other part. -- the lpc handler portion will know to return
+ * false.
+ */
 bool FirmwareBlobHandler::writeMeta(uint16_t session, uint32_t offset,
                                     const std::vector<uint8_t>& data)
 {
-    /*
-     * If the active session (image or hash) is over LPC, this allows
-     * configuring it.  This option is only available before you start
-     * writing data for the given item (image or hash).  This will return
-     * false at any other part.
-     */
-    return false;
+    auto item = lookup.find(session);
+    if (item == lookup.end())
+    {
+        return false;
+    }
+
+    if (item->second->flags & FirmwareUpdateFlags::ipmi)
+    {
+        return false;
+    }
+
+    return item->second->dataHandler->write(data);
 }
+
 bool FirmwareBlobHandler::commit(uint16_t session,
                                  const std::vector<uint8_t>& data)
 {
diff --git a/lpc_handler.cpp b/lpc_handler.cpp
index acd58c8..94bc73d 100644
--- a/lpc_handler.cpp
+++ b/lpc_handler.cpp
@@ -1,6 +1,7 @@
 #include "lpc_handler.hpp"
 
 #include <cstdint>
+#include <cstring>
 #include <vector>
 
 namespace blobs
@@ -12,4 +13,26 @@
     return {};
 }
 
+bool LpcDataHandler::write(const std::vector<std::uint8_t>& configuration)
+{
+    struct LpcRegion lpcRequest;
+
+    if (configuration.size() != sizeof(lpcRequest))
+    {
+        return false;
+    }
+
+    std::memcpy(&lpcRequest, configuration.data(), configuration.size());
+    /* TODO: Implement the call to the driver or aspeed lpc ctrl library to send
+     * ioctl.
+     */
+
+    return false;
+}
+
+std::vector<std::uint8_t> LpcDataHandler::read()
+{
+    return {};
+}
+
 } // namespace blobs
diff --git a/lpc_handler.hpp b/lpc_handler.hpp
index 903cf26..27f0e03 100644
--- a/lpc_handler.hpp
+++ b/lpc_handler.hpp
@@ -8,13 +8,27 @@
 namespace blobs
 {
 
+struct LpcRegion
+{
+    /* Host LPC address where the chunk is to be mapped. */
+    std::uint32_t address;
+
+    /* Size of the chunk to be mapped. */
+    std::uint32_t length;
+} __attribute__((packed));
+
+/**
+ * Data Handler for configuration the ASPEED LPC memory region, reading and
+ * writing data.
+ */
 class LpcDataHandler : public DataInterface
 {
-
   public:
     LpcDataHandler() = default;
 
     std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+    bool write(const std::vector<std::uint8_t>& configuration) override;
+    std::vector<std::uint8_t> read() override;
 };
 
 } // namespace blobs
diff --git a/pci_handler.cpp b/pci_handler.cpp
index 91edf63..1bcd700 100644
--- a/pci_handler.cpp
+++ b/pci_handler.cpp
@@ -12,4 +12,22 @@
     return {};
 }
 
+bool PciDataHandler::write(const std::vector<std::uint8_t>& configuration)
+{
+    /* PCI handler doesn't require configuration write, only read. */
+    return false;
+}
+
+std::vector<std::uint8_t> PciDataHandler::read()
+{
+    /* PCI handler does require returning a configuration from read. */
+    struct PciConfigResponse reply;
+    reply.address = 0;
+
+    std::vector<std::uint8_t> bytes;
+    bytes.resize(sizeof(reply));
+
+    return bytes;
+}
+
 } // namespace blobs
diff --git a/pci_handler.hpp b/pci_handler.hpp
index df936e4..2594f09 100644
--- a/pci_handler.hpp
+++ b/pci_handler.hpp
@@ -8,13 +8,23 @@
 namespace blobs
 {
 
+/** P2A configuration response. */
+struct PciConfigResponse
+{
+    std::uint32_t address;
+} __attribute__((packed));
+
+/**
+ * Data handler for reading and writing data via the P2A bridge.
+ */
 class PciDataHandler : public DataInterface
 {
-
   public:
     PciDataHandler() = default;
 
     std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+    bool write(const std::vector<std::uint8_t>& configuration) override;
+    std::vector<std::uint8_t> read() override;
 };
 
 } // namespace blobs
diff --git a/test/Makefile.am b/test/Makefile.am
index 243e733..aeb3758 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -14,7 +14,8 @@
 	firmware_stat_unittest \
 	firmware_canhandle_unittest \
 	firmware_open_unittest \
-	firmware_write_unittest
+	firmware_write_unittest \
+	firmware_writemeta_unittest
 
 TESTS = $(check_PROGRAMS)
 
@@ -32,3 +33,6 @@
 
 firmware_write_unittest_SOURCES = firmware_write_unittest.cpp
 firmware_write_unittest_LDADD = $(top_builddir)/firmware_handler.o
+
+firmware_writemeta_unittest_SOURCES = firmware_writemeta_unittest.cpp
+firmware_writemeta_unittest_LDADD = $(top_builddir)/firmware_handler.o
diff --git a/test/data_mock.hpp b/test/data_mock.hpp
index 1fd1fae..a79c562 100644
--- a/test/data_mock.hpp
+++ b/test/data_mock.hpp
@@ -13,6 +13,8 @@
     virtual ~DataHandlerMock() = default;
 
     MOCK_METHOD1(copyFrom, std::vector<std::uint8_t>(std::uint32_t));
+    MOCK_METHOD1(write, bool(const std::vector<std::uint8_t>&));
+    MOCK_METHOD0(read, std::vector<std::uint8_t>());
 };
 
 } // namespace blobs
diff --git a/test/firmware_writemeta_unittest.cpp b/test/firmware_writemeta_unittest.cpp
new file mode 100644
index 0000000..0a795c9
--- /dev/null
+++ b/test/firmware_writemeta_unittest.cpp
@@ -0,0 +1,69 @@
+#include "data_mock.hpp"
+#include "firmware_handler.hpp"
+#include "image_mock.hpp"
+
+#include <gtest/gtest.h>
+
+namespace blobs
+{
+using ::testing::Eq;
+using ::testing::Return;
+
+TEST(FirmwareHandlerWriteMetaTest, WriteConfigParametersFailIfOverIPMI)
+{
+    ImageHandlerMock imageMock1, imageMock2;
+    std::vector<HandlerPack> blobs = {
+        {FirmwareBlobHandler::hashBlobID, &imageMock1},
+        {"asdf", &imageMock2},
+    };
+
+    DataHandlerMock dataMock;
+
+    std::vector<DataHandlerPack> data = {
+        {FirmwareBlobHandler::FirmwareUpdateFlags::ipmi, nullptr},
+        {FirmwareBlobHandler::FirmwareUpdateFlags::lpc, &dataMock},
+    };
+
+    auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(blobs, data);
+
+    EXPECT_CALL(imageMock2, open("asdf")).WillOnce(Return(true));
+
+    EXPECT_TRUE(handler->open(
+        0, OpenFlags::write | FirmwareBlobHandler::FirmwareUpdateFlags::ipmi,
+        "asdf"));
+
+    std::vector<std::uint8_t> bytes = {0xaa, 0x55};
+
+    EXPECT_FALSE(handler->writeMeta(0, 0, bytes));
+}
+
+TEST(FirmwareHandlerWriteMetaTest, WriteConfigParametersPassedThrough)
+{
+    ImageHandlerMock imageMock1, imageMock2;
+    std::vector<HandlerPack> blobs = {
+        {FirmwareBlobHandler::hashBlobID, &imageMock1},
+        {"asdf", &imageMock2},
+    };
+
+    DataHandlerMock dataMock;
+
+    std::vector<DataHandlerPack> data = {
+        {FirmwareBlobHandler::FirmwareUpdateFlags::ipmi, nullptr},
+        {FirmwareBlobHandler::FirmwareUpdateFlags::lpc, &dataMock},
+    };
+
+    auto handler = FirmwareBlobHandler::CreateFirmwareBlobHandler(blobs, data);
+
+    EXPECT_CALL(imageMock2, open("asdf")).WillOnce(Return(true));
+
+    EXPECT_TRUE(handler->open(
+        0, OpenFlags::write | FirmwareBlobHandler::FirmwareUpdateFlags::lpc,
+        "asdf"));
+
+    std::vector<std::uint8_t> bytes = {0x01, 0x02, 0x03, 0x04};
+
+    EXPECT_CALL(dataMock, write(Eq(bytes))).WillOnce(Return(true));
+    EXPECT_TRUE(handler->writeMeta(0, 0, bytes));
+}
+
+} // namespace blobs