summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2016-11-25 18:30:48 -0800
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2017-02-07 12:15:19 +0000
commit2e33a6b6dfd663e8a72de067831720dd3c8f77f5 (patch)
tree6d71889998910b24704637bfdb1d9d3b5415bfe4
parent7c7b3c01b8798df0749039ab5f0a8c1869479e2b (diff)
downloadchrome-ec-2e33a6b6dfd663e8a72de067831720dd3c8f77f5.tar.gz
usb_updater: protocol version 6 (vendor commands over usb)
This patch introduces version 6 of the cr50 USB update protocol. This version allows to multiplex TPM vendor and extension commands over the same USB endpoint which is used for firmware updates. When channeling TPM vendor commands the USB update 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. In the previous protocol versions target reset was requested by the host sending a 4 byte PDU after the target receives the UPGRADE_DONE message and moves the state machine to the 'awaiting_reset' state. With the ability to transfer vendor commands, there is no need for the target to have a special state for reset. The host can send the posted or immediate reboot request using the appropriate vendor command. As a result the 'awaiting_reset' state has been removed, the target accepts vendor commands only when state machine is in the rx_idle state. Vendor command response size is not fixed, it is subcommand dependent. In the current implementation the total size of the vendor command PDU can not exceed 64 bytes, as there is no reassembly on the target side. For backwards compatibility in case the target is running protocol version earlier than 6, the 4 byte PDU is still sent to the target after UPGRADE_DONE is sent. BRANCH=none BUG=chrome-os-partner:60013 TEST=tested updates on Reef and Gru, observed that it is possible to update earlier versions of firmware, and that it is possible to request immediate and posted reset (depending on the presence of the -u flag in the usb_updater invocation). Change-Id: I6ea9e9f742c96b8ab0670e9cec87a83cd47bb20e Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/414948 Reviewed-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/439095 Reviewed-by: Youcheng Syu <youcheng@google.com> Commit-Queue: Youcheng Syu <youcheng@google.com> Tested-by: Youcheng Syu <youcheng@google.com>
-rw-r--r--chip/g/upgrade_fw.h2
-rw-r--r--chip/g/usb_upgrade.c77
-rw-r--r--extra/usb_updater/usb_updater.c159
3 files changed, 206 insertions, 32 deletions
diff --git a/chip/g/upgrade_fw.h b/chip/g/upgrade_fw.h
index 01e8addbc4..f91e684e38 100644
--- a/chip/g/upgrade_fw.h
+++ b/chip/g/upgrade_fw.h
@@ -32,7 +32,7 @@
* first_response_pdu structure below.
*/
-#define UPGRADE_PROTOCOL_VERSION 5
+#define UPGRADE_PROTOCOL_VERSION 6
/* This is the format of the update frame header. */
struct upgrade_command {
diff --git a/chip/g/usb_upgrade.c b/chip/g/usb_upgrade.c
index d709f1f49b..0dee949fa3 100644
--- a/chip/g/usb_upgrade.c
+++ b/chip/g/usb_upgrade.c
@@ -7,6 +7,7 @@
#include "common.h"
#include "console.h"
#include "consumer.h"
+#include "extension.h"
#include "queue_policies.h"
#include "shared_mem.h"
#include "system.h"
@@ -65,7 +66,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. */
};
enum rx_state rx_state_ = rx_idle;
@@ -106,6 +106,64 @@ static int valid_transfer_start(struct consumer const *consumer, size_t count,
return 0;
return 1;
}
+static int try_vendor_command(struct consumer const *consumer, size_t count)
+{
+ struct update_frame_header ufh;
+ struct update_frame_header *cmd_buffer;
+ int rv = 0;
+
+ if (count < sizeof(ufh))
+ return 0; /* Too short to be a valid vendor command. */
+
+ /*
+ * Let's copy off the queue the upgrade frame header, to see if this
+ * is a channeled vendor command.
+ */
+ queue_peek_units(consumer->queue, &ufh, 0, sizeof(ufh));
+ if (be32toh(ufh.cmd.block_base) != CONFIG_EXTENSION_COMMAND)
+ return 0;
+
+ if (be32toh(ufh.block_size) != count) {
+ CPRINTS("%s: problem: block size and count mismatch (%d != %d)",
+ __func__, be32toh(ufh.block_size), count);
+ return 0;
+ }
+
+ if (shared_mem_acquire(count, (char **)&cmd_buffer)
+ != EC_SUCCESS) {
+ CPRINTS("%s: problem: failed to allocate block of %d",
+ __func__, count);
+ return 0;
+ }
+
+ /* Get the entire command, don't remove it from the queue just yet. */
+ queue_peek_units(consumer->queue, cmd_buffer, 0, count);
+
+ /* Looks like this is a vendor command, let's verify it. */
+ if (usb_pdu_valid(&cmd_buffer->cmd,
+ count - offsetof(struct update_frame_header, cmd))) {
+ uint16_t *subcommand;
+ size_t response_size;
+
+ /* looks good, let's process it. */
+ rv = 1;
+
+ /* Now remove if from the queue. */
+ queue_advance_head(consumer->queue, count);
+
+ subcommand = (uint16_t *)(cmd_buffer + 1);
+ extension_route_command(be16toh(*subcommand),
+ subcommand + 1,
+ count -
+ sizeof(struct update_frame_header),
+ &response_size);
+
+ QUEUE_ADD_UNITS(&upgrade_to_usb, subcommand + 1, response_size);
+ }
+ shared_mem_release(cmd_buffer);
+
+ return rv;
+}
/*
* When was last time a USB callback was called, in microseconds, free running
@@ -155,6 +213,10 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
};
} u;
+ /* Check is this is a channeled TPM extension command. */
+ if (try_vendor_command(consumer, count))
+ return;
+
if (!valid_transfer_start(consumer, count, &u.upfr)) {
/*
* Something is wrong, this payload is not a valid
@@ -181,17 +243,6 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
return;
}
- if (rx_state_ == rx_awaiting_reset) {
- /*
- * Any USB data received in this state should cause a post of
- * a system reset request on the next TPM reboot, no USB
- * response required.
- */
- rx_state_ = rx_idle;
- post_reboot_request();
- return;
- }
-
if (rx_state_ == rx_outside_block) {
/*
* Expecting to receive the beginning of the block or the
@@ -210,7 +261,7 @@ static void upgrade_out_handler(struct consumer const *consumer, size_t count)
resp_value = 0;
QUEUE_ADD_UNITS(&upgrade_to_usb,
&resp_value, 1);
- rx_state_ = rx_awaiting_reset;
+ rx_state_ = rx_idle;
return;
}
}
diff --git a/extra/usb_updater/usb_updater.c b/extra/usb_updater/usb_updater.c
index a1248c58f1..71ca80a822 100644
--- a/extra/usb_updater/usb_updater.c
+++ b/extra/usb_updater/usb_updater.c
@@ -120,9 +120,34 @@
* the header is the absolute address to place the block to, in version 3 and
* later it is the offset into the flash.
*
- * The CR50 device responds to each 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 succes, non zero value is the error code reported by CR50.
+ * 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 */
@@ -960,12 +985,77 @@ static void setup_connection(struct transfer_descriptor *td)
exit(update_error);
}
+/*
+ * 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));
+ 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(UPGRADE_DONE);
+ xfer(uep, &out, sizeof(out), &out,
+ protocol_version < 3 ? sizeof(out) : 1);
+}
+
/* Returns number of successfully transmitted image sections. */
static int transfer_and_reboot(struct transfer_descriptor *td,
uint8_t *data, size_t data_len)
{
size_t i;
int num_txed_secitons = 0;
+ /* By default target is reset immediately after update. */
+ uint16_t subcommand = VENDOR_CC_IMMEDIATE_RESET;
for (i = 0; i < ARRAY_SIZE(sections); i++)
if (sections[i].ustatus == needed) {
@@ -977,38 +1067,73 @@ static int transfer_and_reboot(struct transfer_descriptor *td,
}
if (!num_txed_secitons) {
+ if (td->ep_type == usb_xfer)
+ send_done(&td->uep);
+
printf("nothing to do\n");
return 0;
}
printf("-------\nupdate complete\n");
+
+ /*
+ * In upstart mode or in case target is running older protocol version
+ * - post reset is requested.
+ */
+ if (td->upstart_mode || (protocol_version <= 5))
+ subcommand = EXTENSION_POST_RESET;
+
if (td->ep_type == usb_xfer) {
uint32_t out;
- /* Send stop request, ignoring reply. */
- out = htobe32(UPGRADE_DONE);
- xfer(&td->uep, &out, sizeof(out), &out,
- protocol_version < 3 ? sizeof(out) : 1);
- /*
- * Send a second stop request, which should reboot without
- * replying.
- */
- xfer(&td->uep, &out, sizeof(out), 0, 0);
+ send_done(&td->uep);
+
+ if (protocol_version > 5) {
+ uint8_t response;
+ size_t response_size;
+ void *presponse;
+
+ /*
+ * Protocol versions 6 and above use vendor command to
+ * communicate reset mode (immediate or posted) to the
+ * target.
+ *
+ * No response is expected in case of immediate reset.
+ */
+ if (subcommand == VENDOR_CC_IMMEDIATE_RESET) {
+ presponse = NULL;
+ response_size = 0;
+ } else {
+ presponse = &response;
+ response_size = sizeof(response);
+ }
+
+ ext_cmd_over_usb(&td->uep, subcommand,
+ NULL, 0,
+ presponse, &response_size);
+ } else {
+ /*
+ * Send a second stop request, which should reboot
+ * without replying.
+ */
+ xfer(&td->uep, &out, sizeof(out), 0, 0);
+ }
} else {
uint8_t response;
size_t response_size;
/* Need to send extended command for posted reboot. */
if (tpm_send_pkt(td->tpm_fd, 0, 0, NULL, 0,
- &response, &response_size,
- EXTENSION_POST_RESET) < 0) {
+ &response, &response_size, subcommand) < 0) {
fprintf(stderr, "Failed to request posted reboot\n");
exit(update_error);
}
}
- printf("reboot request posted");
+ printf("reboot %s\n", subcommand == EXTENSION_POST_RESET ?
+ "request posted" : "triggered");
+
return num_txed_secitons;
}
@@ -1153,8 +1278,6 @@ int main(int argc, char *argv[])
if (data) {
transferred_sections = transfer_and_reboot(&td, data, data_len);
-
- printf("bye\n");
free(data);
}
if (td.ep_type == usb_xfer) {
@@ -1175,6 +1298,6 @@ int main(int argc, char *argv[])
return rw_updated;
}
- printf("image updated, reboot is needed\n");
+ printf("image updated\n");
return all_updated;
}