mirror of
https://github.com/tillitis/tillitis-key1.git
synced 2024-12-18 12:24:31 -05:00
Update doc to reflect real hardware
This commit is contained in:
parent
3110d1218d
commit
20c570e2be
@ -1,21 +1,22 @@
|
||||
|
||||
# NOTE: this document is outdated, an update is pending.
|
||||
|
||||
# MTA1-MKDF software
|
||||
# Tillitis Key software
|
||||
|
||||
## 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
|
||||
The PicoRV32 is a 32-bit RISC-V system. All types are little-endian.
|
||||
* Firmware -- software that is part of ROM, and is currently
|
||||
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
|
||||
|
||||
The application FPGA is a Lattice UP5K, with the following
|
||||
specifications:
|
||||
|
||||
* 32KB x 4 SPRAM => 128KB for Application
|
||||
* 4Kb x 30 EBR => 120Kb, PicoRV32 uses ~4 EBR internally => 13KB for
|
||||
Firmware. We should probably aim for less; 8KB should be the
|
||||
@ -23,19 +24,21 @@ specifications:
|
||||
|
||||
## Introduction
|
||||
|
||||
The MTA1_MKDF has two modes of operation; firmware/loader mode and
|
||||
application mode. The firmware mode has the responsibility of receive,
|
||||
measure, and load the application.
|
||||
The Tillitis Key has two modes of operation; firmware/loader mode and
|
||||
application mode. The firmware mode has the responsibility of
|
||||
receiving, measuring, and loading the application.
|
||||
|
||||
The firmware and application uses a memory mapped IO for SoC
|
||||
communication. The memory map resides at `0x9000_0000`. The
|
||||
application has a constrained variant of the firmware memory map,
|
||||
which is outlined below. E.g. UID and UDA are not readable, and the
|
||||
`APP_{ADDR, SIZE}` are not writable for the application.
|
||||
communication. This MMIO resides at `0xc000_0000`. *Nota bene*: All
|
||||
access to MMIO should be word (32 bit) aligned.
|
||||
|
||||
The MTA1_MKDF software communicates to the host via the `{RX,TX}_FIFO`
|
||||
registers, using the framing protocol described in [Framing
|
||||
Protocol](../framing_protocol/framing_protocol.md).
|
||||
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,
|
||||
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 framing layer, which is used to bootstrap the application onto the
|
||||
@ -50,24 +53,27 @@ between the host and the device.
|
||||
|
||||
## Firmware
|
||||
|
||||
The firware is part of FPGA bitstream (ROM), and is loaded at
|
||||
`0x0000_1000`.
|
||||
The firmware is part of FPGA bitstream (ROM), and is loaded at
|
||||
`0x0000_0000`.
|
||||
|
||||
### Reset
|
||||
|
||||
The PicoRV32 executes `_start` from `crt0.S` `.text` at `0x0000_1000`,
|
||||
which initializes the stack, `.data`, and `.bss` at
|
||||
`0x8000_0000`. When the initialization is finished, the firmware waits
|
||||
for incoming commands from the host, by busy-polling the
|
||||
`RX_FIFO_{AVAILABLE,DATA}`registers. When a complete command is read,
|
||||
the firmware executes the command.
|
||||
The PicoRV32 starts executing at `0x0000_0000`. Our firmware starts at
|
||||
`_start` from `start.S` which initializes the `.data`, and `.bss` at
|
||||
`0x4000_0000` and upwards. A stack is also initialized, starting at
|
||||
0x4000_fff0 and downwards. When the initialization is finished, the
|
||||
firmware waits for incoming commands from the host, by busy-polling
|
||||
the `RX_FIFO_{AVAILABLE,DATA}`registers. When a complete command is
|
||||
read, the firmware executes the command.
|
||||
|
||||
### Loading an application
|
||||
|
||||
The purpose of the firmware is to bootstrapping an application.
|
||||
1. The host sends a raw binary, targeted to be loaded at
|
||||
`0x8000_0000` in the device. The host starts off by sending the
|
||||
binary size using the `FW_CMD_LOAD_APP_SIZE` command.
|
||||
The purpose of the firmware is to bootstrap an application. The host
|
||||
will send a raw binary targeted to be loaded at `0x4001_0000` in the
|
||||
device.
|
||||
|
||||
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
|
||||
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
|
||||
@ -75,16 +81,19 @@ The purpose of the firmware is to bootstrapping an application.
|
||||
3. If the the host receive a sucessful command, it will send
|
||||
multiple `FW_CMD_LOAD_APP_DATA` commands, containing the full
|
||||
application.
|
||||
4. For each received `FW_CMD_LOAD_APP_DATA` commands, the firmware
|
||||
measures (XXX define how blake2s is used) the data, and places it
|
||||
into `0x8000_0000`. The firmware response with
|
||||
`FW_RSP_LOAD_APP_DATA` response to the host for each received
|
||||
block.
|
||||
5. When the final block of the application image is received,
|
||||
`0x8000_0000` is written to `APP_ADDR`. The `CDI` is computed by
|
||||
used the `UDS` and measurement from the application, and placed
|
||||
in the `CDI` register. The final `FW_RSP_LOAD_APP_DATA` response
|
||||
is sent to the host, completing the loading.
|
||||
4. For each received `FW_CMD_LOAD_APP_DATA` command the firmware
|
||||
places the data into `0x4001_0000` and upwards. The firmware
|
||||
response with `FW_RSP_LOAD_APP_DATA` response to the host for
|
||||
each received block.
|
||||
5. When the final block of the application image is received, we
|
||||
measure the application by computing a BLAKE2s digest over the
|
||||
entire application,
|
||||
|
||||
The Compound Device Identifier is computed by using the `UDS` and
|
||||
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
|
||||
that the application image does not overwrite the firmware's running
|
||||
@ -97,31 +106,31 @@ application.
|
||||
### Starting an application
|
||||
|
||||
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
|
||||
context switch. Enter application mode, means the the MMIO region is
|
||||
restricted; E.g. some registers are removed (`UDS`), and some are
|
||||
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
|
||||
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.
|
||||
Procedure:
|
||||
|
||||
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
|
||||
```
|
||||
// a0 = 0x9000_0000 + 0x420 (APP_ADDR address)
|
||||
lw a0,1056(a0)
|
||||
jalr x0,0(a0)
|
||||
```
|
||||
4. The device is now in application mode, and executes the code from
|
||||
`0x8000_0000`.
|
||||
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
|
||||
|
||||
Available commands/reponses:
|
||||
|
||||
#### `FW_{CMD,RSP}_LOAD_APP_SIZE`
|
||||
#### `FW_{CMD,RSP}_LOAD_APP_DATA`
|
||||
#### `FW_{CMD,RSP}_RUN_APP`
|
||||
@ -129,20 +138,19 @@ Available commands/reponses:
|
||||
#### `FW_{CMD,RSP}_UID`
|
||||
#### `FW_{CMD,RSP}_TRNG_DATA`
|
||||
#### `FW_{CMD,RSP}_TRNG_STATUS`
|
||||
|
||||
#### `FW_{CMD,RSP}_VERIFY_DEVICE`
|
||||
|
||||
Verification that the device is an authentic Mullvad
|
||||
device. Implemented using challenge/response.
|
||||
|
||||
#### `FW_{CMD,RSP}_GET_APPLICATION_DIGEST`
|
||||
|
||||
This command returns 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.
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
@ -166,11 +174,12 @@ host <-
|
||||
```
|
||||
|
||||
#### Load an application
|
||||
|
||||
```
|
||||
host ->
|
||||
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[2..6] = APP_SIZE
|
||||
@ -187,20 +196,19 @@ host <-
|
||||
|
||||
RSP[3..] = 0
|
||||
|
||||
|
||||
repeat ceil(APP_SIZE / 63) times:
|
||||
repeat ceil(APP_SIZE / 127) times:
|
||||
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[2..] = APP_DATA (pad with zeros)
|
||||
CMD[2..] = APP_DATA (127 bytes of app data, pad with zeros)
|
||||
|
||||
host <-
|
||||
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[2] = STATUS
|
||||
@ -210,37 +218,106 @@ host <-
|
||||
|
||||
### Memory map
|
||||
|
||||
The memory map exposes SoC functionality to the software, when in
|
||||
firmware mode (privileged mode) It is s set of memory mapped
|
||||
registers, starting at base address `0x9000_0000`.
|
||||
Assigned top level prefixes:
|
||||
|
||||
| *name* | *r/w* | *offset* | *size* | *type* | *content* | *description* |
|
||||
|--------------------|-------|----------|--------|---------|-----------|---------------------------------------------------------|
|
||||
| UDS[^1] | r | 0x0 | 32B | u8[32] | | Unique Device Secret key. |
|
||||
| UDA | r | 0x20 | 16B | u8[16] | | Unique Device Authentication key. |
|
||||
| SWITCH_APP | w | 0x30 | 1B | u8 | | Switch to application mode. Write non-zero to trigger. |
|
||||
| XXX 460 bytes hole | | | | | | |
|
||||
| UDI | r | 0x200 | 8B | u64 | | Unique Device ID (UDI). |
|
||||
| NAME0 | r | 0x208 | 4B | char[4] | "mta1" | |
|
||||
| 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. |
|
||||
| RX_FIFO_DATA | r | 0x215 | 1B | u8 | | FIFO Rx data. |
|
||||
| TX_FIFO_AVAILABLE | r | 0x216 | 1B | u8 | | Non-zero if a valid byte can be written to TX_FIFO_DATA |
|
||||
| TX_FIFO_DATA | w | 0x217 | 1B | u8 | | FIFO Tx data. |
|
||||
| LED | r/w | 0x218 | 4B | u32 | | LED |
|
||||
| COUNTER | r | 0x21c | 4B | u32 | | Counter |
|
||||
| TRNG_STATUS | r | 0x220 | 4B | u32 | | data_ready/error |
|
||||
| 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... |
|
||||
| APP_ADDR | r/w | 0x420 | 4B | u32 | | Application address (0x8000_0000) |
|
||||
| APP_SIZE | r/w | 0x424 | 4B | u32 | | Application size |
|
||||
| *name* | *prefix* | *address length* |
|
||||
|----------|----------|--------------------------------------|
|
||||
| ROM | 0b00 | 30 bit address |
|
||||
| RAM | 0b01 | 30 bit address |
|
||||
| reserved | 0b10 | |
|
||||
| MMIO | 0b11 | 6 bits for core select, 24 bits rest |
|
||||
|
||||
Addressing:
|
||||
|
||||
```
|
||||
31st bit 0th bit
|
||||
v v
|
||||
0000 0000 0000 0000 0000 0000 0000 0000
|
||||
|
||||
- Bits [31 .. 30] (2 bits): Top level prefix (described above)
|
||||
- Bits [29 .. 24] (6 bits): Core select. We want to support at least 16 cores
|
||||
- Bits [23 .. 0] (24 bits): Memory/in-core address.
|
||||
```
|
||||
|
||||
The memory exposes SoC functionality to the software when in firmware
|
||||
mode. It is a set of memory mapped registers (MMIO), starting at base
|
||||
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.
|
||||
|
||||
## Application
|
||||
|
||||
### Memory map
|
||||
See the [Memory model](./memory_model.md) for information about the
|
||||
memory map and how access to memory areas work.
|
||||
|
Loading…
Reference in New Issue
Block a user