Refactor DMA function and add responder for WriteFileFromMemory command

The WriteFileFromMemory command transfers data to the BMC from the Host
using DMA. Refactor the DMA transfer function so that the code between
the read and write file commands is common.

Signed-off-by: Eddie James <eajames@us.ibm.com>
Change-Id: I1ce5739c312c4789b882bff04f0e686cc438ab52
diff --git a/libpldmresponder/file_io.cpp b/libpldmresponder/file_io.cpp
index 9caa45a..ed043c6 100644
--- a/libpldmresponder/file_io.cpp
+++ b/libpldmresponder/file_io.cpp
@@ -21,76 +21,141 @@
 namespace fs = std::filesystem;
 using namespace phosphor::logging;
 
-int transferDatatoHost(const fs::path& file, uint32_t offset, uint32_t length,
-                       uint64_t address)
+namespace dma
 {
-    // Align the length of the memory mapping to the page size.
+
+/** @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";
+
+int 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 pageLength = numPages * pageSize;
-    if (length > pageLength)
+    uint32_t pageAlignedLength = numPages * pageSize;
+
+    if (length > pageAlignedLength)
     {
-        pageLength += pageSize;
+        pageAlignedLength += pageSize;
     }
 
-    auto mmapCleanup = [pageLength](void* vgaMem) {
-        munmap(vgaMem, pageLength);
+    auto mmapCleanup = [pageAlignedLength](void* vgaMem) {
+        munmap(vgaMem, pageAlignedLength);
     };
 
     int fd = -1;
     int rc = 0;
-    fd = open(dma::xdmaDev, O_RDWR);
+    fd = open(xdmaDev, O_RDWR);
     if (fd < 0)
     {
-        log<level::ERR>("Opening the xdma device failed", entry("RC=%d", rc));
+        rc = -errno;
+        log<level::ERR>("Failed to open the XDMA device", entry("RC=%d", rc));
         return rc;
     }
-    auto xdmaFDPtr = std::make_unique<utils::CustomFD>(fd);
-    auto& xdmaFD = *(xdmaFDPtr.get());
 
-    void* vgaMem = nullptr;
+    utils::CustomFD xdmaFd(fd);
 
-    vgaMem = mmap(nullptr, pageLength, PROT_WRITE, MAP_SHARED, xdmaFD(), 0);
+    void* vgaMem;
+    vgaMem = mmap(nullptr, pageAlignedLength, upstream ? PROT_WRITE : PROT_READ,
+                  MAP_SHARED, xdmaFd(), 0);
     if (MAP_FAILED == vgaMem)
     {
         rc = -errno;
-        log<level::ERR>("mmap operation failed", entry("RC=%d", rc));
+        log<level::ERR>("Failed to mmap the XDMA device", 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)
+    if (upstream)
     {
-        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;
+        std::ifstream stream(path.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;
+    AspeedXdmaOp xdmaOp;
+    xdmaOp.upstream = upstream ? 1 : 0;
     xdmaOp.hostAddr = address;
     xdmaOp.len = length;
 
-    // Initiate the DMA operation
-    rc = write(xdmaFD(), &xdmaOp, sizeof(xdmaOp));
+    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),
+        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;
     }
 
-    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
+
+void transferAll(uint8_t command, fs::path& path, uint32_t offset,
+                 uint32_t length, uint64_t address, bool upstream,
+                 pldm_msg* response)
+{
+    uint32_t origLength = length;
+
+    while (length > dma::maxSize)
+    {
+        auto rc = dma::transferDataHost(path, offset, dma::maxSize, address,
+                                        upstream);
+        if (rc < 0)
+        {
+            encode_rw_file_memory_resp(0, command, PLDM_ERROR, 0, response);
+            return;
+        }
+
+        offset += dma::maxSize;
+        length -= dma::maxSize;
+        address += dma::maxSize;
+    }
+
+    auto rc = dma::transferDataHost(path, offset, length, address, upstream);
+    if (rc < 0)
+    {
+        encode_rw_file_memory_resp(0, command, PLDM_ERROR, 0, response);
+        return;
+    }
+
+    encode_rw_file_memory_resp(0, command, PLDM_SUCCESS, origLength, response);
+    return;
 }
 
 void readFileIntoMemory(const uint8_t* request, size_t payloadLength,
@@ -100,33 +165,33 @@
     uint32_t offset = 0;
     uint32_t length = 0;
     uint64_t address = 0;
+    fs::path path("");
 
-    if (payloadLength != PLDM_READ_FILE_MEM_REQ_BYTES)
+    if (payloadLength != PLDM_RW_FILE_MEM_REQ_BYTES)
     {
-        encode_read_file_memory_resp(0, PLDM_ERROR_INVALID_LENGTH, 0, response);
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_ERROR_INVALID_LENGTH, 0, response);
         return;
     }
 
-    decode_read_file_memory_req(request, payloadLength, &fileHandle, &offset,
-                                &length, &address);
+    decode_rw_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);
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   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);
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_DATA_OUT_OF_RANGE, 0, response);
         return;
     }
 
@@ -137,41 +202,65 @@
 
     if (length % dma::minSize)
     {
-        log<level::ERR>("Readlength is not a multiple of DMA minSize",
+        log<level::ERR>("Read length is not a multiple of DMA minSize",
                         entry("LENGTH=%d", length));
-        encode_read_file_memory_resp(0, PLDM_INVALID_READ_LENGTH, 0, response);
+        encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+                                   PLDM_INVALID_READ_LENGTH, 0, response);
         return;
     }
 
-    uint32_t origLength = length;
+    transferAll(PLDM_READ_FILE_INTO_MEMORY, path, offset, length, address, true,
+                response);
+}
 
-    while (length > 0)
+void writeFileFromMemory(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;
+    fs::path path("");
+
+    if (payloadLength != PLDM_RW_FILE_MEM_REQ_BYTES)
     {
-        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;
-        }
+        encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                   PLDM_ERROR_INVALID_LENGTH, 0, response);
+        return;
     }
+
+    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, response);
+        return;
+    }
+
+    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, 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_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+                                   PLDM_DATA_OUT_OF_RANGE, 0, response);
+        return;
+    }
+
+    transferAll(PLDM_WRITE_FILE_FROM_MEMORY, path, offset, length, address,
+                false, response);
 }
 
 } // namespace responder
diff --git a/libpldmresponder/file_io.hpp b/libpldmresponder/file_io.hpp
index a983963..ae475b9 100644
--- a/libpldmresponder/file_io.hpp
+++ b/libpldmresponder/file_io.hpp
@@ -51,45 +51,51 @@
 
 } // namespace utils
 
+namespace fs = std::filesystem;
+
 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
+/** @brief API to transfer data between BMC and 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
+ * @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 directon of the transfer; true indicates
+ *                       transfer to the host
+ *
+ * @return             - returns 0 on success, negative errno on failure
  */
-int transferDatatoHost(const fs::path& file, uint32_t offset, uint32_t length,
-                       uint64_t address);
-
+int transferDataHost(const fs::path& path, uint32_t offset, uint32_t length,
+                     uint64_t address, bool upstream);
 } // namespace dma
 
+/** @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.
+ *
+ * @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
+ * @param[out] response - response message location
+ */
+void transferAll(uint8_t command, fs::path& path, uint32_t offset,
+                 uint32_t length, uint64_t address, bool upstream,
+                 pldm_msg* response);
+
 /** @brief Handler for readFileIntoMemory command
  *
  *  @param[in] request - pointer to PLDM request payload
@@ -99,5 +105,14 @@
 void readFileIntoMemory(const uint8_t* request, size_t payloadLength,
                         pldm_msg* response);
 
+/** @brief Handler for writeFileIntoMemory command
+ *
+ *  @param[in] request - pointer to PLDM request payload
+ *  @param[in] payloadLength - length of the message payload
+ *  @param[out] response - response message location
+ */
+void writeFileFromMemory(const uint8_t* request, size_t payloadLength,
+                         pldm_msg* response);
+
 } // namespace responder
 } // namespace pldm