summaryrefslogtreecommitdiff
path: root/common/usb_update.c
diff options
context:
space:
mode:
authorNicolas Boichat <drinkcat@google.com>2017-03-22 10:50:39 +0800
committerchrome-bot <chrome-bot@chromium.org>2017-04-15 04:08:07 -0700
commit2f5e46cef48ec0671fd014bbdb38b060339db998 (patch)
tree147061592ddb8fa0ba504b5b1ba93dbdd1b23bd4 /common/usb_update.c
parent20c439be209a9cc0bb949ad21f289c453126395f (diff)
downloadchrome-ec-2f5e46cef48ec0671fd014bbdb38b060339db998.tar.gz
common/update: Update common code updater to latest chip/g version
Let's move to protocol version 6, which provides most of the new features we want to update generic EC firmware. Note that this matches chip/g version as of commit 0e5497db6, plus the following uncommited chip/g patches (CL:458364): c73af7dd2 chip/g/upgrade: Clarify pdu/frame terminology baea0a8c7 chip/g/upgrade: Rename SIGNED_TRANSFER_SIZE to UPDATE_PDU_SIZE d6e41b75c chip/g/upgrade: Remove cr50-specific upgrade subclass and protocol 3dc0b9a25 chip/g/upgrade: Rename upgrade to update 13436f9b9 chip/g/upgrade: Split rdpu initialization to a separate function fab9a0936 chip/g/upgrade: Minor formatting fixups 8161ef7c0 chip/g/upgrade: Fix valid_transfer_start logic bd6d79434 chip/g/upgrade: Fix logic for short USB packets within frames b09e252ed chip/g/upgrade: Improve error handling Then: diff -u include/update_fw.h chip/g/upgrade_fw.h diff -u common/usb_update.c chip/g/usb_upgrade.c diff -u common/update_fw.c chip/g/upgrade_fw.c Only shows chip/g specific differences. BRANCH=none BUG=b:36375666 BUG=b:35587171 TEST=make buildall -j TEST=Can update hammer over USB using usb_updater2 Change-Id: I5b0f0281d844972dab572955d5495f808127e523 Reviewed-on: https://chromium-review.googlesource.com/458321 Commit-Ready: Nicolas Boichat <drinkcat@chromium.org> Tested-by: Nicolas Boichat <drinkcat@chromium.org> Reviewed-by: Nick Sanders <nsanders@chromium.org> Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
Diffstat (limited to 'common/usb_update.c')
-rw-r--r--common/usb_update.c271
1 files changed, 126 insertions, 145 deletions
diff --git a/common/usb_update.c b/common/usb_update.c
index 382677835f..6a85f19424 100644
--- a/common/usb_update.c
+++ b/common/usb_update.c
@@ -7,13 +7,11 @@
#include "common.h"
#include "console.h"
#include "consumer.h"
-#include "include/compile_time_macros.h"
+#include "extension.h"
#include "queue_policies.h"
#include "shared_mem.h"
#include "system.h"
#include "update_fw.h"
-#include "usb_api.h"
-#include "usb_hw.h"
#include "usb-stream.h"
#include "util.h"
@@ -30,12 +28,13 @@
* 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.
+ * The programmer reports results by putting the return value into the same
+ * buffer where the block was passed in. This wrapper retrieves the
+ * programmer's return value, and sends it back to the host. The return value
+ * is usually one byte in size, the only exception is the connection
+ * establishment phase where the return value is 16 bytes in size.
*
- * In the end of the successful image transfer and programming, the host send
+ * In the end of the successful image transfer and programming, the host sends
* the reset command, and the device reboots itself.
*/
@@ -68,26 +67,6 @@ enum rx_state {
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;
@@ -96,12 +75,12 @@ 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.
+ * Fetches a transfer start frame from the queue. This can be either an update
+ * start frame (block_size = 0, all of cmd = 0), or the beginning of a frame
+ * (block_size > 0, valid block_base in cmd).
*/
-static int valid_transfer_start(struct consumer const *consumer, size_t count,
- struct update_pdu_header *pupdu)
+static int fetch_transfer_start(struct consumer const *consumer, size_t count,
+ struct update_frame_header *pupfr)
{
int i;
@@ -109,40 +88,61 @@ static int valid_transfer_start(struct consumer const *consumer, size_t count,
* 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.
+ *
+ * Note: If count > sizeof(*pupfr), pupfr will be corrupted. This is
+ * ok as we will immediately fail after this.
*/
i = count;
while (i > 0) {
- QUEUE_REMOVE_UNITS(consumer->queue, pupdu,
- MIN(i, sizeof(*pupdu)));
- i -= sizeof(*pupdu);
+ QUEUE_REMOVE_UNITS(consumer->queue, pupfr,
+ MIN(i, sizeof(*pupfr)));
+ i -= sizeof(*pupfr);
}
- if (count != sizeof(struct update_pdu_header)) {
+ if (count != sizeof(struct update_frame_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;
}
+static int try_vendor_command(struct consumer const *consumer, size_t count)
+{
+ /* TODO(b/35587171): Vendor commands not implemented (yet). */
+ return 0;
+}
+
/*
* 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
+/*
+ * A flag indicating that at least one valid PDU containing flash update block
+ * has been received in the current transfer session.
+ */
+static uint8_t data_was_transferred;
+
+/* Reply with an error to remote side, reset state. */
+static void send_error_reset(uint8_t resp_value)
+{
+ QUEUE_ADD_UNITS(&update_to_usb, &resp_value, 1);
+ rx_state_ = rx_idle;
+ data_was_transferred = 0;
+ if (block_buffer) {
+ shared_mem_release(block_buffer);
+ block_buffer = NULL;
+ }
+}
/* 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;
+ struct update_frame_header upfr;
size_t resp_size;
- uint32_t resp_value;
+ uint8_t resp_value;
uint64_t delta_time;
/* How much time since the previous USB callback? */
@@ -165,67 +165,59 @@ static void update_out_handler(struct consumer const *consumer, size_t count)
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.
+ * The payload must be an update initiating PDU.
*
- * 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.
+ * The size of the response returned in the same buffer will
+ * exceed the received frame size; Let's make sure there is
+ * enough room for the response in the buffer.
*/
- struct {
- uint32_t value;
- uint32_t version;
- } startup_resp;
+ union {
+ struct update_frame_header upfr;
+ struct {
+ uint32_t unused;
+ struct first_response_pdu startup_resp;
+ };
+ } u;
+
+ /* Check is this is a channeled TPM extension command. */
+ if (try_vendor_command(consumer, count))
+ return;
- if (!valid_transfer_start(consumer, count, &updu))
+ /*
+ * An update start PDU is a command without any payload, with
+ * digest = 0, and base = 0.
+ */
+ if (!fetch_transfer_start(consumer, count, &u.upfr) ||
+ be32toh(u.upfr.block_size) !=
+ sizeof(struct update_frame_header) ||
+ u.upfr.cmd.block_digest != 0 ||
+ u.upfr.cmd.block_base != 0) {
+ /*
+ * Something is wrong, this payload is not a valid
+ * update start PDU. Let'w indicate this by returning
+ * a single byte error code.
+ */
+ CPRINTS("FW update: invalid start.");
+ send_error_reset(UPDATE_GEN_ERROR);
return;
+ }
CPRINTS("FW update: starting...");
-
- fw_update_command_handler(&updu.cmd, count -
- offsetof(struct update_pdu_header,
+ fw_update_command_handler(&u.upfr.cmd, count -
+ offsetof(struct update_frame_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));
+ if (!u.startup_resp.return_value) {
+ rx_state_ = rx_outside_block; /* We're in business. */
+ data_was_transferred = 0; /* No data received yet. */
}
- 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));
+ QUEUE_ADD_UNITS(&update_to_usb, &u.startup_resp, resp_size);
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
@@ -239,40 +231,49 @@ static void update_out_handler(struct consumer const *consumer, size_t count)
command = be32toh(command);
if (command == UPDATE_DONE) {
CPRINTS("FW update: done");
+
+ if (data_was_transferred) {
+ fw_update_complete();
+ data_was_transferred = 0;
+ }
+
resp_value = 0;
- QUEUE_ADD_UNITS(&update_to_usb, &resp_value,
- sizeof(resp_value));
- rx_state_ = rx_awaiting_reset;
+ QUEUE_ADD_UNITS(&update_to_usb,
+ &resp_value, 1);
+ rx_state_ = rx_idle;
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.
+ * sizeof(upfr) bytes in size.
*/
- 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.
- */
+ if (!fetch_transfer_start(consumer, count, &upfr)) {
+ CPRINTS("Invalid block start.");
+ send_error_reset(UPDATE_GEN_ERROR);
return;
+ }
/* Let's allocate a large enough buffer. */
- block_size = be32toh(updu.block_size) -
- offsetof(struct update_pdu_header, cmd);
+ block_size = be32toh(upfr.block_size) -
+ offsetof(struct update_frame_header, cmd);
+
+ /*
+ * Only update start PDU is allowed to have a size 0 payload.
+ */
+ if (block_size <= sizeof(struct update_command) ||
+ block_size > (UPDATE_PDU_SIZE +
+ sizeof(struct update_command))) {
+ CPRINTS("Invalid block size (%d).", block_size);
+ send_error_reset(UPDATE_GEN_ERROR);
+ return;
+ }
+
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);
+ != EC_SUCCESS) {
+ CPRINTS("Alloc error (%d).", block_size);
+ send_error_reset(UPDATE_MALLOC_ERROR);
return;
}
@@ -280,9 +281,9 @@ static void update_out_handler(struct consumer const *consumer, size_t count)
* 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_index = sizeof(upfr) -
+ offsetof(struct update_frame_header, cmd);
+ memcpy(block_buffer, &upfr.cmd, block_index);
block_size -= block_index;
rx_state_ = rx_inside_block;
return;
@@ -294,39 +295,14 @@ static void update_out_handler(struct consumer const *consumer, size_t count)
block_size -= count;
if (block_size) {
- if (count == sizeof(updu)) {
+ if (count <= sizeof(upfr)) {
/*
* 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.
+ * has been received, let's abort the transfer.
*/
- block_index = sizeof(updu) -
- offsetof(struct update_pdu_header, cmd);
- memcpy(block_buffer, &updu.cmd, block_index);
- block_size -= block_index;
+ CPRINTS("Unexpected header");
+ send_error_reset(UPDATE_GEN_ERROR);
+ return;
}
return; /* More to come. */
}
@@ -337,6 +313,11 @@ static void update_out_handler(struct consumer const *consumer, size_t count)
*/
fw_update_command_handler(block_buffer, block_index, &resp_size);
+ /*
+ * There was at least an attempt to program the flash, set the
+ * flag.
+ */
+ data_was_transferred = 1;
resp_value = block_buffer[0];
QUEUE_ADD_UNITS(&update_to_usb, &resp_value, sizeof(resp_value));
rx_state_ = rx_outside_block;