Enable IBM PLDM OEM commands support
Create folder structure (oem/ibm) for ibm pldm oem commands. Move the
files from the ibm-pldm-oem repo [https://github.com/openbmc/ibm-pldm-oem]
to the folder oem/ibm/ under the pldm repo and enable conditional
compilation for it. The test files are also conditionally compiled.
You would need to provide --enable-oem-ibm to configure.
This is done to simplify the build time and runtime dependencies between the
standard and oem implementations.
Signed-off-by: Jinu Joy Thomas <jinu.joy.thomas@in.ibm.com>
Change-Id: Iaa93c73faff87290e3d3d5d155d9ecae6e7ee6f9
diff --git a/oem/ibm/libpldmresponder/file_io.cpp b/oem/ibm/libpldmresponder/file_io.cpp
new file mode 100644
index 0000000..9cf093c
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io.cpp
@@ -0,0 +1,243 @@
+#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;
+
+namespace dma
+{
+
+/** @struct AspeedXdmaOp
+ *
+ * Structure representing XDMA operation
+ */
+struct AspeedXdmaOp
+{
+ 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
+ uint32_t upstream; //!< boolean indicating the direction of the DMA
+ //!< operation, true means a transfer from BMC to host.
+};
+
+constexpr auto xdmaDev = "/dev/xdma";
+
+int DMA::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 pageAlignedLength = numPages * pageSize;
+
+ if (length > pageAlignedLength)
+ {
+ pageAlignedLength += pageSize;
+ }
+
+ auto mmapCleanup = [pageAlignedLength](void* vgaMem) {
+ munmap(vgaMem, pageAlignedLength);
+ };
+
+ int fd = -1;
+ int rc = 0;
+ fd = open(xdmaDev, O_RDWR);
+ if (fd < 0)
+ {
+ rc = -errno;
+ log<level::ERR>("Failed to open the XDMA device", entry("RC=%d", rc));
+ return rc;
+ }
+
+ utils::CustomFD xdmaFd(fd);
+
+ void* vgaMem;
+ vgaMem = mmap(nullptr, pageAlignedLength, upstream ? PROT_WRITE : PROT_READ,
+ MAP_SHARED, xdmaFd(), 0);
+ if (MAP_FAILED == vgaMem)
+ {
+ rc = -errno;
+ log<level::ERR>("Failed to mmap the XDMA device", entry("RC=%d", rc));
+ return rc;
+ }
+
+ std::unique_ptr<void, decltype(mmapCleanup)> vgaMemPtr(vgaMem, mmapCleanup);
+
+ if (upstream)
+ {
+ std::ifstream stream(path.string());
+
+ stream.seekg(offset);
+ stream.read(static_cast<char*>(vgaMemPtr.get()), length);
+
+ if (static_cast<uint32_t>(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;
+ }
+ }
+
+ AspeedXdmaOp xdmaOp;
+ xdmaOp.upstream = upstream ? 1 : 0;
+ xdmaOp.hostAddr = address;
+ xdmaOp.len = length;
+
+ rc = write(xdmaFd(), &xdmaOp, sizeof(xdmaOp));
+ if (rc < 0)
+ {
+ rc = -errno;
+ 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;
+ }
+
+ if (!upstream)
+ {
+ std::ofstream stream(path.string());
+
+ stream.seekp(offset);
+ stream.write(static_cast<const char*>(vgaMemPtr.get()), length);
+ }
+
+ return 0;
+}
+
+} // namespace dma
+
+Response readFileIntoMemory(const uint8_t* request, size_t payloadLength)
+{
+ uint32_t fileHandle = 0;
+ uint32_t offset = 0;
+ uint32_t length = 0;
+ uint64_t address = 0;
+ fs::path path("");
+
+ Response response((sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES), 0);
+ auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+ if (payloadLength != PLDM_RW_FILE_MEM_REQ_BYTES)
+ {
+ encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+ PLDM_ERROR_INVALID_LENGTH, 0, responsePtr);
+ return response;
+ }
+
+ decode_rw_file_memory_req(request, payloadLength, &fileHandle, &offset,
+ &length, &address);
+
+ if (!fs::exists(path))
+ {
+ log<level::ERR>("File does not exist", entry("HANDLE=%d", fileHandle));
+ encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+ PLDM_INVALID_FILE_HANDLE, 0, responsePtr);
+ return response;
+ }
+
+ 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_READ_FILE_INTO_MEMORY,
+ PLDM_DATA_OUT_OF_RANGE, 0, responsePtr);
+ return response;
+ }
+
+ if (offset + length > fileSize)
+ {
+ length = fileSize - offset;
+ }
+
+ if (length % dma::minSize)
+ {
+ log<level::ERR>("Read length is not a multiple of DMA minSize",
+ entry("LENGTH=%d", length));
+ encode_rw_file_memory_resp(0, PLDM_READ_FILE_INTO_MEMORY,
+ PLDM_INVALID_READ_LENGTH, 0, responsePtr);
+ return response;
+ }
+
+ using namespace dma;
+ DMA intf;
+ return transferAll<DMA>(&intf, PLDM_READ_FILE_INTO_MEMORY, path, offset,
+ length, address, true);
+}
+
+Response writeFileFromMemory(const uint8_t* request, size_t payloadLength)
+{
+ uint32_t fileHandle = 0;
+ uint32_t offset = 0;
+ uint32_t length = 0;
+ uint64_t address = 0;
+ fs::path path("");
+
+ Response response(sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES, 0);
+ auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+ if (payloadLength != PLDM_RW_FILE_MEM_REQ_BYTES)
+ {
+ encode_rw_file_memory_resp(0, PLDM_WRITE_FILE_FROM_MEMORY,
+ PLDM_ERROR_INVALID_LENGTH, 0, responsePtr);
+ return response;
+ }
+
+ 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, responsePtr);
+ return response;
+ }
+
+ 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, responsePtr);
+ return response;
+ }
+
+ 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, responsePtr);
+ return response;
+ }
+
+ using namespace dma;
+ DMA intf;
+ return transferAll<DMA>(&intf, PLDM_WRITE_FILE_FROM_MEMORY, path, offset,
+ length, address, false);
+}
+
+} // namespace responder
+} // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io.hpp b/oem/ibm/libpldmresponder/file_io.hpp
new file mode 100644
index 0000000..17a6fe2
--- /dev/null
+++ b/oem/ibm/libpldmresponder/file_io.hpp
@@ -0,0 +1,168 @@
+#pragma once
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <filesystem>
+
+#include "libpldm/base.h"
+#include "libpldm/file_io.h"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+using Response = std::vector<uint8_t>;
+
+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
+{
+
+// 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;
+
+/**
+ * @class DMA
+ *
+ * Expose API to initiate transfer of data by DMA
+ *
+ * This class only exposes the public API transferDataHost to transfer data
+ * between BMC and host using DMA. This allows for mocking the transferDataHost
+ * for unit testing purposes.
+ */
+class DMA
+{
+ public:
+ /** @brief API to transfer data between BMC and host using DMA
+ *
+ * @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
+ *
+ * @return returns 0 on success, negative errno on failure
+ */
+ int transferDataHost(const fs::path& path, uint32_t offset, uint32_t length,
+ uint64_t address, bool upstream);
+};
+
+/** @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.
+ *
+ * @tparam[in] T - DMA interface type
+ * @param[in] intf - interface passed to invoke DMA transfer
+ * @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
+ * @return PLDM response message
+ */
+
+template <class DMAInterface>
+Response transferAll(DMAInterface* intf, uint8_t command, fs::path& path,
+ uint32_t offset, uint32_t length, uint64_t address,
+ bool upstream)
+{
+ uint32_t origLength = length;
+ Response response(sizeof(pldm_msg_hdr) + PLDM_RW_FILE_MEM_RESP_BYTES, 0);
+ auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+ while (length > dma::maxSize)
+ {
+ auto rc = intf->transferDataHost(path, offset, dma::maxSize, address,
+ upstream);
+ if (rc < 0)
+ {
+ encode_rw_file_memory_resp(0, command, PLDM_ERROR, 0, responsePtr);
+ return response;
+ }
+
+ offset += dma::maxSize;
+ length -= dma::maxSize;
+ address += dma::maxSize;
+ }
+
+ auto rc = intf->transferDataHost(path, offset, length, address, upstream);
+ if (rc < 0)
+ {
+ encode_rw_file_memory_resp(0, command, PLDM_ERROR, 0, responsePtr);
+ return response;
+ }
+
+ encode_rw_file_memory_resp(0, command, PLDM_SUCCESS, origLength,
+ responsePtr);
+ return response;
+}
+
+} // namespace dma
+
+/** @brief Handler for readFileIntoMemory command
+ *
+ * @param[in] request - pointer to PLDM request payload
+ * @param[in] payloadLength - length of the message payload
+ *
+ * @return PLDM response message
+ */
+Response readFileIntoMemory(const uint8_t* request, size_t payloadLength);
+
+/** @brief Handler for writeFileIntoMemory command
+ *
+ * @param[in] request - pointer to PLDM request payload
+ * @param[in] payloadLength - length of the message payload
+ *
+ * @return PLDM response message
+ */
+Response writeFileFromMemory(const uint8_t* request, size_t payloadLength);
+} // namespace responder
+} // namespace pldm