Enable IBM PLDM OEM commands support

Create folder structure (oem/ibm) for ibm pldm oem commands. Move the
files from the ibm-pldm-oem repo [https://github.com/openbmc/ibm-pldm-oem]
to the folder oem/ibm/ under the pldm repo and enable conditional
compilation for it. The test files are also conditionally compiled.

You would need to provide --enable-oem-ibm to configure.

This is done to simplify the build time and runtime dependencies between the
standard and oem implementations.

Signed-off-by: Jinu Joy Thomas <jinu.joy.thomas@in.ibm.com>
Change-Id: Iaa93c73faff87290e3d3d5d155d9ecae6e7ee6f9
diff --git a/README.md b/README.md
index aad37a9..e1347a7 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,40 @@
 Source files are named according to the PLDM Type, for eg base.[hpp/cpp],
 fru.[hpp/cpp], etc.
 
+
+## OEM/vendor-specific functions
+This will support OEM or vendor-specific functions and semantic information.
+Following directory structure has to be used:
+```
+    pldm repo
+     |---- oem
+            |----<oem_name>
+                      |----libpldm
+                            |----<oem based encoding and decoding files>
+                      |----libpldmresponder
+                            |---<oem based handler files>
+
+```
+<oem_name> - This folder must be created with the name of the OEM/vendor
+in lower case. Folders named libpldm and libpldmresponder must be created under
+the folder <oem_name>
+
+Files having the oem functionality for the libpldm library should be placed
+under the folder oem/<oem_name>/libpldm. They must be adhering to the rules
+mentioned under the libpldm section above.
+
+Files having the oem functionality for the libpldmresponder library should be
+placed under the folder oem/<oem_name>/libpldmresponder. They must be adhering
+to the rules mentioned under the libpldmresponder section above.
+
+Once the above is done a conditional flag has to be created in the configure.ac
+to enable conditional compilation.
+
+For consistency would recommend using "--enable-oem-<oem_name>".
+
+The Makefile.am files in libpldm and libpldmresponder will need to be changed
+to allow conditional compilation of the code.
+
 ## TODO
 Consider hosting libpldm above in a repo of its own, probably even outside the
 OpenBMC project? A separate repo would enable something like git submodule.
diff --git a/configure.ac b/configure.ac
index 23e07ff..845a324 100644
--- a/configure.ac
+++ b/configure.ac
@@ -64,6 +64,17 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
+# Setup IBM OEM commands support for PLDM
+AC_ARG_ENABLE([oem-ibm], AS_HELP_STRING([
+	--enable-oem-ibm
+	], [
+	Enable PLDM OEM command support For IBM.
+]))
+AM_CONDITIONAL([OEM_IBM], [test "x$enable_oem_ibm" = "xyes"])
+AS_IF([test "x$oem-ibm" == "xtrue"], [
+    AX_APPEND_COMPILE_FLAGS([-DOEM_IBM], [CXXFLAGS])
+])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile libpldm/Makefile libpldmresponder/Makefile test/Makefile])
 AC_CONFIG_FILES([libpldm/libpldm.pc])
diff --git a/libpldm/Makefile.am b/libpldm/Makefile.am
index e37b639..7dddf02 100644
--- a/libpldm/Makefile.am
+++ b/libpldm/Makefile.am
@@ -11,6 +11,14 @@
 	base.c \
 	platform.c \
 	bios.c
+
+if OEM_IBM
+libpldm_include_HEADERS += \
+        $(top_builddir)/oem/ibm/libpldm/file_io.h
+libpldm_la_SOURCES += \
+        $(top_builddir)/oem/ibm/libpldm/file_io.c
+endif
+
 libpldm_la_LDFLAGS = -version-info 1:0:0 -shared
 libpldm_la_CFLAGS = $(CODE_COVERAGE_CFLAGS)
 libpldm_la_LIBADD = $(CODE_COVERAGE_LIBS)
diff --git a/libpldmresponder/Makefile.am b/libpldmresponder/Makefile.am
index 1b82777..21cfdc8 100644
--- a/libpldmresponder/Makefile.am
+++ b/libpldmresponder/Makefile.am
@@ -13,9 +13,15 @@
 libpldmresponder_la_CXXFLAGS = \
 	$(CODE_COVERAGE_CXXFLAGS) \
 	$(SDBUSPLUS_CFLAGS)
-
 libpldmresponder_la_CPPFLAGS = \
 	$(CODE_COVERAGE_CPPFLAGS) \
+   	-I$(top_builddir)/libpldm/ \
 	-I$(top_srcdir)
 
