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
12 changes: 12 additions & 0 deletions .github/workflows/test-configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ jobs:
arch: arm
config-file: ./config/examples/same51-dualbank.config

samd21_test:
uses: ./.github/workflows/test-build.yml
with:
arch: arm
config-file: ./config/examples/samd21.config

samd21_j18_test:
uses: ./.github/workflows/test-build.yml
with:
arch: arm
config-file: ./config/examples/samd21_j18.config

imx_rt1040_test:
uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml
with:
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ $(LSCRIPT): $(LSCRIPT_IN) FORCE
sed -e "s/@STACK_SIZE_PER_HART@/$(STACK_SIZE_PER_HART)/g" | \
sed -e "s/@WOLFBOOT_L2SCRATCH_SIZE@/$(WOLFBOOT_L2SCRATCH_SIZE)/g" | \
sed -e "s/@STACK_SIZE@/$(STACK_SIZE)/g" | \
sed -e "s/@WOLFBOOT_RAM_SIZE@/$(WOLFBOOT_RAM_SIZE)/g" | \
sed -e 's/@WOLFHAL_FLASH_EXCLUDE_TEXT@/$(WOLFHAL_FLASH_EXCLUDE_TEXT)/g' | \
sed -e 's/@WOLFHAL_FLASH_EXCLUDE_RODATA@/$(WOLFHAL_FLASH_EXCLUDE_RODATA)/g' | \
sed -e 's/@WOLFHAL_FLASH_RAM_SECTIONS@/$(WOLFHAL_FLASH_RAM_SECTIONS)/g' \
Expand Down
3 changes: 3 additions & 0 deletions arch.mk
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ ifeq ($(ARCH),ARM)
## Target specific configuration
ifeq ($(TARGET),samr21)
CORTEX_M0=1
# SAMD21x17 and SAMR21 have 16KB of SRAM, SAMD21x18 has 32KB. The
# linker script takes this as the length of its RAM region.
WOLFBOOT_RAM_SIZE?=0x4000
endif
ifeq ($(TARGET),imx_rt)
CORTEX_M7=1
Expand Down
41 changes: 41 additions & 0 deletions config/examples/samd21.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
ARCH?=ARM
TARGET?=samr21
SIGN?=ECC256
HASH?=SHA256
DEBUG?=0
DEBUG_UART?=0
VTOR?=1
CORTEX_M0?=1
NO_ASM?=0
NO_MPU?=1
EXT_FLASH?=0
SPI_FLASH?=0
ALLOW_DOWNGRADE?=0
NVM_FLASH_WRITEONCE?=1
WOLFBOOT_VERSION?=0
V?=0
SPMATH?=1
RAM_CODE?=0
DUALBANK_SWAP?=0

# Cortex-M0+ doesn't support unaligned word accesses. WOLFSSL_USE_ALIGN
# routes word-sized hash operations through byte-safe code paths when the
# input/output buffers aren't 4-byte aligned.
CFLAGS_EXTRA+=-DWOLFSSL_USE_ALIGN

# ATSAMD21J17A/G17A: 128KB flash, 16KB SRAM.
# NVMCTRL erases a 256-byte row, which is the smallest unit wolfBoot can swap.
WOLFBOOT_SECTOR_SIZE?=0x100

# 32KB bootloader, 2 * 47.75KB boot/update partitions, one row of swap.
# Largest signed application is 47KB: the partition less the 256-byte header
# and the two trailer rows NVM_FLASH_WRITEONCE uses.
#
# The bootloader region is 32KB because the NVMCTRL BOOTPROT fuse only
# protects 8KB, 16KB or 32KB. Any other boundary either leaves the
# bootloader writable or extends the protection into the boot partition,
# where it would block the swap.
WOLFBOOT_PARTITION_SIZE?=0xBF00
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x8000
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x13F00
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x1FE00
38 changes: 38 additions & 0 deletions config/examples/samd21_j18.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ARCH?=ARM
TARGET?=samr21
SIGN?=ECC256
HASH?=SHA256
DEBUG?=0
DEBUG_UART?=0
VTOR?=1
CORTEX_M0?=1
NO_ASM?=0
NO_MPU?=1
EXT_FLASH?=0
SPI_FLASH?=0
ALLOW_DOWNGRADE?=0
NVM_FLASH_WRITEONCE?=1
WOLFBOOT_VERSION?=0
V?=0
SPMATH?=1
RAM_CODE?=0
DUALBANK_SWAP?=0

# Cortex-M0+ doesn't support unaligned word accesses. WOLFSSL_USE_ALIGN
# routes word-sized hash operations through byte-safe code paths when the
# input/output buffers aren't 4-byte aligned.
CFLAGS_EXTRA+=-DWOLFSSL_USE_ALIGN

