Changing to Nonsecure State
We got Secure State running in the previous chapter. But we also want to run a different application in Nonsecure State at the same time.
Your tasks are to:
- Call the BSP function to program the SAU.
- Call the BSP function to program the Global TrustZone Controller’s Memory Protection Controller for SRAM3
- Use the
cortex-mlibrary function to bootstrap the Nonsecure State application - Hand the GPIO for the Green LED over the Nonsecure State so it can blink it
Once we’ve done that, we can load the nonsecure-app and hopefully the
secure-loader will boot and then jump to the nonsecure-app.
The nonsecure-app sees the world like this:
| Description | Start Address | End Address | IDAU | SAU | Contains |
|---|---|---|---|---|---|
| Flash Bank 1 | 0x0800_0000 | 0x081F_FFFF | Nonsecure | — | Hidden |
| Flash Bank 2 | 0x0820_0000 | 0x083F_FFFF | Nonsecure | Region 0, Nonsecure | Code |
| SRAM1 | 0x2000_0000 | 0x200B_FFFF | Nonsecure | — | Hidden |
| SRAM3 | 0x200D_0000 | 0x201A_FFFF | Nonsecure | Region 1, Nonsecure | Data and Stack |
| Peripherals | 0x4000_0000 | 0x4FFF_FFFF | Nonsecure | Region 2, Nonsecure | Peripherals |
The secure-loader sees the world like this:
| Description | Start Address | End Address | IDAU | SAU | Contains |
|---|---|---|---|---|---|
| Flash Bank 1 | 0x0800_0000 | 0x081F_FFFF | Nonsecure | — | Alias of Loader Code |
| Flash Bank 2 | 0x0820_0000 | 0x083F_FFFF | Nonsecure | Region 0, Nonsecure | App Code |
| Flash Bank 1 | 0x0C00_0000 | 0x0C1F_FFFF | Secure | — | Loader Code |
| Flash Bank 2 | 0x0C20_0000 | 0x0C3F_FFFF | Secure | — | Alias of App Code |
| SRAM1 | 0x2000_0000 | 0x200B_FFFF | Nonsecure | — | Alias of Loader Data and Stack |
| SRAM3 | 0x200D_0000 | 0x201A_FFFF | Nonsecure | Region 1, Nonsecure | App Data and Stack |
| SRAM1 | 0x3000_0000 | 0x300B_FFFF | Secure | — | Loader Data and Stack |
| SRAM3 | 0x300D_0000 | 0x301A_FFFF | Secure | — | Alias of App Data and Stack |
| Peripherals | 0x4000_0000 | 0x4FFF_FFFF | Nonsecure | Region 2, Nonsecure | Peripherals |
| Peripherals | 0x5000_0000 | 0x5FFF_FFFF | Secure | — | Secure Peripherals |
Task 1 - Program the SAU
By default the SAU will say no to anything the Nonsecure State asks for (which is
a very reasonable default). However, our nonsecure-app is going to need some
resources, so we’ll give it:
- All of Flash Bank 2
- All of SRAM3
- All of the Peripherals at
0x4000_0000
To do this, the cortex-m library has a SAU driver we can use. And the BSP has
helpfully given us access to an instance of the SAU driver, as bsp.sau.
We need to call the init function, and pass in a slice of SauRegion objects.
use cortex_m::peripheral::sau::{SauRegion, SauRegionAttribute};
let region0 = SauRegion {
base_address: 0x0820_0000
limit_address: 0x083F_FFFF,
attribute: SauRegionAttribute::NonSecure,
}
Set up the three regions we need, and pass them in a slice to the
bsp.sau.init() function.
Hint
There’s a function in the BSP that will do this for you.
Task 2 - Program the Global TrustZone Controller’s Memory Protection Controller
The STM32U5 has a separate device for controlling which parts of which SRAM are available to Nonsecure State. This device can apportion memory on a 512-byte block by block basis, which seems overkill. We just want to leave SRAM1 for Secure State, and give all of SRAM3 to Nonsecure State. As SRAM3 is 832 KiB in length, that’s 1664 blocks we have to individually flip over to Nonsecure State.
The GTZC MPCBB peripheral does this with a whole bunch of 32-bit registers, where each register controls 32 blocks (1 bit per block). SRAM3 therefore has 52 registers we need to hit.
Luckily for you, I wrote a driver for this, you can you just call
bsp.gztc.map_addresses_nonsecure and give it the address range for SRAM3. It
will then hit all the right bits in all the right registers for you.
You may also want to call bsp.gztc.allow_secure_read_write to tell it that
although SRAM3 is given over to Nonsecure State, Secure State would still like to
read it. Technically you don’t need this to boot the application, but if you later
want to receive pointers through a Nonsecure Callable API and then read the data
they point at, you’ll need this enabled.
Hint
There’s a function in the BSP that will do this for you.
Task 3 - Bootstrap the Nonsecure State
Nonsecure State and Secure State have their own copies of some important things:
- They have their own Nested Vector Interrupt Controller (NVIC), which is a
memory-mapped peripheral at
0xE000_E100 - They have their own System Control Block (SCB), which is a memory-mapped
peripheral at
0xE000_ED04 - They have their own Main Stack Pointers, which is a system register called
MSP, or justSP
These things have the same name regardless of which world you are in - MSP is
the Secure Main Stack Pointer when you are Secure State, and is the nonsecure
Main Stack Pointer when you are in Nonsecure State.
But, Secure State also gets access to the Nonsecure State values as well, through different names.
- The nonsecure MSP is available as system register called
MSP_NS - The nonsecure NVIC is available at
0xE002_E100 - The nonsecure SCB is available at
0xE002_ED04
Think for a moment about whether this should work the other way around - should Nonsecure State have access to the Secure State peripherals?
Answer
No!
The point of TrustZone is to keep things in Secure State (like encryption keys) a secret from Nonsecure State (which might get hacked when processing untrusted input).
Secure State is more trusted, and has more permissions that Nonsecure State.
To actually enter Nonsecure world, we need to:
- Set Nonsecure State’s Vector Table Offset Register to where the code lives in flash, so that interrupts and exceptions work
- Set Nonsecure State’s Main Stack Pointer, so it has a stack to use
- Zero all the CPU registers so we don’t leak any secrets to Nonsecure State
- Execute the
BXNSinstruction, with the address of the Nonsecure reset function
Handily for us, this has been added to the cortex-m library. All you need to
do is:
let ns_app_base = ....; // some number goes here
unsafe {
cortex_m::asm::bootload_ns(ns_app_base as *const u32, bsp.scb_ns);
}
As an aside, we’re using a fork of the cortex-m crate that adds access to some
of these Nonsecure peripherals. So don’t expect the documentation at
https://docs.rs/cortex-m to be that helpful here. You’ll need to look at
https://github.com/rust-embedded/cortex-m/tree/jp/release-cm-and-cmrt/cortex-m
for the time being.
The bootload_ns function needs access to the Nonsecure System Control Base
peripheral, but the BSP has provided that so we can just hand it over. Feel free
to “Go to Definition” and dig into what this function is doing.
The bootload_ns function is also unsafe - why is that? What rules does the
human reviewer need to check before we use that function?
Bonus question (hard-mode): Can you find a way that information might be leaked from Secure State to Nonsecure State through this function? I hope not, but maybe there’s a bug!
Some nonsecure firmware
If you run your program now, you probably end up in the Secure Fault handler.
$ cargo run --bin secure-loader
Compiling nucleo-u5a5zj-bsp v0.1.0 (/Users/jonathan/Documents/ferrous-systems/rust-exercises/stm32-code/nucleo-u5a5zj-bsp)
Compiling secure-loader v0.1.0 (/Users/jonathan/Documents/ferrous-systems/rust-exercises/stm32-code/secure-loader)
Finished `dev` profile [optimized + debuginfo] target(s) in 0.42s
Running `probe-rs run --chip STM32U5A5ZJ target/thumbv8m.main-none-eabi/debug/secure-loader`
Erasing ✔ 100% [####################] 24.00 KiB @ 179.30 KiB/s (took 0s)
Programming ✔ 100% [####################] 14.00 KiB @ 27.27 KiB/s (took 1s)
Finished in 0.75s
Hello, this is secure-loader. Configuring peripherals...
...SAU configured
...GTZC1 configured
...LEDs configured
Booting NS application at 0x08200000...
SECURE FAULT:
- SFAR = fffffff0
- Invalid Entry Point
- Attribution Unit Violation
The fffffff0 value is suspicious - it’s like 0xFFFF_FFFF rounded down, and
0xFFFF_FFFF is what blank flash reads as.
That’s because we haven’t loaded a Nonsecure State binary! Let’s do that.
$ cargo run --bin nonsecure-app
Compiling nonsecure-app v0.1.0 (/Users/jonathan/Documents/ferrous-systems/rust-exercises/stm32-code/nonsecure-app)
Finished `dev` profile [optimized + debuginfo] target(s) in 0.15s
Running `probe-rs run --chip STM32U5A5ZJ target/thumbv8m.main-none-eabi/debug/nonsecure-app`
Erasing ✔ 100% [####################] 16.00 KiB @ 143.76 KiB/s (took 0s)
Programming ✔ 100% [####################] 9.00 KiB @ 25.61 KiB/s (took 0s)
Finished in 0.57s
Hello, this is secure-loader. Configuring peripherals...
...SAU configured
...GTZC1 configured
...LEDs configured
Booting NS application at 0x08200000...
[INFO ] Hello, this is nonsecure-app!
Note how even though we just loaded nonsecure-app, the secure-loader program
still ran first. But it bounced us into Nonsecure State and our nonsecure
application is happily running.
Well done!
As an aside, the nonsecure-app is using defmt over SEGGER RTT for the text
output, which is much faster than semihosting. The trade-off is that if you
re-run the secure-loader program to get a new version on to the board, you
will not see any defmt output from the nonsecure side. This is because
probe-rs can only show you defmt output from the program you are loading
right now and not some random program you loaded half an hour ago. If you are
running secure-loader, you cannot see defmt logs from nonsecure-app. That’s
the price you pay for defmt being so fast.
It’s also why secure-loader is set to use the much slower semihosting output,
so we can always see it regardless of which app we’ve just flashed.
Task 4
The Nonsecure state app is trying to blink the LED but nothing is happening. This is because the GPIO peripheral is TrustZone-aware, and it knows the GPIO for the Green LED is still associated with Secure state.
To fix this, use the make_nonsecure method on the Green LED variable in the secure-loader.
Remember, you’ll need to reflash the secure-loader when you’ve made that change but, you
probably won’t need to change the nonsecure-app - it should just start blinking away!
Solution
Simply add this code to fn main() for secure-loader (after you’ve been given
the board):
// We give Green to Nonsecure State
_ = board.green_ld1.make_nonsecure(&mut board.gpio);
The API gives us an object back representing access to the LED from Nonsecure
state, but as we don’t want the secure-loader playing with the LED we just
discard the value by assigning to _.
The API takes an exclusive reference to the GPIO peripheral to ensure that no-one else touches the registers it needs whilst it does a read-modify-write cycle on them.