Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/PolarFire.md
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,31 @@ The AES offload is worth about 4.2x on the decrypt. **The hash offload is about

Athena also does not fit eNVM alongside the full feature set: with the manifest linked it needs both `GZIP` and `MPFS_SBI_SC_FLASH` dropped. Dropping `GZIP` costs nothing when the FIT's kernel is stored uncompressed.

The port also serves AES-256-ECB, which is how AES-256-XTS reaches the engine: wolfCrypt's XTS masks the whole buffer with the tweak chain and issues one bulk ECB call per XTS call (`HAVE_AES_ECB` required, distinct in/out buffers), while the tweak math stays in software. wolfBoot itself encrypts images with CTR and has no XTS use; the ECB path is for an application. Requests under `WC_MPFS_ATHENA_AES_MIN_SZ` (32 bytes) are declined to software, which keeps the XTS tweak and ciphertext-stealing blocks off the engine. See `wolfcrypt/src/port/microchip/README.md` in wolfSSL.

#### Measuring the offload bare-metal

The test application doubles as a bare-metal wolfCrypt test and benchmark harness for the engine, in the same M-mode environment wolfBoot uses, so its figures compare with the software build of the same app and not with a Linux userspace benchmark:

```sh
cp config/examples/polarfire_mpfs250_m_qspi.config .config
make clean
make MPFS_ATHENA=1 MPFS_ATHENA_CAL_DIR=/path/to/hart-software-services/services/crypto \
WOLFCRYPT_BENCHMARK=1 WOLFBOOT_L2SCRATCH_SIZE=512k
```

Load `test-app/image_v1_signed.bin` with `mpfs_qspi_prog.py` as for any M-mode test app. The benchmark app is about 240 KB and is loaded at `0x0A020200`, so the bootloader's stack has to sit at the top of all four scratchpad ways rather than the default 256 KB, which is what `WOLFBOOT_L2SCRATCH_SIZE=512k` does; `test-app/RISCV64-mpfs250.ld` reserves 64 KB for the app's own stack above that, sized for the benchmark's SP math frames. The `WOLFCRYPT_TEST=1` app is about 370 KB and does not fit the scratchpad beside the bootloader: build it on `polarfire_mpfs250_m_qspi_ddr.config` with its `-DWOLFBOOT_MMODE_SMODE_BOOT` line removed, which loads the signed app into DDR and enters it with the M-mode direct jump, and with `NO_ASM=1`, since the test-app's RISC-V SHA assembly does not assemble against the test configuration. `MPFS_ATHENA=1` makes the app bring the engine up itself, so the bootloader it runs under may be built with or without the offload. The app registers the port, runs its known-answer self-test and prints `Athena: self-test passed, engine served N call(s)`; the stock `wolfcrypt_test()` and `benchmark_test()` then drive the engine through `WC_USE_DEVID`, with the benchmark labelling offloaded results `HW`. The benchmark runs once per call size in `MPFS_BENCH_SIZES` (default 1 KiB, 512 B and 4 KiB; each needs twice that from `WOLFSSL_STATIC_MEMORY_TEST_SZ`), since one engine transfer per call amortises very differently at sector size and in bulk. The app ends with the total engine call count: a passing test or a plausible rate proves nothing about offload on its own, because every case the port declines runs in software. The same app built without `MPFS_ATHENA` is the software baseline. Object files are configuration-specific, so `make clean` between the two builds.

Measured on the Video Kit with the DDR configuration (E51 at 600 MHz), the software column being wolfCrypt's default C AES for RISC-V:

| Call size | AES-256-XTS software | AES-256-XTS Athena | AES-256-CTR Athena | SHA-384 software | SHA-384 Athena |
|---|---|---|---|---|---|
| 512 B | 48 KiB/s | 1.23 MiB/s | 12.9 MiB/s | 5.7 MiB/s | 11.4 MiB/s |
| 1 KiB | 49 KiB/s | 2.06 MiB/s | 14.6 MiB/s | 5.8 MiB/s | 13.2 MiB/s |
| 4 KiB | 49 KiB/s | 4.2 MiB/s | 16.1 MiB/s | 5.8 MiB/s | 14.8 MiB/s |

