diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 822929a..7e6a6b6 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -126,7 +126,15 @@ 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 \ + $(P)/fw/tk1/preload_app.o \ + $(P)/fw/tk1/mgmt_app.o FIRMWARE_SOURCES = \ $(P)/fw/tk1/main.c \ @@ -187,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 @@ -196,6 +204,9 @@ 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: 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/README.md b/hw/application_fpga/fw/README.md index 44effb4..2a09b0e 100644 --- a/hw/application_fpga/fw/README.md +++ b/hw/application_fpga/fw/README.md @@ -102,81 +102,154 @@ 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 four 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 - S1: initial + S0: initial + S1: waitcommand S2: loading - S3: running + S3: flash_loading + S4: auth_app + S5: starting + S6: compute_cdi SE: failed - [*] --> S1 + [*] --> S0 + + S0 --> S1 + S0 --> S4: Default S1 --> S1: Commands S1 --> S2: LOAD_APP S1 --> SE: Error S2 --> S2: LOAD_APP_DATA - S2 --> S3: Last block received + S2 --> S6: Last block received S2 --> SE: Error - S3 --> [*] + S6 --> S3 + + S3 --> S5 + + S4 --> S5 + S4 --> SE: Error + + SE --> [*] + S5 --> [*] ``` States: -- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`, - `LOAD_APP`. -- `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. +- `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`. +- `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. -Commands in state `initial`: +Allowed data in state `resetinfo`: + +| *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 `flash_loading`: + +| *I/O* | *next state* | +|--------------------|--------------| +| Last app data read | `starting` | + +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`: -| *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. -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". +Plain text explanation of the states: -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. +- `initial`: Execution starts here. The firmware checks in the + `FW_RAM` for `startfrom` for what to do next. -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. + 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. + +- `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: + + digest = blake2s(cdi, nonce from flash) + + and then compared against the stored digest in the app's flash slot. + +- `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 -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, @@ -186,54 +259,101 @@ 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. All zeroes(?) meaning default + behaviour. - 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 it was reset with intention to start a device app from client, + see App loaded from client below. - 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. Default is to start the first device app from flash. If resetinfo + says otherwise it starts the other one. - 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. Load flash app into RAM without USS. - 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. Compute digest of loaded app. - 5. 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. Compare against stored app digest in partition table to note if app + has been corrupted on flash. If corrupted, halt CPU. - 6. The start address of the device app, currently `0x4000_0000`, is - written to `APP_ADDR` and the size of the binary to `APP_SIZE` to - let the device application know where it is loaded and how large - it is, if it wants to relocate in RAM. +7. Proceed to [Start the device app](#start-the-device-app) below. - 7. The firmware now clears the part of the special `FW_RAM` where it - keeps it stack. +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: - 8. The interrupt handler for system calls is enabled. +- includes a security policy, for instance a public key and code to + check a signature. - 9. Firmware starts the application by jumping to the contents of - `APP_ADDR`. Hardware automatically switches from firmware mode to - application mode. In this mode some memory access is restricted, - e.g. some addresses are inaccessible (`UDS`), and some are - switched from read/write to read-only (see [the memory - map](https://dev.tillitis.se/memory/)). +- 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 + 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. + +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. + +#### Start the device app + +1. If there is an app digest in the resetinfo left from previous app, + compare the digests. Halt CPU if differences. + +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. + +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. + +4. The firmware now clears the part of the special `FW_RAM` where it + keeps it stack. + +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 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..784d610 --- /dev/null +++ b/hw/application_fpga/fw/reset_test/main.c @@ -0,0 +1,134 @@ +/* + * 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("reset_test: 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); + } + + led_set(LED_BLUE | LED_RED); + + 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[] = "0123456789abcdef0123456789abcdef012" + "3456789abcdef0123456789abcdef"; + 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 tkeylibs_example_app_digest[] = + "96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30" + "c9d4bf66a98d97"; + rst.type = START_CLIENT_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; + + 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/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 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/testloadapp/Makefile b/hw/application_fpga/fw/testloadapp/Makefile new file mode 100644 index 0000000..ac852cf --- /dev/null +++ b/hw/application_fpga/fw/testloadapp/Makefile @@ -0,0 +1,74 @@ +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 -lblake2s + +.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 \ + +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..93b225e --- /dev/null +++ b/hw/application_fpga/fw/testloadapp/main.c @@ -0,0 +1,187 @@ +#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" + +// 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; + } + + 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)); + + if (err < 0) { + puts(IO_CDC, "couldn't store app, error: "); + putinthex(IO_CDC, err); + puts(IO_CDC, "\r\n"); + + 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; + } + + 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\r\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 + syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest, + (uint32_t)app_signature, 0); + + 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; + } + + puts(IO_CDC, "Resetting into pre loaded app (slot 2)...\r\n"); + + // 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) +{ + 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; + } + } +} 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..b3a87c5 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -10,11 +10,16 @@ #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" +#include "resetinfo.h" // clang-format off static volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST; @@ -33,8 +38,24 @@ 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 struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE; // clang-format on +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 @@ -42,6 +63,8 @@ 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 + uint8_t flash_slot; // App is loaded from flash slot number + volatile uint8_t *ver_digest; // Verify loaded app against this digest }; static void print_hw_version(void); @@ -56,11 +79,14 @@ 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], uint8_t slot); +static enum state start_where(struct context *ctx); static void print_hw_version(void) { @@ -300,7 +326,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; } @@ -320,13 +346,11 @@ static enum state loading_commands(const struct frame_header *hdr, return state; } -static void run(const struct context *ctx) +static void jump_to_app(void) { + /* Start of app is always at the beginning of RAM */ *app_addr = TK1_RAM_BASE; - // 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); @@ -365,6 +389,51 @@ static void run(const struct context *ctx) __builtin_unreachable(); } +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[slot].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 enum state auth_flash_app(const struct context *ctx, struct partition_table *part_table) +{ + 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[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[ctx->flash_slot].auth)) { + debug_puts("!Authenticated\n"); + + return FW_STATE_FAIL; + } + + return FW_STATE_START; +} + #if !defined(SIMULATION) static uint32_t xorwow(uint32_t state, uint32_t acc) { @@ -399,6 +468,62 @@ 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); +} + +static enum state start_where(struct context *ctx) +{ + // Where do we start? Read resetinfo 'startfrom' + switch (resetinfo->type) { + case START_DEFAULT: + // fallthrough + case START_FLASH1: + ctx->flash_slot = 0; + ctx->ver_digest = NULL; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH2: + ctx->flash_slot = 1; + ctx->ver_digest = NULL; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH1_VER: + ctx->flash_slot = 0; + ctx->ver_digest = resetinfo->app_digest; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH2_VER: + ctx->flash_slot = 1; + 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->ver_digest = resetinfo->app_digest; + + return FW_STATE_WAITCOMMAND; + + default: + debug_puts("Unknown startfrom\n"); + + return FW_STATE_FAIL; + } +} + int main(void) { struct context ctx = {0}; @@ -406,6 +531,8 @@ int main(void) uint8_t cmd[CMDSIZE] = {0}; enum state state = FW_STATE_INITIAL; + led_set(LED_BLUE); + print_hw_version(); /*@-mustfreeonly@*/ @@ -418,13 +545,50 @@ 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; + 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); + } + */ + + // TODO end of remove block + + if (part_table_read(&part_table) != 0) { + // Couldn't read or create partition table + assert(1 != 2); + } + #if defined(SIMULATION) run(&ctx); #endif + // 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 + // FW_RAM. + //state = FW_STATE_LOAD_FLASH; + for (;;) { switch (state) { case FW_STATE_INITIAL: + state = start_where(&ctx); + break; + + case FW_STATE_WAITCOMMAND: if (readcommand(&hdr, cmd, state) == -1) { state = FW_STATE_FAIL; break; @@ -445,9 +609,52 @@ 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); + + 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[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"); + state = FW_STATE_FAIL; + 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); + + state = auth_flash_app(&ctx, &part_table); + 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 case FW_STATE_FAIL: // fallthrough @@ -462,5 +669,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/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..731b626 --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -0,0 +1,124 @@ +// 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 1 128KiB 0x30000 */ +/* Pre load 2 128KiB 0x50000 */ +/* ---- ---- ---- */ +/* 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 + * 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 N_PRELOADED_APP 2 +#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 +#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_0 + (N_PRELOADED_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 */ +/* - 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. */ +/* - 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; + uint8_t digest[32]; + uint8_t signature[64]; +} __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[N_PRELOADED_APP]; + 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/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c new file mode 100644 index 0000000..0d00e2e --- /dev/null +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -0,0 +1,180 @@ +// 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" + +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) +{ + if (slot >= N_PRELOADED_APP) { + return false; + } + + if (part_table->pre_app_data[slot].status == 0x00 && + part_table->pre_app_data[slot].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, 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; + } + uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE; + + /* Read from flash, straight into RAM */ + int ret = flash_read_data(slot_to_start_address(from_slot), loadaddr, + part_table->pre_app_data[from_slot].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, uint8_t to_slot) +{ + /* 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, to_slot)) { + return -1; + } + + if ((offset + size) > SIZE_PRE_LOADED_APP || size > 4096) { + /* Writing outside of area */ + return -2; + } + + uint32_t address = slot_to_start_address(to_slot) + 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, 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; + } + + /* Check for a valid app in flash, bale out if it already exists */ + if (preload_check_valid_app(part_table, to_slot)) { + return -1; + } + + if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) { + return -2; + } + + 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(); + + 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, 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; + } + + /*Check for a valid app in flash */ + if (!preload_check_valid_app(part_table, slot)) { + return 0; + // TODO: Nothing here, return zero like all is good? + } + part_table->pre_app_data[slot].size = 0; + part_table->pre_app_data[slot].status = 0; + + 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[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 */ + 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; +} + +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 new file mode 100644 index 0000000..bdb3c2d --- /dev/null +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -0,0 +1,25 @@ +// 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, + 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 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); +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/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 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/start.S b/hw/application_fpga/fw/tk1/start.S index f9b72fc..f947e1e 100644 --- a/hw/application_fpga/fw/tk1/start.S +++ b/hw/application_fpga/fw/tk1/start.S @@ -4,8 +4,19 @@ */ #include + +#ifdef QEMU_SYSCALL + +#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/state.h b/hw/application_fpga/fw/tk1/state.h index 694448b..3276140 100644 --- a/hw/application_fpga/fw/tk1/state.h +++ b/hw/application_fpga/fw/tk1/state.h @@ -8,8 +8,11 @@ enum state { FW_STATE_INITIAL, + FW_STATE_WAITCOMMAND, FW_STATE_LOADING, - FW_STATE_RUN, + FW_STATE_CDI, + FW_STATE_LOAD_FLASH, + FW_STATE_START, FW_STATE_FAIL, FW_STATE_MAX, }; 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_enable.S b/hw/application_fpga/fw/tk1/syscall_enable.S index 2bcd357..2060ddb 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_SYSCALL + +#define picorv32_maskirq_insn(...) + +#else + #include "../tk1/picorv32/custom_ops.S" +#endif + .section ".text" .globl syscall_enable diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index 0d26fa8..0100ae2 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -5,20 +5,65 @@ #include #include +#include #include +#include +#include "mgmt_app.h" +#include "partition_table.h" +#include "preload_app.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 -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: + // 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) { + 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); @@ -28,6 +73,30 @@ int32_t syscall_handler(uint32_t number, uint32_t arg1) // 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 + // arg2 app_digest + // arg3 app_signature + // 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); + 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 82f3f06..a201046 100644 --- a/hw/application_fpga/fw/tk1/syscall_num.h +++ b/hw/application_fpga/fw/tk1/syscall_num.h @@ -6,8 +6,18 @@ enum syscall_num { TK1_SYSCALL_RESET = 1, - TK1_SYSCALL_SET_LED = 10, - TK1_SYSCALL_GET_VIDPID = 12, + 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_PRELOAD_STORE = 8, + TK1_SYSCALL_PRELOAD_STORE_FIN = 9, + TK1_SYSCALL_PRELOAD_DELETE = 10, + TK1_SYSCALL_PRELOAD_GET_DIGSIG = 11, + TK1_SYSCALL_REG_MGMT = 12, + TK1_SYSCALL_SET_LED = 30, }; #endif 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) 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..8b01a59 --- /dev/null +++ b/hw/application_fpga/tools/load_preloaded_app.sh @@ -0,0 +1,33 @@ +#!/bin/bash -e + +if [ $# != 2 ] +then + echo "Usage: $0 slot_num app_file" + echo "" + echo "Where slot_num is 0 or 1." + exit +fi + +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 + +# Erase partition table +tillitis-iceprog -o 0x20000 -e 64k + +# Erase existing pre loaded app +tillitis-iceprog -o "$START_ADDRESS" -e 128k + +# Write pre loaded app +tillitis-iceprog -o "$START_ADDRESS" "$APP"