bmc: lpc: add support for lpc mapping and copying

The lpc handler object now handles copying the data after the window is
mapped, but the hardware specific pieces still play in dealing with the
driver directly.

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I2f448411fd4ef9b61b5d1158915c70902b16666b
diff --git a/lpc_aspeed.cpp b/lpc_aspeed.cpp
index 184f18a..7f111f2 100644
--- a/lpc_aspeed.cpp
+++ b/lpc_aspeed.cpp
@@ -16,12 +16,14 @@
 
 #include "lpc_aspeed.hpp"
 
+#include "mapper_errors.hpp"
 #include "window_hw_interface.hpp"
 
 #include <fcntl.h>
 #include <linux/aspeed-lpc-ctrl.h>
 #include <linux/kernel.h>
 
+#include <cerrno>
 #include <cstdint>
 #include <cstring>
 #include <memory>
@@ -58,9 +60,10 @@
     }
 }
 
-std::pair<std::uint32_t, std::uint32_t>
-    LpcMapperAspeed::mapWindow(std::uint32_t address, std::uint32_t length)
+WindowMapResult LpcMapperAspeed::mapWindow(std::uint32_t address,
+                                           std::uint32_t length)
 {
+    WindowMapResult result = {};
     static const std::uint32_t MASK_64K = 0xFFFFU;
     const std::uint32_t offset = address & MASK_64K;
 
@@ -71,11 +74,10 @@
                      " is too large for mem region"
                      " of size %zu\n",
                      length, offset, regionSize);
-        /* TODO: need to throw an exception at this point to store the data to
-         * provide an EBIG response later.
-         */
-        /* *windowSize = regionSize - offset; */
-        return std::make_pair(0, 0);
+
+        result.response = EFBIG;
+        result.windowSize = regionSize - offset;
+        return result;
     }
 
     struct aspeed_lpc_ctrl_mapping map = {
@@ -98,7 +100,9 @@
         std::fprintf(stderr,
                      "cannot open Aspeed LPC kernel control dev \"%s\"\n",
                      lpcControlPath.c_str());
-        return std::make_pair(0, 0);
+
+        result.response = EINVAL;
+        return result;
     }
 
     if (sys->ioctl(lpcControlFd, ASPEED_LPC_CTRL_IOCTL_MAP, &map) == -1)
@@ -106,11 +110,30 @@
         std::fprintf(stderr, "Failed to ioctl Aspeed LPC map with error %s\n",
                      std::strerror(errno));
         sys->close(lpcControlFd);
-        return std::make_pair(0, 0);
+
+        result.response = EINVAL;
+        return result;
     }
 
     sys->close(lpcControlFd);
-    return std::make_pair(offset, length);
+
+    result.response = 0;
+    result.windowOffset = offset;
+    result.windowSize = length;
+    return result;
+}
+
+MemorySet LpcMapperAspeed::open()
+{
+    if (mapRegion())
+    {
+        MemorySet output;
+        output.mappedFd = mappedFd;
+        output.mapped = mappedRegion;
+        return output;
+    }
+
+    throw MapperException("Unable to memory-map region");
 }
 
 bool LpcMapperAspeed::mapRegion()
@@ -139,22 +162,4 @@
     return true;
 }
 
-std::vector<std::uint8_t> LpcMapperAspeed::copyFrom(std::uint32_t length)
-{
-    if (mappedFd < 0)
-    {
-        /* NOTE: may make more sense to do this in the open() */
-        if (!mapRegion())
-        {
-            /* Was unable to map region -- this call only required if using mmap
-             * and not ioctl.
-             */
-            /* TODO: have a better failure. */
-            return {};
-        }
-    }
-
-    return std::vector<std::uint8_t>(mappedRegion, mappedRegion + length);
-}
-
 } // namespace ipmi_flash
diff --git a/lpc_aspeed.hpp b/lpc_aspeed.hpp
index 9300efd..ac87c22 100644
--- a/lpc_aspeed.hpp
+++ b/lpc_aspeed.hpp
@@ -31,12 +31,13 @@
     LpcMapperAspeed(LpcMapperAspeed&&) = default;
     LpcMapperAspeed& operator=(LpcMapperAspeed&&) = default;
 
+    /* throws MapperException */
+    MemorySet open() override;
+
     void close() override;
 
-    std::pair<std::uint32_t, std::uint32_t>
-        mapWindow(std::uint32_t address, std::uint32_t length) override;
-
-    std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+    WindowMapResult mapWindow(std::uint32_t address,
+                              std::uint32_t length) override;
 
     /**
      * Attempt to mmap the region.
diff --git a/lpc_handler.cpp b/lpc_handler.cpp
index 7f70750..e3a5e08 100644
--- a/lpc_handler.cpp
+++ b/lpc_handler.cpp
@@ -56,7 +56,11 @@
         return {};
     }
 
-    return mapper->copyFrom(length);
+    std::vector<std::uint8_t> results(length);
+    std::memcpy(results.data(), memory.mapped + mappingResult.windowOffset,
+                length);
+
+    return results;
 }
 
 bool LpcDataHandler::writeMeta(const std::vector<std::uint8_t>& configuration)
@@ -70,15 +74,12 @@
 
     std::memcpy(&lpcRegion, configuration.data(), configuration.size());
 
-    std::uint32_t windowOffset;
-    std::uint32_t windowSize;
-
     /* TODO: LpcRegion sanity checking. */
-
-    std::tie(windowOffset, windowSize) =
-        mapper->mapWindow(lpcRegion.address, lpcRegion.length);
-    if (windowSize == 0)
+    mappingResult = mapper->mapWindow(lpcRegion.address, lpcRegion.length);
+    if (mappingResult.response != 0)
     {
+        std::fprintf(stderr, "mappingResult.response %u\n",
+                     mappingResult.response);
         /* Failed to map region. */
         return false;
     }
@@ -88,10 +89,23 @@
 
 std::vector<std::uint8_t> LpcDataHandler::readMeta()
 {
-    /* TODO: Implement this call, s.t. with lpc_aspeed or whatever, you can
-     * validate the region.
-     */
-    return {};
+    /* Return the MemoryResult structure packed. */
+    std::vector<std::uint8_t> output(
+        sizeof(std::uint8_t) + sizeof(std::uint32_t) + sizeof(std::uint32_t));
+
+    int index = 0;
+    std::memcpy(&output[index], &mappingResult.response,
+                sizeof(mappingResult.response));
+
+    index += sizeof(mappingResult.response);
+    std::memcpy(&output[index], &mappingResult.windowOffset,
+                sizeof(mappingResult.windowOffset));
+
+    index += sizeof(mappingResult.windowOffset);
+    std::memcpy(&output[index], &mappingResult.windowSize,
+                sizeof(mappingResult.windowSize));
+
+    return output;
 }
 
 } // namespace ipmi_flash
diff --git a/lpc_handler.hpp b/lpc_handler.hpp
index cbdf8ef..4158b9b 100644
--- a/lpc_handler.hpp
+++ b/lpc_handler.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "data_handler.hpp"
+#include "mapper_errors.hpp"
 #include "window_hw_interface.hpp"
 
 #include <cstdint>
@@ -45,12 +46,37 @@
   private:
     bool setInitializedAndReturn(bool value)
     {
+        if (value)
+        {
+            try
+            {
+                /* Try really opening the map. */
+                memory = mapper->open();
+            }
+            catch (const MapperException& e)
+            {
+                std::fprintf(stderr, "received mapper exception: %s\n",
+                             e.what());
+                return false;
+            }
+        }
+
         initialized = value;
         return value;
     }
 
     std::unique_ptr<HardwareMapperInterface> mapper;
     bool initialized;
+    /* The LPC Handler does not take ownership of this, in case there's cleanup
+     * required for close()
+     */
+    MemorySet memory = {};
+
+    /* Offset in reserved memory at which host data arrives. */
+    /* Size of the chunk of the memory region in use by the host (e.g.
+     * mapped over external block mechanism).
+     */
+    WindowMapResult mappingResult = {};
 };
 
 } // namespace ipmi_flash
diff --git a/lpc_nuvoton.cpp b/lpc_nuvoton.cpp
index 416cef6..eaa316f 100644
--- a/lpc_nuvoton.cpp
+++ b/lpc_nuvoton.cpp
@@ -16,11 +16,13 @@
 
 #include "lpc_nuvoton.hpp"
 
+#include "mapper_errors.hpp"
 #include "window_hw_interface.hpp"
 
 #include <fcntl.h>
 #include <sys/mman.h>
 
+#include <cerrno>
 #include <cinttypes>
 #include <cstdint>
 #include <cstdio>
@@ -35,14 +37,54 @@
 using std::uint8_t;
 
 std::unique_ptr<HardwareMapperInterface>
-    LpcMapperNuvoton::createNuvotonMapper(std::uint32_t regionAddress)
+    LpcMapperNuvoton::createNuvotonMapper(std::uint32_t regionAddress,
+                                          std::uint32_t regionSize)
 {
     /* NOTE: Considered making one factory for both types. */
-    return std::make_unique<LpcMapperNuvoton>(regionAddress);
+    return std::make_unique<LpcMapperNuvoton>(regionAddress, regionSize);
+}
+
+MemorySet LpcMapperNuvoton::open()
+{
+    static constexpr auto devmem = "/dev/mem";
+
+    mappedFd = sys->open(devmem, O_RDWR | O_SYNC);
+    if (mappedFd == -1)
+    {
+        throw MapperException("Unable to open /dev/mem");
+    }
+
+    mapped = reinterpret_cast<uint8_t*>(sys->mmap(
+        0, memoryRegionSize, PROT_READ, MAP_SHARED, mappedFd, regionAddress));
+    if (mapped == MAP_FAILED)
+    {
+        sys->close(mappedFd);
+        mappedFd = -1;
+        mapped = nullptr;
+
+        throw MapperException("Unable to map region");
+    }
+
+    MemorySet output;
+    output.mappedFd = mappedFd;
+    output.mapped = mapped;
+
+    return output;
 }
 
 void LpcMapperNuvoton::close()
 {
+    if (mapped)
+    {
+        sys->munmap(mapped, memoryRegionSize);
+        mapped = nullptr;
+    }
+
+    if (mappedFd != -1)
+    {
+        sys->close(mappedFd);
+        mappedFd = -1;
+    }
 }
 
 /*
@@ -56,9 +98,11 @@
  *   - WindowOffset = 4 and WindowSize = len - 4 if (addr & 0x7) == 0
  *   - WindowSize = 0 means that the region cannot be mapped otherwise
  */
