doc: Update documentation about syscalls

- Revise firmware implementation notes
- Document how to do fw syscalls
  - Document how to trigger a syscall function in the firmware, how to
    pass arguments, what the caller is responsible for and what is
    returned.
- Describe hardware syscall implementation
  - how the syscall interrupts are triggered,
  - the hardware privilege escalation,
  - the UDS protection.

Co-authored-by: Daniel Jobson <jobson@tillitis.se>
Co-authored-by: Michael Cardell Widerkrantz <mc@tillitis.se>
This commit is contained in:
Mikael Ågren 2025-02-26 07:03:56 +01:00
parent 7554787678
commit c52442b54c
No known key found for this signature in database
GPG Key ID: E02DA3D397792C46
4 changed files with 161 additions and 120 deletions

View File

@ -11,9 +11,10 @@ The design top level is in `rtl/application_fpga.v`. It contains
instances of all cores as well as the memory system.
The memory system allows the CPU to access cores in different ways
given the current execution mode. There are two execution modes -
firmware and application. Basically, in application mode the access is
more restrictive.
given the current execution mode. There are three execution modes -
firmware, application and system call. Each mode give access to a
different set of resources. Where app mode is the most restrictive and
firmware mode is the least restrictive.
The rest of the components are under `cores`. They typically have
their own `README.md` file documenting them and their API in detail.
@ -34,6 +35,7 @@ Rough memory map:
| UART | 0xc3 |
| Touch | 0xc4 |
| FW\_RAM | 0xd0 |
| Syscall | 0xe1 |
| TK1 | 0xff |
## `clk_reset_gen`
@ -96,6 +98,54 @@ hours, days) there is also a 32 bit prescaler.
The timer is available to use by firmware and applications.
## Syscall
System call trigger area. A 32-bit write to address 0xe1000000 will
trigger interrupt 31, which in turn triggers a system call in the
firmware.
## Interrupts
Triggering an interrupt will cause the CPU to execute the interrupt
handler att address 0x10.
The interrupt handler is shared by all PicoRV32 interrupts but only
interrupt 31 is enabled on the Tkey. Register `x4` can be inspected to
determine the interrupt source. Each interrupt source is assigned one
bit in x4. Triggered interrupts have their bit set to `1`.
| *Source* | *x4 Bit* |
|----------|----------|
| Syscall | 31 |
The return address is located in register `x3`. Calling the PicoRV32
specific instruction `retirq` exits the interrupt handler and clears
the interrupt source.
No registers are stored/restored when entering/exiting the interrupt
handler. It is up to the software to store/restore as necessary.
Interrupts can be enabled/disabled using the PicoRV32 specific
`maskirq` instruction.
## Restricted resources
The following table shows resource availablility for each execution
mode:
| *Execution Mode* | *ROM* | *FW RAM* | *SPI* | *UDS* |
|------------------|-------|----------|-------|-------|
| Firmware mode | r/x | r/w | r/w | r/w* |
| Syscall | r/x | r/w | r/w | i |
| Application mode | r | i | i | i |
Legend:
r = readable
w = writeable
x = executable
i = invisible
* = UDS words are readable only once in firmware mode.
## `tk1`
See [tk1 README](core/tk1/README.md) for details.
@ -107,7 +157,6 @@ Contains:
- RGB LED control.
- General purpose input/output (GPIO) pin control.
- Application introspection: start address and size of binary.
- BLAKE2s function access.
- Compound Device Identity (CDI).
- Unique Device Identity (UDI).
- RAM memory protection.
@ -135,13 +184,13 @@ should make it infeasible to improve asset extraction by observing
multiple memory dumps from the same TKey device. The attack should
also not directly scale to multiple TKey devices.
The RAM address and data scrambling is done in de RAM core.
The RAM address and data scrambling is done in the RAM core.
The memory protection is setup by the firmware. Access to the memory
protection controls is disabled for applications. Before the memory
protecetion is enabled, the RAM is filled with randomised data using
Xorwow. So during boot the firmware perform the following steps to
setup the memory protection:
Xorwow. During boot the firmware perform the following steps to setup
the memory protection:
1. Get a random 32-bit value from the TRNG to use as data state for
Xorwow.

