diff options
-rw-r--r-- | chip/g/upgrade_fw.c | 4 | ||||
-rw-r--r-- | chip/g/upgrade_fw.h | 37 | ||||
-rw-r--r-- | extra/usb_updater/usb_updater.c | 340 |
3 files changed, 307 insertions, 74 deletions
diff --git a/chip/g/upgrade_fw.c b/chip/g/upgrade_fw.c index 299c355e79..714db3abf5 100644 --- a/chip/g/upgrade_fw.c +++ b/chip/g/upgrade_fw.c @@ -170,10 +170,10 @@ void fw_upgrade_command_handler(void *body, return; } - rpdu->vers3.backup_ro_offset = + rpdu->backup_ro_offset = htobe32(valid_sections.ro_base_offset); - rpdu->vers3.backup_rw_offset = + rpdu->backup_rw_offset = htobe32(valid_sections.rw_base_offset); return; diff --git a/chip/g/upgrade_fw.h b/chip/g/upgrade_fw.h index 19aa5d9e5c..8eca3761b6 100644 --- a/chip/g/upgrade_fw.h +++ b/chip/g/upgrade_fw.h @@ -28,13 +28,11 @@ * contains no data and is destined to offset 0. Receiving such a frame * signals the CR50 that the host intends to transfer a new image. * - * Version 3 connection establishment response is 16 bytes in size, all values - * in network byte order. The first 4 bytes are the error code (if any), the - * second 4 bytes are the protocol version (set to 3) and then 4 byte offset - * of the RO section followed by the 4 byte offset of the RW section. + * The connection establishment response is described by the + * first_response_pdu structure below. */ -#define UPGRADE_PROTOCOL_VERSION 3 +#define UPGRADE_PROTOCOL_VERSION 4 /* This is the format of the update frame header. */ struct upgrade_command { @@ -63,6 +61,19 @@ struct update_frame_header { }; /* + * A convenience structure which allows to group together various revision + * fields of the header created by the signer. + * + * These fields are compared when deciding if versions of two images are the + * same or when deciding which one of the available images to run. + */ +struct signed_header_version { + uint32_t minor; + uint32_t major; + uint32_t epoch; +}; + +/* * Response to the connection establishment request. * * When responding to the very first packet of the upgrade sequence, the @@ -84,13 +95,17 @@ struct update_frame_header { */ struct first_response_pdu { uint32_t return_value; + + /* The below fields are present in versions 2 and up. */ uint32_t protocol_version; - union { - struct { - uint32_t backup_ro_offset; - uint32_t backup_rw_offset; - } vers3; - }; + + /* The below fields are present in versions 3 and up. */ + uint32_t backup_ro_offset; + uint32_t backup_rw_offset; + + /* The below fields are present in versions 4 and up. */ + /* Versions of the currently active RO and RW sections. */ + struct signed_header_version shv[2]; }; /* TODO: Handle this in upgrade_fw.c, not usb_upgrade.c */ diff --git a/extra/usb_updater/usb_updater.c b/extra/usb_updater/usb_updater.c index c813ede27e..ceb33cf674 100644 --- a/extra/usb_updater/usb_updater.c +++ b/extra/usb_updater/usb_updater.c @@ -22,12 +22,14 @@ #define __packed __attribute__((packed)) #endif -#include "misc_util.h" -#include "usb_descriptor.h" -#include "upgrade_fw.h" #include "config_chip.h" #include "board.h" + #include "compile_time_macros.h" +#include "misc_util.h" +#include "signed_header.h" +#include "upgrade_fw.h" +#include "usb_descriptor.h" #ifdef DEBUG #define debug printf @@ -103,10 +105,14 @@ * updates), or an error code, the same as in Version 1. The second 4 bytes * are the protocol version number (set to 2). * - * Version 3 is used over both USB and SPI. The response is 16 bytes in size. - * The first 4 bytes are the error code, the second 4 bytes are the protocol - * version (set to 3) and then 4 byte *offset* of the RO section followed by - * the 4 byte *offset* of the RW section. + * All versions above 2 behave the same over SPI and USB. + * + * Version 3 response is 16 bytes in size. The first 4 bytes are the error code + * the second 4 bytes are the protocol version (set to 3) and then 4 byte + * *offset* of the RO section followed by the 4 byte *offset* of the RW section. + * + * Version 4 response in addition to version 3 provides header revision fields + * for active RO and RW images running on the target. * * Once the connection is established, the image to be programmed into flash * is transferred to the CR50 in 1K PDUs. In versions 1 and 2 the address in @@ -126,6 +132,13 @@ #define FLASH_BASE 0x40000 +enum exit_values { + noop = 0, /* All up to date, no update needed. */ + all_updated = 1, /* Update completed, reboot required. */ + rw_updated = 2, /* RO was not updated, reboot required. */ + update_error = 3 /* Something went wrong. */ +}; + /* * Need to create an entire TPM PDU when upgrading over /dev/tpm0 and need to * have space to prepare the entire PDU. @@ -152,7 +165,20 @@ struct usb_endpoint { }; struct transfer_descriptor { - int update_ro; /* True if RO update is required. */ + /* + * Set to true for use in an upstart script. Do not reboot after + * transfer, and do not transfer RW if versions are the same. + * + * When using in development environment it is beneficial to transfer + * RW images with the same version, as they get started based on the + * header timestamp. + */ + uint32_t upstart_mode; + + /* + * offsets of RO and WR sections available for update (not currently + * active). + */ uint32_t ro_offset; uint32_t rw_offset; @@ -168,13 +194,13 @@ struct transfer_descriptor { static uint32_t protocol_version; static char *progname; -static char *short_opts = ":d:hrs"; +static char *short_opts = ":d:hsu"; static const struct option long_opts[] = { /* name hasarg *flag val */ {"device", 1, NULL, 'd'}, {"help", 0, NULL, 'h'}, - {"ro", 0, NULL, 'r'}, {"spi", 0, NULL, 's'}, + {"upstart", 0, NULL, 'u'}, {NULL, 0, NULL, 0}, }; @@ -257,12 +283,12 @@ static void shut_down(struct usb_endpoint *uep) { libusb_close(uep->devh); libusb_exit(NULL); - exit(1); + exit(update_error); } static void usage(int errs) { - printf("\nUsage: %s [options] ec.bin\n" + printf("\nUsage: %s [options] <binary image>\n" "\n" "This updates the Cr50 RW firmware over USB.\n" "The required argument is the full RO+RW image.\n" @@ -271,11 +297,12 @@ static void usage(int errs) "\n" " -d,--device VID:PID USB device (default %04x:%04x)\n" " -h,--help Show this message\n" - " -r,--ro Update RO section along with RW\n" " -s,--spi Use /dev/tmp0 (-d is ignored)\n" + " -u,--upstart " + "Upstart mode (strict header checks, no reboot)\n" "\n", progname, VID, PID); - exit(!!errs); + exit(errs ? update_error : noop); } /* Read file into buffer */ @@ -289,11 +316,11 @@ static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr) fp = fopen(filename, "rb"); if (!fp) { perror(filename); - exit(1); + exit(update_error); } if (fstat(fileno(fp), &st)) { perror("stat"); - exit(1); + exit(update_error); } len = st.st_size; @@ -301,12 +328,12 @@ static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr) data = malloc(len); if (!data) { perror("malloc"); - exit(1); + exit(update_error); } if (1 != fread(data, st.st_size, 1, fp)) { perror("fread"); - exit(1); + exit(update_error); } fclose(fp); @@ -340,7 +367,7 @@ static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen, &actual, 1000); if (r < 0) { USB_ERROR("libusb_bulk_transfer", r); - exit(1); + exit(update_error); } if (actual != outlen) { fprintf(stderr, "%s:%d, only sent %d/%d bytes\n", @@ -358,7 +385,7 @@ static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen, &actual, 1000); if (r < 0) { USB_ERROR("libusb_bulk_transfer", r); - exit(1); + exit(update_error); } if ((actual != inlen) && !allow_less) { fprintf(stderr, "%s:%d, only received %d/%d bytes\n", @@ -462,7 +489,7 @@ static void usb_findit(uint16_t vid, uint16_t pid, struct usb_endpoint *uep) r = libusb_init(NULL); if (r < 0) { USB_ERROR("libusb_init", r); - exit(1); + exit(update_error); } printf("open_device %04x:%04x\n", vid, pid); @@ -470,7 +497,7 @@ static void usb_findit(uint16_t vid, uint16_t pid, struct usb_endpoint *uep) uep->devh = libusb_open_device_with_vid_pid(NULL, vid, pid); if (!uep->devh) { fprintf(stderr, "can't find device\n"); - exit(1); + exit(update_error); } iface_num = find_interface(uep); @@ -543,7 +570,7 @@ static int transfer_block(struct usb_endpoint *uep, struct update_pdu *updu, if (reply) { fprintf(stderr, "error: status %#x\n", reply); - exit(1); + exit(update_error); } return 0; @@ -608,7 +635,7 @@ static void transfer_section(struct transfer_descriptor *td, fprintf(stderr, "Failed to transfer block, %zd to go\n", data_len); - exit(1); + exit(update_error); } } else { uint8_t error_code[4]; @@ -635,17 +662,17 @@ static void transfer_section(struct transfer_descriptor *td, fprintf(stderr, "Failed to trasfer block, %zd to go\n", data_len); - exit(1); + exit(update_error); } if (rxed_size != 1) { fprintf(stderr, "Unexpected return size %zd\n", rxed_size); - exit(1); + exit(update_error); } if (error_code[0]) { fprintf(stderr, "error %d\n", error_code[0]); - exit(1); + exit(update_error); } } data_len -= payload_size; @@ -654,6 +681,157 @@ static void transfer_section(struct transfer_descriptor *td, } } +/* + * Header versions retrieved from the target, RO at index 0 and RW at index + * 1. + */ +static struct signed_header_version target_shv[2]; + +/* + * Each RO or RW section of the new image can be in one of the following + * states. + */ +enum upgrade_status { + not_needed = 0, /* Version below or equal that on the target. */ + not_possible, /* + * RO is newer, but can't be transferred due to + * target RW shortcomings. + */ + needed /* + * This section needs to be transferred to the + * target. + */ +}; + +/* This array describes all four sections of the new image. */ +static struct { + const char *name; + uint32_t offset; + uint32_t size; + enum upgrade_status ustatus; + struct signed_header_version shv; +} sections[] = { + {"RO_A", CONFIG_RO_MEM_OFF, CONFIG_RO_SIZE}, + {"RW_A", CONFIG_RW_MEM_OFF, CONFIG_RW_SIZE}, + {"RO_B", CHIP_RO_B_MEM_OFF, CONFIG_RO_SIZE}, + {"RW_B", CONFIG_RW_B_MEM_OFF, CONFIG_RW_SIZE} +}; + +/* + * Scan the new image and retrieve versions of all four sections, two RO and + * two RW. + */ +static void fetch_header_versions(const void *image) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(sections); i++) { + const struct SignedHeader *h; + + h = (const struct SignedHeader *)((uintptr_t)image + + sections[i].offset); + sections[i].shv.epoch = h->epoch_; + sections[i].shv.major = h->major_; + sections[i].shv.minor = h->minor_; + } +} + + +/* Compare to signer headers and determine which one is newer. */ +static int a_newer_than_b(struct signed_header_version *a, + struct signed_header_version *b) +{ + uint32_t fields[][3] = { + {a->epoch, a->major, a->minor}, + {b->epoch, b->major, b->minor}, + }; + size_t i; + + /* + * Even though header version fields are 32 bits in size, we don't + * exepect any version field ever exceed say 1000. Anything in excess + * of 1000 should is considered zero. + * + * This would cover old images where one of the RO version fields is + * the number of git patches since last tag (and is in excess of + * 4000), and images where there is no code in a section (all fields + * are set to 0xffffffff). + */ + for (i = 0; i < ARRAY_SIZE(fields[0]); i++) { + uint32_t a_value; + uint32_t b_value; + + a_value = fields[0][i]; + b_value = fields[1][i]; + + if (a_value > 4000) + a_value = 0; + + if (b_value > 4000) + b_value = 0; + + if (a_value != b_value) + return a_value > b_value; + } + + return 0; /* All else being equal A is no newer than B. */ +} +/* + * Pick sections to transfer based on information retrieved from the target, + * the new image, and the protocol version the target is running. + */ +static void pick_sections(struct transfer_descriptor *td) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(sections); i++) { + uint32_t offset = sections[i].offset; + + if ((offset == CONFIG_RW_MEM_OFF) || + (offset == CONFIG_RW_B_MEM_OFF)) { + + /* Skip currently active section. */ + if (offset != td->rw_offset) + continue; + /* + * Ok, this would be the RW section to transfer to the + * device. Is it newer in the new image than the + * running RW section on the device? + * + * If not in 'upstart' mode - transfer even if + * versions are the same, timestamps could be + * different. + */ + + if (a_newer_than_b(§ions[i].shv, target_shv + 1) || + !td->upstart_mode) + sections[i].ustatus = needed; + continue; + } + + /* + * RO update not supported in versions below 3, another + * invocation will be required once the RW is updated to + * handle protocol 3 or above. + */ + if (protocol_version < 3) { + sections[i].ustatus = not_possible; + continue; + } + + /* Skip currently active section. */ + if (offset != td->ro_offset) + continue; + /* + * Ok, this would be the RO section to transfer to the device. + * Is it newer in the new image than the running RO section on + * the device? + */ + if (a_newer_than_b(§ions[i].shv, target_shv)) + sections[i].ustatus = needed; + } +} + static void setup_connection(struct transfer_descriptor *td) { size_t rxed_size; @@ -668,8 +846,8 @@ static void setup_connection(struct transfer_descriptor *td) uint32_t legacy_resp; } start_resp; - /* Send start/erase request */ - printf("erase\n"); + /* Send start request. */ + printf("start\n"); if (td->ep_type == usb_xfer) { struct update_pdu updu; @@ -683,7 +861,7 @@ static void setup_connection(struct transfer_descriptor *td) if (tpm_send_pkt(td->tpm_fd, 0, 0, NULL, 0, &start_resp, &rxed_size) < 0) { fprintf(stderr, "Failed to start transfer\n"); - exit(1); + exit(update_error); } } @@ -691,7 +869,7 @@ static void setup_connection(struct transfer_descriptor *td) if (td->ep_type != spi_xfer) { fprintf(stderr, "Unexpected response size %zd\n", rxed_size); - exit(1); + exit(update_error); } /* This is a protocol version one response. */ @@ -717,7 +895,20 @@ static void setup_connection(struct transfer_descriptor *td) } else { /* All newer protocols. */ td->rw_offset = be32toh - (start_resp.rpdu.vers3.backup_rw_offset); + (start_resp.rpdu.backup_rw_offset); + if (protocol_version > 3) { + size_t i; + + /* Running header versions are available. */ + for (i = 0; i < ARRAY_SIZE(target_shv); i++) { + target_shv[i].minor = be32toh + (start_resp.rpdu.shv[i].minor); + target_shv[i].major = be32toh + (start_resp.rpdu.shv[i].major); + target_shv[i].epoch = be32toh + (start_resp.rpdu.shv[i].epoch); + } + } } } @@ -726,41 +917,45 @@ static void setup_connection(struct transfer_descriptor *td) if (!error_code) { if (protocol_version > 2) { td->ro_offset = be32toh - (start_resp.rpdu.vers3.backup_ro_offset); + (start_resp.rpdu.backup_ro_offset); printf("Offsets: backup RO at %#x, backup RW at %#x\n", td->ro_offset, td->rw_offset); - return; } - if (!td->update_ro) - return; - - fprintf(stderr, "Target does not support RO updates\n"); - - } else { - fprintf(stderr, "Target reporting error %d\n", error_code); + pick_sections(td); + return; } + fprintf(stderr, "Target reporting error %d\n", error_code); if (td->ep_type == usb_xfer) shut_down(&td->uep); - exit(1); + exit(update_error); } -static void transfer_and_reboot(struct transfer_descriptor *td, - uint8_t *data, size_t data_len) +/* 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; setup_connection(td); - transfer_section(td, data + td->rw_offset, td->rw_offset, - CONFIG_RW_SIZE); + for (i = 0; i < ARRAY_SIZE(sections); i++) + if (sections[i].ustatus == needed) { + transfer_section(td, + data + sections[i].offset, + sections[i].offset, + sections[i].size); + num_txed_secitons++; + } - /* Transfer the RO part if requested. */ - if (td->update_ro) - transfer_section(td, data + td->ro_offset, td->ro_offset, - CONFIG_RO_SIZE); + if (!num_txed_secitons) { + printf("Nothing to do\n"); + return 0; + } printf("-------\nupdate complete\n"); - if (td->ep_type == usb_xfer) { + if ((td->ep_type == usb_xfer) && !td->upstart_mode) { uint32_t out; /* Send stop request, ignoring reply. */ @@ -776,6 +971,8 @@ static void transfer_and_reboot(struct transfer_descriptor *td, */ xfer(&td->uep, &out, sizeof(out), 0, 0); } + + return num_txed_secitons; } int main(int argc, char *argv[]) @@ -786,6 +983,9 @@ int main(int argc, char *argv[]) size_t data_len = 0; uint16_t vid = VID, pid = PID; int i; + size_t j; + int transferred_sections; + progname = strrchr(argv[0], '/'); if (progname) @@ -810,12 +1010,12 @@ int main(int argc, char *argv[]) case 'h': usage(errorcnt); break; - case 'r': - td.update_ro = 1; - break; case 's': td.ep_type = spi_xfer; break; + case 'u': + td.upstart_mode = 1; + break; case 0: /* auto-handled option */ break; case '?': @@ -832,7 +1032,7 @@ int main(int argc, char *argv[]) break; default: printf("Internal error at %s:%d\n", __FILE__, __LINE__); - exit(1); + exit(update_error); } } @@ -840,29 +1040,33 @@ int main(int argc, char *argv[]) usage(errorcnt); if (optind >= argc) { - fprintf(stderr, "\nERROR: Missing required ec.bin file\n\n"); + fprintf(stderr, + "\nERROR: Missing required <binary image>\n\n"); usage(1); } data = get_file_or_die(argv[optind], &data_len); - printf("read 0x%zx bytes from %s\n", data_len, argv[optind]); + printf("read %zd(%#zx) bytes from %s\n", + data_len, data_len, argv[optind]); if (data_len != CONFIG_FLASH_SIZE) { fprintf(stderr, "Image file is not %d bytes\n", CONFIG_FLASH_SIZE); - exit(1); + exit(update_error); } + fetch_header_versions(data); + if (td.ep_type == usb_xfer) { usb_findit(vid, pid, &td.uep); } else { td.tpm_fd = open("/dev/tpm0", O_RDWR); if (td.tpm_fd < 0) { perror("Could not open TPM"); - exit(1); + exit(update_error); } } - transfer_and_reboot(&td, data, data_len); + transferred_sections = transfer_and_reboot(&td, data, data_len); printf("bye\n"); free(data); @@ -871,5 +1075,19 @@ int main(int argc, char *argv[]) libusb_exit(NULL); } - return 0; + if (!transferred_sections) + return noop; + /* + * We should indicate if RO update was not done because of the + * insufficient RW version. + */ + for (j = 0; j < ARRAY_SIZE(sections); j++) + if (sections[j].ustatus == not_possible) { + /* This will allow scripting repeat attempts. */ + printf("Failed to update RO, run the command again\n"); + return rw_updated; + } + + printf("Image updated, reboot is needed\n"); + return all_updated; } |