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/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..471d138
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,30 @@
+noinst_PROGRAMS = burn_my_bmc
+
+AM_CPPFLAGS = -I./bt -I./interface -I./ipmi -I./libs -I./libs/ipmitool/include
+
+burn_my_bmc_SOURCES = main.cpp
+burn_my_bmc_LDADD = libupdater.la
+burn_my_bmc_CXXFLAGS =
+
+noinst_LTLIBRARIES = libupdater.la libipmi.la libipmitool.la
+libupdater_la_LDFLAGS = -static
+libupdater_la_LIBADD = libipmi.la \
+ -lstdc++fs
+libupdater_la_SOURCES = \
+ updater.cpp
+
+libipmi_la_LDFLAGS = -static
+libipmi_la_LIBADD = libipmitool.la
+libipmi_la_SOURCES = \
+ ipmi/raw.cpp \
+ bt/bt.cpp \
+ ipmi/updatehelper.cpp
+
+libipmitool_la_LDFLAGS = -static
+libipmitool_la_LIBADD =
+libipmitool_la_SOURCES = \
+ libs/ipmitoolintf.c \
+ libs/ipmitool.c \
+ libs/ipmitool/src/plugins/open/open.c
+
+SUBDIRS = . test
diff --git a/tools/bt/bt.cpp b/tools/bt/bt.cpp
new file mode 100644
index 0000000..6add8df
--- /dev/null
+++ b/tools/bt/bt.cpp
@@ -0,0 +1,6 @@
+#include "bt.hpp"
+
+void BtDataHandler::SendData(std::ifstream input, int command)
+{
+ /* TODO: implement this. */
+}
diff --git a/tools/bt/bt.hpp b/tools/bt/bt.hpp
new file mode 100644
index 0000000..a8879d4
--- /dev/null
+++ b/tools/bt/bt.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "interface.hpp"
+#include "updatehelper.hpp"
+
+class BtDataHandler : public DataInterface
+{
+ public:
+ explicit BtDataHandler(UpdateHelperInterface* helper) : helper(helper)
+ {
+ }
+
+ void SendData(std::ifstream input, int command) override;
+
+ bool External() override
+ {
+ return false;
+ }
+
+ private:
+ UpdateHelperInterface* helper;
+};
diff --git a/tools/interface/interface.hpp b/tools/interface/interface.hpp
new file mode 100644
index 0000000..94e7048
--- /dev/null
+++ b/tools/interface/interface.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <fstream>
+
+// Abstract Base Class for the Data Interface.
+// The DataInterface defines the required methods for sending data down through
+// a data channel. This can allow the data to be written anywhere as long as
+// the BMC side knows how to read it. The data can also be sent down over IPMI
+// however, it still must follow this interface.
+class DataInterface
+{
+ public:
+ virtual ~DataInterface() = default;
+
+ /* Try to send the file data.
+ * @param[in] input : File stream containing the data content to be
+ * transmitted
+ * @param[in] command : The command corresponding to the data.
+ */
+ virtual void SendData(std::ifstream input, int command) = 0;
+
+ /* Return true if your data is carried outside the IPMI channel. */
+ virtual bool External() = 0;
+};
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;
+};
diff --git a/tools/ipmitool.LICENSE b/tools/ipmitool.LICENSE
new file mode 100644
index 0000000..b332da7
--- /dev/null
+++ b/tools/ipmitool.LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+Redistribution of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+Redistribution in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of Sun Microsystems, Inc. or the names of
+contributors may be used to endorse or promote products derived
+from this software without specific prior written permission.
+
+This software is provided "AS IS," without a warranty of any kind.
+ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
+SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE
+FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
+OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
+SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
+OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
+PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
+LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
diff --git a/tools/libs/ipmitool b/tools/libs/ipmitool
new file mode 160000
index 0000000..232773d
--- /dev/null
+++ b/tools/libs/ipmitool
@@ -0,0 +1 @@
+Subproject commit 232773d171a8f5a929d02744e952bbfbe87c1b8e
diff --git a/tools/libs/ipmitool.c b/tools/libs/ipmitool.c
new file mode 100644
index 0000000..3b64a35
--- /dev/null
+++ b/tools/libs/ipmitool.c
@@ -0,0 +1,244 @@
+/*
+Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+Redistribution of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+Redistribution in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of Sun Microsystems, Inc. or the names of
+contributors may be used to endorse or promote products derived
+from this software without specific prior written permission.
+
+This software is provided "AS IS," without a warranty of any kind.
+ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
+SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE
+FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
+OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL
+SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
+OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
+PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
+LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ */
+#include <ipmitool/ipmi_intf.h>
+#include <ipmitool/ipmi_mc.h>
+#include <ipmitool/log.h>
+
+/* Duplicate this into our memory space. Nothing in our code path calls this
+ * in a critical path.
+ */
+static IPMI_OEM sel_iana = IPMI_OEM_UNKNOWN;
+
+const struct valstr completion_code_vals[] = {
+ {0x00, "Command completed normally"},
+ {0xc0, "Node busy"},
+ {0xc1, "Invalid command"},
+ {0xc2, "Invalid command on LUN"},
+ {0xc3, "Timeout"},
+ {0xc4, "Out of space"},
+ {0xc5, "Reservation cancelled or invalid"},
+ {0xc6, "Request data truncated"},
+ {0xc7, "Request data length invalid"},
+ {0xc8, "Request data field length limit exceeded"},
+ {0xc9, "Parameter out of range"},
+ {0xca, "Cannot return number of requested data bytes"},
+ {0xcb, "Requested sensor, data, or record not found"},
+ {0xcc, "Invalid data field in request"},
+ {0xcd, "Command illegal for specified sensor or record type"},
+ {0xce, "Command response could not be provided"},
+ {0xcf, "Cannot execute duplicated request"},
+ {0xd0, "SDR Repository in update mode"},
+ {0xd1, "Device firmeware in update mode"},
+ {0xd2, "BMC initialization in progress"},
+ {0xd3, "Destination unavailable"},
+ {0xd4, "Insufficient privilege level"},
+ {0xd5, "Command not supported in present state"},
+ {0xd6, "Cannot execute command, command disabled"},
+ {0xff, "Unspecified error"},
+ {0x00, NULL}};
+
+const char* val2str(uint16_t val, const struct valstr* vs)
+{
+ static char un_str[32];
+ int i;
+
+ for (i = 0; vs[i].str != NULL; i++)
+ {
+ if (vs[i].val == val)
+ return vs[i].str;
+ }
+
+ memset(un_str, 0, 32);
+ snprintf(un_str, 32, "Unknown (0x%02X)", val);
+
+ return un_str;
+}
+
+void ipmi_intf_session_set_timeout(struct ipmi_intf* intf, uint32_t timeout)
+{
+ intf->ssn_params.timeout = timeout;
+}
+
+void ipmi_intf_session_set_retry(struct ipmi_intf* intf, int retry)
+{
+ intf->ssn_params.retry = retry;
+}
+
+/* Nullify the methods we don't care about. */
+void lprintf(int level, const char* format, ...)
+{
+ return;
+}
+void lperror(int level, const char* format, ...)
+{
+ return;
+}
+
+int verbose = 0;
+
+const char* buf2str_extended(const uint8_t* buf, int len, const char* sep)
+{
+ static char str[BUF2STR_MAXIMUM_OUTPUT_SIZE];
+ char* cur;
+ int i;
+ int sz;
+ int left;
+ int sep_len;
+
+ if (buf == NULL)
+ {
+ snprintf(str, sizeof(str), "<NULL>");
+ return (const char*)str;
+ }
+ cur = str;
+ left = sizeof(str);
+ if (sep)
+ {
+ sep_len = strlen(sep);
+ }
+ else
+ {
+ sep_len = 0;
+ }
+ for (i = 0; i < len; i++)
+ {
+ /* may return more than 2, depending on locale */
+ sz = snprintf(cur, left, "%2.2x", buf[i]);
+ if (sz >= left)
+ {
+ /* buffer overflow, truncate */
+ break;
+ }
+ cur += sz;
+ left -= sz;
+ /* do not write separator after last byte */
+ if (sep && i != (len - 1))
+ {
+ if (sep_len >= left)
+ {
+ break;
+ }
+ strncpy(cur, sep, left - sz);
+ cur += sep_len;
+ left -= sep_len;
+ }
+ }
+ *cur = '\0';
+
+ return (const char*)str;
+}
+
+const char* buf2str(const uint8_t* buf, int len)
+{
+ return buf2str_extended(buf, len, NULL);
+}
+
+uint8_t ipmi_csum(uint8_t* d, int s)
+{
+ uint8_t c = 0;
+ for (; s > 0; s--, d++)
+ c += *d;
+ return -c;
+}
+
+void printbuf(const uint8_t* buf, int len, const char* desc)
+{
+ int i;
+
+ if (len <= 0)
+ return;
+
+ if (verbose < 1)
+ return;
+
+ fprintf(stderr, "%s (%d bytes)\n", desc, len);
+ for (i = 0; i < len; i++)
+ {
+ if (((i % 16) == 0) && (i != 0))
+ fprintf(stderr, "\n");
+ fprintf(stderr, " %2.2x", buf[i]);
+ }
+ fprintf(stderr, "\n");
+}
+
+IPMI_OEM
+ipmi_get_oem(struct ipmi_intf* intf)
+{
+ /* Execute a Get Device ID command to determine the OEM */
+ struct ipmi_rs* rsp;
+ struct ipmi_rq req;
+ struct ipm_devid_rsp* devid;
+
+ if (intf->fd == 0)
+ {
+ if (sel_iana != IPMI_OEM_UNKNOWN)
+ {
+ return sel_iana;
+ }
+ return IPMI_OEM_UNKNOWN;
+ }
+
+ /*
+ * Return the cached manufacturer id if the device is open and
+ * we got an identified OEM owner. Otherwise just attempt to read
+ * it.
+ */
+ if (intf->opened && intf->manufacturer_id != IPMI_OEM_UNKNOWN)
+ {
+ return intf->manufacturer_id;
+ }
+
+ memset(&req, 0, sizeof(req));
+ req.msg.netfn = IPMI_NETFN_APP;
+ req.msg.cmd = BMC_GET_DEVICE_ID;
+ req.msg.data_len = 0;
+
+ rsp = intf->sendrecv(intf, &req);
+ if (rsp == NULL)
+ {
+ lprintf(LOG_ERR, "Get Device ID command failed");
+ return IPMI_OEM_UNKNOWN;
+ }
+ if (rsp->ccode > 0)
+ {
+ lprintf(LOG_ERR, "Get Device ID command failed: %#x %s", rsp->ccode,
+ val2str(rsp->ccode, completion_code_vals));
+ return IPMI_OEM_UNKNOWN;
+ }
+
+ devid = (struct ipm_devid_rsp*)rsp->data;
+
+ lprintf(LOG_DEBUG, "Iana: %u",
+ IPM_DEV_MANUFACTURER_ID(devid->manufacturer_id));
+
+ return IPM_DEV_MANUFACTURER_ID(devid->manufacturer_id);
+}
diff --git a/tools/libs/ipmitoolintf.c b/tools/libs/ipmitoolintf.c
new file mode 100644
index 0000000..b68d454
--- /dev/null
+++ b/tools/libs/ipmitoolintf.c
@@ -0,0 +1,65 @@
+#include "ipmitoolintf.h"
+
+#include <ipmitool/ipmi_intf.h>
+#include <string.h>
+
+extern struct ipmi_intf ipmi_open_intf;
+
+int ipmiSendCommand(const uint8_t* bytes, int length, struct IpmiResponse* resp)
+{
+ struct ipmi_intf* intf = &ipmi_open_intf;
+
+ /* The length needs to be at least two bytes [netfn][cmd] */
+ if (length < 2)
+ {
+ return -1;
+ }
+
+ uint8_t data[MAX_PIPELINE_BANDWIDTH];
+ struct ipmi_rq request;
+ memset(&data[0], 0, sizeof(data));
+ memset(&request, 0, sizeof(request));
+
+ ipmi_intf_session_set_timeout(intf, 15);
+ /* The retry here isn't used for the normal BT interface comms it appears.
+ * Only references found in sol and serial, and lan.
+ */
+ ipmi_intf_session_set_retry(intf, 1);
+
+ request.msg.netfn = bytes[0];
+ request.msg.lun = 0x00;
+ request.msg.cmd = bytes[1];
+ request.msg.data = &data[0];
+
+ /* Can you fit the request in the buffer? */
+ if ((length - 2) > sizeof(data))
+ {
+ return -1;
+ }
+
+ /* Skip beyond netfn and command. */
+ memcpy(request.msg.data, &bytes[2], length - 2);
+ request.msg.data_len = length - 2;
+
+ /* Actually send the command and check for a response. */
+ struct ipmi_rs* response = intf->sendrecv(intf, &request);
+ if (!response)
+ {
+ return -1;
+ }
+
+ /* If the caller wanted the response back. */
+ if (resp)
+ {
+ resp->ccode = response->ccode;
+ if (response->data_len <= sizeof(resp->data))
+ {
+ memcpy(resp->data, response->data, response->data_len);
+ resp->dataLen = response->data_len;
+ return 0;
+ }
+ /* TODO: deal with truncation... */
+ }
+
+ return 0;
+}
diff --git a/tools/libs/ipmitoolintf.h b/tools/libs/ipmitoolintf.h
new file mode 100644
index 0000000..c4994fd
--- /dev/null
+++ b/tools/libs/ipmitoolintf.h
@@ -0,0 +1,28 @@
+#pragma once
+
+/* The Aspeed AST2400 & AST2500 have 64 bytes of SRAM as the FIFO for each
+ * direction, of which 2 bytes are reserved for len and for seq by upper layer
+ * ipmi driver.
+ */
+#define MAX_PIPELINE_BANDWIDTH 62
+#define IPMI_BUF_SIZE 1024
+
+#include <stdint.h>
+
+struct IpmiResponse
+{
+ uint8_t ccode;
+ uint8_t data[IPMI_BUF_SIZE];
+ int dataLen;
+};
+
+/**
+ * Call into the ipmitool source to send the IPMI packet.
+ *
+ * @param[in] bytes - the IPMI packet contents.
+ * @param[in] length - the number of bytes.
+ * @param[in,out] resp - a pointer to write the response.
+ * @return 0 on success.
+ */
+int ipmiSendCommand(const uint8_t* bytes, int length,
+ struct IpmiResponse* resp);
diff --git a/tools/main.cpp b/tools/main.cpp
new file mode 100644
index 0000000..9976a65
--- /dev/null
+++ b/tools/main.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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 "updater.hpp"
+
+#include <getopt.h>
+
+#include <cstdio>
+#include <set>
+#include <stdexcept>
+#include <string>
+
+static void usage(const char* name)
+{
+ std::fprintf(
+ stderr,
+ "Usage: %s -c <COMMAND> -i <INTERFACE> -m <IMAGE> -s <SIGNATURE>\n"
+ " -c, --command <COMMAND> the command to run 'update', 'reboot', "
+ "'ping'\n"
+ " -i, --interface <INTERFACE> the interface to use, 'ipmibt'\n"
+ " -m, --imaage <IMAGE> the image file\n"
+ " -s, --signature <SIGNATURE> the signature file\n"
+ "\n"
+ " If command is update, you must specify an interface, image, and "
+ "signature.\n",
+ name);
+}
+
+int main(int argc, char* argv[])
+{
+ int opt;
+
+ // clang-format off
+ static const struct option long_options[] = {
+ {"command", required_argument, NULL, 'c'},
+ {"interface", required_argument, NULL, 'i'},
+ {"image", required_argument, NULL, 'm'},
+ {"signature", required_argument, NULL, 's'},
+ {0, 0, 0, 0}
+ };
+ // clang-format on
+ const char* pm = "c:i:m:s:";
+
+ std::string command, interface, image, signature;
+
+ while ((opt = getopt_long(argc, argv, pm, long_options, NULL)) != -1)
+ {
+ switch (opt)
+ {
+ case 0:
+ break;
+ case 'c':
+ command = optarg;
+ break;
+ case 'i':
+ interface = optarg;
+ break;
+ case 'm':
+ image = optarg;
+ break;
+ case 's':
+ signature = optarg;
+ break;
+ default:
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (command.empty())
+ {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ static const std::set<std::string> supportedCommands = {"update"};
+ if (!supportedCommands.count(command))
+ {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (command == "update")
+ {
+ if (interface.empty() || image.empty() || signature.empty())
+ {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ try
+ {
+ UpdaterMain(interface, image, signature);
+ return 0;
+ }
+ catch (const std::runtime_error& e)
+ {
+ fprintf(stderr, "runtime error: %s\n", e.what());
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ return 0;
+}
diff --git a/tools/test/Makefile.am b/tools/test/Makefile.am
new file mode 100644
index 0000000..1d3fa25
--- /dev/null
+++ b/tools/test/Makefile.am
@@ -0,0 +1,7 @@
+AM_CPPFLAGS =
+AM_CXXFLAGS =
+AM_LDFLAGS =
+
+# Run all 'check' test programs
+check_PROGRAMS =
+TESTS = $(check_PROGRAMS)
diff --git a/tools/test/interface_mock.hpp b/tools/test/interface_mock.hpp
new file mode 100644
index 0000000..4b3aeec
--- /dev/null
+++ b/tools/test/interface_mock.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "interface.hpp"
+
+class MockData : public DataInterface
+{
+ public:
+ MOCK_METHOD2(SendData, void(std::FILE*, int));
+ MOCK_METHOD0(External, bool());
+};
diff --git a/tools/test/raw_mock.hpp b/tools/test/raw_mock.hpp
new file mode 100644
index 0000000..bfcb90b
--- /dev/null
+++ b/tools/test/raw_mock.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "raw.hpp"
+
+class MockRaw : public RawInterface
+{
+ public:
+ MOCK_METHOD1(Raw, struct IpmiResponse(const std::vector<uint8_t>&));
+ MOCK_METHOD2(RawWithTries,
+ struct IpmiResponse(const std::vector<uint8_t>&, int));
+};
diff --git a/tools/updater.cpp b/tools/updater.cpp
new file mode 100644
index 0000000..c012c76
--- /dev/null
+++ b/tools/updater.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "updater.hpp"
+
+#include "bt.hpp"
+#include "raw.hpp"
+#include "updatehelper.hpp"
+
+#include <experimental/filesystem>
+#include <fstream>
+#include <memory>
+#include <set>
+#include <stdexcept>
+
+extern "C" {
+#include "ipmitoolintf.h"
+} // extern "C"
+
+std::unique_ptr<UploadManager> UploadManager::BuildUploadMgr(
+ const std::string& image, const std::string& hash,
+ UpdateHelperInterface* helper, DataInterface* dintf)
+{
+ std::ifstream imageStream, hashStream;
+
+ imageStream.open(image);
+ hashStream.open(hash);
+ if (imageStream.bad() || hashStream.bad())
+ {
+ return nullptr;
+ }
+
+ int32_t imageSize = std::experimental::filesystem::file_size(image);
+ int32_t hashSize = std::experimental::filesystem::file_size(hash);
+
+ return std::make_unique<UploadManager>(std::move(imageStream),
+ std::move(hashStream), imageSize,
+ hashSize, helper, dintf);
+}
+
+void UploadManager::UpdateBMC()
+{
+ /* Let's build the raw command input.
+ *
+ * The sequence is:
+ * FLASH_START_TRANSFER,
+ * FLASH_DATA_BLOCK x times. (or FLASH_EXTERNAL_DATA_BLOCK)
+ * FLASH_DATA_FINISH
+ * FLASH_START_HASH
+ * FLASH_HASH_DATA x times. (or FLASH_EXTERNAL_HASH_BLOCK)
+ * FLASH_HASH_FINISH
+ * FLASH_DATA_VERIFY
+ * FLASH_VERIFY_CHECK x times.
+ */
+
+ /* TODO: implement this. */
+
+ /* UploadImage() */
+ /* UploadHash() */
+
+ /*
+ * FLASH_DATA_VERIFY - The verify command will trigger verification of the
+ * image against the signature file sent down.
+ */
+ // ret = helper_->SendEmptyCommand(FLASH_DATA_VERIFY, nullptr);
+ // if (!ret.ok()) return ret;
+
+ return;
+}
+
+void UpdaterMain(const std::string& interface, const std::string& image,
+ const std::string& signature)
+{
+ static const std::set<std::string> supportedInterfaces = {"ipmibt"};
+
+ /* Check if interface is supported. */
+ if (!supportedInterfaces.count(interface))
+ {
+ throw std::runtime_error("Unsupported interface");
+ }
+
+ /* NOTE: Presently, the hash signature being separate is optional on the BMC
+ * but isn't here, for now.
+ */
+
+ /* There are three key components to the process. There's the data handler,
+ * which deals with sending the data, and it uses a convenience method to
+ * package the specific IPMI firmware update commands, and those commands
+ * are then routed through a convenience layer that will handle calling into
+ * the C-library.
+ */
+ IpmiRaw raw;
+ IpmiUpdateHelper ipmih(&raw);
+ std::unique_ptr<DataInterface> handler;
+
+ if (interface == "ipmibt")
+ {
+ handler = std::make_unique<BtDataHandler>(&ipmih);
+ }
+
+ if (handler == nullptr)
+ {
+ throw std::runtime_error("Unable to build interface handler.");
+ }
+
+ auto updater =
+ UploadManager::BuildUploadMgr(image, signature, &ipmih, handler.get());
+
+ if (updater == nullptr)
+ {
+ throw std::runtime_error("Unable to build update manager.");
+ }
+
+ updater->UpdateBMC();
+
+ return;
+}
diff --git a/tools/updater.hpp b/tools/updater.hpp
new file mode 100644
index 0000000..561e6c7
--- /dev/null
+++ b/tools/updater.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "interface.hpp"
+#include "updatehelper.hpp"
+
+#include <fstream>
+#include <memory>
+#include <string>
+
+class UploadManager
+{
+ public:
+ UploadManager(std::ifstream&& image, std::ifstream&& hash,
+ int32_t imageSize, int32_t hashSize,
+ UpdateHelperInterface* helper, DataInterface* dintf) :
+ imageStream(std::move(image)),
+ hashStream(std::move(hash)), imageSize(imageSize), hashSize(hashSize),
+ helper(helper), dintf(dintf)
+ {
+ }
+
+ /**
+ * Instantiate an UploadManager if the parameters check out.
+ *
+ * @param[in] image - path to the firmware image.
+ * @param[in] hash - path to the image's hash.
+ * @param[in] helper - pointer to an UpdateHelperInterface.
+ * @param[in] dintf - pointer to the data interface to use.
+ * @return UploadManager if valid or nullptr if invalid.
+ */
+ static std::unique_ptr<UploadManager>
+ BuildUploadMgr(const std::string& image, const std::string& hash,
+ UpdateHelperInterface* helper, DataInterface* dintf);
+
+ /**
+ * Try to update the BMC flash image over IPMI through BT.
+ */
+ void UpdateBMC();
+
+ private:
+ std::ifstream imageStream;
+ std::ifstream hashStream;
+ int32_t imageSize;
+ int32_t hashSize;
+ UpdateHelperInterface* helper;
+ DataInterface* dintf;
+};
+
+// Main entry point for the update command.
+// Update uploads and verifies the image.
+// throws exception on errors.
+void UpdaterMain(const std::string& interface, const std::string& image,
+ const std::string& signature);