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/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