2987 lines
76 KiB
C
2987 lines
76 KiB
C
/*
|
|
* Copyright 2015 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 <ctype.h>
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <libusb.h>
|
|
#include <openssl/sha.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "ccd_config.h"
|
|
#include "compile_time_macros.h"
|
|
#include "flash_log.h"
|
|
#include "generated_version.h"
|
|
#include "gsctool.h"
|
|
#include "misc_util.h"
|
|
#include "signed_header.h"
|
|
#include "tpm_registers.h"
|
|
#include "tpm_vendor_cmds.h"
|
|
#include "upgrade_fw.h"
|
|
#include "usb_descriptor.h"
|
|
#include "verify_ro.h"
|
|
|
|
/*
|
|
* This file contains the source code of a Linux application used to update
|
|
* CR50 device firmware.
|
|
*
|
|
* The CR50 firmware image consists of multiple sections, of interest to this
|
|
* app are the RO and RW code sections, two of each. When firmware update
|
|
* session is established, the CR50 device reports locations of backup RW and RO
|
|
* sections (those not used by the device at the time of transfer).
|
|
*
|
|
* Based on this information this app carves out the appropriate sections form
|
|
* the full CR50 firmware binary image and sends them to the device for
|
|
* programming into flash. Once the new sections are programmed and the device
|
|
* is restarted, the new RO and RW are used if they pass verification and are
|
|
* logically newer than the existing sections.
|
|
*
|
|
* There are two ways to communicate with the CR50 device: USB and /dev/tpm0
|
|
* (when this app is running on a chromebook with the CR50 device). Originally
|
|
* different protocols were used to communicate over different channels,
|
|
* starting with version 3 the same protocol is used.
|
|
*
|
|
* This app provides backwards compatibility to ensure that earlier CR50
|
|
* devices still can be updated.
|
|
*
|
|
*
|
|
* The host (either a local AP or a workstation) is the master of the firmware
|
|
* update protocol, it sends data to the cr50 device, which proceeses it and
|
|
* responds.
|
|
*
|
|
* The encapsultation format is different between the /dev/tpm0 and USB cases:
|
|
*
|
|
* 4 bytes 4 bytes 4 bytes variable size
|
|
* +-----------+--------------+---------------+----------~~--------------+
|
|
* + total size| block digest | dest address | data |
|
|
* +-----------+--------------+---------------+----------~~--------------+
|
|
* \ \ /
|
|
* \ \ /
|
|
* \ +----- FW update PDU sent over /dev/tpm0 -----------+
|
|
* \ /
|
|
* +--------- USB frame, requires total size field ------------+
|
|
*
|
|
* The update protocol data unints (PDUs) are passed over /dev/tpm0, the
|
|
* encapsulation includes integritiy verification and destination address of
|
|
* the data (more of this later). /dev/tpm0 transactions pretty much do not
|
|
* have size limits, whereas the USB data is sent in chunks of the size
|
|
* determined when the USB connestion is set up. This is why USB requires an
|
|
* additional encapsulation into frames to communicate the PDU size to the
|
|
* client side so that the PDU can be reassembled before passing to the
|
|
* programming function.
|
|
*
|
|
* In general, the protocol consists of two phases: connection establishment
|
|
* and actual image transfer.
|
|
*
|
|
* The very first PDU of the transfer session is used to establish the
|
|
* connection. The first PDU does not have any data, and the dest. address
|
|
* field is set to zero. Receiving such a PDU signals the programming function
|
|
* that the host intends to transfer a new image.
|
|
*
|
|
* The response to the first PDU varies depending on the protocol version.
|
|
*
|
|
* Note that protocol versions before 5 are described here for completeness,
|
|
* but are not supported any more by this utility.
|
|
*
|
|
* Version 1 is used over /dev/tpm0. The response is either 4 or 1 bytes in
|
|
* size. The 4 byte response is the *base address* of the backup RW section,
|
|
* no support for RO updates. The one byte response is an error indication,
|
|
* possibly reporting flash erase failure, command format error, etc.
|
|
*
|
|
* Version 2 is used over USB. The response is 8 bytes in size. The first four
|
|
* bytes are either the *base address* of the backup RW section (still no RO
|
|
* updates), or an error code, the same as in Version 1. The second 4 bytes
|
|
* are the protocol version number (set to 2).
|
|
*
|
|
* All versions above 2 behave the same over /dev/tpm0 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
|
|
* the header is the absolute address to place the block to, in version 3 and
|
|
* later it is the offset into the flash.
|
|
*
|
|
* 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 vendor 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 */
|
|
#define VID USB_VID_GOOGLE
|
|
#define PID CONFIG_USB_PID
|
|
#define SUBCLASS USB_SUBCLASS_GOOGLE_CR50
|
|
#define PROTOCOL USB_PROTOCOL_GOOGLE_CR50_NON_HC_FW_UPDATE
|
|
|
|
/*
|
|
* Need to create an entire TPM PDU when upgrading over /dev/tpm0 and need to
|
|
* have space to prepare the entire PDU.
|
|
*/
|
|
struct upgrade_pkt {
|
|
__be16 tag;
|
|
__be32 length;
|
|
__be32 ordinal;
|
|
__be16 subcmd;
|
|
union {
|
|
/*
|
|
* Upgrade PDUs as opposed to all other vendor and extension
|
|
* commands include two additional fields in the header.
|
|
*/
|
|
struct {
|
|
__be32 digest;
|
|
__be32 address;
|
|
char data[0];
|
|
} upgrade;
|
|
struct {
|
|
char data[0];
|
|
} command;
|
|
};
|
|
} __packed;
|
|
|
|
/*
|
|
* Structure used to simplify mapping command line options into Boolean
|
|
* variables. If an option is present, the corresponding integer value is set
|
|
* to 1.
|
|
*/
|
|
struct options_map {
|
|
char opt;
|
|
int *flag;
|
|
};
|
|
|
|
/*
|
|
* Structure used to combine option description used by getopt_long() and help
|
|
* text for the option.
|
|
*/
|
|
struct option_container {
|
|
struct option opt;
|
|
const char *help_text;
|
|
};
|
|
|
|
/*
|
|
* This by far exceeds the largest vendor command response size we ever
|
|
* expect.
|
|
*/
|
|
#define MAX_BUF_SIZE 500
|
|
|
|
/*
|
|
* Max. length of the board ID string representation.
|
|
*
|
|
* Board ID is either a 4-character ASCII alphanumeric string or an 8-digit
|
|
* hex.
|
|
*/
|
|
#define MAX_BOARD_ID_LENGTH 9
|
|
|
|
/*
|
|
* Length, in bytes, of the SN Bits serial number bits.
|
|
*/
|
|
#define SN_BITS_SIZE (96 >> 3)
|
|
|
|
/*
|
|
* Max. length of FW version in the format of <epoch>.<major>.<minor>
|
|
* (3 uint32_t string representation + 2 separators + NULL terminator).
|
|
*/
|
|
#define MAX_FW_VER_LENGTH 33
|
|
|
|
static int verbose_mode;
|
|
static uint32_t protocol_version;
|
|
static char *progname;
|
|
|
|
/*
|
|
* List of command line options, ***sorted by the short form***.
|
|
*
|
|
* The help_text field does not include the short and long option strings,
|
|
* they are retrieved from the opt structure. In case the help text needs to
|
|
* have something printed immediately after the option strings (for example,
|
|
* an optional parameter), it should be included in the beginning of help_text
|
|
* string separated by the % character.
|
|
*
|
|
* usage() function which prints out the help message will concatenate the
|
|
* short and long options and the optional parameter, if present, and then
|
|
* print the rest of the text message at a fixed indentation.
|
|
*/
|
|
static const struct option_container cmd_line_options[] = {
|
|
/* name has_arg *flag val */
|
|
{{"any", no_argument, NULL, 'a'},
|
|
"Try any interfaces to find Cr50"
|
|
" (-d, -s, -t are all ignored)"},
|
|
{{"background_update_supported", no_argument, NULL, 'B'},
|
|
"Force background update mode (relevant"
|
|
" only when interacting"
|
|
" with Cr50 versions before 0.0.19)"
|
|
},
|
|
{{"binvers", no_argument, NULL, 'b'},
|
|
"Report versions of Cr50 image's "
|
|
"RW and RO headers, do not update"},
|
|
{{"corrupt", no_argument, NULL, 'c'},
|
|
"Corrupt the inactive rw"},
|
|
{{"device", required_argument, NULL, 'd'},
|
|
" VID:PID%USB device (default 18d1:5014)"},
|
|
{{"endorsement_seed", optional_argument, NULL, 'e'},
|
|
"[state]%get/set the endorsement key seed"},
|
|
{{"fwver", no_argument, NULL, 'f'},
|
|
"Report running Cr50 firmware versions"},
|
|
{{"factory", required_argument, NULL, 'F'},
|
|
"[enable|disable]%Control factory mode"},
|
|
{{"help", no_argument, NULL, 'h'},
|
|
"Show this message"},
|
|
{{"ccd_info", no_argument, NULL, 'I'},
|
|
"Get information about CCD state"},
|
|
{{"board_id", optional_argument, NULL, 'i'},
|
|
"[ID[:FLAGS]]%Get or set Info1 board ID fields. ID could be 32 bit "
|
|
"hex or 4 character string."},
|
|
{{"ccd_lock", no_argument, NULL, 'k'},
|
|
"Lock CCD"},
|
|
{{"flog", optional_argument, NULL, 'L'},
|
|
"[prev entry]%Retrieve contents of the flash log"
|
|
" (newer than <prev entry> if specified)"},
|
|
{{"machine", no_argument, NULL, 'M'},
|
|
"Output in a machine-friendly way. "
|
|
"Effective with -b, -f, -i, and -O."},
|
|
{{"tpm_mode", optional_argument, NULL, 'm'},
|
|
"[enable|disable]%Change or query tpm_mode"},
|
|
{{"serial", required_argument, NULL, 'n'},
|
|
"Cr50 CCD serial number"},
|
|
{{"openbox_rma", required_argument, NULL, 'O'},
|
|
"<desc_file>%Verify other device's RO integrity using information "
|
|
"provided in <desc file>"},
|
|
{{"ccd_open", no_argument, NULL, 'o'},
|
|
"Start CCD open sequence"},
|
|
{{"password", no_argument, NULL, 'P'},
|
|
"Set or clear CCD password. Use 'clear:<cur password>' to clear it"},
|
|
{{"post_reset", no_argument, NULL, 'p'},
|
|
"Request post reset after transfer"},
|
|
{{"sn_rma_inc", required_argument, NULL, 'R'},
|
|
"RMA_INC%Increment SN RMA count by RMA_INC. RMA_INC should be 0-7."},
|
|
{{"rma_auth", optional_argument, NULL, 'r'},
|
|
"[auth_code]%Request RMA challenge, process "
|
|
"RMA authentication code"},
|
|
{{"sn_bits", required_argument, NULL, 'S'},
|
|
"SN_BITS%Set Info1 SN bits fields. SN_BITS should be 96 bit hex."},
|
|
{{"systemdev", no_argument, NULL, 's'},
|
|
"Use /dev/tpm0 (-d is ignored)"},
|
|
{{"tstamp", optional_argument, NULL, 'T'},
|
|
"[<tstamp>]%Get or set flash log timestamp base"},
|
|
{{"trunks_send", no_argument, NULL, 't'},
|
|
"Use `trunks_send --raw' (-d is ignored)"},
|
|
{{"ccd_unlock", no_argument, NULL, 'U'},
|
|
"Start CCD unlock sequence"},
|
|
{{"upstart", no_argument, NULL, 'u'},
|
|
"Upstart mode (strict header checks)"},
|
|
{{"verbose", no_argument, NULL, 'V'},
|
|
"Enable debug messages"},
|
|
{{"version", no_argument, NULL, 'v'},
|
|
"Report this utility version"},
|
|
{{"wp", no_argument, NULL, 'w'},
|
|
"Get the current wp setting"}
|
|
};
|
|
|
|
/* Helper to print debug messages when verbose flag is specified. */
|
|
static void debug(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (verbose_mode) {
|
|
va_start(args, fmt);
|
|
vprintf(fmt, args);
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
/* Helpers to convert between binary and hex ascii and back. */
|
|
static char to_hexascii(uint8_t c)
|
|
{
|
|
if (c <= 9)
|
|
return '0' + c;
|
|
return 'a' + c - 10;
|
|
}
|
|
|
|
static int from_hexascii(char c)
|
|
{
|
|
/* convert to lower case. */
|
|
c = tolower(c);
|
|
|
|
if (c < '0' || c > 'f' || ((c > '9') && (c < 'a')))
|
|
return -1; /* Not an ascii character. */
|
|
|
|
if (c <= '9')
|
|
return c - '0';
|
|
|
|
return c - 'a' + 10;
|
|
}
|
|
|
|
/* Functions to communicate with the TPM over the trunks_send --raw channel. */
|
|
|
|
/* File handle to share between write and read sides. */
|
|
static FILE *tpm_output;
|
|
static int ts_write(const void *out, size_t len)
|
|
{
|
|
const char *cmd_head = "PATH=\"${PATH}:/usr/sbin\" trunks_send --raw ";
|
|
size_t head_size = strlen(cmd_head);
|
|
char full_command[head_size + 2 * len + 1];
|
|
size_t i;
|
|
|
|
strcpy(full_command, cmd_head);
|
|
/*
|
|
* Need to convert binary input into hex ascii output to pass to the
|
|
* trunks_send command.
|
|
*/
|
|
for (i = 0; i < len; i++) {
|
|
uint8_t c = ((const uint8_t *)out)[i];
|
|
|
|
full_command[head_size + 2 * i] = to_hexascii(c >> 4);
|
|
full_command[head_size + 2 * i + 1] = to_hexascii(c & 0xf);
|
|
}
|
|
|
|
/* Make it a proper zero terminated string. */
|
|
full_command[sizeof(full_command) - 1] = 0;
|
|
debug("cmd: %s\n", full_command);
|
|
tpm_output = popen(full_command, "r");
|
|
if (tpm_output)
|
|
return len;
|
|
|
|
fprintf(stderr, "Error: failed to launch trunks_send --raw\n");
|
|
return -1;
|
|
}
|
|
|
|
static int ts_read(void *buf, size_t max_rx_size)
|
|
{
|
|
int i;
|
|
int pclose_rv;
|
|
int rv;
|
|
char response[max_rx_size * 2];
|
|
|
|
if (!tpm_output) {
|
|
fprintf(stderr, "Error: attempt to read empty output\n");
|
|
return -1;
|
|
}
|
|
|
|
rv = fread(response, 1, sizeof(response), tpm_output);
|
|
if (rv > 0)
|
|
rv -= 1; /* Discard the \n character added by trunks_send. */
|
|
|
|
debug("response of size %d, max rx size %zd: %s\n",
|
|
rv, max_rx_size, response);
|
|
|
|
pclose_rv = pclose(tpm_output);
|
|
if (pclose_rv < 0) {
|
|
fprintf(stderr,
|
|
"Error: pclose failed: error %d (%s)\n",
|
|
errno, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
tpm_output = NULL;
|
|
|
|
if (rv & 1) {
|
|
fprintf(stderr,
|
|
"Error: trunks_send returned odd number of bytes: %s\n",
|
|
response);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < rv/2; i++) {
|
|
uint8_t byte;
|
|
char c;
|
|
int nibble;
|
|
|
|
c = response[2 * i];
|
|
nibble = from_hexascii(c);
|
|
if (nibble < 0) {
|
|
fprintf(stderr, "Error: "
|
|
"trunks_send returned non hex character %c\n",
|
|
c);
|
|
return -1;
|
|
}
|
|
byte = nibble << 4;
|
|
|
|
c = response[2 * i + 1];
|
|
nibble = from_hexascii(c);
|
|
if (nibble < 0) {
|
|
fprintf(stderr, "Error: "
|
|
"trunks_send returned non hex character %c\n",
|
|
c);
|
|
return -1;
|
|
}
|
|
byte |= nibble;
|
|
|
|
((uint8_t *)buf)[i] = byte;
|
|
}
|
|
|
|
return rv/2;
|
|
}
|
|
|
|
/*
|
|
* Prepare and transfer a block to either to /dev/tpm0 or through trunks_send
|
|
* --raw, get a reply.
|
|
*/
|
|
static int tpm_send_pkt(struct transfer_descriptor *td, unsigned int digest,
|
|
unsigned int addr, const void *data, int size,
|
|
void *response, size_t *response_size,
|
|
uint16_t subcmd)
|
|
{
|
|
/* Used by transfer to /dev/tpm0 */
|
|
static uint8_t outbuf[MAX_BUF_SIZE];
|
|
struct upgrade_pkt *out = (struct upgrade_pkt *)outbuf;
|
|
int len, done;
|
|
int response_offset = offsetof(struct upgrade_pkt, command.data);
|
|
void *payload;
|
|
size_t header_size;
|
|
uint32_t rv;
|
|
const size_t rx_size = sizeof(outbuf);
|
|
|
|
debug("%s: sending to %#x %d bytes\n", __func__, addr, size);
|
|
|
|
out->tag = htobe16(0x8001);
|
|
out->subcmd = htobe16(subcmd);
|
|
|
|
if (subcmd <= LAST_EXTENSION_COMMAND)
|
|
out->ordinal = htobe32(CONFIG_EXTENSION_COMMAND);
|
|
else
|
|
out->ordinal = htobe32(TPM_CC_VENDOR_BIT_MASK);
|
|
|
|
if (subcmd == EXTENSION_FW_UPGRADE) {
|
|
/* FW Upgrade PDU header includes a couple of extra fields. */
|
|
out->upgrade.digest = digest;
|
|
out->upgrade.address = htobe32(addr);
|
|
header_size = offsetof(struct upgrade_pkt, upgrade.data);
|
|
} else {
|
|
header_size = offsetof(struct upgrade_pkt, command.data);
|
|
}
|
|
|
|
payload = outbuf + header_size;
|
|
len = size + header_size;
|
|
|
|
out->length = htobe32(len);
|
|
memcpy(payload, data, size);
|
|
|
|
if (verbose_mode) {
|
|
int i;
|
|
|
|
debug("Writing %d bytes to TPM at %x\n", len, addr);
|
|
for (i = 0; i < MIN(len, 20); i++)
|
|
debug("%2.2x ", outbuf[i]);
|
|
debug("\n");
|
|
}
|
|
|
|
switch (td->ep_type) {
|
|
case dev_xfer:
|
|
done = write(td->tpm_fd, out, len);
|
|
break;
|
|
case ts_xfer:
|
|
done = ts_write(out, len);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Error: %s:%d: unknown transfer type %d\n",
|
|
__func__, __LINE__, td->ep_type);
|
|
return -1;
|
|
}
|
|
|
|
if (done < 0) {
|
|
perror("Could not write to TPM");
|
|
return -1;
|
|
} else if (done != len) {
|
|
fprintf(stderr, "Error: Wrote %d bytes, expected to write %d\n",
|
|
done, len);
|
|
return -1;
|
|
}
|
|
|
|
switch (td->ep_type) {
|
|
case dev_xfer: {
|
|
int read_count;
|
|
|
|
len = 0;
|
|
do {
|
|
uint8_t *rx_buf = outbuf + len;
|
|
size_t rx_to_go = rx_size - len;
|
|
|
|
read_count = read(td->tpm_fd, rx_buf, rx_to_go);
|
|
|
|
len += read_count;
|
|
} while (read_count);
|
|
break;
|
|
}
|
|
case ts_xfer:
|
|
len = ts_read(outbuf, rx_size);
|
|
break;
|
|
default:
|
|
/*
|
|
* This sure will never happen, type is verifed in the
|
|
* previous switch statement.
|
|
*/
|
|
len = -1;
|
|
break;
|
|
}
|
|
|
|
debug("Read %d bytes from TPM\n", len);
|
|
if (len > 0) {
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
debug("%2.2x ", outbuf[i]);
|
|
debug("\n");
|
|
}
|
|
len = len - response_offset;
|
|
if (len < 0) {
|
|
fprintf(stderr, "Problems reading from TPM, got %d bytes\n",
|
|
len + response_offset);
|
|
return -1;
|
|
}
|
|
|
|
if (response && response_size) {
|
|
len = MIN(len, *response_size);
|
|
memcpy(response, outbuf + response_offset, len);
|
|
*response_size = len;
|
|
}
|
|
|
|
/* Return the actual return code from the TPM response header. */
|
|
memcpy(&rv, &((struct upgrade_pkt *)outbuf)->ordinal, sizeof(rv));
|
|
rv = be32toh(rv);
|
|
|
|
/* Clear out vendor command return value offset.*/
|
|
if ((rv & VENDOR_RC_ERR) == VENDOR_RC_ERR)
|
|
rv &= ~VENDOR_RC_ERR;
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Release USB device and return error to the OS. */
|
|
static void shut_down(struct usb_endpoint *uep)
|
|
{
|
|
usb_shut_down(uep);
|
|
exit(update_error);
|
|
}
|
|
|
|
static void usage(int errs)
|
|
{
|
|
size_t i;
|
|
const int indent = 27; /* This is the size used by gsctool all along. */
|
|
|
|
printf("\nUsage: %s [options] [<binary image>]\n"
|
|
"\n"
|
|
"This utility allows to update Cr50 RW firmware, configure\n"
|
|
"various aspects of Cr50 operation, analyze Cr50 binary\n"
|
|
"images, etc.\n\n"
|
|
"<binary image> is the file name of a full RO+RW binary image.\n"
|
|
"\n"
|
|
"Options:\n\n",
|
|
progname);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cmd_line_options); i++) {
|
|
const char *help_text = cmd_line_options[i].help_text;
|
|
int printed_length;
|
|
const char *separator;
|
|
|
|
/*
|
|
* First print the short and long forms of the command line
|
|
* option.
|
|
*/
|
|
printed_length = printf(" -%c,--%s",
|
|
cmd_line_options[i].opt.val,
|
|
cmd_line_options[i].opt.name);
|
|
|
|
/*
|
|
* If there is something to print immediately after the
|
|
* options, print it.
|
|
*/
|
|
separator = strchr(help_text, '%');
|
|
if (separator) {
|
|
char buffer[80];
|
|
size_t extra_size;
|
|
|
|
extra_size = separator - help_text;
|
|
if (extra_size >= sizeof(buffer)) {
|
|
fprintf(stderr, "misformatted help text: %s\n",
|
|
help_text);
|
|
exit(-1);
|
|
}
|
|
memcpy(buffer, help_text, extra_size);
|
|
buffer[extra_size] = '\0';
|
|
printed_length += printf(" %s", buffer);
|
|
help_text = separator + 1;
|
|
}
|
|
|
|
/*
|
|
* If printed length exceeds or is too close to indent, print
|
|
* help text on the next line.
|
|
*/
|
|
if (printed_length >= (indent - 1)) {
|
|
printf("\n");
|
|
printed_length = 0;
|
|
}
|
|
|
|
while (printed_length++ < indent)
|
|
printf(" ");
|
|
printf("%s\n", help_text);
|
|
}
|
|
printf("\n");
|
|
exit(errs ? update_error : noop);
|
|
}
|
|
|
|
/* 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 (1 != fread(data, st.st_size, 1, fp)) {
|
|
perror("fread");
|
|
exit(update_error);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
*len_ptr = len;
|
|
return data;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
struct update_pdu {
|
|
uint32_t block_size; /* Total block size, include this field's size. */
|
|
struct upgrade_command cmd;
|
|
/* The actual payload goes here. */
|
|
};
|
|
|
|
static void do_xfer(struct usb_endpoint *uep, void *outbuf, int outlen,
|
|
void *inbuf, int inlen, int allow_less, size_t *rxed_count)
|
|
{
|
|
if (usb_trx(uep, outbuf, outlen, inbuf, inlen, allow_less, rxed_count))
|
|
shut_down(uep);
|
|
}
|
|
|
|
static int transfer_block(struct usb_endpoint *uep, struct update_pdu *updu,
|
|
uint8_t *transfer_data_ptr, size_t payload_size)
|
|
{
|
|
size_t transfer_size;
|
|
uint32_t reply;
|
|
int actual;
|
|
int r;
|
|
|
|
/* First send the header. */
|
|
do_xfer(uep, updu, sizeof(*updu), NULL, 0, 0, NULL);
|
|
|
|
/* 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);
|
|
do_xfer(uep, transfer_data_ptr, chunk_size, NULL, 0, 0, NULL);
|
|
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
|
|
*/
|
|
static void transfer_section(struct transfer_descriptor *td,
|
|
uint8_t *data_ptr,
|
|
uint32_t section_addr,
|
|
size_t data_len)
|
|
{
|
|
/*
|
|
* Actually, we can skip trailing chunks of 0xff, as the entire
|
|
* section space must be erased before the update is attempted.
|
|
*/
|
|
while (data_len && (data_ptr[data_len - 1] == 0xff))
|
|
data_len--;
|
|
|
|
/*
|
|
* Make sure total size is 4 bytes aligned, this is required for
|
|
* successful flashing.
|
|
*/
|
|
data_len = (data_len + 3) & ~3;
|
|
|
|
printf("sending 0x%zx bytes to %#x\n", data_len, section_addr);
|
|
while (data_len) {
|
|
size_t payload_size;
|
|
SHA_CTX ctx;
|
|
uint8_t digest[SHA_DIGEST_LENGTH];
|
|
int max_retries;
|
|
struct update_pdu updu;
|
|
|
|
/* prepare the header to prepend to the block. */
|
|
payload_size = MIN(data_len, SIGNED_TRANSFER_SIZE);
|
|
updu.block_size = htobe32(payload_size +
|
|
sizeof(struct update_pdu));
|
|
|
|
updu.cmd.block_base = htobe32(section_addr);
|
|
|
|
/* Calculate the digest. */
|
|
SHA1_Init(&ctx);
|
|
SHA1_Update(&ctx, &updu.cmd.block_base,
|
|
sizeof(updu.cmd.block_base));
|
|
SHA1_Update(&ctx, data_ptr, payload_size);
|
|
SHA1_Final(digest, &ctx);
|
|
|
|
/* Copy the first few bytes. */
|
|
memcpy(&updu.cmd.block_digest, digest,
|
|
sizeof(updu.cmd.block_digest));
|
|
if (td->ep_type == usb_xfer) {
|
|
for (max_retries = 10; max_retries; max_retries--)
|
|
if (!transfer_block(&td->uep, &updu,
|
|
data_ptr, payload_size))
|
|
break;
|
|
|
|
if (!max_retries) {
|
|
fprintf(stderr,
|
|
"Failed to transfer block, %zd to go\n",
|
|
data_len);
|
|
exit(update_error);
|
|
}
|
|
} else {
|
|
uint8_t error_code[4];
|
|
size_t rxed_size = sizeof(error_code);
|
|
uint32_t block_addr;
|
|
|
|
block_addr = section_addr;
|
|
|
|
/*
|
|
* A single byte response is expected, but let's give
|
|
* the driver a few extra bytes to catch cases when a
|
|
* different amount of data is transferred (which
|
|
* would indicate a synchronization problem).
|
|
*/
|
|
if (tpm_send_pkt(td,
|
|
updu.cmd.block_digest,
|
|
block_addr,
|
|
data_ptr,
|
|
payload_size, error_code,
|
|
&rxed_size,
|
|
EXTENSION_FW_UPGRADE) < 0) {
|
|
fprintf(stderr,
|
|
"Failed to trasfer block, %zd to go\n",
|
|
data_len);
|
|
exit(update_error);
|
|
}
|
|
if (rxed_size != 1) {
|
|
fprintf(stderr, "Unexpected return size %zd\n",
|
|
rxed_size);
|
|
exit(update_error);
|
|
}
|
|
|
|
if (error_code[0]) {
|
|
fprintf(stderr, "Error %d\n", error_code[0]);
|
|
exit(update_error);
|
|
}
|
|
}
|
|
data_len -= payload_size;
|
|
data_ptr += payload_size;
|
|
section_addr += payload_size;
|
|
}
|
|
}
|
|
|
|
/* Information about the target */
|
|
static struct first_response_pdu targ;
|
|
|
|
/*
|
|
* 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;
|
|
uint32_t keyid;
|
|
} 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_;
|
|
sections[i].keyid = h->keyid;
|
|
}
|
|
}
|
|
|
|
|
|
/* 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;
|
|
|
|
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];
|
|
|
|
/*
|
|
* Let's filter out images where the section is not
|
|
* initialized and the version field value is set to all ones.
|
|
*/
|
|
if (a_value == 0xffffffff)
|
|
a_value = 0;
|
|
|
|
if (b_value == 0xffffffff)
|
|
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, &targ.shv[1]) ||
|
|
!td->upstart_mode)
|
|
sections[i].ustatus = needed;
|
|
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, &targ.shv[0]))
|
|
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");
|
|
|
|
if (td->ep_type == usb_xfer) {
|
|
struct update_pdu updu;
|
|
|
|
memset(&updu, 0, sizeof(updu));
|
|
updu.block_size = htobe32(sizeof(updu));
|
|
do_xfer(&td->uep, &updu, sizeof(updu), &start_resp,
|
|
sizeof(start_resp), 1, &rxed_size);
|
|
} else {
|
|
rxed_size = sizeof(start_resp);
|
|
if (tpm_send_pkt(td, 0, 0, NULL, 0,
|
|
&start_resp, &rxed_size,
|
|
EXTENSION_FW_UPGRADE) < 0) {
|
|
fprintf(stderr, "Failed to start transfer\n");
|
|
exit(update_error);
|
|
}
|
|
}
|
|
|
|
/* 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 = be32toh(start_resp.rpdu.protocol_version);
|
|
if (protocol_version < 5) {
|
|
fprintf(stderr, "Unsupported protocol version %d\n",
|
|
protocol_version);
|
|
exit(update_error);
|
|
}
|
|
|
|
printf("target running protocol version %d\n", protocol_version);
|
|
|
|
error_code = be32toh(start_resp.rpdu.return_value);
|
|
|
|
if (error_code) {
|
|
fprintf(stderr, "Target reporting error %d\n", error_code);
|
|
if (td->ep_type == usb_xfer)
|
|
shut_down(&td->uep);
|
|
exit(update_error);
|
|
}
|
|
|
|
td->rw_offset = be32toh(start_resp.rpdu.backup_rw_offset);
|
|
td->ro_offset = be32toh(start_resp.rpdu.backup_ro_offset);
|
|
|
|
/* Running header versions. */
|
|
for (i = 0; i < ARRAY_SIZE(targ.shv); i++) {
|
|
targ.shv[i].minor = be32toh(start_resp.rpdu.shv[i].minor);
|
|
targ.shv[i].major = be32toh(start_resp.rpdu.shv[i].major);
|
|
targ.shv[i].epoch = be32toh(start_resp.rpdu.shv[i].epoch);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(targ.keyid); i++)
|
|
targ.keyid[i] = be32toh(start_resp.rpdu.keyid[i]);
|
|
|
|
printf("keyids: RO 0x%08x, RW 0x%08x\n", targ.keyid[0], targ.keyid[1]);
|
|
printf("offsets: backup RO at %#x, backup RW at %#x\n",
|
|
td->ro_offset, td->rw_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,
|
|
const 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) {
|
|
fprintf(stderr, "%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));
|
|
|
|
do_xfer(uep, ufh, usb_msg_size, resp,
|
|
resp_size ? *resp_size : 0, 1, resp_size);
|
|
|
|
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);
|
|
do_xfer(uep, &out, sizeof(out), &out, 1, 0, NULL);
|
|
}
|
|
|
|
/* Returns number of successfully transmitted image sections. */
|
|
static int transfer_image(struct transfer_descriptor *td,
|
|
uint8_t *data, size_t data_len)
|
|
{
|
|
size_t j;
|
|
int num_txed_sections = 0;
|
|
|
|
/*
|
|
* In case both RO and RW updates are required, make sure the RW
|
|
* section is updated before the RO. The array below keeps sections
|
|
* offsets in the required order.
|
|
*/
|
|
const size_t update_order[] = {CONFIG_RW_MEM_OFF,
|
|
CONFIG_RW_B_MEM_OFF,
|
|
CONFIG_RO_MEM_OFF,
|
|
CHIP_RO_B_MEM_OFF};
|
|
|
|
for (j = 0; j < ARRAY_SIZE(update_order); j++) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sections); i++) {
|
|
if (sections[i].offset != update_order[j])
|
|
continue;
|
|
|
|
if (sections[i].ustatus != needed)
|
|
break;
|
|
|
|
transfer_section(td,
|
|
data + sections[i].offset,
|
|
sections[i].offset,
|
|
sections[i].size);
|
|
num_txed_sections++;
|
|
}
|
|
}
|
|
|
|
if (!num_txed_sections)
|
|
printf("nothing to do\n");
|
|
else
|
|
printf("-------\nupdate complete\n");
|
|
return num_txed_sections;
|
|
}
|
|
|
|
uint32_t send_vendor_command(struct transfer_descriptor *td,
|
|
uint16_t subcommand,
|
|
const void *command_body,
|
|
size_t command_body_size,
|
|
void *response,
|
|
size_t *response_size)
|
|
{
|
|
int32_t rv;
|
|
|
|
if (td->ep_type == usb_xfer) {
|
|
/*
|
|
* When communicating over USB the response is always supposed
|
|
* to have the result code in the first byte of the response,
|
|
* to be stripped from the actual response body by this
|
|
* function.
|
|
*/
|
|
uint8_t temp_response[MAX_BUF_SIZE];
|
|
size_t max_response_size;
|
|
|
|
if (!response_size) {
|
|
max_response_size = 1;
|
|
} else if (*response_size < (sizeof(temp_response))) {
|
|
max_response_size = *response_size + 1;
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: Expected response too large (%zd)\n",
|
|
*response_size);
|
|
/* Should happen only when debugging. */
|
|
exit(update_error);
|
|
}
|
|
|
|
ext_cmd_over_usb(&td->uep, subcommand,
|
|
command_body, command_body_size,
|
|
temp_response, &max_response_size);
|
|
if (!max_response_size) {
|
|
/*
|
|
* we must be talking to an older Cr50 firmware, which
|
|
* does not return the result code in the first byte
|
|
* on success, nothing to do.
|
|
*/
|
|
if (response_size)
|
|
*response_size = 0;
|
|
rv = 0;
|
|
} else {
|
|
rv = temp_response[0];
|
|
if (response_size) {
|
|
*response_size = max_response_size - 1;
|
|
memcpy(response,
|
|
temp_response + 1, *response_size);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
rv = tpm_send_pkt(td, 0, 0,
|
|
command_body, command_body_size,
|
|
response, response_size, subcommand);
|
|
|
|
if (rv == -1) {
|
|
fprintf(stderr,
|
|
"Error: Failed to send vendor command %d\n",
|
|
subcommand);
|
|
exit(update_error);
|
|
}
|
|
}
|
|
|
|
return rv; /* This will be converted into uint32_t */
|
|
}
|
|
|
|
/*
|
|
* Corrupt the header of the inactive rw image to make sure the system can't
|
|
* rollback
|
|
*/
|
|
static void invalidate_inactive_rw(struct transfer_descriptor *td)
|
|
{
|
|
/* Corrupt the rw image that is not running. */
|
|
uint32_t rv;
|
|
|
|
rv = send_vendor_command(td, VENDOR_CC_INVALIDATE_INACTIVE_RW,
|
|
NULL, 0, NULL, NULL);
|
|
if (!rv) {
|
|
printf("Inactive header invalidated\n");
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr, "*%s: Error %#x\n", __func__, rv);
|
|
exit(update_error);
|
|
}
|
|
|
|
static struct signed_header_version ver19 = {
|
|
.epoch = 0,
|
|
.major = 0,
|
|
.minor = 19,
|
|
};
|
|
|
|
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;
|
|
uint32_t background_update_supported;
|
|
const char *reset_type;
|
|
int rv;
|
|
|
|
if (protocol_version < 6) {
|
|
if (td->ep_type == usb_xfer) {
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/* RW version 0.0.19 and above has support for background updates. */
|
|
background_update_supported = td->background_update_supported ||
|
|
!a_newer_than_b(&ver19, &targ.shv[1]);
|
|
|
|
/*
|
|
* If this is an upstart request and there is support for background
|
|
* updates, don't post a request now. The target should handle it on
|
|
* the next reboot.
|
|
*/
|
|
if (td->upstart_mode && background_update_supported)
|
|
return;
|
|
|
|
/*
|
|
* If the user explicitly wants it or a reset is needed because h1
|
|
* does not support background updates, 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;
|
|
if (td->post_reset || td->upstart_mode) {
|
|
subcommand = EXTENSION_POST_RESET;
|
|
reset_type = "posted";
|
|
} else if (background_update_supported) {
|
|
subcommand = VENDOR_CC_TURN_UPDATE_ON;
|
|
command_body_size = sizeof(command_body);
|
|
command_body[0] = 0;
|
|
command_body[1] = 100; /* Reset in 100 ms. */
|
|
reset_type = "requested";
|
|
} else {
|
|
response_size = 0;
|
|
subcommand = VENDOR_CC_IMMEDIATE_RESET;
|
|
reset_type = "triggered";
|
|
}
|
|
|
|
rv = send_vendor_command(td, subcommand, command_body,
|
|
command_body_size, &response, &response_size);
|
|
|
|
if (rv) {
|
|
fprintf(stderr, "*%s: Error %#x\n", __func__, rv);
|
|
exit(update_error);
|
|
}
|
|
printf("reboot %s\n", reset_type);
|
|
}
|
|
|
|
/*
|
|
* Machine output is formatted as "key=value", one key-value pair per line, and
|
|
* parsed by other programs (e.g., debugd). The value part should be specified
|
|
* in the printf-like way. For example:
|
|
*
|
|
* print_machine_output("date", "%d/%d/%d", 2018, 1, 1),
|
|
*
|
|
* which outputs this line in console:
|
|
*
|
|
* date=2018/1/1
|
|
*
|
|
* The key part should not contain '=' or newline. The value part may contain
|
|
* special characters like spaces, quotes, brackets, but not newlines. The
|
|
* newline character means end of value.
|
|
*
|
|
* Any output format change in this function may require similar changes on the
|
|
* programs that are using this gsctool.
|
|
*/
|
|
__attribute__((__format__(__printf__, 2, 3)))
|
|
static void print_machine_output(const char *key, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (strchr(key, '=') != NULL || strchr(key, '\n') != NULL) {
|
|
fprintf(stderr,
|
|
"Error: key %s contains '=' or a newline character.\n",
|
|
key);
|
|
return;
|
|
}
|
|
|
|
if (strchr(format, '\n') != NULL) {
|
|
fprintf(stderr,
|
|
"Error: value format %s contains a newline character. "
|
|
"\n",
|
|
format);
|
|
return;
|
|
}
|
|
|
|
va_start(args, format);
|
|
|
|
printf("%s=", key);
|
|
vprintf(format, args);
|
|
printf("\n");
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
/*
|
|
* Prints out the header, including FW versions and board IDs, of the given
|
|
* image. Output in a machine-friendly format if show_machine_output is true.
|
|
*/
|
|
static int show_headers_versions(const void *image, bool show_machine_output)
|
|
{
|
|
/*
|
|
* There are 2 FW slots in an image, and each slot has 2 sections, RO
|
|
* and RW. The 2 slots should have identical FW versions and board
|
|
* IDs.
|
|
*/
|
|
const struct {
|
|
const char *name;
|
|
uint32_t offset;
|
|
} sections[] = {
|
|
/* Slot A. */
|
|
{"RO", CONFIG_RO_MEM_OFF},
|
|
{"RW", CONFIG_RW_MEM_OFF},
|
|
/* Slot B. */
|
|
{"RO", CHIP_RO_B_MEM_OFF},
|
|
{"RW", CONFIG_RW_B_MEM_OFF}
|
|
};
|
|
const size_t kNumSlots = 2;
|
|
const size_t kNumSectionsPerSlot = 2;
|
|
|
|
/*
|
|
* String representation of FW version (<epoch>:<major>:<minor>), one
|
|
* string for each FW section.
|
|
*/
|
|
char ro_fw_ver[kNumSlots][MAX_FW_VER_LENGTH];
|
|
char rw_fw_ver[kNumSlots][MAX_FW_VER_LENGTH];
|
|
|
|
uint32_t dev_id0_[kNumSlots];
|
|
uint32_t dev_id1_[kNumSlots];
|
|
uint32_t print_devid = 0;
|
|
|
|
struct board_id {
|
|
uint32_t id;
|
|
uint32_t mask;
|
|
uint32_t flags;
|
|
} bid[kNumSlots];
|
|
|
|
char bid_string[kNumSlots][MAX_BOARD_ID_LENGTH];
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sections); i++) {
|
|
const struct SignedHeader *h =
|
|
(const struct SignedHeader *)
|
|
((uintptr_t)image + sections[i].offset);
|
|
const size_t slot_idx = i / kNumSectionsPerSlot;
|
|
|
|
uint32_t cur_bid;
|
|
size_t j;
|
|
|
|
if (sections[i].name[1] == 'O') {
|
|
/* RO. */
|
|
snprintf(ro_fw_ver[slot_idx], MAX_FW_VER_LENGTH,
|
|
"%d.%d.%d", h->epoch_, h->major_, h->minor_);
|
|
/* No need to read board ID in an RO section. */
|
|
continue;
|
|
} else {
|
|
/* RW. */
|
|
snprintf(rw_fw_ver[slot_idx], MAX_FW_VER_LENGTH,
|
|
"%d.%d.%d", h->epoch_, h->major_, h->minor_);
|
|
}
|
|
|
|
/*
|
|
* For RW sections, retrieves the board ID fields' contents,
|
|
* which are stored XORed with a padding value.
|
|
*/
|
|
bid[slot_idx].id = h->board_id_type ^ SIGNED_HEADER_PADDING;
|
|
bid[slot_idx].mask =
|
|
h->board_id_type_mask ^ SIGNED_HEADER_PADDING;
|
|
bid[slot_idx].flags = h->board_id_flags ^ SIGNED_HEADER_PADDING;
|
|
|
|
dev_id0_[slot_idx] = h->dev_id0_;
|
|
dev_id1_[slot_idx] = h->dev_id1_;
|
|
/* Print the devid if any slot has a non-zero devid. */
|
|
print_devid |= h->dev_id0_ | h->dev_id1_;
|
|
|
|
/*
|
|
* If board ID is a 4-uppercase-letter string (as it ought to
|
|
* be), print it as 4 letters, otherwise print it as an 8-digit
|
|
* hex.
|
|
*/
|
|
cur_bid = bid[slot_idx].id;
|
|
for (j = 0; j < sizeof(cur_bid); ++j)
|
|
if (!isupper(((const char *)&cur_bid)[j]))
|
|
break;
|
|
|
|
if (j == sizeof(cur_bid)) {
|
|
cur_bid = be32toh(cur_bid);
|
|
snprintf(bid_string[slot_idx], MAX_BOARD_ID_LENGTH,
|
|
"%.4s", (const char *)&cur_bid);
|
|
} else {
|
|
snprintf(bid_string[slot_idx], MAX_BOARD_ID_LENGTH,
|
|
"%08x", cur_bid);
|
|
}
|
|
}
|
|
|
|
if (show_machine_output) {
|
|
print_machine_output("IMAGE_RO_FW_VER", "%s", ro_fw_ver[0]);
|
|
print_machine_output("IMAGE_RW_FW_VER", "%s", rw_fw_ver[0]);
|
|
print_machine_output("IMAGE_BID_STRING", "%s", bid_string[0]);
|
|
print_machine_output("IMAGE_BID_MASK", "%08x", bid[0].mask);
|
|
print_machine_output("IMAGE_BID_FLAGS", "%08x", bid[0].flags);
|
|
} else {
|
|
printf("RO_A:%s RW_A:%s[%s:%08x:%08x] ",
|
|
ro_fw_ver[0], rw_fw_ver[0],
|
|
bid_string[0], bid[0].mask, bid[0].flags);
|
|
printf("RO_B:%s RW_B:%s[%s:%08x:%08x]\n",
|
|
ro_fw_ver[1], rw_fw_ver[1],
|
|
bid_string[1], bid[1].mask, bid[1].flags);
|
|
|
|
if (print_devid) {
|
|
printf("DEVID: 0x%08x 0x%08x", dev_id0_[0],
|
|
dev_id1_[0]);
|
|
/*
|
|
* Only print the second devid if it's different.
|
|
* Separate the devids with tabs, so it's easier to
|
|
* read.
|
|
*/
|
|
if (dev_id0_[0] != dev_id0_[1] ||
|
|
dev_id1_[0] != dev_id1_[1])
|
|
printf("\t\t\t\tDEVID_B: 0x%08x 0x%08x",
|
|
dev_id0_[1], dev_id1_[1]);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The default flag value will allow to run images built for any hardware
|
|
* generation of a particular board ID.
|
|
*/
|
|
#define DEFAULT_BOARD_ID_FLAG 0xff00
|
|
static int parse_bid(const char *opt,
|
|
struct board_id *bid,
|
|
enum board_id_action *bid_action)
|
|
{
|
|
char *e;
|
|
const char *param2;
|
|
size_t param1_length;
|
|
|
|
if (!opt) {
|
|
*bid_action = bid_get;
|
|
return 1;
|
|
}
|
|
|
|
/* Set it here to make bailing out easier later. */
|
|
bid->flags = DEFAULT_BOARD_ID_FLAG;
|
|
|
|
*bid_action = bid_set; /* Ignored by caller on errors. */
|
|
|
|
/*
|
|
* Pointer to the optional second component of the command line
|
|
* parameter, when present - separated by a colon.
|
|
*/
|
|
param2 = strchr(opt, ':');
|
|
if (param2) {
|
|
param1_length = param2 - opt;
|
|
param2++;
|
|
if (!*param2)
|
|
return 0; /* Empty second parameter. */
|
|
} else {
|
|
param1_length = strlen(opt);
|
|
}
|
|
|
|
if (!param1_length)
|
|
return 0; /* Colon is the first character of the string? */
|
|
|
|
if (param1_length <= 4) {
|
|
unsigned i;
|
|
|
|
/* Input must be a symbolic board name. */
|
|
bid->type = 0;
|
|
for (i = 0; i < param1_length; i++)
|
|
bid->type = (bid->type << 8) | opt[i];
|
|
} else {
|
|
bid->type = (uint32_t)strtoul(opt, &e, 0);
|
|
if ((param2 && (*e != ':')) || (!param2 && *e))
|
|
return 0;
|
|
}
|
|
|
|
if (param2) {
|
|
bid->flags = (uint32_t)strtoul(param2, &e, 0);
|
|
if (*e)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Reads a two-character hexadecimal byte from a string. If the string is
|
|
* ill-formed, returns 0. Otherwise, |byte| contains the byte value and the
|
|
* return value is non-zero.
|
|
*/
|
|
static int read_hex_byte(const char* s, uint8_t* byte) {
|
|
uint8_t b = 0;
|
|
for (const char* end = s + 2; s < end; ++s) {
|
|
if (*s >= '0' && *s <= '9')
|
|
b = b * 16 + *s - '0';
|
|
else if (*s >= 'A' && *s <= 'F')
|
|
b = b * 16 + 10 + *s - 'A';
|
|
else if (*s >= 'a' && *s <= 'f')
|
|
b = b * 16 + 10 + *s - 'a';
|
|
else
|
|
return 0;
|
|
}
|
|
*byte = b;
|
|
return 1;
|
|
}
|
|
|
|
static int parse_sn_bits(const char *opt, uint8_t *sn_bits)
|
|
{
|
|
size_t len = strlen(opt);
|
|
|
|
if (!strncmp(opt, "0x", 2)) {
|
|
opt += 2;
|
|
len -= 2;
|
|
}
|
|
if (len != SN_BITS_SIZE * 2) return 0;
|
|
|
|
for (int i = 0; i < SN_BITS_SIZE; ++i, opt +=2)
|
|
if (!read_hex_byte(opt, sn_bits++)) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int parse_sn_inc_rma(const char *opt, uint8_t *arg)
|
|
{
|
|
uint32_t inc;
|
|
char *e;
|
|
|
|
inc = (uint32_t)strtoul(opt, &e, 0);
|
|
|
|
if (opt == e || *e != '\0' || inc > 7)
|
|
return 0;
|
|
|
|
*arg = inc;
|
|
return 1;
|
|
}
|
|
|
|
static uint32_t common_process_password(struct transfer_descriptor *td,
|
|
enum ccd_vendor_subcommands subcmd)
|
|
{
|
|
size_t response_size;
|
|
uint8_t response;
|
|
uint32_t rv;
|
|
char *password = NULL;
|
|
char *password_copy = NULL;
|
|
size_t copy_len = 0;
|
|
size_t len = 0;
|
|
struct termios oldattr, newattr;
|
|
|
|
/* Suppress command line echo while password is being entered. */
|
|
tcgetattr(STDIN_FILENO, &oldattr);
|
|
newattr = oldattr;
|
|
newattr.c_lflag &= ~ECHO;
|
|
newattr.c_lflag |= (ICANON | ECHONL);
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
|
|
|
|
/* With command line echo suppressed request password entry twice. */
|
|
printf("Enter password:");
|
|
len = getline(&password, &len, stdin);
|
|
printf("Re-enter password:");
|
|
getline(&password_copy, ©_len, stdin);
|
|
|
|
/* Restore command line echo. */
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
|
|
|
|
/* Empty password will still have the newline. */
|
|
if ((len <= 1) || !password_copy) {
|
|
if (password)
|
|
free(password);
|
|
if (password_copy)
|
|
free(password_copy);
|
|
fprintf(stderr, "Error reading password\n");
|
|
exit(update_error);
|
|
}
|
|
|
|
/* Compare the two inputs. */
|
|
if (strcmp(password, password_copy)) {
|
|
fprintf(stderr, "Entered passwords don't match\n");
|
|
free(password);
|
|
free(password_copy);
|
|
exit(update_error);
|
|
}
|
|
|
|
/*
|
|
* Ok, we have a password, let's move it in the buffer to overwrite
|
|
* the newline and free a byte to prepend the subcommand code.
|
|
*/
|
|
memmove(password + 1, password, len - 1);
|
|
password[0] = subcmd;
|
|
response_size = sizeof(response);
|
|
rv = send_vendor_command(td, VENDOR_CC_CCD,
|
|
password, len,
|
|
&response, &response_size);
|
|
free(password);
|
|
free(password_copy);
|
|
|
|
if ((rv != VENDOR_RC_SUCCESS) && (rv != VENDOR_RC_IN_PROGRESS))
|
|
fprintf(stderr, "Error sending password: rv %d, response %d\n",
|
|
rv, response_size ? response : 0);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void process_password(struct transfer_descriptor *td)
|
|
{
|
|
if (common_process_password(td, CCDV_PASSWORD) == VENDOR_RC_SUCCESS)
|
|
return;
|
|
|
|
exit(update_error);
|
|
}
|
|
|
|
void poll_for_pp(struct transfer_descriptor *td,
|
|
uint16_t command,
|
|
uint8_t poll_type)
|
|
{
|
|
uint8_t response;
|
|
uint8_t prev_response;
|
|
size_t response_size;
|
|
int rv;
|
|
|
|
prev_response = ~0; /* Guaranteed invalid value. */
|
|
|
|
while (1) {
|
|
response_size = sizeof(response);
|
|
rv = send_vendor_command(td, command,
|
|
&poll_type, sizeof(poll_type),
|
|
&response, &response_size);
|
|
|
|
if (((rv != VENDOR_RC_SUCCESS) && (rv != VENDOR_RC_IN_PROGRESS))
|
|
|| (response_size != 1)) {
|
|
fprintf(stderr, "Error: rv %d, response %d\n",
|
|
rv, response_size ? response : 0);
|
|
exit(update_error);
|
|
}
|
|
|
|
if (response == CCD_PP_DONE) {
|
|
printf("PP Done!\n");
|
|
return;
|
|
}
|
|
|
|
if (response == CCD_PP_CLOSED) {
|
|
fprintf(stderr,
|
|
"Error: Physical presence check timeout!\n");
|
|
exit(update_error);
|
|
}
|
|
|
|
|
|
if (response == CCD_PP_AWAITING_PRESS) {
|
|
printf("Press PP button now!\n");
|
|
} else if (response == CCD_PP_BETWEEN_PRESSES) {
|
|
if (prev_response != response)
|
|
printf("Another press will be required!\n");
|
|
} else {
|
|
fprintf(stderr, "Error: unknown poll result %d\n",
|
|
response);
|
|
exit(update_error);
|
|
}
|
|
prev_response = response;
|
|
|
|
usleep(500 * 1000); /* Poll every half a second. */
|
|
}
|
|
|
|
}
|
|
|
|
static void print_ccd_info(void *response, size_t response_size)
|
|
{
|
|
struct ccd_info_response ccd_info;
|
|
size_t i;
|
|
const struct ccd_capability_info cap_info[] = CAP_INFO_DATA;
|
|
const char *state_names[] = CCD_STATE_NAMES;
|
|
const char *cap_state_names[] = CCD_CAP_STATE_NAMES;
|
|
uint32_t caps_bitmap = 0;
|
|
|
|
if (response_size != sizeof(ccd_info)) {
|
|
fprintf(stderr, "Unexpected CCD info response size %zd\n",
|
|
response_size);
|
|
exit(update_error);
|
|
}
|
|
|
|
memcpy(&ccd_info, response, sizeof(ccd_info));
|
|
|
|
/* Convert it back to host endian format. */
|
|
ccd_info.ccd_flags = be32toh(ccd_info.ccd_flags);
|
|
for (i = 0; i < ARRAY_SIZE(ccd_info.ccd_caps_current); i++) {
|
|
ccd_info.ccd_caps_current[i] =
|
|
be32toh(ccd_info.ccd_caps_current[i]);
|
|
ccd_info.ccd_caps_defaults[i] =
|
|
be32toh(ccd_info.ccd_caps_defaults[i]);
|
|
}
|
|
|
|
/* Now report CCD state on the console. */
|
|
printf("State: %s\n", ccd_info.ccd_state > ARRAY_SIZE(state_names) ?
|
|
"Error" : state_names[ccd_info.ccd_state]);
|
|
printf("Password: %s\n", (ccd_info.ccd_indicator_bitmap &
|
|
CCD_INDICATOR_BIT_HAS_PASSWORD) ? "Set" : "None");
|
|
printf("Flags: %#06x\n", ccd_info.ccd_flags);
|
|
printf("Capabilities, current and default:\n");
|
|
for (i = 0; i < CCD_CAP_COUNT; i++) {
|
|
int is_enabled;
|
|
int index;
|
|
int shift;
|
|
int cap_current;
|
|
int cap_default;
|
|
|
|
index = i / (32 / CCD_CAP_BITS);
|
|
shift = (i % (32 / CCD_CAP_BITS)) * CCD_CAP_BITS;
|
|
|
|
cap_current = (ccd_info.ccd_caps_current[index] >> shift)
|
|
& CCD_CAP_BITMASK;
|
|
cap_default = (ccd_info.ccd_caps_defaults[index] >> shift)
|
|
& CCD_CAP_BITMASK;
|
|
|
|
if (ccd_info.ccd_force_disabled) {
|
|
is_enabled = 0;
|
|
} else {
|
|
switch (cap_current) {
|
|
case CCD_CAP_STATE_ALWAYS:
|
|
is_enabled = 1;
|
|
break;
|
|
case CCD_CAP_STATE_UNLESS_LOCKED:
|
|
is_enabled = (ccd_info.ccd_state !=
|
|
CCD_STATE_LOCKED);
|
|
break;
|
|
default:
|
|
is_enabled = (ccd_info.ccd_state ==
|
|
CCD_STATE_OPENED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
printf(" %-15s %c %s",
|
|
cap_info[i].name,
|
|
is_enabled ? 'Y' : '-',
|
|
cap_state_names[cap_current]);
|
|
|
|
if (cap_current != cap_default)
|
|
printf(" (%s)", cap_state_names[cap_default]);
|
|
|
|
printf("\n");
|
|
|
|
if (is_enabled)
|
|
caps_bitmap |= (1 << i);
|
|
}
|
|
printf("CCD caps bitmap: %#x\n", caps_bitmap);
|
|
printf("Capabilities are %s.\n", (ccd_info.ccd_indicator_bitmap &
|
|
CCD_INDICATOR_BIT_ALL_CAPS_DEFAULT) ? "default" : "modified");
|
|
}
|
|
|
|
static void process_ccd_state(struct transfer_descriptor *td, int ccd_unlock,
|
|
int ccd_open, int ccd_lock, int ccd_info)
|
|
{
|
|
uint8_t payload;
|
|
/* Max possible response size is when ccd_info is requested. */
|
|
uint8_t response[sizeof(struct ccd_info_response)];
|
|
size_t response_size;
|
|
int rv;
|
|
|
|
if (ccd_unlock)
|
|
payload = CCDV_UNLOCK;
|
|
else if (ccd_open)
|
|
payload = CCDV_OPEN;
|
|
else if (ccd_lock)
|
|
payload = CCDV_LOCK;
|
|
else
|
|
payload = CCDV_GET_INFO;
|
|
|
|
response_size = sizeof(response);
|
|
rv = send_vendor_command(td, VENDOR_CC_CCD,
|
|
&payload, sizeof(payload),
|
|
&response, &response_size);
|
|
|
|
/*
|
|
* If password is required - try sending the same subcommand
|
|
* accompanied by user password.
|
|
*/
|
|
if (rv == VENDOR_RC_PASSWORD_REQUIRED)
|
|
rv = common_process_password(td, payload);
|
|
|
|
if (rv == VENDOR_RC_SUCCESS) {
|
|
if (ccd_info)
|
|
print_ccd_info(response, response_size);
|
|
return;
|
|
}
|
|
|
|
if (rv != VENDOR_RC_IN_PROGRESS) {
|
|
fprintf(stderr, "Error: rv %d, response %d\n",
|
|
rv, response_size ? response[0] : 0);
|
|
exit(update_error);
|
|
}
|
|
|
|
/*
|
|
* Physical presence process started, poll for the state the user
|
|
* asked for. Only two subcommands would return 'IN_PROGRESS'.
|
|
*/
|
|
if (ccd_unlock)
|
|
poll_for_pp(td, VENDOR_CC_CCD, CCDV_PP_POLL_UNLOCK);
|
|
else
|
|
poll_for_pp(td, VENDOR_CC_CCD, CCDV_PP_POLL_OPEN);
|
|
}
|
|
|
|
static void process_wp(struct transfer_descriptor *td)
|
|
{
|
|
size_t response_size;
|
|
uint8_t response;
|
|
int rv = 0;
|
|
|
|
response_size = sizeof(response);
|
|
|
|
printf("Getting WP\n");
|
|
|
|
rv = send_vendor_command(td, VENDOR_CC_WP, NULL, 0,
|
|
&response, &response_size);
|
|
if (rv != VENDOR_RC_SUCCESS) {
|
|
fprintf(stderr, "Error %d getting write protect\n", rv);
|
|
exit(update_error);
|
|
}
|
|
if (response_size != sizeof(response)) {
|
|
fprintf(stderr, "Unexpected response size %zd while getting "
|
|
"write protect\n",
|
|
response_size);
|
|
exit(update_error);
|
|
}
|
|
|
|
printf("WP: %08x\n", response);
|
|
printf("Flash WP: %s%s\n",
|
|
response & WPV_FORCE ? "forced " : "",
|
|
response & WPV_ENABLE ? "enabled" : "disabled");
|
|
printf(" at boot: %s\n",
|
|
!(response & WPV_ATBOOT_SET) ? "follow_batt_pres" :
|
|
response & WPV_ATBOOT_ENABLE ? "forced enabled" :
|
|
"forced disabled");
|
|
}
|
|
|
|
|
|
void process_bid(struct transfer_descriptor *td,
|
|
enum board_id_action bid_action,
|
|
struct board_id *bid,
|
|
bool show_machine_output)
|
|
{
|
|
size_t response_size;
|
|
|
|
if (bid_action == bid_get) {
|
|
|
|
response_size = sizeof(*bid);
|
|
send_vendor_command(td, VENDOR_CC_GET_BOARD_ID,
|
|
bid, sizeof(*bid),
|
|
bid, &response_size);
|
|
|
|
if (response_size != sizeof(*bid)) {
|
|
fprintf(stderr,
|
|
"Error reading board ID: response size %zd, "
|
|
"first byte %#02x\n",
|
|
response_size,
|
|
response_size ? *(uint8_t *)&bid : -1);
|
|
exit(update_error);
|
|
}
|
|
|
|
if (show_machine_output) {
|
|
print_machine_output(
|
|
"BID_TYPE", "%08x", be32toh(bid->type));
|
|
print_machine_output(
|
|
"BID_TYPE_INV", "%08x", be32toh(bid->type_inv));
|
|
print_machine_output(
|
|
"BID_FLAGS", "%08x", be32toh(bid->flags));
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
if (!isupper(((const char *)bid)[i])) {
|
|
print_machine_output(
|
|
"BID_RLZ", "%s", "????");
|
|
return;
|
|
}
|
|
}
|
|
|
|
print_machine_output(
|
|
"BID_RLZ", "%c%c%c%c",
|
|
((const char *)bid)[0],
|
|
((const char *)bid)[1],
|
|
((const char *)bid)[2],
|
|
((const char *)bid)[3]);
|
|
} else {
|
|
printf("Board ID space: %08x:%08x:%08x\n",
|
|
be32toh(bid->type),
|
|
be32toh(bid->type_inv),
|
|
be32toh(bid->flags));
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (bid_action == bid_set) {
|
|
/* Sending just two fields: type and flags. */
|
|
uint32_t command_body[2];
|
|
uint8_t response;
|
|
|
|
command_body[0] = htobe32(bid->type);
|
|
command_body[1] = htobe32(bid->flags);
|
|
|
|
response_size = sizeof(command_body);
|
|
send_vendor_command(td, VENDOR_CC_SET_BOARD_ID,
|
|
command_body, sizeof(command_body),
|
|
command_body, &response_size);
|
|
|
|
/*
|
|
* Speculative assignment: the response is expected to be one
|
|
* byte in size and be placed in the first byte of the buffer.
|
|
*/
|
|
response = *((uint8_t *)command_body);
|
|
|
|
if (response_size == 1) {
|
|
if (!response)
|
|
return; /* Success! */
|
|
|
|
fprintf(stderr, "Error %d while setting board id\n",
|
|
response);
|
|
} else {
|
|
fprintf(stderr, "Unexpected response size %zd"
|
|
" while setting board id\n",
|
|
response_size);
|
|
}
|
|
exit(update_error);
|
|
}
|
|
}
|
|
|
|
static void process_sn_bits(struct transfer_descriptor *td,
|
|
uint8_t *sn_bits)
|
|
{
|
|
int rv;
|
|
uint8_t response_code;
|
|
size_t response_size = sizeof(response_code);
|
|
|
|
rv = send_vendor_command(td, VENDOR_CC_SN_SET_HASH,
|
|
sn_bits, SN_BITS_SIZE,
|
|
&response_code, &response_size);
|
|
|
|
if (rv) {
|
|
fprintf(stderr, "Error %d while sending vendor command\n", rv);
|
|
exit(update_error);
|
|
}
|
|
|
|
if (response_size != 1) {
|
|
fprintf(stderr,
|
|
"Unexpected response size while setting sn bits\n");
|
|
exit(update_error);
|
|
}
|
|
|
|
if (response_code != 0) {
|
|
fprintf(stderr, "Error %d while setting sn bits\n",
|
|
response_code);
|
|
exit(update_error);
|
|
}
|
|
}
|
|
|
|
static void process_sn_inc_rma(struct transfer_descriptor *td,
|
|
uint8_t arg)
|
|
{
|
|
int rv;
|
|
uint8_t response_code;
|
|
size_t response_size = sizeof(response_code);
|
|
|
|
rv = send_vendor_command(td, VENDOR_CC_SN_INC_RMA,
|
|
&arg, sizeof(arg),
|
|
&response_code, &response_size);
|
|
if (rv) {
|
|
fprintf(stderr, "Error %d while sending vendor command\n", rv);
|
|
exit(update_error);
|
|
}
|
|
|
|
if (response_size != 1) {
|
|
fprintf(stderr,
|
|
"Unexpected response size while "
|
|
"incrementing sn rma count\n");
|
|
exit(update_error);
|
|
}
|
|
|
|
if (response_code != 0) {
|
|
fprintf(stderr, "Error %d while incrementing rma count\n",
|
|
response_code);
|
|
exit(update_error);
|
|
}
|
|
}
|
|
|
|
/* Get/Set the primary seed of the info1 manufacture state. */
|
|
static int process_endorsement_seed(struct transfer_descriptor *td,
|
|
const char *endorsement_seed_str)
|
|
{
|
|
uint8_t endorsement_seed[32];
|
|
uint8_t response_seed[32];
|
|
size_t seed_size = sizeof(endorsement_seed);
|
|
size_t response_size = sizeof(response_seed);
|
|
size_t i;
|
|
int rv;
|
|
|
|
if (!endorsement_seed_str) {
|
|
rv = send_vendor_command(td, VENDOR_CC_ENDORSEMENT_SEED, NULL,
|
|
0, response_seed, &response_size);
|
|
if (rv) {
|
|
fprintf(stderr, "Error sending vendor command %d\n",
|
|
rv);
|
|
return update_error;
|
|
}
|
|
printf("Endorsement key seed: ");
|
|
for (i = 0; i < response_size; i++)
|
|
printf("%02x", response_seed[i]);
|
|
printf("\n");
|
|
return 0;
|
|
}
|
|
if (seed_size * 2 != strlen(endorsement_seed_str)) {
|
|
printf("Invalid seed %s\n", endorsement_seed_str);
|
|
return update_error;
|
|
}
|
|
|
|
for (i = 0; i < seed_size; i++) {
|
|
int nibble;
|
|
char c;
|
|
|
|
c = endorsement_seed_str[2 * i];
|
|
nibble = from_hexascii(c);
|
|
if (nibble < 0) {
|
|
fprintf(stderr, "Error: Non hex character in seed %c\n",
|
|
c);
|
|
return update_error;
|
|
}
|
|
endorsement_seed[i] = nibble << 4;
|
|
|
|
c = endorsement_seed_str[2 * i + 1];
|
|
nibble = from_hexascii(c);
|
|
if (nibble < 0) {
|
|
fprintf(stderr, "Error: Non hex character in seed %c\n",
|
|
c);
|
|
return update_error;
|
|
}
|
|
endorsement_seed[i] |= nibble;
|
|
}
|
|
|
|
printf("Setting seed: %s\n", endorsement_seed_str);
|
|
rv = send_vendor_command(td, VENDOR_CC_ENDORSEMENT_SEED,
|
|
endorsement_seed, seed_size,
|
|
response_seed, &response_size);
|
|
if (rv == VENDOR_RC_NOT_ALLOWED) {
|
|
fprintf(stderr, "Seed already set\n");
|
|
return update_error;
|
|
}
|
|
if (rv) {
|
|
fprintf(stderr, "Error sending vendor command %d\n", rv);
|
|
return update_error;
|
|
}
|
|
printf("Updated endorsement key seed.\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the RMA authentication challenge from the Cr50, print out the
|
|
* challenge on the console, then prompt the user for the authentication code,
|
|
* and send the code back to Cr50. The Cr50 would report if the code matched
|
|
* its expectations or not.
|
|
*/
|
|
static void process_rma(struct transfer_descriptor *td, const char *authcode)
|
|
{
|
|
char rma_response[81];
|
|
size_t response_size = sizeof(rma_response);
|
|
size_t i;
|
|
size_t auth_size = 0;
|
|
|
|
if (!authcode) {
|
|
send_vendor_command(td, VENDOR_CC_RMA_CHALLENGE_RESPONSE,
|
|
NULL, 0, rma_response, &response_size);
|
|
|
|
if (response_size == 1) {
|
|
fprintf(stderr, "error %d\n", rma_response[0]);
|
|
if (td->ep_type == usb_xfer)
|
|
shut_down(&td->uep);
|
|
exit(update_error);
|
|
}
|
|
|
|
printf("Challenge:");
|
|
for (i = 0; i < response_size; i++) {
|
|
if (!(i % 5)) {
|
|
if (!(i % 40))
|
|
printf("\n");
|
|
printf(" ");
|
|
}
|
|
printf("%c", rma_response[i]);
|
|
}
|
|
printf("\n");
|
|
return;
|
|
}
|
|
|
|
if (!*authcode) {
|
|
printf("Empty response.\n");
|
|
exit(update_error);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(authcode, "disable")) {
|
|
printf("Invalid arg. Try using 'gsctool -F disable'\n");
|
|
exit(update_error);
|
|
return;
|
|
}
|
|
|
|
printf("Processing response...\n");
|
|
auth_size = strlen(authcode);
|
|
response_size = sizeof(rma_response);
|
|
|
|
send_vendor_command(td, VENDOR_CC_RMA_CHALLENGE_RESPONSE,
|
|
authcode, auth_size,
|
|
rma_response, &response_size);
|
|
|
|
if (response_size == 1) {
|
|
fprintf(stderr, "\nrma unlock failed, code %d ",
|
|
*rma_response);
|
|
switch (*rma_response) {
|
|
case VENDOR_RC_BOGUS_ARGS:
|
|
fprintf(stderr, "(wrong authcode size)\n");
|
|
break;
|
|
case VENDOR_RC_INTERNAL_ERROR:
|
|
fprintf(stderr, "(authcode mismatch)\n");
|
|
break;
|
|
default:
|
|
fprintf(stderr, "(unknown error)\n");
|
|
break;
|
|
}
|
|
if (td->ep_type == usb_xfer)
|
|
shut_down(&td->uep);
|
|
exit(update_error);
|
|
}
|
|
printf("RMA unlock succeeded.\n");
|
|
}
|
|
|
|
/*
|
|
* Enable or disable factory mode. Factory mode will only be enabled if HW
|
|
* write protect is removed.
|
|
*/
|
|
static void process_factory_mode(struct transfer_descriptor *td,
|
|
const char *arg)
|
|
{
|
|
uint8_t rma_response;
|
|
size_t response_size = sizeof(rma_response);
|
|
char *cmd_str;
|
|
int rv;
|
|
uint16_t subcommand;
|
|
|
|
if (!strcasecmp(arg, "disable")) {
|
|
subcommand = VENDOR_CC_DISABLE_FACTORY;
|
|
cmd_str = "dis";
|
|
} else if (!strcasecmp(arg, "enable")) {
|
|
subcommand = VENDOR_CC_RESET_FACTORY;
|
|
cmd_str = "en";
|
|
|
|
} else {
|
|
fprintf(stderr, "Invalid factory mode arg %s", arg);
|
|
exit(update_error);
|
|
}
|
|
|
|
printf("%sabling factory mode\n", cmd_str);
|
|
rv = send_vendor_command(td, subcommand, NULL, 0, &rma_response,
|
|
&response_size);
|
|
if (rv) {
|
|
fprintf(stderr, "Failed %sabling factory mode\nvc error "
|
|
"%d\n", cmd_str, rv);
|
|
if (response_size == 1)
|
|
fprintf(stderr, "ec error %d\n", rma_response);
|
|
exit(update_error);
|
|
}
|
|
printf("Factory %sable succeeded.\n", cmd_str);
|
|
}
|
|
|
|
static void report_version(void)
|
|
{
|
|
/* Get version from the generated file, ignore the underscore prefix. */
|
|
const char *v = strchr(VERSION, '_');
|
|
|
|
printf("Version: %s, built on %s by %s\n", v ? v + 1 : "?",
|
|
DATE, BUILDER);
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* Either change or query TPM mode value.
|
|
*/
|
|
static int process_tpm_mode(struct transfer_descriptor *td,
|
|
const char *arg)
|
|
{
|
|
int rv;
|
|
size_t command_size;
|
|
size_t response_size;
|
|
uint8_t response;
|
|
uint8_t command_body;
|
|
|
|
response_size = sizeof(response);
|
|
if (!arg) {
|
|
command_size = 0;
|
|
} else if (!strcasecmp(arg, "disable")) {
|
|
command_size = sizeof(command_body);
|
|
command_body = (uint8_t) TPM_MODE_DISABLED;
|
|
} else if (!strcasecmp(arg, "enable")) {
|
|
command_size = sizeof(command_body);
|
|
command_body = (uint8_t) TPM_MODE_ENABLED;
|
|
} else {
|
|
fprintf(stderr, "Invalid tpm mode arg: %s.\n", arg);
|
|
return update_error;
|
|
}
|
|
|
|
rv = send_vendor_command(td, VENDOR_CC_TPM_MODE,
|
|
&command_body, command_size,
|
|
&response, &response_size);
|
|
if (rv) {
|
|
fprintf(stderr, "Error %d in setting TPM mode.\n", rv);
|
|
return update_error;
|
|
}
|
|
if (response_size != sizeof(response)) {
|
|
fprintf(stderr, "Error in the size of response,"
|
|
" %zu.\n", response_size);
|
|
return update_error;
|
|
}
|
|
if (response >= TPM_MODE_MAX) {
|
|
fprintf(stderr, "Error in the value of response,"
|
|
" %d.\n", response);
|
|
return update_error;
|
|
}
|
|
|
|
printf("TPM Mode: %s (%d)\n", (response == TPM_MODE_DISABLED) ?
|
|
"disabled" : "enabled", response);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Retrieve from H1 flash log entries which are newer than the passed in
|
|
* timestamp.
|
|
*
|
|
* On error retry a few times just in case flash log is locked by a concurrent
|
|
* access.
|
|
*/
|
|
static int process_get_flog(struct transfer_descriptor *td, uint32_t prev_stamp)
|
|
{
|
|
int rv;
|
|
const int max_retries = 3;
|
|
int retries = max_retries;
|
|
|
|
while (retries--) {
|
|
union entry_u entry;
|
|
size_t resp_size;
|
|
size_t i;
|
|
|
|
resp_size = sizeof(entry);
|
|
rv = send_vendor_command(td, VENDOR_CC_POP_LOG_ENTRY,
|
|
&prev_stamp, sizeof(prev_stamp),
|
|
&entry, &resp_size);
|
|
|
|
if (rv) {
|
|
/*
|
|
* Flash log could be momentarily locked by a
|
|
* concurrent access, let it settle and try again, 10
|
|
* ms should be enough.
|
|
*/
|
|
usleep(10 * 1000);
|
|
continue;
|
|
}
|
|
|
|
if (resp_size == 0)
|
|
/* No more entries. */
|
|
return 0;
|
|
|
|
memcpy(&prev_stamp, &entry.r.timestamp, sizeof(prev_stamp));
|
|
printf("%10u:%02x", prev_stamp, entry.r.type);
|
|
for (i = 0; i < FLASH_LOG_PAYLOAD_SIZE(entry.r.size); i++)
|
|
printf(" %02x", entry.r.payload[i]);
|
|
printf("\n");
|
|
retries = max_retries;
|
|
}
|
|
|
|
fprintf(stderr, "%s: error %d\n", __func__, rv);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int process_tstamp(struct transfer_descriptor *td,
|
|
const char *tstamp_ascii)
|
|
{
|
|
char *e;
|
|
size_t expected_response_size;
|
|
size_t message_size;
|
|
size_t response_size;
|
|
uint32_t rv;
|
|
uint32_t tstamp = 0;
|
|
uint8_t max_response[sizeof(uint32_t)];
|
|
|
|
if (tstamp_ascii) {
|
|
tstamp = strtoul(tstamp_ascii, &e, 10);
|
|
if (*e) {
|
|
fprintf(stderr, "invalid base timestamp value \"%s\"\n",
|
|
tstamp_ascii);
|
|
return -1;
|
|
}
|
|
tstamp = htobe32(tstamp);
|
|
expected_response_size = 0;
|
|
message_size = sizeof(tstamp);
|
|
} else {
|
|
expected_response_size = 4;
|
|
message_size = 0;
|
|
}
|
|
|
|
response_size = sizeof(max_response);
|
|
rv = send_vendor_command(td, VENDOR_CC_FLOG_TIMESTAMP, &tstamp,
|
|
message_size, max_response, &response_size);
|
|
|
|
if (rv) {
|
|
fprintf(stderr, "error: return value %d\n", rv);
|
|
return rv;
|
|
}
|
|
if (response_size != expected_response_size) {
|
|
fprintf(stderr, "error: got %zd bytes, expected %zd\n",
|
|
response_size, expected_response_size);
|
|
return -1; /* Should never happen. */
|
|
}
|
|
|
|
if (response_size) {
|
|
memcpy(&tstamp, max_response, sizeof(tstamp));
|
|
printf("Current H1 time is %d\n", be32toh(tstamp));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Search the passed in zero terminated array of options_map structures for
|
|
* option 'option'.
|
|
*
|
|
* If found - set the corresponding integer to 1 and return 1. If not found -
|
|
* return 0.
|
|
*/
|
|
static int check_boolean(const struct options_map *omap, char option)
|
|
{
|
|
do {
|
|
if (omap->opt != option)
|
|
continue;
|
|
|
|
*omap->flag = 1;
|
|
return 1;
|
|
} while ((++omap)->opt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Set the long_opts table and short_opts string.
|
|
*
|
|
* This function allows to avoid maintaining two command line option
|
|
* descriptions, for short and long forms.
|
|
*
|
|
* The long_opts table is built based on the cmd_line_options table contents,
|
|
* and the short form is built based on the long_opts table contents.
|
|
*
|
|
* The 'required_argument' short options are followed by ':'.
|
|
*
|
|
* The passed in long_opts array and short_opts string are guaranteed to
|
|
* accommodate all necessary objects/characters.
|
|
*/
|
|
static void set_opt_descriptors(struct option *long_opts, char *short_opts)
|
|
{
|
|
size_t i;
|
|
int j;
|
|
|
|
for (i = j = 0; i < ARRAY_SIZE(cmd_line_options); i++) {
|
|
long_opts[i] = cmd_line_options[i].opt;
|
|
short_opts[j++] = long_opts[i].val;
|
|
if (long_opts[i].has_arg == required_argument)
|
|
short_opts[j++] = ':';
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find the long_opts table index where .val field is set to the passed in
|
|
* short option value.
|
|
*/
|
|
static int get_longindex(int short_opt, const struct option *long_opts)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; long_opts[i].name; i++)
|
|
if (long_opts[i].val == short_opt)
|
|
return i;
|
|
|
|
/*
|
|
* We could never come here as the short options list is compiled
|
|
* based on long options table.
|
|
*/
|
|
fprintf(stderr, "could not find long opt table index for %d\n",
|
|
short_opt);
|
|
exit(1);
|
|
|
|
return -1; /* Not reached. */
|
|
}
|
|
|
|
/*
|
|
* Combine searching for command line parameters and optional arguments.
|
|
*
|
|
* The canonical short options description string does not allow to specify
|
|
* that a command line argument expects an optional parameter. but gsctool
|
|
* users expect to be able to use the following styles for optional
|
|
* parameters:
|
|
*
|
|
* a) -x <param value>
|
|
* b) --x_long <param_value>
|
|
* c) --x_long=<param_value>
|
|
*
|
|
* Styles a) and b) are not supported standard getopt_long(), this function
|
|
* adds ability to handle cases a) and b).
|
|
*/
|
|
static int getopt_all(int argc, char *argv[])
|
|
{
|
|
int longindex = -1;
|
|
static char short_opts[2 * ARRAY_SIZE(cmd_line_options)] = {};
|
|
static struct option long_opts[ARRAY_SIZE(cmd_line_options) + 1] = {};
|
|
int i;
|
|
|
|
if (!short_opts[0])
|
|
set_opt_descriptors(long_opts, short_opts);
|
|
|
|
i = getopt_long(argc, argv, short_opts, long_opts, &longindex);
|
|
if (i != -1) {
|
|
|
|
if (longindex < 0) {
|
|
/*
|
|
* longindex is not set, this must have been the short
|
|
* option case, Find the long_opts table index based
|
|
* on the short option value.
|
|
*/
|
|
longindex = get_longindex(i, long_opts);
|
|
}
|
|
|
|
if (long_opts[longindex].has_arg == optional_argument) {
|
|
/*
|
|
* This command line option may include an argument,
|
|
* let's check if it is there as the next token in the
|
|
* command line.
|
|
*/
|
|
if (!optarg && argv[optind] && argv[optind][0] != '-')
|
|
/* Yes, it is. */
|
|
optarg = argv[optind++];
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct transfer_descriptor td;
|
|
int errorcnt;
|
|
uint8_t *data = 0;
|
|
size_t data_len = 0;
|
|
uint16_t vid = 0;
|
|
uint16_t pid = 0;
|
|
int i;
|
|
size_t j;
|
|
int transferred_sections = 0;
|
|
int binary_vers = 0;
|
|
int show_fw_ver = 0;
|
|
int rma = 0;
|
|
const char *rma_auth_code;
|
|
int get_endorsement_seed = 0;
|
|
const char *endorsement_seed_str;
|
|
int corrupt_inactive_rw = 0;
|
|
struct board_id bid;
|
|
enum board_id_action bid_action;
|
|
int password = 0;
|
|
int ccd_open = 0;
|
|
int ccd_unlock = 0;
|
|
int ccd_lock = 0;
|
|
int ccd_info = 0;
|
|
int get_flog = 0;
|
|
uint32_t prev_log_entry = 0;
|
|
int wp = 0;
|
|
int try_all_transfer = 0;
|
|
int tpm_mode = 0;
|
|
bool show_machine_output = false;
|
|
int tstamp = 0;
|
|
const char *tstamp_arg = NULL;
|
|
|
|
const char *exclusive_opt_error =
|
|
"Options -a, -s and -t are mutually exclusive\n";
|
|
const char *openbox_desc_file = NULL;
|
|
int factory_mode = 0;
|
|
char *factory_mode_arg;
|
|
char *tpm_mode_arg = NULL;
|
|
char *serial = NULL;
|
|
int sn_bits = 0;
|
|
uint8_t sn_bits_arg[SN_BITS_SIZE];
|
|
int sn_inc_rma = 0;
|
|
uint8_t sn_inc_rma_arg;
|
|
|
|
/*
|
|
* All options which result in setting a Boolean flag to True, along
|
|
* with addresses of the flags. Terminated by a zeroed entry.
|
|
*/
|
|
const struct options_map omap[] = {
|
|
{ 'B', &td.background_update_supported},
|
|
{ 'b', &binary_vers },
|
|
{ 'c', &corrupt_inactive_rw },
|
|
{ 'f', &show_fw_ver },
|
|
{ 'I', &ccd_info },
|
|
{ 'k', &ccd_lock },
|
|
{ 'o', &ccd_open },
|
|
{ 'P', &password },
|
|
{ 'p', &td.post_reset },
|
|
{ 'U', &ccd_unlock },
|
|
{ 'u', &td.upstart_mode },
|
|
{ 'V', &verbose_mode },
|
|
{ 'w', &wp },
|
|
{},
|
|
};
|
|
|
|
/*
|
|
* Explicitly sets buffering type to line buffered so that output
|
|
* lines can be written to pipe instantly. This is needed when the
|
|
* cr50-verify-ro.sh execution in verify_ro is moved from crosh to
|
|
* debugd.
|
|
*/
|
|
setlinebuf(stdout);
|
|
|
|
progname = strrchr(argv[0], '/');
|
|
if (progname)
|
|
progname++;
|
|
else
|
|
progname = argv[0];
|
|
|
|
/* Usb transfer - default mode. */
|
|
memset(&td, 0, sizeof(td));
|
|
td.ep_type = usb_xfer;
|
|
|
|
bid_action = bid_none;
|
|
errorcnt = 0;
|
|
opterr = 0; /* quiet, you */
|
|
|
|
while ((i = getopt_all(argc, argv)) != -1) {
|
|
if (check_boolean(omap, i))
|
|
continue;
|
|
switch (i) {
|
|
case 'a':
|
|
if (td.ep_type) {
|
|
errorcnt++;
|
|
fprintf(stderr, "%s", exclusive_opt_error);
|
|
break;
|
|
}
|
|
try_all_transfer = 1;
|
|
/* Try dev_xfer first. */
|
|
td.ep_type = dev_xfer;
|
|
break;
|
|
case 'd':
|
|
if (!parse_vidpid(optarg, &vid, &pid)) {
|
|
fprintf(stderr,
|
|
"Invalid device argument: \"%s\"\n",
|
|
optarg);
|
|
errorcnt++;
|
|
}
|
|
break;
|
|
case 'e':
|
|
get_endorsement_seed = 1;
|
|
endorsement_seed_str = optarg;
|
|
break;
|
|
case 'F':
|
|
factory_mode = 1;
|
|
factory_mode_arg = optarg;
|
|
break;
|
|
case 'h':
|
|
usage(errorcnt);
|
|
break;
|
|
case 'i':
|
|
if (!parse_bid(optarg, &bid, &bid_action)) {
|
|
fprintf(stderr,
|
|
"Invalid board id argument: \"%s\"\n",
|
|
optarg);
|
|
errorcnt++;
|
|
}
|
|
break;
|
|
case 'L':
|
|
get_flog = 1;
|
|
if (optarg)
|
|
prev_log_entry = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'M':
|
|
show_machine_output = true;
|
|
break;
|
|
case 'm':
|
|
tpm_mode = 1;
|
|
tpm_mode_arg = optarg;
|
|
break;
|
|
case 'n':
|
|
serial = optarg;
|
|
break;
|
|
case 'O':
|
|
openbox_desc_file = optarg;
|
|
break;
|
|
case 'r':
|
|
rma = 1;
|
|
rma_auth_code = optarg;
|
|
break;
|
|
case 'R':
|
|
sn_inc_rma = 1;
|
|
if (!parse_sn_inc_rma(optarg, &sn_inc_rma_arg)) {
|
|
fprintf(stderr,
|
|
"Invalid sn_rma_inc argument: \"%s\"\n",
|
|
optarg);
|
|
errorcnt++;
|
|
}
|
|
|
|
break;
|
|
case 's':
|
|
if (td.ep_type || try_all_transfer) {
|
|
errorcnt++;
|
|
fprintf(stderr, "%s", exclusive_opt_error);
|
|
break;
|
|
}
|
|
td.ep_type = dev_xfer;
|
|
break;
|
|
case 'S':
|
|
sn_bits = 1;
|
|
if (!parse_sn_bits(optarg, sn_bits_arg)) {
|
|
fprintf(stderr,
|
|
"Invalid sn_bits argument: \"%s\"\n",
|
|
optarg);
|
|
errorcnt++;
|
|
}
|
|
|
|
break;
|
|
case 't':
|
|
if (td.ep_type || try_all_transfer) {
|
|
errorcnt++;
|
|
fprintf(stderr, "%s", exclusive_opt_error);
|
|
break;
|
|
}
|
|
td.ep_type = ts_xfer;
|
|
break;
|
|
case 'T':
|
|
tstamp = 1;
|
|
tstamp_arg = optarg;
|
|
break;
|
|
case 'v':
|
|
report_version(); /* This will call exit(). */
|
|
break;
|
|
case 0: /* auto-handled option */
|
|
break;
|
|
case '?':
|
|
if (optopt)
|
|
fprintf(stderr, "Unrecognized option: -%c\n",
|
|
optopt);
|
|
else
|
|
fprintf(stderr, "Unrecognized option: %s\n",
|
|
argv[optind - 1]);
|
|
errorcnt++;
|
|
break;
|
|
case ':':
|
|
fprintf(stderr, "Missing argument to %s\n",
|
|
argv[optind - 1]);
|
|
errorcnt++;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Internal error at %s:%d\n",
|
|
__FILE__, __LINE__);
|
|
exit(update_error);
|
|
}
|
|
}
|
|
|
|
if (errorcnt)
|
|
usage(errorcnt);
|
|
|
|
/*
|
|
* If no usb device information was given, default to the using cr50
|
|
* vendor and product id to find the usb device.
|
|
*/
|
|
if (!serial && !vid && !pid) {
|
|
vid = VID;
|
|
pid = PID;
|
|
}
|
|
|
|
if ((bid_action == bid_none) &&
|
|
!ccd_info &&
|
|
!ccd_lock &&
|
|
!ccd_open &&
|
|
!ccd_unlock &&
|
|
!corrupt_inactive_rw &&
|
|
!get_flog &&
|
|
!get_endorsement_seed &&
|
|
!factory_mode &&
|
|
!password &&
|
|
!rma &&
|
|
!show_fw_ver &&
|
|
!sn_bits &&
|
|
!sn_inc_rma &&
|
|
!openbox_desc_file &&
|
|
!tstamp &&
|
|
!tpm_mode &&
|
|
!wp) {
|
|
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]);
|
|
if (data_len != CONFIG_FLASH_SIZE) {
|
|
fprintf(stderr, "Image file is not %d bytes\n",
|
|
CONFIG_FLASH_SIZE);
|
|
exit(update_error);
|
|
}
|
|
|
|
fetch_header_versions(data);
|
|
|
|
if (binary_vers)
|
|
exit(show_headers_versions(data, show_machine_output));
|
|
} else {
|
|
if (optind < argc)
|
|
printf("Ignoring binary image %s\n", argv[optind]);
|
|
}
|
|
|
|
|
|
if (((bid_action != bid_none) + !!rma + !!password + !!ccd_open +
|
|
!!ccd_unlock + !!ccd_lock + !!ccd_info + !!get_flog +
|
|
!!openbox_desc_file + !!factory_mode + !!wp +
|
|
!!get_endorsement_seed) > 1) {
|
|
fprintf(stderr,
|
|
"ERROR: options"
|
|
"-e, -F, -I, -i, -k, -L, -O, -o, -P, -r, -U and -w "
|
|
"are mutually exclusive\n");
|
|
exit(update_error);
|
|
}
|
|
|
|
if (td.ep_type == usb_xfer) {
|
|
if (usb_findit(serial, vid, pid, USB_SUBCLASS_GOOGLE_CR50,
|
|
USB_PROTOCOL_GOOGLE_CR50_NON_HC_FW_UPDATE,
|
|
&td.uep))
|
|
exit(update_error);
|
|
} else if (td.ep_type == dev_xfer) {
|
|
td.tpm_fd = open("/dev/tpm0", O_RDWR);
|
|
if (td.tpm_fd < 0) {
|
|
if (!try_all_transfer) {
|
|
perror("Could not open TPM");
|
|
exit(update_error);
|
|
}
|
|
td.ep_type = ts_xfer;
|
|
}
|
|
}
|
|
|
|
if (openbox_desc_file)
|
|
return verify_ro(&td, openbox_desc_file, show_machine_output);
|
|
|
|
if (ccd_unlock || ccd_open || ccd_lock || ccd_info)
|
|
process_ccd_state(&td, ccd_unlock, ccd_open,
|
|
ccd_lock, ccd_info);
|
|
|
|
if (password)
|
|
process_password(&td);
|
|
|
|
if (bid_action != bid_none)
|
|
process_bid(&td, bid_action, &bid, show_machine_output);
|
|
|
|
if (get_endorsement_seed)
|
|
exit(process_endorsement_seed(&td, endorsement_seed_str));
|
|
|
|
if (rma)
|
|
process_rma(&td, rma_auth_code);
|
|
|
|
if (factory_mode)
|
|
process_factory_mode(&td, factory_mode_arg);
|
|
if (wp)
|
|
process_wp(&td);
|
|
|
|
if (corrupt_inactive_rw)
|
|
invalidate_inactive_rw(&td);
|
|
|
|
if (tpm_mode) {
|
|
int rv = process_tpm_mode(&td, tpm_mode_arg);
|
|
|
|
exit(rv);
|
|
}
|
|
|
|
if (tstamp)
|
|
return process_tstamp(&td, tstamp_arg);
|
|
|
|
if (sn_bits)
|
|
process_sn_bits(&td, sn_bits_arg);
|
|
|
|
if (sn_inc_rma)
|
|
process_sn_inc_rma(&td, sn_inc_rma_arg);
|
|
|
|
if (get_flog)
|
|
process_get_flog(&td, prev_log_entry);
|
|
|
|
if (data || show_fw_ver) {
|
|
|
|
setup_connection(&td);
|
|
|
|
if (data) {
|
|
transferred_sections = transfer_image(&td,
|
|
data, data_len);
|
|
free(data);
|
|
}
|
|
|
|
/*
|
|
* Move USB updater sate machine to idle state so that vendor
|
|
* commands can be processed later, if any.
|
|
*/
|
|
if (td.ep_type == usb_xfer)
|
|
send_done(&td.uep);
|
|
|
|
if (transferred_sections)
|
|
generate_reset_request(&td);
|
|
|
|
if (show_fw_ver) {
|
|
if (show_machine_output) {
|
|
print_machine_output("RO_FW_VER", "%d.%d.%d",
|
|
targ.shv[0].epoch,
|
|
targ.shv[0].major,
|
|
targ.shv[0].minor);
|
|
print_machine_output("RW_FW_VER", "%d.%d.%d",
|
|
targ.shv[1].epoch,
|
|
targ.shv[1].major,
|
|
targ.shv[1].minor);
|
|
} else {
|
|
printf("Current versions:\n");
|
|
printf("RO %d.%d.%d\n", targ.shv[0].epoch,
|
|
targ.shv[0].major, targ.shv[0].minor);
|
|
printf("RW %d.%d.%d\n", targ.shv[1].epoch,
|
|
targ.shv[1].major, targ.shv[1].minor);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (td.ep_type == usb_xfer) {
|
|
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;
|
|
}
|