Update documentation according to new firmware protocol

This commit is contained in:
Michael Cardell Widerkrantz 2022-11-24 16:37:23 +01:00 committed by Joachim Strömbergson
parent 18433df357
commit 0331bddc10

View File

@ -13,8 +13,9 @@ Learn more about the concepts in the
## CPU ## CPU
We use a PicoRV32, a 32-bit RISC-V system, as the CPU for running the We use a PicoRV32, a 32-bit RISC-V system (RV32IMC), as the CPU for
firmware and the loaded app. All types are little-endian. running the firmware and the loaded app. The firmware and device app
both run in machine mode. All types are little-endian.
## Constraints ## Constraints
@ -34,7 +35,7 @@ specifications:
The Tillitis Key has two modes of operation; firmware/loader mode and The Tillitis Key has two modes of operation; firmware/loader mode and
application mode. The firmware mode has the responsibility of application mode. The firmware mode has the responsibility of
receiving, measuring, and loading the application. receiving, measuring, loading, and starting the application.
The firmware and application uses a memory mapped IO for SoC The firmware and application uses a memory mapped IO for SoC
communication. This MMIO resides at `0xc000_0000`. *Nota bene*: Almost communication. This MMIO resides at `0xc000_0000`. *Nota bene*: Almost
@ -44,21 +45,19 @@ The application has a constrained variant of the firmware memory map,
which is outlined below. E.g. UDS isn't readable, and the `APP_{ADDR, which is outlined below. E.g. UDS isn't readable, and the `APP_{ADDR,
SIZE}` are not writable for the application. SIZE}` are not writable for the application.
The software on the Tillitis Key communicates to the host via the The firmware (and optionally all software) on the Tillitis Key
`UART_{RX,TX}_{STATUS,DATA}` registers, using the framing protocol communicates to the host via the `UART_{RX,TX}_{STATUS,DATA}`
described in [Framing registers, using the framing protocol described in [Framing
Protocol](../framing_protocol/framing_protocol.md). Protocol](../framing_protocol/framing_protocol.md).
The firmware defines a protocol (command/response interface) on top of The firmware defines a protocol (command/response interface) on top of
the framing layer, which is used to bootstrap the application onto the this framing layer which is used to bootstrap the application onto the
device. device. All commands are initiated by the host. All commands receive a
reply.
On the framing layer, it's required that each frame the device Applications define their own per-application protocol used for
receives, a responding frame must be sent back to the host, in a communication with their host part. They may or may not be based on
ping-pong manner. the Framing Protocol.
Applications define a per-application protocol, which is the contract
between the host and the device.
## Firmware ## Firmware
@ -86,40 +85,48 @@ The purpose of the firmware is to bootstrap an application. The host
will send a raw binary targeted to be loaded at `0x4000_7000` in the will send a raw binary targeted to be loaded at `0x4000_7000` in the
device. device.
1. The host sends the User Supplied Secret (USS) by using the 1. The host sends the `FW_CMD_LOAD_APP` command with the size of the
`FW_CMD_LOAD_USS` command and gets a `FW_RSP_LOAD_USS` back. device app and the user-supplied secret as arguments and and gets
2. The host sends the size of the app by using the a `FW_RSP_LOAD_APP` back.
`FW_CMD_LOAD_APP_SIZE` command. 2. If the the host receive a sucessful response, it will send
3. The firmware executes `FW_CMD_LOAD_APP_SIZE` command, which
stores the application size into `APP_SIZE`, and sets `APP_ADDR`
to zero. A `FW_RSP_LOAD_APP_SIZE` reponse is sent back to the
host, with the status of the action (ok/fail).
4. If the the host receive a sucessful response, it will send
multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
full application. full application.
5. For each received `FW_CMD_LOAD_APP_DATA` command the firmware 3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
places the data into `0x4000_7000` and upwards. The firmware the data into `0x4000_7000` and upwards. The firmware replies
replies with a `FW_RSP_LOAD_APP_DATA` response to the host for with a `FW_RSP_LOAD_APP_DATA` response to the host for each
each received block. received block except the last data block.
6. When the final block of the application image is received, we 4. When the final block of the application image is received with a
measure the application by computing a BLAKE2s digest over the `FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
entire application, computing a BLAKE2s digest over the entire application. Then
firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
containing the measurement.
5. The Compound Device Identifier (CDI) is then computed by using
the `UDS`, the measurement of the application, and the `USS`, and
placed in the `CDI` register. Then the start address of the
device app, `0x4000_7000`, is written to `APP_ADDR` and the size
to `APP_SIZE` to let the device application know where it is
loaded and how large it is, if it wants to relocate in RAM.
6. The firmware now starts the application by switching to
application mode by writing to the `SWITCH_APP` register. In this
mode the MMIO region is restricted; e.g. some registers are
removed (`UDS`), and some are switched from read/write to
read-only. This is outlined in the memory map below.
The Compound Device Identifier is computed by using the `UDS`, The firmware now executes assembler code that writes zeros to
the measurement of the application, and the `USS`, and placed in stack and data of the firmware, then jumps to what's in
the `CDI` register. Then `0x4000_7000` is written to `APP_ADDR`. `APP_ADDR` which starts device app execution.
The final `FW_RSP_LOAD_APP_DATA` response is sent to the host,
completing the loading.
NOTE: The firmware uses SPRAM for data and stack. We need to make sure There is now no other means of getting back from application mode
that the application image does not overwrite the firmware's running to firmware mode than resetting/power cycling the device.
state. The application should probably do a similar relocation for
stack/data at reset, as the firmware does. Further; the firmware need
to check application image is sane. The shared firmware data area
(e.g. `.data` and the stack must be cleared prior launching the
application.
### CDI computation ### User-supplied Secret (USS)
USS is a 32 bytes long secret provided by the user. Typically a host
program gets a passphrase from the user and then does KDF of some
sort, for instance a BLAKE2s, to get 32 bytes which it sends to the
firmware to be part of the CDI computation.
### Compound Device Identifier (CDI) computation
The CDI is computed by: The CDI is computed by:
@ -139,62 +146,100 @@ The `blake2s()` function in the firmware is fed with a buffer in
fed with a context used for computations that is also part of the fed with a context used for computations that is also part of the
`FW_RAM`. `FW_RAM`.
### Loading the User Supplied Secret (USS) After doing the computation `FW_RAM` is also cleared with zeroes.
The host program may send `FW_CMD_LOAD_USS` and `FW_CMD_LOAD_APP_SIZE` ### Firmware protocol definition
in any order. But it *should* always send both `FW_CMD_LOAD_USS` and
`FW_CMD_LOAD_APP_SIZE` before sending the multiple
`FW_CMD_LOAD_APP_DATA`. If it does not, the USS will not be
predictable because somebody could have send `FW_CMD_LOAD_USS` before,
and the last `FW_CMD_LOAD_APP_DATA` (on whichever iteration) will
cause the currently loaded USS to be used for calculating CDI.
### Starting an application The firmware commands and responses are built on top of the [Framing
Protocol](../framing_protocol/framing_protocol.md).
Starting an application includes the "switch to application mode" The commands look like this:
step, which is done by writing to the `SWITCH_APP` register. The
switch from firmware mode to application mode is a mode switch, and
context switch. When entering application mode the MMIO region is
restricted; e.g. some registers are removed (`UDS`), and some are
switched from read/write to read-only. This is outlined in the memory
map below.
There is no other means of getting back from application mode to | *name* | *size (bytes)* | *comment* |
firmware mode than resetting/power cycling the device. |------------------|----------------|------------------------------------------|
| Header | 1 | Framing protocol header including length |
| Command/Response | 1 | Any of the below commands or responses |
| Data | n | Any additional data |
Prerequisites: `APP_SIZE` and `APP_ADDR` has to be non-zero. The responses might include a one byte status field where 0 is
Procedure: `STATUS_OK` and 1 is `STATUS_BAD`.
1. The host sends `FW_CMD_RUN_APP` to the device. #### `FW_CMD_NAME_VERSION` (0x01)
2. The firmware responds with `FW_RSP_RUN_APP`
3. The firmware writes to `SWITCH_APP`, and executes assembler code
that writes zeros to stack and data of the firmware, then jumps
to what's in APP_ADDR.
4. The device is now in application mode and is executing the
application.
### Protocol definition Get the name and version of the stick.
Available commands/reponses: #### `FW_RSP_NAME_VERSION` (0x02)
#### `FW_{CMD,RSP}_LOAD_USS` | *name* | *size (bytes)* | *comment* |
#### `FW_{CMD,RSP}_LOAD_APP_SIZE` |---------|----------------|-----------------|
#### `FW_{CMD,RSP}_LOAD_APP_DATA` | name0 | 4 | ASCII |
#### `FW_{CMD,RSP}_RUN_APP` | name1 | 4 | ASCII |
#### `FW_{CMD,RSP}_NAME_VERSION` | version | 4 | Integer version |
#### `FW_{CMD,RSP}_UDI`
#### `FW_{CMD,RSP}_VERIFY_DEVICE` In a bad response the fields will be zeroed.
Verification that the device is an authentic Tillitis #### `FW_CMD_LOAD_APP` (0x03)
device. Implemented using challenge/response.
#### `FW_{CMD,RSP}_GET_APP_DIGEST` | *name* | *size (bytes)* | *comment* |
|--------------|----------------|---------------------|
| size | 4 | |
| uss-provided | 1 | 0 = false, 1 = true |
| uss | 32 | Ignored if above 0 |
This command returns the un-keyed hash digest for the application that Start an application loading session by setting the size of the
was loaded. It allows the host to verify that the application was expected device application and a user-supplied secret, if
correctly loaded. This means that the CDI calculated will be correct `uss-provided` is 1. Otherwise `USS` is ignored.
given that the UDS has not been modified.
#### `FW_RSP_LOAD_APP` (0x04)
Response to `FW_CMD_LOAD_APP`
| *name* | *size (bytes)* | *comment* |
|--------|----------------|-----------------------------|
| status | 1 | `STATUS_OK` or `STATUS_BAD` |
#### `FW_CMD_LOAD_APP_DATA` (0x05)
| *name* | *size (bytes)* | *comment* |
|--------|----------------|---------------------|
| data | 127 | Raw binary app data |
Load 127 bytes of raw app binary into device RAM. Should be sent
consecutively over the complete raw binary.
#### `FW_RSP_LOAD_APP_DATA` (0x06)
Response to all but the ultimate `FW_CMD_LOAD_APP_DATA` commands.
| *name* | *size (bytes)* | *comment* |
|--------|----------------|--------------------------|
| status | 1 | `STATUS_OK`/`STATUS_BAD` |
#### `FW_RSP_LOAD_APP_DATA_READY` (0x07)
The response to the last `FW_CMD_LOAD_APP_DATA` is an
`FW_RSP_LOAD_APP_DATA_READY` with the un-keyed hash digest for the
application that was loaded. It allows the host to verify that the
application was correctly loaded. This means that the CDI calculated
will be correct given that the UDS has not been modified.
| *name* | *size (bytes)* | *comment* |
|--------|----------------|--------------------------|
| status | 1 | `STATUS_OK`/`STATUS_BAD` |
| digest | 32 | BLAKE2s(app) |
#### `FW_CMD_GET_UDI` (0x08)
Ask for the Unique Device Identifier (UDI) of the device.
#### `FW_RSP_GET_UDI` (0x09)
Response to `FW_CMD_GET_UDI`.
| *name* | *size (bytes)* | *comment* |
|--------|----------------|-------------------------------------------------|
| status | 1 | `STATUS_OK`/`STATUS_BAD` |
| udi | 8 | Vendor (2B), Product (1B), rev(4b), serial (4B) |
#### Get the name and version of the device #### Get the name and version of the device
@ -225,37 +270,19 @@ host ->
u8 CMD[1 + 128]; u8 CMD[1 + 128];
CMD[0].len = 128 // command frame format CMD[0].len = 128 // command frame format
CMD[1] = 0x0a // FW_CMD_LOAD_USS CMD[1] = 0x03 // FW_CMD_LOAD_APP
CMD[2..6] = User Supplied Secret
CMD[6..] = 0
host <-
u8 RSP[1 + 4];
RSP[0].len = 4 // command frame format
RSP[1] = 0x0b // FW_RSP_LOAD_USS
RSP[2] = STATUS
RSP[3..] = 0
host ->
u8 CMD[1 + 32];
CMD[0].len = 32 // command frame format
CMD[1] = 0x03 // FW_CMD_LOAD_APP_SIZE
CMD[2..6] = APP_SIZE CMD[2..6] = APP_SIZE
CMD[6..] = 0 CMD[6] = USS supplied? 0 = false, 1 = true
CMD[7..39] = USS
CMD[40..] = 0
host <- host <-
u8 RSP[1 + 4]; u8 RSP[1 + 4];
RSP[0].len = 4 // command frame format RSP[0].len = 4 // command frame format
RSP[1] = 0x04 // FW_RSP_LOAD_APP_SIZE RSP[1] = 0x04 // FW_RSP_LOAD_APP
RSP[2] = STATUS RSP[2] = STATUS
@ -281,6 +308,21 @@ host <-
RSP[3..] = 0 RSP[3..] = 0
``` ```
Except response from last chunk of app data which is:
```
host <-
u8 RSP[1 + 4]
RSP[0].len = 128 // command frame format
RSP[1] = 0x07 // FW_RSP_LOAD_APP_DATA_READY
RSP[2] = STATUS
RSP[3..35] = app digest
RSP[36..] = 0
```
### Memory map ### Memory map
Assigned top level prefixes: Assigned top level prefixes: