tools: implement lpc support

Tested: Verified this works for a Nuvoton BMC.

EXTRA_OECONF_append_xxx = " \
  --enable-static-layout \
  --enable-lpc-bridge \
  --enable-nuvoton-lpc \
  --enable-reboot-update \
  MAPPED_ADDRESS=0xc0008000 \
  "

/tmp/phosphor_ipmi_flash_tool \
 --command update \
 --interface ipmilpc \
 --image image-bmc \
 --sig image-bmc.sig \
 --type static \
 --address 0xfedc1000 \
 --length 0x1000
Sending over the firmware image.
trying to open blob
sending writeMeta
caught exception
EFBIG returned!
sending writeMeta
writemeta sent
Sending over the hash file.
trying to open blob
sending writeMeta
caught exception
EFBIG returned!
sending writeMeta
writemeta sent
Opening the verification file
Committing to /flash/verify to trigger service
Calling stat on /flash/verify session to check status
other
success
Returned success
succeeded
Opening the update file
Committing to /flash/update to trigger service
Calling stat on /flash/update session to check status
running
Opening the cleanup blob
Exception received: blob exception received: Received IPMI_CC: 255

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: If866303e95e9b6a19dc8b20a99bb89fd66f95eeb
diff --git a/tools/lpc.cpp b/tools/lpc.cpp
index 3d6bc4b..9359b92 100644
--- a/tools/lpc.cpp
+++ b/tools/lpc.cpp
@@ -16,7 +16,9 @@
 
 #include "lpc.hpp"
 
+#include <cerrno>
 #include <cstring>
+#include <ipmiblob/blob_errors.hpp>
 
 namespace host_tool
 {
@@ -24,28 +26,126 @@
 bool LpcDataHandler::sendContents(const std::string& input,
                                   std::uint16_t session)
 {
-    /* TODO: Add mechanism for configuring this. */
     LpcRegion host_lpc_buf;
-
-    /* TODO: Remove hard-coded configuration used with test machine. */
     host_lpc_buf.address = address;
     host_lpc_buf.length = length;
 
     std::vector<std::uint8_t> payload(sizeof(host_lpc_buf));
-    std::memcpy(payload.data(), &host_lpc_buf, sizeof(host_lpc_buf));
 
-    blob->writeMeta(session, 0x00, payload);
+    while (true)
+    {
+        /* If the writeMeta() is rejected we need to call sessionStat on it. */
+        try
+        {
+            std::fprintf(stderr, "sending writeMeta\n");
 
-    /* TODO: Call sessionstat and see if the metadata confirms the region was
-     * mapped successfully, once the lpc data handler implements it.
+            std::memcpy(payload.data(), &host_lpc_buf, sizeof(host_lpc_buf));
+            blob->writeMeta(session, 0x00, payload);
+
+            std::fprintf(stderr, "writemeta sent\n");
+
+            break;
+        }
+        catch (...)
+        {
+            std::fprintf(stderr, "caught exception\n");
+
+            ipmiblob::StatResponse resp = blob->getStat(session);
+            if (resp.metadata.empty())
+            {
+                std::fprintf(stderr, "Received no metadata bytes back!");
+                return false;
+            }
+
+            struct MemoryMapResultDetails
+            {
+                std::uint8_t code;
+                std::uint32_t offset;
+                std::uint32_t length;
+            } __attribute__((packed));
+
+            struct MemoryMapResultDetails bytes;
+
+            if (resp.metadata.size() != sizeof(bytes))
+            {
+                std::fprintf(
+                    stderr,
+                    "Received insufficient bytes back on expected return!\n");
+                return false;
+            }
+
+            std::memcpy(&bytes, resp.metadata.data(), sizeof(bytes));
+
+            if (bytes.code == EFBIG)
+            {
+                std::fprintf(stderr, "EFBIG returned!\n");
+
+                host_lpc_buf.length = bytes.length;
+                host_lpc_buf.address += bytes.offset;
+            }
+            else if (bytes.code == 0)
+            {
+                /* We're good, continue! */
+                break;
+            }
+        }
+    }
+
+    /* For data blockss, stage data, and send blob write command. */
+    int inputFd = sys->open(input.c_str(), 0);
+    if (inputFd < 0)
+    {
+        return false;
+    }
+
+    /* For Nuvoton the maximum is 4K */
+    auto readBuffer = std::make_unique<std::uint8_t[]>(host_lpc_buf.length);
+    if (nullptr == readBuffer)
+    {
+        sys->close(inputFd);
+        std::fprintf(stderr, "Unable to allocate memory for read buffer.\n");
+        return false;
+    }
+
+    /* TODO: This is similar to PCI insomuch as how it sends data, so combine.
      */
+    try
+    {
+        int bytesRead = 0;
+        std::uint32_t offset = 0;
 
-    /* todo:
-     * configure memory region (somehow)
-     * copy contents from file to memory region
-     * send external chunk (writeBlob) until it's all sent.
-     */
-    return false;
+        do
+        {
+            bytesRead =
+                sys->read(inputFd, readBuffer.get(), host_lpc_buf.length);
+            if (bytesRead > 0)
+            {
+                if (!io->write(host_lpc_buf.address, bytesRead,
+                               readBuffer.get()))
+                {
+                    std::fprintf(stderr,
+                                 "Failed to write to region in memory!\n");
+                }
+
+                struct ipmi_flash::ExtChunkHdr chunk;
+                chunk.length = bytesRead;
+                std::vector<std::uint8_t> chunkBytes(sizeof(chunk));
+                std::memcpy(chunkBytes.data(), &chunk, sizeof(chunk));
+
+                /* This doesn't return anything on success. */
+                blob->writeBytes(session, offset, chunkBytes);
+                offset += bytesRead;
+            }
+        } while (bytesRead > 0);
+    }
+    catch (const ipmiblob::BlobException& b)
+    {
+        sys->close(inputFd);
+        return false;
+    }
+
+    sys->close(inputFd);
+    return true;
 }
 
 } // namespace host_tool