Add responder for ReadFileIntoMemory command

The ReadFileIntoMemory command transfers data from BMC to Host
using DMA. If the data to be transferred is more than the maximum size
of DMA operation, the command implementation takes care of breaking
down into multiple DMA operations.

Change-Id: If9d41a1c57f4bbc1951e9889e2a46d63d34ce60e
Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index e2a2d96..406e1bc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1 +1 @@
-SUBDIRS = libpldm test
+SUBDIRS = libpldm libpldmresponder test
diff --git a/configure.ac b/configure.ac
index 88c4d9b..53d9228 100644
--- a/configure.ac
+++ b/configure.ac
@@ -20,6 +20,9 @@
 # For linking
 LT_INIT
 
+# Check for needed modules
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging])
+
 # Check/set gtest specific functions.
 AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
 AC_SUBST(GTEST_CPPFLAGS)
@@ -42,5 +45,5 @@
 )
 
 # Create configured output
-AC_CONFIG_FILES([Makefile libpldm/Makefile test/Makefile])
+AC_CONFIG_FILES([Makefile libpldm/Makefile libpldmresponder/Makefile test/Makefile])
 AC_OUTPUT
diff --git a/libpldmresponder/Makefile.am b/libpldmresponder/Makefile.am
new file mode 100644
index 0000000..597cbd3
--- /dev/null
+++ b/libpldmresponder/Makefile.am
@@ -0,0 +1,10 @@
+libpldmoemresponder_LTLIBRARIES = libpldmoemresponder.la
+libpldmoemresponderdir = ${libdir}
+libpldmoemresponder_la_SOURCES = \
+	file_io.cpp
+
+libpldmoemresponder_la_LIBADD = \
+	../libpldm/libpldmoem.la
+libpldmoemresponder_la_LDFLAGS = \
+	-version-info 1:0:0 -shared \
+        -lstdc++fs
diff --git a/libpldmresponder/file_io.cpp b/libpldmresponder/file_io.cpp
new file mode 100644
index 0000000..9caa45a
--- /dev/null
+++ b/libpldmresponder/file_io.cpp
@@ -0,0 +1,178 @@
+#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;
+
+int transferDatatoHost(const fs::path& file, uint32_t offset, uint32_t length,
+                       uint64_t address)
+{
+    // Align the length of the memory mapping to the page size.
+    static const size_t pageSize = getpagesize();
+    uint32_t numPages = length / pageSize;
+    uint32_t pageLength = numPages * pageSize;
+    if (length > pageLength)
+    {
+        pageLength += pageSize;
+    }
+
+    auto mmapCleanup = [pageLength](void* vgaMem) {
+        munmap(vgaMem, pageLength);
+    };
+
+    int fd = -1;
+    int rc = 0;
+    fd = open(dma::xdmaDev, O_RDWR);
+    if (fd < 0)
+    {
+        log<level::ERR>("Opening the xdma device failed", entry("RC=%d", rc));
+        return rc;
+    }
+    auto xdmaFDPtr = std::make_unique<utils::CustomFD>(fd);
+    auto& xdmaFD = *(xdmaFDPtr.get());
+
+    void* vgaMem = nullptr;
+
+    vgaMem = mmap(nullptr, pageLength, PROT_WRITE, MAP_SHARED, xdmaFD(), 0);
+    if (MAP_FAILED == vgaMem)
+    {
+        rc = -errno;
+        log<level::ERR>("mmap operation failed", entry("RC=%d", rc));
+        return rc;
+    }
+    std::unique_ptr<void, decltype(mmapCleanup)> vgaMemPtr(vgaMem, mmapCleanup);
+
+    // Populate the VGA memory with the contents of the file
+    std::ifstream stream(file.string());
+    stream.seekg(offset);
+    stream.read(static_cast<char*>(vgaMemPtr.get()), length);
+    if (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;
+    }
+
+    struct dma::AspeedXdmaOp xdmaOp
+    {
+    };
+    xdmaOp.upstream = true;
+    xdmaOp.hostAddr = address;
+    xdmaOp.len = length;
+
+    // Initiate the DMA operation
+    rc = write(xdmaFD(), &xdmaOp, sizeof(xdmaOp));
+    if (rc < 0)
+    {
+        rc = -errno;
+        log<level::ERR>("the dma operation failed", entry("RC=%d", rc),
+                        entry("UPSTREAM=%d", xdmaOp.upstream),
+                        entry("ADDRESS=%lld", address),
+                        entry("LENGTH=%d", length));
+        return rc;
+    }
+
+    return rc;
+}
+
+void readFileIntoMemory(const uint8_t* request, size_t payloadLength,
+                        pldm_msg* response)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    uint64_t address = 0;
+
+    if (payloadLength != PLDM_READ_FILE_MEM_REQ_BYTES)
+    {
+        encode_read_file_memory_resp(0, PLDM_ERROR_INVALID_LENGTH, 0, response);
+        return;
+    }
+
+    decode_read_file_memory_req(request, payloadLength, &fileHandle, &offset,
+                                &length, &address);
+
+    constexpr auto readFilePath = "";
+
+    fs::path path{readFilePath};
+    if (!fs::exists(path))
+    {
+        log<level::ERR>("File does not exist", entry("HANDLE=%d", fileHandle));
+        encode_read_file_memory_resp(0, PLDM_INVALID_FILE_HANDLE, 0, response);
+        return;
+    }
+
+    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_read_file_memory_resp(0, PLDM_DATA_OUT_OF_RANGE, 0, response);
+        return;
+    }
+
+    if (offset + length > fileSize)
+    {
+        length = fileSize - offset;
+    }
+
+    if (length % dma::minSize)
+    {
+        log<level::ERR>("Readlength is not a multiple of DMA minSize",
+                        entry("LENGTH=%d", length));
+        encode_read_file_memory_resp(0, PLDM_INVALID_READ_LENGTH, 0, response);
+        return;
+    }
+
+    uint32_t origLength = length;
+
+    while (length > 0)
+    {
+        if (length > dma::maxSize)
+        {
+            auto rc =
+                dma::transferDatatoHost(path, offset, dma::maxSize, address);
+            if (rc < 0)
+            {
+                encode_read_file_memory_resp(0, PLDM_ERROR, 0, response);
+                return;
+            }
+            offset += dma::maxSize;
+            length -= dma::maxSize;
+            address += dma::maxSize;
+        }
+        else
+        {
+            auto rc = dma::transferDatatoHost(path, offset, length, address);
+            if (rc < 0)
+            {
+                encode_read_file_memory_resp(0, PLDM_ERROR, 0, response);
+                return;
+            }
+            encode_read_file_memory_resp(0, PLDM_SUCCESS, origLength, response);
+            return;
+        }
+    }
+}
+
+} // namespace responder
+} // namespace pldm
diff --git a/libpldmresponder/file_io.hpp b/libpldmresponder/file_io.hpp
new file mode 100644
index 0000000..a983963
--- /dev/null
+++ b/libpldmresponder/file_io.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <filesystem>
+
+#include "libpldm/base.h"
+#include "libpldm/file_io.h"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+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
+{
+
+/** @struct AspeedXdmaOp
+ *
+ * Structure representing XDMA operation
+ */
+struct AspeedXdmaOp
+{
+    uint8_t upstream;  //!< boolean indicating the direction of the DMA
+                       //!< operation, true means a transfer from BMC to host.
+    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
+} __attribute__((packed));
+
+constexpr auto xdmaDev = "/dev/xdma";
+
+// 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;
+
+/** @brief API to transfer data from BMC to host using DMA
+ *
+ *  @param[in] file - pathname of the file from which to DMA data
+ *  @param[in] offset - offset in the file
+ *  @param[in] length - length of data to read from the file
+ *  @param[in] address - dma address on the host side to transfer data
+ */
+int transferDatatoHost(const fs::path& file, uint32_t offset, uint32_t length,
+                       uint64_t address);
+
+} // namespace dma
+
+/** @brief Handler for readFileIntoMemory command
+ *
+ *  @param[in] request - pointer to PLDM request payload
+ *  @param[in] payloadLength - length of the message payload
+ *  @param[out] response - response message location
+ */
+void readFileIntoMemory(const uint8_t* request, size_t payloadLength,
+                        pldm_msg* response);
+
+} // namespace responder
+} // namespace pldm