Merge branch 'main' of github.com:tillitis/tillitis-key1

This commit is contained in:
Joachim Strömbergson 2022-09-19 12:30:58 +02:00
commit 8785da9280

View File

@ -1,21 +1,22 @@
# Tillitis Key software
# NOTE: this document is outdated, an update is pending.
# MTA1-MKDF software
## Definitions ## Definitions
* Firmware -- software that is part of ROM, and is supplied via the
FPGA bit stream
* Secure Application (short: Application) -- software supplied by
the host machine, which is received, measured, and loaded by the
firmware.
## Types * Firmware -- software that is part of ROM, and is currently
The PicoRV32 is a 32-bit RISC-V system. All types are little-endian. supplied via the FPGA bit stream.
* Application -- software supplied by the host machine, which is
received, loaded, and measured by the firmware.
## CPU
We use a PicoRV32, a 32-bit RISC-V system, as the CPU for running the
firmware and the loaded app. All types are little-endian.
## Constraints ## Constraints
The application FPGA is a Lattice UP5K, with the following The application FPGA is a Lattice UP5K, with the following
specifications: specifications:
* 32KB x 4 SPRAM => 128KB for Application * 32KB x 4 SPRAM => 128KB for Application
* 4Kb x 30 EBR => 120Kb, PicoRV32 uses ~4 EBR internally => 13KB for * 4Kb x 30 EBR => 120Kb, PicoRV32 uses ~4 EBR internally => 13KB for
Firmware. We should probably aim for less; 8KB should be the Firmware. We should probably aim for less; 8KB should be the
@ -23,19 +24,21 @@ specifications:
## Introduction ## Introduction
The MTA1_MKDF 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 receive, application mode. The firmware mode has the responsibility of
measure, and load the application. receiving, measuring, and loading 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. The memory map resides at `0x9000_0000`. The communication. This MMIO resides at `0xc000_0000`. *Nota bene*: All
application has a constrained variant of the firmware memory map, access to MMIO should be word (32 bit) aligned.
which is outlined below. E.g. UID and UDA are not readable, and the
`APP_{ADDR, SIZE}` are not writable for the application.
The MTA1_MKDF software communicates to the host via the `{RX,TX}_FIFO` The application has a constrained variant of the firmware memory map,
registers, using the framing protocol described in [Framing which is outlined below. E.g. UDS isn't readable, and the `APP_{ADDR,
Protocol](../framing_protocol/framing_protocol.md). SIZE}` are not writable for the application.
The software on the Tillitis Key communicates to the host via the
`{RX,TX}_FIFO` registers, using the framing protocol described in
[Framing 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 the framing layer, which is used to bootstrap the application onto the
@ -50,24 +53,30 @@ between the host and the device.
## Firmware ## Firmware
The firware is part of FPGA bitstream (ROM), and is loaded at The device has 128 kB RAM. The current firmware loads the app at the
`0x0000_1000`. upper 64 kB. The lower 64 kB is currently set up as stack for the app.
The firmware is part of FPGA bitstream (ROM), and is loaded at
`0x0000_0000`.
### Reset ### Reset
The PicoRV32 executes `_start` from `crt0.S` `.text` at `0x0000_1000`, The PicoRV32 starts executing at `0x0000_0000`. Our firmware starts at
which initializes the stack, `.data`, and `.bss` at `_start` from `start.S` which initializes the `.data`, and `.bss` at
`0x8000_0000`. When the initialization is finished, the firmware waits `0x4000_0000` and upwards. A stack is also initialized, starting at
for incoming commands from the host, by busy-polling the 0x4000_fff0 and downwards. When the initialization is finished, the
`RX_FIFO_{AVAILABLE,DATA}`registers. When a complete command is read, firmware waits for incoming commands from the host, by busy-polling
the firmware executes the command. the `RX_FIFO_{AVAILABLE,DATA}`registers. When a complete command is
read, the firmware executes the command.
### Loading an application ### Loading an application
The purpose of the firmware is to bootstrapping an application. The purpose of the firmware is to bootstrap an application. The host
1. The host sends a raw binary, targeted to be loaded at will send a raw binary targeted to be loaded at `0x4001_0000` in the
`0x8000_0000` in the device. The host starts off by sending the device.
binary size using the `FW_CMD_LOAD_APP_SIZE` command.
1. The host sends sets the size of the app by using the
`FW_CMD_LOAD_APP_SIZE` command.
2. The firmware executes `FW_CMD_LOAD_APP_SIZE` command, which 2. The firmware executes `FW_CMD_LOAD_APP_SIZE` command, which
stores the application size into `APP_SIZE`, and sets `APP_ADDR` 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 to zero. A `FW_RSP_LOAD_APP_SIZE` reponse is sent back to the
@ -75,16 +84,19 @@ The purpose of the firmware is to bootstrapping an application.
3. If the the host receive a sucessful command, it will send 3. If the the host receive a sucessful command, it will send
multiple `FW_CMD_LOAD_APP_DATA` commands, containing the full multiple `FW_CMD_LOAD_APP_DATA` commands, containing the full
application. application.
4. For each received `FW_CMD_LOAD_APP_DATA` commands, the firmware 4. For each received `FW_CMD_LOAD_APP_DATA` command the firmware
measures (XXX define how blake2s is used) the data, and places it places the data into `0x4001_0000` and upwards. The firmware
into `0x8000_0000`. The firmware response with response with `FW_RSP_LOAD_APP_DATA` response to the host for
`FW_RSP_LOAD_APP_DATA` response to the host for each received each received block.
block. 5. When the final block of the application image is received, we
5. When the final block of the application image is received, measure the application by computing a BLAKE2s digest over the
`0x8000_0000` is written to `APP_ADDR`. The `CDI` is computed by entire application,
used the `UDS` and measurement from the application, and placed
in the `CDI` register. The final `FW_RSP_LOAD_APP_DATA` response The Compound Device Identifier is computed by using the `UDS` and
is sent to the host, completing the loading. the measurement of the application, and placed in the `CDI`
register. Then `0x4001_0000` is written to `APP_ADDR`. 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 NOTE: The firmware uses SPRAM for data and stack. We need to make sure
that the application image does not overwrite the firmware's running that the application image does not overwrite the firmware's running
@ -97,31 +109,31 @@ application.
### Starting an application ### Starting an application
Starting an application includes the "switch to application mode" Starting an application includes the "switch to application mode"
step, which is done by writing to the `SWITCH_APP` regiester. The step, which is done by writing to the `SWITCH_APP` register. The
switch from firmware mode to application mode is a mode switch, and switch from firmware mode to application mode is a mode switch, and
context switch. Enter application mode, means the the MMIO region is context switch. When entering application mode the MMIO region is
restricted; E.g. some registers are removed (`UDS`), and some are restricted; e.g. some registers are removed (`UDS`), and some are
switched from read/write to read-only. This is outlined in the memory switched from read/write to read-only. This is outlined in the memory
map below. map below.
There is no other means of getting back from application mode to There is no other means of getting back from application mode to
firmware mode, than resetting/power cycling the device. firmware mode than resetting/power cycling the device.
Prerequisites: `APP_SIZE` and `APP_ADDR` has to be non-zero. Prerequisites: `APP_SIZE` and `APP_ADDR` has to be non-zero.
Procedure:
1. The host sends `FW_CMD_RUN_APP` to the device. 1. The host sends `FW_CMD_RUN_APP` to the device.
2. The firmware respons with `FW_RSP_RUN_APP` 2. The firmware responds with `FW_RSP_RUN_APP`
3. The firmware writes a non-zero to `SWITCH_APP`, and executes 3. The firmware writes a non-zero to `SWITCH_APP`, and executes
``` assembler code that writes zeros to stack and data of the
// a0 = 0x9000_0000 + 0x420 (APP_ADDR address) firmware, then jumps to what's in APP_ADDR.
lw a0,1056(a0) 4. The device is now in application mode and is executing the
jalr x0,0(a0) application.
```
4. The device is now in application mode, and executes the code from
`0x8000_0000`.
### Protocol definition ### Protocol definition
Available commands/reponses: Available commands/reponses:
#### `FW_{CMD,RSP}_LOAD_APP_SIZE` #### `FW_{CMD,RSP}_LOAD_APP_SIZE`
#### `FW_{CMD,RSP}_LOAD_APP_DATA` #### `FW_{CMD,RSP}_LOAD_APP_DATA`
#### `FW_{CMD,RSP}_RUN_APP` #### `FW_{CMD,RSP}_RUN_APP`
@ -129,20 +141,19 @@ Available commands/reponses:
#### `FW_{CMD,RSP}_UID` #### `FW_{CMD,RSP}_UID`
#### `FW_{CMD,RSP}_TRNG_DATA` #### `FW_{CMD,RSP}_TRNG_DATA`
#### `FW_{CMD,RSP}_TRNG_STATUS` #### `FW_{CMD,RSP}_TRNG_STATUS`
#### `FW_{CMD,RSP}_VERIFY_DEVICE` #### `FW_{CMD,RSP}_VERIFY_DEVICE`
Verification that the device is an authentic Mullvad Verification that the device is an authentic Mullvad
device. Implemented using challenge/response. device. Implemented using challenge/response.
#### `FW_{CMD,RSP}_GET_APPLICATION_DIGEST` #### `FW_{CMD,RSP}_GET_APPLICATION_DIGEST`
This command returns the un-keyed hash digest for the application that This command returns the un-keyed hash digest for the application that
was loaded. It allows the host to verify that the application was was loaded. It allows the host to verify that the application was
correctly loaded. This means that the CDI calculated will be correct correctly loaded. This means that the CDI calculated will be correct
given that the UDS has not been modified. given that the UDS has not been modified.
XXX Should we think a bit more about versioning/possiblity to extend?
Is 1B enough for a command/response range?
#### Get the name and version of the device #### Get the name and version of the device
``` ```
@ -166,11 +177,12 @@ host <-
``` ```
#### Load an application #### Load an application
``` ```
host -> host ->
u8 CMD[1 + 32]; u8 CMD[1 + 32];
CMD[0].len = 5 // command frame format CMD[0].len = 4 // command frame format
CMD[1] = 0x03 // FW_CMD_LOAD_APP_SIZE CMD[1] = 0x03 // FW_CMD_LOAD_APP_SIZE
CMD[2..6] = APP_SIZE CMD[2..6] = APP_SIZE
@ -187,20 +199,19 @@ host <-
RSP[3..] = 0 RSP[3..] = 0
repeat ceil(APP_SIZE / 127) times:
repeat ceil(APP_SIZE / 63) times:
host -> host ->
u8 CMD[1 + 64]; u8 CMD[1 + 128];
CMD[0].len = 65 // command frame format CMD[0].len = 128 // command frame format
CMD[1] = 0x05 // FW_CMD_LOAD_APP_DATA CMD[1] = 0x05 // FW_CMD_LOAD_APP_DATA
CMD[2..] = APP_DATA (pad with zeros) CMD[2..] = APP_DATA (127 bytes of app data, pad with zeros)
host <- host <-
u8 RSP[1 + 4] u8 RSP[1 + 4]
RSP[0].len = 5 // command frame format RSP[0].len = 4 // command frame format
RSP[1] = 0x06 // FW_RSP_LOAD_APP_DATA RSP[1] = 0x06 // FW_RSP_LOAD_APP_DATA
RSP[2] = STATUS RSP[2] = STATUS
@ -210,37 +221,106 @@ host <-
### Memory map ### Memory map
The memory map exposes SoC functionality to the software, when in Assigned top level prefixes:
firmware mode (privileged mode) It is s set of memory mapped
registers, starting at base address `0x9000_0000`.
| *name* | *r/w* | *offset* | *size* | *type* | *content* | *description* | | *name* | *prefix* | *address length* |
|--------------------|-------|----------|--------|---------|-----------|---------------------------------------------------------| |----------|----------|--------------------------------------|
| UDS[^1] | r | 0x0 | 32B | u8[32] | | Unique Device Secret key. | | ROM | 0b00 | 30 bit address |
| UDA | r | 0x20 | 16B | u8[16] | | Unique Device Authentication key. | | RAM | 0b01 | 30 bit address |
| SWITCH_APP | w | 0x30 | 1B | u8 | | Switch to application mode. Write non-zero to trigger. | | reserved | 0b10 | |
| XXX 460 bytes hole | | | | | | | | MMIO | 0b11 | 6 bits for core select, 24 bits rest |
| UDI | r | 0x200 | 8B | u64 | | Unique Device ID (UDI). |
| NAME0 | r | 0x208 | 4B | char[4] | "mta1" | | Addressing:
| NAME1 | r | 0x20c | 4B | char[4] | "mkdf" | |
| VERSION | r | 0x210 | 4B | u32 | 1 | Current version. | ```
| RX_FIFO_AVAILABLE | r | 0x214 | 1B | u8 | | Non-zero if a valid byte can be read from RX_FIFO_DATA. | 31st bit 0th bit
| RX_FIFO_DATA | r | 0x215 | 1B | u8 | | FIFO Rx data. | v v
| TX_FIFO_AVAILABLE | r | 0x216 | 1B | u8 | | Non-zero if a valid byte can be written to TX_FIFO_DATA | 0000 0000 0000 0000 0000 0000 0000 0000
| TX_FIFO_DATA | w | 0x217 | 1B | u8 | | FIFO Tx data. |
| LED | r/w | 0x218 | 4B | u32 | | LED | - Bits [31 .. 30] (2 bits): Top level prefix (described above)
| COUNTER | r | 0x21c | 4B | u32 | | Counter | - Bits [29 .. 24] (6 bits): Core select. We want to support at least 16 cores
| TRNG_STATUS | r | 0x220 | 4B | u32 | | data_ready/error | - Bits [23 .. 0] (24 bits): Memory/in-core address.
| TRNG_DATA | r | 0x224 | 4B | u32 | | TRNG data | ```
| XXX 472 bytes hole | | | | | | |
| CDI | r/w | 0x400 | 32B | u8[32] | | Compound Device Identifier (CDI). UDS+measurement... | The memory exposes SoC functionality to the software when in firmware
| APP_ADDR | r/w | 0x420 | 4B | u32 | | Application address (0x8000_0000) | mode. It is a set of memory mapped registers (MMIO), starting at base
| APP_SIZE | r/w | 0x424 | 4B | u32 | | Application size | address `0xc000_0000`. For specific offsets/bitmasks, see the file
[mta1_mkdf_mem.h](../../hw/application_fpga/fw/mta1_mkdf_mem.h) (in
this repo).
Assigned core prefixes:
| *name* | *prefix* |
|--------|----------|
| ROM | 0x00 |
| RAM | 0x40 |
| TRNG | 0xc0 |
| TIMER | 0xc1 |
| UDS | 0xc2 |
| UART | 0xc3 |
| TOUCH | 0xc4 |
| MTA1 | 0xff |
| | |
Every core has a NAME1, NAME2, and VERSION to identify it.
Examples:
| *address* | *description* |
|------------|-----------------|
| 0xc3000004 | NAME1 in UART |
| 0xff000000 | NAME0 in MTA1 |
| 0xff000008 | VERSION in MTA1 |
*Nota bene*: All MMIO accesses should be 32 bit wide, e.g use `lw` and `sw`.
| *name* | *fw* | *app | *size* | *type* | *content* | *description* |
|--------------------|------|------------|--------|---------|-----------|--------------------------------------------------------|
| `TRNG_NAME0` | r | r | 4B | char[4] | | ID of core |
| `TRNG_NAME1` | r | r | 4B | char[4] | | ID of core |
| `TRNG_VERSION` | r | r | 4B | u32 | | Version of core |
| `TRNG_STATUS` | r | r | | | | TBD |
| `TRNG_SAMPLE_RATE` | | r | | | | TBD |
| `TRNG_ENTROPY` | | | | | | TBD |
| `TIMER_NAME0` | r | r | | | | ID of core |
| `TIMER_NAME1` | r | r | | | | ID of core |
| `TIMER_VERSION` | r | r | | | | Version of core |
| `TIMER_CTRL` | | | | | | TBD |
| `TIMER_STATUS` | r | | | | | TBD |
| `TIMER_PRESCALER` | | r/w | | | | TBD |
| `TIMER_TIMER` | | r | | | | TBD |
| `UDS_NAME0` | r | invisible | | | | ID of core |
| `UDS_NAME1` | r | invisible | | | | ID of core |
| `UDS_VERSION` | r | invisible | | | | Version of core |
| `UDS_START` | r[^1]| invisible | 4B | u8[32] | | First word of Unique Device Secret key. |
| `UDS_LAST` | | invisible | | | | The last word of the UDS |
| `UART_NAME0` | r | r | | | | ID of core |
| `UART_NAME1` | r | r | | | | ID of core |
| `UART_VERSION` | r | r | | | | Version of core |
| `UART_BITRATE` | r/w | | | | | TBD |
| `UART_DATABITS` | r/w | | | | | TBD |
| `UART_STOPBITS` | r/w | | | | | TBD |
| `UART_RX_STATUS` | r | r | 1B | u8 | | Non-zero when there is data to read |
| `UART_RX_DATA` | r | r | 1B | u8 | | Data to read. Only LSB contains data |
| `UART_TX_STATUS` | r | r | 1B | u8 | | Non-zero when it's OK to write data |
| `UART_TX_DATA` | w | w | 1B | u8 | | Data to send. Only LSB contains data |
| `TOUCH_NAME0` | r | r | | | | ID of core |
| `TOUCH_NAME1` | r | r | | | | ID of core |
| `TOUCH_VERSION` | r | r | | | | Version of core |
| `TOUCH_STATUS` | r/w | r/w | | | | STATUS_EVENT_BIT set 1 when touched; write to it after |
| `UDA` | r | | 16B | u8[16] | | Unique Device Authentication key. |
| `UDI` | r | | 8B | u64 | | Unique Device ID (UDI). |
| `QEMU_DEBUG` | w | w | | u8 | | Debug console (only in QEMU) |
| `NAME0` | r | r | 4B | char[4] | "mta1" | ID of core/stick |
| `NAME1` | r | r | 4B | char[4] | "mkdf" | ID of core/stick |
| `VERSION` | r | r | 4B | u32 | 1 | Current version. |
| `SWITCH_APP` | w | invisible? | 1B | u8 | | Switch to application mode. Write non-zero to trigger. |
| `LED` | w | w | 1B | u8 | | |
| `GPIO` | | | | | | |
| `APP_ADDR` | r/w | r | 4B | u32 | | Application address (0x4000_0000) |
| `APP_SIZE` | r/w | r | 4B | u32 | | Application size |
| `DEBUG` | | | | | | TBD |
| `CDI_START` | r/w | r | 32B | u8[32] | | Compound Device Identifier (CDI). UDS+measurement... |
| `CDI_LAST` | | r | | | | Last word of CDI |
[^1]: The UDS can only be read *once* per power-cycle. [^1]: The UDS can only be read *once* per power-cycle.
## Application
### Memory map
See the [Memory model](./memory_model.md) for information about the
memory map and how access to memory areas work.