Oberon RTK

Secure (RP2350)

S/NS test programs for the RP2350 – with and without flash partitions

Introduction

This page describes two example programs that implement a simple Secure/Non-secure separation on the RP2350 using the standard ARM TrustZone veneer mechanism, as described in Secure/Non-secure Program Structure. For the design rationale – when and why to split a program S/NS – see Secure/Non-secure Program Design. For the STM32 counterpart, see Secure (STM32).

The two variants differ in how the images are placed in flash:

  • Secure – both the Secure and the Non-secure image live at fixed flash addresses and are executed from there directly.

  • SecurePart – both images live in flash partitions, and both are executed via address translation. The Secure-side translation is set up automatically by the bootrom; the Non-secure-side translation is set up by us, via Secure.InstallNonSecImage.

In both variants the standard ARM veneer mechanism is used for NS-to-S calls, and the application code is essentially the same. The placement difference described above manifests in sec/S.mod (SecurePart adds the Secure.InstallNonSecImage call that sets up the NS translation), in the NS image's linker base address, and (for SecurePart) in a flash partition table – detailed in The Two Variants below.

Functionality

Overview

The program implements a blinker plus console output across the S/NS boundary. The Secure (S) program configures the S/NS separation and the resources released to the Non-secure side, then starts the Non-secure (NS) program. The NS loop calls the Secure side to set the Pico LED (which also prints from the Secure console), waits, then clears the LED itself (which also prints from the Non-secure console), waits, and repeats.

Both sides drive the same onboard Pico LED, and both print over the same UART0. The Secure side configures both peripherals and releases them to the Non-secure side; the way the UART is shared is described in Sharing the UART below.

The Secure code lives in directory sec, the Non-secure code in nonsec.

Module Map

The two programs that make up each project, and where to start reading the code. The structure is the same for both projects (Secure and SecurePart); only sec/S.mod differs (see The Two Variants below).

Secure program – sec/:

Module Role
Main Local Main: the standard framework start-up (clocks, console, RuntimeErrors, FPU), with Security.Config as the final step
Security Program-specific S/NS configuration: SAU regions, separate Secure/Non-secure fault handling, ACCESSCTRL releases of the UART and the LED pin, NS-FPU access
S The Secure program proper: hands control to NS via Secure.StartNonSecProg. In the SecurePart variant, also calls Secure.InstallNonSecImage first
S0 A Secure service exposed to NS – sets the Pico LED on, with a procedure chain useful for run-time error testing

Non-secure program – nonsec/:

Module Role
Main Local Main for the Non-secure program (Console, RuntimeErrors)
Console Non-secure serial console – the "cloak" over the S-configured UART (see below)
NS The Non-secure control program: calls S0.ToggleLED across the boundary, then clears the LED locally, with its own procedure chain for error testing
ns_/NS_S0 Generated veneer stub that routes S0.ToggleLED across the boundary

NS_S0 is generated by gen-secure.

Secure Program

