| /* |
| * Copyright 2017 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <asm/byteorder.h> |
| #include <endian.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <libusb.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <fmap.h> |
| |
| #ifndef __packed |
| #define __packed __attribute__((packed)) |
| #endif |
| |
| #include "compile_time_macros.h" |
| #include "misc_util.h" |
| #include "usb_descriptor.h" |
| #include "update_fw.h" |
| #include "vb21_struct.h" |
| |
| #ifdef DEBUG |
| #define debug printf |
| #else |
| #define debug(fmt, args...) |
| #endif |
| |
| /* |
| * This file contains the source code of a Linux application used to update |
| * EC device firmware (common code only, gsctool takes care of cr50). |
| */ |
| |
| #define VID USB_VID_GOOGLE |
| #define PID 0x5022 |
| #define SUBCLASS USB_SUBCLASS_GOOGLE_UPDATE |
| #define PROTOCOL USB_PROTOCOL_GOOGLE_UPDATE |
| |
| 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. */ |
| }; |
| |
| struct usb_endpoint { |
| struct libusb_device_handle *devh; |
| uint8_t ep_num; |
| int chunk_len; |
| }; |
| |
| struct transfer_descriptor { |
| /* |
| * offsets of section available for update (not currently active). |
| */ |
| uint32_t offset; |
| |
| struct usb_endpoint uep; |
| }; |
| |
| /* Information about the target */ |
| static struct first_response_pdu targ; |
| |
| static uint16_t protocol_version; |
| static uint16_t header_type; |
| static char *progname; |
| static char *short_opts = "bd:efg:hjlnp:rsS:tuw"; |
| static const struct option long_opts[] = { |
| /* name hasarg *flag val */ |
| {"binvers", 1, NULL, 'b'}, |
| {"device", 1, NULL, 'd'}, |
| {"entropy", 0, NULL, 'e'}, |
| {"fwver", 0, NULL, 'f'}, |
| {"tp_debug", 1, NULL, 'g'}, |
| {"help", 0, NULL, 'h'}, |
| {"jump_to_rw", 0, NULL, 'j'}, |
| {"follow_log", 0, NULL, 'l'}, |
| {"no_reset", 0, NULL, 'n'}, |
| {"tp_update", 1, NULL, 'p'}, |
| {"reboot", 0, NULL, 'r'}, |
| {"stay_in_ro", 0, NULL, 's'}, |
| {"serial", 1, NULL, 'S'}, |
| {"tp_info", 0, NULL, 't'}, |
| {"unlock_rollback", 0, NULL, 'u'}, |
| {"unlock_rw", 0, NULL, 'w'}, |
| {}, |
| }; |
| |
| /* Release USB device and return error to the OS. */ |
| static void shut_down(struct usb_endpoint *uep) |
| { |
| libusb_close(uep->devh); |
| libusb_exit(NULL); |
| exit(update_error); |
| } |
| |
| static void usage(int errs) |
| { |
| printf("\nUsage: %s [options] <binary image>\n" |
| "\n" |
| "This updates EC firmware over USB (common code EC, no cr50).\n" |
| "The required argument is the full RO+RW image.\n" |
| "\n" |
| "Options:\n" |
| "\n" |
| " -b,--binvers Report versions of image's " |
| "RW and RO, do not update\n" |
| " -d,--device VID:PID USB device (default %04x:%04x)\n" |
| " -e,--entropy Add entropy to device secret\n" |
| " -f,--fwver Report running firmware versions.\n" |
| " -g,--tp_debug <hex data> Touchpad debug command\n" |
| " -h,--help Show this message\n" |
| " -j,--jump_to_rw Tell EC to jump to RW\n" |
| " -l,--follow_log Get console log\n" |
| " -p,--tp_update file Update touchpad FW\n" |
| " -r,--reboot Tell EC to reboot\n" |
| " -s,--stay_in_ro Tell EC to stay in RO\n" |
| " -S,--serial Device serial number\n" |
| " -t,--tp_info Get touchpad information\n" |
| " -u,--unlock_rollback Tell EC to unlock the rollback region\n" |
| " -w,--unlock_rw Tell EC to unlock the RW region\n" |
| "\n", progname, VID, PID); |
| |
| exit(errs ? update_error : noop); |
| } |
| |
| static void str2hex(const char *str, uint8_t *data, int *len) |
| { |
| int i; |
| int slen = strlen(str); |
| |
| if (slen/2 > *len) { |
| fprintf(stderr, "Hex string too long.\n"); |
| exit(update_error); |
| } |
| |
| if (slen % 2 != 0) { |
| fprintf(stderr, "Hex string length not a multiple of 2.\n"); |
| exit(update_error); |
| } |
| |
| for (i = 0, *len = 0; i < slen; i += 2, (*len)++) { |
| char *end; |
| char tmp[3]; |
| |
| tmp[0] = str[i]; |
| tmp[1] = str[i+1]; |
| tmp[2] = 0; |
| |
| data[*len] = strtol(tmp, &end, 16); |
| |
| if (*end != 0) { |
| fprintf(stderr, "Invalid hex string.\n"); |
| exit(update_error); |
| } |
| } |
| } |
| |
| static void hexdump(const uint8_t *data, int len) |
| { |
| int i; |
| |
| for (i = 0; i < len; i++) { |
| printf("%02x", data[i]); |
| if ((i % 16) == 15) |
| printf("\n"); |
| } |
| |
| if ((len % 16) != 0) |
| printf("\n"); |
| } |
| |
| static void dump_touchpad_info(const uint8_t *data, int len) |
| { |
| const struct touchpad_info *info = (const struct touchpad_info *)data; |
| |
| if (len != sizeof(struct touchpad_info)) { |
| fprintf(stderr, "Hex string length is not %zu", |
| sizeof(struct touchpad_info)); |
| hexdump(data, len); |
| return; |
| } |
| |
| printf("\n"); |
| printf("status: 0x%02x\n", info->status); |
| printf("vendor: 0x%04x\n", info->vendor); |
| printf("fw_address: 0x%08x\n", info->fw_address); |
| printf("fw_size: 0x%08x\n", info->fw_size); |
| |
| printf("allowed_fw_hash:\n"); |
| hexdump(info->allowed_fw_hash, sizeof(info->allowed_fw_hash)); |
| |
| switch (info->vendor) { |
| case 0x04f3: /* ELAN */ |
| case 0x0483: /* ST */ |
| printf("id: 0x%04x\n", info->elan.id); |
| printf("fw_version: 0x%04x\n", info->elan.fw_version); |
| printf("fw_fw_checksum: 0x%04x\n", info->elan.fw_checksum); |
| break; |
| default: |
| fprintf(stderr, "Unknown vendor, vendor specific data:\n"); |
| hexdump((const uint8_t *)&info->elan, sizeof(info->elan)); |
| break; |
| } |
| } |
| |
| /* Read file into buffer */ |
| static uint8_t *get_file_or_die(const char *filename, size_t *len_ptr) |
| { |
| FILE *fp; |
| struct stat st; |
| uint8_t *data; |
| size_t len; |
| |
| fp = fopen(filename, "rb"); |
| if (!fp) { |
| perror(filename); |
| exit(update_error); |
| } |
| if (fstat(fileno(fp), &st)) { |
| perror("stat"); |
| exit(update_error); |
| } |
| |
| len = st.st_size; |
| |
| data = malloc(len); |
| if (!data) { |
| perror("malloc"); |
| exit(update_error); |
| } |
| |
| if (fread(data, st.st_size, 1, fp) != 1) { |
| perror("fread"); |
| exit(update_error); |
| } |
| |
| fclose(fp); |
| |
| *len_ptr = len; |
| return data; |
| } |
| |
| #define USB_ERROR(m, r) \ |
| fprintf(stderr, "%s:%d, %s returned %d (%s)\n", __FILE__, __LINE__, \ |
| m, r, libusb_strerror(r)) |
| |
| /* |
| * Actual USB transfer function, the 'allow_less' flag indicates that the |
| * valid response could be shortef than allotted memory, the 'rxed_count' |
| * pointer, if provided along with 'allow_less' lets the caller know how mavy |
| * bytes were received. |
| */ |
| static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen, |
| void *inbuf, int inlen, int allow_less, |
| size_t *rxed_count) |
| { |
| |
| int r, actual; |
| |
| /* Send data out */ |
| if (outbuf && outlen) { |
| actual = 0; |
| r = libusb_bulk_transfer(uep->devh, uep->ep_num, |
| outbuf, outlen, |
| &actual, 2000); |
| if (r < 0) { |
| USB_ERROR("libusb_bulk_transfer", r); |
| exit(update_error); |
| } |
| if (actual != outlen) { |
| fprintf(stderr, "%s:%d, only sent %d/%d bytes\n", |
| __FILE__, __LINE__, actual, outlen); |
| shut_down(uep); |
| } |
| } |
| |
| /* Read reply back */ |
| if (inbuf && inlen) { |
| |
| actual = 0; |
| r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80, |
| inbuf, inlen, |
| &actual, 5000); |
| if (r < 0) { |
| USB_ERROR("libusb_bulk_transfer", r); |
| exit(update_error); |
| } |
| if ((actual != inlen) && !allow_less) { |
| fprintf(stderr, "%s:%d, only received %d/%d bytes\n", |
| __FILE__, __LINE__, actual, inlen); |
| hexdump(inbuf, actual); |
| shut_down(uep); |
| } |
| |
| if (rxed_count) |
| *rxed_count = actual; |
| } |
| } |
| |
| static void xfer(struct usb_endpoint *uep, void *outbuf, |
| size_t outlen, void *inbuf, size_t inlen, int allow_less) |
| { |
| do_xfer(uep, outbuf, outlen, inbuf, inlen, allow_less, NULL); |
| } |
| |
| /* Return 0 on error, since it's never gonna be EP 0 */ |
| static int find_endpoint(const struct libusb_interface_descriptor *iface, |
| struct usb_endpoint *uep) |
| { |
| const struct libusb_endpoint_descriptor *ep; |
| |
| if (iface->bInterfaceClass == 255 && |
| iface->bInterfaceSubClass == SUBCLASS && |
| iface->bInterfaceProtocol == PROTOCOL && |
| iface->bNumEndpoints) { |
| ep = &iface->endpoint[0]; |
| uep->ep_num = ep->bEndpointAddress & 0x7f; |
| uep->chunk_len = ep->wMaxPacketSize; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Return -1 on error */ |
| static int find_interface(struct usb_endpoint *uep) |
| { |
| int iface_num = -1; |
| int r, i, j; |
| struct libusb_device *dev; |
| struct libusb_config_descriptor *conf = 0; |
| const struct libusb_interface *iface0; |
| const struct libusb_interface_descriptor *iface; |
| |
| dev = libusb_get_device(uep->devh); |
| r = libusb_get_active_config_descriptor(dev, &conf); |
| if (r < 0) { |
| USB_ERROR("libusb_get_active_config_descriptor", r); |
| goto out; |
| } |
| |
| for (i = 0; i < conf->bNumInterfaces; i++) { |
| iface0 = &conf->interface[i]; |
| for (j = 0; j < iface0->num_altsetting; j++) { |
| iface = &iface0->altsetting[j]; |
| if (find_endpoint(iface, uep)) { |
| iface_num = i; |
| goto out; |
| } |
| } |
| } |
| |
| out: |
| libusb_free_config_descriptor(conf); |
| return iface_num; |
| } |
| |
| /* Returns true if parsed. */ |
| static int parse_vidpid(const char *input, uint16_t *vid_ptr, uint16_t *pid_ptr) |
| { |
| char *copy, *s, *e = 0; |
| |
| copy = strdup(input); |
| |
| s = strchr(copy, ':'); |
| if (!s) |
| return 0; |
| *s++ = '\0'; |
| |
| *vid_ptr = (uint16_t) strtoull(copy, &e, 16); |
| if (!*optarg || (e && *e)) |
| return 0; |
| |
| *pid_ptr = (uint16_t) strtoull(s, &e, 16); |
| if (!*optarg || (e && *e)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static libusb_device_handle *check_device(libusb_device *dev, |
| uint16_t vid, uint16_t pid, char *serialno) |
| { |
| struct libusb_device_descriptor desc; |
| libusb_device_handle *handle = NULL; |
| char sn[256]; |
| int ret; |
| int match = 1; |
| int snvalid = 0; |
| |
| ret = libusb_get_device_descriptor(dev, &desc); |
| if (ret < 0) |
| return NULL; |
| |
| ret = libusb_open(dev, &handle); |
| |
| if (ret != LIBUSB_SUCCESS) |
| return NULL; |
| |
| if (desc.iSerialNumber) { |
| ret = libusb_get_string_descriptor_ascii(handle, |
| desc.iSerialNumber, (unsigned char *)sn, sizeof(sn)); |
| if (ret > 0) |
| snvalid = 1; |
| } |
| |
| if (vid != 0 && vid != desc.idVendor) |
| match = 0; |
| if (pid != 0 && pid != desc.idProduct) |
| match = 0; |
| if (serialno != NULL && (!snvalid || strstr(sn, serialno) == NULL)) |
| match = 0; |
| |
| if (match) |
| return handle; |
| |
| libusb_close(handle); |
| return NULL; |
| } |
| |
| static void usb_findit(uint16_t vid, uint16_t pid, |
| char *serialno, struct usb_endpoint *uep) |
| { |
| int iface_num, r, i; |
| libusb_device **devs; |
| libusb_device_handle *devh = NULL; |
| ssize_t count; |
| |
| memset(uep, 0, sizeof(*uep)); |
| |
| r = libusb_init(NULL); |
| if (r < 0) { |
| USB_ERROR("libusb_init", r); |
| exit(update_error); |
| } |
| |
| count = libusb_get_device_list(NULL, &devs); |
| if (count < 0) |
| return; |
| |
| for (i = 0; devs[i]; i++) { |
| devh = check_device(devs[i], vid, pid, serialno); |
| if (devh) { |
| printf("Found device.\n"); |
| break; |
| } |
| } |
| |
| libusb_free_device_list(devs, 1); |
| |
| if (!devh) { |
| fprintf(stderr, "Can't find device\n"); |
| exit(update_error); |
| } |
| |
| uep->devh = devh; |
| |
| iface_num = find_interface(uep); |
| if (iface_num < 0) { |
| fprintf(stderr, "USB FW update not supported by that device\n"); |
| shut_down(uep); |
| } |
| if (!uep->chunk_len) { |
| fprintf(stderr, "wMaxPacketSize isn't valid\n"); |
| shut_down(uep); |
| } |
| |
| printf("found interface %d endpoint %d, chunk_len %d\n", |
| iface_num, uep->ep_num, uep->chunk_len); |
| |
| libusb_set_auto_detach_kernel_driver(uep->devh, 1); |
| r = libusb_claim_interface(uep->devh, iface_num); |
| if (r < 0) { |
| USB_ERROR("libusb_claim_interface", r); |
| shut_down(uep); |
| } |
| |
| printf("READY\n-------\n"); |
| } |
| |
| static int transfer_block(struct usb_endpoint *uep, |
| struct update_frame_header *ufh, |
| uint8_t *transfer_data_ptr, size_t payload_size) |
| { |
| size_t transfer_size; |
| uint32_t reply; |
| int actual; |
| int r; |
| |
| /* First send the header. */ |
| xfer(uep, ufh, sizeof(*ufh), NULL, 0, 0); |
| |
| /* Now send the block, chunk by chunk. */ |
| for (transfer_size = 0; transfer_size < payload_size;) { |
| int chunk_size; |
| |
| chunk_size = MIN(uep->chunk_len, payload_size - transfer_size); |
| xfer(uep, transfer_data_ptr, chunk_size, NULL, 0, 0); |
| transfer_data_ptr += chunk_size; |
| transfer_size += chunk_size; |
| } |
| |
| /* Now get the reply. */ |
| r = libusb_bulk_transfer(uep->devh, uep->ep_num | 0x80, |
| (void *) &reply, sizeof(reply), |
| &actual, 5000); |
| if (r) { |
| if (r == -7) { |
| fprintf(stderr, "Timeout!\n"); |
| return r; |
| } |
| USB_ERROR("libusb_bulk_transfer", r); |
| shut_down(uep); |
| } |
| |
| reply = *((uint8_t *)&reply); |
| if (reply) { |
| fprintf(stderr, "Error: status %#x\n", reply); |
| exit(update_error); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Transfer an image section (typically RW or RO). |
| * |
| * td - transfer descriptor to use to communicate with the target |
| * data_ptr - pointer at the section base in the image |
| * section_addr - address of the section in the target memory space |
| * data_len - section size |
| * smart_update - non-zero to enable the smart trailing of 0xff. |
| */ |
| static void transfer_section(struct transfer_descriptor *td, |
| uint8_t *data_ptr, |
| uint32_t section_addr, |
| size_t data_len, |
| uint8_t smart_update) |
| { |
| /* |
| * Actually, we can skip trailing chunks of 0xff, as the entire |
| * section space must be erased before the update is attempted. |
| * |
| * FIXME: We can be smarter than this and skip blocks within the image. |
| */ |
| if (smart_update) |
| while (data_len && (data_ptr[data_len - 1] == 0xff)) |
| data_len--; |
| |
| printf("sending 0x%zx bytes to %#x\n", data_len, section_addr); |
| while (data_len) { |
| size_t payload_size; |
| uint32_t block_base; |
| int max_retries; |
| |
| /* prepare the header to prepend to the block. */ |
| payload_size = MIN(data_len, targ.common.maximum_pdu_size); |
| |
| block_base = htobe32(section_addr); |
| |
| struct update_frame_header ufh; |
| |
| ufh.block_size = htobe32(payload_size + |
| sizeof(struct update_frame_header)); |
| ufh.cmd.block_base = block_base; |
| ufh.cmd.block_digest = 0; |
| for (max_retries = 10; max_retries; max_retries--) |
| if (!transfer_block(&td->uep, &ufh, |
| data_ptr, payload_size)) |
| break; |
| |
| if (!max_retries) { |
| fprintf(stderr, |
| "Failed to transfer block, %zd to go\n", |
| data_len); |
| exit(update_error); |
| } |
| data_len -= payload_size; |
| data_ptr += payload_size; |
| section_addr += payload_size; |
| } |
| } |
| |
| /* |
| * 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 sections of the new image. */ |
| static struct { |
| const char *name; |
| uint32_t offset; |
| uint32_t size; |
| enum upgrade_status ustatus; |
| char version[32]; |
| int32_t rollback; |
| uint32_t key_version; |
| } sections[] = { |
| {"RO"}, |
| {"RW"} |
| }; |
| |
| static const struct fmap_area *fmap_find_area_or_die(const struct fmap *fmap, |
| const char *name) |
| { |
| const struct fmap_area *fmaparea; |
| |
| fmaparea = fmap_find_area(fmap, name); |
| if (!fmaparea) { |
| fprintf(stderr, "Cannot find FMAP area %s\n", name); |
| exit(update_error); |
| } |
| |
| return fmaparea; |
| } |
| |
| /* |
| * Scan the new image and retrieve versions of all sections. |
| */ |
| static void fetch_header_versions(const uint8_t *image, size_t len) |
| { |
| const struct fmap *fmap; |
| const struct fmap_area *fmaparea; |
| long int offset; |
| size_t i; |
| |
| offset = fmap_find(image, len); |
| if (offset < 0) { |
| fprintf(stderr, "Cannot find FMAP in image\n"); |
| exit(update_error); |
| } |
| fmap = (const struct fmap *)(image+offset); |
| |
| /* FIXME: validate fmap struct more than this? */ |
| if (fmap->size != len) { |
| fprintf(stderr, "Mismatch between FMAP size and image size\n"); |
| exit(update_error); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(sections); i++) { |
| const char *fmap_name; |
| const char *fmap_fwid_name; |
| const char *fmap_rollback_name = NULL; |
| const char *fmap_key_name = NULL; |
| |
| if (!strcmp(sections[i].name, "RO")) { |
| fmap_name = "EC_RO"; |
| fmap_fwid_name = "RO_FRID"; |
| } else if (!strcmp(sections[i].name, "RW")) { |
| fmap_name = "EC_RW"; |
| fmap_fwid_name = "RW_FWID"; |
| fmap_rollback_name = "RW_RBVER"; |
| /* |
| * Key version comes from key RO (RW signature does not |
| * contain the key version. |
| */ |
| fmap_key_name = "KEY_RO"; |
| } else { |
| fprintf(stderr, "Invalid section name\n"); |
| exit(update_error); |
| } |
| |
| fmaparea = fmap_find_area_or_die(fmap, fmap_name); |
| |
| /* FIXME: endianness? */ |
| sections[i].offset = fmaparea->offset; |
| sections[i].size = fmaparea->size; |
| |
| fmaparea = fmap_find_area_or_die(fmap, fmap_fwid_name); |
| |
| if (fmaparea->size != sizeof(sections[i].version)) { |
| fprintf(stderr, "Invalid fwid size\n"); |
| exit(update_error); |
| } |
| memcpy(sections[i].version, image+fmaparea->offset, |
| fmaparea->size); |
| |
| sections[i].rollback = -1; |
| if (fmap_rollback_name) { |
| fmaparea = fmap_find_area(fmap, fmap_rollback_name); |
| if (fmaparea) |
| memcpy(§ions[i].rollback, |
| image+fmaparea->offset, |
| sizeof(sections[i].rollback)); |
| } |
| |
| sections[i].key_version = -1; |
| if (fmap_key_name) { |
| fmaparea = fmap_find_area(fmap, fmap_key_name); |
| if (fmaparea) { |
| const struct vb21_packed_key *key = |
| (const void *)(image+fmaparea->offset); |
| sections[i].key_version = key->key_version; |
| } |
| } |
| } |
| } |
| |
| static int show_headers_versions(const void *image) |
| { |
| size_t i; |
| |
| for (i = 0; i < ARRAY_SIZE(sections); i++) { |
| printf("%s off=%08x/%08x v=%.32s rb=%d kv=%d\n", |
| sections[i].name, sections[i].offset, sections[i].size, |
| sections[i].version, sections[i].rollback, |
| sections[i].key_version); |
| } |
| return 0; |
| } |
| |
| /* |
| * 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; |
| |
| /* Skip currently active section. */ |
| if (offset != td->offset) |
| continue; |
| |
| sections[i].ustatus = needed; |
| } |
| } |
| |
| static void setup_connection(struct transfer_descriptor *td) |
| { |
| size_t rxed_size; |
| size_t i; |
| uint32_t error_code; |
| |
| /* |
| * Need to be backwards compatible, communicate with targets running |
| * different protocol versions. |
| */ |
| union { |
| struct first_response_pdu rpdu; |
| uint32_t legacy_resp; |
| } start_resp; |
| |
| /* Send start request. */ |
| printf("start\n"); |
| |
| struct update_frame_header ufh; |
| uint8_t inbuf[td->uep.chunk_len]; |
| int actual = 0; |
| |
| /* Flush all data from endpoint to recover in case of error. */ |
| while (!libusb_bulk_transfer(td->uep.devh, |
| td->uep.ep_num | 0x80, |
| (void *)&inbuf, td->uep.chunk_len, |
| &actual, 10)) { |
| printf("flush\n"); |
| } |
| |
| memset(&ufh, 0, sizeof(ufh)); |
| ufh.block_size = htobe32(sizeof(ufh)); |
| do_xfer(&td->uep, &ufh, sizeof(ufh), &start_resp, |
| sizeof(start_resp), 1, &rxed_size); |
| |
| /* We got something. Check for errors in response */ |
| if (rxed_size < 8) { |
| fprintf(stderr, "Unexpected response size %zd: ", rxed_size); |
| for (i = 0; i < rxed_size; i++) |
| fprintf(stderr, " %02x", ((uint8_t *)&start_resp)[i]); |
| fprintf(stderr, "\n"); |
| exit(update_error); |
| } |
| |
| protocol_version = be16toh(start_resp.rpdu.protocol_version); |
| if (protocol_version < 5 || protocol_version > 6) { |
| fprintf(stderr, "Unsupported protocol version %d\n", |
| protocol_version); |
| exit(update_error); |
| } |
| |
| header_type = be16toh(start_resp.rpdu.header_type); |
| |
| printf("target running protocol version %d (type %d)\n", |
| protocol_version, header_type); |
| if (header_type != UPDATE_HEADER_TYPE_COMMON) { |
| fprintf(stderr, "Unsupported header type %d\n", |
| header_type); |
| exit(update_error); |
| } |
| |
| error_code = be32toh(start_resp.rpdu.return_value); |
| |
| if (error_code) { |
| fprintf(stderr, "Target reporting error %d\n", error_code); |
| shut_down(&td->uep); |
| exit(update_error); |
| } |
| |
| td->offset = be32toh(start_resp.rpdu.common.offset); |
| memcpy(targ.common.version, start_resp.rpdu.common.version, |
| sizeof(start_resp.rpdu.common.version)); |
| targ.common.maximum_pdu_size = |
| be32toh(start_resp.rpdu.common.maximum_pdu_size); |
| targ.common.flash_protection = |
| be32toh(start_resp.rpdu.common.flash_protection); |
| targ.common.min_rollback = be32toh(start_resp.rpdu.common.min_rollback); |
| targ.common.key_version = be32toh(start_resp.rpdu.common.key_version); |
| |
| printf("maximum PDU size: %d\n", targ.common.maximum_pdu_size); |
| printf("Flash protection status: %04x\n", targ.common.flash_protection); |
| printf("version: %32s\n", targ.common.version); |
| printf("key_version: %d\n", targ.common.key_version); |
| printf("min_rollback: %d\n", targ.common.min_rollback); |
| printf("offset: writable at %#x\n", td->offset); |
| |
| pick_sections(td); |
| } |
| |
| /* |
| * 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, |
| int allow_less) |
| { |
| struct update_frame_header *ufh; |
| uint16_t *frame_ptr; |
| size_t usb_msg_size; |
| |
| 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_digest = 0; |
| ufh->cmd.block_base = htobe32(UPDATE_EXTRA_CMD); |
| frame_ptr = (uint16_t *)(ufh + 1); |
| *frame_ptr = htobe16(subcommand); |
| |
| if (body_size) |
| memcpy(frame_ptr + 1, cmd_body, body_size); |
| |
| xfer(uep, ufh, usb_msg_size, resp, resp_size ? *resp_size : 0, |
| allow_less); |
| |
| 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(UPDATE_DONE); |
| xfer(uep, &out, sizeof(out), &out, 1, 0); |
| } |
| |
| static void send_subcommand(struct transfer_descriptor *td, uint16_t subcommand, |
| void *cmd_body, size_t body_size, |
| uint8_t *response, size_t response_size) |
| { |
| send_done(&td->uep); |
| |
| ext_cmd_over_usb(&td->uep, subcommand, |
| cmd_body, body_size, |
| response, &response_size, 0); |
| printf("sent command %x, resp %x\n", subcommand, response[0]); |
| } |
| |
| /* Returns number of successfully transmitted image sections. */ |
| static int transfer_image(struct transfer_descriptor *td, |
| uint8_t *data, size_t data_len) |
| { |
| size_t i; |
| int num_txed_sections = 0; |
| |
| 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, 1); |
| num_txed_sections++; |
| } |
| |
| /* |
| * Move USB receiver sate machine to idle state so that vendor |
| * commands can be processed later, if any. |
| */ |
| send_done(&td->uep); |
| |
| if (!num_txed_sections) |
| printf("nothing to do\n"); |
| else |
| printf("-------\nupdate complete\n"); |
| return num_txed_sections; |
| } |
| |
| static void generate_reset_request(struct transfer_descriptor *td) |
| { |
| size_t response_size; |
| uint8_t response; |
| uint16_t subcommand; |
| uint8_t command_body[2]; /* Max command body size. */ |
| size_t command_body_size; |
| |
| if (protocol_version < 6) { |
| /* |
| * Send a second stop request, which should reboot |
| * without replying. |
| */ |
| send_done(&td->uep); |
| /* Nothing we can do over /dev/tpm0 running versions below 6. */ |
| return; |
| } |
| |
| /* |
| * If the user explicitly wants it, request post reset instead of |
| * immediate reset. In this case next time the target reboots, the h1 |
| * will reboot as well, and will consider running the uploaded code. |
| * |
| * In case target RW version is 19 or above, to reset the target the |
| * host is supposed to send the command to enable the uploaded image |
| * disabled by default. |
| * |
| * Otherwise the immediate reset command would suffice. |
| */ |
| /* Most common case. */ |
| command_body_size = 0; |
| response_size = 1; |
| subcommand = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; |
| ext_cmd_over_usb(&td->uep, subcommand, |
| command_body, command_body_size, |
| &response, &response_size, 0); |
| |
| printf("reboot not triggered\n"); |
| } |
| |
| static void get_random(uint8_t *data, int len) |
| { |
| FILE *fp; |
| int i = 0; |
| |
| fp = fopen("/dev/random", "rb"); |
| if (!fp) { |
| perror("Can't open /dev/random"); |
| exit(update_error); |
| } |
| |
| while (i < len) { |
| int ret = fread(data+i, len-i, 1, fp); |
| |
| if (ret < 0) { |
| perror("fread"); |
| exit(update_error); |
| } |
| |
| i += ret; |
| } |
| |
| fclose(fp); |
| } |
| |
| static void read_console(struct transfer_descriptor *td) |
| { |
| uint8_t payload[] = { 0x1 }; |
| uint8_t response[64]; |
| size_t response_size = 64; |
| struct timespec sleep_duration = { /* 100 ms */ |
| .tv_sec = 0, |
| .tv_nsec = 100l * 1000l * 1000l, |
| }; |
| |
| send_done(&td->uep); |
| |
| printf("\n"); |
| while (1) { |
| response_size = 1; |
| ext_cmd_over_usb(&td->uep, |
| UPDATE_EXTRA_CMD_CONSOLE_READ_INIT, |
| NULL, 0, |
| response, &response_size, 0); |
| |
| while (1) { |
| response_size = 64; |
| ext_cmd_over_usb(&td->uep, |
| UPDATE_EXTRA_CMD_CONSOLE_READ_NEXT, |
| payload, sizeof(payload), |
| response, &response_size, 1); |
| if (response[0] == 0) |
| break; |
| /* make sure it's null-terminated. */ |
| response[response_size - 1] = 0; |
| printf("%s", (const char *)response); |
| } |
| nanosleep(&sleep_duration, NULL); |
| } |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct transfer_descriptor td; |
| int errorcnt; |
| uint8_t *data = 0; |
| size_t data_len = 0; |
| uint16_t vid = VID, pid = PID; |
| char *serialno = NULL; |
| int i; |
| size_t j; |
| int transferred_sections = 0; |
| int binary_vers = 0; |
| int show_fw_ver = 0; |
| int no_reset_request = 0; |
| int touchpad_update = 0; |
| int extra_command = -1; |
| uint8_t extra_command_data[50]; |
| int extra_command_data_len = 0; |
| uint8_t extra_command_answer[64]; |
| int extra_command_answer_len = 1; |
| |
| progname = strrchr(argv[0], '/'); |
| if (progname) |
| progname++; |
| else |
| progname = argv[0]; |
| |
| /* Usb transfer - default mode. */ |
| memset(&td, 0, sizeof(td)); |
| |
| errorcnt = 0; |
| opterr = 0; /* quiet, you */ |
| while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { |
| switch (i) { |
| case 'b': |
| binary_vers = 1; |
| break; |
| case 'd': |
| if (!parse_vidpid(optarg, &vid, &pid)) { |
| printf("Invalid argument: \"%s\"\n", optarg); |
| errorcnt++; |
| } |
| break; |
| case 'e': |
| get_random(extra_command_data, 32); |
| extra_command_data_len = 32; |
| extra_command = UPDATE_EXTRA_CMD_INJECT_ENTROPY; |
| break; |
| case 'f': |
| show_fw_ver = 1; |
| break; |
| case 'g': |
| extra_command = UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG; |
| /* Maximum length. */ |
| extra_command_data_len = 50; |
| str2hex(optarg, |
| extra_command_data, &extra_command_data_len); |
| hexdump(extra_command_data, extra_command_data_len); |
| extra_command_answer_len = 64; |
| break; |
| case 'h': |
| usage(errorcnt); |
| break; |
| case 'j': |
| extra_command = UPDATE_EXTRA_CMD_JUMP_TO_RW; |
| break; |
| case 'l': |
| extra_command = UPDATE_EXTRA_CMD_CONSOLE_READ_INIT; |
| break; |
| case 'n': |
| no_reset_request = 1; |
| break; |
| case 'p': |
| touchpad_update = 1; |
| |
| data = get_file_or_die(optarg, &data_len); |
| printf("read %zd(%#zx) bytes from %s\n", |
| data_len, data_len, argv[optind]); |
| |
| break; |
| case 'r': |
| extra_command = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; |
| break; |
| case 's': |
| extra_command = UPDATE_EXTRA_CMD_STAY_IN_RO; |
| break; |
| case 'S': |
| serialno = optarg; |
| break; |
| case 't': |
| extra_command = UPDATE_EXTRA_CMD_TOUCHPAD_INFO; |
| extra_command_answer_len = |
| sizeof(struct touchpad_info); |
| break; |
| case 'u': |
| extra_command = UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK; |
| break; |
| case 'w': |
| extra_command = UPDATE_EXTRA_CMD_UNLOCK_RW; |
| break; |
| case 0: /* auto-handled option */ |
| break; |
| case '?': |
| if (optopt) |
| printf("Unrecognized option: -%c\n", optopt); |
| else |
| printf("Unrecognized option: %s\n", |
| argv[optind - 1]); |
| errorcnt++; |
| break; |
| case ':': |
| printf("Missing argument to %s\n", argv[optind - 1]); |
| errorcnt++; |
| break; |
| default: |
| printf("Internal error at %s:%d\n", __FILE__, __LINE__); |
| exit(update_error); |
| } |
| } |
| |
| if (errorcnt) |
| usage(errorcnt); |
| |
| if (!show_fw_ver && extra_command == -1 && !touchpad_update) { |
| if (optind >= argc) { |
| fprintf(stderr, |
| "\nERROR: Missing required <binary image>\n\n"); |
| usage(1); |
| } |
| |
| data = get_file_or_die(argv[optind], &data_len); |
| printf("read %zd(%#zx) bytes from %s\n", |
| data_len, data_len, argv[optind]); |
| |
| fetch_header_versions(data, data_len); |
| |
| if (binary_vers) |
| exit(show_headers_versions(data)); |
| } else { |
| if (optind < argc) |
| printf("Ignoring binary image %s\n", argv[optind]); |
| } |
| |
| usb_findit(vid, pid, serialno, &td.uep); |
| |
| setup_connection(&td); |
| |
| if (show_fw_ver) { |
| printf("Current versions:\n"); |
| printf("Writable %32s\n", targ.common.version); |
| } |
| |
| if (data) { |
| if (touchpad_update) { |
| transfer_section(&td, |
| data, |
| 0x80000000, |
| data_len, 0); |
| free(data); |
| |
| send_done(&td.uep); |
| } else { |
| transferred_sections = transfer_image(&td, |
| data, data_len); |
| free(data); |
| |
| if (transferred_sections && !no_reset_request) |
| generate_reset_request(&td); |
| } |
| } else if (extra_command == UPDATE_EXTRA_CMD_CONSOLE_READ_INIT) { |
| read_console(&td); |
| } else if (extra_command > -1) { |
| send_subcommand(&td, extra_command, |
| extra_command_data, extra_command_data_len, |
| extra_command_answer, extra_command_answer_len); |
| |
| switch (extra_command) { |
| case UPDATE_EXTRA_CMD_TOUCHPAD_INFO: |
| dump_touchpad_info(extra_command_answer, |
| extra_command_answer_len); |
| break; |
| case UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG: |
| hexdump(extra_command_answer, extra_command_answer_len); |
| break; |
| } |
| } |
| |
| libusb_close(td.uep.devh); |
| libusb_exit(NULL); |
| |
| 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\n"); |
| return all_updated; |
| } |