diff options
-rw-r--r-- | chip/g/upgrade_fw.h | 2 | ||||
-rw-r--r-- | chip/g/usb_upgrade.c | 77 | ||||
-rw-r--r-- | extra/usb_updater/usb_updater.c | 159 |
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; } |