summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;
}