initial commit

Add initial code from phosphor-ipmi-flash/tools that was not specific to
firmware update over ipmi-blobs.

Change-Id: I360537a7392347fe989397a699f6a712bc36e62c
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/src/ipmiblob/ipmi_handler.cpp b/src/ipmiblob/ipmi_handler.cpp
new file mode 100644
index 0000000..9278338
--- /dev/null
+++ b/src/ipmiblob/ipmi_handler.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ipmi_handler.hpp"
+
+#include "ipmi_errors.hpp"
+
+#include <fcntl.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_msgdefs.h>
+#include <sys/ioctl.h>
+
+#include <array>
+#include <cstdint>
+#include <cstring>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace host_tool
+{
+
+void IpmiHandler::open()
+{
+    const int device = 0;
+    const std::vector<std::string> formats = {"/dev/ipmi", "/dev/ipmi/",
+                                              "/dev/ipmidev/"};
+
+    for (const auto& format : formats)
+    {
+        std::ostringstream path;
+        path << format << device;
+
+        fd = sys->open(path.str().c_str(), O_RDWR);
+        if (fd < 0)
+        {
+            continue;
+        }
+        break;
+    }
+
+    if (fd < 0)
+    {
+        throw IpmiException("Unable to open any ipmi devices");
+    }
+}
+
+std::vector<std::uint8_t>
+    IpmiHandler::sendPacket(std::vector<std::uint8_t>& data)
+{
+    if (fd < 0)
+    {
+        open();
+    }
+
+    constexpr int ipmiOEMNetFn = 46;
+    constexpr int ipmiOEMLun = 0;
+    /* /openbmc/phosphor-host-ipmid/blob/master/host-ipmid/oemopenbmc.hpp */
+    constexpr int ipmiOEMBlobCmd = 128;
+    constexpr int fifteenMs = 15 * 1000;
+    constexpr int ipmiReadTimeout = fifteenMs;
+    constexpr int ipmiResponseBufferLen = IPMI_MAX_MSG_LENGTH;
+    constexpr int ipmiOk = 0;
+
+    /* We have a handle to the IPMI device. */
+    std::array<std::uint8_t, ipmiResponseBufferLen> responseBuffer;
+
+    /* Build address. */
+    struct ipmi_system_interface_addr systemAddress;
+    systemAddress.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+    systemAddress.channel = IPMI_BMC_CHANNEL;
+    systemAddress.lun = ipmiOEMLun;
+
+    /* Build request. */
+    struct ipmi_req request;
+    std::memset(&request, 0, sizeof(request));
+    request.addr = reinterpret_cast<unsigned char*>(&systemAddress);
+    request.addr_len = sizeof(systemAddress);
+    request.msgid = sequence++;
+    request.msg.data = reinterpret_cast<unsigned char*>(data.data());
+    request.msg.data_len = data.size();
+    request.msg.netfn = ipmiOEMNetFn;
+    request.msg.cmd = ipmiOEMBlobCmd;
+
+    struct ipmi_recv reply;
+    reply.addr = reinterpret_cast<unsigned char*>(&systemAddress);
+    reply.addr_len = sizeof(systemAddress);
+    reply.msg.data = reinterpret_cast<unsigned char*>(responseBuffer.data());
+    reply.msg.data_len = responseBuffer.size();
+
+    /* Try to send request. */
+    int rc = sys->ioctl(fd, IPMICTL_SEND_COMMAND, &request);
+    if (rc < 0)
+    {
+        throw IpmiException("Unable to send IPMI request.");
+    }
+
+    /* Could use sdeventplus, but for only one type of event is it worth it? */
+    struct pollfd pfd;
+    pfd.fd = fd;
+    pfd.events = POLLIN;
+
+    do
+    {
+        rc = sys->poll(&pfd, 1, ipmiReadTimeout);
+        if (rc < 0)
+        {
+            if (errno == EINTR)
+            {
+                continue;
+            }
+            throw IpmiException("Error occurred.");
+        }
+        else if (rc == 0)
+        {
+            throw IpmiException("Timeout waiting for reply.");
+        }
+
+        /* Yay, happy case! */
+        rc = sys->ioctl(fd, IPMICTL_RECEIVE_MSG_TRUNC, &reply);
+        if (rc < 0)
+        {
+            throw IpmiException("Unable to read reply.");
+        }
+
+        if (request.msgid != reply.msgid)
+        {
+            std::fprintf(stderr, "Received wrong message, trying again.\n");
+        }
+    } while (request.msgid != reply.msgid);
+
+    if (responseBuffer[0] != ipmiOk)
+    {
+        throw IpmiException(static_cast<int>(responseBuffer[0]));
+    }
+
+    std::vector<std::uint8_t> returning;
+    auto dataLen = reply.msg.data_len - 1;
+
+    returning.insert(returning.begin(), responseBuffer.begin() + 1,
+                     responseBuffer.begin() + dataLen + 1);
+
+    for (const auto& byte : returning)
+    {
+        std::fprintf(stderr, "0x%02x ", byte);
+    }
+    std::fprintf(stderr, "\n");
+
+    return returning;
+}
+
+} // namespace host_tool