-std::pair<std::uint32_t, std::uint32_t>
-    LpcMapperNuvoton::mapWindow(std::uint32_t address, std::uint32_t length)
+WindowMapResult LpcMapperNuvoton::mapWindow(std::uint32_t address,
+                                            std::uint32_t length)
 {
+    WindowMapResult result = {};
+
     /* We reserve the first 4 bytes from the mapped region; the first byte
      * is shared semaphore, and the number of 4 is for alignment.
      */
@@ -69,7 +113,8 @@
     {
         std::fprintf(stderr, "window size %" PRIx32 " too small to map.\n",
                      length);
-        return std::make_pair(0, 0);
+        result.response = EINVAL;
+        return result;
     }
 
     if (length > bmcMapMaxSizeBytes)
@@ -84,19 +129,27 @@
      * bytes so as to skip the semaphore register.
      */
     uint32_t windowOffset = bmcMapReserveBytes;
-    uint32_t windowSize = length;
+    // uint32_t windowSize = length;
+
+    result.response = 0;
+    result.windowOffset = windowOffset;
+    result.windowSize = length;
 
     const uint32_t addressOffset = address & 0x7;
 
     if (addressOffset == 0)
     {
         std::fprintf(stderr, "Map address offset should be 4 for Nuvoton.\n");
-        return std::make_pair(0, 0);
+
+        result.response = EFBIG;
+        return result;
     }
     else if (addressOffset != bmcMapReserveBytes)
     {
         std::fprintf(stderr, "Map address offset should be 4 for Nuvoton.\n");
-        return std::make_pair(0, 0);
+
+        result.response = EINVAL;
+        return result;
     }
 
     /* TODO: need a kernel driver to handle mapping configuration.
@@ -107,7 +160,9 @@
     {
         std::fprintf(stderr, "Failed to open /dev/mem\n");
         sys->close(fd);
-        return std::make_pair(0, 0);
+
+        result.response = EINVAL;
+        return result;
     }
 
     const uint32_t bmcMapConfigBaseAddr = 0xc0001000;
@@ -132,13 +187,7 @@
     sys->munmap(mapBasePtr, pageSize);
     sys->close(fd);
 
-    return std::make_pair(windowOffset, windowSize);
-}
-
-std::vector<std::uint8_t> LpcMapperNuvoton::copyFrom(std::uint32_t length)
-{
-    /* TODO: implement. */
-    return {};
+    return result;
 }
 
 } // namespace ipmi_flash
diff --git a/lpc_nuvoton.hpp b/lpc_nuvoton.hpp
index eabcd09..c66653b 100644
--- a/lpc_nuvoton.hpp
+++ b/lpc_nuvoton.hpp
@@ -15,30 +15,42 @@
 {
   public:
     static std::unique_ptr<HardwareMapperInterface>
-        createNuvotonMapper(std::uint32_t regionAddress);
+        createNuvotonMapper(std::uint32_t regionAddress,
+                            std::uint32_t regionSize);
 
     /**
      * Create an LpcMapper for Nuvoton.
      *
      * @param[in] regionAddress - where to map the window into BMC memory.
+     * @param[in] regionSize - the size to map for copying data.
      * @param[in] a sys call interface pointer.
      * @todo Needs reserved memory region's physical address and size.
      */
-    LpcMapperNuvoton(std::uint32_t regionAddress,
+    LpcMapperNuvoton(std::uint32_t regionAddress, std::uint32_t regionSize,
                      const internal::Sys* sys = &internal::sys_impl) :
         regionAddress(regionAddress),
-        sys(sys){};
+        memoryRegionSize(regionSize), sys(sys){};
+
+    /** Attempt to map the window for copying bytes, after mapWindow is called.
+     * throws MapperException
+     */
+    MemorySet open() override;
 
     void close() override;
 
-    std::pair<std::uint32_t, std::uint32_t>
-        mapWindow(std::uint32_t address, std::uint32_t length) override;
-
-    std::vector<std::uint8_t> copyFrom(std::uint32_t length) override;
+    WindowMapResult mapWindow(std::uint32_t address,
+                              std::uint32_t length) override;
 
   private:
     std::uint32_t regionAddress;
+    std::uint32_t memoryRegionSize;
     const internal::Sys* sys;
+
+    /* The file handle to /dev/mem. */
+    int mappedFd = -1;
+
+    /* The pointer to the memory-mapped region. */
+    std::uint8_t* mapped = nullptr;
 };
 
 } // namespace ipmi_flash
diff --git a/main.cpp b/main.cpp
index d28ecba..2dd9531 100644
--- a/main.cpp
+++ b/main.cpp
@@ -54,8 +54,8 @@
 LpcDataHandler lpcDataHandler(
     LpcMapperAspeed::createAspeedMapper(MAPPED_ADDRESS, memoryRegionSize));
 #elif defined(NUVOTON_LPC)