Module Security performs the program-specific S/NS configuration, invoked as the final step of the Secure Main:

  • SAU regions for the NSC flash, the NS flash, and the NS SRAM range – the full table is in Memory Configuration below.

  • separate Secure / Non-secure fault handling: it sets BFHFNMINS in PPB.AIRCR, so Bus Faults, Hard Faults, and NMI are handled by the corresponding handlers in the Secure and the Non-secure vector tables.

  • peripheral security via ACCESSCTRL (the RP2350's per-peripheral security controller): UART0 is released to Non-secure privileged access (for the NS console) and the Pico LED's GPIO pin is released to Non-secure (so NS can drive the LED).

  • Non-secure FPU access is enabled.

  • SRAM needs no explicit configuration: the RP2350 SRAM is fully open after reset, so the SAU region for SRAM4-7 is sufficient.

Once Main has completed (start-up phase 1), the S module body hands control to the Non-secure program via Secure.StartNonSecProg. S imports S0 only so the Secure service module is linked into the image; S itself does not call it.

Non-secure Program

After being started by S, the Non-secure program NS enters a loop with simple counter-based timing. Each pass calls S0.ToggleLED (routed through the NSC gateway as described in Secure/Non-secure Program Structure), waits, then clears the Pico LED via LED.Clear and prints to the Non-secure console, and waits again. The result is a slow blink with alternating messages on the same UART – one from the Secure side (after the Secure call) and one from the Non-secure side.

Sharing the UART

The Non-secure program runs a serial console of its own, yet it never configures a UART. The Secure side has already set UART0 up – clock, pins, baud rate – and released the peripheral via ACCESSCTRL. The NS Console only needs a software handle to drive it.

So Console.Install calls UART.Init(uartDev, uartNo) and nothing more: UART.Init fills in a Device record but touches no hardware. The record holds UART0's register addresses; NS accesses succeed because the Secure side has released the peripheral via ACCESSCTRL. The record is in effect a "cloak" – an NS-side handle over the running UART, driving the hardware the Secure side configured.

Run-time Errors

Both NS and S0 toggle the LED through a short chain of procedures. Un-commenting the error-inducing ASSERT statements in those chains – or the fail0 call in the body of S.mod – triggers a run-time error, useful for exercising the fault handling and stack-trace reporting across the S/NS boundary – see Run-time Errors.

The Two Variants

Secure – Without Flash Partitions

Both images are loaded at fixed flash addresses (Secure code + NSC at 010000100H upward; NS at 010080000H, immediately above the Secure region) and execute directly from there. No address translation, no partition table. Secure/sec/S.mod simply calls Secure.StartNonSecProg(NSimageAddr, VTORoffset).

SecurePart – With Flash Partitions

Both images live in flash partitions, and both execute via QMI address translation. The Secure partition is mapped automatically by the bootrom (no programmer action). The Non-secure-side translation is set up by the Secure program: SecurePart/sec/S.mod calls Secure.InstallNonSecImage(NScodeBaseAddr) first, which configures QMI pane 1 so the NS partition maps to the virtual address 010400000H, and then calls Secure.StartNonSecProg.

This variant brings the RP2350's partition machinery into play – partition table, family-id tagging, and picotool-based loading. The standard veneer mechanism itself is unchanged.

Build and Load

Building and loading an S/NS program entails creating two images, one for the Secure program (with the NSC veneer gateways) and one for the Non-secure program.

The S/NS build is best done using the project's build script build-elf.cmd, which uses the Astrobe config files in sec and nonsec.

See Set-up: Build Scripts on RP for instructions on setting up the environment for build scripts.

See Build & Load: RP2350 (S/NS) for the full build-and-load procedure, including the partition-table and picotool steps required for the SecurePart variant.

Memory Configuration

The RP2350 does not use Secure/Non-secure address aliasing – the same address is used by both worlds, with security determined by the SAU and ACCESSCTRL.

Flash

The addresses below are the linker base addresses, ie. the addresses at which the code executes. In the Secure variant they are also the physical flash addresses; in SecurePart the bootrom maps the Secure partition to the Secure addresses automatically, and Secure.InstallNonSecImage maps the NS partition to the NS address.

Common to both variants:

  • Secure code: from 010000100H upward.

  • NSC (Non-secure Callable veneer block): 01007E000H to 01007FFFFH (8K, at the top of the Secure region).

The Non-secure address differs:

  • Secure variant: 010080000H to 0100FFFFFH (512K, immediately above the Secure region in physical flash).

  • SecurePart variant: 010400000H to 01047FFFFH (512K, mapped via QMI address translation pane 1; the partition lives elsewhere in physical flash).

SRAM

  • SRAM0-3: Secure (no SAU region; the all-Secure default applies).

  • SRAM4-7: Non-secure (020040000H to 02007FFFFH, via SAU region 2).

The RP2350 SRAM is open after reset, so the SAU region alone is sufficient to mark the Non-secure SRAM range.

SAU

The Secure program configures the following SAU regions (volatile configuration, applied at every Secure-side start-up):

  • Region 0: 01007E000H to 01007FFFFH as Non-secure Callable flash.

  • Region 1 (Secure variant): 010080000H to 0100FFFFFH as Non-secure flash.

  • Region 1 (SecurePart variant): 010400000H to 01047FFFFH as Non-secure flash.

  • Region 2: 020040000H to 02007FFFFH as Non-secure SRAM.

  • Region 7: reserved for the bootrom (do not reconfigure – see Secure/Non-secure Program Structure).

The SAU is enabled at the end of the configuration. See Secure/Non-secure Program Structure for how the IDAU and SAU interact.

The peripherals do not need a Non-secure SAU region on the RP2350; ACCESSCTRL handles per-peripheral security separately.

Memory Configuration Overview (Secure variant)

The flash and SRAM layout for the Secure variant. The SecurePart variant has the same NSC, Secure-flash and SRAM layout; only the Non-secure flash region differs in address (mapped to 010400000H upward via QMI address translation – see The Two Variants above).

flash memory (XIP)                              SRAM
+------------------+ -                    SRAM7 +------------------+ -
| 0100FFFFFH       | ^                          | 02007FFFFH       | ^
|                  | |                          |                  | |
| Non-secure       | |                          | Non-secure       | |
| SAU: region 1    | |                          | SAU: region 2    | |
| IDAU: Non-secure | |                          | IDAU: Non-secure | |
~                  ~ |                          ~                  ~ |
|                  | |                          |                  | |
| 010080000H       | V                    SRAM4 | 020040000H       | V
+------------------+ -                          +------------------+ -
| 01007FFFFH       | ^                    SRAM3 | 02003FFFFH       | ^
| NSC              | |                          |                  | |
| SAU: region 0    | |                          | Secure           | |
| IDAU: Non-secure | |                          | SAU: no region   | |
| 01007E000H       | V                          | IDAU: Non-secure | |
+------------------+ -                          ~                  ~ |
| 01007DFFFH       | ^                          |                  | |
|                  | |                    SRAM0 | 020000000H       | V
| Secure           | |                          +------------------+ -
| SAU: no region   | |
| IDAU: Non-secure | |
~                  ~ |
|                  | |
| 010000100H       | V
+------------------+ -

SRAM is fully open after reset; the SAU regions configure the security split.
Lower physical addresses are at the bottom of the figure.
No SAU region means Secure.

Flash Partition Layout (SecurePart variant)

In the SecurePart variant, the Secure and Non-secure images live in separate flash partitions. The Secure partition is mapped to its execution addresses automatically by the bootrom; the Non-secure partition is mapped by Secure.InstallNonSecImage, which configures QMI address translation (pane 1) at start-up. The result is that the execution addresses on the right are not the physical flash addresses on the left.

flash storage                              execution addresses (via QMI)

                                           +------------------+ -
                                           | 01047FFFFH       | ^
                                           |                  | |
                                           |                  | |
                                           | Non-secure       | |
                                           | code             | |
                                           | SAU: region 1    | |
                                           | IDAU: Non-secure | |
                                           ~                  ~ |
                                           |                  | |
+------------------+                       | 010400000H       | V
|                  | --- QMI pane 1 -----> +------------------+ -
| Non-secure       |
| partition        |
| 512K             |
| rp2350-arm-ns    |
|                  |
+------------------+                       +------------------+ -
| Secure partition |                       | 01007FFFFH       | ^
| 512K             |                       | NSC veneers      | |
| rp2350-arm-s     |                       | SAU: region 0    | |
|                  |                       | IDAU: Non-secure | |
|                  |                       | 01007E000H       | V
|                  |                       +------------------+ -
|                  | --- bootrom --------> | 01007DFFFH       | ^
|                  |                       |                  | |
+------------------+                       | Secure code      | |
| partition tables |                       | SAU: no region   | |
|    4K + 4K       |                       | IDAU: Non-secure | |
+------------------+                       ~                  ~ |
                                           |                  | |
                                           | 010000100H       | V
                                           +------------------+ -

Lower physical storage addresses are at the bottom of the left-hand figure;
lower execution addresses are at the bottom of the right-hand figure.
  • The SAU and IDAU regions are configured on the execution addresses, not the physical storage addresses. The bootrom maps the full Secure partition (512K) to the contiguous execution range 010000100H to 01007FFFFH, which covers both the Secure code (lower) and the NSC veneers at the top.

  • The two execution ranges are far apart – QMI address translation operates in 4 MB panes, and the Non-secure execution address sits in a different pane from the Secure one. The partition-table slots at the bottom of the storage column hold only the partition definitions; they are not executed.

Further Reading

For the development history and a deeper walk-through of the underlying mechanics, see the experimental write-ups Secure/Non-secure Part 3 (the bootrom-dispatcher prototype that RTK does not use; documents why) and Secure/Non-secure Part 4 (an earlier veneer-based prototype with three flash partitions; today's SecurePart uses two, with NSC co-located with the Secure code).

References

Repository

  • <repo>/examples/v3.1/rpi/pico2/Secure
  • <repo>/examples/v3.1/rpi/pico2/SecurePart

Last updated: 30 May 2026