+if OEM_IBM
+libpldmresponder_la_SOURCES += \
+    $(top_builddir)/oem/ibm/libpldmresponder/file_io.cpp
+libpldmresponder_la_CPPFLAGS += \
+        -I$(top_builddir)/oem/ibm/
+endif
 
diff --git a/oem/ibm/libpldm/.clang-format b/oem/ibm/libpldm/.clang-format
new file mode 100644
index 0000000..a3ef233
--- /dev/null
+++ b/oem/ibm/libpldm/.clang-format
@@ -0,0 +1,6 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: Always
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
diff --git a/oem/ibm/libpldm/file_io.c b/oem/ibm/libpldm/file_io.c
new file mode 100644
index 0000000..ca33cef
--- /dev/null
+++ b/oem/ibm/libpldm/file_io.c
@@ -0,0 +1,54 @@
+#include "file_io.h"
+#include <endian.h>
+#include <string.h>
+
+int decode_rw_file_memory_req(const uint8_t *msg, size_t payload_length,
+			      uint32_t *file_handle, uint32_t *offset,
+			      uint32_t *length, uint64_t *address)
+{
+	if (msg == NULL || file_handle == NULL || offset == NULL ||
+	    length == NULL || address == NULL) {
+		return PLDM_ERROR_INVALID_DATA;
+	}
+
+	if (payload_length != PLDM_RW_FILE_MEM_REQ_BYTES) {
+		return PLDM_ERROR_INVALID_LENGTH;
+	}
+
+	*file_handle = le32toh(*((uint32_t *)msg));
+	*offset = le32toh(*((uint32_t *)(msg + sizeof(*file_handle))));
+	*length = le32toh(
+	    *((uint32_t *)(msg + sizeof(*file_handle) + sizeof(*offset))));
+	*address = le64toh(*((uint64_t *)(msg + sizeof(*file_handle) +
+					  sizeof(*offset) + sizeof(*length))));
+
+	return PLDM_SUCCESS;
+}
+
+int encode_rw_file_memory_resp(uint8_t instance_id, uint8_t command,
+			       uint8_t completion_code, uint32_t length,
+			       struct pldm_msg *msg)
+{
+	struct pldm_header_info header = {0};
+	int rc = PLDM_SUCCESS;
+
+	uint8_t *payload = msg->payload;
+	*payload = completion_code;
+
+	header.msg_type = PLDM_RESPONSE;
+	header.instance = instance_id;
+	header.pldm_type = PLDM_IBM_OEM_TYPE;
+	header.command = command;
+
+	if ((rc = pack_pldm_header(&header, &(msg->hdr))) > PLDM_SUCCESS) {
+		return rc;
+	}
+
+	if (msg->payload[0] == PLDM_SUCCESS) {
+		uint8_t *dst = msg->payload + sizeof(completion_code);
+		length = htole32(length);
+		memcpy(dst, &length, sizeof(length));
+	}
+
+	return PLDM_SUCCESS;
+}
diff --git a/oem/ibm/libpldm/file_io.h b/oem/ibm/libpldm/file_io.h
new file mode 100644
index 0000000..345b363
--- /dev/null
+++ b/oem/ibm/libpldm/file_io.h
@@ -0,0 +1,70 @@
+#ifndef FILEIO_H
+#define FILEIO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base.h"
+
+#define PLDM_IBM_OEM_TYPE 0x3F
+
+/** @brief PLDM Commands in IBM OEM type
+ */
+enum pldm_fileio_commands {
+	PLDM_READ_FILE_INTO_MEMORY = 0x6,
+	PLDM_WRITE_FILE_FROM_MEMORY = 0x7,
+};
+
+/** @brief PLDM Command specific codes
+ */
+enum pldm_fileio_completion_codes {
+	PLDM_INVALID_FILE_HANDLE = 0x80,
+	PLDM_DATA_OUT_OF_RANGE = 0x81,
+	PLDM_INVALID_READ_LENGTH = 0x82,
+	PLDM_INVALID_WRITE_LENGTH = 0x83,
+};
+
+#define PLDM_RW_FILE_MEM_REQ_BYTES 20
+#define PLDM_RW_FILE_MEM_RESP_BYTES 5
+
+/** @brief Decode ReadFileIntoMemory and WriteFileFromMemory commands request
+ *         data
+ *
+ *  @param[in] msg - Pointer to PLDM request message payload
+ *  @param[in] payload_length - Length of request payload
+ *  @param[out] file_handle - A handle to the file
+ *  @param[out] offset - Offset to the file at which the read should begin
+ *  @param[out] length - Number of bytes to be read
+ *  @param[out] address - Memory address where the file content has to be
+ *                        written to
+ *  @return pldm_completion_codes
+ */
+int decode_rw_file_memory_req(const uint8_t *msg, size_t payload_length,
+			      uint32_t *file_handle, uint32_t *offset,
+			      uint32_t *length, uint64_t *address);
+
+/** @brief Create a PLDM response for ReadFileIntoMemory and
+ *         WriteFileFromMemory
+ *
+ *  @param[in] instance_id - Message's instance id
+ *  @param[in] command - PLDM command
+ *  @param[in] completion_code - PLDM completion code
+ *  @param[in] length - Number of bytes read. This could be less than what the
+			 requester asked for.
+ *  @param[in,out] msg - Message will be written to this
+ *  @return pldm_completion_codes
+ *  @note  Caller is responsible for memory alloc and dealloc of param 'msg'
+ */
+int encode_rw_file_memory_resp(uint8_t instance_id, uint8_t command,
+			       uint8_t completion_code, uint32_t length,
+			       struct pldm_msg *msg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FILEIO_H */
diff --git a/oem/ibm/libpldmresponder/file_io.cpp b/oem/ibm/libpldmresponder/file_io.cpp
new file mode 100644
index 0000000..9cf093c
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io.cpp
@@ -0,0 +1,243 @@
+#include "file_io.hpp"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+#include "libpldm/base.h"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+
+namespace dma
+{
+
+/** @struct AspeedXdmaOp
+ *
+ * Structure representing XDMA operation
+ */
+struct AspeedXdmaOp
+{
+    uint64_t hostAddr; //!< the DMA address on the host side, configured by
+                       //!< PCI subsystem.
+    uint32_t len;      //!< the size of the transfer in bytes, it should be a
+                       //!< multiple of 16 bytes
+    uint32_t upstream; //!< boolean indicating the direction of the DMA
+                       //!< operation, true means a transfer from BMC to host.
+};
+
+constexpr auto xdmaDev = "/dev/xdma";
+
+int DMA::transferDataHost(const fs::path& path, uint32_t offset,
+                          uint32_t length, uint64_t address, bool upstream)
+{
+    static const size_t pageSize = getpagesize();
+    uint32_t numPages = length / pageSize;
+    uint32_t pageAlignedLength = numPages * pageSize;
+
+    if (length > pageAlignedLength)
+    {
+        pageAlignedLength += pageSize;
+    }
+
+    auto mmapCleanup = [pageAlignedLength](void* vgaMem) {
+        munmap(vgaMem, pageAlignedLength);
+    };
+
+    int fd = -1;
+    int rc = 0;
+    fd = open(xdmaDev, O_RDWR);
+    if (fd < 0)
+    {
+        rc = -errno;
+        log<level::ERR>("Failed to open the XDMA device", entry("RC=%d", rc));
+        return rc;
+    }
+
+    utils::CustomFD xdmaFd(fd);
+
+    void* vgaMem;
+    vgaMem = mmap(nullptr, pageAlignedLength, upstream ? PROT_WRITE : PROT_READ,
+                  MAP_SHARED, xdmaFd(), 0);
+    if (MAP_FAILED == vgaMem)
+    {
+        rc = -errno;
+        log<level::ERR>("Failed to mmap the XDMA device", entry("RC=%d", rc));
+        return rc;
+    }
+
+    std::unique_ptr<void, decltype(mmapCleanup)> vgaMemPtr(vgaMem, mmapCleanup);
+
+    if (upstream)
+    {
+        std::ifstream stream(path.string());
+
+        stream.seekg(offset);
+        stream.read(static_cast<char*>(vgaMemPtr.get()), length);
+
+        if (static_cast<uint32_t>(stream.gcount()) != length)
+        {
+            log<level::ERR>("mismatch between number of characters to read and "
+                            "the length read",
+                            entry("LENGTH=%d", length),
+                            entry("COUNT=%d", stream.gcount()));
+            return -1;
+        }
+    }
+
+    AspeedXdmaOp xdmaOp;
+    xdmaOp.upstream = upstream ? 1 : 0;
+    xdmaOp.hostAddr = address;
+    xdmaOp.len = length;
+
+    rc = write(xdmaFd(), &xdmaOp, sizeof(xdmaOp));
+    if (rc < 0)
+    {
+        rc = -errno;
+        log<level::ERR>("Failed to execute the DMA operation",
+                        entry("RC=%d", rc), entry("UPSTREAM=%d", upstream),
+                        entry("ADDRESS=%lld", address),
+                        entry("LENGTH=%d", length));
+        return rc;
+    }
+
+    if (!upstream)
+    {
+        std::ofstream stream(path.string());
+
+        stream.seekp(offset);
+        stream.write(static_cast<const char*>(vgaMemPtr.get()), length);
+    }
+
+    return 0;
+}
+
+} // namespace dma
+
+Response readFileIntoMemory(const uint8_t* request, size_t payloadLength)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    uint64_t address = 0;
+    fs::path path("");
+
+    Response response((sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES), 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    if (payloadLength != PLDM_RW_FILE_MEM_REQ_BYTES)
+    {
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_ERROR_INVALID_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    decode_rw_file_memory_req(request, payloadLength, &fileHandle, &offset,
+                              &length, &address);
+
+    if (!fs::exists(path))
+    {
+        log<level::ERR>("File does not exist", entry("HANDLE=%d", fileHandle));
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_INVALID_FILE_HANDLE, 0, responsePtr);
+        return response;
+    }
+
+    auto fileSize = fs::file_size(path);
+    if (offset >= fileSize)
+    {
+        log<level::ERR>("Offset exceeds file size", entry("OFFSET=%d", offset),
+                        entry("FILE_SIZE=%d", fileSize));
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_DATA_OUT_OF_RANGE, 0, responsePtr);
+        return response;
+    }
+
+    if (offset + length > fileSize)
+    {
+        length = fileSize - offset;
+    }
+
+    if (length % dma::minSize)
+    {
+        log<level::ERR>("Read length is not a multiple of DMA minSize",
+                        entry("LENGTH=%d", length));
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_INVALID_READ_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    using namespace dma;
+    DMA intf;
+    return transferAll<DMA>(&intf, PLDM_READ_FILE_INTO_MEMORY, path, offset,
+                            length, address, true);
+}
+
+Response writeFileFromMemory(const uint8_t* request, size_t payloadLength)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    uint64_t address = 0;
+    fs::path path("");
+
+    Response response(sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    if (payloadLength != PLDM_RW_FILE_MEM_REQ_BYTES)
+    {
+        encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                   PLDM_ERROR_INVALID_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    decode_rw_file_memory_req(request, payloadLength, &fileHandle, &offset,
+                              &length, &address);
+
+    if (length % dma::minSize)
+    {
+        log<level::ERR>("Write length is not a multiple of DMA minSize",
+                        entry("LENGTH=%d", length));
+        encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                   PLDM_INVALID_WRITE_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    if (!fs::exists(path))
+    {
+        log<level::ERR>("File does not exist", entry("HANDLE=%d", fileHandle));
+        encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                   PLDM_INVALID_FILE_HANDLE, 0, responsePtr);
+        return response;
+    }
+
+    auto fileSize = fs::file_size(path);
+    if (offset >= fileSize)
+    {
+        log<level::ERR>("Offset exceeds file size", entry("OFFSET=%d", offset),
+                        entry("FILE_SIZE=%d", fileSize));
+        encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                   PLDM_DATA_OUT_OF_RANGE, 0, responsePtr);
+        return response;
+    }
+
+    using namespace dma;
+    DMA intf;
+    return transferAll<DMA>(&intf, PLDM_WRITE_FILE_FROM_MEMORY, path, offset,
+                            length, address, false);
+}
+
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io.hpp b/oem/ibm/libpldmresponder/file_io.hpp
new file mode 100644
index 0000000..17a6fe2
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io.hpp
@@ -0,0 +1,168 @@
+#pragma once
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <filesystem>
+
+#include "libpldm/base.h"
+#include "libpldm/file_io.h"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+using Response = std::vector<uint8_t>;
+
+namespace utils
+{
+
+/** @struct CustomFD
+ *
+ *  RAII wrapper for file descriptor.
+ */
+struct CustomFD
+{
+    CustomFD(const CustomFD&) = delete;
+    CustomFD& operator=(const CustomFD&) = delete;
+    CustomFD(CustomFD&&) = delete;
+    CustomFD& operator=(CustomFD&&) = delete;
+
+    CustomFD(int fd) : fd(fd)
+    {
+    }
+
+    ~CustomFD()
+    {
+        if (fd >= 0)
+        {
+            close(fd);
+        }
+    }
+
+    int operator()() const
+    {
+        return fd;
+    }
+
+  private:
+    int fd = -1;
+};
+
+} // namespace utils
+
+namespace dma
+{
+
+// The minimum data size of dma transfer in bytes
+constexpr uint32_t minSize = 16;
+
+// 16MB - 4096B (16773120 bytes) is the maximum data size of DMA transfer
+constexpr size_t maxSize = (16 * 1024 * 1024) - 4096;
+
+namespace fs = std::filesystem;
+
+/**
+ * @class DMA
+ *
+ * Expose API to initiate transfer of data by DMA
+ *
+ * This class only exposes the public API transferDataHost to transfer data
+ * between BMC and host using DMA. This allows for mocking the transferDataHost
+ * for unit testing purposes.
+ */
+class DMA
+{
+  public:
+    /** @brief API to transfer data between BMC and host using DMA
+     *
+     * @param[in] path     - pathname of the file to transfer data from or to
+     * @param[in] offset   - offset in the file
+     * @param[in] length   - length of the data to transfer
+     * @param[in] address  - DMA address on the host
+     * @param[in] upstream - indicates direction of the transfer; true indicates
+     *                       transfer to the host
+     *
+     * @return returns 0 on success, negative errno on failure
+     */
+    int transferDataHost(const fs::path& path, uint32_t offset, uint32_t length,
+                         uint64_t address, bool upstream);
+};
+
+/** @brief Transfer the data between BMC and host using DMA.
+ *
+ *  There is a max size for each DMA operation, transferAll API abstracts this
+ *  and the requested length is broken down into multiple DMA operations if the
+ *  length exceed max size.
+ *
+ * @tparam[in] T - DMA interface type
+ * @param[in] intf - interface passed to invoke DMA transfer
+ * @param[in] command  - PLDM command
+ * @param[in] path     - pathname of the file to transfer data from or to
+ * @param[in] offset   - offset in the file
+ * @param[in] length   - length of the data to transfer
+ * @param[in] address  - DMA address on the host
+ * @param[in] upstream - indicates direction of the transfer; true indicates
+ *                       transfer to the host
+ * @return PLDM response message
+ */
+
+template <class DMAInterface>
+Response transferAll(DMAInterface* intf, uint8_t command, fs::path& path,
+                     uint32_t offset, uint32_t length, uint64_t address,
+                     bool upstream)
+{
+    uint32_t origLength = length;
+    Response response(sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    while (length > dma::maxSize)
+    {
+        auto rc = intf->transferDataHost(path, offset, dma::maxSize, address,
+                                         upstream);
+        if (rc < 0)
+        {
+            encode_rw_file_memory_resp(0, command, PLDM_ERROR, 0, responsePtr);
+            return response;
+        }
+
+        offset += dma::maxSize;
+        length -= dma::maxSize;
+        address += dma::maxSize;
+    }
+
+    auto rc = intf->transferDataHost(path, offset, length, address, upstream);
+    if (rc < 0)
+    {
+        encode_rw_file_memory_resp(0, command, PLDM_ERROR, 0, responsePtr);
+        return response;
+    }
+
+    encode_rw_file_memory_resp(0, command, PLDM_SUCCESS, origLength,
+                               responsePtr);
+    return response;
+}
+
+} // namespace dma
+
+/** @brief Handler for readFileIntoMemory command
+ *
+ *  @param[in] request - pointer to PLDM request payload
+ *  @param[in] payloadLength - length of the message payload
+ *
+ *  @return PLDM response message
+ */
+Response readFileIntoMemory(const uint8_t* request, size_t payloadLength);
+
+/** @brief Handler for writeFileIntoMemory command
+ *
+ *  @param[in] request - pointer to PLDM request payload
+ *  @param[in] payloadLength - length of the message payload
+ *
+ *  @return PLDM response message
+ */
+Response writeFileFromMemory(const uint8_t* request, size_t payloadLength);
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/test/libpldm_fileio_test.cpp b/oem/ibm/test/libpldm_fileio_test.cpp
new file mode 100644
index 0000000..2a5dce1
--- /dev/null
+++ b/oem/ibm/test/libpldm_fileio_test.cpp
@@ -0,0 +1,132 @@
+#include <string.h>
+
+#include <array>
+
+#include "libpldm/base.h"
+#include "libpldm/file_io.h"
+
+#include <gtest/gtest.h>
+
+TEST(ReadWriteFileMemory, testGoodDecodeRequest)
+{
+    std::array<uint8_t, PLDM_RW_FILE_MEM_REQ_BYTES> requestMsg{};
+
+    // Random value for fileHandle, offset, length, address
+    uint32_t fileHandle = 0x12345678;
+    uint32_t offset = 0x87654321;
+    uint32_t length = 0x13245768;
+    uint64_t address = 0x124356879ACBDE0F;
+
+    memcpy(requestMsg.data(), &fileHandle, sizeof(fileHandle));
+    memcpy(requestMsg.data() + sizeof(fileHandle), &offset, sizeof(offset));
+    memcpy(requestMsg.data() + sizeof(fileHandle) + sizeof(offset), &length,
+           sizeof(length));
+    memcpy(requestMsg.data() + sizeof(fileHandle) + sizeof(offset) +
+               sizeof(length),
+           &address, sizeof(address));
+
+    uint32_t retFileHandle = 0;
+    uint32_t retOffset = 0;
+    uint32_t retLength = 0;
+    uint64_t retAddress = 0;
+
+    // Invoke decode the read file memory request
+    auto rc = decode_rw_file_memory_req(requestMsg.data(), requestMsg.size(),
+                                        &retFileHandle, &retOffset, &retLength,
+                                        &retAddress);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(fileHandle, retFileHandle);
+    ASSERT_EQ(offset, retOffset);
+    ASSERT_EQ(length, retLength);
+    ASSERT_EQ(address, retAddress);
+}
+
+TEST(ReadWriteFileMemory, testBadDecodeRequest)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    uint64_t address = 0;
+
+    // Request payload message is missing
+    auto rc = decode_rw_file_memory_req(NULL, 0, &fileHandle, &offset, &length,
+                                        &address);
+    ASSERT_EQ(rc, PLDM_ERROR_INVALID_DATA);
+
+    std::array<uint8_t, PLDM_RW_FILE_MEM_REQ_BYTES> requestMsg{};
+
+    // Address is NULL
+    rc = decode_rw_file_memory_req(requestMsg.data(), requestMsg.size(),
+                                   &fileHandle, &offset, &length, NULL);
+    ASSERT_EQ(rc, PLDM_ERROR_INVALID_DATA);
+
+    // Payload length is invalid
+    rc = decode_rw_file_memory_req(requestMsg.data(), 0, &fileHandle, &offset,
+                                   &length, &address);
+    ASSERT_EQ(rc, PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(ReadWriteFileMemory, testGoodEncodeResponse)
+{
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES>
+        responseMsg{};
+    uint32_t length = 0xFF00EE11;
+    pldm_msg* response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+
+    // ReadFileIntoMemory
+    auto rc = encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                         PLDM_SUCCESS, length, response);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(response->hdr.request, PLDM_RESPONSE);
+    ASSERT_EQ(response->hdr.instance_id, 0);
+    ASSERT_EQ(response->hdr.type, PLDM_IBM_OEM_TYPE);
+    ASSERT_EQ(response->hdr.command, PLDM_READ_FILE_INTO_MEMORY);
+    ASSERT_EQ(response->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(response->payload + sizeof(response->payload[0]),
+                        &length, sizeof(length)));
+
+    // WriteFileFromMemory
+    rc = encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                    PLDM_SUCCESS, length, response);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(response->hdr.request, PLDM_RESPONSE);
+    ASSERT_EQ(response->hdr.instance_id, 0);
+    ASSERT_EQ(response->hdr.type, PLDM_IBM_OEM_TYPE);
+    ASSERT_EQ(response->hdr.command, PLDM_WRITE_FILE_FROM_MEMORY);
+    ASSERT_EQ(response->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(response->payload + sizeof(response->payload[0]),
+                        &length, sizeof(length)));
+}
+
+TEST(ReadWriteFileMemory, testBadEncodeResponse)
+{
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES>
+        responseMsg{};
+    uint32_t length = 0;
+    pldm_msg* response = reinterpret_cast<pldm_msg*>(responseMsg.data());
+
+    // ReadFileIntoMemory
+    auto rc = encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                         PLDM_ERROR, length, response);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(response->hdr.request, PLDM_RESPONSE);
+    ASSERT_EQ(response->hdr.instance_id, 0);
+    ASSERT_EQ(response->hdr.type, PLDM_IBM_OEM_TYPE);
+    ASSERT_EQ(response->hdr.command, PLDM_READ_FILE_INTO_MEMORY);
+    ASSERT_EQ(response->payload[0], PLDM_ERROR);
+
+    // WriteFileFromMemory
+    rc = encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY, PLDM_ERROR,
+                                    length, response);
+
+    ASSERT_EQ(rc, PLDM_SUCCESS);
+    ASSERT_EQ(response->hdr.request, PLDM_RESPONSE);
+    ASSERT_EQ(response->hdr.instance_id, 0);
+    ASSERT_EQ(response->hdr.type, PLDM_IBM_OEM_TYPE);
+    ASSERT_EQ(response->hdr.command, PLDM_WRITE_FILE_FROM_MEMORY);
+    ASSERT_EQ(response->payload[0], PLDM_ERROR);
+}
diff --git a/oem/ibm/test/libpldmresponder_fileio_test.cpp b/oem/ibm/test/libpldmresponder_fileio_test.cpp
new file mode 100644
index 0000000..060b0ed
--- /dev/null
+++ b/oem/ibm/test/libpldmresponder_fileio_test.cpp
@@ -0,0 +1,189 @@
+#include "libpldmresponder/file_io.hpp"
+
+#include "libpldm/base.h"
+#include "libpldm/file_io.h"
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include <systemd/sd-journal.h>
+
+std::vector<std::string> logs;
+
+extern "C" {
+
+int sd_journal_send(const char* format, ...)
+{
+    logs.push_back(format);
+    return 0;
+}
+
+int sd_journal_send_with_location(const char* file, const char* line,
+                                  const char* func, const char* format, ...)
+{
+    logs.push_back(format);
+    return 0;
+}
+}
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace dma
+{
+
+class MockDMA
+{
+  public:
+    MOCK_METHOD5(transferDataHost,
+                 int(const fs::path& file, uint32_t offset, uint32_t length,
+                     uint64_t address, bool upstream));
+};
+
+} // namespace dma
+} // namespace responder
+} // namespace pldm
+using namespace pldm::responder;
+using ::testing::_;
+using ::testing::Return;
+
+TEST(TransferDataHost, GoodPath)
+{
+    using namespace pldm::responder::dma;
+
+    MockDMA dmaObj;
+    fs::path path("");
+
+    // Minimum length of 16 and expect transferDataHost to be called once
+    // returns the default value of 0 (the return type of transferDataHost is
+    // int, the default value for int is 0)
+    uint32_t length = minSize;
+    EXPECT_CALL(dmaObj, transferDataHost(path, 0, length, 0, true)).Times(1);
+    auto response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY,
+                                         path, 0, length, 0, true);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]),
+                        &length, sizeof(length)));
+
+    // maxsize of DMA
+    length = maxSize;
+    EXPECT_CALL(dmaObj, transferDataHost(path, 0, length, 0, true)).Times(1);
+    response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path,
+                                    0, length, 0, true);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]),
+                        &length, sizeof(length)));
+
+    // length greater than maxsize of DMA
+    length = maxSize + minSize;
+    EXPECT_CALL(dmaObj, transferDataHost(path, 0, maxSize, 0, true)).Times(1);
+    EXPECT_CALL(dmaObj, transferDataHost(path, maxSize, minSize, maxSize, true))
+        .Times(1);
+    response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path,
+                                    0, length, 0, true);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]),
+                        &length, sizeof(length)));
+
+    // length greater than 2*maxsize of DMA
+    length = 3 * maxSize;
+    EXPECT_CALL(dmaObj, transferDataHost(_, _, _, _, true)).Times(3);
+    response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path,
+                                    0, length, 0, true);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]),
+                        &length, sizeof(length)));
+
+    // check for downstream(copy data from host to BMC) parameter
+    length = minSize;
+    EXPECT_CALL(dmaObj, transferDataHost(path, 0, length, 0, false)).Times(1);
+    response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path,
+                                    0, length, 0, false);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS);
+    ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]),
+                        &length, sizeof(length)));
+}
+
+TEST(TransferDataHost, BadPath)
+{
+    using namespace pldm::responder::dma;
+
+    MockDMA dmaObj;
+    fs::path path("");
+
+    // Minimum length of 16 and transferDataHost returning a negative errno
+    uint32_t length = minSize;
+    EXPECT_CALL(dmaObj, transferDataHost(_, _, _, _, _)).WillOnce(Return(-1));
+    auto response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY,
+                                         path, 0, length, 0, true);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR);
+
+    // length greater than maxsize of DMA and transferDataHost returning a
+    // negative errno
+    length = maxSize + minSize;
+    EXPECT_CALL(dmaObj, transferDataHost(_, _, _, _, _)).WillOnce(Return(-1));
+    response = transferAll<MockDMA>(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path,
+                                    0, length, 0, true);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR);
+}
+
+TEST(ReadFileIntoMemory, BadPath)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 10;
+    uint64_t address = 0;
+
+    std::array<uint8_t, PLDM_RW_FILE_MEM_REQ_BYTES> requestMsg{};
+    memcpy(requestMsg.data(), &fileHandle, sizeof(fileHandle));
+    memcpy(requestMsg.data() + sizeof(fileHandle), &offset, sizeof(offset));
+    memcpy(requestMsg.data() + sizeof(fileHandle) + sizeof(offset), &length,
+           sizeof(length));
+    memcpy(requestMsg.data() + sizeof(fileHandle) + sizeof(offset) +
+               sizeof(length),
+           &address, sizeof(address));
+
+    // Pass invalid payload length
+    auto response = readFileIntoMemory(requestMsg.data(), 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH);
+}
+
+TEST(WriteFileFromMemory, BadPath)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 10;
+    uint64_t address = 0;
+
+    std::array<uint8_t, PLDM_RW_FILE_MEM_REQ_BYTES> requestMsg{};
+    memcpy(requestMsg.data(), &fileHandle, sizeof(fileHandle));
+    memcpy(requestMsg.data() + sizeof(fileHandle), &offset, sizeof(offset));
+    memcpy(requestMsg.data() + sizeof(fileHandle) + sizeof(offset), &length,
+           sizeof(length));
+    memcpy(requestMsg.data() + sizeof(fileHandle) + sizeof(offset) +
+               sizeof(length),
+           &address, sizeof(address));
+
+    // Pass invalid payload length
+    auto response = writeFileFromMemory(requestMsg.data(), 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH);
+
+    // The length field is not a multiple of DMA minsize
+    response = writeFileFromMemory(requestMsg.data(), requestMsg.size());
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_WRITE_LENGTH);
+}
diff --git a/test/Makefile.am b/test/Makefile.am
index 71d0a02..ca75b47 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -9,6 +9,12 @@
 	libpldm_bios_test \
 	libpldmresponder_bios_test
 