# ATSAMD21J18A/G18A: 256KB flash, 32KB SRAM.
# NVMCTRL erases a 256-byte row, which is the smallest unit wolfBoot can swap.
WOLFBOOT_SECTOR_SIZE?=0x100
WOLFBOOT_RAM_SIZE?=0x8000

# 32KB bootloader, 2 * 111.75KB boot/update partitions, one row of swap.
# 32KB is one of the three sizes the NVMCTRL BOOTPROT fuse can protect.
# Largest signed application is 111KB: the partition less the 256-byte header
# and the two trailer rows NVM_FLASH_WRITEONCE uses.
WOLFBOOT_PARTITION_SIZE?=0x1BF00
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x8000
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x23F00
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x3FE00
103 changes: 103 additions & 0 deletions docs/Targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This README describes configuration of supported targets.
* [Microchip PolarFire SoC](#microchip-polarfire-soc)
* [Microchip SAMA5D3](#microchip-sama5d3)
* [Microchip SAME51](#microchip-same51)
* [Microchip SAMD21 / SAMR21](#microchip-samd21--samr21)
* [Nordic nRF52840](#nordic-nrf52840)
* [Nordic nRF5340](#nordic-nrf5340)
* [Nordic nRF54L15](#nordic-nrf54l15)
Expand Down Expand Up @@ -5328,6 +5329,108 @@ The above is assuming the default configuration where the BOOT partition starts
address `0x8000`.


## Microchip SAMD21 / SAMR21

SAM D21 and SAM R21 are Cortex-M0+ microcontrollers running at up to 48MHz. They
share one SYSCTRL/GCLK/NVMCTRL/PAC design, so a single HAL covers both and the
target name is `samr21` for either part.

NVMCTRL programs a 64-byte page and erases a 256-byte row of four pages. The row
is the smallest unit wolfBoot can swap, so `WOLFBOOT_SECTOR_SIZE` is `0x100`.
That is also the floor for `IMAGE_HEADER_SIZE`, which keeps the practical
signature choice to ECC256 or ED25519: ECC384, ED448 and RSA need a 512-byte
header, and LMS/XMSS need 2KB or more, each forcing a logical sector of that
size and the same amount again of SRAM for the NVM cache.

### Configurations

* `config/examples/samd21.config` - ATSAMD21J17A/G17A, 128KB flash, 16KB SRAM
* `config/examples/samd21_j18.config` - ATSAMD21J18A/G18A, 256KB flash, 32KB SRAM

`WOLFBOOT_RAM_SIZE` gives the linker script the length of the SRAM region, and
must match the part: `0x4000` for the x17 variants and SAMR21, `0x8000` for the
x18. The 128KB layout is a 32KB bootloader, two 47.75KB partitions and one row
of swap, which leaves 47KB for a signed application. The 256KB layout is a 32KB
bootloader, two 111.75KB partitions and one row of swap, leaving 111KB.

Both layouts put the bootloader region at 32KB because the NVMCTRL `BOOTPROT`
fuse in the user row protects 8KB, 16KB or 32KB and nothing in between. A
boundary that is not one of those sizes either leaves the bootloader writable
or pushes the protected region into the boot partition, where it blocks the
swap.

`WOLFSSL_USE_ALIGN` is set in both configs. Cortex-M0+ does not support
unaligned word accesses, and it routes word-sized hash operations through
byte-safe paths when a buffer is not 4-byte aligned.

### Footprint and signature choice

Measured with arm-none-eabi-gcc 13.2.1 at `-Os`, `SPMATH=1`, `DEBUG=0`,
`DEBUG_UART=0`. "Flash" is the sum of the allocated bootloader sections and
"stack" is the `STACK_USAGE` the build reports for the chosen algorithm:

| Signature | Flash | Stack | Fits 16KB SRAM |
|---|---|---|---|
| ECC256 + SHA256 | 18320 | 7632 | yes |
| ECC256 + SHA256, `NO_ASM=1` | 15300 | 7632 | yes |
| ED25519 + SHA256 | 13316 | 5000 | yes |
| RSA2048 + SHA256 | 12940 | 17568 | no |
| LMS-8-10-1 + SHA256 | 9340 | 1320 | yes |

RSA2048 is the smallest build and has by far the cheapest verification, because
a public-key operation with a small exponent costs much less than an ECDSA
verify. It needs more stack than a 16KB part has, so it is an option on the x18
only. ED25519 gains nothing from `NO_ASM=0`: the Thumb-1 SP assembly accelerates
the integer math that ECC256 and RSA use, and ED25519 does not go through it.

Cortex-M0+ is ARMv6-M, so `sp_armthumb.c` is the only assembly tier available;
`sp_cortexm.c` is Thumb-2 and will not assemble for this core. There is no
Thumb-1 per-algorithm assembly either, so SHA-256 is the portable C
implementation. `NO_ASM=1` drops the SP assembly as well, trading roughly a
factor of four in verification time for about 3KB of flash.

### Uploading the bootloader and the firmware image

The on-board EDBG debugger on an Xplained Pro board presents a CMSIS-DAP
interface that OpenOCD drives with its stock scripts:

```
$ openocd -f interface/cmsis-dap.cfg -f target/at91samdXX.cfg \
-c "program wolfboot.bin 0x0 verify" \
-c "program test-app/image_v1_signed.bin 0x8000 verify reset exit"
```

`0x8000` is `WOLFBOOT_PARTITION_BOOT_ADDRESS`, the same in both configs. A
J-Link or an MPLAB ICD 5 through MDB works equally well.

### Console

`hal/uart/uart_drv_samr21.c` drives a SERCOM in USART mode, and `hal_init()`
brings it up at 115200 8N1 when built with `DEBUG_UART=1`. That costs about
2.5KB of flash over a silent build.

The defaults describe the SAM D21 Xplained Pro EDBG virtual COM port: SERCOM3,
TX on PA22 (pad 0), RX on PA23 (pad 1), both peripheral function C. A board that
wires its console differently overrides `UART_SERCOM`, `UART_PORT_GROUP`,
`UART_TX_PIN`, `UART_RX_PIN`, `UART_PIN_MUX`, `UART_TXPO` and `UART_RXPO`;
`UART_TXPO` and `UART_RXPO` are pad selections out of the SERCOM CTRLA field
tables rather than pin numbers.

The driver sets the SERCOM core's generic clock channel itself, because
`hal_init()` attaches every peripheral channel to GCLK generator 7 and never
configures that generator - only generators 0 and 1 are set up. Nothing else in
wolfBoot needs a peripheral core clock, since NVMCTRL runs from the APB clock,
so the generator 7 assignment is left alone.

### Status

The HAL, both configurations, the console driver and the test application build
and are covered by CI and by the host unit tests `unit-samr21-erase-advance` and
`unit-flash-write-samr21`. **None of it has been validated on silicon yet**, and
the console pin assignment in particular is from the Xplained Pro schematic
rather than from a working board.


## NXP iMX-RT

The NXP iMX-RT10xx family of devices contain a Cortex-M7 with a DCP coprocessor for SHA256 acceleration.
Expand Down
64 changes: 53 additions & 11 deletions hal/samr21.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

#include <stdint.h>
#include "image.h"
#ifdef DEBUG_UART
#include "uart_drv.h"
#endif

/* Clock settings for cpu samd21g18a @ 48MHz */
#define CPU_FREQ (48000000)
Expand Down Expand Up @@ -59,12 +62,18 @@
#define NVMCTRLA_REG *((volatile uint16_t *)(NVMCTRL_BASE))
#define NVMCTRLB_REG *((volatile uint32_t *)(NVMCTRL_BASE + 4))
#define NVMCTRL_INTFLAG *((volatile uint8_t *)(NVMCTRL_BASE + 0x14))
#define NVMCTRL_STATUS *((volatile uint16_t *)(NVMCTRL_BASE + 0x18))
#define NVMCTRL_ADDR *((volatile uint32_t *)(NVMCTRL_BASE + 0x1c))
#define NVMCMD_KEY (0xA500)
#define NVMCMD_ERASE (0x02)
#define NVMCMD_WP (0x04)
#define NVMCMD_PBC (0x44)
#define NVMCTRL_INTFLAG_NVMREADY (1)
/* CTRLB.MANW: commit each page with an explicit NVMCMD_WP instead of
* letting the controller write it out when the page buffer fills. */
#define NVMCTRLB_MANW (1 << 7)
/* STATUS error flags, write-one-to-clear: PROGE, LOCKE, NVME */
#define NVMSTATUS_ERR ((1 << 2) | (1 << 3) | (1 << 4))


#define GCLK_BASE (0x40000C00)
Expand Down Expand Up @@ -100,26 +109,27 @@
#define PAC1_BASE (0x41000000)
#define PAC1_WPCLR *((volatile uint32_t *)(PAC1_BASE)) /* Register to clear write protection (unlock flash) */
#define PAC1_WPSET *((volatile uint32_t *)(PAC1_BASE + 4)) /* Register to set write protection (lock flash) */
#define PAC_WP_NVMCTL (1 << 1) /* Bit position for NVM in WPCLR/WPSET registers */
#define PAC_WP_NVMCTL (1 << 2) /* Bit position for NVM in WPCLR/WPSET registers */


#define GCLK_WAITBUSY() { while (GCLK_STATUS & GCLK_STATUS_SYNCBUSY) {} }

void hal_init(void)
{
uint32_t i, reg;

WDT_CTRL &= (~WDT_EN);
#ifndef WOLFBOOT_UNIT_TEST_FLASH_WRITE
__asm__ volatile ("cpsid i");
#endif
uint32_t i, reg;
/* enable clocks for the power, sysctrl and gclk modules */
APBAMASK_REG = APBAMASK_PM_EN | APBAMASK_SYSCTRL_EN | APBAMASK_GCLK_EN;

/* set NVM wait states */
/* Set NVM wait states and take manual control of the page write. The
* NVMCTRL APB clock is enabled out of reset and has to stay on: the
* flash write and erase paths below drive these same registers. */
APBBMASK_REG |= APBBMASK_NVM_EN;
NVMCTRLB_REG |= ((WAITSTATES & 0x0f) << 1);
APBBMASK_REG &= ~APBBMASK_NVM_EN;
NVMCTRLB_REG |= ((WAITSTATES & 0x0f) << 1) | NVMCTRLB_MANW;


/* Set 8MHz oscillator */
Expand All @@ -145,15 +155,18 @@ void hal_init(void)
GCLK_CLKCTRL = GCLK_CLKCTRL_GEN_CLK7 | i;
GCLK_WAITBUSY();
}

#ifdef DEBUG_UART
uart_init(115200, 8, 'N', 1);
#endif
}

void hal_prepare_boot(void)
{
#ifdef WOLFBOOT_RESTORE_CLOCK
/* Reset NVM wait states */
/* Restore the NVM wait states and page write mode to their reset values */
APBBMASK_REG |= APBBMASK_NVM_EN;
NVMCTRLB_REG &= ~((WAITSTATES & 0x0f) << 1);
APBBMASK_REG &= ~APBBMASK_NVM_EN;
NVMCTRLB_REG &= ~(((WAITSTATES & 0x0f) << 1) | NVMCTRLB_MANW);

/* Reset clock controller */
GCLK_CTRL = GCLK_CTRL_RESET;
Expand All @@ -162,6 +175,21 @@ void hal_prepare_boot(void)
}


/* Wait for the controller to go idle, then report and clear any error it
* latched. The STATUS error flags are write-one-to-clear. */
static int RAMFUNCTION nvm_wait(void)
{
uint16_t status;

while (!(NVMCTRL_INTFLAG & NVMCTRL_INTFLAG_NVMREADY)) { }
status = (NVMCTRL_STATUS & NVMSTATUS_ERR);
if (status != 0) {
NVMCTRL_STATUS = status;
return -1;
}
return 0;
}

int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
int i = 0;
Expand All @@ -172,6 +200,8 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)

/* Clear page buffer */
NVMCTRLA_REG = (NVMCMD_PBC | NVMCMD_KEY);
if (nvm_wait() != 0)
return -1;
while (i < len) {
if ((len - i > 3) && ((((address + i) & 0x03) == 0) && ((((uint32_t)data) + i) & 0x03) == 0)) {
dst = (uint32_t *)(address + i);
Expand All @@ -192,9 +222,20 @@ int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
}
*dst = val;
}
/* The page buffer holds one page, and MANW stops the controller
* from writing it out by itself, so commit at every page boundary
* and once more for a trailing partial page. */
if ((i == len) || (((address + i) % FLASH_PAGESIZE) == 0)) {
NVMCTRLA_REG = (NVMCMD_WP | NVMCMD_KEY);
if (nvm_wait() != 0)
return -1;
if (i < len) {
NVMCTRLA_REG = (NVMCMD_PBC | NVMCMD_KEY);
if (nvm_wait() != 0)
return -1;
}
}
}
/* Enable write protection */
NVMCTRLA_REG = (NVMCMD_WP | NVMCMD_KEY);
return 0;
}

Expand All @@ -213,7 +254,8 @@ int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
while (len > 0) {
NVMCTRL_ADDR = (address >> 1); /* This register holds the address of a 16-bit row */
NVMCTRLA_REG = NVMCMD_ERASE | NVMCMD_KEY;
while (!(NVMCTRL_INTFLAG & NVMCTRL_INTFLAG_NVMREADY)) { }
if (nvm_wait() != 0)
return -1;
address += FLASH_ROW_SIZE;
len -= FLASH_ROW_SIZE;
}
Expand Down
2 changes: 1 addition & 1 deletion hal/samr21.ld
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = @BOOTLOADER_PARTITION_SIZE@
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x4000
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = @WOLFBOOT_RAM_SIZE@
}

SECTIONS
Expand Down
Loading
Loading