View File

@ -23,17 +23,6 @@ and version of the device. They can be read by FW as well as
applications.
### Control of execution mode
```
ADDR_APP_MODE_CTRL: 0x08
```
This register controls if the device is executing in FW mode or in App
mode. The register can be written once between power cycles, and only
by FW. If set the device is in app mode.
### Control of RGB LED
```
@ -75,19 +64,7 @@ ADDR_APP_SIZE: 0x0d
These registers provide read only information to the loaded app to
itself - where it was loaded and its size. The values are written by
FW as part of the loading of the app. The registers can't be written
when the `ADDR_APP_MODE_CTRL` has been set.
### Access to Blake2s
```
ADDR_BLAKE2S: 0x10
```
This register provides the 32-bit function pointer address to the
Blake2s hash function in the FW. It is written by FW during boot. The
register can't be written to when the `ADDR_APP_MODE_CTRL` has been
set.
in application mode.
### Access to CDI
@ -99,10 +76,10 @@ ADDR_CDI_LAST: 0x27
These registers provide access to the 256-bit compound device secret
calculated by the FW as part of loading an application. The registers
are written by the FW. The register can't be written to when the
`ADDR_APP_MODE_CTRL` has been set. The CDI is readable by apps, which
can then use it as a base secret for any other secrets required to
carry out their intended use case.
are written by the FW. The register can't be written in application
mode. The CDI is readable by apps, which can then use it as a base
secret for any other secrets required to carry out their intended use
case.
### Access to UDI

View File

@ -6,8 +6,7 @@ Unique Device Secret core
This core store and protect the Unique Device Secret (UDS) asset. The
UDS can be accessed as eight separate 32-bit words. The words can only
be accessed as long as the app_mode input is low, implying that the
CPU is executing the FW.
be accessed as long as the `en` input is high.
The UDS words can be accessed in any order, but a given word can only
be accessed once between reset cycles. This read once functionality is

View File

@ -1,35 +1,43 @@
# Firmware
# Firmware implementation notes
## Introduction
This text is an introduction to, a requirement specification of,
and some implementation notes of the TKey firmware. It also gives a
few hint on developing and debugging the firmware.
This text is specific for the firmware. For a more general description
on how to implement device apps, see [the TKey Developer
Handbook](https://dev.tillitis.se/).
This text is specific for the firmware, the piece of software in TKey
ROM. For a more general description on how to implement device apps,
see [the TKey Developer Handbook](https://dev.tillitis.se/).
## Definitions
- Firmware - software in ROM responsible for loading applications. The
firmware is included as part of the FPGA bitstream and not
replacable on a usual consumer TKey.
- Device application or app - software supplied by the client which is
received, loaded, measured, and started by the firmware.
- Firmware: Software in ROM responsible for loading, measuring, and
starting applications. The firmware is included as part of the FPGA
bitstream and not replacable on a usual consumer TKey.
- Client: Software running on a computer or a mobile phone the TKey is
inserted into.
- Device application or app: Software supplied by the client that runs
on the TKey.
## CPU modes and firmware
The TKey has two modes of software operation: firmware mode and
application mode. The TKey always starts in firmware mode and starts
the firmware. When the firmware is about to start the application it
switches to a more constrained environment, the application mode.
application mode. The TKey always starts in firmware mode when it
starts the firmware. When the application starts the hardware
automatically switches to a more constrained environment: the
application mode.
The TKey hardware cores are memory mapped. Firmware has complete
access, except that the UDS is readable only once. The memory map is
constrained when running in application mode, e.g. FW\_RAM and UDS
isn't readable, and several other hardware addresses are either not
readable or not writable for the application.
The TKey hardware cores are memory mapped but the memory access is
different depending on mode. Firmware has complete access, except that
the Unique Device Secret (UDS) words are readable only once even in
firmware mode. The memory map is constrained when running in
application mode, e.g. FW\_RAM and UDS isn't readable, and several
other hardware addresses are either not readable or not writable for
the application.
When doing system calls from a device app the context switches back to
firmware mode. However, the UDS is still not available, protected by
two measures: 1) the UDS words can only be read out once and have
already been read by firmware when measuring the app, and, 2) the UDS
is protected by hardware after the execution leaves ROM for the first
time.
See the table in [the Developer
Handbook](https://dev.tillitis.se/memory/) for an overview about the
@ -72,12 +80,17 @@ Dev Handbook for specific details.
## Memory constraints
- ROM: 6 kByte.
- FW\_RAM: 4 kByte.
- fw stack: 3824 bytes.
- resetinfo: 256 bytes.
- rest is available for .data and .bss.
- RAM: 128 kByte.
| *Name* | *Size* | *FW mode* | *App mode* |
|---------|-----------|-----------|------------|
| ROM | 8 kByte | r-x | r |
| FW\_RAM | 4 kByte* | rw- | - |
| RAM | 128 kByte | rwx | rwx |
* FW\_RAM is divided into the following areas:
- fw stack: 3824 bytes.
- resetinfo: 256 bytes.
- rest is available for .data and .bss.
## Firmware behaviour
@ -87,7 +100,7 @@ application received from the client over the USB/UART.
The firmware binary is part of the FPGA bitstream as the initial
values of the Block RAMs used to construct the `FW_ROM`. The `FW_ROM`
start address is located at `0x0000_0000` in the CPU memory map, which
is the CPU reset vector.
is also the CPU reset vector.
### Firmware state machine
@ -151,26 +164,30 @@ is received, when state is changed to "running".
In "running", the loaded device app is measured, the Compound Device
Identifier (CDI) is computed, we do some cleanup of firmware data
structures, flip to application mode, and finally start the app, which
ends the firmware state machine.
structures, enable the system calls, and finally start the app, which
ends the firmware state machine. Hardware guarantees that we leave
firmware mode automatically when the program counter leaves ROM.
The device app is now running in application mode. There is no other
means of getting back from application mode to firmware mode than
resetting/power cycling the device. Note that ROM is still accessible
in the memory map, so it's still possible to execute firmware code in
application mode, but with no privileged access.
The device app is now running in application mode. We can, however,
return to firmware mode (excepting access to the UDS) by doing system
calls. Note that ROM is still readable, but is now hardware protected
from execution, except through the system call mechanism.
### Golden path
Firmware loads the application at the start of RAM (`0x4000_0000`). It
use a part of the special FW\_RAM for its own stack.
When reset is released, the CPU starts executing the firmware. It
begins by clearing all CPU registers, clears all FW\_RAM, sets up a
stack for itself there, and then jumps to `main()`.
begins in `start.S` by clearing all CPU registers, clears all FW\_RAM,
sets up a stack for itself there, and then jumps to `main()`. Also
included in the assembly part of firmware is an interrupt handler for
the system calls, but the handler is not yet enabled.
Beginning at `main()` it sets up the "system calls", then fills the
entire RAM with pseudo random data and setting up the RAM address and
data hardware scrambling with values from the True Random Number
Generator (TRNG). It then waits for data coming in through the UART.
Beginning at `main()` it fills the entire RAM with pseudo random data
and setting up the RAM address and data hardware scrambling with
values from the True Random Number Generator (TRNG). It then waits for
data coming in through the UART.
Typical expected use scenario:
@ -199,7 +216,7 @@ Typical expected use scenario:
([CDI]((#compound-device-identifier-computation))) is then
computed by doing a new BLAKE2s using the Unique Device Secret
(UDS), the application digest, and any User Supplied Secret
(USS).
(USS) digest already received.
6. The start address of the device app, currently `0x4000_0000`, is
written to `APP_ADDR` and the size of the binary to `APP_SIZE` to
@ -207,24 +224,22 @@ Typical expected use scenario:
it is, if it wants to relocate in RAM.
7. The firmware now clears the part of the special `FW_RAM` where it
keeps it stack. After this it performs no more function calls and
uses no more automatic variables.
keeps it stack.
8. Firmware starts the application by first switching from firmware
mode to application mode by writing to the `APP_MODE_CTRL`
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 (see [the memory
8. The interrupt handler for system calls is enabled.
9. Firmware starts the application by jumping to the contents of
`APP_ADDR`. Hardware automatically switches from firmware mode to
application mode. In this mode some memory access is restricted,
e.g. some addresses are inaccessible (`UDS`), and some are
switched from read/write to read-only (see [the memory
map](https://dev.tillitis.se/memory/)).
Then the firmware jumps to what is in `APP_ADDR` which starts the
application.
If during this whole time any commands are received which are not
allowed in the current state, we enter the "failed" state and execute
an illegal instruction. An illegal instruction traps the CPU and
hardware blinks the status LED red until a power cycle. No further
instructions are executed.
allowed in the current state, or any errors occur, we enter the
"failed" state and execute an illegal instruction. An illegal
instruction traps the CPU and hardware blinks the status LED red until
a power cycle. No further instructions are executed.
### User-supplied Secret (USS)
@ -256,42 +271,43 @@ call `blake2s_update()` with it and then immediately call
`blake2s_update()` again with the program digest, destroying the UDS
stored in the internal context buffer. UDS should now not be in
`FW_RAM` anymore. We can read UDS only once per power cycle so UDS
should now not be available to firmware at all.
should now not be available even to firmware.
Then we continue with the CDI computation by updating with an optional
USS and then finalizing the hash, storing the resulting digest in
USS digest and finalizing the hash, storing the resulting digest in
`CDI`.
### Firmware services
### Firmware system calls
The firmware exposes a BLAKE2s function through a function pointer
located in MMIO `BLAKE2S` (see [memory
map](system_description.md#memory-mapped-hardware-functions)) with the
with function signature:
```c
int blake2s(void *out, unsigned long outlen, const void *key,
unsigned long keylen, const void *in, unsigned long inlen,
blake2s_ctx *ctx);
The firmware provides a system call mechanism through the use of the
PicoRV32 interrupt handler. They are triggered by writing to the
trigger address: 0xe1000000. It's typically done with a function
signature like this:
```
where `blake2s_ctx` is:
```c
typedef struct {
uint8_t b[64]; // input buffer
uint32_t h[8]; // chained state
uint32_t t[2]; // total number of bytes
size_t c; // pointer for b[]
size_t outlen; // digest size
} blake2s_ctx;
int syscall(uint32_t number, uint32_t arg1);
```
The `libcommon` library in
[tkey-libs](https://github.com/tillitis/tkey-libs/)
has a wrapper for using this function called `blake2s()` which needs
to be maintained if you do any changes to the firmware call.
Arguments are system call number and upto 6 generic arguments passed
to the system call handler. The caller should place the system call
number in the a0 register and the arguments in registers a1 to a7
according to the RISC-V calling convention. The caller is responsible
for saving and restoring registers.
The syscall handler returns execution on the next instruction after
the store instruction to the trigger address. The return value from
the syscall is now available in x10 (a0).
To add or change syscalls, see the `syscall_handler()` in
`syscall_handler.c`.
Currently supported syscalls:
| *Name* | *Number* | *Argument* | *Description* |
|-------------|----------|------------|----------------------------------|
| RESET | 1 | Unused | Reset the TKey |
| SET\_LED | 10 | Colour | Set the colour of the status LED |
| GET\_VIDPID | 12 | Unused | Get Vendor and Product ID |
## Developing firmware