summaryrefslogtreecommitdiff
path: root/extra/usb_updater/gsctool.c
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2017-09-06 15:51:46 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-10-10 22:13:43 -0700
commita08f61506cc71b690858e51da905c7dcb2d7ef10 (patch)
tree7f504d1fdaa6c6a8630656e95f1ed15e2b774d4d /extra/usb_updater/gsctool.c
parentf1e6af516c87623f475900f8fc85cb9c40668092 (diff)
downloadchrome-ec-a08f61506cc71b690858e51da905c7dcb2d7ef10.tar.gz
g: rename usb_updater into gsctool
The usb_updater utility has long been not just an updater, and has long been using other interfaces in addition to USB. gsctool is a much more suitable name. CQ-DEPEND=CL:709776 BRANCH=cr50 BUG=b:67007500 TEST=verified that make -C ./extra/usb_updater generates ./extra/usb_updater/gsctool: $ ./extra/usb_updater/gsctool --help Usage: gsctool [options] <binary image> This updates the Cr50 RW firmware over USB. The required argument is the full RO+RW image. Options: [...] $ Change-Id: I3ab70c28acf3664ddefaa923a87ba1fd5c3c437b Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/709738 Reviewed-by: Mary Ruthven <mruthven@chromium.org>
Diffstat (limited to 'extra/usb_updater/gsctool.c')
-rw-r--r--extra/usb_updater/gsctool.c1841
1 files changed, 1841 insertions, 0 deletions
diff --git a/extra/usb_updater/gsctool.c b/extra/usb_updater/gsctool.c
new file mode 100644
index 0000000000..6e79be1e3a
--- /dev/null
+++ b/extra/usb_updater/gsctool.c
@@ -0,0 +1,1841 @@
+/*
+ * Copyright 2015 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <asm/byteorder.h>
+#include <ctype.h>
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libusb.h>
+#include <openssl/sha.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#include "config.h"
+
+#include "compile_time_macros.h"
+#include "misc_util.h"
+#include "signed_header.h"
+#include "tpm_vendor_cmds.h"
+#include "upgrade_fw.h"
+#include "usb_descriptor.h"
+
+#ifdef DEBUG
+#define debug printf
+#else
+#define debug(fmt, args...)
+#endif
+
+/*
+ * This file contains the source code of a Linux application used to update
+ * CR50 device firmware.
+ *
+ * The CR50 firmware image consists of multiple sections, of interest to this
+ * app are the RO and RW code sections, two of each. When firmware update
+ * session is established, the CR50 device reports locations of backup RW and RO
+ * sections (those not used by the device at the time of transfer).
+ *
+ * Based on this information this app carves out the appropriate sections form
+ * the full CR50 firmware binary image and sends them to the device for
+ * programming into flash. Once the new sections are programmed and the device
+ * is restarted, the new RO and RW are used if they pass verification and are
+ * logically newer than the existing sections.
+ *
+ * There are two ways to communicate with the CR50 device: USB and /dev/tpm0
+ * (when this app is running on a chromebook with the CR50 device). Originally
+ * different protocols were used to communicate over different channels,
+ * starting with version 3 the same protocol is used.
+ *
+ * This app provides backwards compatibility to ensure that earlier CR50
+ * devices still can be updated.
+ *
+ *
+ * The host (either a local AP or a workstation) is the master of the firmware
+ * update protocol, it sends data to the cr50 device, which proceeses it and
+ * responds.
+ *
+ * The encapsultation format is different between the /dev/tpm0 and USB cases:
+ *
+ * 4 bytes 4 bytes 4 bytes variable size
+ * +-----------+--------------+---------------+----------~~--------------+
+ * + total size| block digest | dest address | data |
+ * +-----------+--------------+---------------+----------~~--------------+
+ * \ \ /
+ * \ \ /
+ * \ +----- FW update PDU sent over /dev/tpm0 -----------+
+ * \ /
+ * +--------- USB frame, requires total size field ------------+
+ *
+ * The update protocol data unints (PDUs) are passed over /dev/tpm0, the
+ * encapsulation includes integritiy verification and destination address of
+ * the data (more of this later). /dev/tpm0 transactions pretty much do not
+ * have size limits, whereas the USB data is sent in chunks of the size
+ * determined when the USB connestion is set up. This is why USB requires an
+ * additional encapsulation into frames to communicate the PDU size to the
+ * client side so that the PDU can be reassembled before passing to the
+ * programming function.
+ *
+ * In general, the protocol consists of two phases: connection establishment
+ * and actual image transfer.
+ *
+ * The very first PDU of the transfer session is used to establish the
+ * connection. The first PDU does not have any data, and the dest. address
+ * field is set to zero. Receiving such a PDU signals the programming function
+ * that the host intends to transfer a new image.
+ *
+ * The response to the first PDU varies depending on the protocol version.
+ *
+ * Note that protocol versions before 5 are described here for completeness,
+ * but are not supported any more by this utility.
+ *
+ * Version 1 is used over /dev/tpm0. The response is either 4 or 1 bytes in
+ * size. The 4 byte response is the *base address* of the backup RW section,
+ * no support for RO updates. The one byte response is an error indication,
+ * possibly reporting flash erase failure, command format error, etc.
+ *
+ * Version 2 is used over USB. The response is 8 bytes in size. The first four
+ * bytes are either the *base address* of the backup RW section (still no RO
+ * updates), or an error code, the same as in Version 1. The second 4 bytes
+ * are the protocol version number (set to 2).
+ *
+ * All versions above 2 behave the same over /dev/tpm0 and USB.
+ *
+ * Version 3 response is 16 bytes in size. The first 4 bytes are the error code
+ * the second 4 bytes are the protocol version (set to 3) and then 4 byte
+ * *offset* of the RO section followed by the 4 byte *offset* of the RW section.
+ *
+ * Version 4 response in addition to version 3 provides header revision fields
+ * for active RO and RW images running on the target.
+ *
+ * Once the connection is established, the image to be programmed into flash
+ * is transferred to the CR50 in 1K PDUs. In versions 1 and 2 the address in
+ * the header is the absolute address to place the block to, in version 3 and
+ * later it is the offset into the flash.
+ *
+ * Protocol version 5 includes RO and RW key ID information into the first PDU
+ * response. The key ID could be used to tell between prod and dev signing
+ * modes, among other things.
+ *
+ * Protocol version 6 does not change the format of the first PDU response,
+ * but it indicates the target's ablitiy to channel TPM venfor commands
+ * through USB connection.
+ *
+ * When channeling TPM vendor commands the USB frame looks as follows:
+ *
+ * 4 bytes 4 bytes 4 bytes 2 bytes variable size
+ * +-----------+--------------+---------------+-----------+------~~~-------+
+ * + total size| block digest | EXT_CMD | Vend. sub.| data |
+ * +-----------+--------------+---------------+-----------+------~~~-------+
+ *
+ * Where 'Vend. sub' is the vendor subcommand, and data field is subcommand
+ * dependent. The target tells between update PDUs and encapsulated vendor
+ * subcommands by looking at the EXT_CMD value - it is set to 0xbaccd00a and
+ * as such is guaranteed not to be a valid update PDU destination address.
+ *
+ * The vendor command response size is not fixed, it is subcommand dependent.
+ *
+ * The CR50 device responds to each update PDU with a confirmation which is 4
+ * bytes in size in protocol version 2, and 1 byte in size in all other
+ * versions. Zero value means success, non zero value is the error code
+ * reported by CR50.
+ *
+ * Again, vendor command responses are subcommand specific.
+ */
+
+/* Look for Cr50 FW update interface */
+#define VID USB_VID_GOOGLE
+#define PID CONFIG_USB_PID
+#define SUBCLASS USB_SUBCLASS_GOOGLE_CR50
+#define PROTOCOL USB_PROTOCOL_GOOGLE_CR50_NON_HC_FW_UPDATE
+
+enum exit_values {
+ noop = 0, /* All up to date, no update needed. */
+ all_updated = 1, /* Update completed, reboot required. */
+ rw_updated = 2, /* RO was not updated, reboot required. */
+ update_error = 3 /* Something went wrong. */
+};
+
+/*
+ * Need to create an entire TPM PDU when upgrading over /dev/tpm0 and need to
+ * have space to prepare the entire PDU.
+ */
+struct upgrade_pkt {
+ __be16 tag;
+ __be32 length;
+ __be32 ordinal;
+ __be16 subcmd;
+ union {
+ /*
+ * Upgrade PDUs as opposed to all other vendor and extension
+ * commands include two additional fields in the header.
+ */
+ struct {
+ __be32 digest;
+ __be32 address;
+ char data[0];
+ } upgrade;
+ struct {
+ char data[0];
+ } command;
+ };
+} __packed;
+
+
+/*
+ * This by far exceeds the largest vendor command response size we ever
+ * expect.
+ */
+#define MAX_BUF_SIZE 500
+
+struct usb_endpoint {
+ struct libusb_device_handle *devh;
+ uint8_t ep_num;
+ int chunk_len;
+};
+
+struct transfer_descriptor {
+ /*
+ * Set to true for use in an upstart script. Do not reboot after
+ * transfer, and do not transfer RW if versions are the same.
+ *
+ * When using in development environment it is beneficial to transfer
+ * RW images with the same version, as they get started based on the
+ * header timestamp.
+ */
+ uint32_t upstart_mode;
+
+ /*
+ * offsets of RO and WR sections available for update (not currently
+ * active).
+ */
+ uint32_t ro_offset;
+ uint32_t rw_offset;
+ uint32_t post_reset;
+ enum transfer_type {
+ usb_xfer = 0,
+ dev_xfer = 1,
+ ts_xfer = 2
+ } ep_type;
+ union {
+ struct usb_endpoint uep;
+ int tpm_fd;
+ };
+};
+
+static uint32_t protocol_version;
+static char *progname;
+static char *short_opts = "bcd:fhiprstu";
+static const struct option long_opts[] = {
+ /* name hasarg *flag val */
+ {"binvers", 0, NULL, 'b'},
+ {"board_id", 2, NULL, 'i'},
+ {"corrupt", 0, NULL, 'c'},
+ {"device", 1, NULL, 'd'},
+ {"fwver", 0, NULL, 'f'},
+ {"help", 0, NULL, 'h'},
+ {"post_reset", 0, NULL, 'p'},
+ {"rma_auth", 0, NULL, 'r'},
+ {"systemdev", 0, NULL, 's'},
+ {"trunks_send", 0, NULL, 't'},
+ {"upstart", 0, NULL, 'u'},
+ {},
+};
+
+
+
+/* Helpers to convert between binary and hex ascii and back. */
+static char to_hexascii(uint8_t c)
+{
+ if (c <= 9)
+ return '0' + c;
+ return 'a' + c - 10;
+}
+
+static int from_hexascii(char c)
+{
+ /* convert to lower case. */
+ c = tolower(c);
+
+ if (c < '0' || c > 'f' || ((c > '9') && (c < 'a')))
+ return -1; /* Not an ascii character. */
+
+ if (c <= '9')
+ return c - '0';
+
+ return c - 'a' + 10;
+}
+
+/* Functions to communicate with the TPM over the trunks_send --raw channel. */
+
+/* File handle to share between write and read sides. */
+static FILE *tpm_output;
+static int ts_write(const void *out, size_t len)
+{
+ const char *cmd_head = "trunks_send --raw ";
+ size_t head_size = strlen(cmd_head);
+ char full_command[head_size + 2 * len + 1];
+ size_t i;
+
+ strcpy(full_command, cmd_head);
+ /*
+ * Need to convert binary input into hex ascii output to pass to the
+ * trunks_send command.
+ */
+ for (i = 0; i < len; i++) {
+ uint8_t c = ((const uint8_t *)out)[i];
+
+ full_command[head_size + 2 * i] = to_hexascii(c >> 4);
+ full_command[head_size + 2 * i + 1] = to_hexascii(c & 0xf);
+ }
+
+ /* Make it a proper zero terminated string. */
+ full_command[sizeof(full_command) - 1] = 0;
+ debug("cmd: %s\n", full_command);
+ tpm_output = popen(full_command, "r");
+ if (tpm_output)
+ return len;
+
+ fprintf(stderr, "Error: failed to launch trunks_send --raw\n");
+ return -1;
+}
+
+static int ts_read(void *buf, size_t max_rx_size)
+{
+ int i;
+ int pclose_rv;
+ int rv;
+ char response[max_rx_size * 2];
+
+ if (!tpm_output) {
+ fprintf(stderr, "Error: attempt to read empty output\n");
+ return -1;
+ }
+
+ rv = fread(response, 1, sizeof(response), tpm_output);
+ if (rv > 0)
+ rv -= 1; /* Discard the \n character added by trunks_send. */
+
+ debug("response of size %d, max rx size %zd: %s\n",
+ rv, max_rx_size, response);
+
+ pclose_rv = pclose(tpm_output);
+ if (pclose_rv < 0) {
+ fprintf(stderr,
+ "Error: pclose failed: error %d (%s)\n",
+ errno, strerror(errno));
+ return -1;
+ }
+
+ tpm_output = NULL;
+
+ if (rv & 1) {
+ fprintf(stderr,
+ "Error: trunks_send returned odd number of bytes: %s\n",
+ response);
+ return -1;
+ }
+
+ for (i = 0; i < rv/2; i++) {
+ uint8_t byte;
+ char c;
+ int nibble;
+
+ c = response[2 * i];
+ nibble = from_hexascii(c);
+ if (nibble < 0) {
+ fprintf(stderr, "Error: "
+ "trunks_send returned non hex character %c\n",
+ c);
+ return -1;
+ }
+ byte = nibble << 4;
+
+ c = response[2 * i + 1];
+ nibble = from_hexascii(c);
+ if (nibble < 0) {
+ fprintf(stderr, "Error: "
+ "trunks_send returned non hex character %c\n",
+ c);
+ return -1;
+ }
+ byte |= nibble;
+
+ ((uint8_t *)buf)[i] = byte;
+ }
+
+ return rv/2;
+}
+
+/*
+ * Prepare and transfer a block to either to /dev/tpm0 or through trunks_send
+ * --raw, get a reply.
+ */
+static int tpm_send_pkt(struct transfer_descriptor *td, unsigned int digest,
+ unsigned int addr, const void *data, int size,
+ void *response, size_t *response_size,
+ uint16_t subcmd)
+{
+ /* Used by transfer to /dev/tpm0 */
+ static uint8_t outbuf[MAX_BUF_SIZE];
+ struct upgrade_pkt *out = (struct upgrade_pkt *)outbuf;
+ int len, done;
+ int response_offset = offsetof(struct upgrade_pkt, command.data);
+ void *payload;
+ size_t header_size;
+ uint32_t rv;
+ const size_t rx_size = sizeof(outbuf);
+
+ debug("%s: sending to %#x %d bytes\n", __func__, addr, size);
+
+ out->tag = htobe16(0x8001);
+ out->subcmd = htobe16(subcmd);
+
+ if (subcmd <= LAST_EXTENSION_COMMAND)
+ out->ordinal = htobe32(CONFIG_EXTENSION_COMMAND);
+ else
+ out->ordinal = htobe32(TPM_CC_VENDOR_BIT_MASK);
+
+ if (subcmd == EXTENSION_FW_UPGRADE) {
+ /* FW Upgrade PDU header includes a couple of extra fields. */
+ out->upgrade.digest = digest;
+ out->upgrade.address = htobe32(addr);
+ header_size = offsetof(struct upgrade_pkt, upgrade.data);
+ } else {
+ header_size = offsetof(struct upgrade_pkt, command.data);
+ }
+
+ payload = outbuf + header_size;
+ len = size + header_size;
+
+ out->length = htobe32(len);
+ memcpy(payload, data, size);
+#ifdef DEBUG
+ {
+ int i;
+
+ debug("Writing %d bytes to TPM at %x\n", len, addr);
+ for (i = 0; i < 20; i++)
+ debug("%2.2x ", outbuf[i]);
+ debug("\n");
+ }
+#endif
+ switch (td->ep_type) {
+ case dev_xfer:
+ done = write(td->tpm_fd, out, len);
+ break;
+ case ts_xfer:
+ done = ts_write(out, len);
+ break;
+ default:
+ fprintf(stderr, "Error: %s:%d: unknown transfer type %d\n",
+ __func__, __LINE__, td->ep_type);
+ return -1;
+ }
+
+ if (done < 0) {
+ perror("Could not write to TPM");
+ return -1;
+ } else if (done != len) {
+ fprintf(stderr, "Error: Wrote %d bytes, expected to write %d\n",
+ done, len);
+ return -1;
+ }
+
+ switch (td->ep_type) {
+ case dev_xfer: {
+ int read_count;
+
+ len = 0;
+ do {
+ uint8_t *rx_buf = outbuf + len;
+ size_t rx_to_go = rx_size - len;
+
+ read_count = read(td->tpm_fd, rx_buf, rx_to_go);
+
+ len += read_count;
+ } while (read_count);
+ break;
+ }
+ case ts_xfer:
+ len = ts_read(outbuf, rx_size);
+ break;
+ default:
+ /*
+ * This sure will never happen, type is verifed in the
+ * previous switch statement.
+ */
+ len = -1;
+ break;
+ }
+
+#ifdef DEBUG
+ debug("Read %d bytes from TPM\n", len);
+ if (len > 0) {
+ int i;
+
+ for (i = 0; i < len; i++)
+ debug("%2.2x ", outbuf[i]);
+ debug("\n");
+ }
+#endif
+ len = len - response_offset;
+ if (len < 0) {
+ fprintf(stderr, "Problems reading from TPM, got %d bytes\n",
+ len + response_offset);
+ return -1;
+ }
+
+ if (response && response_size) {
+ len = MIN(len, *response_size);
+ memcpy(response, outbuf + response_offset, len);
+ *response_size = len;
+ }
+
+ /* Return the actual return code from the TPM response header. */
+ memcpy(&rv, &((struct upgrade_pkt *)outbuf)->ordinal, sizeof(rv));
+ rv = be32toh(rv);
+
+ return rv;
+}
+
+/* Release USB device and return error to the OS. */
+static void shut_down(struct usb_endpoint *uep)
+{
+ libusb_close(uep->devh);
+ libusb_exit(NULL);
+ exit(update_error);
+}
+
+static void usage(int errs)
+{
+ printf("\nUsage: %s [options] <binary image>\n"
+ "\n"
+ "This updates the Cr50 RW firmware over USB.\n"
+ "The required argument is the full RO+RW image.\n"
+ "\n"
+ "Options:\n"
+ "\n"
+ " -b,--binvers Report versions of image's "
+ "RW and RO headers, do not update\n"
+ " -c,--corrupt Corrupt the inactive rw.\n"
+ " -d,--device VID:PID USB device (default %04x:%04x)\n"
+ " -f,--fwver Report running firmware versions.\n"
+ " -h,--help Show this message\n"
+ " -i,--board_id [ID[:FLAGS]]\n"
+ " Get or set Info1 board ID fields.\n"
+ " ID could be 32 bit hex or 4 "
+ "character string.\n"
+ " -p,--post_reset Request post reset after transfer\n"
+ " -r,--rma_auth Process RMA challenge-response\n"
+ " -s,--systemdev Use /dev/tpm0 (-d is ignored)\n"
+ " -t,--trunks_send Use `trunks_send --raw' "
+ "(-d is ignored)\n"
+ " -u,--upstart "
+ "Upstart mode (strict header checks)\n"
+ "\n", progname, VID, PID);
+
+ exit(errs ? update_error : noop);
+}
+
+/* Read file into buffer */
+static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr)
+{
+ FILE *fp;
+ struct stat st;
+ uint8_t *data;
+ size_t len;
+
+ fp = fopen(filename, "rb");
+ if (!fp) {
+ perror(filename);
+ exit(update_error);
+ }
+ if (fstat(fileno(fp), &st)) {
+ perror("stat");
+ exit(update_error);
+ }
+
+ len = st.st_size;
+
+ data = malloc(len);
+ if (!data) {
+ perror("malloc");
+ exit(update_error);
+ }
+
+ if (1 != fread(data, st.st_size, 1, fp)) {
+ perror("fread");
+ exit(update_error);
+ }
+
+ fclose(fp);
+
+ *len_ptr = len;
+ return data;
+}
+
+#define USB_ERROR(m, r) \
+ fprintf(stderr, "%s:%d, %s returned %d (%s)\n", __FILE__, __LINE__, \
+ m, r, libusb_strerror(r))
+
+/*
+ * Actual USB transfer function, the 'allow_less' flag indicates that the
+ * valid response could be shortef than allotted memory, the 'rxed_count'
+ * pointer, if provided along with 'allow_less' lets the caller know how mavy
+ * bytes were received.
+ */
+static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen,
+ void *inbuf, int inlen, int allow_less,
+ size_t *rxed_count)
+{
+
+ int r, actual;
+
+ /* Send data out */
+ if (outbuf && outlen) {
+ actual = 0;
+ r = libusb_bulk_transfer(uep->devh, uep->ep_num,
+ outbuf, outlen,
+ &actual, 1000);
+ if (r < 0) {
+ USB_ERROR("libusb_bulk_transfer", r);
+ exit(update_error);
+ }
+ if (actual != outlen) {
+ fprintf(stderr, "%s:%d, only sent %d/%d bytes\n",
+ __FILE__, __LINE__, actual, outlen);
+ shut_down(uep);
+ }
+ }
+
+ /* Read reply back */
+ if (inbuf && inlen) {
+
+ actual = 0;
+ r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80,
+ inbuf, inlen,
+ &actual, 1000);
+ if (r < 0) {
+ USB_ERROR("libusb_bulk_transfer", r);
+ exit(update_error);
+ }
+ if ((actual != inlen) && !allow_less) {
+ fprintf(stderr, "%s:%d, only received %d/%d bytes\n",
+ __FILE__, __LINE__, actual, inlen);
+ shut_down(uep);
+ }
+
+ if (rxed_count)
+ *rxed_count = actual;
+ }
+}
+
+static void xfer(struct usb_endpoint *uep, void *outbuf,
+ size_t outlen, void *inbuf, size_t inlen)
+{
+ do_xfer(uep, outbuf, outlen, inbuf, inlen, 0, NULL);
+}
+
+/* Return 0 on error, since it's never gonna be EP 0 */
+static int find_endpoint(const struct libusb_interface_descriptor *iface,
+ struct usb_endpoint *uep)
+{
+ const struct libusb_endpoint_descriptor *ep;
+
+ if (iface->bInterfaceClass == 255 &&
+ iface->bInterfaceSubClass == SUBCLASS &&
+ iface->bInterfaceProtocol == PROTOCOL &&
+ iface->bNumEndpoints) {
+ ep = &iface->endpoint[0];
+ uep->ep_num = ep->bEndpointAddress & 0x7f;
+ uep->chunk_len = ep->wMaxPacketSize;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Return -1 on error */
+static int find_interface(struct usb_endpoint *uep)
+{
+ int iface_num = -1;
+ int r, i, j;
+ struct libusb_device *dev;
+ struct libusb_config_descriptor *conf = 0;
+ const struct libusb_interface *iface0;
+ const struct libusb_interface_descriptor *iface;
+
+ dev = libusb_get_device(uep->devh);
+ r = libusb_get_active_config_descriptor(dev, &conf);
+ if (r < 0) {
+ USB_ERROR("libusb_get_active_config_descriptor", r);
+ goto out;
+ }
+
+ for (i = 0; i < conf->bNumInterfaces; i++) {
+ iface0 = &conf->interface[i];
+ for (j = 0; j < iface0->num_altsetting; j++) {
+ iface = &iface0->altsetting[j];
+ if (find_endpoint(iface, uep)) {
+ iface_num = i;
+ goto out;
+ }
+ }
+ }
+
+out:
+ libusb_free_config_descriptor(conf);
+ return iface_num;
+}
+
+/* Returns true if parsed. */
+static int parse_vidpid(const char *input, uint16_t *vid_ptr, uint16_t *pid_ptr)
+{
+ char *copy, *s, *e = 0;
+
+ copy = strdup(input);
+
+ s = strchr(copy, ':');
+ if (!s)
+ return 0;
+ *s++ = '\0';
+
+ *vid_ptr = (uint16_t) strtoul(copy, &e, 16);
+ if (!*optarg || (e && *e))
+ return 0;
+
+ *pid_ptr = (uint16_t) strtoul(s, &e, 16);
+ if (!*optarg || (e && *e))
+ return 0;
+
+ return 1;
+}
+
+
+static void usb_findit(uint16_t vid, uint16_t pid, struct usb_endpoint *uep)
+{
+ int iface_num, r;
+
+ memset(uep, 0, sizeof(*uep));
+
+ r = libusb_init(NULL);
+ if (r < 0) {
+ USB_ERROR("libusb_init", r);
+ exit(update_error);
+ }
+
+ printf("open_device %04x:%04x\n", vid, pid);
+ /* NOTE: This doesn't handle multiple matches! */
+ uep->devh = libusb_open_device_with_vid_pid(NULL, vid, pid);
+ if (!uep->devh) {
+ fprintf(stderr, "Can't find device\n");
+ exit(update_error);
+ }
+
+ iface_num = find_interface(uep);
+ if (iface_num < 0) {
+ fprintf(stderr, "USB FW update not supported by that device\n");
+ shut_down(uep);
+ }
+ if (!uep->chunk_len) {
+ fprintf(stderr, "wMaxPacketSize isn't valid\n");
+ shut_down(uep);
+ }
+
+ printf("found interface %d endpoint %d, chunk_len %d\n",
+ iface_num, uep->ep_num, uep->chunk_len);
+
+ libusb_set_auto_detach_kernel_driver(uep->devh, 1);
+ r = libusb_claim_interface(uep->devh, iface_num);
+ if (r < 0) {
+ USB_ERROR("libusb_claim_interface", r);
+ shut_down(uep);
+ }
+
+ printf("READY\n-------\n");
+}
+
+struct update_pdu {
+ uint32_t block_size; /* Total block size, include this field's size. */
+ struct upgrade_command cmd;
+ /* The actual payload goes here. */
+};
+
+static int transfer_block(struct usb_endpoint *uep, struct update_pdu *updu,
+ uint8_t *transfer_data_ptr, size_t payload_size)
+{
+ size_t transfer_size;
+ uint32_t reply;
+ int actual;
+ int r;
+
+ /* First send the header. */
+ xfer(uep, updu, sizeof(*updu), NULL, 0);
+
+ /* Now send the block, chunk by chunk. */
+ for (transfer_size = 0; transfer_size < payload_size;) {
+ int chunk_size;
+
+ chunk_size = MIN(uep->chunk_len, payload_size - transfer_size);
+ xfer(uep, transfer_data_ptr, chunk_size, NULL, 0);
+ transfer_data_ptr += chunk_size;
+ transfer_size += chunk_size;
+ }
+
+ /* Now get the reply. */
+ r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80,
+ (void *) &reply, sizeof(reply),
+ &actual, 1000);
+ if (r) {
+ if (r == -7) {
+ fprintf(stderr, "Timeout!\n");
+ return r;
+ }
+ USB_ERROR("libusb_bulk_transfer", r);
+ shut_down(uep);
+ }
+
+ reply = *((uint8_t *)&reply);
+ if (reply) {
+ fprintf(stderr, "Error: status %#x\n", reply);
+ exit(update_error);
+ }
+
+ return 0;
+}
+
+/**
+ * Transfer an image section (typically RW or RO).
+ *
+ * td - transfer descriptor to use to communicate with the target
+ * data_ptr - pointer at the section base in the image
+ * section_addr - address of the section in the target memory space
+ * data_len - section size
+ */
+static void transfer_section(struct transfer_descriptor *td,
+ uint8_t *data_ptr,
+ uint32_t section_addr,
+ size_t data_len)
+{
+ /*
+ * Actually, we can skip trailing chunks of 0xff, as the entire
+ * section space must be erased before the update is attempted.
+ */
+ while (data_len && (data_ptr[data_len - 1] == 0xff))
+ data_len--;
+
+ printf("sending 0x%zx bytes to %#x\n", data_len, section_addr);
+ while (data_len) {
+ size_t payload_size;
+ SHA_CTX ctx;
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ int max_retries;
+ struct update_pdu updu;
+
+ /* prepare the header to prepend to the block. */
+ payload_size = MIN(data_len, SIGNED_TRANSFER_SIZE);
+ updu.block_size = htobe32(payload_size +
+ sizeof(struct update_pdu));
+
+ updu.cmd.block_base = htobe32(section_addr);
+
+ /* Calculate the digest. */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, &updu.cmd.block_base,
+ sizeof(updu.cmd.block_base));
+ SHA1_Update(&ctx, data_ptr, payload_size);
+ SHA1_Final(digest, &ctx);
+
+ /* Copy the first few bytes. */
+ memcpy(&updu.cmd.block_digest, digest,
+ sizeof(updu.cmd.block_digest));
+ if (td->ep_type == usb_xfer) {
+ for (max_retries = 10; max_retries; max_retries--)
+ if (!transfer_block(&td->uep, &updu,
+ data_ptr, payload_size))
+ break;
+
+ if (!max_retries) {
+ fprintf(stderr,
+ "Failed to transfer block, %zd to go\n",
+ data_len);
+ exit(update_error);
+ }
+ } else {
+ uint8_t error_code[4];
+ size_t rxed_size = sizeof(error_code);
+ uint32_t block_addr;
+
+ block_addr = section_addr;
+
+ /*
+ * A single byte response is expected, but let's give
+ * the driver a few extra bytes to catch cases when a
+ * different amount of data is transferred (which
+ * would indicate a synchronization problem).
+ */
+ if (tpm_send_pkt(td,
+ updu.cmd.block_digest,
+ block_addr,
+ data_ptr,
+ payload_size, error_code,
+ &rxed_size,
+ EXTENSION_FW_UPGRADE) < 0) {
+ fprintf(stderr,
+ "Failed to trasfer block, %zd to go\n",
+ data_len);
+ exit(update_error);
+ }
+ if (rxed_size != 1) {
+ fprintf(stderr, "Unexpected return size %zd\n",
+ rxed_size);
+ exit(update_error);
+ }
+
+ if (error_code[0]) {
+ fprintf(stderr, "Error %d\n", error_code[0]);
+ exit(update_error);
+ }
+ }
+ data_len -= payload_size;
+ data_ptr += payload_size;
+ section_addr += payload_size;
+ }
+}
+
+/* Information about the target */
+static struct first_response_pdu targ;
+
+/*
+ * Each RO or RW section of the new image can be in one of the following
+ * states.
+ */
+enum upgrade_status {
+ not_needed = 0, /* Version below or equal that on the target. */
+ not_possible, /*
+ * RO is newer, but can't be transferred due to
+ * target RW shortcomings.
+ */
+ needed /*
+ * This section needs to be transferred to the
+ * target.
+ */
+};
+
+/* This array describes all four sections of the new image. */
+static struct {
+ const char *name;
+ uint32_t offset;
+ uint32_t size;
+ enum upgrade_status ustatus;
+ struct signed_header_version shv;
+ uint32_t keyid;
+} sections[] = {
+ {"RO_A", CONFIG_RO_MEM_OFF, CONFIG_RO_SIZE},
+ {"RW_A", CONFIG_RW_MEM_OFF, CONFIG_RW_SIZE},
+ {"RO_B", CHIP_RO_B_MEM_OFF, CONFIG_RO_SIZE},
+ {"RW_B", CONFIG_RW_B_MEM_OFF, CONFIG_RW_SIZE}
+};
+
+/*
+ * Scan the new image and retrieve versions of all four sections, two RO and
+ * two RW.
+ */
+static void fetch_header_versions(const void *image)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sections); i++) {
+ const struct SignedHeader *h;
+
+ h = (const struct SignedHeader *)((uintptr_t)image +
+ sections[i].offset);
+ sections[i].shv.epoch = h->epoch_;
+ sections[i].shv.major = h->major_;
+ sections[i].shv.minor = h->minor_;
+ sections[i].keyid = h->keyid;
+ }
+}
+
+
+/* Compare to signer headers and determine which one is newer. */
+static int a_newer_than_b(struct signed_header_version *a,
+ struct signed_header_version *b)
+{
+ uint32_t fields[][3] = {
+ {a->epoch, a->major, a->minor},
+ {b->epoch, b->major, b->minor},
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(fields[0]); i++) {
+ uint32_t a_value;
+ uint32_t b_value;
+
+ a_value = fields[0][i];
+ b_value = fields[1][i];
+
+ /*
+ * Let's filter out images where the section is not
+ * initialized and the version field value is set to all ones.
+ */
+ if (a_value == 0xffffffff)
+ a_value = 0;
+
+ if (b_value == 0xffffffff)
+ b_value = 0;
+
+ if (a_value != b_value)
+ return a_value > b_value;
+ }
+
+ return 0; /* All else being equal A is no newer than B. */
+}
+/*
+ * Pick sections to transfer based on information retrieved from the target,
+ * the new image, and the protocol version the target is running.
+ */
+static void pick_sections(struct transfer_descriptor *td)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sections); i++) {
+ uint32_t offset = sections[i].offset;
+
+ if ((offset == CONFIG_RW_MEM_OFF) ||
+ (offset == CONFIG_RW_B_MEM_OFF)) {
+
+ /* Skip currently active section. */
+ if (offset != td->rw_offset)
+ continue;
+ /*
+ * Ok, this would be the RW section to transfer to the
+ * device. Is it newer in the new image than the
+ * running RW section on the device?
+ *
+ * If not in 'upstart' mode - transfer even if
+ * versions are the same, timestamps could be
+ * different.
+ */
+
+ if (a_newer_than_b(&sections[i].shv, &targ.shv[1]) ||
+ !td->upstart_mode)
+ sections[i].ustatus = needed;
+ continue;
+ }
+
+ /* Skip currently active section. */
+ if (offset != td->ro_offset)
+ continue;
+ /*
+ * Ok, this would be the RO section to transfer to the device.
+ * Is it newer in the new image than the running RO section on
+ * the device?
+ */
+ if (a_newer_than_b(&sections[i].shv, &targ.shv[0]))
+ sections[i].ustatus = needed;
+ }
+}
+
+static void setup_connection(struct transfer_descriptor *td)
+{
+ size_t rxed_size;
+ size_t i;
+ uint32_t error_code;
+
+ /*
+ * Need to be backwards compatible, communicate with targets running
+ * different protocol versions.
+ */
+ union {
+ struct first_response_pdu rpdu;
+ uint32_t legacy_resp;
+ } start_resp;
+
+ /* Send start request. */
+ printf("start\n");
+
+ if (td->ep_type == usb_xfer) {
+ struct update_pdu updu;
+
+ memset(&updu, 0, sizeof(updu));
+ updu.block_size = htobe32(sizeof(updu));
+ do_xfer(&td->uep, &updu, sizeof(updu), &start_resp,
+ sizeof(start_resp), 1, &rxed_size);
+ } else {
+ rxed_size = sizeof(start_resp);
+ if (tpm_send_pkt(td, 0, 0, NULL, 0,
+ &start_resp, &rxed_size,
+ EXTENSION_FW_UPGRADE) < 0) {
+ fprintf(stderr, "Failed to start transfer\n");
+ exit(update_error);
+ }
+ }
+
+ /* We got something. Check for errors in response */
+ if (rxed_size < 8) {
+ fprintf(stderr, "Unexpected response size %zd: ", rxed_size);
+ for (i = 0; i < rxed_size; i++)
+ fprintf(stderr, " %02x", ((uint8_t *)&start_resp)[i]);
+ fprintf(stderr, "\n");
+ exit(update_error);
+ }
+
+ protocol_version = be32toh(start_resp.rpdu.protocol_version);
+ if (protocol_version < 5) {
+ fprintf(stderr, "Unsupported protocol version %d\n",
+ protocol_version);
+ exit(update_error);
+ }
+
+ printf("target running protocol version %d\n", protocol_version);
+
+ error_code = be32toh(start_resp.rpdu.return_value);
+
+ if (error_code) {
+ fprintf(stderr, "Target reporting error %d\n", error_code);
+ if (td->ep_type == usb_xfer)
+ shut_down(&td->uep);
+ exit(update_error);
+ }
+
+ td->rw_offset = be32toh(start_resp.rpdu.backup_rw_offset);
+ td->ro_offset = be32toh(start_resp.rpdu.backup_ro_offset);
+
+ /* Running header versions. */
+ for (i = 0; i < ARRAY_SIZE(targ.shv); i++) {
+ targ.shv[i].minor = be32toh(start_resp.rpdu.shv[i].minor);
+ targ.shv[i].major = be32toh(start_resp.rpdu.shv[i].major);
+ targ.shv[i].epoch = be32toh(start_resp.rpdu.shv[i].epoch);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(targ.keyid); i++)
+ targ.keyid[i] = be32toh(start_resp.rpdu.keyid[i]);
+
+ printf("keyids: RO 0x%08x, RW 0x%08x\n", targ.keyid[0], targ.keyid[1]);
+ printf("offsets: backup RO at %#x, backup RW at %#x\n",
+ td->ro_offset, td->rw_offset);
+
+ pick_sections(td);
+}
+
+/*
+ * Channel TPM extension/vendor command over USB. The payload of the USB frame
+ * in this case consists of the 2 byte subcommand code concatenated with the
+ * command body. The caller needs to indicate if a response is expected, and
+ * if it is - of what maximum size.
+ */
+static int ext_cmd_over_usb(struct usb_endpoint *uep, uint16_t subcommand,
+ void *cmd_body, size_t body_size,
+ void *resp, size_t *resp_size)
+{
+ struct update_frame_header *ufh;
+ uint16_t *frame_ptr;
+ size_t usb_msg_size;
+ SHA_CTX ctx;
+ uint8_t digest[SHA_DIGEST_LENGTH];
+
+ usb_msg_size = sizeof(struct update_frame_header) +
+ sizeof(subcommand) + body_size;
+
+ ufh = malloc(usb_msg_size);
+ if (!ufh) {
+ printf("%s: failed to allocate %zd bytes\n",
+ __func__, usb_msg_size);
+ return -1;
+ }
+
+ ufh->block_size = htobe32(usb_msg_size);
+ ufh->cmd.block_base = htobe32(CONFIG_EXTENSION_COMMAND);
+ frame_ptr = (uint16_t *)(ufh + 1);
+ *frame_ptr = htobe16(subcommand);
+
+ if (body_size)
+ memcpy(frame_ptr + 1, cmd_body, body_size);
+
+ /* Calculate the digest. */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, &ufh->cmd.block_base,
+ usb_msg_size -
+ offsetof(struct update_frame_header, cmd.block_base));
+ SHA1_Final(digest, &ctx);
+ memcpy(&ufh->cmd.block_digest, digest, sizeof(ufh->cmd.block_digest));
+
+ do_xfer(uep, ufh, usb_msg_size, resp,
+ resp_size ? *resp_size : 0, 1, resp_size);
+
+ free(ufh);
+ return 0;
+}
+
+/*
+ * Indicate to the target that update image transfer has been completed. Upon
+ * receiveing of this message the target state machine transitions into the
+ * 'rx_idle' state. The host may send an extension command to reset the target
+ * after this.
+ */
+static void send_done(struct usb_endpoint *uep)
+{
+ uint32_t out;
+
+ /* Send stop request, ignoring reply. */
+ out = htobe32(UPGRADE_DONE);
+ xfer(uep, &out, sizeof(out), &out, 1);
+}
+
+/* Returns number of successfully transmitted image sections. */
+static int transfer_image(struct transfer_descriptor *td,
+ uint8_t *data, size_t data_len)
+{
+ size_t i;
+ int num_txed_sections = 0;
+
+ for (i = 0; i < ARRAY_SIZE(sections); i++)
+ if (sections[i].ustatus == needed) {
+ transfer_section(td,
+ data + sections[i].offset,
+ sections[i].offset,
+ sections[i].size);
+ num_txed_sections++;
+ }
+
+ if (!num_txed_sections)
+ printf("nothing to do\n");
+ else
+ printf("-------\nupdate complete\n");
+ return num_txed_sections;
+}
+
+static uint32_t send_vendor_command(struct transfer_descriptor *td,
+ uint16_t subcommand,
+ void *command_body,
+ size_t command_body_size,
+ void *response,
+ size_t *response_size)
+{
+ int32_t rv;
+
+ if (td->ep_type == usb_xfer) {
+ /*
+ * When communicating over USB the response is always supposed
+ * to have the result code in the first byte of the response,
+ * to be stripped from the actual response body by this
+ * function.
+ */
+ uint8_t temp_response[MAX_BUF_SIZE];
+ size_t max_response_size;
+
+ if (!response_size) {
+ max_response_size = 1;
+ } else if (*response_size < (sizeof(temp_response))) {
+ max_response_size = *response_size + 1;
+ } else {
+ fprintf(stderr,
+ "Error: Expected response too large (%zd)\n",
+ *response_size);
+ /* Should happen only when debugging. */
+ exit(update_error);
+ }
+
+ ext_cmd_over_usb(&td->uep, subcommand,
+ command_body, command_body_size,
+ temp_response, &max_response_size);
+ if (!max_response_size) {
+ /*
+ * we must be talking to an older Cr50 firmware, which
+ * does not return the result code in the first byte
+ * on success, nothing to do.
+ */
+ if (response_size)
+ *response_size = 0;
+ rv = 0;
+ } else {
+ rv = temp_response[0];
+ if (response_size) {
+ *response_size = max_response_size - 1;
+ memcpy(response,
+ temp_response + 1, *response_size);
+ }
+ }
+ } else {
+
+ rv = tpm_send_pkt(td, 0, 0,
+ command_body, command_body_size,
+ response, response_size, subcommand);
+
+ if (rv == -1) {
+ fprintf(stderr,
+ "Error: Failed to send vendor command %d\n",
+ subcommand);
+ exit(update_error);
+ }
+ }
+
+ return rv; /* This will be converted into uint32_t */
+}
+
+/*
+ * Corrupt the header of the inactive rw image to make sure the system can't
+ * rollback
+ */
+static void invalidate_inactive_rw(struct transfer_descriptor *td)
+{
+ /* Corrupt the rw image that is not running. */
+ uint32_t rv;
+
+ rv = send_vendor_command(td, VENDOR_CC_INVALIDATE_INACTIVE_RW,
+ NULL, 0, NULL, NULL);
+ if (!rv) {
+ printf("Inactive header invalidated\n");
+ return;
+ }
+
+ fprintf(stderr, "*%s: Error %#x\n", __func__, rv);
+ exit(update_error);
+}
+
+static struct signed_header_version ver19 = {
+ .epoch = 0,
+ .major = 0,
+ .minor = 19,
+};
+
+static void generate_reset_request(struct transfer_descriptor *td)
+{
+ size_t response_size;
+ uint8_t response;
+ uint16_t subcommand;
+ uint8_t command_body[2]; /* Max command body size. */
+ size_t command_body_size;
+ uint32_t background_update_supported;
+ const char *reset_type;
+ int rv;
+
+ if (protocol_version < 6) {
+ if (td->ep_type == usb_xfer) {
+ /*
+ * Send a second stop request, which should reboot
+ * without replying.
+ */
+ send_done(&td->uep);
+ }
+ /* Nothing we can do over /dev/tpm0 running versions below 6. */
+ return;
+ }
+
+ /* RW version 0.0.19 and above has support for background updates. */
+ background_update_supported = !a_newer_than_b(&ver19, &targ.shv[1]);
+
+ /*
+ * If this is an upstart request and there is support for background
+ * updates, don't post a request now. The target should handle it on
+ * the next reboot.
+ */
+ if (td->upstart_mode && background_update_supported)
+ return;
+
+ /*
+ * If the user explicitly wants it or a reset is needed because h1
+ * does not support background updates, request post reset instead of
+ * immediate reset. In this case next time the target reboots, the h1
+ * will reboot as well, and will consider running the uploaded code.
+ *
+ * In case target RW version is 19 or above, to reset the target the
+ * host is supposed to send the command to enable the uploaded image
+ * disabled by default.
+ *
+ * Otherwise the immediate reset command would suffice.
+ */
+ /* Most common case. */
+ command_body_size = 0;
+ response_size = 1;
+ if (td->post_reset || td->upstart_mode) {
+ subcommand = EXTENSION_POST_RESET;
+ reset_type = "posted";
+ } else if (background_update_supported) {
+ subcommand = VENDOR_CC_TURN_UPDATE_ON;
+ command_body_size = sizeof(command_body);
+ command_body[0] = 0;
+ command_body[1] = 100; /* Reset in 100 ms. */
+ reset_type = "requested";
+ } else {
+ response_size = 0;
+ subcommand = VENDOR_CC_IMMEDIATE_RESET;
+ reset_type = "triggered";
+ }
+
+ rv = send_vendor_command(td, subcommand, command_body,
+ command_body_size, &response, &response_size);
+
+ if (rv) {
+ fprintf(stderr, "*%s: Error %#x\n", __func__, rv);
+ exit(update_error);
+ }
+ printf("reboot %s\n", reset_type);
+}
+
+static int show_headers_versions(const void *image)
+{
+ const struct {
+ const char *name;
+ uint32_t offset;
+ } sections[] = {
+ {"RO_A", CONFIG_RO_MEM_OFF},
+ {"RW_A", CONFIG_RW_MEM_OFF},
+ {"RO_B", CHIP_RO_B_MEM_OFF},
+ {"RW_B", CONFIG_RW_B_MEM_OFF}
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sections); i++) {
+ const struct SignedHeader *h;
+ size_t j;
+ uint32_t bid;
+ uint32_t bid_mask;
+ uint32_t bid_flags;
+
+ h = (const struct SignedHeader *)((uintptr_t)image +
+ sections[i].offset);
+ printf("%s%s:%d.%d.%d", i ? " " : "", sections[i].name,
+ h->epoch_, h->major_, h->minor_);
+
+ if (sections[i].name[1] != 'W')
+ continue;
+
+ /*
+ * For read/write sections print the board ID fields'
+ * contents, which are stored XORed with a padding value.
+ */
+ bid = h->board_id_type ^ SIGNED_HEADER_PADDING;
+ bid_mask = h->board_id_type_mask ^ SIGNED_HEADER_PADDING;
+ bid_flags = h->board_id_flags ^ SIGNED_HEADER_PADDING;
+
+ /* Beginning of a board ID section of the string. */
+ printf("[");
+
+ /*
+ * If board ID is an ASCII string (as it ought to be), print
+ * it as 4 symbols, otherwise print it as an 8 digit hex.
+ */
+ for (j = 0; j < sizeof(bid); j++)
+ if (!isalnum(((const char *)&bid)[j]))
+ break;
+
+ if (j == sizeof(bid)) {
+ /* Convert it for proper string representation. */
+ bid = be32toh(bid);
+ printf("%.4s", (const char *)&bid);
+ } else {
+ printf("%08x", bid);
+ }
+
+ /* Print the rest of the board ID fields. */
+ printf(":%08x:%08x]", bid_mask, bid_flags);
+ }
+ printf("\n");
+
+ return 0;
+}
+
+struct board_id {
+ uint32_t type; /* Board type */
+ uint32_t type_inv; /* Board type (inverted) */
+ uint32_t flags; /* Flags */
+};
+
+enum board_id_action {
+ bid_none,
+ bid_get,
+ bid_set
+};
+
+/*
+ * The default flag value will allow to run images built for any hardware
+ * generation of a particular board ID.
+ */
+#define DEFAULT_BOARD_ID_FLAG 0xff00
+static int parse_bid(const char *opt,
+ struct board_id *bid,
+ enum board_id_action *bid_action)
+{
+ char *e;
+ const char *param2;
+ size_t param1_length;
+
+ if (!opt) {
+ *bid_action = bid_get;
+ return 1;
+ }
+
+ /* Set it here to make bailing out easier later. */
+ bid->flags = DEFAULT_BOARD_ID_FLAG;
+
+ *bid_action = bid_set; /* Ignored by caller on errors. */
+
+ /*
+ * Pointer to the optional second component of the command line
+ * parameter, when present - separated by a colon.
+ */
+ param2 = strchr(opt, ':');
+ if (param2) {
+ param1_length = param2 - opt;
+ param2++;
+ if (!*param2)
+ return 0; /* Empty second parameter. */
+ } else {
+ param1_length = strlen(opt);
+ }
+
+ if (!param1_length)
+ return 0; /* Colon is the first character of the string? */
+
+ if (param1_length <= 4) {
+ unsigned i;
+
+ /* Input must be a symbolic board name. */
+ bid->type = 0;
+ for (i = 0; i < param1_length; i++)
+ bid->type = (bid->type << 8) | opt[i];
+ } else {
+ bid->type = (uint32_t)strtoul(opt, &e, 0);
+ if ((param2 && (*e != ':')) || (!param2 && *e))
+ return 0;
+ }
+
+ if (param2) {
+ bid->flags = (uint32_t)strtoul(param2, &e, 0);
+ if (*e)
+ return 0;
+ }
+
+ return 1;
+}
+
+static void process_bid(struct transfer_descriptor *td,
+ enum board_id_action bid_action,
+ struct board_id *bid)
+{
+ size_t response_size;
+
+ if (bid_action == bid_get) {
+ struct board_id bid;
+
+ response_size = sizeof(bid);
+ send_vendor_command(td, VENDOR_CC_GET_BOARD_ID,
+ &bid, sizeof(bid),
+ &bid, &response_size);
+
+ if (response_size == sizeof(bid)) {
+ printf("Board ID space: %08x:%08x:%08x\n",
+ be32toh(bid.type), be32toh(bid.type_inv),
+ be32toh(bid.flags));
+ return;
+
+ }
+ fprintf(stderr, "Error reading board ID: response size %zd,"
+ " first byte %#02x\n", response_size,
+ response_size ? *(uint8_t *)&bid : -1);
+ exit(update_error);
+ }
+
+ if (bid_action == bid_set) {
+ /* Sending just two fields: type and flags. */
+ uint32_t command_body[2];
+ uint8_t response;
+
+ command_body[0] = htobe32(bid->type);
+ command_body[1] = htobe32(bid->flags);
+
+ response_size = sizeof(command_body);
+ send_vendor_command(td, VENDOR_CC_SET_BOARD_ID,
+ command_body, sizeof(command_body),
+ command_body, &response_size);
+
+ /*
+ * Speculative assignment: the response is expected to be one
+ * byte in size and be placed in the first byte of the buffer.
+ */
+ response = *((uint8_t *)command_body);
+
+ if (response_size == 1) {
+ if (!response)
+ return; /* Success! */
+
+ fprintf(stderr, "Error %d while setting board id\n",
+ response);
+ } else {
+ fprintf(stderr, "Unexpected response size %zd"
+ " while setting board id\n",
+ response_size);
+ }
+ exit(update_error);
+ }
+}
+
+/*
+ * Retrieve the RMA authentication challenge from the Cr50, print out the
+ * challenge on the console, then prompt the user for the authentication code,
+ * and send the code back to Cr50. The Cr50 would report if the code matched
+ * its expectations or not.
+ */
+static void process_rma(struct transfer_descriptor *td)
+{
+ char rma_response[81];
+ size_t response_size = sizeof(rma_response);
+ size_t i;
+ char *authcode = NULL;
+ size_t auth_size = 0;
+
+ send_vendor_command(td, VENDOR_CC_RMA_CHALLENGE_RESPONSE,
+ NULL, 0, rma_response, &response_size);
+
+ if (response_size == 1) {
+ printf("error %d\n", rma_response[0]);
+ if (td->ep_type == usb_xfer)
+ shut_down(&td->uep);
+ exit(update_error);
+ }
+
+ printf("Challenge:");
+ for (i = 0; i < response_size; i++) {
+ if (!(i % 5)) {
+ if (!(i % 40))
+ printf("\n");
+ printf(" ");
+ }
+ printf("%c", rma_response[i]);
+ }
+ printf("\nNow enter response: ");
+ auth_size = getline(&authcode, &auth_size, stdin);
+ if (auth_size > 0) {
+
+ response_size = sizeof(rma_response);
+
+ send_vendor_command(td, VENDOR_CC_RMA_CHALLENGE_RESPONSE,
+ authcode, auth_size - 1, /* drop the '\n' */
+ rma_response, &response_size);
+
+ if (response_size == 1) {
+ printf("\nrma unlock failed, code %d\n",
+ *rma_response);
+ if (td->ep_type == usb_xfer)
+ shut_down(&td->uep);
+ exit(update_error);
+ }
+ printf("RMA unlock succeeded.\n");
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct transfer_descriptor td;
+ int errorcnt;
+ uint8_t *data = 0;
+ size_t data_len = 0;
+ uint16_t vid = VID, pid = PID;
+ int i;
+ size_t j;
+ int transferred_sections = 0;
+ int binary_vers = 0;
+ int show_fw_ver = 0;
+ int rma = 0;
+ int corrupt_inactive_rw = 0;
+ struct board_id bid;
+ enum board_id_action bid_action;
+
+ progname = strrchr(argv[0], '/');
+ if (progname)
+ progname++;
+ else
+ progname = argv[0];
+
+ /* Usb transfer - default mode. */
+ memset(&td, 0, sizeof(td));
+ td.ep_type = usb_xfer;
+
+ bid_action = bid_none;
+ errorcnt = 0;
+ opterr = 0; /* quiet, you */
+ while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
+ switch (i) {
+ case 'b':
+ binary_vers = 1;
+ break;
+ case 'd':
+ if (!parse_vidpid(optarg, &vid, &pid)) {
+ printf("Invalid device argument: \"%s\"\n",
+ optarg);
+ errorcnt++;
+ }
+ break;
+ case 'c':
+ corrupt_inactive_rw = 1;
+ break;
+ case 'f':
+ show_fw_ver = 1;
+ break;
+ case 'h':
+ usage(errorcnt);
+ break;
+ case 'i':
+ if (!optarg && argv[optind] && argv[optind][0] != '-') {
+ /* optional argument present. */
+ optarg = argv[optind];
+ optind++;
+ }
+ if (!parse_bid(optarg, &bid, &bid_action)) {
+ printf("Invalid board id argument: \"%s\"\n",
+ optarg);
+ errorcnt++;
+ }
+ break;
+ case 'r':
+ rma = 1;
+ break;
+ case 's':
+ td.ep_type = dev_xfer;
+ break;
+ case 't':
+ td.ep_type = ts_xfer;
+ break;
+ case 'p':
+ td.post_reset = 1;
+ break;
+ case 'u':
+ td.upstart_mode = 1;
+ break;
+ case 0: /* auto-handled option */
+ break;
+ case '?':
+ if (optopt)
+ printf("Unrecognized option: -%c\n", optopt);
+ else
+ printf("Unrecognized option: %s\n",
+ argv[optind - 1]);
+ errorcnt++;
+ break;
+ case ':':
+ printf("Missing argument to %s\n", argv[optind - 1]);
+ errorcnt++;
+ break;
+ default:
+ printf("Internal error at %s:%d\n", __FILE__, __LINE__);
+ exit(update_error);
+ }
+ }
+
+ if (errorcnt)
+ usage(errorcnt);
+
+ if (!show_fw_ver &&
+ !corrupt_inactive_rw &&
+ (bid_action == bid_none) &&
+ !rma) {
+ if (optind >= argc) {
+ fprintf(stderr,
+ "\nERROR: Missing required <binary image>\n\n");
+ usage(1);
+ }
+
+ data = get_file_or_die(argv[optind], &data_len);
+ printf("read %zd(%#zx) bytes from %s\n",
+ data_len, data_len, argv[optind]);
+ if (data_len != CONFIG_FLASH_SIZE) {
+ fprintf(stderr, "Image file is not %d bytes\n",
+ CONFIG_FLASH_SIZE);
+ exit(update_error);
+ }
+
+ fetch_header_versions(data);
+
+ if (binary_vers)
+ exit(show_headers_versions(data));
+ } else {
+ if (optind < argc)
+ printf("Ignoring binary image %s\n", argv[optind]);
+ }
+
+ if (td.ep_type == usb_xfer) {
+ usb_findit(vid, pid, &td.uep);
+ } else if (td.ep_type == dev_xfer) {
+ td.tpm_fd = open("/dev/tpm0", O_RDWR);
+ if (td.tpm_fd < 0) {
+ perror("Could not open TPM");
+ exit(update_error);
+ }
+ }
+
+ if (bid_action != bid_none)
+ process_bid(&td, bid_action, &bid);
+
+ if (rma)
+ process_rma(&td);
+
+ if (corrupt_inactive_rw)
+ invalidate_inactive_rw(&td);
+
+ if (data || show_fw_ver) {
+
+ setup_connection(&td);
+
+ if (data) {
+ transferred_sections = transfer_image(&td,
+ data, data_len);
+ free(data);
+ }
+
+ /*
+ * Move USB updater sate machine to idle state so that vendor
+ * commands can be processed later, if any.
+ */
+ if (td.ep_type == usb_xfer)
+ send_done(&td.uep);
+
+ if (transferred_sections)
+ generate_reset_request(&td);
+
+ if (show_fw_ver) {
+ printf("Current versions:\n");
+ printf("RO %d.%d.%d\n", targ.shv[0].epoch,
+ targ.shv[0].major,
+ targ.shv[0].minor);
+ printf("RW %d.%d.%d\n", targ.shv[1].epoch,
+ targ.shv[1].major,
+ targ.shv[1].minor);
+ }
+ }
+
+ if (td.ep_type == usb_xfer) {
+ libusb_close(td.uep.devh);
+ libusb_exit(NULL);
+ }
+
+ if (!transferred_sections)
+ return noop;
+ /*
+ * We should indicate if RO update was not done because of the
+ * insufficient RW version.
+ */
+ for (j = 0; j < ARRAY_SIZE(sections); j++)
+ if (sections[j].ustatus == not_possible) {
+ /* This will allow scripting repeat attempts. */
+ printf("Failed to update RO, run the command again\n");
+ return rw_updated;
+ }
+
+ printf("image updated\n");
+ return all_updated;
+}