+if OEM_IBM
+check_PROGRAMS += \
+	libpldmoem_fileio_test \
+	libpldmoemresponder_fileio_test
+endif
+
 test_cppflags = \
 	-Igtest \
 	$(GTEST_CPPFLAGS) \
@@ -24,6 +30,13 @@
 	$(PTHREAD_LIBS) \
 	$(OESDK_TESTCASE_FLAGS)
 
+if OEM_IBM
+test_ldflags += \
+	$(SDBUSPLUS_LIBS) \
+	-lgmock \
+	-lstdc++fs
+endif
+
 libpldm_base_test_CPPFLAGS = $(test_cppflags)
 libpldm_base_test_CXXFLAGS = $(test_cxxflags)
 libpldm_base_test_LDFLAGS = $(test_ldflags)
@@ -65,7 +78,6 @@
 libpldmresponder_bios_test_SOURCES = \
 	libpldmresponder_bios_test.cpp
 
-
 libpldmresponder_base_test_CPPFLAGS = $(test_cppflags)
 libpldmresponder_base_test_CXXFLAGS = $(test_cxxflags)
 libpldmresponder_base_test_LDFLAGS = $(test_ldflags)
@@ -73,5 +85,28 @@
 	$(top_builddir)/libpldm/libpldm_la-base.o \
 	$(top_builddir)/libpldmresponder/libpldmresponder_la-base.o \
 	$(CODE_COVERAGE_LIBS)
