203 lines
9.2 KiB
Markdown
203 lines
9.2 KiB
Markdown
|
EC update over USB
|
||
|
==================
|
||
|
|
||
|
chip/g (Cr50) and common code (hammer, servo_micro/v4) update over USB protocols
|
||
|
share a lot in terms of protocol and ideas, but use different code bases.
|
||
|
|
||
|
chip/g EC-side implementation is found in `chip/g/*upgrade*`, and the
|
||
|
userspace tool which provides updates over USB among with supporting other
|
||
|
features and interfaces is found in `extra/usb_updater/gsctool.c`.
|
||
|
|
||
|
Common code uses implementations in `common/*update*.c` and
|
||
|
`include/*update*.h`, and `extra/usb_updater/usb_updater2.c` for the userspace
|
||
|
updater.
|
||
|
|
||
|
Cr50-specific notes
|
||
|
-------------------
|
||
|
|
||
|
The Cr50 firmware image consists of multiple sections, of interest to the
|
||
|
USB updater 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 currently used by the device).
|
||
|
|
||
|
Based on this information the updater carves out the appropriate sections from
|
||
|
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 `gsctool` 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.
|
||
|
|
||
|
Common-code notes
|
||
|
-----------------
|
||
|
|
||
|
For non-Cr50 or chip/g devices (common code), the layout is a bit different,
|
||
|
as devices usually have a single RO and a single RW, where RO is truly read-only
|
||
|
in production, and verifies RW before jumping to it.
|
||
|
|
||
|
For testing and development, `usb_updater2` is provided, while production code
|
||
|
will use `hammerd` (in `src/platform/hammerd`) to update the device.
|
||
|
|
||
|
Update protocol
|
||
|
---------------
|
||
|
|
||
|
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 processes it and
|
||
|
responds.
|
||
|
|
||
|
The encapsulation 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 units (PDUs) are passed over `/dev/tpm0`, the
|
||
|
encapsulation includes integrity 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 connection 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.
|
||
|
|
||
|
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,
|
||
|
and there is 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 ability to channel TPM vendor commands
|
||
|
through USB connection.
|
||
|
|
||
|
Common-code updater also uses protocol version 6, but has a fairly different
|
||
|
`first_response_pdu` header, indicated by setting `1` in the higher 16-bit for
|
||
|
the protocol version field (`header_type`). The response includes fields
|
||
|
such as maximum PDU size (which is not fixed to 1KB like for Cr50), flash
|
||
|
protection status, version string, and a minimum rollback version.
|
||
|
|
||
|
Details can be found in `include/update_fw.h`.
|
||
|
|
||
|
### State machine (update over USB)
|
||
|
|
||
|
This describes the EC-side state machine for update over USB.
|
||
|
|
||
|
IDLE state:
|
||
|
|
||
|
* If host sends update start PDU (a command without any payload, digest = 0
|
||
|
and base = 0):
|
||
|
|
||
|
* Reply with `first_update_pdu` block. Go to OUTSIDE_BLOCK state.
|
||
|
|
||
|
* If host sends a vendor command (see below), execute that, reply, and stay
|
||
|
in IDLE state. Note that vendor commands are only accepted in IDLE state.
|
||
|
|
||
|
OUTSIDE_BLOCK (preparing to receive start of PDU):
|
||
|
|
||
|
* If no data is received in 5 seconds, go back to IDLE state.
|
||
|
* If host sends `UPDATE_DONE` command (by setting `dest address` to
|
||
|
`0xb007ab1e`), go back to IDLE state.
|
||
|
* If host sends a valid block start with a valid address, copy the rest
|
||
|
of the payload and go to INSIDE_BLOCK state.
|
||
|
|
||
|
INSIDE_BLOCK (in a middle of a PDU):
|
||
|
|
||
|
* If no data is received in 5 seconds, go back to IDLE state.
|
||
|
* Copy data to a buffer.
|
||
|
|
||
|
* If buffer is full (i.e. matches the total expected PDU size), write the
|
||
|
data and go to OUTSIDE_BLOCK.
|
||
|
* Else, stay in INSIDE_BLOCK.
|
||
|
|
||
|
### Vendor commands (channeled TPM command, Cr50)
|
||
|
|
||
|
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.
|
||
|
|
||
|
These commands cannot exceed the USB packet size (typically 64 bytes), as
|
||
|
no reassembly is performed for such frames.
|
||
|
|
||
|
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.
|
||
|
|
||
|
### Vendor commands (common code)
|
||
|
|
||
|
Vendor commands for command code look very similar to the TPM vendor commands
|
||
|
above, except that we use `UPDATE_EXTRA_CMD` (`b007ab1f`) instead of `EXT_CMD`,
|
||
|
and `Vend. sub.` have a limit set of values (unless otherwise noted, commands
|
||
|
take no parameter, and reply with a single 1-byte status code):
|
||
|
|
||
|
* UPDATE_EXTRA_CMD_IMMEDIATE_RESET (0): Tell EC to reboot immediately.
|
||
|
* UPDATE_EXTRA_CMD_JUMP_TO_RW (1): Tell EC (in RO) to jump to RW, if the
|
||
|
signature verifies.
|
||
|
* UPDATE_EXTRA_CMD_STAY_IN_RO (2): Tell EC (in RO), to stay in RO, and not
|
||
|
jump to RW automatically. After this command is sent, a reset is necessary
|
||
|
for the EC to accept to jump to RW again.
|
||
|
* UPDATE_EXTRA_CMD_UNLOCK_RW (3): Tell EC to unlock RW on next reset.
|
||
|
* UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK (4): Tell EC to unlock ROLLBACK on next reset.
|
||
|
* UPDATE_EXTRA_CMD_INJECT_ENTROPY (5): Inject entropy into the device-specific
|
||
|
unique identifier (takes at least CONFIG_ROLLBACK_SECRET_SIZE=32 bytes of
|
||
|
data).
|
||
|
* UPDATE_EXTRA_CMD_PAIR_CHALLENGE (6): Tell EC to answer a X25519 challenge
|
||
|
for pairing. Takes in a `struct pair_challenge` as data, answers with a
|
||
|
`struct pair_challenge_response`.
|