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/libpldmresponder/meson.build b/libpldmresponder/meson.build
index 116bfa5..770ef8c 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -20,7 +20,9 @@
 if get_option('oem-ibm').enabled()
   sources += [
     '../oem/ibm/libpldmresponder/file_io.cpp',
-    '../oem/ibm/libpldmresponder/file_table.cpp'
+    '../oem/ibm/libpldmresponder/file_table.cpp',
+    '../oem/ibm/libpldmresponder/file_io_by_type.cpp',
+    '../oem/ibm/libpldmresponder/file_io_type_pel.cpp'
   ]
 endif
 
diff --git a/meson.build b/meson.build
index 547466a..13a1899 100644
--- a/meson.build
+++ b/meson.build
@@ -19,6 +19,7 @@
 conf_data.set_quoted('PDR_JSONS_DIR', '/usr/share/pldm/pdr')
 if get_option('oem-ibm').enabled()
   conf_data.set_quoted('FILE_TABLE_JSON', '/usr/share/pldm/fileTable.json')
+  conf_data.set_quoted('PEL_TEMP_DIR', '/tmp/pel')
   add_global_arguments('-DOEM_IBM', language : 'c')
   add_global_arguments('-DOEM_IBM', language : 'cpp')
 endif
diff --git a/oem/ibm/libpldm/file_io.h b/oem/ibm/libpldm/file_io.h
index 15b568b..c7b4a7c 100644
--- a/oem/ibm/libpldm/file_io.h
+++ b/oem/ibm/libpldm/file_io.h
@@ -31,6 +31,7 @@
 	PLDM_INVALID_WRITE_LENGTH = 0x83,
 	PLDM_FILE_TABLE_UNAVAILABLE = 0x84,
 	PLDM_INVALID_FILE_TABLE_TYPE = 0x85,
+	PLDM_INVALID_FILE_TYPE = 0x86,
 };
 
 /** @brief PLDM File I/O table types
@@ -40,6 +41,12 @@
 	PLDM_OEM_FILE_ATTRIBUTE_TABLE = 1,
 };
 
+/** @brief PLDM File I/O table types
+ */
+enum pldm_fileio_file_type {
+	PLDM_FILE_TYPE_PEL = 0,
+};
+
 #define PLDM_RW_FILE_MEM_REQ_BYTES 20
 #define PLDM_RW_FILE_MEM_RESP_BYTES 5
 #define PLDM_GET_FILE_TABLE_REQ_BYTES 6
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
diff --git a/oem/ibm/test/libpldmresponder_fileio_test.cpp b/oem/ibm/test/libpldmresponder_fileio_test.cpp
index 3ebf96a..603c0c6 100644
--- a/oem/ibm/test/libpldmresponder_fileio_test.cpp
+++ b/oem/ibm/test/libpldmresponder_fileio_test.cpp
@@ -1,9 +1,13 @@
 #include "libpldmresponder/file_io.hpp"
+#include "libpldmresponder/file_io_by_type.hpp"
+#include "libpldmresponder/file_io_type_pel.hpp"
 #include "libpldmresponder/file_table.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
 
 #include <filesystem>
 #include <fstream>
 #include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
 
 #include "libpldm/base.h"
 #include "libpldm/file_io.h"
@@ -719,3 +723,46 @@
 
     table.clear();
 }
+
+TEST(writeFileByTypeFromMemory, testBadPath)
+{
+    const auto hdr_size = sizeof(pldm_msg_hdr);
+    std::array<uint8_t, hdr_size + PLDM_RW_FILE_BY_TYPE_MEM_REQ_BYTES>
+        requestMsg{};
+    auto req = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    size_t requestPayloadLength = requestMsg.size() - hdr_size;
+    struct pldm_read_write_file_by_type_memory_req* request =
+        reinterpret_cast<struct pldm_read_write_file_by_type_memory_req*>(
+            req->payload);
+    request->file_type = PLDM_FILE_TYPE_PEL;
+    request->file_handle = 0xFFFFFFFF;
+    request->offset = 0;
+    request->length = 17;
+    request->address = 0;
+
+    auto response = writeFileByTypeFromMemory(req, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    struct pldm_read_write_file_by_type_memory_resp* resp =
+        reinterpret_cast<struct pldm_read_write_file_by_type_memory_resp*>(
+            responsePtr->payload);
+    ASSERT_EQ(PLDM_ERROR_INVALID_LENGTH, resp->completion_code);
+
+    response = writeFileByTypeFromMemory(req, requestPayloadLength);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    resp = reinterpret_cast<struct pldm_read_write_file_by_type_memory_resp*>(
+        responsePtr->payload);
+    ASSERT_EQ(PLDM_INVALID_WRITE_LENGTH, resp->completion_code);
+}
+
+TEST(getHandlerByType, allPaths)
+{
+    uint32_t fileHandle{};
+    auto handler = getHandlerByType(PLDM_FILE_TYPE_PEL, fileHandle);
+    auto pelType = dynamic_cast<PelHandler*>(handler.get());
+    ASSERT_TRUE(pelType != nullptr);
+
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+    ASSERT_THROW(getHandlerByType(0xFFFF, fileHandle), InternalFailure);
+}
diff --git a/test/meson.build b/test/meson.build
index db250a7..1749605 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -41,6 +41,6 @@
                      implicit_include_directories: false,
                      link_args: dynamic_linker,
                      build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
-                     dependencies: [libpldm, libpldmresponder, gtest, gmock, dependency('sdbusplus')]),
+                     dependencies: [libpldm, libpldmresponder, gtest, gmock, dependency('sdbusplus'),dependency('phosphor-logging')]),
        workdir: meson.current_source_dir())
 endforeach