-
 libpldmresponder_base_test_SOURCES = libpldmresponder_base_test.cpp
+
+if OEM_IBM
+libpldmoem_fileio_test_CPPFLAGS = $(test_cppflags) \
+	-I$(top_builddir)/oem/ibm/ \
+	-I$(top_builddir)/libpldm/
+libpldmoem_fileio_test_CXXFLAGS = $(test_cxxflags)
+libpldmoem_fileio_test_LDFLAGS = $(test_ldflags)
+libpldmoem_fileio_test_LDADD = \
+	$(top_builddir)/libpldm/libpldm_la-base.o \
+	$(top_builddir)/oem/ibm/libpldm/libpldm_la-file_io.o
+libpldmoem_fileio_test_SOURCES = $(top_builddir)/oem/ibm/test/libpldm_fileio_test.cpp
+
+libpldmoemresponder_fileio_test_CPPFLAGS = $(test_cppflags) \
+	-I$(top_builddir)/oem/ibm/ \
+	-I$(top_builddir)/libpldm/
+libpldmoemresponder_fileio_test_CXXFLAGS = $(test_cxxflags)
+libpldmoemresponder_fileio_test_LDFLAGS = $(test_ldflags)
+libpldmoemresponder_fileio_test_LDADD = \
+	$(top_builddir)/libpldm/libpldm_la-base.o \
+	$(top_builddir)/oem/ibm/libpldm/libpldm_la-file_io.o \
+	$(top_builddir)/oem/ibm/libpldmresponder/libpldmresponder_la-file_io.o
+libpldmoemresponder_fileio_test_SOURCES = $(top_builddir)/oem/ibm/test/libpldmresponder_fileio_test.cpp
+endif
+