-LpcDataHandler
-    lpcDataHandler(LpcMapperNuvoton::createNuvotonMapper(MAPPED_ADDRESS));
+LpcDataHandler lpcDataHandler(
+    LpcMapperNuvoton::createNuvotonMapper(MAPPED_ADDRESS, memoryRegionSize));
 #else
 #error "You must specify a hardware implementation."
 #endif
diff --git a/mapper_errors.hpp b/mapper_errors.hpp
new file mode 100644
index 0000000..8f30f30
--- /dev/null
+++ b/mapper_errors.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <exception>
+#include <string>
+
+namespace ipmi_flash
+{
+
+class MapperException : public std::exception
+{
+  public:
+    explicit MapperException(const std::string& message) : message(message)
+    {
+    }
+
+    virtual const char* what() const noexcept override
+    {
+        return message.c_str();
+    }
+
+  private:
+    std::string message;
+};
+
+} // namespace ipmi_flash
diff --git a/test/window_mapper_mock.hpp b/test/window_mapper_mock.hpp
index 8fc41fa..dd3a89b 100644
--- a/test/window_mapper_mock.hpp
+++ b/test/window_mapper_mock.hpp
@@ -16,11 +16,9 @@
   public:
     virtual ~HardwareInterfaceMock() = default;
 
+    MOCK_METHOD0(open, MemorySet());
     MOCK_METHOD0(close, ());
-    MOCK_METHOD2(mapWindow,
-                 std::pair<std::uint32_t, std::uint32_t>(std::uint32_t,
-                                                         std::uint32_t));
-    MOCK_METHOD1(copyFrom, std::vector<std::uint8_t>(std::uint32_t));
+    MOCK_METHOD2(mapWindow, WindowMapResult(std::uint32_t, std::uint32_t));
 };
 
 } // namespace ipmi_flash
diff --git a/window_hw_interface.hpp b/window_hw_interface.hpp
index 0c24dc8..7d15521 100644
--- a/window_hw_interface.hpp
+++ b/window_hw_interface.hpp
@@ -7,6 +7,24 @@
 namespace ipmi_flash
 {
 
+struct MemorySet
+{
+    int mappedFd = -1;
+    std::uint8_t* mapped = nullptr;
+};
+
+/** The result from the mapWindow command. */
+struct WindowMapResult
+{
+    /* The response can validly be 0, or EFBIG.  If it's EFBIG that means the
+     * region available is within the requested region. If the value is anything
+     * else, it's a complete failure.
+     */
+    std::uint8_t response;
+    std::uint32_t windowOffset;
+    std::uint32_t windowSize;
+};
+
 /**
  * Different LPC (or P2a) memory map implementations may require different
  * mechanisms for specific tasks such as mapping the memory window or copying
@@ -18,6 +36,11 @@
     virtual ~HardwareMapperInterface() = default;
 
     /**
+     * Open the driver or whatever and map the region.
+     */
+    virtual MemorySet open() = 0;
+
+    /**
      * Close the mapper.  This could mean, send an ioctl to turn off the region,
      * or unmap anything mmapped.
      */
@@ -31,19 +54,10 @@
      *
      * @param[in] address - The address for mapping (passed to LPC window)
      * @param[in] length - The length of the region
-     * @return windowOffset, windowSize - The offset into the window and
-     * length of the region.  On failure, length is set to 0.
+     * @return WindowMapResult - the result of the call
      */
-    virtual std::pair<std::uint32_t, std::uint32_t>
-        mapWindow(std::uint32_t address, std::uint32_t length) = 0;
-
-    /**
-     * Returns the bytes from the mapped window.
-     *
-     * @param[in] length - the number of bytes to copy.
-     * @return the bytes copied out of the region.
-     */
-    virtual std::vector<std::uint8_t> copyFrom(std::uint32_t length) = 0;
+    virtual WindowMapResult mapWindow(std::uint32_t address,
+                                      std::uint32_t length) = 0;
 };
 
 } // namespace ipmi_flash