diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index cfedf02..1bb8367 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -126,7 +126,15 @@ FIRMWARE_OBJS = \ $(P)/fw/tk1/start.o \ $(P)/fw/tk1/proto.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 \ diff --git a/hw/application_fpga/fw/testapp/main.c b/hw/application_fpga/fw/testapp/main.c index 58298eb..a5e4fba 100644 --- a/hw/application_fpga/fw/testapp/main.c +++ b/hw/application_fpga/fw/testapp/main.c @@ -121,7 +121,7 @@ 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"); @@ -223,7 +223,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..1fdc8ab --- /dev/null +++ b/hw/application_fpga/fw/tk1/auth_app.c @@ -0,0 +1,76 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#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) +{ + assert(nonce != NULL); + assert(auth_digest != NULL); + + blake2s_ctx ctx = {0}; + + // Generate a 16 byte authentication digest + int blake2err = blake2s_init(&ctx, 16, NULL, 0); + assert(blake2err == 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) +{ + assert(nonce != NULL); + + 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) +{ + assert(auth_table != NULL); + + uint8_t nonce[16] = {0}; + uint8_t auth_digest[16] = {0}; + + 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) +{ + assert(auth_table != NULL); + + uint8_t auth_digest[16] = {0}; + + 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..d6d0c87 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.c @@ -0,0 +1,244 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#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 bool flash_is_busy(void); +static void flash_wait_busy(void); +static void flash_write_enable(void); + +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); +} + +static bool flash_is_busy(void) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + uint8_t rx_buf = {0x00}; + + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf, + sizeof(rx_buf)) == 0); + + if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) { + return true; + } + + return false; +} + +// Blocking until !busy +static void flash_wait_busy(void) +{ + while (flash_is_busy()) { + delay(10); + } +} + +static void flash_write_enable(void) +{ + uint8_t tx_buf = WRITE_ENABLE; + + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0); +} + +void flash_write_disable(void) +{ + uint8_t tx_buf = WRITE_DISABLE; + + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 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(); + assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 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(); + assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 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(); + assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0); + flash_wait_busy(); +} + +void flash_release_powerdown(void) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = RELEASE_POWER_DOWN; + + assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0); +} + +void flash_powerdown(void) +{ + uint8_t tx_buf = POWER_DOWN; + + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0); +} + +void flash_read_manufacturer_device_id(uint8_t *device_id) +{ + assert(device_id != NULL); + + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_MANUFACTURER_ID; + + assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2) == + 0); +} + +void flash_read_jedec_id(uint8_t *jedec_id) +{ + assert(jedec_id != NULL); + + uint8_t tx_buf = READ_JEDEC_ID; + + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3) == + 0); +} + +void flash_read_unique_id(uint8_t *unique_id) +{ + assert(unique_id != NULL); + + uint8_t tx_buf[5] = {0x00}; + tx_buf[0] = READ_UNIQUE_ID; + + assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8) == + 0); +} + +void flash_read_status(uint8_t *status_reg) +{ + assert(status_reg != NULL); + + uint8_t tx_buf = READ_STATUS_REG_1; + + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1) == + 0); + + tx_buf = READ_STATUS_REG_2; + assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1, + 1) == 0); +} + +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size) +{ + if (dest_buf == NULL) { + return -1; + } + + 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 (data == NULL) { + return -1; + } + + 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..1151d4e --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.h @@ -0,0 +1,55 @@ +// 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 + +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 164bc91..2ac817e 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -3,16 +3,21 @@ * SPDX-License-Identifier: GPL-2.0-only */ +#include #include #include #include #include #include +#include #include #include -#include "blake2s/blake2s.h" +#include "mgmt_app.h" +#include "partition_table.h" +#include "preload_app.h" #include "proto.h" +#include "resetinfo.h" #include "state.h" #include "syscall_enable.h" @@ -33,8 +38,11 @@ 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_storage part_table_storage; + // Context for the loading of a TKey program struct context { uint32_t left; // Bytes left to receive @@ -42,6 +50,9 @@ 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 + /*@null@*/ volatile uint8_t + *ver_digest; // Verify loaded app against this digest }; static void print_hw_version(void); @@ -56,11 +67,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) { @@ -80,6 +94,7 @@ static void print_digest(uint8_t *md) for (int j = 0; j < 4; j++) { for (int i = 0; i < 8; i++) { debug_puthex(md[i + 8 * j]); + (void)md; } debug_lf(); } @@ -144,6 +159,7 @@ static void compute_cdi(const uint8_t *digest, const uint8_t use_uss, static void copy_name(uint8_t *buf, const size_t bufsiz, const uint32_t word) { assert(bufsiz >= 4); + assert(buf != NULL); buf[0] = word >> 24; buf[1] = word >> 16; @@ -287,9 +303,7 @@ static enum state loading_commands(const struct frame_header *hdr, // Compute Blake2S digest of the app, // storing it for FW_STATE_RUN - blake2err = - blake2s(&ctx->digest, 32, NULL, 0, - (const void *)TK1_RAM_BASE, *app_size); + blake2err = compute_app_digest(ctx->digest); assert(blake2err == 0); print_digest(ctx->digest); @@ -299,7 +313,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_START; break; } @@ -319,13 +333,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); @@ -364,6 +376,29 @@ 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; +} + #if !defined(SIMULATION) static uint32_t xorwow(uint32_t state, uint32_t acc) { @@ -398,6 +433,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) +{ + return blake2s(digest, 32, NULL, 0, (const void *)TK1_RAM_BASE, + *app_size); +} + +static enum state start_where(struct context *ctx) +{ + assert(ctx != NULL); + + // Where do we start? Read resetinfo 'startfrom' + switch (resetinfo->type) { + case START_DEFAULT: + // fallthrough + case START_FLASH0: + ctx->flash_slot = 0; + ctx->ver_digest = mgmt_app_allowed_digest(); + + return FW_STATE_LOAD_FLASH_MGMT; + + case START_FLASH1: + ctx->flash_slot = 1; + ctx->ver_digest = NULL; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH0_VER: + ctx->flash_slot = 0; + ctx->ver_digest = resetinfo->app_digest; + + return FW_STATE_LOAD_FLASH; + + case START_FLASH1_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}; @@ -405,6 +496,8 @@ int main(void) uint8_t cmd[CMDSIZE] = {0}; enum state state = FW_STATE_INITIAL; + led_set(LED_BLUE); + print_hw_version(); /*@-mustfreeonly@*/ @@ -417,6 +510,11 @@ int main(void) scramble_ram(); + if (part_table_read(&part_table_storage) != 0) { + // Couldn't read or create partition table + assert(1 == 2); + } + #if defined(SIMULATION) run(&ctx); #endif @@ -424,6 +522,10 @@ int main(void) 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; @@ -444,9 +546,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_LOAD_FLASH: + if (load_flash_app(&part_table_storage.table, + ctx.digest, ctx.flash_slot) < 0) { + debug_puts("Couldn't load app from flash\n"); + state = FW_STATE_FAIL; + break; + } + + state = FW_STATE_START; + break; + + case FW_STATE_LOAD_FLASH_MGMT: + if (load_flash_app(&part_table_storage.table, + ctx.digest, ctx.flash_slot) < 0) { + debug_puts("Couldn't load app from flash\n"); + state = FW_STATE_FAIL; + break; + } + + if (mgmt_app_init(ctx.digest) != 0) { + state = FW_STATE_FAIL; + break; + } + + state = FW_STATE_START; + break; + + case FW_STATE_START: + // CDI = hash(uds, hash(app), uss) + compute_cdi(ctx.digest, ctx.use_uss, ctx.uss); + + 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; + } + } + + (void)memset((void *)resetinfo->app_digest, 0, + sizeof(resetinfo->app_digest)); + + jump_to_app(); + break; // Not reached case FW_STATE_FAIL: // fallthrough @@ -461,5 +606,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..4632aa9 --- /dev/null +++ b/hw/application_fpga/fw/tk1/mgmt_app.c @@ -0,0 +1,43 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "mgmt_app.h" + +// Lock down what app can start from flash slot 0. +// +// To update this, compute the BLAKE2s digest of the app.bin +static const uint8_t allowed_app_digest[32] = { + 0xb6, 0x86, 0x1b, 0x26, 0xef, 0x69, 0x77, 0x12, 0xed, 0x6c, 0xca, + 0xe8, 0x35, 0xb4, 0x5c, 0x01, 0x07, 0x71, 0xab, 0xce, 0x3f, 0x30, + 0x79, 0xda, 0xe6, 0xf9, 0xee, 0x4b, 0xe2, 0x06, 0x95, 0x33, +}; + +static uint8_t current_app_digest[32]; + +int mgmt_app_init(uint8_t app_digest[32]) +{ + if (app_digest == NULL) { + return -1; + } + + memcpy_s(current_app_digest, sizeof(current_app_digest), app_digest, + 32); + + return 0; +} + +/* Authenticate an management app */ +bool mgmt_app_authenticate(void) +{ + return memeq(current_app_digest, allowed_app_digest, 32) != 0; +} + +uint8_t *mgmt_app_allowed_digest(void) +{ + return (uint8_t *)allowed_app_digest; +} 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..1e5a3fb --- /dev/null +++ b/hw/application_fpga/fw/tk1/mgmt_app.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef MGMT_APP_H +#define MGMT_APP_H + +#include +#include + +int mgmt_app_init(uint8_t app_digest[32]); +bool mgmt_app_authenticate(void); +uint8_t *mgmt_app_allowed_digest(void); + +#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..a75b2d9 --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.c @@ -0,0 +1,106 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "blake2s/blake2s.h" +#include "flash.h" +#include "partition_table.h" +#include "proto.h" + +static enum part_status part_status; + +enum part_status part_get_status(void) +{ + return part_status; +} + +static void part_digest(struct partition_table *part_table, uint8_t *out_digest, + size_t out_len); + +static void part_digest(struct partition_table *part_table, uint8_t *out_digest, + size_t out_len) +{ + int blake2err = 0; + + uint8_t key[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + assert(part_table != NULL); + assert(out_digest != NULL); + + blake2err = blake2s(out_digest, out_len, key, sizeof(key), part_table, + sizeof(struct partition_table)); + + assert(blake2err == 0); +} + +// part_table_read reads and verifies the partition table storage, +// first trying slot 0, then slot 1 if slot 0 does not verify. +// +// It stores the partition table in storage. +// +// Returns negative values on errors. +int part_table_read(struct partition_table_storage *storage) +{ + uint32_t offset[2] = { + ADDR_PARTITION_TABLE_0, + ADDR_PARTITION_TABLE_1, + }; + uint8_t check_digest[PART_DIGEST_SIZE] = {0}; + + if (storage == NULL) { + return -1; + } + + flash_release_powerdown(); + (void)memset(storage, 0x00, sizeof(*storage)); + + for (int i = 0; i < 2; i++) { + if (flash_read_data(offset[i], (uint8_t *)storage, + sizeof(*storage)) != 0) { + return -1; + } + part_digest(&storage->table, check_digest, + sizeof(check_digest)); + + if (memeq(check_digest, storage->check_digest, + sizeof(check_digest))) { + if (i == 1) { + part_status = PART_SLOT0_INVALID; + } + + return 0; + } + } + + return -1; +} + +int part_table_write(struct partition_table_storage *storage) +{ + uint32_t offset[2] = { + ADDR_PARTITION_TABLE_0, + ADDR_PARTITION_TABLE_1, + }; + + if (storage == NULL) { + return -1; + } + + part_digest(&storage->table, storage->check_digest, + sizeof(storage->check_digest)); + + for (int i = 0; i < 2; i++) { + flash_sector_erase(offset[i]); + if (flash_write_data(offset[i], (uint8_t *)storage, + sizeof(*storage)) != 0) { + return -1; + } + } + + 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..dd4fa84 --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -0,0 +1,109 @@ +// 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 */ +/* ---- ---- ---- */ +/* Partition2 64KiB 0xf0000 */ + +/* 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_0 (ADDR_BITSTREAM + SIZE_BITSTREAM) +#define ADDR_PARTITION_TABLE_1 0xf0000 +#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_0 + SIZE_PARTITION_TABLE) +#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB + +#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 PART_DIGEST_SIZE 16 + +enum part_status { + PART_SLOT0_INVALID = 1, +}; + +/* Partition Table */ +/*- Table header */ +/* - 1 bytes Version */ +/**/ +/*- Pre-loaded device app 1 */ +/* - 4 bytes length. */ +/* - 32 bytes digest. */ +/* - 64 bytes signature. */ +/**/ +/*- Pre-loaded device app 2 */ +/* - 4 bytes length. */ +/* - 32 bytes digest. */ +/* - 64 bytes signature. */ +/**/ +/*- Device app storage area */ +/* - 1 byte status. */ +/* - 16 bytes random nonce. */ +/* - 16 bytes authentication tag. */ + +struct auth_metadata { + uint8_t nonce[16]; + uint8_t authentication_digest[16]; +} __attribute__((packed)); + +struct pre_loaded_app_metadata { + uint32_t size; + uint8_t digest[32]; + uint8_t signature[64]; +} __attribute__((packed)); + +struct app_storage_area { + uint8_t status; + struct auth_metadata auth; +} __attribute__((packed)); + +struct table_header { + uint8_t version; +} __attribute__((packed)); + +struct partition_table { + struct table_header header; + struct pre_loaded_app_metadata pre_app_data[N_PRELOADED_APP]; + struct app_storage_area app_storage[N_STORAGE_AREA]; +} __attribute__((packed)); + +struct partition_table_storage { + struct partition_table table; + uint8_t check_digest[PART_DIGEST_SIZE]; +} __attribute__((packed)); + +enum part_status part_get_status(void); +int part_table_read(struct partition_table_storage *storage); +int part_table_write(struct partition_table_storage *storage); + +#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..499bbe5 --- /dev/null +++ b/hw/application_fpga/fw/tk1/preload_app.c @@ -0,0 +1,198 @@ +// 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; +} + +/* Loads a preloaded app from flash to app RAM */ +int preload_load(struct partition_table *part_table, uint8_t from_slot) +{ + if (part_table == NULL) { + return -5; + } + + if (from_slot >= N_PRELOADED_APP) { + return -4; + } + + /*Check for a valid app in flash */ + if (part_table->pre_app_data[from_slot].size == 0) { + 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) +{ + if (part_table == NULL || data == NULL) { + return -5; + } + + if (to_slot >= N_PRELOADED_APP) { + return -4; + } + + /* Check if we are allowed to store */ + if (!mgmt_app_authenticate()) { + return -3; + } + + /* Check for a valid app in flash, bale out if it already exists */ + if (part_table->pre_app_data[to_slot].size != 0) { + 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_storage *part_table_storage, + size_t app_size, uint8_t app_digest[32], + uint8_t app_signature[64], uint8_t to_slot) +{ + struct partition_table *part_table = &part_table_storage->table; + + if (part_table == NULL || app_digest == NULL || app_signature == NULL) { + return -5; + } + + if (to_slot >= N_PRELOADED_APP) { + return -4; + } + + /* Check if we are allowed to store */ + if (!mgmt_app_authenticate()) { + return -3; + } + + /* Check for a valid app in flash, bale out if it already exists */ + if (part_table->pre_app_data[to_slot].size != 0) { + return -1; + } + + if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) { + return -2; + } + + part_table->pre_app_data[to_slot].size = app_size; + 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(); + + if (part_table_write(part_table_storage) != 0) { + return -6; + } + + return 0; +} + +int preload_delete(struct partition_table_storage *part_table_storage, + uint8_t slot) +{ + struct partition_table *part_table = &part_table_storage->table; + + if (part_table_storage == NULL) { + return -5; + } + + if (slot >= N_PRELOADED_APP) { + return -4; + } + + /* Check if we are allowed to deleted */ + if (!mgmt_app_authenticate()) { + return -3; + } + + /*Check for a valid app in flash */ + if (part_table->pre_app_data[slot].size == 0) { + // Nothing to do. + return 0; + } + + part_table->pre_app_data[slot].size = 0; + + (void)memset(part_table->pre_app_data[slot].digest, 0, + sizeof(part_table->pre_app_data[slot].digest)); + + (void)memset(part_table->pre_app_data[slot].signature, 0, + sizeof(part_table->pre_app_data[slot].signature)); + + if (part_table_write(part_table_storage) != 0) { + return -6; + } + + /* 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 (part_table == NULL || app_digest == NULL || app_signature == NULL) { + return -5; + } + + if (slot >= N_PRELOADED_APP) { + return -4; + } + + /* Check if we are allowed to read */ + if (!mgmt_app_authenticate()) { + return -3; + } + + 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..af2c498 --- /dev/null +++ b/hw/application_fpga/fw/tk1/preload_app.h @@ -0,0 +1,24 @@ +// 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 + +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_storage *part_table_storage, + size_t app_size, uint8_t app_digest[32], + uint8_t app_signature[64], uint8_t to_slot); +int preload_delete(struct partition_table_storage *part_table_storage, + 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..0a26ace --- /dev/null +++ b/hw/application_fpga/fw/tk1/resetinfo.h @@ -0,0 +1,28 @@ +// 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_FLASH0 = 1, + START_FLASH1 = 2, + START_FLASH0_VER = 3, + START_FLASH1_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..b2cd3bd --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.c @@ -0,0 +1,100 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "spi.h" +#include +#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 + +static int spi_ready(void); +static void spi_enable(void); +static void spi_disable(void); +static void spi_write(uint8_t *cmd, size_t size); +static void spi_read(uint8_t *buf, size_t size); + +// 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. +static 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) +{ + assert(cmd != NULL); + + 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) +{ + assert(buf != NULL); + + 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..d2571e0 --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.h @@ -0,0 +1,13 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_SPI_H +#define TKEY_SPI_H + +#include +#include + +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/state.h b/hw/application_fpga/fw/tk1/state.h index 694448b..fd085dd 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_LOAD_FLASH, + FW_STATE_LOAD_FLASH_MGMT, + 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..a8f5c4e --- /dev/null +++ b/hw/application_fpga/fw/tk1/storage.c @@ -0,0 +1,278 @@ +// 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) +{ + if (part_table == NULL) { + return -4; + } + + for (uint8_t i = 0; i < N_STORAGE_AREA; i++) { + if (part_table->app_storage[i].status == 0x00) { + return i; + } + } + return -1; +} + +static int index_to_address(int index, uint32_t *address) +{ + if (address == NULL) { + return -4; + } + + if ((index < 0) || (index >= N_STORAGE_AREA)) { + return -1; + } + + *address = ADDR_STORAGE_AREA + index * SIZE_STORAGE_AREA; + + return 0; +} + +/* 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) +{ + if (part_table == NULL) { + return -4; + } + + 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_storage *part_table_storage) +{ + if (part_table_storage == NULL) { + return -4; + } + + struct partition_table *part_table = &part_table_storage->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; + } + + uint32_t start_address = 0; + int err = index_to_address(index, &start_address); + if (err) { + return -3; + } + + /* Allocate the empty index found */ + /* Erase area first */ + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(start_address); // Erase first 64 KB block + flash_block_64_erase(start_address + + 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); + + if (part_table_write(part_table_storage) != 0) { + return -5; + } + + return 0; +} + +/* Dealloacate a previously allocated storage area. Returns zero on success, and + * non-zero on errors. */ +int storage_deallocate_area(struct partition_table_storage *part_table_storage) +{ + if (part_table_storage == NULL) { + return -4; + } + + struct partition_table *part_table = &part_table_storage->table; + + int index = storage_get_area(part_table); + if (index == -1) { + /* No area to deallocate */ + return -1; + } + + uint32_t start_address = 0; + int err = index_to_address(index, &start_address); + if (err) { + return -3; + } + + /* Erase area first */ + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(start_address); // Erase first 64 KB block + flash_block_64_erase(start_address + + 0x10000); // Erase second 64 KB block + + /* Clear partition table lastly */ + part_table->app_storage[index].status = 0; + + (void)memset(part_table->app_storage[index].auth.nonce, 0x00, + sizeof(part_table->app_storage[index].auth.nonce)); + + (void)memset( + part_table->app_storage[index].auth.authentication_digest, 0x00, + sizeof(part_table->app_storage[index].auth.authentication_digest)); + + if (part_table_write(part_table_storage) != 0) { + return -5; + } + + 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) +{ + if (part_table == NULL) { + return -4; + } + + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + uint32_t start_address = 0; + int err = index_to_address(index, &start_address); + if (err) { + return -3; + } + + /* Cannot only erase entire sectors */ + if (offset % 4096 != 0) { + return -2; + } + + /* Cannot erase less than one sector */ + if (size < 4096 || size > SIZE_STORAGE_AREA || size % 4096 != 0) { + return -2; + } + + if ((offset + size) >= SIZE_STORAGE_AREA) { + return -2; + } + + uint32_t address = start_address + 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) +{ + if (part_table == NULL || data == NULL) { + return -4; + } + + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + uint32_t start_address = 0; + int err = index_to_address(index, &start_address); + if (err) { + return -3; + } + + if ((offset + size) > SIZE_STORAGE_AREA || size > 4096) { + /* Writing outside of area */ + return -2; + } + + uint32_t address = start_address + 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) +{ + if (part_table == NULL || data == NULL) { + return -4; + } + + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + uint32_t start_address = 0; + int err = index_to_address(index, &start_address); + if (err) { + return -3; + } + + if ((offset + size) > SIZE_STORAGE_AREA) { + /* Reading outside of area */ + return -2; + } + + uint32_t address = start_address + 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..c61860f --- /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_deallocate_area(struct partition_table_storage *part_table_storage); +int storage_allocate_area(struct partition_table_storage *part_table_storage); +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..7a24021 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -5,29 +5,112 @@ #include #include -#include +#include +#include +#include +#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 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 struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE; // clang-format on -int32_t syscall_handler(uint32_t number, uint32_t arg1) +extern struct partition_table_storage part_table_storage; +extern uint8_t part_status; + +int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, + uint32_t arg3) { + struct reset *userreset = (struct reset *)arg1; + switch (number) { case TK1_SYSCALL_RESET: + if (arg2 > sizeof(resetinfo->next_app_data)) { + return -1; + } + + (void)memset((void *)resetinfo, 0, sizeof(*resetinfo)); + resetinfo->type = userreset->type; + memcpy((void *)resetinfo->app_digest, userreset->app_digest, + 32); + memcpy((void *)resetinfo->next_app_data, + userreset->next_app_data, arg2); *system_reset = 1; + + // Should not be reached. + assert(1 == 2); + break; + + case TK1_SYSCALL_ALLOC_AREA: + if (storage_allocate_area(&part_table_storage) < 0) { + debug_puts("couldn't allocate storage area\n"); + return -1; + } + return 0; - case TK1_SYSCALL_SET_LED: - led_set(arg1); + case TK1_SYSCALL_DEALLOC_AREA: + if (storage_deallocate_area(&part_table_storage) < 0) { + debug_puts("couldn't deallocate storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_WRITE_DATA: + if (storage_write_data(&part_table_storage.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_storage.table, arg1, + (uint8_t *)arg2, arg3) < 0) { + debug_puts("couldn't read storage area\n"); + return -1; + } + return 0; case TK1_SYSCALL_GET_VIDPID: // UDI is 2 words: VID/PID & serial. Return just the // first word. Serial is kept secret to the device // app. return udi[0]; + + case TK1_SYSCALL_PRELOAD_DELETE: + return preload_delete(&part_table_storage, 1); + + case TK1_SYSCALL_PRELOAD_STORE: + // arg1 offset + // arg2 data + // arg3 size + // always using slot 1 + return preload_store(&part_table_storage.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_storage, arg1, + (uint8_t *)arg2, (uint8_t *)arg3, + 1); + + case TK1_SYSCALL_PRELOAD_GET_DIGSIG: + return preload_get_digsig(&part_table_storage.table, + (uint8_t *)arg1, (uint8_t *)arg2, 1); + + case TK1_SYSCALL_STATUS: + return part_get_status(); + 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..f7255d6 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_STATUS = 13, }; #endif