From 6ef6c36f6f572668b63e843fc72191768d2a8f5a Mon Sep 17 00:00:00 2001 From: Michael Cardell Widerkrantz Date: Wed, 12 Mar 2025 16:17:48 +0100 Subject: [PATCH] Add filesystem code and storage syscalls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds syscalls: - ALLOCATE_AREA - DEALLOCATE_AREA - WRITE_DATA - READ_DATA and code to access the filesystem and the flash over SPI. Based on original work by Daniel Jobson for these files: - auth_app.[ch] - flash.[ch] - spi.[ch] - partition_table.[ch] - rng.[ch] - storage.[ch] which are used with small changes to integrate with the new syscall method. Co-authored-by: Daniel Jobson Co-authored-by: Mikael Ă…gren --- hw/application_fpga/Makefile | 8 +- hw/application_fpga/fw/testapp/main.c | 39 +++- hw/application_fpga/fw/testapp/syscall.h | 3 +- hw/application_fpga/fw/tk1/auth_app.c | 68 ++++++ hw/application_fpga/fw/tk1/auth_app.h | 14 ++ hw/application_fpga/fw/tk1/firmware.lds | 2 +- hw/application_fpga/fw/tk1/flash.c | 217 +++++++++++++++++++ hw/application_fpga/fw/tk1/flash.h | 58 +++++ hw/application_fpga/fw/tk1/main.c | 8 + hw/application_fpga/fw/tk1/partition_table.c | 50 +++++ hw/application_fpga/fw/tk1/partition_table.h | 105 +++++++++ hw/application_fpga/fw/tk1/rng.c | 29 +++ hw/application_fpga/fw/tk1/rng.h | 11 + hw/application_fpga/fw/tk1/spi.c | 90 ++++++++ hw/application_fpga/fw/tk1/spi.h | 14 ++ hw/application_fpga/fw/tk1/storage.c | 197 +++++++++++++++++ hw/application_fpga/fw/tk1/storage.h | 22 ++ hw/application_fpga/fw/tk1/syscall_handler.c | 39 +++- hw/application_fpga/fw/tk1/syscall_num.h | 7 +- 19 files changed, 974 insertions(+), 7 deletions(-) create mode 100644 hw/application_fpga/fw/tk1/auth_app.c create mode 100644 hw/application_fpga/fw/tk1/auth_app.h create mode 100644 hw/application_fpga/fw/tk1/flash.c create mode 100644 hw/application_fpga/fw/tk1/flash.h create mode 100644 hw/application_fpga/fw/tk1/partition_table.c create mode 100644 hw/application_fpga/fw/tk1/partition_table.h create mode 100644 hw/application_fpga/fw/tk1/rng.c create mode 100644 hw/application_fpga/fw/tk1/rng.h create mode 100644 hw/application_fpga/fw/tk1/spi.c create mode 100644 hw/application_fpga/fw/tk1/spi.h create mode 100644 hw/application_fpga/fw/tk1/storage.c create mode 100644 hw/application_fpga/fw/tk1/storage.h diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index 822929a..8ec26a6 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -126,7 +126,13 @@ FIRMWARE_OBJS = \ $(P)/fw/tk1/proto.o \ $(P)/fw/tk1/blake2s/blake2s.o \ $(P)/fw/tk1/syscall_enable.o \ - $(P)/fw/tk1/syscall_handler.o + $(P)/fw/tk1/syscall_handler.o \ + $(P)/fw/tk1/spi.o \ + $(P)/fw/tk1/flash.o \ + $(P)/fw/tk1/storage.o \ + $(P)/fw/tk1/partition_table.o \ + $(P)/fw/tk1/auth_app.o \ + $(P)/fw/tk1/rng.o FIRMWARE_SOURCES = \ $(P)/fw/tk1/main.c \ diff --git a/hw/application_fpga/fw/testapp/main.c b/hw/application_fpga/fw/testapp/main.c index 58298eb..a09ffa2 100644 --- a/hw/application_fpga/fw/testapp/main.c +++ b/hw/application_fpga/fw/testapp/main.c @@ -121,13 +121,48 @@ int main(void) } // But a syscall to get parts of UDI should be able to run - int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0); + int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0, 0, 0); if (vidpid != 0x00010203) { failmsg("Expected VID/PID to be 0x00010203"); anyfailed = 1; } + puts(IO_CDC, "\r\nAllocating storage area..."); + + if (syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0) != 0) { + failmsg("Failed to allocate storage area"); + } + puts(IO_CDC, "done.\r\n"); + + puts(IO_CDC, "\r\nWriting to storage area..."); + + uint8_t out_data[14] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13 }; + if (syscall(TK1_SYSCALL_WRITE_DATA, 0, (uint32_t)out_data, sizeof(out_data)) != 0) { + failmsg("Failed to write to storage area"); + } + puts(IO_CDC, "done.\r\n"); + + puts(IO_CDC, "\r\nReading from storage area..."); + + uint8_t in_data[14] = { 0 }; + if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data, sizeof(in_data)) != 0) { + failmsg("Failed to write to storage area"); + } + if (!memeq(in_data, out_data, sizeof(in_data))) { + failmsg("Failed to read back data from storage area"); + anyfailed = 1; + } + puts(IO_CDC, "done.\r\n"); + + puts(IO_CDC, "\r\nDeallocating storage area..."); + + if (syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0) != 0) { + failmsg("Failed to deallocate storage area"); + } + puts(IO_CDC, "done.\r\n"); + uint32_t cdi_local[CDI_WORDS]; uint32_t cdi_local2[CDI_WORDS]; wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS); @@ -223,7 +258,7 @@ int main(void) } if (in == '+') { - syscall(TK1_SYSCALL_RESET, 0); + syscall(TK1_SYSCALL_RESET, 0, 0, 0); } write(IO_CDC, &in, 1); diff --git a/hw/application_fpga/fw/testapp/syscall.h b/hw/application_fpga/fw/testapp/syscall.h index 293f3e5..42b76e3 100644 --- a/hw/application_fpga/fw/testapp/syscall.h +++ b/hw/application_fpga/fw/testapp/syscall.h @@ -6,6 +6,7 @@ #ifndef TKEY_APP_SYSCALL_H #define TKEY_APP_SYSCALL_H -int syscall(uint32_t number, uint32_t arg1); +int syscall(uint32_t number, uint32_t arg1, uint32_t arg2, + uint32_t arg3); #endif diff --git a/hw/application_fpga/fw/tk1/auth_app.c b/hw/application_fpga/fw/tk1/auth_app.c new file mode 100644 index 0000000..4ee2c07 --- /dev/null +++ b/hw/application_fpga/fw/tk1/auth_app.c @@ -0,0 +1,68 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "auth_app.h" +#include "blake2s/blake2s.h" +#include "partition_table.h" +#include "rng.h" + +static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST; + +/* Calculates the authentication digest based on a supplied nonce and the CDI. + * Requires that the CDI is already calculated and stored */ +static void calculate_auth_digest(uint8_t *nonce, uint8_t *auth_digest) +{ + /* TODO: Check so the CDI is non-zero? */ + + blake2s_ctx ctx = {0}; + + // Generate a 16 byte authentication digest + blake2s_init(&ctx, 16, NULL, 0); + blake2s_update(&ctx, (const void *)cdi, 32); + blake2s_update(&ctx, nonce, 16); + blake2s_final(&ctx, auth_digest); +} + +/* Generates a 16 byte nonce */ +static void generate_nonce(uint32_t *nonce) +{ + + for (uint8_t i = 0; i < 4; i++) { + nonce[i] = rng_get_word(); + } + return; +} +/* Returns the authentication digest and random nonce. Requires that the CDI is + * already calculated and stored */ +void auth_app_create(struct auth_metadata *auth_table) +{ + uint8_t nonce[16]; + uint8_t auth_digest[16]; + + generate_nonce((uint32_t *)nonce); + + calculate_auth_digest(nonce, auth_digest); + + memcpy_s(auth_table->authentication_digest, 16, auth_digest, 16); + memcpy_s(auth_table->nonce, 16, nonce, 16); + + return; +} + +bool auth_app_authenticate(struct auth_metadata *auth_table) +{ + uint8_t auth_digest[16]; + + calculate_auth_digest(auth_table->nonce, auth_digest); + + if (memeq(auth_digest, auth_table->authentication_digest, 16)) { + return true; + } + + return false; +} diff --git a/hw/application_fpga/fw/tk1/auth_app.h b/hw/application_fpga/fw/tk1/auth_app.h new file mode 100644 index 0000000..fcc411a --- /dev/null +++ b/hw/application_fpga/fw/tk1/auth_app.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef AUTH_APP_H +#define AUTH_APP_H + +#include "partition_table.h" + +#include + +void auth_app_create(struct auth_metadata *auth_table); +bool auth_app_authenticate(struct auth_metadata *auth_table); + +#endif diff --git a/hw/application_fpga/fw/tk1/firmware.lds b/hw/application_fpga/fw/tk1/firmware.lds index c9c8579..ef1299a 100644 --- a/hw/application_fpga/fw/tk1/firmware.lds +++ b/hw/application_fpga/fw/tk1/firmware.lds @@ -7,7 +7,7 @@ OUTPUT_ARCH("riscv") ENTRY(_start) /* Define stack size */ -STACK_SIZE = 0xEF0; /* 3824 B */ +STACK_SIZE = 3000; MEMORY { diff --git a/hw/application_fpga/fw/tk1/flash.c b/hw/application_fpga/fw/tk1/flash.c new file mode 100644 index 0000000..8bc69f3 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.c @@ -0,0 +1,217 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include + +#include "flash.h" +#include "spi.h" + +// clang-format off +static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER; +static volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER; +static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS; +static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL; +// clang-format on + +// CPU clock frequency in Hz +#define CPUFREQ 21000000 +#define PAGE_SIZE 256 + +static void delay(int timeout_ms) +{ + // Tick once every centisecond + *timer_prescaler = CPUFREQ / 100; + *timer = timeout_ms / 10; + + *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_START_BIT); + + while (*timer_status != 0) { + } + + // Stop timer + *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT); +} + +bool flash_is_busy(void) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + uint8_t rx_buf = {0x00}; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf, sizeof(rx_buf)); + + if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) { + return true; + } + + return false; +} + +// Blocking until !busy +void flash_wait_busy(void) +{ + while (flash_is_busy()) { + delay(10); + } +} + +void flash_write_enable(void) +{ + uint8_t tx_buf = WRITE_ENABLE; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_write_disable(void) +{ + uint8_t tx_buf = WRITE_DISABLE; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_sector_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = SECTOR_ERASE; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + /* tx_buf[3] is within a sector, and hence does not make a difference */ + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +void flash_block_32_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = BLOCK_ERASE_32K; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF; + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +// 64 KiB block erase, only cares about address bits 16 and above. +void flash_block_64_erase(uint32_t address) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = BLOCK_ERASE_64K; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + /* tx_buf[2] and tx_buf[3] is within a block, and hence does not make a + * difference */ + + flash_write_enable(); + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); + flash_wait_busy(); +} + +void flash_release_powerdown(void) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = RELEASE_POWER_DOWN; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_powerdown(void) +{ + uint8_t tx_buf = POWER_DOWN; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0); +} + +void flash_read_manufacturer_device_id(uint8_t *device_id) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_MANUFACTURER_ID; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2); +} + +void flash_read_jedec_id(uint8_t *jedec_id) +{ + uint8_t tx_buf = READ_JEDEC_ID; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3); +} + +void flash_read_unique_id(uint8_t *unique_id) +{ + uint8_t tx_buf[5] = {0x00}; + tx_buf[0] = READ_UNIQUE_ID; + + spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8); +} + +void flash_read_status(uint8_t *status_reg) +{ + uint8_t tx_buf = READ_STATUS_REG_1; + + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1); + + tx_buf = READ_STATUS_REG_2; + spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1, 1); +} + +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size) +{ + uint8_t tx_buf[4] = {0x00}; + tx_buf[0] = READ_DATA; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF; + + return spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, dest_buf, size); +} + +// Only handles writes where the least significant byte of the start address is +// zero. +int flash_write_data(uint32_t address, uint8_t *data, size_t size) +{ + if (size <= 0 || size > 4096) { + return -1; + } + + size_t left = size; + uint8_t *p_data = data; + size_t n_bytes = 0; + + uint8_t tx_buf[4] = { + PAGE_PROGRAM, /* tx_buf[0] */ + (address >> ADDR_BYTE_3_BIT) & 0xFF, /* tx_buf[1] */ + (address >> ADDR_BYTE_2_BIT) & 0xFF, /* tx_buf[2] */ + 0x00, /* tx_buf[3] */ + }; + + while (left > 0) { + if (left >= PAGE_SIZE) { + n_bytes = PAGE_SIZE; + } else { + n_bytes = left; + } + + flash_write_enable(); + + if (spi_transfer(tx_buf, sizeof(tx_buf), p_data, n_bytes, NULL, + 0) != 0) { + return -1; + } + + left -= n_bytes; + p_data += n_bytes; + + address += n_bytes; + tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF; + tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF; + + flash_wait_busy(); + } + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/flash.h b/hw/application_fpga/fw/tk1/flash.h new file mode 100644 index 0000000..cad6f07 --- /dev/null +++ b/hw/application_fpga/fw/tk1/flash.h @@ -0,0 +1,58 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_FLASH_H +#define TKEY_FLASH_H + +#include +#include +#include + +#define WRITE_ENABLE 0x06 +#define WRITE_DISABLE 0x04 + +#define READ_STATUS_REG_1 0x05 +#define READ_STATUS_REG_2 0x35 +#define WRITE_STATUS_REG 0x01 + +#define PAGE_PROGRAM 0x02 +#define SECTOR_ERASE 0x20 +#define BLOCK_ERASE_32K 0x52 +#define BLOCK_ERASE_64K 0xD8 +#define CHIP_ERASE 0xC7 + +#define POWER_DOWN 0xB9 +#define READ_DATA 0x03 +#define RELEASE_POWER_DOWN 0xAB + +#define READ_MANUFACTURER_ID 0x90 +#define READ_JEDEC_ID 0x9F +#define READ_UNIQUE_ID 0x4B + +#define ENABLE_RESET 0x66 +#define RESET 0x99 + +#define ADDR_BYTE_3_BIT 16 +#define ADDR_BYTE_2_BIT 8 +#define ADDR_BYTE_1_BIT 0 + +#define STATUS_REG_BUSY_BIT 0 +#define STATUS_REG_WEL_BIT 1 + +bool flash_is_busy(void); +void flash_wait_busy(void); +void flash_write_enable(void); +void flash_write_disable(void); +void flash_sector_erase(uint32_t address); +void flash_block_32_erase(uint32_t address); +void flash_block_64_erase(uint32_t address); +void flash_release_powerdown(void); +void flash_powerdown(void); +void flash_read_manufacturer_device_id(uint8_t *device_id); +void flash_read_jedec_id(uint8_t *jedec_id); +void flash_read_unique_id(uint8_t *unique_id); +void flash_read_status(uint8_t *status_reg); +int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size); +int flash_write_data(uint32_t address, uint8_t *data, size_t size); + +#endif diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c index 62e496c..1b3b944 100644 --- a/hw/application_fpga/fw/tk1/main.c +++ b/hw/application_fpga/fw/tk1/main.c @@ -12,6 +12,7 @@ #include #include "blake2s/blake2s.h" +#include "partition_table.h" #include "proto.h" #include "state.h" #include "syscall_enable.h" @@ -35,6 +36,8 @@ static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_R static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND; // clang-format on +struct partition_table part_table; + // Context for the loading of a TKey program struct context { uint32_t left; // Bytes left to receive @@ -418,6 +421,11 @@ int main(void) scramble_ram(); + if (part_table_read(&part_table) != 0) { + // Couldn't read or create partition table + assert(1 != 2); + } + #if defined(SIMULATION) run(&ctx); #endif diff --git a/hw/application_fpga/fw/tk1/partition_table.c b/hw/application_fpga/fw/tk1/partition_table.c new file mode 100644 index 0000000..1825cb1 --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.c @@ -0,0 +1,50 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include + +#include "flash.h" +#include "partition_table.h" +#include "proto.h" + +int part_table_read(struct partition_table *part_table) +{ + // Read from flash, if it exists, otherwise create a new one. + + flash_release_powerdown(); + memset(part_table, 0x00, sizeof(*part_table)); + + flash_read_data(ADDR_PARTITION_TABLE, (uint8_t *)part_table, + sizeof(*part_table)); + + // TODO: Implement redundancy and consistency check + + if (part_table->header.version != PART_TABLE_VERSION) { + // Partition table is not ours. Make a new one, and store it. + memset(part_table, 0x00, sizeof(*part_table)); + + part_table->header.version = PART_TABLE_VERSION; + + for (int i = 0; i < 4; i++) { + part_table->app_storage[i].addr_start = + (ADDR_STORAGE_AREA + i * SIZE_STORAGE_AREA); + part_table->app_storage[i].size = SIZE_STORAGE_AREA; + } + + part_table_write(part_table); + } + + // Now the partition table is synced between flash and RAM. + + return 0; +} + +int part_table_write(struct partition_table *part_table) +{ + flash_sector_erase(ADDR_PARTITION_TABLE); + flash_write_data(ADDR_PARTITION_TABLE, (uint8_t *)part_table, + sizeof(*part_table)); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h new file mode 100644 index 0000000..7a954bb --- /dev/null +++ b/hw/application_fpga/fw/tk1/partition_table.h @@ -0,0 +1,105 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef PARTITION_TABLE_H +#define PARTITION_TABLE_H + +#include + +/* ---- Flash ---- ---- */ +/* name size start addr */ +/* ---- ---- ---- */ +/* bitstream 128KiB 0x00 */ +/* ---- ---- ---- */ +/* Partition 64KiB 0x20000 */ +/* ---- ---- ---- */ +/* Pre load 128KiB 0x30000 */ +/* ---- ---- ---- */ +/* storage 1 128KiB 0x50000 */ +/* storage 2 128KiB 0x70000 */ +/* storage 3 128KiB 0x90000 */ +/* storage 4 128KiB 0xB0000 */ +/* ---- ---- ---- */ + +/* To simplify all blocks are aligned with the 64KiB blocks on the W25Q80DL + * flash. */ + +#define PART_TABLE_VERSION 1 + +#define ADDR_BITSTREAM 0UL +#define SIZE_BITSTREAM 0x20000UL // 128KiB + +#define ADDR_PARTITION_TABLE (ADDR_BITSTREAM + SIZE_BITSTREAM) +#define SIZE_PARTITION_TABLE \ + 0x10000UL // 64KiB, 60 KiB reserved, 2 flash pages (2 x 4KiB) for the + // partition table + +#define ADDR_PRE_LOADED_APP (ADDR_PARTITION_TABLE + SIZE_PARTITION_TABLE) +#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB + +#define ADDR_STORAGE_AREA (ADDR_PRE_LOADED_APP + SIZE_PRE_LOADED_APP) +#define SIZE_STORAGE_AREA 0x20000UL // 128KiB +#define N_STORAGE_AREA 4 + +#define EMPTY_AREA + +/* Partition Table */ +/*- Table header */ +/* - 1 bytes Version */ +/**/ +/*- Management device app */ +/* - Status. */ +/* - 16 byte random nonce. */ +/* - 16 byte authentication digest. */ +/**/ +/*- Pre-loaded device app */ +/* - 1 byte status. */ +/* - 4 bytes length. */ +/* - 16 bytes random nonce. */ +/* - 16 bytes authentication digest. */ +/**/ +/*- Device app storage area */ +/* - 1 byte status. */ +/* - 16 bytes random nonce. */ +/* - 16 bytes authentication tag. */ +/* - 4 bytes physical start address. */ +/* - 4 bytes physical end address. */ + +struct auth_metadata { + uint8_t nonce[16]; + uint8_t authentication_digest[16]; +} __attribute__((packed)); + +struct management_app_metadata { + uint8_t status; + struct auth_metadata auth; +} __attribute__((packed)); + +struct pre_loaded_app_metadata { + uint8_t status; + uint32_t size; + struct auth_metadata auth; +} __attribute__((packed)); + +struct app_storage_area { + uint8_t status; + struct auth_metadata auth; + uint32_t addr_start; + uint32_t size; +} __attribute__((packed)); + +struct table_header { + uint8_t version; +} __attribute__((packed)); + +struct partition_table { + struct table_header header; + struct management_app_metadata mgmt_app_data; + struct pre_loaded_app_metadata pre_app_data; + struct app_storage_area app_storage[N_STORAGE_AREA]; +} __attribute__((packed)); + +int part_table_read(struct partition_table *part_table); +int part_table_write(struct partition_table *part_table); + +#endif diff --git a/hw/application_fpga/fw/tk1/rng.c b/hw/application_fpga/fw/tk1/rng.c new file mode 100644 index 0000000..258b327 --- /dev/null +++ b/hw/application_fpga/fw/tk1/rng.c @@ -0,0 +1,29 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "rng.h" +#include + +#include + +// clang-format off +static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS; +static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY; +// clang-format on +// +// +uint32_t rng_get_word(void) +{ + while ((*trng_status & (1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) { + } + return *trng_entropy; +} + +uint32_t rng_xorwow(uint32_t state, uint32_t acc) +{ + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + state += acc; + return state; +} diff --git a/hw/application_fpga/fw/tk1/rng.h b/hw/application_fpga/fw/tk1/rng.h new file mode 100644 index 0000000..616b551 --- /dev/null +++ b/hw/application_fpga/fw/tk1/rng.h @@ -0,0 +1,11 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only +#ifndef RNG_H +#define RNG_H + +#include + +uint32_t rng_get_word(void); +uint32_t rng_xorwow(uint32_t state, uint32_t acc); + +#endif diff --git a/hw/application_fpga/fw/tk1/spi.c b/hw/application_fpga/fw/tk1/spi.c new file mode 100644 index 0000000..4487ee3 --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.c @@ -0,0 +1,90 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include "spi.h" +#include + +#include +#include + +// clang-format off +static volatile uint32_t *spi_en = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x200); +static volatile uint32_t *spi_xfer = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x204); +static volatile uint32_t *spi_data = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x208); +// clang-format on + +// returns non-zero when the SPI-master is ready, and zero if not +// ready. This can be used to check if the SPI-master is available +// in the hardware. +int spi_ready(void) +{ + return *spi_xfer; +} + +static void spi_enable(void) +{ + *spi_en = 1; +} + +static void spi_disable(void) +{ + *spi_en = 0; +} + +static void _spi_write(uint8_t *cmd, size_t size) +{ + for (size_t i = 0; i < size; i++) { + while (!spi_ready()) { + } + + *spi_data = cmd[i]; + *spi_xfer = 1; + } + + while (!spi_ready()) { + } +} + +static void _spi_read(uint8_t *buf, size_t size) +{ + + while (!spi_ready()) { + } + + for (size_t i = 0; i < size; i++) { + + *spi_data = 0x00; + *spi_xfer = 1; + + // wait until spi master is done + while (!spi_ready()) { + } + + buf[i] = (*spi_data & 0xff); + } +} + +// Function to both read and write data to the connected SPI flash. +int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size, + uint8_t *rx_buf, size_t rx_size) +{ + if (cmd == NULL || cmd_size == 0) { + return -1; + } + + spi_enable(); + + _spi_write(cmd, cmd_size); + + if (tx_buf != NULL || tx_size != 0) { + _spi_write(tx_buf, tx_size); + } + + if (rx_buf != NULL && rx_size != 0) { + _spi_read(rx_buf, rx_size); + } + + spi_disable(); + + return 0; +} diff --git a/hw/application_fpga/fw/tk1/spi.h b/hw/application_fpga/fw/tk1/spi.h new file mode 100644 index 0000000..2c59f4c --- /dev/null +++ b/hw/application_fpga/fw/tk1/spi.h @@ -0,0 +1,14 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef TKEY_SPI_H +#define TKEY_SPI_H + +#include +#include + +int spi_ready(void); +int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size, + uint8_t *rx_buf, size_t rx_size); + +#endif diff --git a/hw/application_fpga/fw/tk1/storage.c b/hw/application_fpga/fw/tk1/storage.c new file mode 100644 index 0000000..86f32dc --- /dev/null +++ b/hw/application_fpga/fw/tk1/storage.c @@ -0,0 +1,197 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include + +#include "auth_app.h" +#include "flash.h" +#include "partition_table.h" +#include "storage.h" + +/* Returns the index of the first empty area. If there is no empty area -1 is + * returned. */ +static int get_first_empty(struct partition_table *part_table) +{ + + for (uint8_t i = 0; i < N_STORAGE_AREA; i++) { + if (part_table->app_storage[i].status == 0x00) { + return i; + } + } + return -1; +} + +/* Returns the index of the area an app has allocated. If no area is + * authenticated -1 is returned. */ +static int storage_get_area(struct partition_table *part_table) +{ + for (uint8_t i = 0; i < N_STORAGE_AREA; i++) { + if (part_table->app_storage[i].status != 0x00) { + if (auth_app_authenticate( + &part_table->app_storage[i].auth)) { + return i; + } + } + } + return -1; +} + +/* Allocate a new area for an app. Returns zero if a new area is allocated, one + * if an area already was allocated, and negative values for errors. */ +int storage_allocate_area(struct partition_table *part_table) +{ + if (storage_get_area(part_table) != -1) { + /* Already has an area */ + return 1; + } + + int index = get_first_empty(part_table); + if (index == -1) { + /* No empty slot */ + return -1; + } + + /* Allocate the empty index found */ + /* Erase area first */ + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(part_table->app_storage[index] + .addr_start); // Erase first 64 KB block + flash_block_64_erase(part_table->app_storage[index].addr_start + + 0x10000); // Erase second 64 KB block + + /* Write partition table lastly */ + part_table->app_storage[index].status = 0x01; + auth_app_create(&part_table->app_storage[index].auth); + + part_table_write(part_table); + + return 0; +} + +/* Dealloacate a previously allocated storage area. Returns zero on success, and + * non-zero on errors. */ +int storage_deallocate_area(struct partition_table *part_table) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No area to deallocate */ + return -1; + } + + /* Erase area first */ + + /* Assumes the area is 64 KiB block aligned */ + flash_block_64_erase(part_table->app_storage[index] + .addr_start); // Erase first 64 KB block + flash_block_64_erase(part_table->app_storage[index].addr_start + + 0x10000); // Erase second 64 KB block + + /* Clear partition table lastly */ + part_table->app_storage[index].status = 0; + + memset(part_table->app_storage[index].auth.nonce, 0x00, + sizeof(part_table->app_storage[index].auth.nonce)); + + memset( + part_table->app_storage[index].auth.authentication_digest, 0x00, + sizeof(part_table->app_storage[index].auth.authentication_digest)); + + part_table_write(part_table); + + return 0; +} + +/* Erases sector. Offset of a sector to begin erasing, must be a multiple of + * the sector size. Size to erase in bytes, must be a multiple of the sector * + * size. Returns zero on success, negative error code on failure */ +int storage_erase_sector(struct partition_table *part_table, uint32_t offset, + size_t size) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + /* Cannot erase less than one sector */ + if (size < 4096 || size > part_table->app_storage[index].size || + size % 4096 != 0) { + return -2; + } + + if ((offset) >= part_table->app_storage[index].size) { + return -2; + } + + uint32_t address = part_table->app_storage[index].addr_start + offset; + + debug_puts("storage: erase addr: "); + debug_putinthex(address); + debug_lf(); + + for (size_t i = 0; i < size; i += 4096) { + flash_sector_erase(address); + address += 4096; + } + + return 0; +} + +/* Writes the specified data to the offset inside of the + * allocated area. Assumes area has been erased before hand. + * Currently only handles writes to one sector, hence max size of 4096 bytes. + * Returns zero on success. */ +int storage_write_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + if ((offset + size) > part_table->app_storage[index].size || + size > 4096) { + /* Writing outside of area */ + return -2; + } + + uint32_t address = part_table->app_storage[index].addr_start + offset; + + debug_puts("storage: write to addr: "); + debug_putinthex(address); + debug_lf(); + + return flash_write_data(address, data, size); +} + +/* Reads size bytes of data at the specified offset inside of + * the allocated area. Returns zero on success. Only read limit + * is the size of the allocated area */ +int storage_read_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size) +{ + int index = storage_get_area(part_table); + if (index == -1) { + /* No allocated area */ + return -1; + } + + if ((offset + size) > part_table->app_storage[index].size) { + /* Reading outside of area */ + return -2; + } + + uint32_t address = part_table->app_storage[index].addr_start + offset; + + debug_puts("storage: read from addr: "); + debug_putinthex(address); + debug_lf(); + + return flash_read_data(address, data, size); +} diff --git a/hw/application_fpga/fw/tk1/storage.h b/hw/application_fpga/fw/tk1/storage.h new file mode 100644 index 0000000..f48cb0b --- /dev/null +++ b/hw/application_fpga/fw/tk1/storage.h @@ -0,0 +1,22 @@ +// Copyright (C) 2024 - Tillitis AB +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef STORAGE_H +#define STORAGE_H + +#include "partition_table.h" + +#include +#include +#include + +int storage_allocate_area(struct partition_table *part_table); +int storage_deallocate_area(struct partition_table *part_table); +int storage_erase_sector(struct partition_table *part_table, uint32_t offset, + size_t size); +int storage_write_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size); +int storage_read_data(struct partition_table *part_table, uint32_t offset, + uint8_t *data, size_t size); + +#endif diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c index 0d26fa8..083824d 100644 --- a/hw/application_fpga/fw/tk1/syscall_handler.c +++ b/hw/application_fpga/fw/tk1/syscall_handler.c @@ -5,8 +5,12 @@ #include #include +#include #include +#include "partition_table.h" +#include "storage.h" + #include "../tk1/syscall_num.h" // clang-format off @@ -14,11 +18,44 @@ static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTE static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST; // clang-format on -int32_t syscall_handler(uint32_t number, uint32_t arg1) +extern struct partition_table part_table; + +int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2, + uint32_t arg3) { switch (number) { case TK1_SYSCALL_RESET: *system_reset = 1; + return 0; + case TK1_SYSCALL_ALLOC_AREA: + if (storage_allocate_area(&part_table) < 0) { + debug_puts("couldn't allocate storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_DEALLOC_AREA: + if (storage_deallocate_area(&part_table) < 0) { + debug_puts("couldn't deallocate storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_WRITE_DATA: + if (storage_write_data(&part_table, arg1, (uint8_t *)arg2, + arg3) < 0) { + debug_puts("couldn't write storage area\n"); + return -1; + } + + return 0; + case TK1_SYSCALL_READ_DATA: + if (storage_read_data(&part_table, arg1, (uint8_t *)arg2, + arg3) < 0) { + debug_puts("couldn't read storage area\n"); + return -1; + } + return 0; case TK1_SYSCALL_SET_LED: led_set(arg1); diff --git a/hw/application_fpga/fw/tk1/syscall_num.h b/hw/application_fpga/fw/tk1/syscall_num.h index 82f3f06..e1b5a95 100644 --- a/hw/application_fpga/fw/tk1/syscall_num.h +++ b/hw/application_fpga/fw/tk1/syscall_num.h @@ -6,8 +6,13 @@ enum syscall_num { TK1_SYSCALL_RESET = 1, + TK1_SYSCALL_ALLOC_AREA = 2, + TK1_SYSCALL_DEALLOC_AREA = 3, + TK1_SYSCALL_WRITE_DATA = 4, + TK1_SYSCALL_READ_DATA = 5, + TK1_SYSCALL_ERASE_DATA = 6, + TK1_SYSCALL_GET_VIDPID = 7, TK1_SYSCALL_SET_LED = 10, - TK1_SYSCALL_GET_VIDPID = 12, }; #endif