From bfda615f8c81f186e327b07a4d9d02f94783f85e Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Fri, 28 Feb 2025 12:20:10 +0100 Subject: [PATCH 01/30] doc: Add description on how firmware starts app from flash A first attempt at describing how to start an app from flash and how to handle information left in resetinfo from the previous app in the chain. --- hw/application_fpga/fw/README.md | 66 ++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/hw/application_fpga/fw/README.md b/hw/application_fpga/fw/README.md index 44effb4..8ff2966 100644 --- a/hw/application_fpga/fw/README.md +++ b/hw/application_fpga/fw/README.md @@ -186,10 +186,56 @@ the system calls, but the handler is not yet enabled. 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. +values from the True Random Number Generator (TRNG). -Typical expected use scenario: +1. Check the special resetinfo area in FW\_RAM to see if there is any + data about why a reset has been made. Or all zeroes(?) meaning a power + loss. + +2. If it was reset intentende to start a device app from client, see + App loaded from client below. + +3. If it was reset to start a device app from the flash it first + checks which app it should start from the resetinfo (out of two + available). If no data is available, start with the first. + +4. Load flash app into RAM without USS. + +5. Compute digest of loaded app. + +6. Compare against stored app digest in partition table to note if app + has been corrupted on flash. + +7. If there is an app digest in the resetinfo left from previous app, + compare the digests. Halt CPU if differences. + +8. Start the app. See details in description below. + +If the app is the first set in a chain, it's the job of the app itself +to reset the TKey when it has done its job. For instance, a verified +boot loader app: + + - includes a security policy, for instance a public key and code to + check a signature. + + - the app reads the message and the signature over the message (the + digest of the next app in the chain) from the filesystem or from + the client. + + - if the signature provided over the message is verified to be done + by the corresponding private key, this app would do a `reset()`, + passing the digest to the firmware for control and instructing it + to start *just* that app. + + - firmware would see the instructions about the reset in FW\_RAM: + + 1. Where to expect the next app from: client, a slot in the + filesystem? + 2. The expected digest of the next app. + +#### App loaded from client + +Firmware waits for data coming in through the UART. 1. The client sends the `FW_CMD_LOAD_APP` command with the size of the device app and the optional 32 byte hash of the user-supplied @@ -212,23 +258,27 @@ Typical expected use scenario: firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response containing the digest. - 5. The Compound Device Identifier + 5. If there was a digest left in resetinfo from earlier app in the + chain, compare the computed digest with the left digest. If it's + not the same, halt CPU. + + 6. The Compound Device Identifier ([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) digest already received. - 6. The start address of the device app, currently `0x4000_0000`, is + 7. 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 let the device application know where it is loaded and how large it is, if it wants to relocate in RAM. - 7. The firmware now clears the part of the special `FW_RAM` where it + 8. The firmware now clears the part of the special `FW_RAM` where it keeps it stack. - 8. The interrupt handler for system calls is enabled. + 9. The interrupt handler for system calls is enabled. - 9. Firmware starts the application by jumping to the contents of + 10. 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 From 3195f2f21bdb842b703f4a12a6da1606e822c1d5 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Thu, 6 Mar 2025 16:30:37 +0100 Subject: [PATCH 02/30] Clarify golden path - Clarify what the default behaviour is. - Clarify when we should halt CPU. - Move common things when booting from flash and UART to its own section. --- hw/application_fpga/fw/README.md | 128 +++++++++++++++---------------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/hw/application_fpga/fw/README.md b/hw/application_fpga/fw/README.md index 8ff2966..a710cec 100644 --- a/hw/application_fpga/fw/README.md +++ b/hw/application_fpga/fw/README.md @@ -175,8 +175,9 @@ 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. +Firmware loads the application at the start of RAM (`0x4000_0000`) +from either flash or the UART. 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 in `start.S` by clearing all CPU registers, clears all FW\_RAM, @@ -189,101 +190,98 @@ and setting up the RAM address and data hardware scrambling with values from the True Random Number Generator (TRNG). 1. Check the special resetinfo area in FW\_RAM to see if there is any - data about why a reset has been made. Or all zeroes(?) meaning a power - loss. + data about why a reset has been made. All zeroes(?) meaning default + behaviour. -2. If it was reset intentende to start a device app from client, see - App loaded from client below. +2. If it was reset with intention to start a device app from client, + see App loaded from client below. -3. If it was reset to start a device app from the flash it first - checks which app it should start from the resetinfo (out of two - available). If no data is available, start with the first. +3. Default is to start the first device app from flash. If resetinfo + says otherwise it starts the other one. 4. Load flash app into RAM without USS. 5. Compute digest of loaded app. 6. Compare against stored app digest in partition table to note if app - has been corrupted on flash. + has been corrupted on flash. If corrupted, halt CPU. -7. If there is an app digest in the resetinfo left from previous app, - compare the digests. Halt CPU if differences. - -8. Start the app. See details in description below. +7. Proceed to [Start the device app](#start-the-device-app) below. If the app is the first set in a chain, it's the job of the app itself to reset the TKey when it has done its job. For instance, a verified boot loader app: - - includes a security policy, for instance a public key and code to - check a signature. +- includes a security policy, for instance a public key and code to + check a signature. - - the app reads the message and the signature over the message (the - digest of the next app in the chain) from the filesystem or from - the client. +- the app reads the message and the signature over the message (the + digest of the next app in the chain) from the filesystem or from + the client. - - if the signature provided over the message is verified to be done - by the corresponding private key, this app would do a `reset()`, - passing the digest to the firmware for control and instructing it - to start *just* that app. +- if the signature provided over the message is verified to be done + by the corresponding private key, this app would do a `reset()`, + passing the digest to the firmware for control and instructing it + to start *just* that app. - - firmware would see the instructions about the reset in FW\_RAM: +- firmware would see the instructions about the reset in FW\_RAM: - 1. Where to expect the next app from: client, a slot in the - filesystem? - 2. The expected digest of the next app. + 1. Where to expect the next app from: client, a slot in the + filesystem? + 2. The expected digest of the next app. #### App loaded from client Firmware waits for data coming in through the UART. - 1. The client sends the `FW_CMD_LOAD_APP` command with the size of - the device app and the optional 32 byte hash of the user-supplied - secret as arguments and gets a `FW_RSP_LOAD_APP` back. After - using this it's not possible to restart the loading of an - application. +1. The client sends the `FW_CMD_LOAD_APP` command with the size of + the device app and the optional 32 byte hash of the user-supplied + secret as arguments and gets a `FW_RSP_LOAD_APP` back. After + using this it's not possible to restart the loading of an + application. - 2. If the the client receive a sucessful response, it will send - multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the - full application. +2. If the the client receive a sucessful response, it will send + multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the + full application. - 3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places - the data into `0x4000_0000` and upwards. The firmware replies - with a `FW_RSP_LOAD_APP_DATA` response to the client for each - received block except the last data block. +3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places + the data into `0x4000_0000` and upwards. The firmware replies + with a `FW_RSP_LOAD_APP_DATA` response to the client for each + received block except the last data block. - 4. When the final block of the application image is received with a - `FW_CMD_LOAD_APP_DATA`, the firmware measure the application by - computing a BLAKE2s digest over the entire application. Then - firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response - containing the digest. +4. When the final block of the application image is received with a + `FW_CMD_LOAD_APP_DATA`, the firmware measure the application by + computing a BLAKE2s digest over the entire application. Then + firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response + containing the digest. - 5. If there was a digest left in resetinfo from earlier app in the - chain, compare the computed digest with the left digest. If it's - not the same, halt CPU. +#### Start the device app - 6. The Compound Device Identifier - ([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) digest already received. +1. If there is an app digest in the resetinfo left from previous app, + compare the digests. Halt CPU if differences. - 7. 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 - let the device application know where it is loaded and how large - it is, if it wants to relocate in RAM. +2. The Compound Device Identifier + ([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) digest + already received. - 8. The firmware now clears the part of the special `FW_RAM` where it - keeps it stack. +3. 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 + let the device application know where it is loaded and how large it + is, if it wants to relocate in RAM. - 9. The interrupt handler for system calls is enabled. +4. The firmware now clears the part of the special `FW_RAM` where it + keeps it stack. - 10. 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/)). +5. The interrupt handler for system calls is enabled. + +6. 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/)). If during this whole time any commands are received which are not allowed in the current state, or any errors occur, we enter the From e86e60fcfe93437345f4c0ab7e2ce151e7679148 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Mon, 10 Mar 2025 15:54:54 +0100 Subject: [PATCH 03/30] Update firmware state machine Include flash apps and states in firmware state machine description and diagram. --- hw/application_fpga/fw/README.md | 87 +++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/hw/application_fpga/fw/README.md b/hw/application_fpga/fw/README.md index a710cec..6336c9a 100644 --- a/hw/application_fpga/fw/README.md +++ b/hw/application_fpga/fw/README.md @@ -104,48 +104,73 @@ is also the CPU reset vector. ### Firmware state machine -This is the state diagram of the firmware. There are only four states. +This is the state diagram of the firmware. There are only six states. + Change of state occur when we receive specific I/O or a fatal error occurs. ```mermaid stateDiagram-v2 - S1: initial - S2: loading - S3: running - SE: failed + S0: resetinfo + S4: loadflash + S1: waitcommand + S2: loading + S3: running + SE: failed - [*] --> S1 + [*] --> S0 - S1 --> S1: Commands - S1 --> S2: LOAD_APP - S1 --> SE: Error + S0 --> S4: load 1 (def) or load 2 + S0 --> S1 - S2 --> S2: LOAD_APP_DATA - S2 --> S3: Last block received - S2 --> SE: Error + S1 --> S1: Commands + S1 --> S2: LOAD_APP + S1 --> SE: Error - S3 --> [*] + S2 --> S2: LOAD_APP_DATA + S2 --> S3: Last block received + S2 --> SE: Error + + S4 --> S3 + S4 --> SE: Error + + SE --> [*] + S3 --> [*] ``` States: -- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`, - `LOAD_APP`. +- `resetinfo` - We start by checking resetinfo data in `FW_RAM` +- `waitcommand` - Waiting for initial commands from client. Allows the + commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`. +- `loadflash` - Loading an app from flash. - `loading` - Expect application data. Allows only the command `LOAD_APP_DATA`. -- `run` - Computes CDI and starts the application. Allows no commands. -- `fail` - Stops waiting for commands, flashes LED forever. Allows no - commands. +- `running` - Computes CDI and starts the application. Allows no commands. +- `failed` - Halts CPU. Allows no commands. -Commands in state `initial`: +Allowed data in state `resetinfo`: + +| *startfrom* | *next state* | +|----------------------|---------------| +| `default` | `loadflash` | +| `Start flash slot 1` | `loadflash` | +| `Start flash slot 2` | `loadflash` | +| `Start from client` | `waitcommand` | + +I/O in state `loadflash`: + +| *I/O* | *next state* | +|--------------------|--------------| +| Last app data read | `run` | + +Commands in state `waitcommand`: | *command* | *next state* | |-----------------------|--------------| | `FW_CMD_NAME_VERSION` | unchanged | | `FW_CMD_GET_UDI` | unchanged | | `FW_CMD_LOAD_APP` | `loading` | -| | | Commands in state `loading`: @@ -157,12 +182,24 @@ See [Firmware protocol in the Dev Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the definition of the specific commands and their responses. -State changes from "initial" to "loading" when receiving `LOAD_APP`, -which also sets the size of the number of data blocks to expect. After -that we expect several `LOAD_APP_DATA` commands until the last block -is received, when state is changed to "running". +Exection starts in state `resetinfo` where the firmware checks in +`FW_RAM` for what to do next. -In "running", the loaded device app is measured, the Compound Device +State changes to `loadflash` if the `FW_RAM` data indicates +that it should start one of the two flash apps. + +State changes to `waitcommand` if the `FW_RAM` data indicates that it +instead should wait for commands from a client. + +In `loadflash` state changes to `running` if the app has been +successfully loaded into RAM or to `failed` otherwise. + +State changes from `waitcommand` to `loading` when receiving +`LOAD_APP`, which also sets the size of the number of data blocks to +expect. After that we expect several `LOAD_APP_DATA` commands until +the last block 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, enable the system calls, and finally start the app, which ends the firmware state machine. Hardware guarantees that we leave From 6ef6c36f6f572668b63e843fc72191768d2a8f5a Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Wed, 12 Mar 2025 16:17:48 +0100 Subject: [PATCH 04/30] Add filesystem code and storage syscalls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds syscalls: - ALLOCATE_AREA - DEALLOCATE_AREA - WRITE_DATA - READ_DATA and code to access the filesystem and the flash over SPI. Based on original work by Daniel Jobson for these files: - auth_app.[ch] - flash.[ch] - spi.[ch] - partition_table.[ch] - rng.[ch] - storage.[ch] which are used with small changes to integrate with the new syscall method. Co-authored-by: Daniel Jobson Co-authored-by: Mikael Ågren --- hw/application_fpga/Makefile | 8 +- hw/application_fpga/fw/testapp/main.c | 39 +++- hw/application_fpga/fw/testapp/syscall.h | 3 +- hw/application_fpga/fw/tk1/auth_app.c | 68 ++++++ hw/application_fpga/fw/tk1/auth_app.h | 14 ++ hw/application_fpga/fw/tk1/firmware.lds | 2 +- hw/application_fpga/fw/tk1/flash.c | 217 +++++++++++++++++++ hw/application_fpga/fw/tk1/flash.h | 58 +++++ hw/application_fpga/fw/tk1/main.c | 8 + hw/application_fpga/fw/tk1/partition_table.c | 50 +++++ hw/application_fpga/fw/tk1/partition_table.h | 105 +++++++++ hw/application_fpga/fw/tk1/rng.c | 29 +++ hw/application_fpga/fw/tk1/rng.h | 11 + hw/application_fpga/fw/tk1/spi.c | 90 ++++++++ hw/application_fpga/fw/tk1/spi.h | 14 ++ hw/application_fpga/fw/tk1/storage.c | 197 +++++++++++++++++ hw/application_fpga/fw/tk1/storage.h | 22 ++ hw/application_fpga/fw/tk1/syscall_handler.c | 39 +++- hw/application_fpga/fw/tk1/syscall_num.h | 7 +- 19 files changed, 974 insertions(+), 7 deletions(-) create mode 100644 hw/application_fpga/fw/tk1/auth_app.c create mode 100644 hw/application_fpga/fw/tk1/auth_app.h create mode 100644 hw/application_fpga/fw/tk1/flash.c create mode 100644 hw/application_fpga/fw/tk1/flash.h create mode 100644 hw/application_fpga/fw/tk1/partition_table.c create mode 100644 hw/application_fpga/fw/tk1/partition_table.h create mode 100644 hw/application_fpga/fw/tk1/rng.c create mode 100644 hw/application_fpga/fw/tk1/rng.h create mode 100644 hw/application_fpga/fw/tk1/spi.c create mode 100644 hw/application_fpga/fw/tk1/spi.h create mode 100644 hw/application_fpga/fw/tk1/storage.c create mode 100644 hw/application_fpga/fw/tk1/storage.h diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 822929a..8ec26a6 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -126,7 +126,13 @@ FIRMWARE_OBJS = \ $(P)/fw/tk1/proto.o \ $(P)/fw/tk1/blake2s/blake2s.o \ $(P)/fw/tk1/syscall_enable.o \ - $(P)/fw/tk1/syscall_handler.o + $(P)/fw/tk1/syscall_handler.o \ + $(P)/fw/tk1/spi.o \ + $(P)/fw/tk1/flash.o \ + $(P)/fw/tk1/storage.o \ + $(P)/fw/tk1/partition_table.o \ + $(P)/fw/tk1/auth_app.o \ + $(P)/fw/tk1/rng.o FIRMWARE_SOURCES = \ $(P)/fw/tk1/main.c \ diff --git a/hw/application_fpga/fw/testapp/main.c b/hw/application_fpga/fw/testapp/main.c index 58298eb..a09ffa2 100644 --- a/hw/application_fpga/fw/testapp/main.c +++ b/hw/application_fpga/fw/testapp/main.c @@ -121,13 +121,48 @@ int main(void) } // But a syscall to get parts of UDI should be able to run - int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0); + int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0, 0, 0); if (vidpid != 0x00010203) { failmsg("Expected VID/PID to be 0x00010203"); anyfailed = 1; } + puts(IO_CDC, "\r\nAllocating storage area..."); + + if (syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0) != 0) { + failmsg("Failed to allocate storage area"); + } + puts(IO_CDC, "done.\r\n"); + + puts(IO_CDC, "\r\nWriting to storage area..."); + + uint8_t out_data[14] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13 }; + if (syscall(TK1_SYSCALL_WRITE_DATA, 0, (uint32_t)out_data, sizeof(out_data)) != 0) { + failmsg("Failed to write to storage area"); + } + puts(IO_CDC, "done.\r\n"); + + puts(IO_CDC, "\r\nReading from storage area..."); + + uint8_t in_data[14] = { 0 }; + if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data, sizeof(in_data)) != 0) { + failmsg("Failed to write to storage area"); + } + if (!memeq(in_data, out_data, sizeof(in_data))) { + failmsg("Failed to read back data from storage area"); + anyfailed = 1; + } + puts(IO_CDC, "done.\r\n"); + + puts(IO_CDC, "\r\nDeallocating storage area..."); + + if (syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0) != 0) { + failmsg("Failed to deallocate storage area"); + } + puts(IO_CDC, "done.\r\n"); + uint32_t cdi_local[CDI_WORDS]; uint32_t cdi_local2[CDI_WORDS]; wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS); @@ -223,7 +258,7 @@ int main(void) } if (in == '+') { - syscall(TK1_SYSCALL_RESET, 0); + syscall(TK1_SYSCALL_RESET, 0, 0, 0); } write(IO_CDC, &in, 1); diff --git a/hw/application_fpga/fw/testapp/syscall.h b/hw/application_fpga/fw/testapp/syscall.h index 293f3e5..42b76e3 100644 --- a/hw/application_fpga/fw/testapp/syscall.h +++ b/hw/application_fpga/fw/testapp/syscall.h @@ -6,6 +6,7 @@ #ifndef TKEY_APP_SYSCALL_H #define TKEY_APP_SYSCALL_H -int syscall(uint32_t number, uint32_t arg1); +int syscall(uint32_t number, uint32_t arg1, uint32_t arg2, + uint32_t arg3); #endif diff --git a/hw/application_fpga/fw/tk1/auth_app.c b/hw/application_fpga/fw/tk1/auth_app.c new file mode 100644 index 0000000..4ee2c07 --- /dev/null +++ b/hw/application_fpga/fw/tk1/auth_app.c @@ -0,0 +1,68 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "auth_app.h" +#include "blake2s/blake2s.h" +#include "partition_table.h" +#include "rng.h" + +static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST; + +/* Calculates the authentication digest based on a supplied nonce and the CDI. + * Requires that the CDI is already calculated and stored */ +static void calculate_auth_digest(uint8_t *nonce, uint8_t *auth_digest) +{ + /* TODO: Check so the CDI is non-zero? */ + + blake2s_ctx ctx = {0}; + + // Generate a 16 byte authentication digest + blake2s_init(&ctx, 16, NULL, 0); + blake2s_update(&ctx, (const void *)cdi, 32); + blake2s_update(&ctx, nonce, 16); + blake2s_final(&ctx, auth_digest); +} + +/* Generates a 16 byte nonce */ +static void generate_nonce(uint32_t *nonce) +{ + + for (uint8_t i = 0; i < 4; i++) { + nonce[i] = rng_get_word(); + } + return; +} +/* Returns the authentication digest and random nonce. Requires that the CDI is + * already calculated and stored */ +void auth_app_create(struct auth_metadata *auth_table) +{ + uint8_t nonce[16]; + uint8_t auth_digest[16]; + + generate_nonce((uint32_t *)nonce); + + calculate_auth_digest(nonce, auth_digest); + + memcpy_s(auth_table->authentication_digest, 16, auth_digest, 16); + memcpy_s(auth_table->nonce, 16, nonce, 16); + + return; +} + +bool auth_app_authenticate(struct auth_metadata *auth_table) +{ + uint8_t auth_digest[16]; + + calculate_auth_digest(auth_table->nonce, auth_digest); + + if (memeq(auth_digest, auth_table->authentication_digest, 16)) { + return true; + } + + return false; +} diff --git a/hw/application_fpga/fw/tk1/auth_app.h b/hw/application_fpga/fw/tk1/auth_app.h new file mode 100644 index 0000000..fcc411a --- /dev/null +++ b/hw/application_fpga/fw/tk1/auth_app.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef AUTH_APP_H +#define AUTH_APP_H + +#include "partition_table.h" + +#include + +void auth_app_create(struct auth_metadata *auth_table); +bool auth_app_authenticate(struct auth_metadata *auth_table); + +#endif diff --git a/hw/application_fpga/fw/tk1/firmware.lds b/hw/application_fpga/fw/tk1/firmware.lds index c9c8579..ef1299a 100644 --- a/hw/application_fpga/fw/tk1/firmware.lds +++ b/hw/application_fpga/fw/tk1/firmware.lds @@ -7,7 +7,7 @@ OUTPUT_ARCH("riscv") ENTRY(_start) /* Define stack size */ -STACK_SIZE = 0xEF0; /* 3824 B */ +STACK_SIZE = 3000; MEMORY { diff --git a/hw/application_fpga/fw/tk1/flash.c b/hw/application_fpga/fw/tk1/flash.c new file mode 100644 index 0000000..8bc69f3 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.c @@ -0,0 +1,217 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "flash.h" +#include "spi.h" + +// clang-format off +static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER; +static volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER; +static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS; +static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL; +// clang-format on + +// CPU clock frequency in Hz +#define CPUFREQ 21000000 +#define PAGE_SIZE 256 + +static void delay(int timeout_ms) +{ + // Tick once every centisecond + *timer_prescaler = CPUFREQ / 100; + *timer = timeout_ms / 10; + + *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_START_BIT); + + while (*timer_status != 0) { + } + + // Stop timer + *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT); +} + +bool flash_is_busy(void) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + uint8_t rx_buf = {0x00}; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf, sizeof(rx_buf)); + + if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) { + return true; + } + + return false; +} + +// Blocking until !busy +void flash_wait_busy(void) +{ + while (flash_is_busy()) { + delay(10); + } +} + +void flash_write_enable(void) +{ + uint8_t tx_buf = WRITE_ENABLE; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_write_disable(void) +{ + uint8_t tx_buf = WRITE_DISABLE; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_sector_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = SECTOR_ERASE; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + /* tx_buf[3] is within a sector, and hence does not make a difference */ + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +void flash_block_32_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = BLOCK_ERASE_32K; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF; + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +// 64 KiB block erase, only cares about address bits 16 and above. +void flash_block_64_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = BLOCK_ERASE_64K; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + /* tx_buf[2] and tx_buf[3] is within a block, and hence does not make a + * difference */ + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +void flash_release_powerdown(void) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = RELEASE_POWER_DOWN; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_powerdown(void) +{ + uint8_t tx_buf = POWER_DOWN; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_read_manufacturer_device_id(uint8_t *device_id) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_MANUFACTURER_ID; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2); +} + +void flash_read_jedec_id(uint8_t *jedec_id) +{ + uint8_t tx_buf = READ_JEDEC_ID; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3); +} + +void flash_read_unique_id(uint8_t *unique_id) +{ + uint8_t tx_buf[5] = {0x00}; + tx_buf[0] = READ_UNIQUE_ID; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8); +} + +void flash_read_status(uint8_t *status_reg) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1); + + tx_buf = READ_STATUS_REG_2; + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1, 1); +} + +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_DATA; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF; + + return spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, dest_buf, size); +} + +// Only handles writes where the least significant byte of the start address is +// zero. +int flash_write_data(uint32_t address, uint8_t *data, size_t size) +{ + if (size <= 0 || size > 4096) { + return -1; + } + + size_t left = size; + uint8_t *p_data = data; + size_t n_bytes = 0; + + uint8_t tx_buf[4] = { + PAGE_PROGRAM, /* tx_buf[0] */ + (address >> ADDR_BYTE_3_BIT) & 0xFF, /* tx_buf[1] */ + (address >> ADDR_BYTE_2_BIT) & 0xFF, /* tx_buf[2] */ + 0x00, /* tx_buf[3] */ + }; + + while (left > 0) { + if (left >= PAGE_SIZE) { + n_bytes = PAGE_SIZE; + } else { + n_bytes = left; + } + + flash_write_enable(); + + if (spi_transfer(tx_buf, sizeof(tx_buf), p_data, n_bytes, NULL, + 0) != 0) { + return -1; + } + + left -= n_bytes; + p_data += n_bytes; + + address += n_bytes; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + + flash_wait_busy(); + } + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/flash.h b/hw/application_fpga/fw/tk1/flash.h new file mode 100644 index 0000000..cad6f07 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.h @@ -0,0 +1,58 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_FLASH_H +#define TKEY_FLASH_H + +#include +#include +#include + +#define WRITE_ENABLE 0x06 +#define WRITE_DISABLE 0x04 + +#define READ_STATUS_REG_1 0x05 +#define READ_STATUS_REG_2 0x35 +#define WRITE_STATUS_REG 0x01 + +#define PAGE_PROGRAM 0x02 +#define SECTOR_ERASE 0x20 +#define BLOCK_ERASE_32K 0x52 +#define BLOCK_ERASE_64K 0xD8 +#define CHIP_ERASE 0xC7 + +#define POWER_DOWN 0xB9 +#define READ_DATA 0x03 +#define RELEASE_POWER_DOWN 0xAB + +#define READ_MANUFACTURER_ID 0x90 +#define READ_JEDEC_ID 0x9F +#define READ_UNIQUE_ID 0x4B + +#define ENABLE_RESET 0x66 +#define RESET 0x99 + +#define ADDR_BYTE_3_BIT 16 +#define ADDR_BYTE_2_BIT 8 +#define ADDR_BYTE_1_BIT 0 + +#define STATUS_REG_BUSY_BIT 0 +#define STATUS_REG_WEL_BIT 1 + +bool flash_is_busy(void); +void flash_wait_busy(void); +void flash_write_enable(void); +void flash_write_disable(void); +void flash_sector_erase(uint32_t address); +void flash_block_32_erase(uint32_t address); +void flash_block_64_erase(uint32_t address); +void flash_release_powerdown(void); +void flash_powerdown(void); +void flash_read_manufacturer_device_id(uint8_t *device_id); +void flash_read_jedec_id(uint8_t *jedec_id); +void flash_read_unique_id(uint8_t *unique_id); +void flash_read_status(uint8_t *status_reg); +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size); +int flash_write_data(uint32_t address, uint8_t *data, size_t size); + +#endif diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 62e496c..1b3b944 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -12,6 +12,7 @@ #include #include "blake2s/blake2s.h" +#include "partition_table.h" #include "proto.h" #include "state.h" #include "syscall_enable.h" @@ -35,6 +36,8 @@ static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_R static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND; // clang-format on +struct partition_table part_table; + // Context for the loading of a TKey program struct context { uint32_t left; // Bytes left to receive @@ -418,6 +421,11 @@ int main(void) scramble_ram(); + if (part_table_read(&part_table) != 0) { + // Couldn't read or create partition table + assert(1 != 2); + } + #if defined(SIMULATION) run(&ctx); #endif diff --git a/hw/application_fpga/fw/tk1/partition_table.c b/hw/application_fpga/fw/tk1/partition_table.c new file mode 100644 index 0000000..1825cb1 --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.c @@ -0,0 +1,50 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include "flash.h" +#include "partition_table.h" +#include "proto.h" + +int part_table_read(struct partition_table *part_table) +{ + // Read from flash, if it exists, otherwise create a new one. + + flash_release_powerdown(); + memset(part_table, 0x00, sizeof(*part_table)); + + flash_read_data(ADDR_PARTITION_TABLE, (uint8_t *)part_table, + sizeof(*part_table)); + + // TODO: Implement redundancy and consistency check + + if (part_table->header.version != PART_TABLE_VERSION) { + // Partition table is not ours. Make a new one, and store it. + memset(part_table, 0x00, sizeof(*part_table)); + + part_table->header.version = PART_TABLE_VERSION; + + for (int i = 0; i < 4; i++) { + part_table->app_storage[i].addr_start = + (ADDR_STORAGE_AREA + i * SIZE_STORAGE_AREA); + part_table->app_storage[i].size = SIZE_STORAGE_AREA; + } + + part_table_write(part_table); + } + + // Now the partition table is synced between flash and RAM. + + return 0; +} + +int part_table_write(struct partition_table *part_table) +{ + flash_sector_erase(ADDR_PARTITION_TABLE); + flash_write_data(ADDR_PARTITION_TABLE, (uint8_t *)part_table, + sizeof(*part_table)); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h new file mode 100644 index 0000000..7a954bb --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -0,0 +1,105 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef PARTITION_TABLE_H +#define PARTITION_TABLE_H + +#include + +/* ---- Flash ---- ---- */ +/* name size start addr */ +/* ---- ---- ---- */ +/* bitstream 128KiB 0x00 */ +/* ---- ---- ---- */ +/* Partition 64KiB 0x20000 */ +/* ---- ---- ---- */ +/* Pre load 128KiB 0x30000 */ +/* ---- ---- ---- */ +/* storage 1 128KiB 0x50000 */ +/* storage 2 128KiB 0x70000 */ +/* storage 3 128KiB 0x90000 */ +/* storage 4 128KiB 0xB0000 */ +/* ---- ---- ---- */ + +/* To simplify all blocks are aligned with the 64KiB blocks on the W25Q80DL + * flash. */ + +#define PART_TABLE_VERSION 1 + +#define ADDR_BITSTREAM 0UL +#define SIZE_BITSTREAM 0x20000UL // 128KiB + +#define ADDR_PARTITION_TABLE (ADDR_BITSTREAM + SIZE_BITSTREAM) +#define SIZE_PARTITION_TABLE \ + 0x10000UL // 64KiB, 60 KiB reserved, 2 flash pages (2 x 4KiB) for the + // partition table + +#define ADDR_PRE_LOADED_APP (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE) +#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB + +#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + SIZE_PRE_LOADED_APP) +#define SIZE_STORAGE_AREA 0x20000UL // 128KiB +#define N_STORAGE_AREA 4 + +#define EMPTY_AREA + +/* Partition Table */ +/*- Table header */ +/* - 1 bytes Version */ +/**/ +/*- Management device app */ +/* - Status. */ +/* - 16 byte random nonce. */ +/* - 16 byte authentication digest. */ +/**/ +/*- Pre-loaded device app */ +/* - 1 byte status. */ +/* - 4 bytes length. */ +/* - 16 bytes random nonce. */ +/* - 16 bytes authentication digest. */ +/**/ +/*- Device app storage area */ +/* - 1 byte status. */ +/* - 16 bytes random nonce. */ +/* - 16 bytes authentication tag. */ +/* - 4 bytes physical start address. */ +/* - 4 bytes physical end address. */ + +struct auth_metadata { + uint8_t nonce[16]; + uint8_t authentication_digest[16]; +} __attribute__((packed)); + +struct management_app_metadata { + uint8_t status; + struct auth_metadata auth; +} __attribute__((packed)); + +struct pre_loaded_app_metadata { + uint8_t status; + uint32_t size; + struct auth_metadata auth; +} __attribute__((packed)); + +struct app_storage_area { + uint8_t status; + struct auth_metadata auth; + uint32_t addr_start; + uint32_t size; +} __attribute__((packed)); + +struct table_header { + uint8_t version; +} __attribute__((packed)); + +struct partition_table { + struct table_header header; + struct management_app_metadata mgmt_app_data; + struct pre_loaded_app_metadata pre_app_data; + struct app_storage_area app_storage[N_STORAGE_AREA]; +} __attribute__((packed)); + +int part_table_read(struct partition_table *part_table); +int part_table_write(struct partition_table *part_table); + +#endif diff --git a/hw/application_fpga/fw/tk1/rng.c b/hw/application_fpga/fw/tk1/rng.c new file mode 100644 index 0000000..258b327 --- /dev/null +++ b/hw/application_fpga/fw/tk1/rng.c @@ -0,0 +1,29 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "rng.h" +#include + +#include + +// clang-format off +static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS; +static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY; +// clang-format on +// +// +uint32_t rng_get_word(void) +{ + while ((*trng_status & (1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) { + } + return *trng_entropy; +} + +uint32_t rng_xorwow(uint32_t state, uint32_t acc) +{ + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + state += acc; + return state; +} diff --git a/hw/application_fpga/fw/tk1/rng.h b/hw/application_fpga/fw/tk1/rng.h new file mode 100644 index 0000000..616b551 --- /dev/null +++ b/hw/application_fpga/fw/tk1/rng.h @@ -0,0 +1,11 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only +#ifndef RNG_H +#define RNG_H + +#include + +uint32_t rng_get_word(void); +uint32_t rng_xorwow(uint32_t state, uint32_t acc); + +#endif diff --git a/hw/application_fpga/fw/tk1/spi.c b/hw/application_fpga/fw/tk1/spi.c new file mode 100644 index 0000000..4487ee3 --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.c @@ -0,0 +1,90 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "spi.h" +#include + +#include +#include + +// clang-format off +static volatile uint32_t *spi_en = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x200); +static volatile uint32_t *spi_xfer = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x204); +static volatile uint32_t *spi_data = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x208); +// clang-format on + +// returns non-zero when the SPI-master is ready, and zero if not +// ready. This can be used to check if the SPI-master is available +// in the hardware. +int spi_ready(void) +{ + return *spi_xfer; +} + +static void spi_enable(void) +{ + *spi_en = 1; +} + +static void spi_disable(void) +{ + *spi_en = 0; +} + +static void _spi_write(uint8_t *cmd, size_t size) +{ + for (size_t i = 0; i < size; i++) { + while (!spi_ready()) { + } + + *spi_data = cmd[i]; + *spi_xfer = 1; + } + + while (!spi_ready()) { + } +} + +static void _spi_read(uint8_t *buf, size_t size) +{ + + while (!spi_ready()) { + } + + for (size_t i = 0; i < size; i++) { + + *spi_data = 0x00; + *spi_xfer = 1; + + // wait until spi master is done + while (!spi_ready()) { + } + + buf[i] = (*spi_data & 0xff); + } +} + +// Function to both read and write data to the connected SPI flash. +int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size, + uint8_t *rx_buf, size_t rx_size) +{ + if (cmd == NULL || cmd_size == 0) { + return -1; + } + + spi_enable(); + + _spi_write(cmd, cmd_size); + + if (tx_buf != NULL || tx_size != 0) { + _spi_write(tx_buf, tx_size); + } + + if (rx_buf != NULL && rx_size != 0) { + _spi_read(rx_buf, rx_size); + } + + spi_disable(); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/spi.h b/hw/application_fpga/fw/tk1/spi.h new file mode 100644 index 0000000..2c59f4c --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_SPI_H +#define TKEY_SPI_H + +#include +#include + +int spi_ready(void); +int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size, + uint8_t *rx_buf, size_t rx_size); + +#endif diff --git a/hw/application_fpga/fw/tk1/storage.c b/hw/application_fpga/fw/tk1/storage.c new file mode 100644 index 0000000..86f32dc --- /dev/null +++ b/hw/application_fpga/fw/tk1/storage.c @@ -0,0 +1,197 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "auth_app.h" +#include "flash.h" +#include "partition_table.h" +#include "storage.h" + +/* Returns the index of the first empty area. If there is no empty area -1 is + * returned. */ +static int get_first_empty(struct partition_table *part_table) +{ + + for (uint8_t i = 0; i < N_STORAGE_AREA; i++) { + if (part_table->app_storage[i].status == 0x00) { + return i; + } + } + return -1; +} + +/* Returns the index of the area an app has allocated. If no area is + * authenticated -1 is returned. */ +static int storage_get_area(struct partition_table *part_table) +{ + for (uint8_t i = 0; i < N_STORAGE_AREA; i++) { + if (part_table->app_storage[i].status != 0x00) { + if (auth_app_authenticate( + &part_table->app_storage[i].auth)) { + return i; + } + } + } + return -1; +} + +/* Allocate a new area for an app. Returns zero if a new area is allocated, one + * if an area already was allocated, and negative values for errors. */ +int storage_allocate_area(struct partition_table *part_table) +{ + if (storage_get_area(part_table) != -1) { + /* Already has an area */ + return 1; + } + + int index = get_first_empty(part_table); + if (index == -1) { + /* No empty slot */ + return -1; + } + + /* Allocate the empty index found */ + /* Erase area first */ + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(part_table->app_storage[index] + .addr_start); // Erase first 64 KB block + flash_block_64_erase(part_table->app_storage[index].addr_start + + 0x10000); // Erase second 64 KB block + + /* Write partition table lastly */ + part_table->app_storage[index].status = 0x01; + auth_app_create(&part_table->app_storage[index].auth); + + part_table_write(part_table); + + return 0; +} + +/* Dealloacate a previously allocated storage area. Returns zero on success, and + * non-zero on errors. */ +int storage_deallocate_area(struct partition_table *part_table) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No area to deallocate */ + return -1; + } + + /* Erase area first */ + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(part_table->app_storage[index] + .addr_start); // Erase first 64 KB block + flash_block_64_erase(part_table->app_storage[index].addr_start + + 0x10000); // Erase second 64 KB block + + /* Clear partition table lastly */ + part_table->app_storage[index].status = 0; + + memset(part_table->app_storage[index].auth.nonce, 0x00, + sizeof(part_table->app_storage[index].auth.nonce)); + + memset( + part_table->app_storage[index].auth.authentication_digest, 0x00, + sizeof(part_table->app_storage[index].auth.authentication_digest)); + + part_table_write(part_table); + + return 0; +} + +/* Erases sector. Offset of a sector to begin erasing, must be a multiple of + * the sector size. Size to erase in bytes, must be a multiple of the sector * + * size. Returns zero on success, negative error code on failure */ +int storage_erase_sector(struct partition_table *part_table, uint32_t offset, + size_t size) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + /* Cannot erase less than one sector */ + if (size < 4096 || size > part_table->app_storage[index].size || + size % 4096 != 0) { + return -2; + } + + if ((offset) >= part_table->app_storage[index].size) { + return -2; + } + + uint32_t address = part_table->app_storage[index].addr_start + offset; + + debug_puts("storage: erase addr: "); + debug_putinthex(address); + debug_lf(); + + for (size_t i = 0; i < size; i += 4096) { + flash_sector_erase(address); + address += 4096; + } + + return 0; +} + +/* Writes the specified data to the offset inside of the + * allocated area. Assumes area has been erased before hand. + * Currently only handles writes to one sector, hence max size of 4096 bytes. + * Returns zero on success. */ +int storage_write_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + if ((offset + size) > part_table->app_storage[index].size || + size > 4096) { + /* Writing outside of area */ + return -2; + } + + uint32_t address = part_table->app_storage[index].addr_start + offset; + + debug_puts("storage: write to addr: "); + debug_putinthex(address); + debug_lf(); + + return flash_write_data(address, data, size); +} + +/* Reads size bytes of data at the specified offset inside of + * the allocated area. Returns zero on success. Only read limit + * is the size of the allocated area */ +int storage_read_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + if ((offset + size) > part_table->app_storage[index].size) { + /* Reading outside of area */ + return -2; + } + + uint32_t address = part_table->app_storage[index].addr_start + offset; + + debug_puts("storage: read from addr: "); + debug_putinthex(address); + debug_lf(); + + return flash_read_data(address, data, size); +} diff --git a/hw/application_fpga/fw/tk1/storage.h b/hw/application_fpga/fw/tk1/storage.h new file mode 100644 index 0000000..f48cb0b --- /dev/null +++ b/hw/application_fpga/fw/tk1/storage.h @@ -0,0 +1,22 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef STORAGE_H +#define STORAGE_H + +#include "partition_table.h" + +#include +#include +#include + +int storage_allocate_area(struct partition_table *part_table); +int storage_deallocate_area(struct partition_table *part_table); +int storage_erase_sector(struct partition_table *part_table, uint32_t offset, + size_t size); +int storage_write_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size); +int storage_read_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size); + +#endif diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index 0d26fa8..083824d 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -5,8 +5,12 @@ #include #include +#include #include +#include "partition_table.h" +#include "storage.h" + #include "../tk1/syscall_num.h" // clang-format off @@ -14,11 +18,44 @@ static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTE static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST; // clang-format on -int32_t syscall_handler(uint32_t number, uint32_t arg1) +extern struct partition_table part_table; + +int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, + uint32_t arg3) { switch (number) { case TK1_SYSCALL_RESET: *system_reset = 1; + return 0; + case TK1_SYSCALL_ALLOC_AREA: + if (storage_allocate_area(&part_table) < 0) { + debug_puts("couldn't allocate storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_DEALLOC_AREA: + if (storage_deallocate_area(&part_table) < 0) { + debug_puts("couldn't deallocate storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_WRITE_DATA: + if (storage_write_data(&part_table, arg1, (uint8_t *)arg2, + arg3) < 0) { + debug_puts("couldn't write storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_READ_DATA: + if (storage_read_data(&part_table, arg1, (uint8_t *)arg2, + arg3) < 0) { + debug_puts("couldn't read storage area\n"); + return -1; + } + return 0; case TK1_SYSCALL_SET_LED: led_set(arg1); diff --git a/hw/application_fpga/fw/tk1/syscall_num.h b/hw/application_fpga/fw/tk1/syscall_num.h index 82f3f06..e1b5a95 100644 --- a/hw/application_fpga/fw/tk1/syscall_num.h +++ b/hw/application_fpga/fw/tk1/syscall_num.h @@ -6,8 +6,13 @@ enum syscall_num { TK1_SYSCALL_RESET = 1, + TK1_SYSCALL_ALLOC_AREA = 2, + TK1_SYSCALL_DEALLOC_AREA = 3, + TK1_SYSCALL_WRITE_DATA = 4, + TK1_SYSCALL_READ_DATA = 5, + TK1_SYSCALL_ERASE_DATA = 6, + TK1_SYSCALL_GET_VIDPID = 7, TK1_SYSCALL_SET_LED = 10, - TK1_SYSCALL_GET_VIDPID = 12, }; #endif From 2cb5f2eca67bbec998b5b073c7beda9d2882ba88 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Thu, 13 Mar 2025 14:18:38 +0100 Subject: [PATCH 05/30] Add start of pre-loaded app from flash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on earlier code by Daniel Jobson now integrated into the new world order. Co-authored-by: Mikael Ågren Co-authored-by: Daniel Jobson --- hw/application_fpga/Makefile | 4 +- hw/application_fpga/fw/tk1/main.c | 99 ++++++++++++++++ hw/application_fpga/fw/tk1/mgmt_app.c | 75 ++++++++++++ hw/application_fpga/fw/tk1/mgmt_app.h | 15 +++ hw/application_fpga/fw/tk1/preload_app.c | 139 +++++++++++++++++++++++ hw/application_fpga/fw/tk1/preload_app.h | 20 ++++ hw/application_fpga/fw/tk1/state.h | 1 + 7 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 hw/application_fpga/fw/tk1/mgmt_app.c create mode 100644 hw/application_fpga/fw/tk1/mgmt_app.h create mode 100644 hw/application_fpga/fw/tk1/preload_app.c create mode 100644 hw/application_fpga/fw/tk1/preload_app.h diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 8ec26a6..75d076d 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -132,7 +132,9 @@ FIRMWARE_OBJS = \ $(P)/fw/tk1/storage.o \ $(P)/fw/tk1/partition_table.o \ $(P)/fw/tk1/auth_app.o \ - $(P)/fw/tk1/rng.o + $(P)/fw/tk1/rng.o \ + $(P)/fw/tk1/preload_app.o \ + $(P)/fw/tk1/mgmt_app.o FIRMWARE_SOURCES = \ $(P)/fw/tk1/main.c \ diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 1b3b944..5197489 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -10,9 +10,12 @@ #include #include #include +#include +#include "auth_app.h" #include "blake2s/blake2s.h" #include "partition_table.h" +#include "preload_app.h" #include "proto.h" #include "state.h" #include "syscall_enable.h" @@ -45,6 +48,7 @@ struct context { uint8_t *loadaddr; // Where we are currently loading a TKey program bool use_uss; // Use USS? uint8_t uss[32]; // User Supplied Secret, if any + bool from_flash; // App is loaded from flash }; static void print_hw_version(void); @@ -323,8 +327,68 @@ static enum state loading_commands(const struct frame_header *hdr, return state; } +static void run_flash(const struct context *ctx, struct partition_table *part_table) +{ + /* At this point we expect an app to be loaded into RAM */ + *app_addr = TK1_RAM_BASE; + + // CDI = hash(uds, hash(app), uss) + compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); + + if (part_table->pre_app_data.status == 0x02) { + debug_puts("Create auth\n"); + auth_app_create(&part_table->pre_app_data.auth); + part_table->pre_app_data.status = 0x01; + part_table_write(part_table); + } + + if (!auth_app_authenticate(&part_table->pre_app_data.auth)) { + debug_puts("!Authenticated\n"); + assert(1 == 2); + } + + debug_puts("Flipping to app mode!\n"); + debug_puts("Jumping to "); + debug_putinthex(*app_addr); + debug_lf(); + + // Clear the firmware stack + // clang-format off +#ifndef S_SPLINT_S + asm volatile( + "la a0, _sstack;" + "la a1, _estack;" + "loop:;" + "sw zero, 0(a0);" + "addi a0, a0, 4;" + "blt a0, a1, loop;" + ::: "memory"); +#endif + // clang-format on + + syscall_enable(); + + // Jump to app - doesn't return + // Hardware is responsible for switching to app mode + // clang-format off +#ifndef S_SPLINT_S + asm volatile( + // Get value at TK1_MMIO_TK1_APP_ADDR + "lui a0,0xff000;" + "lw a0,0x030(a0);" + // Jump to it + "jalr x0,0(a0);" + ::: "memory"); +#endif + // clang-format on + + __builtin_unreachable(); +} + + static void run(const struct context *ctx) { + /* At this point we expect an app to be loaded into RAM */ *app_addr = TK1_RAM_BASE; // CDI = hash(uds, hash(app), uss) @@ -402,6 +466,15 @@ static void scramble_ram(void) *ram_data_rand = rnd_word(); } +/* Computes the blake2s digest of the app loaded into RAM */ +static int compute_app_digest(uint8_t *digest) +{ + blake2s_ctx b2s_ctx = {0}; + + return blake2s(digest, 32, NULL, 0, (const void *)TK1_RAM_BASE, + *app_size, &b2s_ctx); +} + int main(void) { struct context ctx = {0}; @@ -409,6 +482,8 @@ int main(void) uint8_t cmd[CMDSIZE] = {0}; enum state state = FW_STATE_INITIAL; + led_set(LED_BLUE); + print_hw_version(); /*@-mustfreeonly@*/ @@ -426,10 +501,29 @@ int main(void) assert(1 != 2); } + #if defined(SIMULATION) run(&ctx); #endif + // Just start the preloaded app. + if (preload_load(&part_table) == -1) { + state = FW_STATE_FAIL; + } + + *app_size = part_table.pre_app_data.size; + assert(*app_size <= TK1_APP_MAX_SIZE); + + int digest_err = compute_app_digest(ctx.digest); + assert(digest_err == 0); + print_digest(ctx.digest); + + part_table.pre_app_data.status = 0x02; + + state = FW_STATE_RUN_FLASH; + + led_set(LED_GREEN); + for (;;) { switch (state) { case FW_STATE_INITIAL: @@ -457,6 +551,10 @@ int main(void) run(&ctx); break; // This is never reached! + case FW_STATE_RUN_FLASH: + run_flash(&ctx, &part_table); + break; // This is never reached! + case FW_STATE_FAIL: // fallthrough default: @@ -470,5 +568,6 @@ int main(void) /*@ -compdestroy @*/ /* We don't care about memory leaks here. */ + return (int)0xcafebabe; } diff --git a/hw/application_fpga/fw/tk1/mgmt_app.c b/hw/application_fpga/fw/tk1/mgmt_app.c new file mode 100644 index 0000000..a61f26a --- /dev/null +++ b/hw/application_fpga/fw/tk1/mgmt_app.c @@ -0,0 +1,75 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include "mgmt_app.h" +#include "auth_app.h" +#include "partition_table.h" + +/* Returns true if an management app is already registered */ +static bool mgmt_app_registered(struct management_app_metadata *mgmt_table) +{ + + if (mgmt_table->status == 0x00) { + /* No management app registered */ + return false; + // TODO: Should we also check nonce, authentication digest for + // non-zero? + } + + return true; +} + +/* Authenticate an management app */ +bool mgmt_app_authenticate(struct management_app_metadata *mgmt_table) +{ + if (!mgmt_app_registered(mgmt_table)) { + return false; + } + + return auth_app_authenticate(&mgmt_table->auth); +} + +/* Register an management app, returns zero on success */ +int mgmt_app_register(struct partition_table *part_table) +{ + /* Check if the current app is the mgmt app */ + if (mgmt_app_authenticate(&part_table->mgmt_app_data)) { + return 0; + } + + /* Check if another management app is registered */ + if (mgmt_app_registered(&part_table->mgmt_app_data)) { + return -1; + } + + auth_app_create(&part_table->mgmt_app_data.auth); + part_table->mgmt_app_data.status = 0x01; + + part_table_write(part_table); + + return 0; +} + +/* Unregister the currently registered app, returns zero on success */ +int mgmt_app_unregister(struct partition_table *part_table) +{ + /* Only the management app should be able to unregister itself */ + if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { + return -1; + } + + part_table->mgmt_app_data.status = 0; + + memset(part_table->mgmt_app_data.auth.nonce, 0x00, + sizeof(part_table->mgmt_app_data.auth.nonce)); + + memset(part_table->mgmt_app_data.auth.authentication_digest, 0x00, + sizeof(part_table->mgmt_app_data.auth.authentication_digest)); + + part_table_write(part_table); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/mgmt_app.h b/hw/application_fpga/fw/tk1/mgmt_app.h new file mode 100644 index 0000000..1b99b0d --- /dev/null +++ b/hw/application_fpga/fw/tk1/mgmt_app.h @@ -0,0 +1,15 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef MGMT_APP_H +#define MGMT_APP_H + +#include "partition_table.h" + +#include + +bool mgmt_app_authenticate(struct management_app_metadata *mgmt_table); +int mgmt_app_register(struct partition_table *part_table); +int mgmt_app_unregister(struct partition_table *part_table); + +#endif diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c new file mode 100644 index 0000000..f47d021 --- /dev/null +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -0,0 +1,139 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +#include "flash.h" +#include "mgmt_app.h" +#include "partition_table.h" +#include "preload_app.h" + +/* Returns non-zero if the app is valid */ +bool preload_check_valid_app(struct partition_table *part_table) +{ + + if (part_table->pre_app_data.status == 0x00 && + part_table->pre_app_data.size == 0) { + /*No valid app*/ + return false; + } + + return true; +} + +/* Loads a preloaded app from flash to app RAM */ +int preload_load(struct partition_table *part_table) +{ + /*Check for a valid app in flash */ + if (!preload_check_valid_app(part_table)) { + return -1; + } + uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE; + + /* Read from flash, straight into RAM */ + int ret = flash_read_data(ADDR_PRE_LOADED_APP, loadaddr, + part_table->pre_app_data.size); + + return ret; +} + +/* Expects to receive chunks of data up to 4096 bytes to store into the + * preloaded area. The offset needs to be kept and updated between each call. + * Once done, call preload_store_finalize() with the last parameters. + * */ +int preload_store(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size) +{ + /* Check if we are allowed to store */ + if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { + return -3; + } + + /* Check for a valid app in flash, bale out if it already exists */ + if (preload_check_valid_app(part_table)) { + return -1; + } + + if ((offset + size) > SIZE_PRE_LOADED_APP || size > 4096) { + /* Writing outside of area */ + return -2; + } + + uint32_t address = ADDR_PRE_LOADED_APP + offset; + + debug_puts("preload_store: write to addr: "); + debug_putinthex(address); + debug_lf(); + + return flash_write_data(address, data, size); +} + +int preload_store_finalize(struct partition_table *part_table, bool use_uss, + uint8_t *uss, size_t app_size) +{ + /* Check if we are allowed to store */ + if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { + return -3; + } + + /* Check for a valid app in flash, bale out if it already exists */ + if (preload_check_valid_app(part_table)) { + return -1; + } + + // TODO: Maybe add the uss fields + + if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) { + return -2; + } + + part_table->pre_app_data.size = app_size; + part_table->pre_app_data.status = + 0x02; /* Stored but not yet authenticated */ + debug_puts("preload_*_final: size: "); + debug_putinthex(app_size); + debug_lf(); + + part_table_write(part_table); + + /* Force a restart to authenticate the stored app */ + /* TODO: Should this be done by the management app or by firmware? */ + + return 0; +} + +int preload_delete(struct partition_table *part_table) +{ + /* Check if we are allowed to deleted */ + if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { + return -3; + } + + /*Check for a valid app in flash */ + if (!preload_check_valid_app(part_table)) { + return 0; + // TODO: Nothing here, return zero like all is good? + } + part_table->pre_app_data.size = 0; + part_table->pre_app_data.status = 0; + + memset(part_table->pre_app_data.auth.nonce, 0x00, + sizeof(part_table->pre_app_data.auth.nonce)); + + memset(part_table->pre_app_data.auth.authentication_digest, 0x00, + sizeof(part_table->pre_app_data.auth.authentication_digest)); + + part_table_write(part_table); + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(ADDR_PRE_LOADED_APP); // Erase first 64 KB block + flash_block_64_erase(ADDR_PRE_LOADED_APP + + 0x10000); // Erase second 64 KB block + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/preload_app.h b/hw/application_fpga/fw/tk1/preload_app.h new file mode 100644 index 0000000..d518129 --- /dev/null +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -0,0 +1,20 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef PRELOAD_APP_H +#define PRELOAD_APP_H + +#include "partition_table.h" +#include +#include +#include + +bool preload_check_valid_app(struct partition_table *part_table); +int preload_load(struct partition_table *part_table); +int preload_store(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size); +int preload_store_finalize(struct partition_table *part_table, bool use_uss, + uint8_t *uss, size_t app_size); +int preload_delete(struct partition_table *part_table); + +#endif diff --git a/hw/application_fpga/fw/tk1/state.h b/hw/application_fpga/fw/tk1/state.h index 694448b..2ded225 100644 --- a/hw/application_fpga/fw/tk1/state.h +++ b/hw/application_fpga/fw/tk1/state.h @@ -10,6 +10,7 @@ enum state { FW_STATE_INITIAL, FW_STATE_LOADING, FW_STATE_RUN, + FW_STATE_RUN_FLASH, FW_STATE_FAIL, FW_STATE_MAX, }; From d239b952b03685c1753366550aee1a51e17566b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Thu, 13 Mar 2025 15:33:20 +0100 Subject: [PATCH 06/30] fw: Replace custom picorv32 instructions in qemu target --- hw/application_fpga/Makefile | 1 + hw/application_fpga/fw/tk1/start.S | 11 +++++++++++ hw/application_fpga/fw/tk1/syscall_enable.S | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 75d076d..3a6db4b 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -204,6 +204,7 @@ simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG +qemu_firmware.elf: ASFLAGS += -DQEMU_DEBUG qemu_firmware.elf: firmware.elf mv firmware.elf qemu_firmware.elf diff --git a/hw/application_fpga/fw/tk1/start.S b/hw/application_fpga/fw/tk1/start.S index f9b72fc..cdfb39f 100644 --- a/hw/application_fpga/fw/tk1/start.S +++ b/hw/application_fpga/fw/tk1/start.S @@ -4,8 +4,19 @@ */ #include + +#ifdef QEMU_DEBUG + +#define picorv32_retirq_insn(...) \ + mv ra, x3; \ + ret + +#else + #include "picorv32/custom_ops.S" // PicoRV32 custom instructions +#endif + #define illegal_insn() .word 0 // Variables in bss diff --git a/hw/application_fpga/fw/tk1/syscall_enable.S b/hw/application_fpga/fw/tk1/syscall_enable.S index 2bcd357..58cc62a 100644 --- a/hw/application_fpga/fw/tk1/syscall_enable.S +++ b/hw/application_fpga/fw/tk1/syscall_enable.S @@ -1,5 +1,13 @@ +#ifdef QEMU_DEBUG + +#define picorv32_maskirq_insn(...) + +#else + #include "../tk1/picorv32/custom_ops.S" +#endif + .section ".text" .globl syscall_enable From 9e878c3288017d0f145f0522784bd5ff3b4bfe33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Thu, 13 Mar 2025 15:34:14 +0100 Subject: [PATCH 07/30] Add script to load pre-loaded app into flash --- .../tools/load_preloaded_app.sh | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100755 hw/application_fpga/tools/load_preloaded_app.sh diff --git a/hw/application_fpga/tools/load_preloaded_app.sh b/hw/application_fpga/tools/load_preloaded_app.sh new file mode 100755 index 0000000..83163b1 --- /dev/null +++ b/hw/application_fpga/tools/load_preloaded_app.sh @@ -0,0 +1,21 @@ +#!/bin/bash -e + +if [ $# != 1 ] +then + echo "Usage: $0 app_file" + exit +fi + +APP="$1" + +echo "WARNING: Will erase entire partition table." +read -p "Press CTRL-C to abort. Press key to continue." -n1 -s + +# Erase partition table +tillitis-iceprog -o 0x20000 -e 64k + +# Erase existing pre loaded app +tillitis-iceprog -o 0x30000 -e 128k + +# Write pre loaded app +tillitis-iceprog -o 0x30000 "$APP" From 955c7e4736df124646424aead8afc8c6c1035568 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Thu, 13 Mar 2025 16:02:02 +0100 Subject: [PATCH 08/30] Add hardcoded preloaded app size - Enable TKEY_DEBUG - Wait for something on CDC before continuing --- hw/application_fpga/Makefile | 2 +- hw/application_fpga/fw/tk1/main.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 3a6db4b..a5c5819 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -195,7 +195,7 @@ tkey-libs: $(FIRMWARE_OBJS): $(FIRMWARE_DEPS) $(TESTFW_OBJS): $(FIRMWARE_DEPS) -#firmware.elf: CFLAGS += -DTKEY_DEBUG +firmware.elf: CFLAGS += -DTKEY_DEBUG firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 5197489..7ded86a 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -496,6 +496,21 @@ int main(void) scramble_ram(); + // Wait for terminal program and a character to be typed + enum ioend endpoint = IO_NONE; + uint8_t available = 0; + uint8_t in = 0; + + if (readselect(IO_CDC, &endpoint, &available) < 0) { + // readselect failed! I/O broken? Just redblink. + assert(1 == 2); + } + + if (read(IO_CDC, &in, 1, 1) < 0) { + // read failed! I/O broken? Just redblink. + assert(1 == 2); + } + if (part_table_read(&part_table) != 0) { // Couldn't read or create partition table assert(1 != 2); @@ -506,6 +521,10 @@ int main(void) run(&ctx); #endif + // Lie and tell filesystem we have a 128 kiB device app on + // flash. + part_table.pre_app_data.size = 0x20000; + // Just start the preloaded app. if (preload_load(&part_table) == -1) { state = FW_STATE_FAIL; From 980a3c84a17923b38bf9abfb5499ed10c5d2eb28 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Thu, 13 Mar 2025 16:31:32 +0100 Subject: [PATCH 09/30] Make run_flash() and run() both call jump_to_app() --- hw/application_fpga/fw/tk1/main.c | 85 ++++++++++--------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 7ded86a..4232936 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -327,26 +327,8 @@ static enum state loading_commands(const struct frame_header *hdr, return state; } -static void run_flash(const struct context *ctx, struct partition_table *part_table) +static void jump_to_app(void) { - /* At this point we expect an app to be loaded into RAM */ - *app_addr = TK1_RAM_BASE; - - // CDI = hash(uds, hash(app), uss) - compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); - - if (part_table->pre_app_data.status == 0x02) { - debug_puts("Create auth\n"); - auth_app_create(&part_table->pre_app_data.auth); - part_table->pre_app_data.status = 0x01; - part_table_write(part_table); - } - - if (!auth_app_authenticate(&part_table->pre_app_data.auth)) { - debug_puts("!Authenticated\n"); - assert(1 == 2); - } - debug_puts("Flipping to app mode!\n"); debug_puts("Jumping to "); debug_putinthex(*app_addr); @@ -385,6 +367,28 @@ static void run_flash(const struct context *ctx, struct partition_table *part_ta __builtin_unreachable(); } +static void run_flash(const struct context *ctx, struct partition_table *part_table) +{ + /* At this point we expect an app to be loaded into RAM */ + *app_addr = TK1_RAM_BASE; + + // CDI = hash(uds, hash(app), uss) + compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); + + if (part_table->pre_app_data.status == 0x02) { + debug_puts("Create auth\n"); + auth_app_create(&part_table->pre_app_data.auth); + part_table->pre_app_data.status = 0x01; + part_table_write(part_table); + } + + if (!auth_app_authenticate(&part_table->pre_app_data.auth)) { + debug_puts("!Authenticated\n"); + assert(1 == 2); + } + + jump_to_app(); +} static void run(const struct context *ctx) { @@ -394,42 +398,7 @@ static void run(const struct context *ctx) // CDI = hash(uds, hash(app), uss) compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); - debug_puts("Flipping to app mode!\n"); - debug_puts("Jumping to "); - debug_putinthex(*app_addr); - debug_lf(); - - // Clear the firmware stack - // clang-format off -#ifndef S_SPLINT_S - asm volatile( - "la a0, _sstack;" - "la a1, _estack;" - "loop:;" - "sw zero, 0(a0);" - "addi a0, a0, 4;" - "blt a0, a1, loop;" - ::: "memory"); -#endif - // clang-format on - - syscall_enable(); - - // Jump to app - doesn't return - // Hardware is responsible for switching to app mode - // clang-format off -#ifndef S_SPLINT_S - asm volatile( - // Get value at TK1_MMIO_TK1_APP_ADDR - "lui a0,0xff000;" - "lw a0,0x030(a0);" - // Jump to it - "jalr x0,0(a0);" - ::: "memory"); -#endif - // clang-format on - - __builtin_unreachable(); + jump_to_app(); } #if !defined(SIMULATION) @@ -525,7 +494,9 @@ int main(void) // flash. part_table.pre_app_data.size = 0x20000; - // Just start the preloaded app. + // Just start the preloaded app. This should be a part of an + // initial state. The initial state should check resetinfo if + // it should start from flash or not. if (preload_load(&part_table) == -1) { state = FW_STATE_FAIL; } @@ -541,7 +512,7 @@ int main(void) state = FW_STATE_RUN_FLASH; - led_set(LED_GREEN); + // End of initial state. for (;;) { switch (state) { From c359a529051e9e1bea6e331c5a7d8c21dccc8e66 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Thu, 13 Mar 2025 16:36:54 +0100 Subject: [PATCH 10/30] Introduce symbolic names for present and present & authenticated A preloaded app can be: - present and not yet authenticated (0x01) - present and authenticated (0x02) Let's use symbolic names for these. --- hw/application_fpga/fw/tk1/main.c | 6 +++--- hw/application_fpga/fw/tk1/partition_table.h | 5 +++++ hw/application_fpga/fw/tk1/preload_app.c | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 4232936..a098619 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -375,10 +375,10 @@ static void run_flash(const struct context *ctx, struct partition_table *part_ta // CDI = hash(uds, hash(app), uss) compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); - if (part_table->pre_app_data.status == 0x02) { + if (part_table->pre_app_data.status == PRE_LOADED_STATUS_PRESENT) { debug_puts("Create auth\n"); auth_app_create(&part_table->pre_app_data.auth); - part_table->pre_app_data.status = 0x01; + part_table->pre_app_data.status = PRE_LOADED_STATUS_AUTH; part_table_write(part_table); } @@ -508,7 +508,7 @@ int main(void) assert(digest_err == 0); print_digest(ctx.digest); - part_table.pre_app_data.status = 0x02; + part_table.pre_app_data.status = PRE_LOADED_STATUS_PRESENT; state = FW_STATE_RUN_FLASH; diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h index 7a954bb..361bbe3 100644 --- a/hw/application_fpga/fw/tk1/partition_table.h +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -37,6 +37,11 @@ #define ADDR_PRE_LOADED_APP (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE) #define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB +// Pre-loaded app present and authenticated +#define PRE_LOADED_STATUS_AUTH 0x01 +// Pre-loaded app present but not yet authenticated +#define PRE_LOADED_STATUS_PRESENT 0x02 + #define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + SIZE_PRE_LOADED_APP) #define SIZE_STORAGE_AREA 0x20000UL // 128KiB #define N_STORAGE_AREA 4 diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index f47d021..7f929bc 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -94,7 +94,7 @@ int preload_store_finalize(struct partition_table *part_table, bool use_uss, part_table->pre_app_data.size = app_size; part_table->pre_app_data.status = - 0x02; /* Stored but not yet authenticated */ + PRE_LOADED_STATUS_PRESENT; /* Stored but not yet authenticated */ debug_puts("preload_*_final: size: "); debug_putinthex(app_size); debug_lf(); From 1ab6dc12bfaf21bee790c583d1e499e8de918448 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Fri, 14 Mar 2025 17:02:14 +0100 Subject: [PATCH 11/30] Experiment with state machine when starting from flash - Move around code to start an app from flash. - Mark experimental stuff and debug stuff more clearly. --- hw/application_fpga/fw/tk1/main.c | 60 ++++++++++++++++++++---------- hw/application_fpga/fw/tk1/state.h | 1 + 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index a098619..f826dd8 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -68,6 +68,8 @@ static void run(const struct context *ctx); static uint32_t xorwow(uint32_t state, uint32_t acc); #endif static void scramble_ram(void); +static int compute_app_digest(uint8_t *digest); +static int load_flash_app(struct partition_table *part_table, uint8_t digest[32]); static void print_hw_version(void) { @@ -367,6 +369,24 @@ static void jump_to_app(void) __builtin_unreachable(); } +static int load_flash_app(struct partition_table *part_table, uint8_t digest[32]) +{ + if (preload_load(part_table) == -1) { + return -1; + } + + *app_size = part_table->pre_app_data.size; + if (*app_size > TK1_APP_MAX_SIZE) { + return -1; + } + + int digest_err = compute_app_digest(digest); + assert(digest_err == 0); + print_digest(digest); + + return 0; +} + static void run_flash(const struct context *ctx, struct partition_table *part_table) { /* At this point we expect an app to be loaded into RAM */ @@ -465,6 +485,7 @@ int main(void) scramble_ram(); + // TODO Remove // Wait for terminal program and a character to be typed enum ioend endpoint = IO_NONE; uint8_t available = 0; @@ -480,6 +501,8 @@ int main(void) assert(1 == 2); } + // TODO end of remove block + if (part_table_read(&part_table) != 0) { // Couldn't read or create partition table assert(1 != 2); @@ -490,33 +513,20 @@ int main(void) run(&ctx); #endif - // Lie and tell filesystem we have a 128 kiB device app on - // flash. + // TODO Lie and tell filesystem we have a 128 kiB device app + // on flash. part_table.pre_app_data.size = 0x20000; - // Just start the preloaded app. This should be a part of an - // initial state. The initial state should check resetinfo if - // it should start from flash or not. - if (preload_load(&part_table) == -1) { - state = FW_STATE_FAIL; - } - - *app_size = part_table.pre_app_data.size; - assert(*app_size <= TK1_APP_MAX_SIZE); - - int digest_err = compute_app_digest(ctx.digest); - assert(digest_err == 0); - print_digest(ctx.digest); - - part_table.pre_app_data.status = PRE_LOADED_STATUS_PRESENT; - + // TODO Just start something from flash without looking in + // FW_RAM. state = FW_STATE_RUN_FLASH; - // End of initial state. - for (;;) { switch (state) { case FW_STATE_INITIAL: + // Where do we start? Read resetinfo 'startfrom' + + case FW_STATE_WAITCOMMAND: if (readcommand(&hdr, cmd, state) == -1) { state = FW_STATE_FAIL; break; @@ -542,6 +552,16 @@ int main(void) break; // This is never reached! case FW_STATE_RUN_FLASH: + // TODO Just lie and say that an app is present but not yet + // authenticated. + part_table.pre_app_data.status = PRE_LOADED_STATUS_PRESENT; + + if (load_flash_app(&part_table, ctx.digest) < 0) { + debug_puts("Couldn't load app from flash\n"); + state = FW_STATE_FAIL; + break; + } + run_flash(&ctx, &part_table); break; // This is never reached! diff --git a/hw/application_fpga/fw/tk1/state.h b/hw/application_fpga/fw/tk1/state.h index 2ded225..0723d2c 100644 --- a/hw/application_fpga/fw/tk1/state.h +++ b/hw/application_fpga/fw/tk1/state.h @@ -8,6 +8,7 @@ enum state { FW_STATE_INITIAL, + FW_STATE_WAITCOMMAND, FW_STATE_LOADING, FW_STATE_RUN, FW_STATE_RUN_FLASH, From 15036c4d0c3885b502763b954814601c97b4fc82 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Mon, 17 Mar 2025 10:31:08 +0100 Subject: [PATCH 12/30] Experiment with new state machine --- hw/application_fpga/fw/tk1/main.c | 115 +++++++++++++++++++++-------- hw/application_fpga/fw/tk1/state.h | 5 +- 2 files changed, 87 insertions(+), 33 deletions(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index f826dd8..bb9a7f0 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -37,6 +37,7 @@ static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL; static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_ADDR_RAND; static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND; +static volatile uint8_t *startfrom = (volatile uint8_t *)0xd0000000bb9; /// FW_RAM after stack // clang-format on struct partition_table part_table; @@ -48,7 +49,18 @@ struct context { uint8_t *loadaddr; // Where we are currently loading a TKey program bool use_uss; // Use USS? uint8_t uss[32]; // User Supplied Secret, if any - bool from_flash; // App is loaded from flash + uint8_t flash_slot; // App is loaded from flash slot number + bool verify; // Verify? + uint8_t ver_digest[32]; // Verify loaded app against this digest +}; + +enum reset_start { + START_FLASH1 = 1, + START_FLASH2 = 2, + START_FLASH1_VER = 3, + START_FLASH2_VER = 4, + START_CLIENT = 5, + START_CLIENT_VER = 6, }; static void print_hw_version(void); @@ -63,13 +75,13 @@ static enum state initial_commands(const struct frame_header *hdr, static enum state loading_commands(const struct frame_header *hdr, const uint8_t *cmd, enum state state, struct context *ctx); -static void run(const struct context *ctx); #if !defined(SIMULATION) static uint32_t xorwow(uint32_t state, uint32_t acc); #endif static void scramble_ram(void); static int compute_app_digest(uint8_t *digest); static int load_flash_app(struct partition_table *part_table, uint8_t digest[32]); +static enum state start_where(struct context *ctx); static void print_hw_version(void) { @@ -309,7 +321,7 @@ static enum state loading_commands(const struct frame_header *hdr, memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32); fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp); - state = FW_STATE_RUN; + state = FW_STATE_CDI; break; } @@ -331,6 +343,9 @@ static enum state loading_commands(const struct frame_header *hdr, static void jump_to_app(void) { + /* Start of app is always at the beginning of RAM */ + *app_addr = TK1_RAM_BASE; + debug_puts("Flipping to app mode!\n"); debug_puts("Jumping to "); debug_putinthex(*app_addr); @@ -387,14 +402,8 @@ static int load_flash_app(struct partition_table *part_table, uint8_t digest[32] return 0; } -static void run_flash(const struct context *ctx, struct partition_table *part_table) +static enum state auth_flash_app(const struct context *ctx, struct partition_table *part_table) { - /* At this point we expect an app to be loaded into RAM */ - *app_addr = TK1_RAM_BASE; - - // CDI = hash(uds, hash(app), uss) - compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); - if (part_table->pre_app_data.status == PRE_LOADED_STATUS_PRESENT) { debug_puts("Create auth\n"); auth_app_create(&part_table->pre_app_data.auth); @@ -404,21 +413,11 @@ static void run_flash(const struct context *ctx, struct partition_table *part_ta if (!auth_app_authenticate(&part_table->pre_app_data.auth)) { debug_puts("!Authenticated\n"); - assert(1 == 2); + + return FW_STATE_FAIL; } - jump_to_app(); -} - -static void run(const struct context *ctx) -{ - /* At this point we expect an app to be loaded into RAM */ - *app_addr = TK1_RAM_BASE; - - // CDI = hash(uds, hash(app), uss) - compute_cdi(ctx->digest, ctx->use_uss, ctx->uss); - - jump_to_app(); + return FW_STATE_START; } #if !defined(SIMULATION) @@ -464,6 +463,49 @@ static int compute_app_digest(uint8_t *digest) *app_size, &b2s_ctx); } +static enum state start_where(struct context *ctx) +{ + // Where do we start? Read resetinfo 'startfrom' + switch (*startfrom) { + case START_FLASH1: + ctx->flash_slot = 1; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH2: + ctx->flash_slot = 2; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH1_VER: + ctx->flash_slot = 1; + ctx->verify = true; + // ctx.ver_digest = ... + + return FW_STATE_LOAD_FLASH; + + case START_FLASH2_VER: + ctx->flash_slot = 2; + ctx->verify = true; + // ctx.ver_digest = ... + + return FW_STATE_LOAD_FLASH; + + case START_CLIENT: + return FW_STATE_WAITCOMMAND; + + case START_CLIENT_VER: + ctx->verify = true; + + return FW_STATE_WAITCOMMAND; + + default: + debug_puts("Unknown startfrom\n"); + + return FW_STATE_FAIL; + } +} + int main(void) { struct context ctx = {0}; @@ -519,12 +561,13 @@ int main(void) // TODO Just start something from flash without looking in // FW_RAM. - state = FW_STATE_RUN_FLASH; + state = FW_STATE_LOAD_FLASH; for (;;) { switch (state) { case FW_STATE_INITIAL: - // Where do we start? Read resetinfo 'startfrom' + state = start_where(&ctx); + break; case FW_STATE_WAITCOMMAND: if (readcommand(&hdr, cmd, state) == -1) { @@ -547,11 +590,14 @@ int main(void) state = loading_commands(&hdr, cmd, state, &ctx); break; - case FW_STATE_RUN: - run(&ctx); - break; // This is never reached! + case FW_STATE_CDI: + // CDI = hash(uds, hash(app), uss) + compute_cdi(ctx.digest, ctx.use_uss, ctx.uss); - case FW_STATE_RUN_FLASH: + state = FW_STATE_START; + break; + + case FW_STATE_LOAD_FLASH: // TODO Just lie and say that an app is present but not yet // authenticated. part_table.pre_app_data.status = PRE_LOADED_STATUS_PRESENT; @@ -562,8 +608,15 @@ int main(void) break; } - run_flash(&ctx, &part_table); - break; // This is never reached! + // CDI = hash(uds, hash(app), uss) + compute_cdi(ctx.digest, ctx.use_uss, ctx.uss); + + state = auth_flash_app(&ctx, &part_table); + break; + + case FW_STATE_START: + jump_to_app(); + break; // Not reached case FW_STATE_FAIL: // fallthrough diff --git a/hw/application_fpga/fw/tk1/state.h b/hw/application_fpga/fw/tk1/state.h index 0723d2c..3276140 100644 --- a/hw/application_fpga/fw/tk1/state.h +++ b/hw/application_fpga/fw/tk1/state.h @@ -10,8 +10,9 @@ enum state { FW_STATE_INITIAL, FW_STATE_WAITCOMMAND, FW_STATE_LOADING, - FW_STATE_RUN, - FW_STATE_RUN_FLASH, + FW_STATE_CDI, + FW_STATE_LOAD_FLASH, + FW_STATE_START, FW_STATE_FAIL, FW_STATE_MAX, }; From 101c6fe1a2bd45f4d60be962af09c6c0952c1f93 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Mon, 17 Mar 2025 10:31:32 +0100 Subject: [PATCH 13/30] Document state machine Golden path not updated --- hw/application_fpga/fw/README.md | 157 +++++++++++++++++++------------ 1 file changed, 96 insertions(+), 61 deletions(-) diff --git a/hw/application_fpga/fw/README.md b/hw/application_fpga/fw/README.md index 6336c9a..2a09b0e 100644 --- a/hw/application_fpga/fw/README.md +++ b/hw/application_fpga/fw/README.md @@ -102,67 +102,90 @@ 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 also the CPU reset vector. +### Reset intentions + +We have a number of reset options we call `startfrom`: + +1. Start from flash slot 1 (default): `FLASH1` +2. Start from flash slot 2: `FLASH2`. +3. Load and start an app from flash slot 1 with a specific app hash: + `FLASH1_VER` +4. Load and start an app from flash slot 2 with a specific app hash: + `FLASH2_VER`. +5. Load and start a new app from client: `CLIENT`. +6. load and start an app from client with a specific app hash: + `CLIENT_VER`. + ### Firmware state machine -This is the state diagram of the firmware. There are only six states. - -Change of state occur when we receive specific I/O or a fatal error -occurs. +This is the state diagram of the firmware. Change of state occur when +we receive specific I/O or a fatal error occurs. ```mermaid stateDiagram-v2 - S0: resetinfo - S4: loadflash - S1: waitcommand - S2: loading - S3: running - SE: failed + S0: initial + S1: waitcommand + S2: loading + S3: flash_loading + S4: auth_app + S5: starting + S6: compute_cdi + SE: failed - [*] --> S0 + [*] --> S0 - S0 --> S4: load 1 (def) or load 2 - S0 --> S1 + S0 --> S1 + S0 --> S4: Default - S1 --> S1: Commands - S1 --> S2: LOAD_APP - S1 --> SE: Error + S1 --> S1: Commands + S1 --> S2: LOAD_APP + S1 --> SE: Error - S2 --> S2: LOAD_APP_DATA - S2 --> S3: Last block received - S2 --> SE: Error + S2 --> S2: LOAD_APP_DATA + S2 --> S6: Last block received + S2 --> SE: Error - S4 --> S3 - S4 --> SE: Error + S6 --> S3 - SE --> [*] - S3 --> [*] + S3 --> S5 + + S4 --> S5 + S4 --> SE: Error + + SE --> [*] + S5 --> [*] ``` States: -- `resetinfo` - We start by checking resetinfo data in `FW_RAM` -- `waitcommand` - Waiting for initial commands from client. Allows the +- `initial`: We start by checking resetinfo data in `FW_RAM` for + `startfrom`. +- `waitcommand`: Waiting for initial commands from client. Allows the commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`. -- `loadflash` - Loading an app from flash. -- `loading` - Expect application data. Allows only the command - `LOAD_APP_DATA`. -- `running` - Computes CDI and starts the application. Allows no commands. +- `loading`: Expecting application data from client. Allows only the + command `LOAD_APP_DATA`. +- `flash_loading`: Loading and authentication app from flash. Computes CDI, + creates or checks the authentication of the flash app. Allows no commands. +- `starting`: Starts the application. Does not return to firmware. + Allows no commands. - `failed` - Halts CPU. Allows no commands. Allowed data in state `resetinfo`: -| *startfrom* | *next state* | -|----------------------|---------------| -| `default` | `loadflash` | -| `Start flash slot 1` | `loadflash` | -| `Start flash slot 2` | `loadflash` | -| `Start from client` | `waitcommand` | +| *startfrom* | *next state* | +|--------------|-----------------| +| `FLASH1` | `flash_loading` | +| `FLASH2` | `flash_loading` | +| `FLASH1_VER` | `flash_loading` | +| `FLASH2_VER` | `flash_loading` | +| `CLIENT` | `waitcommand` | +| `CLIENT_VER` | `waitcommand` | -I/O in state `loadflash`: +I/O in state `flash_loading`: | *I/O* | *next state* | |--------------------|--------------| -| Last app data read | `run` | +| Last app data read | `starting` | Commands in state `waitcommand`: @@ -174,41 +197,53 @@ Commands in state `waitcommand`: Commands in state `loading`: -| *command* | *next state* | -|------------------------|----------------------------------| -| `FW_CMD_LOAD_APP_DATA` | unchanged or `run` on last chunk | +| *command* | *next state* | +|------------------------|---------------------------------------| +| `FW_CMD_LOAD_APP_DATA` | unchanged or `starting` on last chunk | + +No other states allows commands. See [Firmware protocol in the Dev Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the definition of the specific commands and their responses. -Exection starts in state `resetinfo` where the firmware checks in -`FW_RAM` for what to do next. +Plain text explanation of the states: -State changes to `loadflash` if the `FW_RAM` data indicates -that it should start one of the two flash apps. +- `initial`: Execution starts here. The firmware checks in the + `FW_RAM` for `startfrom` for what to do next. -State changes to `waitcommand` if the `FW_RAM` data indicates that it -instead should wait for commands from a client. + For all `startfrom` values `FLASH_*` the next state is `startflash`. + Otherwise it goes to `waitcommand`, indicating that it should wait + for further commands from the client. -In `loadflash` state changes to `running` if the app has been -successfully loaded into RAM or to `failed` otherwise. +- `flash_loading` loads and measure an app from flash, the Compound + Device Identifier (CDI) is computed, then the app is authenticated + against a stored digest to see that no one has changed the app by + manipulating the flash. The compuation is done by: -State changes from `waitcommand` to `loading` when receiving -`LOAD_APP`, which also sets the size of the number of data blocks to -expect. After that we expect several `LOAD_APP_DATA` commands until -the last block is received, when state is changed to `running`. + digest = blake2s(cdi, nonce from flash) -In `running`, the loaded device app is measured, the Compound Device -Identifier (CDI) is computed, we do some cleanup of firmware data -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. + and then compared against the stored digest in the app's flash slot. -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. +- `waitcommand` waits for command from the client. State changes to + `loading` when receiving `LOAD_APP`, which also sets the size of the + number of data blocks to expect. After that we expect several + `LOAD_APP_DATA` commands until the last block is received, when + state is changed to `running`. + +- `compute_cdi`: The the Compound Device Identifier (CDI) is computed + and we go to `starting`. + +- `starting`: Clean up firmware data structures, enable the system + calls, and start the app, which ends the firmware state machine. + Hardware guarantees that we leave firmware mode automatically when + the program counter leaves ROM. + +After `starting` 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 From 1f81c9bdb6db01de9074e1c5d51606bcad06f8dc Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Mon, 17 Mar 2025 15:47:19 +0100 Subject: [PATCH 14/30] Add resetinfo handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decide where to start from with data from resetinfo part of FW_RAM. Co-authored-by: Jonas Thörnblad Co-authored-by: Mikael Ågren --- hw/application_fpga/fw/tk1/main.c | 31 +++++++++++--------------- hw/application_fpga/fw/tk1/resetinfo.h | 30 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 hw/application_fpga/fw/tk1/resetinfo.h diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index bb9a7f0..98d54ee 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -19,6 +19,7 @@ #include "proto.h" #include "state.h" #include "syscall_enable.h" +#include "resetinfo.h" // clang-format off static volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST; @@ -37,7 +38,7 @@ static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL; static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_ADDR_RAND; static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND; -static volatile uint8_t *startfrom = (volatile uint8_t *)0xd0000000bb9; /// FW_RAM after stack +static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE; // clang-format on struct partition_table part_table; @@ -50,17 +51,7 @@ struct context { bool use_uss; // Use USS? uint8_t uss[32]; // User Supplied Secret, if any uint8_t flash_slot; // App is loaded from flash slot number - bool verify; // Verify? - uint8_t ver_digest[32]; // Verify loaded app against this digest -}; - -enum reset_start { - START_FLASH1 = 1, - START_FLASH2 = 2, - START_FLASH1_VER = 3, - START_FLASH2_VER = 4, - START_CLIENT = 5, - START_CLIENT_VER = 6, + volatile uint8_t *ver_digest; // Verify loaded app against this digest }; static void print_hw_version(void); @@ -466,36 +457,40 @@ static int compute_app_digest(uint8_t *digest) static enum state start_where(struct context *ctx) { // Where do we start? Read resetinfo 'startfrom' - switch (*startfrom) { + switch (resetinfo->type) { + case START_DEFAULT: + // fallthrough case START_FLASH1: ctx->flash_slot = 1; + ctx->ver_digest = NULL; return FW_STATE_LOAD_FLASH; case START_FLASH2: ctx->flash_slot = 2; + ctx->ver_digest = NULL; return FW_STATE_LOAD_FLASH; case START_FLASH1_VER: ctx->flash_slot = 1; - ctx->verify = true; - // ctx.ver_digest = ... + ctx->ver_digest = resetinfo->app_digest; return FW_STATE_LOAD_FLASH; case START_FLASH2_VER: ctx->flash_slot = 2; - ctx->verify = true; - // ctx.ver_digest = ... + ctx->ver_digest = resetinfo->app_digest; return FW_STATE_LOAD_FLASH; case START_CLIENT: + ctx->ver_digest = NULL; + return FW_STATE_WAITCOMMAND; case START_CLIENT_VER: - ctx->verify = true; + ctx->ver_digest = resetinfo->app_digest; return FW_STATE_WAITCOMMAND; diff --git a/hw/application_fpga/fw/tk1/resetinfo.h b/hw/application_fpga/fw/tk1/resetinfo.h new file mode 100644 index 0000000..787bb33 --- /dev/null +++ b/hw/application_fpga/fw/tk1/resetinfo.h @@ -0,0 +1,30 @@ +// Copyright (C) 2025 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_RESETINFO_H +#define TKEY_RESETINFO_H + +#include + + +#define TK1_MMIO_RESETINFO_BASE 0xd0000f00 +#define TK1_MMIO_RESETINFO_SIZE 0x100 + + +enum reset_start { + START_DEFAULT = 0, // Probably cold boot + START_FLASH1 = 1, + START_FLASH2 = 2, + START_FLASH1_VER = 3, + START_FLASH2_VER = 4, + START_CLIENT = 5, + START_CLIENT_VER = 6, +}; + +struct reset { + uint32_t type; // Reset type + uint8_t app_digest[32]; // Program digest + uint8_t next_app_data[220]; // Data to leave around for next app +}; + +#endif From 7a59d778f2d5b8a442fbabeccfd42c19720345f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Th=C3=B6rnblad?= Date: Mon, 17 Mar 2025 16:39:45 +0100 Subject: [PATCH 15/30] Add resetinfo testapp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikael Ågren Co-authored-by: Michael Cardell Widerkrantz --- hw/application_fpga/fw/reset_test/Makefile | 76 ++++++++++++++ hw/application_fpga/fw/reset_test/app.lds | 64 ++++++++++++ hw/application_fpga/fw/reset_test/crt0.S | 53 ++++++++++ hw/application_fpga/fw/reset_test/main.c | 110 ++++++++++++++++++++ hw/application_fpga/fw/reset_test/syscall.S | 85 +++++++++++++++ 5 files changed, 388 insertions(+) create mode 100644 hw/application_fpga/fw/reset_test/Makefile create mode 100644 hw/application_fpga/fw/reset_test/app.lds create mode 100644 hw/application_fpga/fw/reset_test/crt0.S create mode 100644 hw/application_fpga/fw/reset_test/main.c create mode 100644 hw/application_fpga/fw/reset_test/syscall.S diff --git a/hw/application_fpga/fw/reset_test/Makefile b/hw/application_fpga/fw/reset_test/Makefile new file mode 100644 index 0000000..ed09be5 --- /dev/null +++ b/hw/application_fpga/fw/reset_test/Makefile @@ -0,0 +1,76 @@ +P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +LIBDIR ?= ../../tkey-libs +OBJCOPY ?= llvm-objcopy +CC = clang +CFLAGS = \ + -target riscv32-unknown-none-elf \ + -march=rv32iczmmul \ + -mabi=ilp32 \ + -mcmodel=medany \ + -static \ + -std=gnu99 \ + -O2 \ + -ffast-math \ + -fno-common \ + -fno-builtin-printf \ + -fno-builtin-putchar \ + -fno-builtin-memcpy \ + -nostdlib \ + -mno-relax \ + -Wall \ + -Wpedantic \ + -Wno-language-extension-token \ + -Werror \ + -flto \ + -g \ + -I $(LIBDIR)/include \ + -I $(LIBDIR) \ + -DTKEY_DEBUG + +AS = clang + +ASFLAGS = \ + -target riscv32-unknown-none-elf \ + -march=rv32iczmmul \ + -mabi=ilp32 \ + -mno-relax + +LDFLAGS = \ + -T $(P)/app.lds \ + -L $(LIBDIR) -lcommon + +.PHONY: all +all: reset_test.bin + +# Turn elf into bin for device +%.bin: %.elf + $(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@ + chmod a-x $@ + +.PHONY: tkey-libs +tkey-libs: + make -C $(LIBDIR) + +RESET_TEST_FMTFILES = \ + $(P)/main.c \ + +RESET_TEST_OBJS = \ + $(P)/main.o \ + $(P)/crt0.o \ + $(P)/syscall.o + +reset_test.elf: tkey-libs $(RESET_TEST_OBJS) + $(CC) $(CFLAGS) $(RESET_TEST_OBJS) $(LDFLAGS) -o $@ + +.PHONY: fmt +fmt: + clang-format --dry-run --ferror-limit=0 $(RESET_TEST_FMTFILES) + clang-format --verbose -i $(RESET_TEST_FMTFILES) + +.PHONY: checkfmt +checkfmt: + clang-format --dry-run --ferror-limit=0 --Werror $(RESET_TEST_FMTFILES) + +.PHONY: clean +clean: + rm -f reset_test.bin reset_test.elf $(RESET_TEST_OBJS) diff --git a/hw/application_fpga/fw/reset_test/app.lds b/hw/application_fpga/fw/reset_test/app.lds new file mode 100644 index 0000000..421122c --- /dev/null +++ b/hw/application_fpga/fw/reset_test/app.lds @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2022 Tillitis AB + * SPDX-License-Identifier: BSD-2-Clause + */ + +OUTPUT_ARCH( "riscv" ) +ENTRY(_start) + +MEMORY +{ + RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */ +} + +SECTIONS +{ + .text.init : + { + *(.text.init) + } >RAM + + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.srodata) /* .rodata sections (constants, strings, etc.) */ + *(.srodata*) /* .rodata* sections (constants, strings, etc.) */ + . = ALIGN(4); + _etext = .; + _sidata = _etext; + } >RAM + + .data : AT (_etext) + { + . = ALIGN(4); + _sdata = .; + . = ALIGN(4); + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + *(.sdata) /* .sdata sections */ + *(.sdata*) /* .sdata* sections */ + . = ALIGN(4); + _edata = .; + } >RAM + + /* Uninitialized data section */ + .bss : + { + . = ALIGN(4); + _sbss = .; + *(.bss) + *(.bss*) + *(.sbss) + *(.sbss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; + } >RAM + + /* libcrt0/crt0.S inits stack to start just below end of RAM */ +} diff --git a/hw/application_fpga/fw/reset_test/crt0.S b/hw/application_fpga/fw/reset_test/crt0.S new file mode 100644 index 0000000..f484b7d --- /dev/null +++ b/hw/application_fpga/fw/reset_test/crt0.S @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2022 Tillitis AB +// SPDX-License-Identifier: BSD-2-Clause + + .section ".text.init" + .global _start +_start: + li x1, 0 + li x2, 0 + li x3, 0 + li x4, 0 + li x5, 0 + li x6, 0 + li x7, 0 + li x8, 0 + li x9, 0 + li x10,0 + li x11,0 + li x12,0 + li x13,0 + li x14,0 + li x15,0 + li x16,0 + li x17,0 + li x18,0 + li x19,0 + li x20,0 + li x21,0 + li x22,0 + li x23,0 + li x24,0 + li x25,0 + li x26,0 + li x27,0 + li x28,0 + li x29,0 + li x30,0 + li x31,0 + + /* init stack below 0x40020000 (TK1_RAM_BASE+TK1_RAM_SIZE) */ + li sp, 0x4001fff0 + + /* zero-init bss section */ + la a0, _sbss + la a1, _ebss + bge a0, a1, end_init_bss + +loop_init_bss: + sw zero, 0(a0) + addi a0, a0, 4 + blt a0, a1, loop_init_bss + +end_init_bss: + call main diff --git a/hw/application_fpga/fw/reset_test/main.c b/hw/application_fpga/fw/reset_test/main.c new file mode 100644 index 0000000..8488d8f --- /dev/null +++ b/hw/application_fpga/fw/reset_test/main.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022, 2023 - Tillitis AB + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../testapp/syscall.h" +#include "../tk1/proto.h" +#include "../tk1/resetinfo.h" +#include "../tk1/syscall_num.h" + +// Converts a single hex character to its integer value +static uint8_t hex_char_to_byte(uint8_t c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return 0; // Invalid character, should not happen if input is valid +} + +// Converts a 64-char hex string into a 32-byte array +int hex_string_to_bytes(uint8_t *hex_str, uint8_t *out_bytes, size_t out_len) +{ + if (!hex_str || !out_bytes || out_len < 32) + return -1; // Error handling + + for (size_t i = 0; i < 32; i++) { + out_bytes[i] = (hex_char_to_byte(hex_str[i * 2]) << 4) | + hex_char_to_byte(hex_str[i * 2 + 1]); + } + + return 0; // Success +} +//--------------------------------------- + +#define BUFSIZE 32 + +int main(void) +{ + uint8_t available = 0; + uint8_t cmdbuf[BUFSIZE] = {0}; + enum ioend endpoint = IO_NONE; + led_set(LED_BLUE); + struct reset rst = {0}; + + while (1) { + + debug_puts("Waiting for command\n"); + + memset(cmdbuf, 0, BUFSIZE); + + // Wait for data + if (readselect(IO_TKEYCTRL, &endpoint, &available) < 0) { + assert(1 == 2); + } + + if (read(IO_TKEYCTRL, cmdbuf, BUFSIZE, available) < 0) { + // read failed! I/O broken? Just redblink. + assert(1 == 2); + } + + switch (cmdbuf[0]) { + case '1': + rst.type = START_DEFAULT; + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + break; + + case '2': + rst.type = START_CLIENT; + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + break; + + case '3': + rst.type = START_FLASH1; + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + break; + + case '4': { + uint8_t string[] = "83da11b65f9c3721879bc4d9cffa6eac236" + "8dcd9562aedde4002e6108ac939b3"; + rst.type = START_CLIENT_VER; + hex_string_to_bytes(string, (uint8_t *)&rst.app_digest, + sizeof(rst.app_digest)); + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + } break; + + case '5': { + uint8_t string[] = "ef1337a922945fd87683b71ed275e02af44" + "b3489057a29d14fd78daff8b73a28"; + rst.type = START_CLIENT_VER; + hex_string_to_bytes(string, (uint8_t *)&rst.app_digest, + sizeof(rst.app_digest)); + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + } break; + + default: + break; + } + } +} diff --git a/hw/application_fpga/fw/reset_test/syscall.S b/hw/application_fpga/fw/reset_test/syscall.S new file mode 100644 index 0000000..5fc9b3f --- /dev/null +++ b/hw/application_fpga/fw/reset_test/syscall.S @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2024 Tillitis AB +// SPDX-License-Identifier: BSD-2-Clause + +#include "../tk1/picorv32/custom_ops.S" + + .section ".text" + .globl syscall + + +syscall: + // Save registers to stack + addi sp, sp, -32*4 + sw x0, 0*4(sp) + sw x1, 1*4(sp) + // x2 (sp) is assumed to be preserved by the interrupt handler. + sw x3, 3*4(sp) + sw x4, 4*4(sp) + sw x5, 5*4(sp) + sw x6, 6*4(sp) + sw x7, 7*4(sp) + sw x8, 8*4(sp) + sw x9, 9*4(sp) + // x10 (a0) will contain syscall return value. And should not be saved. + sw x11, 11*4(sp) + sw x12, 12*4(sp) + sw x13, 13*4(sp) + sw x14, 14*4(sp) + sw x15, 15*4(sp) + sw x16, 16*4(sp) + sw x17, 17*4(sp) + sw x18, 18*4(sp) + sw x19, 19*4(sp) + sw x20, 20*4(sp) + sw x21, 21*4(sp) + sw x22, 22*4(sp) + sw x23, 23*4(sp) + sw x24, 24*4(sp) + sw x25, 25*4(sp) + sw x26, 26*4(sp) + sw x27, 27*4(sp) + sw x28, 28*4(sp) + sw x29, 29*4(sp) + sw x30, 30*4(sp) + sw x31, 31*4(sp) + + // Trigger syscall interrupt + li t1, 0xe1000000 // Syscall interrupt trigger address + sw zero, 0(t1) // Trigger interrupt + + // Restore registers from stack + lw x0, 0*4(sp) + lw x1, 1*4(sp) + // x2 (sp) is assumed to be preserved by the interrupt handler. + lw x3, 3*4(sp) + lw x4, 4*4(sp) + lw x5, 5*4(sp) + lw x6, 6*4(sp) + lw x7, 7*4(sp) + lw x8, 8*4(sp) + lw x9, 9*4(sp) + // x10 (a0) contains syscall return value. And should not be destroyed. + lw x11, 11*4(sp) + lw x12, 12*4(sp) + lw x13, 13*4(sp) + lw x14, 14*4(sp) + lw x15, 15*4(sp) + lw x16, 16*4(sp) + lw x17, 17*4(sp) + lw x18, 18*4(sp) + lw x19, 19*4(sp) + lw x20, 20*4(sp) + lw x21, 21*4(sp) + lw x22, 22*4(sp) + lw x23, 23*4(sp) + lw x24, 24*4(sp) + lw x25, 25*4(sp) + lw x26, 26*4(sp) + lw x27, 27*4(sp) + lw x28, 28*4(sp) + lw x29, 29*4(sp) + lw x30, 30*4(sp) + lw x31, 31*4(sp) + addi sp, sp, 32*4 + + ret From 6067d130a24eb102a8f8f1613aec9fa29d088694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Mon, 17 Mar 2025 18:41:50 +0100 Subject: [PATCH 16/30] Handle reset info in reset syscall Disabling debug printouts to get firmware to fit in ROM --- hw/application_fpga/Makefile | 2 +- hw/application_fpga/fw/reset_test/main.c | 4 +++- hw/application_fpga/fw/tk1/main.c | 4 +++- hw/application_fpga/fw/tk1/syscall_handler.c | 6 ++++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index a5c5819..447ff38 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -195,7 +195,7 @@ tkey-libs: $(FIRMWARE_OBJS): $(FIRMWARE_DEPS) $(TESTFW_OBJS): $(FIRMWARE_DEPS) -firmware.elf: CFLAGS += -DTKEY_DEBUG +# firmware.elf: CFLAGS += -DTKEY_DEBUG firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map diff --git a/hw/application_fpga/fw/reset_test/main.c b/hw/application_fpga/fw/reset_test/main.c index 8488d8f..f3b008e 100644 --- a/hw/application_fpga/fw/reset_test/main.c +++ b/hw/application_fpga/fw/reset_test/main.c @@ -55,7 +55,7 @@ int main(void) while (1) { - debug_puts("Waiting for command\n"); + debug_puts("reset_test: Waiting for command\n"); memset(cmdbuf, 0, BUFSIZE); @@ -69,6 +69,8 @@ int main(void) assert(1 == 2); } + led_set(LED_BLUE | LED_RED); + switch (cmdbuf[0]) { case '1': rst.type = START_DEFAULT; diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 98d54ee..6ed270f 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -524,6 +524,7 @@ int main(void) // TODO Remove // Wait for terminal program and a character to be typed + /* enum ioend endpoint = IO_NONE; uint8_t available = 0; uint8_t in = 0; @@ -537,6 +538,7 @@ int main(void) // read failed! I/O broken? Just redblink. assert(1 == 2); } + */ // TODO end of remove block @@ -556,7 +558,7 @@ int main(void) // TODO Just start something from flash without looking in // FW_RAM. - state = FW_STATE_LOAD_FLASH; + //state = FW_STATE_LOAD_FLASH; for (;;) { switch (state) { diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index 083824d..0de09cd 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -7,15 +7,18 @@ #include #include #include +#include #include "partition_table.h" #include "storage.h" +#include "../tk1/resetinfo.h" #include "../tk1/syscall_num.h" // clang-format off static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET; static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST; +static volatile uint8_t *resetinfo = (volatile uint8_t *) TK1_MMIO_RESETINFO_BASE; // clang-format on extern struct partition_table part_table; @@ -25,7 +28,10 @@ int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, { switch (number) { case TK1_SYSCALL_RESET: + // TODO: Take length from user + memcpy((uint8_t *)resetinfo, (uint8_t *)arg1, sizeof(struct reset)); *system_reset = 1; + return 0; case TK1_SYSCALL_ALLOC_AREA: if (storage_allocate_area(&part_table) < 0) { From 6ad32f73175a5839385c663f5930c3e2876c19ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Tue, 18 Mar 2025 09:16:58 +0100 Subject: [PATCH 17/30] When requested, verify app digest before running --- hw/application_fpga/fw/reset_test/main.c | 12 +++++++----- hw/application_fpga/fw/tk1/main.c | 11 +++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/hw/application_fpga/fw/reset_test/main.c b/hw/application_fpga/fw/reset_test/main.c index f3b008e..547d416 100644 --- a/hw/application_fpga/fw/reset_test/main.c +++ b/hw/application_fpga/fw/reset_test/main.c @@ -88,8 +88,8 @@ int main(void) break; case '4': { - uint8_t string[] = "83da11b65f9c3721879bc4d9cffa6eac236" - "8dcd9562aedde4002e6108ac939b3"; + uint8_t string[] = "0123456789abcdef0123456789abcdef012" + "3456789abcdef0123456789abcdef"; rst.type = START_CLIENT_VER; hex_string_to_bytes(string, (uint8_t *)&rst.app_digest, sizeof(rst.app_digest)); @@ -97,10 +97,12 @@ int main(void) } break; case '5': { - uint8_t string[] = "ef1337a922945fd87683b71ed275e02af44" - "b3489057a29d14fd78daff8b73a28"; + uint8_t tkeylibs_example_app_digest[] = + "96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30" + "c9d4bf66a98d97"; rst.type = START_CLIENT_VER; - hex_string_to_bytes(string, (uint8_t *)&rst.app_digest, + hex_string_to_bytes(tkeylibs_example_app_digest, + (uint8_t *)&rst.app_digest, sizeof(rst.app_digest)); syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); } break; diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 6ed270f..31a066a 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -612,6 +612,17 @@ int main(void) break; case FW_STATE_START: + if (ctx.ver_digest != NULL) { + print_digest(ctx.digest); + if (!memeq(ctx.digest, (void*)ctx.ver_digest, sizeof(ctx.digest))) { + debug_puts("Digests do not match\n"); + state = FW_STATE_FAIL; + break; + } + } + + memset((void*)resetinfo->app_digest, 0, sizeof(resetinfo->app_digest)); + jump_to_app(); break; // Not reached From 970668a47b53404f07ae25fe974bde2f1cdd385d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Tue, 18 Mar 2025 13:18:33 +0100 Subject: [PATCH 18/30] Add second pre-loaded app slot in flash --- hw/application_fpga/fw/reset_test/main.c | 20 ++++++++++++++++ hw/application_fpga/fw/tk1/main.c | 18 +++++++------- hw/application_fpga/fw/tk1/partition_table.h | 13 +++++----- hw/application_fpga/fw/tk1/preload_app.c | 24 ++++++++++--------- hw/application_fpga/fw/tk1/preload_app.h | 11 +++++---- .../tools/load_preloaded_app.sh | 22 +++++++++++++---- 6 files changed, 73 insertions(+), 35 deletions(-) diff --git a/hw/application_fpga/fw/reset_test/main.c b/hw/application_fpga/fw/reset_test/main.c index 547d416..784d610 100644 --- a/hw/application_fpga/fw/reset_test/main.c +++ b/hw/application_fpga/fw/reset_test/main.c @@ -107,6 +107,26 @@ int main(void) syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); } break; + case '6': { + uint8_t string[] = "0123456789abcdef0123456789abcdef012" + "3456789abcdef0123456789abcdef"; + rst.type = START_FLASH2_VER; + hex_string_to_bytes(string, (uint8_t *)&rst.app_digest, + sizeof(rst.app_digest)); + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + } break; + + case '7': { + uint8_t tkeylibs_example_app_digest[] = + "a97f6ec2112067c4b5b5860521e252a095d221652f7b3d056b" + "d98eaba40b4967"; + rst.type = START_FLASH2_VER; + hex_string_to_bytes(tkeylibs_example_app_digest, + (uint8_t *)&rst.app_digest, + sizeof(rst.app_digest)); + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + } break; + default: break; } diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 31a066a..b9bd764 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -71,7 +71,8 @@ static uint32_t xorwow(uint32_t state, uint32_t acc); #endif static void scramble_ram(void); static int compute_app_digest(uint8_t *digest); -static int load_flash_app(struct partition_table *part_table, uint8_t digest[32]); +static int load_flash_app(struct partition_table *part_table, + uint8_t digest[32], uint8_t slot); static enum state start_where(struct context *ctx); static void print_hw_version(void) @@ -375,9 +376,10 @@ static void jump_to_app(void) __builtin_unreachable(); } -static int load_flash_app(struct partition_table *part_table, uint8_t digest[32]) +static int load_flash_app(struct partition_table *part_table, + uint8_t digest[32], uint8_t slot) { - if (preload_load(part_table) == -1) { + if (preload_load(part_table, slot) == -1) { return -1; } @@ -461,25 +463,25 @@ static enum state start_where(struct context *ctx) case START_DEFAULT: // fallthrough case START_FLASH1: - ctx->flash_slot = 1; + ctx->flash_slot = 0; ctx->ver_digest = NULL; return FW_STATE_LOAD_FLASH; case START_FLASH2: - ctx->flash_slot = 2; + ctx->flash_slot = 1; ctx->ver_digest = NULL; return FW_STATE_LOAD_FLASH; case START_FLASH1_VER: - ctx->flash_slot = 1; + ctx->flash_slot = 0; ctx->ver_digest = resetinfo->app_digest; return FW_STATE_LOAD_FLASH; case START_FLASH2_VER: - ctx->flash_slot = 2; + ctx->flash_slot = 1; ctx->ver_digest = resetinfo->app_digest; return FW_STATE_LOAD_FLASH; @@ -599,7 +601,7 @@ int main(void) // authenticated. part_table.pre_app_data.status = PRE_LOADED_STATUS_PRESENT; - if (load_flash_app(&part_table, ctx.digest) < 0) { + if (load_flash_app(&part_table, ctx.digest, ctx.flash_slot) < 0) { debug_puts("Couldn't load app from flash\n"); state = FW_STATE_FAIL; break; diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h index 361bbe3..f59baee 100644 --- a/hw/application_fpga/fw/tk1/partition_table.h +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -13,12 +13,13 @@ /* ---- ---- ---- */ /* Partition 64KiB 0x20000 */ /* ---- ---- ---- */ -/* Pre load 128KiB 0x30000 */ +/* Pre load 1 128KiB 0x30000 */ +/* Pre load 2 128KiB 0x50000 */ /* ---- ---- ---- */ -/* storage 1 128KiB 0x50000 */ -/* storage 2 128KiB 0x70000 */ -/* storage 3 128KiB 0x90000 */ -/* storage 4 128KiB 0xB0000 */ +/* storage 1 128KiB 0x70000 */ +/* storage 2 128KiB 0x90000 */ +/* storage 3 128KiB 0xB0000 */ +/* storage 4 128KiB 0xD0000 */ /* ---- ---- ---- */ /* To simplify all blocks are aligned with the 64KiB blocks on the W25Q80DL @@ -42,7 +43,7 @@ // Pre-loaded app present but not yet authenticated #define PRE_LOADED_STATUS_PRESENT 0x02 -#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + SIZE_PRE_LOADED_APP) +#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + (2 * SIZE_PRE_LOADED_APP)) #define SIZE_STORAGE_AREA 0x20000UL // 128KiB #define N_STORAGE_AREA 4 diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index 7f929bc..b46e79b 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -14,7 +14,8 @@ #include "preload_app.h" /* Returns non-zero if the app is valid */ -bool preload_check_valid_app(struct partition_table *part_table) +bool preload_check_valid_app(struct partition_table *part_table, + uint8_t slot) { if (part_table->pre_app_data.status == 0x00 && @@ -27,17 +28,18 @@ bool preload_check_valid_app(struct partition_table *part_table) } /* Loads a preloaded app from flash to app RAM */ -int preload_load(struct partition_table *part_table) +int preload_load(struct partition_table *part_table, uint8_t from_slot) { /*Check for a valid app in flash */ - if (!preload_check_valid_app(part_table)) { + if (!preload_check_valid_app(part_table, from_slot)) { return -1; } uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE; /* Read from flash, straight into RAM */ - int ret = flash_read_data(ADDR_PRE_LOADED_APP, loadaddr, - part_table->pre_app_data.size); + int ret = flash_read_data(ADDR_PRE_LOADED_APP + + from_slot * SIZE_PRE_LOADED_APP, + loadaddr, part_table->pre_app_data.size); return ret; } @@ -47,7 +49,7 @@ int preload_load(struct partition_table *part_table) * Once done, call preload_store_finalize() with the last parameters. * */ int preload_store(struct partition_table *part_table, uint32_t offset, - uint8_t *data, size_t size) + uint8_t *data, size_t size, uint8_t to_slot) { /* Check if we are allowed to store */ if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { @@ -55,7 +57,7 @@ int preload_store(struct partition_table *part_table, uint32_t offset, } /* Check for a valid app in flash, bale out if it already exists */ - if (preload_check_valid_app(part_table)) { + if (preload_check_valid_app(part_table, to_slot)) { return -1; } @@ -74,7 +76,7 @@ int preload_store(struct partition_table *part_table, uint32_t offset, } int preload_store_finalize(struct partition_table *part_table, bool use_uss, - uint8_t *uss, size_t app_size) + uint8_t *uss, size_t app_size, uint8_t to_slot) { /* Check if we are allowed to store */ if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { @@ -82,7 +84,7 @@ int preload_store_finalize(struct partition_table *part_table, bool use_uss, } /* Check for a valid app in flash, bale out if it already exists */ - if (preload_check_valid_app(part_table)) { + if (preload_check_valid_app(part_table, to_slot)) { return -1; } @@ -107,7 +109,7 @@ int preload_store_finalize(struct partition_table *part_table, bool use_uss, return 0; } -int preload_delete(struct partition_table *part_table) +int preload_delete(struct partition_table *part_table, uint8_t slot) { /* Check if we are allowed to deleted */ if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { @@ -115,7 +117,7 @@ int preload_delete(struct partition_table *part_table) } /*Check for a valid app in flash */ - if (!preload_check_valid_app(part_table)) { + if (!preload_check_valid_app(part_table, slot)) { return 0; // TODO: Nothing here, return zero like all is good? } diff --git a/hw/application_fpga/fw/tk1/preload_app.h b/hw/application_fpga/fw/tk1/preload_app.h index d518129..29473ac 100644 --- a/hw/application_fpga/fw/tk1/preload_app.h +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -9,12 +9,13 @@ #include #include -bool preload_check_valid_app(struct partition_table *part_table); -int preload_load(struct partition_table *part_table); +bool preload_check_valid_app(struct partition_table *part_table, + uint8_t slot); +int preload_load(struct partition_table *part_table, uint8_t from_slot); int preload_store(struct partition_table *part_table, uint32_t offset, - uint8_t *data, size_t size); + uint8_t *data, size_t size, uint8_t to_slot); int preload_store_finalize(struct partition_table *part_table, bool use_uss, - uint8_t *uss, size_t app_size); -int preload_delete(struct partition_table *part_table); + uint8_t *uss, size_t app_size, uint8_t to_slot); +int preload_delete(struct partition_table *part_table, uint8_t slot); #endif diff --git a/hw/application_fpga/tools/load_preloaded_app.sh b/hw/application_fpga/tools/load_preloaded_app.sh index 83163b1..8b01a59 100755 --- a/hw/application_fpga/tools/load_preloaded_app.sh +++ b/hw/application_fpga/tools/load_preloaded_app.sh @@ -1,12 +1,24 @@ #!/bin/bash -e -if [ $# != 1 ] +if [ $# != 2 ] then - echo "Usage: $0 app_file" + echo "Usage: $0 slot_num app_file" + echo "" + echo "Where slot_num is 0 or 1." exit fi -APP="$1" +SLOT_NUM="$1" +APP="$2" + +if [ "$SLOT_NUM" = "0" ]; then + START_ADDRESS=0x30000 +elif [ "$SLOT_NUM" = "1" ]; then + START_ADDRESS=0x50000 +else + echo "Invalid slot_num" + exit 1 +fi echo "WARNING: Will erase entire partition table." read -p "Press CTRL-C to abort. Press key to continue." -n1 -s @@ -15,7 +27,7 @@ read -p "Press CTRL-C to abort. Press key to continue." -n1 -s tillitis-iceprog -o 0x20000 -e 64k # Erase existing pre loaded app -tillitis-iceprog -o 0x30000 -e 128k +tillitis-iceprog -o "$START_ADDRESS" -e 128k # Write pre loaded app -tillitis-iceprog -o 0x30000 "$APP" +tillitis-iceprog -o "$START_ADDRESS" "$APP" From dd147657a41dd6311317746e5900c96bda4fadb8 Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Tue, 18 Mar 2025 14:56:15 +0100 Subject: [PATCH 19/30] Introduce syscalls to change preloaded app Introduce syscalls: - TK1_SYSCALL_PRELOAD_STORE - TK1_SYSCALL_PRELOAD_STORE_FIN - TK1_SYSCALL_PRELOAD_DELETE - TK1_SYSCALL_REG_MGMT = 11 Change preload_store_finalize() not to take USS arg. Unused for preloaded apps. --- hw/application_fpga/fw/tk1/preload_app.c | 5 +---- hw/application_fpga/fw/tk1/preload_app.h | 3 +-- hw/application_fpga/fw/tk1/syscall_handler.c | 21 ++++++++++++++++++++ hw/application_fpga/fw/tk1/syscall_num.h | 6 +++++- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index b46e79b..0560bf9 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -75,8 +75,7 @@ int preload_store(struct partition_table *part_table, uint32_t offset, return flash_write_data(address, data, size); } -int preload_store_finalize(struct partition_table *part_table, bool use_uss, - uint8_t *uss, size_t app_size, uint8_t to_slot) +int preload_store_finalize(struct partition_table *part_table, size_t app_size, uint8_t to_slot) { /* Check if we are allowed to store */ if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { @@ -88,8 +87,6 @@ int preload_store_finalize(struct partition_table *part_table, bool use_uss, return -1; } - // TODO: Maybe add the uss fields - if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) { return -2; } diff --git a/hw/application_fpga/fw/tk1/preload_app.h b/hw/application_fpga/fw/tk1/preload_app.h index 29473ac..3401c8a 100644 --- a/hw/application_fpga/fw/tk1/preload_app.h +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -14,8 +14,7 @@ bool preload_check_valid_app(struct partition_table *part_table, int preload_load(struct partition_table *part_table, uint8_t from_slot); int preload_store(struct partition_table *part_table, uint32_t offset, uint8_t *data, size_t size, uint8_t to_slot); -int preload_store_finalize(struct partition_table *part_table, bool use_uss, - uint8_t *uss, size_t app_size, uint8_t to_slot); +int preload_store_finalize(struct partition_table *part_table, size_t app_size, uint8_t to_slot); int preload_delete(struct partition_table *part_table, uint8_t slot); #endif diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index 0de09cd..86b1392 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -9,7 +9,9 @@ #include #include +#include "mgmt_app.h" #include "partition_table.h" +#include "preload_app.h" #include "storage.h" #include "../tk1/resetinfo.h" @@ -71,6 +73,25 @@ int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, // first word. Serial is kept secret to the device // app. return udi[0]; + + case TK1_SYSCALL_PRELOAD_DELETE: + return preload_delete(&part_table, 1); + + case TK1_SYSCALL_PRELOAD_STORE: + // arg1 offset + // arg2 data + // arg3 size + // always using slot 1 + return preload_store(&part_table, arg1, (uint8_t *)arg2, arg3, 1); + + case TK1_SYSCALL_PRELOAD_STORE_FIN: + // arg1 app_size + // always using slot 1 + return preload_store_finalize(&part_table, arg1, 1); + + case TK1_SYSCALL_REG_MGMT: + return mgmt_app_register(&part_table); + default: assert(1 == 2); } diff --git a/hw/application_fpga/fw/tk1/syscall_num.h b/hw/application_fpga/fw/tk1/syscall_num.h index e1b5a95..41fdc0c 100644 --- a/hw/application_fpga/fw/tk1/syscall_num.h +++ b/hw/application_fpga/fw/tk1/syscall_num.h @@ -12,7 +12,11 @@ enum syscall_num { TK1_SYSCALL_READ_DATA = 5, TK1_SYSCALL_ERASE_DATA = 6, TK1_SYSCALL_GET_VIDPID = 7, - TK1_SYSCALL_SET_LED = 10, + TK1_SYSCALL_PRELOAD_STORE = 8, + TK1_SYSCALL_PRELOAD_STORE_FIN = 9, + TK1_SYSCALL_PRELOAD_DELETE = 10, + TK1_SYSCALL_REG_MGMT = 11, + TK1_SYSCALL_SET_LED = 30, }; #endif From 6dcb5018d10cfeb99d24a379415d987e1b34fdd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Tue, 18 Mar 2025 16:25:49 +0100 Subject: [PATCH 20/30] Store app digest and signature for each app slot --- hw/application_fpga/fw/tk1/main.c | 23 +++++++--- hw/application_fpga/fw/tk1/partition_table.h | 19 ++++++-- hw/application_fpga/fw/tk1/preload_app.c | 47 +++++++++++++++----- hw/application_fpga/fw/tk1/preload_app.h | 4 +- hw/application_fpga/fw/tk1/syscall_handler.c | 4 +- 5 files changed, 73 insertions(+), 24 deletions(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index b9bd764..8d94452 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -379,11 +379,15 @@ static void jump_to_app(void) static int load_flash_app(struct partition_table *part_table, uint8_t digest[32], uint8_t slot) { + if (slot >= N_PRELOADED_APP) { + return -1; + } + if (preload_load(part_table, slot) == -1) { return -1; } - *app_size = part_table->pre_app_data.size; + *app_size = part_table->pre_app_data[slot].size; if (*app_size > TK1_APP_MAX_SIZE) { return -1; } @@ -397,14 +401,18 @@ static int load_flash_app(struct partition_table *part_table, static enum state auth_flash_app(const struct context *ctx, struct partition_table *part_table) { - if (part_table->pre_app_data.status == PRE_LOADED_STATUS_PRESENT) { + if (ctx->flash_slot >= N_PRELOADED_APP) { + return FW_STATE_FAIL; + } + + if (part_table->pre_app_data[ctx->flash_slot].status == PRE_LOADED_STATUS_PRESENT) { debug_puts("Create auth\n"); - auth_app_create(&part_table->pre_app_data.auth); - part_table->pre_app_data.status = PRE_LOADED_STATUS_AUTH; + auth_app_create(&part_table->pre_app_data[ctx->flash_slot].auth); + part_table->pre_app_data[ctx->flash_slot].status = PRE_LOADED_STATUS_AUTH; part_table_write(part_table); } - if (!auth_app_authenticate(&part_table->pre_app_data.auth)) { + if (!auth_app_authenticate(&part_table->pre_app_data[ctx->flash_slot].auth)) { debug_puts("!Authenticated\n"); return FW_STATE_FAIL; @@ -556,7 +564,8 @@ int main(void) // TODO Lie and tell filesystem we have a 128 kiB device app // on flash. - part_table.pre_app_data.size = 0x20000; + part_table.pre_app_data[0].size = 0x20000; + part_table.pre_app_data[1].size = 0x20000; // TODO Just start something from flash without looking in // FW_RAM. @@ -599,7 +608,7 @@ int main(void) case FW_STATE_LOAD_FLASH: // TODO Just lie and say that an app is present but not yet // authenticated. - part_table.pre_app_data.status = PRE_LOADED_STATUS_PRESENT; + part_table.pre_app_data[ctx.flash_slot].status = PRE_LOADED_STATUS_PRESENT; if (load_flash_app(&part_table, ctx.digest, ctx.flash_slot) < 0) { debug_puts("Couldn't load app from flash\n"); diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h index f59baee..aff72a2 100644 --- a/hw/application_fpga/fw/tk1/partition_table.h +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -35,6 +35,7 @@ 0x10000UL // 64KiB, 60 KiB reserved, 2 flash pages (2 x 4KiB) for the // partition table +#define N_PRELOADED_APP 2 #define ADDR_PRE_LOADED_APP (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE) #define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB @@ -43,7 +44,7 @@ // Pre-loaded app present but not yet authenticated #define PRE_LOADED_STATUS_PRESENT 0x02 -#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + (2 * SIZE_PRE_LOADED_APP)) +#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + (N_PRELOADED_APP * SIZE_PRE_LOADED_APP)) #define SIZE_STORAGE_AREA 0x20000UL // 128KiB #define N_STORAGE_AREA 4 @@ -58,11 +59,21 @@ /* - 16 byte random nonce. */ /* - 16 byte authentication digest. */ /**/ -/*- Pre-loaded device app */ +/*- Pre-loaded device app 1 */ /* - 1 byte status. */ /* - 4 bytes length. */ /* - 16 bytes random nonce. */ /* - 16 bytes authentication digest. */ +/* - 32 bytes digest. */ +/* - 64 bytes signature. */ +/**/ +/*- Pre-loaded device app 2 */ +/* - 1 byte status. */ +/* - 4 bytes length. */ +/* - 16 bytes random nonce. */ +/* - 16 bytes authentication digest. */ +/* - 32 bytes digest. */ +/* - 64 bytes signature. */ /**/ /*- Device app storage area */ /* - 1 byte status. */ @@ -85,6 +96,8 @@ struct pre_loaded_app_metadata { uint8_t status; uint32_t size; struct auth_metadata auth; + uint8_t digest[32]; + uint8_t signature[64]; } __attribute__((packed)); struct app_storage_area { @@ -101,7 +114,7 @@ struct table_header { struct partition_table { struct table_header header; struct management_app_metadata mgmt_app_data; - struct pre_loaded_app_metadata pre_app_data; + struct pre_loaded_app_metadata pre_app_data[N_PRELOADED_APP]; struct app_storage_area app_storage[N_STORAGE_AREA]; } __attribute__((packed)); diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index 0560bf9..04596f2 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -17,9 +17,12 @@ bool preload_check_valid_app(struct partition_table *part_table, uint8_t slot) { + if (slot >= N_PRELOADED_APP) { + return false; + } - if (part_table->pre_app_data.status == 0x00 && - part_table->pre_app_data.size == 0) { + if (part_table->pre_app_data[slot].status == 0x00 && + part_table->pre_app_data[slot].size == 0) { /*No valid app*/ return false; } @@ -30,6 +33,10 @@ bool preload_check_valid_app(struct partition_table *part_table, /* Loads a preloaded app from flash to app RAM */ int preload_load(struct partition_table *part_table, uint8_t from_slot) { + if (from_slot >= N_PRELOADED_APP) { + return -1; + } + /*Check for a valid app in flash */ if (!preload_check_valid_app(part_table, from_slot)) { return -1; @@ -39,7 +46,7 @@ int preload_load(struct partition_table *part_table, uint8_t from_slot) /* Read from flash, straight into RAM */ int ret = flash_read_data(ADDR_PRE_LOADED_APP + from_slot * SIZE_PRE_LOADED_APP, - loadaddr, part_table->pre_app_data.size); + loadaddr, part_table->pre_app_data[from_slot].size); return ret; } @@ -75,8 +82,14 @@ int preload_store(struct partition_table *part_table, uint32_t offset, return flash_write_data(address, data, size); } -int preload_store_finalize(struct partition_table *part_table, size_t app_size, uint8_t to_slot) +int preload_store_finalize(struct partition_table *part_table, size_t app_size, + uint8_t app_digest[32], uint8_t app_signature[64], + uint8_t to_slot) { + if (to_slot >= N_PRELOADED_APP) { + return -4; + } + /* Check if we are allowed to store */ if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { return -3; @@ -91,9 +104,15 @@ int preload_store_finalize(struct partition_table *part_table, size_t app_size, return -2; } - part_table->pre_app_data.size = app_size; - part_table->pre_app_data.status = + part_table->pre_app_data[to_slot].size = app_size; + part_table->pre_app_data[to_slot].status = PRE_LOADED_STATUS_PRESENT; /* Stored but not yet authenticated */ + memcpy_s(part_table->pre_app_data[to_slot].digest, + sizeof(part_table->pre_app_data[to_slot].digest), + app_digest, 32); + memcpy_s(part_table->pre_app_data[to_slot].signature, + sizeof(part_table->pre_app_data[to_slot].signature), + app_signature, 64); debug_puts("preload_*_final: size: "); debug_putinthex(app_size); debug_lf(); @@ -108,6 +127,10 @@ int preload_store_finalize(struct partition_table *part_table, size_t app_size, int preload_delete(struct partition_table *part_table, uint8_t slot) { + if (slot >= N_PRELOADED_APP) { + return -4; + } + /* Check if we are allowed to deleted */ if (!mgmt_app_authenticate(&part_table->mgmt_app_data)) { return -3; @@ -118,14 +141,14 @@ int preload_delete(struct partition_table *part_table, uint8_t slot) return 0; // TODO: Nothing here, return zero like all is good? } - part_table->pre_app_data.size = 0; - part_table->pre_app_data.status = 0; + part_table->pre_app_data[slot].size = 0; + part_table->pre_app_data[slot].status = 0; - memset(part_table->pre_app_data.auth.nonce, 0x00, - sizeof(part_table->pre_app_data.auth.nonce)); + memset(part_table->pre_app_data[slot].auth.nonce, 0x00, + sizeof(part_table->pre_app_data[slot].auth.nonce)); - memset(part_table->pre_app_data.auth.authentication_digest, 0x00, - sizeof(part_table->pre_app_data.auth.authentication_digest)); + memset(part_table->pre_app_data[slot].auth.authentication_digest, 0x00, + sizeof(part_table->pre_app_data[slot].auth.authentication_digest)); part_table_write(part_table); diff --git a/hw/application_fpga/fw/tk1/preload_app.h b/hw/application_fpga/fw/tk1/preload_app.h index 3401c8a..27ecf11 100644 --- a/hw/application_fpga/fw/tk1/preload_app.h +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -14,7 +14,9 @@ bool preload_check_valid_app(struct partition_table *part_table, int preload_load(struct partition_table *part_table, uint8_t from_slot); int preload_store(struct partition_table *part_table, uint32_t offset, uint8_t *data, size_t size, uint8_t to_slot); -int preload_store_finalize(struct partition_table *part_table, size_t app_size, uint8_t to_slot); +int preload_store_finalize(struct partition_table *part_table, size_t app_size, + uint8_t app_digest[32], uint8_t app_signature[64], + uint8_t to_slot); int preload_delete(struct partition_table *part_table, uint8_t slot); #endif diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index 86b1392..bbc1e33 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -86,8 +86,10 @@ int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, case TK1_SYSCALL_PRELOAD_STORE_FIN: // arg1 app_size + // arg2 app_digest + // arg3 app_signature // always using slot 1 - return preload_store_finalize(&part_table, arg1, 1); + return preload_store_finalize(&part_table, arg1, (uint8_t *)arg2, (uint8_t *)arg3, 1); case TK1_SYSCALL_REG_MGMT: return mgmt_app_register(&part_table); From a86cd4a6182ab3501c42d016e20beb6cc173bca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Tue, 18 Mar 2025 16:45:54 +0100 Subject: [PATCH 21/30] Delete app digest and signature when preloaded app is deleted --- hw/application_fpga/fw/tk1/preload_app.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index 04596f2..2037a1c 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -150,6 +150,12 @@ int preload_delete(struct partition_table *part_table, uint8_t slot) memset(part_table->pre_app_data[slot].auth.authentication_digest, 0x00, sizeof(part_table->pre_app_data[slot].auth.authentication_digest)); + memset(part_table->pre_app_data[slot].digest, 0, + sizeof(part_table->pre_app_data[slot].digest)); + + memset(part_table->pre_app_data[slot].signature, 0, + sizeof(part_table->pre_app_data[slot].signature)); + part_table_write(part_table); /* Assumes the area is 64 KiB block aligned */ From feca8f19f43716b61144dec5cab97f830c00c803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Thu, 20 Mar 2025 16:17:17 +0100 Subject: [PATCH 22/30] Do note delete or corrupt preloaded app 0 when storing preloaded app 1 --- hw/application_fpga/fw/tk1/partition_table.h | 4 ++-- hw/application_fpga/fw/tk1/preload_app.c | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h index aff72a2..731b626 100644 --- a/hw/application_fpga/fw/tk1/partition_table.h +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -36,7 +36,7 @@ // partition table #define N_PRELOADED_APP 2 -#define ADDR_PRE_LOADED_APP (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE) +#define ADDR_PRE_LOADED_APP_0 (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE) #define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB // Pre-loaded app present and authenticated @@ -44,7 +44,7 @@ // Pre-loaded app present but not yet authenticated #define PRE_LOADED_STATUS_PRESENT 0x02 -#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + (N_PRELOADED_APP * SIZE_PRE_LOADED_APP)) +#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP_0 + (N_PRELOADED_APP * SIZE_PRE_LOADED_APP)) #define SIZE_STORAGE_AREA 0x20000UL // 128KiB #define N_STORAGE_AREA 4 diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index 2037a1c..ecdf2c3 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -13,6 +13,10 @@ #include "partition_table.h" #include "preload_app.h" +static uint32_t slot_to_start_address(uint8_t slot) { + return ADDR_PRE_LOADED_APP_0 + slot * SIZE_PRE_LOADED_APP; +} + /* Returns non-zero if the app is valid */ bool preload_check_valid_app(struct partition_table *part_table, uint8_t slot) @@ -44,9 +48,8 @@ int preload_load(struct partition_table *part_table, uint8_t from_slot) uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE; /* Read from flash, straight into RAM */ - int ret = flash_read_data(ADDR_PRE_LOADED_APP + - from_slot * SIZE_PRE_LOADED_APP, - loadaddr, part_table->pre_app_data[from_slot].size); + int ret = flash_read_data(slot_to_start_address(from_slot), loadaddr, + part_table->pre_app_data[from_slot].size); return ret; } @@ -73,7 +76,7 @@ int preload_store(struct partition_table *part_table, uint32_t offset, return -2; } - uint32_t address = ADDR_PRE_LOADED_APP + offset; + uint32_t address = slot_to_start_address(to_slot) + offset; debug_puts("preload_store: write to addr: "); debug_putinthex(address); @@ -159,9 +162,8 @@ int preload_delete(struct partition_table *part_table, uint8_t slot) part_table_write(part_table); /* Assumes the area is 64 KiB block aligned */ - flash_block_64_erase(ADDR_PRE_LOADED_APP); // Erase first 64 KB block - flash_block_64_erase(ADDR_PRE_LOADED_APP + - 0x10000); // Erase second 64 KB block + flash_block_64_erase(slot_to_start_address(slot)); // Erase first 64 KB block + flash_block_64_erase(slot_to_start_address(slot) + 0x10000); // Erase first 64 KB block return 0; } From 97582da97798c73565817deb3b4e407ea8f63eef Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Wed, 19 Mar 2025 17:32:38 +0100 Subject: [PATCH 23/30] Add start of test app for installing and verifying flash app Currently needs LIBDIR set to tkey-libs with blake2s(). --- hw/application_fpga/fw/testloadapp/Makefile | 75 +++++++++++ hw/application_fpga/fw/testloadapp/blink.h | 24 ++++ hw/application_fpga/fw/testloadapp/main.c | 132 ++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 hw/application_fpga/fw/testloadapp/Makefile create mode 100644 hw/application_fpga/fw/testloadapp/blink.h create mode 100644 hw/application_fpga/fw/testloadapp/main.c diff --git a/hw/application_fpga/fw/testloadapp/Makefile b/hw/application_fpga/fw/testloadapp/Makefile new file mode 100644 index 0000000..3e13dc7 --- /dev/null +++ b/hw/application_fpga/fw/testloadapp/Makefile @@ -0,0 +1,75 @@ +P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +LIBDIR ?= ../../tkey-libs +OBJCOPY ?= llvm-objcopy +CC = clang +CFLAGS = \ + -target riscv32-unknown-none-elf \ + -march=rv32iczmmul \ + -mabi=ilp32 \ + -mcmodel=medany \ + -static \ + -std=gnu99 \ + -Os \ + -ffast-math \ + -fno-common \ + -fno-builtin-printf \ + -fno-builtin-putchar \ + -fno-builtin-memcpy \ + -nostdlib \ + -mno-relax \ + -Wall \ + -Wpedantic \ + -Wno-language-extension-token \ + -Werror \ + -flto \ + -g \ + -I $(LIBDIR)/include \ + -I $(LIBDIR) + +AS = clang + +ASFLAGS = \ + -target riscv32-unknown-none-elf \ + -march=rv32iczmmul \ + -mabi=ilp32 \ + -mno-relax + +LDFLAGS = \ + -T $(LIBDIR)/app.lds \ + -L $(LIBDIR) -lcrt0 -lcommon -lmonocypher + +.PHONY: all +all: testloadapp.bin + +# Turn elf into bin for device +%.bin: %.elf + $(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@ + chmod a-x $@ + +.PHONY: tkey-libs +tkey-libs: + make -C $(LIBDIR) + +TESTLOADAPP_FMTFILES = \ + $(P)/main.c + +TESTLOADAPP_OBJS = \ + $(P)/main.o \ + ../testapp/syscall.o \ + ../tk1/blake2s/blake2s.o + +testloadapp.elf: tkey-libs $(TESTLOADAPP_OBJS) + $(CC) $(CFLAGS) $(TESTLOADAPP_OBJS) $(LDFLAGS) -o $@ + +.PHONY: fmt +fmt: + clang-format --dry-run --ferror-limit=0 $(TESTLOADAPP_FMTFILES) + clang-format --verbose -i $(TESTLOADAPP_FMTFILES) + +.PHONY: checkfmt +checkfmt: + clang-format --dry-run --ferror-limit=0 --Werror $(TESTLOADAPP_FMTFILES) + +.PHONY: clean +clean: + rm -f testloadapp.bin testloadapp.elf $(TESTLOADAPP_OBJS) diff --git a/hw/application_fpga/fw/testloadapp/blink.h b/hw/application_fpga/fw/testloadapp/blink.h new file mode 100644 index 0000000..28f3529 --- /dev/null +++ b/hw/application_fpga/fw/testloadapp/blink.h @@ -0,0 +1,24 @@ +#ifndef BLINK_APP_H +#define BLINK_APP_H + +uint8_t blink[] = { + 0x81, 0x40, 0x01, 0x41, 0x81, 0x41, 0x01, 0x42, 0x81, 0x42, 0x01, 0x43, 0x81, 0x43, 0x01, 0x44, + 0x81, 0x44, 0x01, 0x45, 0x81, 0x45, 0x01, 0x46, 0x81, 0x46, 0x01, 0x47, 0x81, 0x47, 0x01, 0x48, + 0x81, 0x48, 0x01, 0x49, 0x81, 0x49, 0x01, 0x4a, 0x81, 0x4a, 0x01, 0x4b, 0x81, 0x4b, 0x01, 0x4c, + 0x81, 0x4c, 0x01, 0x4d, 0x81, 0x4d, 0x01, 0x4e, 0x81, 0x4e, 0x01, 0x4f, 0x81, 0x4f, 0x37, 0x01, + 0x02, 0x40, 0x41, 0x11, 0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x45, 0x0c, 0x97, 0x05, 0x00, 0x00, + 0x93, 0x85, 0xc5, 0x0b, 0x63, 0x57, 0xb5, 0x00, 0x23, 0x20, 0x05, 0x00, 0x11, 0x05, 0xe3, 0x4d, + 0xb5, 0xfe, 0x97, 0x00, 0x00, 0x00, 0xe7, 0x80, 0xa0, 0x00, 0x00, 0x00, 0x41, 0x11, 0x37, 0x05, + 0x00, 0xff, 0x11, 0x48, 0xe1, 0x66, 0x13, 0x86, 0xf6, 0x69, 0x93, 0x86, 0x06, 0x6a, 0x09, 0x47, + 0x85, 0x47, 0x23, 0x22, 0x05, 0x03, 0x02, 0xc2, 0x92, 0x45, 0x63, 0x68, 0xb6, 0x00, 0x92, 0x45, + 0x85, 0x05, 0x2e, 0xc2, 0x92, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x58, 0xd1, 0x02, 0xc4, 0xa2, 0x45, + 0x63, 0x68, 0xb6, 0x00, 0xa2, 0x45, 0x85, 0x05, 0x2e, 0xc4, 0xa2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, + 0x5c, 0xd1, 0x02, 0xc6, 0xb2, 0x45, 0xe3, 0x66, 0xb6, 0xfc, 0xb2, 0x45, 0x85, 0x05, 0x2e, 0xc6, + 0xb2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x75, 0xbf, 0x19, 0xca, 0x2a, 0x96, 0xaa, 0x86, 0x03, 0xc7, + 0x05, 0x00, 0x23, 0x80, 0xe6, 0x00, 0x85, 0x06, 0x85, 0x05, 0xe3, 0x9a, 0xc6, 0xfe, 0x82, 0x80, + 0x11, 0xca, 0x0a, 0x06, 0x2a, 0x96, 0xaa, 0x86, 0x98, 0x41, 0x98, 0xc2, 0x91, 0x06, 0x91, 0x05, + 0xe3, 0x9c, 0xc6, 0xfe, 0x82, 0x80, 0x01, 0xca, 0x2a, 0x96, 0xaa, 0x86, 0x23, 0x80, 0xb6, 0x00, + 0x85, 0x06, 0xe3, 0x9d, 0xc6, 0xfe, 0x82, 0x80 +}; + +#endif diff --git a/hw/application_fpga/fw/testloadapp/main.c b/hw/application_fpga/fw/testloadapp/main.c new file mode 100644 index 0000000..555d922 --- /dev/null +++ b/hw/application_fpga/fw/testloadapp/main.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include + +#include "../testapp/syscall.h" +#include "../tk1/syscall_num.h" +#include "blink.h" +#include "tkey/assert.h" + +// clang-format off +static volatile uint32_t *cdi = (volatile uint32_t *) TK1_MMIO_TK1_CDI_FIRST; +// clang-format on + +int install_app(uint8_t secret_key[64]) +{ + uint8_t app_digest[32]; + uint8_t app_signature[64]; + size_t app_size = sizeof(blink); + + if (syscall(TK1_SYSCALL_REG_MGMT, 0, 0, 0) < 0) { + puts(IO_CDC, "couldn't register as mgmt\r\n"); + return -1; + } + + int err = syscall(TK1_SYSCALL_PRELOAD_STORE, 0, (uint32_t)blink, + sizeof(blink)); + + if (err < 0) { + puts(IO_CDC, "couldn't store app, error: "); + putinthex(IO_CDC, err); + puts(IO_CDC, "\r\n"); + + return -1; + } + + if (blake2s(app_digest, 32, NULL, 0, blink, sizeof(blink)) != 0) { + puts(IO_CDC, "couldn't compute digest\r\n"); + return -1; + } + + crypto_ed25519_sign(app_signature, secret_key, app_digest, + sizeof(app_digest)); + + if (syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size, + (uint32_t)app_digest, (uint32_t)app_signature) < 0) { + puts(IO_CDC, "couldn't finalize storing app\n"); + return -1; + } + + return 0; +} + +int verify(uint8_t pubkey[32]) +{ + uint8_t app_digest[32]; + uint8_t app_signature[64]; + + // pubkey we already have + // read signature + // read digest + + if (!crypto_ed25519_check(app_signature, pubkey, app_digest, + sizeof(app_digest))) { + // failed!!! + } + + // syscall reset flash2_ver with app_digest + + return 0; +} + +int main(void) +{ + uint8_t secret_key[64]; + uint8_t pubkey[32]; + enum ioend endpoint; + uint8_t available; + uint8_t in = 0; + + // Generate a key pair from CDI + crypto_ed25519_key_pair(secret_key, pubkey, (uint8_t *)cdi); + + if (readselect(IO_CDC, &endpoint, &available) < 0) { + // readselect failed! I/O broken? Just redblink. + assert(1 == 2); + } + + if (read(IO_CDC, &in, 1, 1) < 0) { + // read failed! I/O broken? Just redblink. + assert(1 == 2); + } + + puts(IO_CDC, "Hello from testloadapp! 0 = install app in slot 1, 1 = " + "verify app\r\n"); + + for (;;) { + if (readselect(IO_CDC, &endpoint, &available) < 0) { + // readselect failed! I/O broken? Just redblink. + assert(1 == 2); + } + + if (read(IO_CDC, &in, 1, 1) < 0) { + // read failed! I/O broken? Just redblink. + assert(1 == 2); + } + + switch (in) { + case '0': + if (install_app(secret_key) < 0) { + puts(IO_CDC, "Failed to install app\r\n"); + } else { + puts(IO_CDC, "Installed app!\r\n"); + } + + break; + + case '1': + if (verify(pubkey) < 0) { + puts(IO_CDC, "Failed to verify app\r\n"); + } else { + puts(IO_CDC, "Verified app!\r\n"); + } + + break; + + default: + break; + } + } +} From 15bf72bc374b1aaaf3fdd37d878b55b60e229fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Thu, 20 Mar 2025 16:04:09 +0100 Subject: [PATCH 24/30] Add QEMU_SYSCALL to enable Qemu syscalls Enable Qemu syscall handling by defining QEMU_SYSCALL instead of QEMU_DEBUG. That way we can select either or. --- hw/application_fpga/Makefile | 2 ++ hw/application_fpga/fw/tk1/start.S | 2 +- hw/application_fpga/fw/tk1/syscall_enable.S | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 447ff38..7e6a6b6 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -205,6 +205,8 @@ simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG qemu_firmware.elf: ASFLAGS += -DQEMU_DEBUG +qemu_firmware.elf: CFLAGS += -DQEMU_SYSCALL +qemu_firmware.elf: ASFLAGS += -DQEMU_SYSCALL qemu_firmware.elf: firmware.elf mv firmware.elf qemu_firmware.elf diff --git a/hw/application_fpga/fw/tk1/start.S b/hw/application_fpga/fw/tk1/start.S index cdfb39f..f947e1e 100644 --- a/hw/application_fpga/fw/tk1/start.S +++ b/hw/application_fpga/fw/tk1/start.S @@ -5,7 +5,7 @@ #include -#ifdef QEMU_DEBUG +#ifdef QEMU_SYSCALL #define picorv32_retirq_insn(...) \ mv ra, x3; \ diff --git a/hw/application_fpga/fw/tk1/syscall_enable.S b/hw/application_fpga/fw/tk1/syscall_enable.S index 58cc62a..2060ddb 100644 --- a/hw/application_fpga/fw/tk1/syscall_enable.S +++ b/hw/application_fpga/fw/tk1/syscall_enable.S @@ -1,4 +1,4 @@ -#ifdef QEMU_DEBUG +#ifdef QEMU_SYSCALL #define picorv32_maskirq_insn(...) From a8f2a8c30e468c527899ee8d39aa3d5d97f120d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Thu, 20 Mar 2025 16:13:13 +0100 Subject: [PATCH 25/30] Do not hardcode preloaded app 1 size Hardcoding it causes preload_check_valid_app and therefore preload_store to assume there already is an app installed. Causing the TK1_SYSCALL_PRELOAD_STORE syscall to fail. --- hw/application_fpga/fw/tk1/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 8d94452..2db406c 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -565,7 +565,7 @@ int main(void) // TODO Lie and tell filesystem we have a 128 kiB device app // on flash. part_table.pre_app_data[0].size = 0x20000; - part_table.pre_app_data[1].size = 0x20000; + // part_table.pre_app_data[1].size = 0x20000; // TODO Just start something from flash without looking in // FW_RAM. From 478212f72fa9b016f37cb598aa5c595419fa6d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Thu, 20 Mar 2025 16:24:32 +0100 Subject: [PATCH 26/30] Add tool to create a flash image containing a preloaded app at slot 0 --- .../tools/create_flash_image.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 hw/application_fpga/tools/create_flash_image.py diff --git a/hw/application_fpga/tools/create_flash_image.py b/hw/application_fpga/tools/create_flash_image.py new file mode 100755 index 0000000..d82bfff --- /dev/null +++ b/hw/application_fpga/tools/create_flash_image.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import argparse + +FLASH_SIZE_BYTES = 1 * 1024 * 1024 +PRELOADED_APP_0_START = 0x30000 + + +def run(output_path, preloaded_app_0_path): + with ( + open(output_path, "wb") as output_file, + open(preloaded_app_0_path, "rb") as preloaded_app_0_file, + ): + content = bytearray(b"\xFF") * FLASH_SIZE_BYTES + preloaded_app = preloaded_app_0_file.read() + content[ + PRELOADED_APP_0_START : PRELOADED_APP_0_START + len(preloaded_app) + ] = preloaded_app + output_file.write(content) + + +if __name__ == "__main__": + arg_parser = argparse.ArgumentParser() + arg_parser.add_argument("OUTPUT_PATH") + arg_parser.add_argument("PRELOADED_APP_0_PATH") + args = arg_parser.parse_args() + + run(args.OUTPUT_PATH, args.PRELOADED_APP_0_PATH) From 543c5a8968af8bf495525ca33b2c05b1587b8baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Fri, 21 Mar 2025 15:23:19 +0100 Subject: [PATCH 27/30] testloadapp: Use blake2s from tkey-libs --- hw/application_fpga/fw/testloadapp/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw/application_fpga/fw/testloadapp/Makefile b/hw/application_fpga/fw/testloadapp/Makefile index 3e13dc7..ac852cf 100644 --- a/hw/application_fpga/fw/testloadapp/Makefile +++ b/hw/application_fpga/fw/testloadapp/Makefile @@ -36,7 +36,7 @@ ASFLAGS = \ LDFLAGS = \ -T $(LIBDIR)/app.lds \ - -L $(LIBDIR) -lcrt0 -lcommon -lmonocypher + -L $(LIBDIR) -lcrt0 -lcommon -lmonocypher -lblake2s .PHONY: all all: testloadapp.bin @@ -56,7 +56,6 @@ TESTLOADAPP_FMTFILES = \ TESTLOADAPP_OBJS = \ $(P)/main.o \ ../testapp/syscall.o \ - ../tk1/blake2s/blake2s.o testloadapp.elf: tkey-libs $(TESTLOADAPP_OBJS) $(CC) $(CFLAGS) $(TESTLOADAPP_OBJS) $(LDFLAGS) -o $@ From 2caaf2a4534aec8432a648142860512983b55f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Fri, 21 Mar 2025 15:24:56 +0100 Subject: [PATCH 28/30] WIP: verify pre loaded app 2 --- hw/application_fpga/fw/testloadapp/main.c | 62 ++++++++++++++++++-- hw/application_fpga/fw/tk1/preload_app.c | 11 ++++ hw/application_fpga/fw/tk1/preload_app.h | 3 + hw/application_fpga/fw/tk1/syscall_handler.c | 3 + hw/application_fpga/fw/tk1/syscall_num.h | 3 +- 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/hw/application_fpga/fw/testloadapp/main.c b/hw/application_fpga/fw/testloadapp/main.c index 555d922..ffc73ff 100644 --- a/hw/application_fpga/fw/testloadapp/main.c +++ b/hw/application_fpga/fw/testloadapp/main.c @@ -1,10 +1,12 @@ #include #include #include +#include #include #include #include "../testapp/syscall.h" +#include "../tk1/resetinfo.h" #include "../tk1/syscall_num.h" #include "blink.h" #include "tkey/assert.h" @@ -35,6 +37,18 @@ int install_app(uint8_t secret_key[64]) return -1; } + puts(IO_CDC, "blink: "); + putinthex(IO_CDC, (uint32_t)blink); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "blink[0]: "); + putinthex(IO_CDC, blink[0]); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "sizeof(blink): "); + putinthex(IO_CDC, sizeof(blink)); + puts(IO_CDC, "\r\n"); + if (blake2s(app_digest, 32, NULL, 0, blink, sizeof(blink)) != 0) { puts(IO_CDC, "couldn't compute digest\r\n"); return -1; @@ -43,9 +57,21 @@ int install_app(uint8_t secret_key[64]) crypto_ed25519_sign(app_signature, secret_key, app_digest, sizeof(app_digest)); + puts(IO_CDC, "app_digest:\r\n"); + hexdump(IO_CDC, app_digest, sizeof(app_digest)); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "app_signature:\r\n"); + hexdump(IO_CDC, app_signature, sizeof(app_signature)); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "secret_key:\r\n"); + hexdump(IO_CDC, secret_key, 64); + puts(IO_CDC, "\r\n"); + if (syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size, (uint32_t)app_digest, (uint32_t)app_signature) < 0) { - puts(IO_CDC, "couldn't finalize storing app\n"); + puts(IO_CDC, "couldn't finalize storing app\r\n"); return -1; } @@ -60,15 +86,39 @@ int verify(uint8_t pubkey[32]) // pubkey we already have // read signature // read digest + syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest, + (uint32_t)app_signature, 0); - if (!crypto_ed25519_check(app_signature, pubkey, app_digest, - sizeof(app_digest))) { - // failed!!! + puts(IO_CDC, "app_digest:\r\n"); + hexdump(IO_CDC, app_digest, sizeof(app_digest)); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "app_signature:\r\n"); + hexdump(IO_CDC, app_signature, sizeof(app_signature)); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "pubkey:\r\n"); + hexdump(IO_CDC, pubkey, 32); + puts(IO_CDC, "\r\n"); + + puts(IO_CDC, "Checking signature...\r\n"); + + if (crypto_ed25519_check(app_signature, pubkey, app_digest, + sizeof(app_digest)) != 0) { + return -1; } - // syscall reset flash2_ver with app_digest + puts(IO_CDC, "Resetting into pre loaded app (slot 2)...\r\n"); - return 0; + // syscall reset flash2_ver with app_digest + struct reset rst; + rst.type = START_FLASH2_VER; + memcpy_s(rst.app_digest, sizeof(rst.app_digest), app_digest, + sizeof(app_digest)); + memset(rst.next_app_data, 0, sizeof(rst.next_app_data)); + syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0); + + return -2; } int main(void) diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c index ecdf2c3..0d00e2e 100644 --- a/hw/application_fpga/fw/tk1/preload_app.c +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -167,3 +167,14 @@ int preload_delete(struct partition_table *part_table, uint8_t slot) return 0; } + +int preload_get_digsig(struct partition_table *part_table, uint8_t app_digest[32], uint8_t app_signature[64], uint8_t slot) { + if (slot >= N_PRELOADED_APP) { + return -1; + } + + memcpy_s(app_digest, 32, part_table->pre_app_data[slot].digest, sizeof(part_table->pre_app_data[slot].digest)); + memcpy_s(app_signature, 64, part_table->pre_app_data[slot].signature, sizeof(part_table->pre_app_data[slot].signature)); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/preload_app.h b/hw/application_fpga/fw/tk1/preload_app.h index 27ecf11..bdb3c2d 100644 --- a/hw/application_fpga/fw/tk1/preload_app.h +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -18,5 +18,8 @@ int preload_store_finalize(struct partition_table *part_table, size_t app_size, uint8_t app_digest[32], uint8_t app_signature[64], uint8_t to_slot); int preload_delete(struct partition_table *part_table, uint8_t slot); +int preload_get_digsig(struct partition_table *part_table, + uint8_t app_digest[32], uint8_t app_signature[64], + uint8_t slot); #endif diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index bbc1e33..0100ae2 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -91,6 +91,9 @@ int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, // always using slot 1 return preload_store_finalize(&part_table, arg1, (uint8_t *)arg2, (uint8_t *)arg3, 1); + case TK1_SYSCALL_PRELOAD_GET_DIGSIG: + return preload_get_digsig(&part_table, (uint8_t *)arg1, (uint8_t *)arg2, 1); + case TK1_SYSCALL_REG_MGMT: return mgmt_app_register(&part_table); diff --git a/hw/application_fpga/fw/tk1/syscall_num.h b/hw/application_fpga/fw/tk1/syscall_num.h index 41fdc0c..a201046 100644 --- a/hw/application_fpga/fw/tk1/syscall_num.h +++ b/hw/application_fpga/fw/tk1/syscall_num.h @@ -15,7 +15,8 @@ enum syscall_num { TK1_SYSCALL_PRELOAD_STORE = 8, TK1_SYSCALL_PRELOAD_STORE_FIN = 9, TK1_SYSCALL_PRELOAD_DELETE = 10, - TK1_SYSCALL_REG_MGMT = 11, + TK1_SYSCALL_PRELOAD_GET_DIGSIG = 11, + TK1_SYSCALL_REG_MGMT = 12, TK1_SYSCALL_SET_LED = 30, }; From 5cba9b3f7e5a6bdb7ee184ad6285b17499dfabdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20=C3=85gren?= Date: Mon, 24 Mar 2025 13:54:19 +0100 Subject: [PATCH 29/30] testloadapp: Delete any existing preloaded app before installing --- hw/application_fpga/fw/testloadapp/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hw/application_fpga/fw/testloadapp/main.c b/hw/application_fpga/fw/testloadapp/main.c index ffc73ff..93b225e 100644 --- a/hw/application_fpga/fw/testloadapp/main.c +++ b/hw/application_fpga/fw/testloadapp/main.c @@ -26,6 +26,11 @@ int install_app(uint8_t secret_key[64]) return -1; } + if (syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0) < 0) { + puts(IO_CDC, "couldn't delete preloaded app\r\n"); + return -1; + } + int err = syscall(TK1_SYSCALL_PRELOAD_STORE, 0, (uint32_t)blink, sizeof(blink)); From b6e05828e94e6ba0099a75135b67f9a74e947cde Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Mon, 24 Mar 2025 15:53:37 +0100 Subject: [PATCH 30/30] fw: Only allow a specific app to start from first flash Store size and app digest in ROM and compare to what we are booting. --- hw/application_fpga/fw/tk1/main.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 2db406c..b3a87c5 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -43,6 +43,19 @@ static volatile struct reset *resetinfo = (volatile struct reset *)TK1_ struct partition_table part_table; +// Locked down what app can start from first flash slot to be exactly +// this size, producing this digest. +// +// To update this, compute the BLAKE2s digest of the app.bin and +// insert the size in bytes. +#define APP_SIZE_SLOT0 21684 +// BLAKE2s digest of testloadapp.bin +const uint8_t allowed_app_digest[32] = { + 0x3a, 0x34, 0x6f, 0x1f, 0xb7, 0x7f, 0xa6, 0x71, 0x9b, 0x69, 0x8, + 0x36, 0xa0, 0x5, 0xe, 0x26, 0x48, 0x8d, 0xab, 0x6a, 0x51, 0xa6, + 0xe1, 0x18, 0x53, 0xa3, 0x64, 0xc6, 0x5b, 0x42, 0x49, 0xb7, +}; + // Context for the loading of a TKey program struct context { uint32_t left; // Bytes left to receive @@ -557,14 +570,12 @@ int main(void) assert(1 != 2); } - #if defined(SIMULATION) run(&ctx); #endif - // TODO Lie and tell filesystem we have a 128 kiB device app - // on flash. - part_table.pre_app_data[0].size = 0x20000; + // Hardocde size of slot 0 + part_table.pre_app_data[0].size = APP_SIZE_SLOT0; // part_table.pre_app_data[1].size = 0x20000; // TODO Just start something from flash without looking in @@ -616,6 +627,14 @@ int main(void) break; } + if (ctx.flash_slot == 0) { + print_digest(allowed_app_digest); + if (!memeq(ctx.digest, allowed_app_digest, 32)) { + puts(IO_CDC, "app not allowed!\r\n"); + assert(1 == 2); + } + } + // CDI = hash(uds, hash(app), uss) compute_cdi(ctx.digest, ctx.use_uss, ctx.uss);