oem-ibm: implement WriteFileByTypeFromMemory handler

This commit implements a framework for handling oem file types
received to/from host. Along with that it also implements the responder
for oem command WriteFileByTypeFromMemory and processes PELs received
from the host firmware.

Change-Id: Ice866aed0343b90769013c4be31a0c730f6e6bcd
Signed-off-by: Sampa Misra <sampmisr@in.ibm.com>
diff --git a/oem/ibm/libpldmresponder/file_io.cpp b/oem/ibm/libpldmresponder/file_io.cpp
index 64b020b..692ae1b 100644
--- a/oem/ibm/libpldmresponder/file_io.cpp
+++ b/oem/ibm/libpldmresponder/file_io.cpp
@@ -2,9 +2,11 @@
 
 #include "file_io.hpp"
 
+#include "file_io_by_type.hpp"
 #include "file_table.hpp"
 #include "libpldmresponder/utils.hpp"
 #include "registration.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
 
 #include <fcntl.h>
 #include <sys/mman.h>
@@ -14,6 +16,8 @@
 
 #include <cstring>
 #include <fstream>
+#include <memory>
+#include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
 
 #include "libpldm/base.h"
@@ -21,6 +25,9 @@
 namespace pldm
 {
 
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
 namespace responder
 {
 
@@ -36,6 +43,8 @@
                     std::move(writeFileFromMemory));
     registerHandler(PLDM_OEM, PLDM_READ_FILE, std::move(readFile));
     registerHandler(PLDM_OEM, PLDM_WRITE_FILE, std::move(writeFile));
+    registerHandler(PLDM_OEM, PLDM_WRITE_FILE_BY_TYPE_FROM_MEMORY,
+                    std::move(writeFileByTypeFromMemory));
 }
 
 } // namespace oem_ibm
@@ -144,8 +153,12 @@
 
     if (!upstream)
     {
-        std::ofstream stream(path.string(),
-                             std::ios::in | std::ios::out | std::ios::binary);
+        std::ios_base::openmode mode = std::ios::out | std::ios::binary;
+        if (fs::exists(path))
+        {
+            mode |= std::ios::in;
+        }
+        std::ofstream stream(path.string(), mode);
 
         stream.seekp(offset);
         stream.write(static_cast<const char*>(vgaMemPtr.get()), length);
@@ -523,5 +536,66 @@
     return response;
 }
 
