summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Sanders <nsanders@chromium.org>2016-07-19 15:13:50 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-07-21 13:13:35 -0700
commit14dbcb829e2c676cb2325445f7e3b9f352833050 (patch)
tree0e6a1aa974b7f9a3a9286a3339fd9a8b8f9656d0
parent63bd3feaabb95562c1051a718ac88305126c3ead (diff)
downloadchrome-ec-14dbcb829e2c676cb2325445f7e3b9f352833050.tar.gz
servo_v4: copypasta usb updater code into common
This copies the generic USB update code into common so it can be used on other platforms. There should be no functional change. cr50 folks want no change to their code so vbendeb@chomium.org will refactor this back together at a later date. BUG=chromium:571476 TEST=none BRANCH=none Change-Id: I710afb22940013e7db5d8694898b90c0ae245777 Signed-off-by: Nick Sanders <nsanders@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/362131 Reviewed-by: Aseda Aboagye <aaboagye@chromium.org>
-rw-r--r--common/build.mk1
-rw-r--r--common/update_fw.c167
-rw-r--r--common/usb_update.c355
-rw-r--r--include/config.h2
-rw-r--r--include/update_fw.h36
-rw-r--r--include/usb_descriptor.h6
6 files changed, 566 insertions, 1 deletions
diff --git a/common/build.mk b/common/build.mk
index 4cedd5b82f..1163699960 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -90,6 +90,7 @@ common-$(CONFIG_USB_PORT_POWER_SMART)+=usb_port_power_smart.o
common-$(CONFIG_USB_POWER_DELIVERY)+=usb_pd_protocol.o usb_pd_policy.o
common-$(CONFIG_USB_PD_LOGGING)+=pd_log.o
common-$(CONFIG_USB_PD_TCPC)+=usb_pd_tcpc.o
+common-$(CONFIG_USB_UPDATE)+= usb_update.o update_fw.o
common-$(CONFIG_VBOOT_HASH)+=sha256.o vboot_hash.o
common-$(CONFIG_VSTORE)+=vstore.o
common-$(CONFIG_WIRELESS)+=wireless.o
diff --git a/common/update_fw.c b/common/update_fw.c
new file mode 100644
index 0000000000..de374ec4dd
--- /dev/null
+++ b/common/update_fw.c
@@ -0,0 +1,167 @@
+/* Copyright 2016 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 "byteorder.h"
+#include "console.h"
+#include "extension.h"
+#include "flash.h"
+#include "hooks.h"
+#include "include/compile_time_macros.h"
+#include "uart.h"
+#include "update_fw.h"
+#include "util.h"
+
+#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args)
+
+/* Various update extension command return values. */
+enum return_value {
+ UPDATE_SUCCESS = 0,
+ UPDATE_BAD_ADDR = 1,
+ UPDATE_ERASE_FAILURE = 2,
+ UPDATE_DATA_ERROR = 3,
+ UPDATE_WRITE_FAILURE = 4,
+ UPDATE_VERIFY_ERROR = 5,
+ UPDATE_GEN_ERROR = 6,
+};
+
+/*
+ * The payload of the update command. (Integer values in network byte order).
+ *
+ * block digest: the first four bytes of the sha1 digest of the rest of the
+ * structure.
+ * block_base: address where this block needs to be written to.
+ * block_body: variable size data to written at address 'block_base'.
+ */
+struct update_command {
+ uint32_t block_digest;
+ uint32_t block_base;
+ uint8_t block_body[0];
+} __packed;
+
+
+const struct section_descriptor *valid_section;
+
+/* Pick the section where updates can go to based on current code address. */
+static int set_valid_section(void)
+{
+ int i;
+ uint32_t run_time_offs = (uint32_t) set_valid_section -
+ CONFIG_PROGRAM_MEMORY_BASE;
+ valid_section = rw_sections;
+
+ for (i = 0; i < num_rw_sections; i++) {
+ if ((run_time_offs > rw_sections[i].sect_base_offset) &&
+ (run_time_offs < rw_sections[i].sect_top_offset))
+ continue;
+ valid_section = rw_sections + i;
+ break;
+ }
+ if (i == num_rw_sections) {
+ CPRINTF("%s:%d No valid section found!\n", __func__, __LINE__);
+ return EC_ERROR_INVAL;
+ }
+ return EC_SUCCESS;
+}
+
+/* Verify that the passed in block fits into the valid area. */
+static int valid_update_chunk(uint32_t block_offset, size_t body_size)
+{
+ if (valid_section &&
+ (block_offset >= valid_section->sect_base_offset) &&
+ ((block_offset + body_size) <= valid_section->sect_top_offset))
+ return 1;
+
+ return 0;
+}
+
+void fw_update_command_handler(void *body,
+ size_t cmd_size,
+ size_t *response_size)
+{
+ struct update_command *cmd_body = body;
+ uint8_t *rv = body;
+ size_t body_size;
+ uint32_t block_offset;
+
+ /*
+ * A single byte response, unless this is the first message in the
+ * programming sequence.
+ */
+ *response_size = sizeof(*rv);
+
+ body_size = cmd_size - offsetof(struct update_command, block_body);
+ if (body_size < 0) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ *rv = UPDATE_GEN_ERROR;
+ return;
+ }
+
+ if (!cmd_body->block_base && !body_size) {
+ int ret;
+ /*
+ * This is the first message of the update process, let's
+ * determine the valid update section and erase its contents.
+ */
+ ret = set_valid_section();
+ if (ret) {
+ CPRINTF("%s:%d no valid section\n", __func__, __LINE__);
+ return;
+ }
+
+ if (flash_physical_erase(valid_section->sect_base_offset,
+ valid_section->sect_top_offset -
+ valid_section->sect_base_offset)) {
+ CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
+ __func__, __LINE__,
+ valid_section->sect_base_offset,
+ valid_section->sect_top_offset -
+ valid_section->sect_base_offset);
+ *rv = UPDATE_ERASE_FAILURE;
+ return;
+ }
+
+ /*
+ * Successful erase means that we need to return the base
+ * address of the section to be programmed with the update.
+ */
+ *(uint32_t *)body = htobe32(valid_section->sect_base_offset +
+ CONFIG_PROGRAM_MEMORY_BASE);
+ *response_size = sizeof(uint32_t);
+ return;
+ }
+
+ /* Check if the block will fit into the valid area. */
+ block_offset = be32toh(cmd_body->block_base) -
+ CONFIG_PROGRAM_MEMORY_BASE;
+ if (!valid_update_chunk(block_offset, body_size)) {
+ *rv = UPDATE_BAD_ADDR;
+ CPRINTF("%s:%d Write out of range %x ..+%d (Window %x - %x)\n",
+ __func__, __LINE__,
+ block_offset, body_size,
+ valid_section->sect_base_offset,
+ valid_section->sect_top_offset);
+ return;
+ }
+
+ if (flash_physical_write(block_offset, body_size,
+ cmd_body->block_body) != EC_SUCCESS) {
+ *rv = UPDATE_WRITE_FAILURE;
+ CPRINTF("%s:%d update write error @0x%x:%x\n",
+ __func__, __LINE__, block_offset, body_size);
+ return;
+ }
+
+ /* Werify that data was written properly. */
+ if (memcmp(cmd_body->block_body, (void *)
+ (block_offset + CONFIG_PROGRAM_MEMORY_BASE),
+ body_size)) {
+ *rv = UPDATE_VERIFY_ERROR;
+ CPRINTF("%s:%d update verification error\n",
+ __func__, __LINE__);
+ return;
+ }
+
+ *rv = UPDATE_SUCCESS;
+}
diff --git a/common/usb_update.c b/common/usb_update.c
new file mode 100644
index 0000000000..80e1c0498c
--- /dev/null
+++ b/common/usb_update.c
@@ -0,0 +1,355 @@
+/* Copyright 2016 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 "byteorder.h"
+#include "common.h"
+#include "console.h"
+#include "consumer.h"
+#include "include/compile_time_macros.h"
+#include "queue_policies.h"
+#include "shared_mem.h"
+#include "system.h"
+#include "update_fw.h"
+#include "usb-stream.h"
+#include "util.h"
+
+#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
+
+/*
+ * This file is an adaptation layer between the USB interface and the firmware
+ * update engine. The engine expects to receive long blocks of data, 1K or so
+ * in size, prepended by the offset where the data needs to be programmed into
+ * the flash and a 4 byte integrity check value.
+ *
+ * The USB transfer, on the other hand, operates on much shorter chunks of
+ * data, typically 64 bytes in this case. This module reassembles firmware
+ * programming blocks from the USB chunks, and invokes the programmer passing
+ * it the full block.
+ *
+ * The programmer reports results by putting the return value of one or four
+ * bytes into the same buffer where the block was passed in. This wrapper
+ * retrieves the programmer's return value, normalizes it to 4 bytes and sends
+ * it back to the host.
+ *
+ * In the end of the successful image transfer and programming, the host send
+ * the reset command, and the device reboots itself.
+ */
+
+struct consumer const update_consumer;
+struct usb_stream_config const usb_update;
+
+static struct queue const update_to_usb = QUEUE_DIRECT(64, uint8_t,
+ null_producer,
+ usb_update.consumer);
+static struct queue const usb_to_update = QUEUE_DIRECT(64, uint8_t,
+ usb_update.producer,
+ update_consumer);
+
+USB_STREAM_CONFIG_FULL(usb_update,
+ USB_IFACE_UPDATE,
+ USB_CLASS_VENDOR_SPEC,
+ USB_SUBCLASS_GOOGLE_UPDATE,
+ USB_PROTOCOL_GOOGLE_UPDATE,
+ USB_STR_UPDATE_NAME,
+ USB_EP_UPDATE,
+ USB_MAX_PACKET_SIZE,
+ USB_MAX_PACKET_SIZE,
+ usb_to_update,
+ update_to_usb)
+
+
+/* The receiver can be in one of the states below. */
+enum rx_state {
+ rx_idle, /* Nothing happened yet. */
+ rx_inside_block, /* Assembling a block to pass to the programmer. */
+ rx_outside_block, /* Waiting for the next block to start or for the
+ reset command. */
+ rx_awaiting_reset /* Waiting for reset confirmation. */
+};
+
+/* This is the format of the header the programmer expects. */
+struct update_command {
+ uint32_t block_digest; /* first 4 bytes of sha1 of the rest of the
+ block. */
+ uint32_t block_base; /* Offset of this block into the flash SPI. */
+};
+
+/* This is the format of the header the host uses. */
+struct update_pdu_header {
+ uint32_t block_size; /* Total size of the block, including this
+ field. */
+ union {
+ struct update_command cmd;
+ uint32_t resp; /* The programmer puts response to the same
+ buffer where the command was. */
+ };
+ /* The actual payload goes here. */
+};
+
+enum rx_state rx_state_ = rx_idle;
+static uint8_t *block_buffer;
+static uint32_t block_size;
+static uint32_t block_index;
+
+/*
+ * Verify that the contens of the USB rx queue is a valid transfer start
+ * message from host, and if so - save its contents in the passed in
+ * update_pdu_header structure.
+ */
+static int valid_transfer_start(struct consumer const *consumer, size_t count,
+ struct update_pdu_header *pupdu)
+{
+ int i;
+
+ /*
+ * Let's just make sure we drain the queue no matter what the contents
+ * are. This way they won't be in the way during next callback, even
+ * if these contents are not what's expected.
+ */
+ i = count;
+ while (i > 0) {
+ QUEUE_REMOVE_UNITS(consumer->queue, pupdu,
+ MIN(i, sizeof(*pupdu)));
+ i -= sizeof(*pupdu);
+ }
+
+ if (count != sizeof(struct update_pdu_header)) {
+ CPRINTS("FW update: wrong first block, size %d", count);
+ return 0;
+ }
+
+ /* In the first block the payload (updu.cmd) must be all zeros. */
+ for (i = 0; i < sizeof(pupdu->cmd); i++)
+ if (((uint8_t *)&pupdu->cmd)[i])
+ return 0;
+ return 1;
+}
+
+/*
+ * When was last time a USB callback was called, in microseconds, free running
+ * timer.
+ */
+static uint64_t prev_activity_timestamp;
+
+#define UPDATE_PROTOCOL_VERSION 2
+
+/* Called to deal with data from the host */
+static void update_out_handler(struct consumer const *consumer, size_t count)
+{
+ struct update_pdu_header updu;
+ size_t resp_size;
+ uint32_t resp_value;
+ uint64_t delta_time;
+
+ /* How much time since the previous USB callback? */
+ delta_time = get_time().val - prev_activity_timestamp;
+ prev_activity_timestamp += delta_time;
+
+ /* If timeout exceeds 5 seconds - let's start over. */
+ if ((delta_time > 5000000) && (rx_state_ != rx_idle)) {
+ if (block_buffer) {
+ /*
+ * Previous transfer could have been aborted mid
+ * block.
+ */
+ shared_mem_release(block_buffer);
+ block_buffer = NULL;
+ }
+ rx_state_ = rx_idle;
+ CPRINTS("FW update: recovering after timeout");
+ }
+
+ if (rx_state_ == rx_idle) {
+ /*
+ * When responding to the very first packet of the update
+ * sequence, the original implementation was responding with a
+ * four byte value, just as to any other block of the transfer
+ * sequence.
+ *
+ * It became clear that there is a need to be able to enhance
+ * the update protocol, while stayng backwards compatible. To
+ * achieve that we respond to the very first packet with an 8
+ * byte value, the first 4 bytes the same as before, the
+ * second 4 bytes - the protocol version number.
+ *
+ * This way if on the host side receiving of a four byte value
+ * in response to the first packet is an indication of the
+ * 'legacy' protocol, version 0. Receiving of an 8 byte
+ * response would communicate the protocol version in the
+ * second 4 bytes.
+ */
+ struct {
+ uint32_t value;
+ uint32_t version;
+ } startup_resp;
+
+ if (!valid_transfer_start(consumer, count, &updu))
+ return;
+
+ CPRINTS("FW update: starting...");
+
+ fw_update_command_handler(&updu.cmd, count -
+ offsetof(struct update_pdu_header,
+ cmd),
+ &resp_size);
+
+ if (resp_size == 4) {
+ /* Already in network order. */
+ startup_resp.value = updu.resp;
+ rx_state_ = rx_outside_block;
+ } else {
+ /* This must be a single byte error code. */
+ startup_resp.value = htobe32(*((uint8_t *)&updu.resp));
+ }
+
+ startup_resp.version = htobe32(UPDATE_PROTOCOL_VERSION);
+
+ /* Let the host know what updater had to say. */
+ QUEUE_ADD_UNITS(&update_to_usb, &startup_resp,
+ sizeof(startup_resp));
+ return;
+ }
+
+ if (rx_state_ == rx_awaiting_reset) {
+ /*
+ * Any USB data received in this state triggers reset, no
+ * response required.
+ */
+ CPRINTS("reboot hard");
+ cflush();
+ system_reset(SYSTEM_RESET_HARD);
+ while (1)
+ ;
+ }
+
+ if (rx_state_ == rx_outside_block) {
+ /*
+ * Expecting to receive the beginning of the block or the
+ * reset command if all data blocks have been processed.
+ */
+ if (count == 4) {
+ uint32_t command;
+
+ QUEUE_REMOVE_UNITS(consumer->queue, &command,
+ sizeof(command));
+ command = be32toh(command);
+ if (command == UPDATE_DONE) {
+ CPRINTS("FW update: done");
+ resp_value = 0;
+ QUEUE_ADD_UNITS(&update_to_usb, &resp_value,
+ sizeof(resp_value));
+ rx_state_ = rx_awaiting_reset;
+ return;
+ } else {
+ CPRINTS("Unexpected packet command 0x%x",
+ command);
+ }
+ }
+
+ /*
+ * At this point we expect a block start message. It is
+ * sizeof(updu) bytes in size, but is not the transfer start
+ * message, which also is of that size AND has the command
+ * field of all zeros.
+ */
+ if (valid_transfer_start(consumer, count, &updu) ||
+ (count != sizeof(updu)))
+ /*
+ * Instead of a block start message we received either
+ * a transfer start message or a chunk. We must have
+ * gotten out of sync with the host.
+ */
+ return;
+
+ /* Let's allocate a large enough buffer. */
+ block_size = be32toh(updu.block_size) -
+ offsetof(struct update_pdu_header, cmd);
+ if (shared_mem_acquire(block_size, (char **)&block_buffer)
+ != EC_SUCCESS) {
+ /* TODO:(vbendeb) report out of memory here. */
+ CPRINTS("FW update: error: failed to alloc %d bytes.",
+ block_size);
+ return;
+ }
+
+ /*
+ * Copy the rest of the message into the block buffer to pass
+ * to the updater.
+ */
+ block_index = sizeof(updu) -
+ offsetof(struct update_pdu_header, cmd);
+ memcpy(block_buffer, &updu.cmd, block_index);
+ block_size -= block_index;
+ rx_state_ = rx_inside_block;
+ return;
+ }
+
+ /* Must be inside block. */
+ QUEUE_REMOVE_UNITS(consumer->queue, block_buffer + block_index, count);
+ block_index += count;
+ block_size -= count;
+
+ if (block_size) {
+ if (count == sizeof(updu)) {
+ /*
+ * A block header size instead of chunk size message
+ * has been received. There must have been some packet
+ * loss and the host is restarting this block.
+ *
+ * Let's copy its contents into the header structure.
+ */
+ memcpy(&updu, block_buffer + block_index - count,
+ count);
+
+
+ /* And re-allocate a large enough buffer. */
+ shared_mem_release(block_buffer);
+ block_size = be32toh(updu.block_size) -
+ offsetof(struct update_pdu_header, cmd);
+ if (shared_mem_acquire(block_size,
+ (char **)&block_buffer)
+ != EC_SUCCESS) {
+ /* TODO:(vbendeb) report out of memory here. */
+ CPRINTS("FW update: error: failed to alloc "
+ "%d bytes.", block_size);
+ return;
+ }
+
+ /*
+ * Copy the rest of the message into the block buffer
+ * to pass to the updater.
+ */
+ block_index = sizeof(updu) -
+ offsetof(struct update_pdu_header, cmd);
+ memcpy(block_buffer, &updu.cmd, block_index);
+ block_size -= block_index;
+ }
+ return; /* More to come. */
+ }
+
+ /*
+ * Ok, the entire block has been received and reassembled, pass it to
+ * the updater for verification and programming.
+ */
+ fw_update_command_handler(block_buffer, block_index, &resp_size);
+
+ resp_value = block_buffer[0];
+ QUEUE_ADD_UNITS(&update_to_usb, &resp_value, sizeof(resp_value));
+ rx_state_ = rx_outside_block;
+ shared_mem_release(block_buffer);
+ block_buffer = NULL;
+}
+
+static void update_flush(struct consumer const *consumer)
+{
+}
+
+struct consumer const update_consumer = {
+ .queue = &usb_to_update,
+ .ops = &((struct consumer_ops const) {
+ .written = update_out_handler,
+ .flush = update_flush,
+ }),
+};
diff --git a/include/config.h b/include/config.h
index 3cd443a3a3..d868d5671e 100644
--- a/include/config.h
+++ b/include/config.h
@@ -2199,6 +2199,8 @@
/* Firmware updates using other than HC channel(s). */
#undef CONFIG_NON_HC_FW_UPDATE
#undef CONFIG_USB_FW_UPDATE
+/* A different config for the same update. TODO(vbendeb): dedup these */
+#undef CONFIG_USB_UPDATE
/*****************************************************************************/
/*
diff --git a/include/update_fw.h b/include/update_fw.h
new file mode 100644
index 0000000000..f1b76e1033
--- /dev/null
+++ b/include/update_fw.h
@@ -0,0 +1,36 @@
+/* Copyright 2016 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.
+ */
+
+#ifndef __CROS_EC_UPDATE_FW_H
+#define __CROS_EC_UPDATE_FW_H
+
+#include <stddef.h>
+
+/* TODO: Handle this in update_fw.c, not usb_update.c */
+#define UPDATE_DONE 0xB007AB1E
+
+/*
+ * This array defines possible sections available for the firmare update.
+ * The section which does not map the current execting code is picked as the
+ * valid update area. The values are offsets into the flash space.
+ *
+ * This should be defined in board.c, with each entry containing:
+ * {CONFIG_RW_MEM_OFF, CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE}
+ * for its relevant section.
+ */
+struct section_descriptor {
+ uint32_t sect_base_offset;
+ uint32_t sect_top_offset;
+};
+
+extern const struct section_descriptor * const rw_sections;
+extern const int num_rw_sections;
+
+
+void fw_update_command_handler(void *body,
+ size_t cmd_size,
+ size_t *response_size);
+
+#endif /* ! __CROS_EC_UPDATE_FW_H */
diff --git a/include/usb_descriptor.h b/include/usb_descriptor.h
index a29b9eda76..22f2c08257 100644
--- a/include/usb_descriptor.h
+++ b/include/usb_descriptor.h
@@ -167,11 +167,15 @@ struct usb_endpoint_descriptor {
#define USB_SUBCLASS_GOOGLE_I2C 0x52
#define USB_PROTOCOL_GOOGLE_I2C 0x01
+#define USB_SUBCLASS_GOOGLE_UPDATE 0x53
+#define USB_PROTOCOL_GOOGLE_UPDATE 0xff
+
+/* Double define for cr50 code freeze.
+ * TODO(vbendeb): dedup this. */
#define USB_SUBCLASS_GOOGLE_CR50 0x53
/* We can use any protocol we want */
#define USB_PROTOCOL_GOOGLE_CR50_NON_HC_FW_UPDATE 0xff
-
/* Control requests */
/* bRequestType fields */