AES-128-XTS stays in software at about 68 KiB/s. The app keeps the five MSS watchdogs refreshed from a machine timer interrupt every 5 s, since a test or benchmark pass runs far longer than their window, and its M-mode trap handler prints the cause and halts instead of resuming at the faulting instruction. The RISC-V harts trap on misaligned accesses, so `user_settings.h` defines `WOLFSSL_USE_ALIGN` for `ARCH_RISCV64`; the AES-GCM tests and benchmark also need the wolfSSL change that takes the byte-wise GCM table multiply under that define on 64-bit builds.

## Source files

`hal/mpfs250.c` - Hardware abstraction layer (UART, QSPI, SD/eMMC, multi-hart)
Expand Down
2 changes: 1 addition & 1 deletion docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@ Make variables, on the command line or in the `.config`. See [PolarFire.md](Pola
| Option | Purpose |
|--------|---------|
| `LIBERO_FPGA_CONFIG_DIR` | Path to the board's Libero/HSS `fpga_design_config` headers. Enables LPDDR4 init (`MPFS_DDR_INIT`); required for the M-Mode + DDR build. |
| `MPFS_ATHENA` (+ `MPFS_ATHENA_CAL_DIR`) | SHA-384 and AES-256-CTR offload to the Athena F5200 (TeraFire), with a mandatory self-check. |
| `MPFS_ATHENA` (+ `MPFS_ATHENA_CAL_DIR`) | SHA-384, AES-256-CTR and AES-256-ECB (AES-XTS) offload to the Athena F5200 (TeraFire), with a mandatory self-check. With `WOLFCRYPT_TEST=1` or `WOLFCRYPT_BENCHMARK=1` the test-app also drives the engine. |
| `SNVM_KEYSTORE`, `SNVM_KEYSTORE_PROVISION`, `SNVM_KEYSTORE_REQUIRE_ROM` | Serve the verification keystore from secure NVM; the one-time writer; refuse to boot unless every keystore page is ROM-flagged. |
| `SNVM_KEK`, `SNVM_ENCKEY_PROVISION` | PUF-derived KEK with RFC 3394 key-wrap for the sNVM-stored image-encryption key, and its one-time writer. |
| `MPFS_SCB_SMODE_DENY` | Fence S-mode off the System Controller mailbox (PMP plus dtb node removal), so nothing after wolfBoot can reach the sNVM write service. |
Expand Down
10 changes: 4 additions & 6 deletions hal/mpfs250.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,6 @@ extern uint8_t _main_hart_hls; /* linker-provided address symbol; typed as uint8

/* CLINT MSIP register for IPI delivery */
#define CLINT_MSIP_REG(hart) (*(volatile uint32_t*)(CLINT_BASE + (hart) * 4))
/* CLINT machine-timer comparator (per hart) and MTIME counter */
#define CLINT_MTIMECMP_REG(hart) \
(*(volatile uint64_t*)(CLINT_BASE + 0x4000UL + (hart) * 8UL))
#define CLINT_MTIME_REG (*(volatile uint64_t*)(CLINT_BASE + 0xBFF8UL))

/* Signal secondary harts that E51 (main hart) is ready. */
static void mpfs_signal_main_hart_started(void)
Expand Down Expand Up @@ -216,10 +212,10 @@ void secondary_hart_entry(unsigned long hartid, HLS_DATA* hls)
}
}

