blob: e31770b40e2830704a3f187cd151981cd9efa6fb [file] [log] [blame]
/*
* 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 <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:hjnp: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'},
{"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"
" -f,--fwver Report running firmware versions.\n"
" -g,--tp_debug <hex data> Touchpad debug command\n"
" -h,--help Show this message\n"
" -e,--entropy Add entropy to device secret\n"
" -j,--jump_to_rw Tell EC to jump to RW\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, 1000);
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, 1000);
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)
{
do_xfer(uep, outbuf, outlen, inbuf, inlen, 0, 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) strtoul(copy, &e, 16);
if (!*optarg || (e && *e))
return 0;
*pid_ptr = (uint16_t) strtoul(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);
/* 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);
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, 1000);
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(&sections[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)
{
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);
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);
}
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);
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);
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);
}
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 '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 'e':
get_random(extra_command_data, 32);
extra_command_data_len = 32;
extra_command = UPDATE_EXTRA_CMD_INJECT_ENTROPY;
break;
case 'j':
extra_command = UPDATE_EXTRA_CMD_JUMP_TO_RW;
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 > -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;
}