+Response writeFileByTypeFromMemory(const pldm_msg* request,
+                                   size_t payloadLength)
+{
+    Response response(
+        sizeof(pldm_msg_hdr) + PLDM_RW_FILE_BY_TYPE_MEM_RESP_BYTES, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    if (payloadLength != PLDM_RW_FILE_BY_TYPE_MEM_REQ_BYTES)
+    {
+        encode_rw_file_by_type_memory_resp(
+            request->hdr.instance_id, PLDM_WRITE_FILE_BY_TYPE_FROM_MEMORY,
+            PLDM_ERROR_INVALID_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    uint16_t fileType{};
+    uint32_t fileHandle{};
+    uint32_t offset{};
+    uint32_t length{};
+    uint64_t address{};
+    auto rc = decode_rw_file_by_type_memory_req(request, payloadLength,
+                                                &fileType, &fileHandle, &offset,
+                                                &length, &address);
+    if (rc != PLDM_SUCCESS)
+    {
+        encode_rw_file_by_type_memory_resp(request->hdr.instance_id,
+                                           PLDM_WRITE_FILE_BY_TYPE_FROM_MEMORY,
+                                           rc, 0, responsePtr);
+        return response;
+    }
+    if (length % dma::minSize)
+    {
+        log<level::ERR>("Write length is not a multiple of DMA minSize",
+                        entry("LENGTH=%d", length));
+        encode_rw_file_by_type_memory_resp(
+            request->hdr.instance_id, PLDM_WRITE_FILE_BY_TYPE_FROM_MEMORY,
+            PLDM_INVALID_WRITE_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    std::unique_ptr<FileHandler> handler{};
+    try
+    {
+        handler = getHandlerByType(fileType, fileHandle);
+    }
+    catch (const InternalFailure& e)
+    {
+        log<level::ERR>("unknown file type ", entry("TYPE=%d", fileType));
+        encode_rw_file_by_type_memory_resp(
+            request->hdr.instance_id, PLDM_WRITE_FILE_BY_TYPE_FROM_MEMORY,
+            PLDM_INVALID_FILE_TYPE, 0, responsePtr);
+        return response;
+    }
+
+    rc = handler->writeFromMemory(offset, length, address);
+    encode_rw_file_by_type_memory_resp(request->hdr.instance_id,
+                                       PLDM_WRITE_FILE_BY_TYPE_FROM_MEMORY, rc,
+                                       length, responsePtr);
+    return response;
+}
+
 } // namespace responder
 } // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io.hpp b/oem/ibm/libpldmresponder/file_io.hpp
index 0ee4ed6..2086fc1 100644
--- a/oem/ibm/libpldmresponder/file_io.hpp
+++ b/oem/ibm/libpldmresponder/file_io.hpp
@@ -139,6 +139,17 @@
  */
 Response writeFileFromMemory(const pldm_msg* request, size_t payloadLength);
 
+/** @brief Handler for writeFileByTypeFromMemory command
+ *
+ *  @param[in] request - pointer to PLDM request payload
+ *  @param[in] payloadLength - length of the message
+ *
+ *  @return PLDM response message
+ */
+
+Response writeFileByTypeFromMemory(const pldm_msg* request,
+                                   size_t payloadLength);
+
 /** @brief Handler for GetFileTable command
  *
  *  @param[in] request - pointer to PLDM request payload
diff --git a/oem/ibm/libpldmresponder/file_io_by_type.cpp b/oem/ibm/libpldmresponder/file_io_by_type.cpp
new file mode 100644
index 0000000..d8a40a5
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io_by_type.cpp
@@ -0,0 +1,73 @@
+#include "config.h"
+
+#include "file_io_by_type.hpp"
+
+#include "file_io_type_pel.hpp"
+#include "libpldmresponder/utils.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <exception>
+#include <filesystem>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <vector>
+#include <xyz/openbmc_project/Logging/Entry/server.hpp>
+
+#include "libpldm/base.h"
+#include "oem/ibm/libpldm/file_io.h"
+
+namespace pldm
+{
+namespace responder
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+int FileHandler::transferFileData(const fs::path& path, bool upstream,
+                                  uint32_t offset, uint32_t length,
+                                  uint64_t address)
+{
+    dma::DMA xdmaInterface;
+
+    while (length > dma::maxSize)
+    {
+        auto rc = xdmaInterface.transferDataHost(path, offset, dma::maxSize,
+                                                 address, upstream);
+        if (rc < 0)
+        {
+            return PLDM_ERROR;
+        }
+        offset += dma::maxSize;
+        length -= dma::maxSize;
+        address += dma::maxSize;
+    }
+    auto rc =
+        xdmaInterface.transferDataHost(path, offset, length, address, upstream);
+    return rc < 0 ? PLDM_ERROR : PLDM_SUCCESS;
+}
+
+std::unique_ptr<FileHandler> getHandlerByType(uint16_t fileType,
+                                              uint32_t fileHandle)
+{
+    switch (fileType)
+    {
+        case PLDM_FILE_TYPE_PEL:
+        {
+            return std::make_unique<PelHandler>(fileHandle);
+            break;
+        }
+        default:
+        {
+            elog<InternalFailure>();
+            break;
+        }
+    }
+    return nullptr;
+}
+
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io_by_type.hpp b/oem/ibm/libpldmresponder/file_io_by_type.hpp
new file mode 100644
index 0000000..6c31252
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io_by_type.hpp
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "file_io.hpp"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace fs = std::filesystem;
+
+/**
+ *  @class FileHandler
+ *
+ *  Base class to handle read/write of all oem file types
+ */
+class FileHandler
+{
+  public:
+    /** @brief Method to write an oem file type from host memory. Individual
+     *  file types need to override this method to do the file specific
+     *  processing
+     *  @param[in] offset - offset to read/write
+     *  @param[in] length - length to be read/write mentioned by Host
+     *  @param[in] address - DMA address
+     *  @return PLDM status code
+     */
+    virtual int writeFromMemory(uint32_t offset, uint32_t length,
+                                uint64_t address) = 0;
+
+    /** @brief Method to do the file content transfer ove DMA between host and
+     *  bmc. This method is made virtual to be overridden in test case. And need
+     *  not be defined in other child classes
+     *
+     *  @param[in] path - file system path  where read/write will be done
+     *  @param[in] upstream - direction of DMA transfer. "false" means a
+     *                        transfer from host to BMC
+     *  @param[in] offset - offset to read/write
+     *  @param[in] length - length to be read/write mentioned by Host
+     *  @param[in] address - DMA address
+     *
+     *  @return PLDM status code
+     */
+    virtual int transferFileData(const fs::path& path, bool upstream,
+                                 uint32_t offset, uint32_t length,
+                                 uint64_t address);
+
+    /** @brief Constructor to create a FileHandler object
+     */
+    FileHandler(uint32_t fileHandle) : fileHandle(fileHandle)
+    {
+    }
+
+    /** FileHandler destructor
+     */
+    virtual ~FileHandler()
+    {
+    }
+
+  protected:
+    uint32_t fileHandle; //!< file handle indicating name of file or invalid
+};
+
+/** @brief Method to create individual file handler objects based on file type
+ *
+ *  @param[in] fileType - type of file
+ *  @param[in] fileHandle - file handle
+ */
+
+std::unique_ptr<FileHandler> getHandlerByType(uint16_t fileType,
+                                              uint32_t fileHandle);
+
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io_type_pel.cpp b/oem/ibm/libpldmresponder/file_io_type_pel.cpp
new file mode 100644
index 0000000..400d336
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io_type_pel.cpp
@@ -0,0 +1,85 @@
+#include "config.h"
+
+#include "file_io_type_pel.hpp"
+
+#include "libpldmresponder/utils.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <stdint.h>
+#include <systemd/sd-bus.h>
+#include <unistd.h>
+
+#include <exception>
+#include <filesystem>
+#include <sdbusplus/server.hpp>
+#include <vector>
+#include <xyz/openbmc_project/Logging/Entry/server.hpp>
+
+#include "libpldm/base.h"
+#include "oem/ibm/libpldm/file_io.h"
+
+namespace pldm
+{
+namespace responder
+{
+
+using namespace phosphor::logging;
+
+int PelHandler::writeFromMemory(uint32_t offset, uint32_t length,
+                                uint64_t address)
+{
+    fs::create_directories(PEL_TEMP_DIR);
+
+    auto timeMs =
+        std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::high_resolution_clock::now().time_since_epoch())
+            .count();
+    std::string fileName(PEL_TEMP_DIR);
+    fileName += "/pel." + std::to_string(timeMs);
+    fs::path path(std::move(fileName));
+
+    auto rc = transferFileData(path, false, offset, length, address);
+    if (rc == PLDM_SUCCESS)
+    {
+        rc = storePel(path.string());
+    }
+    fs::remove(path);
+    return rc;
+}
+
+int PelHandler::storePel(std::string&& pelFileName)
+{
+    static constexpr auto logObjPath = "/xyz/openbmc_project/logging";
+    static constexpr auto logInterface = "xyz.openbmc_project.Logging.Create";
+
+    static sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+
+    try
+    {
+        auto service = getService(bus, logObjPath, logInterface);
+        using namespace sdbusplus::xyz::openbmc_project::Logging::server;
+        std::map<std::string, std::string> addlData{};
+        addlData.emplace("RAWPEL", std::move(pelFileName));
+        auto severity =
+            sdbusplus::xyz::openbmc_project::Logging::server::convertForMessage(
+                sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level::
+                    Error);
+
+        auto method = bus.new_method_call(service.c_str(), logObjPath,
+                                          logInterface, "Create");
+        method.append("xyz.openbmc_project.Host.Error.Event", severity,
+                      addlData);
+        bus.call_noreply(method);
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("failed to make a d-bus call to PEL daemon",
+                        entry("ERROR=%s", e.what()));
+        return PLDM_ERROR;
+    }
+
+    return PLDM_SUCCESS;
+}
+
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io_type_pel.hpp b/oem/ibm/libpldmresponder/file_io_type_pel.hpp
new file mode 100644
index 0000000..92bcbf6
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io_type_pel.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "file_io_by_type.hpp"
+
+namespace pldm
+{
+namespace responder
+{
+
+using namespace pldm::responder::dma;
+
+/** @class PelHandler
+ *
+ *  @brief Inherits and implements FileHandler. This class is used
+ *  to read/write pels.
+ */
+class PelHandler : public FileHandler
+{
+  public:
+    /** @brief PelHandler constructor
+     */
+    PelHandler(uint32_t fileHandle) : FileHandler(fileHandle)
+    {
+    }
+
+    virtual int writeFromMemory(uint32_t offset, uint32_t length,
+                                uint64_t address);
+
+    /** @brief method to store a pel file in tempfs and send
+     *  d-bus notification to pel daemon that it is ready for consumption
+     *
+     *  @param[in] pelFileName - the pel file path
+     */
+    virtual int storePel(std::string&& pelFileName);
+
+    /** @brief PelHandler destructor
+     */
+    ~PelHandler()
+    {
+    }
+};
+
+} // namespace responder
+} // namespace pldm