tools: starting burn_my_bmc host utility

burn_my_bmc is a host-run utility that in cooperation with the
phosphor-ipmi-flash library, attempts to send a BMC firmware image and
signature file to the BMC and trigger verification of said image.

The program's current design and use were tailored towards the legacy
OpenBMC image and not UBI.  Therefore, changes to support the UBI
process will get addressed as it all takes shape.

The overall process is:
1) Attempts to send firmware image over an interface.
2) Attempts to send signature file contents over an interface*.
3) Triggers a verification step.
4) Reboots the BMC**.

* The only interface in the initial version here is the blocktransfer
interface.  It's technically also possibly KCS.  It's sending the data
over the same communications channel as the normal IPMI packets.  A
later patchset will enable sending the data bytes over an LPC memory
region or the PCI P2A region.

** The 4th step is done by a separate call to the 'reboot' command.
The 'reboot' and 'ping' commands will come in a later patchset.

Change-Id: I62d725274e56c55ca414fa6c2a3eab6c500066ed
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/tools/ipmi/raw.cpp b/tools/ipmi/raw.cpp
new file mode 100644
index 0000000..c38f634
--- /dev/null
+++ b/tools/ipmi/raw.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "raw.hpp"
+
+#include <stdexcept>
+
+struct IpmiResponse IpmiRaw::Raw(const std::vector<uint8_t>& buffer)
+{
+    struct IpmiResponse response;
+    int rc = ipmiSendCommand(buffer.data(), buffer.size(), &response);
+    if (rc)
+    {
+        throw std::runtime_error("Failure sending IPMI packet.");
+    }
+
+    return response;
+}
+
+struct IpmiResponse IpmiRaw::RawWithTries(const std::vector<uint8_t>& buffer,
+                                          int tries)
+{
+    int count = 0;
+    struct IpmiResponse response;
+
+    /* If tries is 0, it'll run once. */
+    do
+    {
+        try
+        {
+            response = Raw(buffer);
+        }
+        catch (const std::runtime_error& e)
+        {
+            continue;
+        }
+
+        count++;
+        if (count >= tries)
+        {
+            throw std::runtime_error("Failure sending IPMI packet.");
+        }
+    } while (count < tries);
+
+    return response;
+}
diff --git a/tools/ipmi/raw.hpp b/tools/ipmi/raw.hpp
new file mode 100644
index 0000000..6e96816
--- /dev/null
+++ b/tools/ipmi/raw.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <vector>
+
+extern "C" {
+#include "ipmitoolintf.h"
+} // extern "C"
+
+class RawInterface
+{
+  public:
+    virtual ~RawInterface() = default;
+
+    virtual struct IpmiResponse Raw(const std::vector<uint8_t>& buffer) = 0;
+    virtual struct IpmiResponse RawWithTries(const std::vector<uint8_t>& buffer,
+                                             int tries) = 0;
+};
+
+class IpmiRaw : public RawInterface
+{
+  public:
+    IpmiRaw() = default;
+
+    struct IpmiResponse Raw(const std::vector<uint8_t>& buffer) override;
+    struct IpmiResponse RawWithTries(const std::vector<uint8_t>& buffer,
+                                     int tries) override;
+};
diff --git a/tools/ipmi/updatehelper.cpp b/tools/ipmi/updatehelper.cpp
new file mode 100644
index 0000000..2be224a
--- /dev/null
+++ b/tools/ipmi/updatehelper.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "config.h"
+
+#include "updatehelper.hpp"
+
+#include <cstring>
+#include <vector>
+
+namespace
+{
+// Output a vector of bytes consisted of the command header and given input.
+// Precondition: packet must be preallocated with kFlashCommandHdrSizeBytes.
+void constructFlashIpmiPacket(int command, const uint8_t* payload,
+                              int payload_size, std::vector<uint8_t>* packet)
+{
+    struct CommandHdr hdr;
+    std::memset(&hdr, 0, sizeof(hdr));
+
+    hdr.command = OEM_FLASH_UPDATE_BT_COMMAND;
+    hdr.subcommand = command;
+
+#ifdef ENABLE_GOOGLE
+    hdr.netfn = NETFN_OEM;
+    std::memcpy(&hdr.oen, OEN_GOOGLE, sizeof(OEN_GOOGLE));
+#else
+    hdr.netfn = NETFN_FIRMWARE;
+#endif
+
+    std::memcpy(packet->data(), &hdr, sizeof(hdr));
+
+    if (payload != nullptr && payload_size > 0)
+    {
+        packet->insert(packet->end(), payload, payload + payload_size);
+    }
+}
+} // namespace
+
+/*
+ * For use with BT Flash commands with payloads
+ */
+struct IpmiResponse
+    IpmiUpdateHelper::SendCommand(int command,
+                                  const std::vector<uint8_t>& payload)
+{
+    std::vector<uint8_t> packet(kFlashCommandHdrSizeBytes);
+    constructFlashIpmiPacket(command, payload.data(), payload.size(), &packet);
+
+    return raw->Raw(packet);
+}
+
+/*
+ * For use with BT Flash commands that are only the command itself.
+ */
+struct IpmiResponse IpmiUpdateHelper::SendEmptyCommand(int command)
+{
+    return SendCommand(command, {});
+}
diff --git a/tools/ipmi/updatehelper.hpp b/tools/ipmi/updatehelper.hpp
new file mode 100644
index 0000000..c502216
--- /dev/null
+++ b/tools/ipmi/updatehelper.hpp
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "config.h"
+
+#include "raw.hpp"
+
+#include <vector>
+
+constexpr int OEM_FLASH_UPDATE_BT_COMMAND = 127;
+constexpr uint8_t NETFN_FIRMWARE = 0x08;
+
+#ifdef ENABLE_GOOGLE
+
+constexpr int OEN_SIZE = 3;
+
+// OEM Command Netfn for IPMI.
+constexpr uint8_t NETFN_OEM = 0x2e;
+
+// OEM Group identifier for OpenBMC.
+constexpr uint8_t OEN_OPENBMC[OEN_SIZE] = {0xcf, 0xc2, 0x00};
+constexpr uint8_t OEN_GOOGLE[OEN_SIZE] = {0x79, 0x2b, 0x00};
+
+/* The header for the IPMI Raw command with a subcommand leading byte. */
+struct CommandHdr
+{
+    uint8_t netfn;
+    uint8_t command;
+    uint8_t oen[OEN_SIZE];
+    /* This is labeled subcommand but anything after padding is data. */
+    uint8_t subcommand;
+} __attribute__((packed));
+
+#else
+
+/* If not using the GOOGLE OEM, it uses the Firmware Netfn. */
+
+struct CommandHdr
+{
+    uint8_t netfn;
+    uint8_t command;
+    uint8_t subcommand;
+} __attribute__((packed));
+
+#endif
+
+constexpr int kFlashCommandHdrSizeBytes = sizeof(CommandHdr);
+
+/* This interface defines an interface of helper calls that'll deal with the
+ * details for sending the updater ipmi commands.  This also enables testing via
+ * injection.
+ */
+class UpdateHelperInterface
+{
+  public:
+    ~UpdateHelperInterface() = default;
+
+    /**
+     * Try to send an IPMI update firmware command that is only the command and
+     * no payload.
+     *
+     * @param[in] command : the command byte to send.
+     * @return the IPMI response.
+     */
+    virtual struct IpmiResponse SendEmptyCommand(int command) = 0;
+
+    /**
+     * Try to send an IPMI update firmware command, possibly with payload.
+     *
+     * @param[in] command : the command byte to send.
+     * @param[in] payload : the payload bytes.
+     * @return the IPMI response.
+     */
+    virtual struct IpmiResponse
+        SendCommand(int command, const std::vector<uint8_t>& payload) = 0;
+};
+
+class IpmiUpdateHelper : public UpdateHelperInterface
+{
+  public:
+    IpmiUpdateHelper(RawInterface* raw) : raw(raw)
+    {
+    }
+
+    struct IpmiResponse SendEmptyCommand(int command) override;
+    struct IpmiResponse
+        SendCommand(int command, const std::vector<uint8_t>& payload) override;
+
+  private:
+    RawInterface* raw;
+};