summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Boichat <drinkcat@google.com>2017-03-15 01:30:15 +0800
committerchrome-bot <chrome-bot@chromium.org>2017-05-21 03:06:24 -0700
commit1d10236f07a453a438745e9ae541307f69df61ce (patch)
tree4858ee16f81eeabf96e70733080d4db8049910f4
parent335bbbf2204a0814f2a1fc09054ed579a0b2cca9 (diff)
downloadchrome-ec-1d10236f07a453a438745e9ae541307f69df61ce.tar.gz
usb_updater2: USB updater for common code EC
This is copied chip/g version as of commit 0e5497db6, plus the following uncommited usb_update patch (CL:458469): a0176a1cc usb_updater: Flush all data from endpoint before trying to update On top of that, a good number of common-code specific modifications are added: new extra commands, new first_response_pdu format, use of FMAP. BRANCH=none BUG=b:35587170 TEST=usb_updater binary identical before and after this change. TEST=make BOARD=hammer -j && \ ( cd extra/usb_updater && make && \ time sudo ./usb_updater2 ../../build/hammer/ec.bin ) TEST=cd extra/usb_updater; make # Jump to RW sudo ./usb_updater2 -j sleep 0.5 # Update RO, then reboot sudo ./usb_updater2 ../../build/hammer/ec.bin sleep 0.5 # Update RW (first tell RO to not jump to RW) sudo ./usb_updater2 -s sudo ./usb_updater2 ../../build/hammer/ec.bin TEST=cd extra/usb_updater; make # Tell RW to jump back to RO sudo ./usb_updater2 -w sleep 0.5 # Update RW, then reboot sudo ./usb_updater2 -s sudo ./usb_updater2 ../../build/hammer/ec.bin TEST=usb_updater2 can update hammer, and read its version, rollback version and key version. Change-Id: I09da894d83e2b4d6c2e46cab301522c27fa0160c Reviewed-on: https://chromium-review.googlesource.com/458468 Commit-Ready: Nicolas Boichat <drinkcat@chromium.org> Tested-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-by: Nick Sanders <nsanders@chromium.org>
-rw-r--r--extra/usb_updater/Makefile28
-rw-r--r--extra/usb_updater/usb_updater2.c961
2 files changed, 980 insertions, 9 deletions
diff --git a/extra/usb_updater/Makefile b/extra/usb_updater/Makefile
index 9120b7d4dc..0a2b3a035e 100644
--- a/extra/usb_updater/Makefile
+++ b/extra/usb_updater/Makefile
@@ -4,8 +4,7 @@
CC ?= gcc
PKG_CONFIG ?= pkg-config
-PROGRAM := usb_updater
-SOURCE := $(PROGRAM).c
+PROGRAMS := usb_updater usb_updater2
LIBS :=
LFLAGS :=
CFLAGS := -std=gnu99 \
@@ -29,17 +28,28 @@ endif
#
# Add libusb-1.0 required flags
#
-LIBS += $(shell $(PKG_CONFIG) --libs libusb-1.0 libcrypto)
-CFLAGS += $(shell $(PKG_CONFIG) --cflags libusb-1.0 libcrypto)
+LIBS += $(shell $(PKG_CONFIG) --libs libusb-1.0)
+CFLAGS += $(shell $(PKG_CONFIG) --cflags libusb-1.0)
+CFLAGS += -I../../include -I../../util
-# NOTE: This may be board-specific
BOARD ?= cr50
-CFLAGS += -I../../include -I../../board/$(BOARD) -I ../../chip/g -I../../util
+LIBS_g = $(shell $(PKG_CONFIG) --libs libcrypto)
+CFLAGS_g = $(shell $(PKG_CONFIG) --cflags libcrypto)
+CFLAGS_g += -I../../board/$(BOARD) -I ../../chip/g
-$(PROGRAM): $(SOURCE) Makefile
- $(CC) $(CFLAGS) $(SOURCE) $(LFLAGS) $(LIBS) -o $@
+LIBS_common = -lfmap
+
+all: $(PROGRAMS)
+
+# chip/g updater
+usb_updater: usb_updater.c Makefile
+ $(CC) $(CFLAGS) $(CFLAGS_g) $< $(LFLAGS) $(LIBS) $(LIBS_g) -o $@
+
+# common EC code USB updater
+usb_updater2: usb_updater2.c Makefile
+ $(CC) $(CFLAGS) $< $(LFLAGS) $(LIBS) $(LIBS_common) -o $@
.PHONY: clean
clean:
- rm -rf $(PROGRAM) *~
+ rm -rf $(PROGRAMS) *~
diff --git a/extra/usb_updater/usb_updater2.c b/extra/usb_updater/usb_updater2.c
new file mode 100644
index 0000000000..3e676c0334
--- /dev/null
+++ b/extra/usb_updater/usb_updater2.c
@@ -0,0 +1,961 @@
+/*
+ * Copyright 2017 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 <endian.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libusb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <fmap.h>
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#include "compile_time_macros.h"
+#include "misc_util.h"
+#include "usb_descriptor.h"
+#include "update_fw.h"
+#include "vb21_struct.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
+ * EC device firmware (common code only, usb_updater takes care of cr50).
+ */
+
+#define VID USB_VID_GOOGLE
+#define PID 0x5022
+#define SUBCLASS USB_SUBCLASS_GOOGLE_UPDATE
+#define PROTOCOL USB_PROTOCOL_GOOGLE_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. */
+};
+
+struct usb_endpoint {
+ struct libusb_device_handle *devh;
+ uint8_t ep_num;
+ int chunk_len;
+};
+
+struct transfer_descriptor {
+ /*
+ * offsets of section available for update (not currently active).
+ */
+ uint32_t offset;
+
+ struct usb_endpoint uep;
+};
+
+/* Information about the target */
+static struct first_response_pdu targ;
+
+static uint16_t protocol_version;
+static uint16_t header_type;
+static char *progname;
+static char *short_opts = "bd:fhjrsuw";
+static const struct option long_opts[] = {
+ /* name hasarg *flag val */
+ {"binvers", 1, NULL, 'b'},
+ {"device", 1, NULL, 'd'},
+ {"fwver", 0, NULL, 'f'},
+ {"help", 0, NULL, 'h'},
+ {"jump_to_rw", 0, NULL, 'j'},
+ {"reboot", 0, NULL, 'r'},
+ {"stay_in_ro", 0, NULL, 's'},
+ {"unlock_rollback", 0, NULL, 'u'},
+ {"unlock_rw", 0, NULL, 'w'},
+ {},
+};
+
+/* 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 EC firmware over USB (common code EC, no cr50).\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, do not update\n"
+ " -d,--device VID:PID USB device (default %04x:%04x)\n"
+ " -f,--fwver Report running firmware versions.\n"
+ " -h,--help Show this message\n"
+ " -j,--jump_to_rw Tell EC to jump to RW\n"
+ " -r,--reboot Tell EC to reboot\n"
+ " -s,--stay_in_ro Tell EC to stay in RO\n"
+ " -u,--unlock_rollback Tell EC to unlock the rollback region\n"
+ " -w,--unlock_rw Tell EC to unlock the RW region\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 (fread(data, st.st_size, 1, fp) != 1) {
+ 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");
+}
+
+static int transfer_block(struct usb_endpoint *uep,
+ struct update_frame_header *ufh,
+ 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, ufh, sizeof(*ufh), 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.
+ *
+ * FIXME: We can be smarter than this and skip blocks within the image.
+ */
+ 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;
+ uint32_t block_base;
+ int max_retries;
+
+ /* prepare the header to prepend to the block. */
+ payload_size = MIN(data_len, targ.common.maximum_pdu_size);
+
+ block_base = htobe32(section_addr);
+
+ struct update_frame_header ufh;
+
+ ufh.block_size = htobe32(payload_size +
+ sizeof(struct update_frame_header));
+ ufh.cmd.block_base = block_base;
+ ufh.cmd.block_digest = 0;
+ for (max_retries = 10; max_retries; max_retries--)
+ if (!transfer_block(&td->uep, &ufh,
+ data_ptr, payload_size))
+ break;
+
+ if (!max_retries) {
+ fprintf(stderr,
+ "Failed to transfer block, %zd to go\n",
+ data_len);
+ exit(update_error);
+ }
+ data_len -= payload_size;
+ data_ptr += payload_size;
+ section_addr += payload_size;
+ }
+}
+
+/*
+ * 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 sections of the new image. */
+static struct {
+ const char *name;
+ uint32_t offset;
+ uint32_t size;
+ enum upgrade_status ustatus;
+ char version[32];
+ int32_t rollback;
+ uint32_t key_version;
+} sections[] = {
+ {"RO"},
+ {"RW"}
+};
+
+static const struct fmap_area *fmap_find_area_or_die(const struct fmap *fmap,
+ const char *name)
+{
+ const struct fmap_area *fmaparea;
+
+ fmaparea = fmap_find_area(fmap, name);
+ if (!fmaparea) {
+ fprintf(stderr, "Cannot find FMAP area %s\n", name);
+ exit(update_error);
+ }
+
+ return fmaparea;
+}
+
+/*
+ * Scan the new image and retrieve versions of all sections.
+ */
+static void fetch_header_versions(const uint8_t *image, size_t len)
+{
+ const struct fmap *fmap;
+ const struct fmap_area *fmaparea;
+ long int offset;
+ size_t i;
+
+ offset = fmap_find(image, len);
+ if (offset < 0) {
+ fprintf(stderr, "Cannot find FMAP in image\n");
+ exit(update_error);
+ }
+ fmap = (const struct fmap *)(image+offset);
+
+ /* FIXME: validate fmap struct more than this? */
+ if (fmap->size != len) {
+ fprintf(stderr, "Mismatch between FMAP size and image size\n");
+ exit(update_error);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sections); i++) {
+ const char *fmap_name;
+ const char *fmap_fwid_name;
+ const char *fmap_rollback_name = NULL;
+ const char *fmap_key_name = NULL;
+
+ if (!strcmp(sections[i].name, "RO")) {
+ fmap_name = "EC_RO";
+ fmap_fwid_name = "RO_FRID";
+ } else if (!strcmp(sections[i].name, "RW")) {
+ fmap_name = "EC_RW";
+ fmap_fwid_name = "RW_FWID";
+ fmap_rollback_name = "RW_RBVER";
+ /*
+ * Key version comes from key RO (RW signature does not
+ * contain the key version.
+ */
+ fmap_key_name = "KEY_RO";
+ } else {
+ fprintf(stderr, "Invalid section name\n");
+ exit(update_error);
+ }
+
+ fmaparea = fmap_find_area_or_die(fmap, fmap_name);
+
+ /* FIXME: endianness? */
+ sections[i].offset = fmaparea->offset;
+ sections[i].size = fmaparea->size;
+
+ fmaparea = fmap_find_area_or_die(fmap, fmap_fwid_name);
+
+ if (fmaparea->size != sizeof(sections[i].version)) {
+ fprintf(stderr, "Invalid fwid size\n");
+ exit(update_error);
+ }
+ memcpy(sections[i].version, image+fmaparea->offset,
+ fmaparea->size);
+
+ sections[i].rollback = -1;
+ if (fmap_rollback_name) {
+ fmaparea = fmap_find_area(fmap, fmap_rollback_name);
+ if (fmaparea)
+ memcpy(&sections[i].rollback,
+ image+fmaparea->offset,
+ sizeof(sections[i].rollback));
+ }
+
+ sections[i].key_version = -1;
+ if (fmap_key_name) {
+ fmaparea = fmap_find_area(fmap, fmap_key_name);
+ if (fmaparea) {
+ const struct vb21_packed_key *key =
+ (const void *)(image+fmaparea->offset);
+ sections[i].key_version = key->key_version;
+ }
+ }
+ }
+}
+
+static int show_headers_versions(const void *image)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sections); i++) {
+ printf("%s off=%08x/%08x v=%.32s rb=%d kv=%d\n",
+ sections[i].name, sections[i].offset, sections[i].size,
+ sections[i].version, sections[i].rollback,
+ sections[i].key_version);
+ }
+ return 0;
+}
+
+/*
+ * 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;
+
+ /* Skip currently active section. */
+ if (offset != td->offset)
+ continue;
+
+ 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");
+
+ struct update_frame_header ufh;
+ uint8_t inbuf[td->uep.chunk_len];
+ int actual = 0;
+
+ /* Flush all data from endpoint to recover in case of error. */
+ while (!libusb_bulk_transfer(td->uep.devh,
+ td->uep.ep_num | 0x80,
+ (void *)&inbuf, td->uep.chunk_len,
+ &actual, 10)) {
+ printf("flush\n");
+ }
+
+ memset(&ufh, 0, sizeof(ufh));
+ ufh.block_size = htobe32(sizeof(ufh));
+ do_xfer(&td->uep, &ufh, sizeof(ufh), &start_resp,
+ sizeof(start_resp), 1, &rxed_size);
+
+ /* 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 = be16toh(start_resp.rpdu.protocol_version);
+ if (protocol_version < 5 || protocol_version > 6) {
+ fprintf(stderr, "Unsupported protocol version %d\n",
+ protocol_version);
+ exit(update_error);
+ }
+
+ header_type = be16toh(start_resp.rpdu.header_type);
+
+ printf("target running protocol version %d (type %d)\n",
+ protocol_version, header_type);
+ if (header_type != UPDATE_HEADER_TYPE_COMMON) {
+ fprintf(stderr, "Unsupported header type %d\n",
+ header_type);
+ exit(update_error);
+ }
+
+ error_code = be32toh(start_resp.rpdu.return_value);
+
+ if (error_code) {
+ fprintf(stderr, "Target reporting error %d\n", error_code);
+ shut_down(&td->uep);
+ exit(update_error);
+ }
+
+ td->offset = be32toh(start_resp.rpdu.common.offset);
+ memcpy(targ.common.version, start_resp.rpdu.common.version,
+ sizeof(start_resp.rpdu.common.version));
+ targ.common.maximum_pdu_size =
+ be32toh(start_resp.rpdu.common.maximum_pdu_size);
+ targ.common.flash_protection =
+ be32toh(start_resp.rpdu.common.flash_protection);
+ targ.common.min_rollback = be32toh(start_resp.rpdu.common.min_rollback);
+ targ.common.key_version = be32toh(start_resp.rpdu.common.key_version);
+
+ printf("maximum PDU size: %d\n", targ.common.maximum_pdu_size);
+ printf("Flash protection status: %04x\n", targ.common.flash_protection);
+ printf("version: %32s\n", targ.common.version);
+ printf("key_version: %d\n", targ.common.key_version);
+ printf("min_rollback: %d\n", targ.common.min_rollback);
+ printf("offset: writable at %#x\n", td->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;
+
+ 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_digest = 0;
+ ufh->cmd.block_base = htobe32(UPDATE_EXTRA_CMD);
+ frame_ptr = (uint16_t *)(ufh + 1);
+ *frame_ptr = htobe16(subcommand);
+
+ if (body_size)
+ memcpy(frame_ptr + 1, cmd_body, body_size);
+
+ xfer(uep, ufh, usb_msg_size, resp, resp_size ? *resp_size : 0);
+
+ 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(UPDATE_DONE);
+ xfer(uep, &out, sizeof(out), &out, 1);
+}
+
+static void send_subcommand(struct transfer_descriptor *td, uint16_t subcommand)
+{
+ send_done(&td->uep);
+
+ if (protocol_version > 5) {
+ uint8_t response = -1;
+ size_t response_size = sizeof(response);
+
+ ext_cmd_over_usb(&td->uep, subcommand,
+ NULL, 0,
+ &response, &response_size);
+ printf("sent command %x, resp %x\n", subcommand, response);
+ }
+}
+
+/* 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++;
+ }
+
+ /*
+ * Move USB receiver sate machine to idle state so that vendor
+ * commands can be processed later, if any.
+ */
+ send_done(&td->uep);
+
+ if (!num_txed_sections)
+ printf("nothing to do\n");
+ else
+ printf("-------\nupdate complete\n");
+ return num_txed_sections;
+}
+
+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;
+
+ if (protocol_version < 6) {
+ /*
+ * 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;
+ }
+
+ /*
+ * If the user explicitly wants it, 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;
+ subcommand = UPDATE_EXTRA_CMD_IMMEDIATE_RESET;
+ ext_cmd_over_usb(&td->uep, subcommand,
+ command_body, command_body_size,
+ &response, &response_size);
+
+ printf("reboot not triggered\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 extra_command = -1;
+
+ progname = strrchr(argv[0], '/');
+ if (progname)
+ progname++;
+ else
+ progname = argv[0];
+
+ /* Usb transfer - default mode. */
+ memset(&td, 0, sizeof(td));
+
+ 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 argument: \"%s\"\n", optarg);
+ errorcnt++;
+ }
+ break;
+ case 'f':
+ show_fw_ver = 1;
+ break;
+ case 'h':
+ usage(errorcnt);
+ break;
+ case 'j':
+ extra_command = UPDATE_EXTRA_CMD_JUMP_TO_RW;
+ break;
+ case 'r':
+ extra_command = UPDATE_EXTRA_CMD_IMMEDIATE_RESET;
+ break;
+ case 's':
+ extra_command = UPDATE_EXTRA_CMD_STAY_IN_RO;
+ break;
+ case 'u':
+ extra_command = UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK;
+ break;
+ case 'w':
+ extra_command = UPDATE_EXTRA_CMD_UNLOCK_RW;
+ 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 && extra_command == -1) {
+ 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]);
+
+ fetch_header_versions(data, data_len);
+
+ if (binary_vers)
+ exit(show_headers_versions(data));
+ } else {
+ if (optind < argc)
+ printf("Ignoring binary image %s\n", argv[optind]);
+ }
+
+ usb_findit(vid, pid, &td.uep);
+
+ setup_connection(&td);
+
+ if (show_fw_ver) {
+ printf("Current versions:\n");
+ printf("Writable %32s\n", targ.common.version);
+ }
+
+ if (data) {
+ transferred_sections = transfer_image(&td, data, data_len);
+ free(data);
+
+ if (transferred_sections)
+ generate_reset_request(&td);
+ } else if (extra_command > -1)
+ send_subcommand(&td, extra_command);
+
+ 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;
+}