diff options
author | Nicolas Boichat <drinkcat@google.com> | 2017-03-22 10:50:39 +0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-04-15 04:08:07 -0700 |
commit | 2f5e46cef48ec0671fd014bbdb38b060339db998 (patch) | |
tree | 147061592ddb8fa0ba504b5b1ba93dbdd1b23bd4 /common/usb_update.c | |
parent | 20c439be209a9cc0bb949ad21f289c453126395f (diff) | |
download | chrome-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.c | 271 |
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; |