#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
#ifdef WOLFBOOT_RISCV_MMODE
/* Enable the CLINT MTIME counter via the SYSREG RTC clock divider. Without it
* MTIME never advances and an S-mode OS has no scheduler tick. */
static void mpfs_enable_mtime(void)
void mpfs_enable_mtime(void)
{
volatile uint32_t *rtc_cr = (volatile uint32_t *)(SYSREG_BASE + 0x0CUL);
uint32_t div;
Expand All @@ -232,7 +228,9 @@ static void mpfs_enable_mtime(void)
*rtc_cr |= (1UL << 16); /* enable RTC/time-base clock */
__asm__ volatile("fence iorw, iorw" ::: "memory");
}
#endif /* WOLFBOOT_RISCV_MMODE */

#if defined(MPFS_DDR_INIT) && defined(WOLFBOOT_MMODE_SMODE_BOOT)
/* SBI HSM hart_start backend: stage the target hart's mailbox and ring its
* MSIP; it enters S-mode at saddr with a0=hartid, a1=opaque. */
int sbi_hal_hart_start(unsigned long hartid, unsigned long saddr,
Expand Down
5 changes: 5 additions & 0 deletions hal/mpfs250.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ int mpfs_nonce(uint8_t *nonce);
#ifndef CLINT_BASE
#define CLINT_BASE 0x02000000UL
#endif
/* Machine-timer comparator (per hart) and the MTIME counter. */
#define CLINT_MTIMECMP_REG(hart) \
(*(volatile uint64_t*)(CLINT_BASE + 0x4000UL + (hart) * 8UL))
#define CLINT_MTIME_REG (*(volatile uint64_t*)(CLINT_BASE + 0xBFF8UL))

/* RTC Clock Frequency (1 MHz after divisor) */
#define RTC_CLOCK_FREQ 1000000UL
Expand Down Expand Up @@ -472,6 +476,7 @@ void uart_write(const char* buf, unsigned int sz);
#endif
#ifdef WOLFBOOT_RISCV_MMODE
void secondary_hart_entry(unsigned long hartid, HLS_DATA* hls);
void mpfs_enable_mtime(void);
#endif
#endif /* __ASSEMBLER__ */

Expand Down
24 changes: 23 additions & 1 deletion include/user_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,14 @@ extern int tolower(int c);
# endif
#endif

/* The RISC-V harts trap on a misaligned load or store with nothing to emulate
* it bare-metal, so make wolfCrypt's ALIGN* attributes real: without this the
* word-sized casts it makes on struct members (the AES-GCM table, for one)
* fault. */
#ifdef ARCH_RISCV64
# define WOLFSSL_USE_ALIGN
#endif

#if defined(EXT_ENCRYPTED)
# define HAVE_PWDBASED
#endif
Expand Down Expand Up @@ -764,7 +772,9 @@ extern int tolower(int c);
#define WC_NO_HASHDRBG
#define CUSTOM_RAND_GENERATE_SEED my_rng_seed_gen
#define CUSTOM_RAND_GENERATE_BLOCK my_rng_seed_gen
#ifndef __ASSEMBLER__
extern int my_rng_seed_gen(unsigned char* output, unsigned int sz);
#endif

/* Full test/benchmark algo set: AES ECB/CBC/CTR/GCM/XTS,
* SHA-256/384/512, SHA-3. (AES-CBC/GCM added below; SHA-384/512
Expand Down Expand Up @@ -919,7 +929,17 @@ extern int tolower(int c);
#define WOLFSSL_STATIC_MEMORY
#endif
#ifndef WOLFSSL_STATIC_MEMORY_TEST_SZ
#define WOLFSSL_STATIC_MEMORY_TEST_SZ (10 * 1024)
#if defined(TARGET_mpfs250) && defined(MPFS_DDR_INIT)
/* Every default bucket, including the two above 4 KB that the
* benchmark's 4 KB buffers and the test's larger contexts need;
* a pool without them hands out something else. Only with DDR:
* the L2 scratchpad app has no room for it. */
#define WOLFSSL_STATIC_MEMORY_TEST_SZ (128 * 1024)
#elif defined(TARGET_mpfs250)
#define WOLFSSL_STATIC_MEMORY_TEST_SZ (64 * 1024)
#else
#define WOLFSSL_STATIC_MEMORY_TEST_SZ (10 * 1024)
#endif
#endif
#define WOLFSSL_STATIC_MEMORY_LEAN

Expand All @@ -929,7 +949,9 @@ extern int tolower(int c);
/* User time functions provided */
#define WOLFSSL_USER_CURRTIME
#define XTIME my_time
#ifndef __ASSEMBLER__
extern unsigned long my_time(unsigned long* timer);
#endif
#endif

#if !defined(WOLFCRYPT_TEST) && !defined(WOLFCRYPT_BENCHMARK)
Expand Down
22 changes: 21 additions & 1 deletion test-app/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,18 @@ ifeq ($(SEC_QORIQ),1)
APP_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/nxp/sec_qoriq_baremetal.o
endif

# PolarFire SoC Athena F5200. options.mk already supplies the defines, the CAL
# include path and the archive in LIBS; the port objects are added here because
# the apps build their own wolfCrypt object list.
ifeq ($(MPFS_ATHENA),1)
ifeq ($(WOLFCRYPT_TEST)$(WOLFCRYPT_BENCHMARK),)
$(warning MPFS_ATHENA=1 without WOLFCRYPT_TEST or WOLFCRYPT_BENCHMARK only runs the engine self-test in the test-app)
endif
WOLFCRYPT_SUPPORT=1
APP_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/cryptocb.o
APP_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/microchip/mpfs_athena.o
endif

ifeq ($(WOLFCRYPT_TEST),1)
CFLAGS+=-DWOLFCRYPT_TEST
WOLFCRYPT_SUPPORT=1
Expand Down Expand Up @@ -1530,6 +1542,13 @@ endif
ifeq ($(TARGET),mpfs250)
LSCRIPT_TEMPLATE:=RISCV64-mpfs250.ld
CFLAGS+=-DRAMFUNCTION='__attribute__((used,section(".ramcode")))'
# The wolfCrypt test and benchmark need far more stack than the plain app,
# which the L2 scratchpad configurations have no room to reserve.
ifneq ($(filter 1,$(WOLFCRYPT_TEST) $(WOLFCRYPT_BENCHMARK)),)
TEST_APP_STACK_SIZE?=0x00010000
else
TEST_APP_STACK_SIZE?=0x00002000
endif
endif

# $(CYPRESS_PDL)/devices/templates/COMPONENT_MTB/COMPONENT_CM0P/system_psoc6_cm0plus.o
Expand Down Expand Up @@ -1978,7 +1997,8 @@ endif
sed -e "s/@WOLFBOOT_PARTITION_BOOT_ADDRESS@/$(WOLFBOOT_PARTITION_BOOT_ADDRESS)/g" | \
sed -e "s/@WOLFBOOT_PARTITION_SIZE@/$(WOLFBOOT_PARTITION_SIZE)/g" | \
sed -e "s/@WOLFBOOT_PARTITION_UPDATE_ADDRESS@/$(WOLFBOOT_PARTITION_UPDATE_ADDRESS)/g" | \
sed -e "s/@WOLFBOOT_PARTITION_SWAP_ADDRESS@/$(WOLFBOOT_PARTITION_SWAP_ADDRESS)/g" \
sed -e "s/@WOLFBOOT_PARTITION_SWAP_ADDRESS@/$(WOLFBOOT_PARTITION_SWAP_ADDRESS)/g" | \
sed -e "s/@TEST_APP_STACK_SIZE@/$(TEST_APP_STACK_SIZE)/g" \
> $(@)
$(Q)rm -f .app-size .entry-point .wolfboot-offset .partition-size .header-size

Expand Down
2 changes: 1 addition & 1 deletion test-app/RISCV64-mpfs250.ld
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/* TODO: Update with actual memory map for MPFS250 */

_Min_Heap_Size = 0x00002000; /* required amount of heap */
_Min_Stack_Size = 0x00002000; /* required amount of stack */
_Min_Stack_Size = @TEST_APP_STACK_SIZE@; /* required amount of stack */

OUTPUT_ARCH( "riscv" )

Expand Down
107 changes: 106 additions & 1 deletion test-app/app_mpfs250.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,84 @@ int wolfcrypt_test(void *args);
int benchmark_test(void *args);
#endif

#ifdef WOLFCRYPT_BENCHMARK
/* Buffer sizes the whole benchmark is run at, in turn: the Athena F5200 does
* one engine transfer per call, so a sector-sized call and a bulk one amortise
* the setup very differently, and the software build runs the same set as the
* baseline. Each needs twice the size from the static pool
* (WOLFSSL_STATIC_MEMORY_TEST_SZ). */
#ifndef MPFS_BENCH_SIZES
#define MPFS_BENCH_SIZES { 1024, 512, 4096 }
#endif
#endif /* WOLFCRYPT_BENCHMARK */

#ifdef MPFS_ATHENA
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/port/microchip/mpfs_athena.h>

static void mpfs_athena_app_init(void)
{
int ret;

/* The engine is brought up here, not by the bootloader: a wolfBoot built
* without MPFS_ATHENA never touched it, and the port's own init handles
* one that did. wolfcrypt_test and benchmark_test register the device
* again, which the port accepts. */
ret = wolfCrypt_Init();
if (ret != 0) {
wolfBoot_printf("Athena: wolfCrypt_Init failed (%d)\r\n", ret);
return;
}
ret = wc_MpfsAthena_RegisterDevice(WC_MPFS_ATHENA_DEVID);
if (ret != 0) {
wolfBoot_printf("Athena: RegisterDevice failed (%d)\r\n", ret);
return;
}
ret = wc_MpfsAthena_SelfTest();
if (ret != 0) {
wolfBoot_printf("Athena: self-test failed: %s (rc %d)\r\n",
wc_MpfsAthena_SelfTestDesc(), ret);
return;
}
wolfBoot_printf("Athena: self-test passed, engine served %u call(s)\r\n",
(unsigned int)wc_MpfsAthena_GetCbCount());
}

#endif /* MPFS_ATHENA */


#ifdef WOLFBOOT_RISCV_MMODE
#include "../hal/riscv.h"
/* MTIME is not running in an M-mode boot: start it, then let the machine
* timer interrupt refresh the watchdogs every 5 s for as long as the app
* runs. */
#define MPFS_WDT_PET_TICKS (5ULL * RTC_CLOCK_FREQ)

/* The vector saved the caller's registers, so plain C: refresh the five MSS
* watchdogs, which the bitstream arms and which reset the chip well inside a
* test run, then rearm. */
void isr_mtimer(void)
{
mpfs_wdt_pet_all();
CLINT_MTIMECMP_REG(0) = CLINT_MTIME_REG + MPFS_WDT_PET_TICKS;
}

static void mpfs_wdt_timer_start(void)
{
mpfs_enable_mtime();
CLINT_MTIMECMP_REG(0) = CLINT_MTIME_REG + MPFS_WDT_PET_TICKS;
csr_set(mie, MIE_MTIE);
csr_set(mstatus, MSTATUS_MIE);
#ifdef MPFS_DDR_INIT
/* The bootloader raised the E51 to the Libero PLL rate; this copy of the
* HAL's clock variable, which times the benchmark, still holds the reset
* rate. Without DDR init the clock is never raised and that reset rate is
* the right one. */
mpfs_cpu_freq_mhz =
(uint32_t)(LIBERO_SETTING_MSS_COREPLEX_CPU_CLK / 1000000UL);
#endif
}
#endif

void main(void)
{
Expand All @@ -61,6 +139,14 @@ void main(void)
wolfBoot_printf("GPL v3\r\n");
wolfBoot_printf("========================\r\n");

#ifdef WOLFBOOT_RISCV_MMODE
mpfs_wdt_timer_start();
#endif

#ifdef MPFS_ATHENA
mpfs_athena_app_init();
#endif

#ifdef WOLFCRYPT_TEST
wolfBoot_printf("\r\nRunning wolfCrypt tests...\r\n");
wolfCrypt_Init();
Expand All @@ -72,11 +158,30 @@ void main(void)
#ifdef WOLFCRYPT_BENCHMARK
wolfBoot_printf("Running wolfCrypt benchmarks...\r\n");
wolfCrypt_Init();
benchmark_test(NULL);
{
static const unsigned int sizes[] = MPFS_BENCH_SIZES;
unsigned int i;
/* benchmark_test() puts the block size back to its default on its
* first run, so take that run's setup first and configure after. */
benchmark_init();
benchmark_free();
for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
wolfBoot_printf("\r\n--- %u-byte calls ---\r\n", sizes[i]);
benchmark_configure(sizes[i]);
benchmark_test(NULL);
}
}
wolfCrypt_Cleanup();
wolfBoot_printf("Benchmarks complete.\r\n\r\n");
#endif

#ifdef MPFS_ATHENA
/* A passing test or a plausible figure proves nothing about offload on
* its own: every case the port declines runs in software. */
wolfBoot_printf("Athena: engine served %u call(s) in total\r\n",
(unsigned int)wc_MpfsAthena_GetCbCount());
#endif

while(1) {
/* Main application loop */
/* TODO: Add watchdog feed if needed */
Expand Down
29 changes: 25 additions & 4 deletions test-app/startup_riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <stdint.h>
#include "hal/riscv.h"
#include "printf.h"

extern void trap_entry(void);
extern void trap_exit(void);
Expand Down Expand Up @@ -77,15 +78,35 @@ void do_boot(const uint32_t *app_offset)
}

static uint32_t synctrap_cause = 0;
void __attribute__((naked)) isr_synctrap(void)
{
#ifdef WOLFBOOT_RISCV_MMODE
asm volatile("csrr %0, mcause" : "=r"(synctrap_cause));
asm volatile("mret");
/* The vector saved the caller's registers, so this can be plain C. Report the
* fault and stop rather than resume at the faulting instruction, which would
* trap again for ever with nothing on the console. Hardware clears mstatus.MIE
* on trap entry, so the timer interrupt that pets the MSS watchdogs cannot run
* here and they reset the board a few seconds after the message. */
void isr_synctrap(void)
{
unsigned long cause, epc, tval;
asm volatile("csrr %0, mcause" : "=r"(cause));
asm volatile("csrr %0, mepc" : "=r"(epc));
asm volatile("csrr %0, mtval" : "=r"(tval));
synctrap_cause = (uint32_t)cause;
wolfBoot_printf("TRAP: cause=%lx epc=%lx tval=%lx\r\n", cause, epc, tval);
while (1) {
/* halt */
}
}
#else
void __attribute__((naked)) isr_synctrap(void)
{
asm volatile("csrr %0, scause" : "=r"(synctrap_cause));
asm volatile("sret");
}
#endif

/* Machine timer interrupt; a target that uses it defines its own. */
void __attribute__((weak)) isr_mtimer(void)
{
}

void isr_empty(void)
Expand Down
7 changes: 6 additions & 1 deletion test-app/vector_riscv.S
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ IV:
.align 2
j trap_empty
.align 2
j trap_empty
j _mtimer
.align 2
j trap_empty
.align 2
Expand Down Expand Up @@ -187,5 +187,10 @@ _synctrap:
jal isr_synctrap
trap_exit

_mtimer:
trap_entry
jal isr_mtimer
trap_exit

trap_empty:
nop
Loading
Loading