From 3355d4e9abb36134fc2aeba8768689a5e5cfc870 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Tue, 11 Nov 2025 14:49:15 +0100 Subject: [PATCH 01/12] Initial work on rt685s-trustzone example --- bootloader-tool/src/commands/run.rs | 1 + bootloader-tool/src/commands/sign.rs | 65 ++- examples/rt685s-trustzone/.cargo/config.toml | 18 + examples/rt685s-trustzone/.gitignore | 3 + examples/rt685s-trustzone/Cargo.toml | 38 ++ .../application-nonsecure/Cargo.toml | 26 + .../application-nonsecure/build.rs | 27 + .../application-nonsecure/memory.x | 14 + .../application-nonsecure/rustfmt.toml | 3 + .../src/bin/ahb_ctrl_violation.rs | 21 + .../src/bin/cpu_violation.rs | 18 + .../src/bin/dma_violation_read.rs | 41 ++ .../src/bin/dma_violation_write.rs | 40 ++ .../src/bin/otp_violation.rs | 21 + .../src/bin/rom_violation.rs | 22 + .../src/bin/sau_violation.rs | 32 ++ .../src/bin/secure_function.rs | 18 + .../src/bin/shadow_violation.rs | 21 + .../application-nonsecure/src/lib.rs | 47 ++ .../application-secure/Cargo.toml | 17 + .../application-secure/build.rs | 54 ++ .../application-secure/memory.x | 18 + .../application-secure/rustfmt.toml | 3 + .../application-secure/src/main.rs | 518 ++++++++++++++++++ .../rt685s-trustzone/bootloader/Cargo.toml | 52 ++ examples/rt685s-trustzone/bootloader/build.rs | 46 ++ examples/rt685s-trustzone/bootloader/memory.x | 34 ++ .../rt685s-trustzone/bootloader/src/main.rs | 53 ++ examples/rt685s-trustzone/bsp/Cargo.toml | 14 + .../rt685s-trustzone/bsp/src/ext-flash.toml | 16 + examples/rt685s-trustzone/bsp/src/lib.rs | 21 + examples/rt685s-trustzone/ci.sh | 31 ++ examples/rt685s-trustzone/rust-toolchain.toml | 4 + 33 files changed, 1338 insertions(+), 19 deletions(-) create mode 100644 examples/rt685s-trustzone/.cargo/config.toml create mode 100644 examples/rt685s-trustzone/.gitignore create mode 100644 examples/rt685s-trustzone/Cargo.toml create mode 100644 examples/rt685s-trustzone/application-nonsecure/Cargo.toml create mode 100644 examples/rt685s-trustzone/application-nonsecure/build.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/memory.x create mode 100644 examples/rt685s-trustzone/application-nonsecure/rustfmt.toml create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/ahb_ctrl_violation.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/cpu_violation.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_read.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_write.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/otp_violation.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/rom_violation.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/sau_violation.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/secure_function.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/bin/shadow_violation.rs create mode 100644 examples/rt685s-trustzone/application-nonsecure/src/lib.rs create mode 100644 examples/rt685s-trustzone/application-secure/Cargo.toml create mode 100644 examples/rt685s-trustzone/application-secure/build.rs create mode 100644 examples/rt685s-trustzone/application-secure/memory.x create mode 100644 examples/rt685s-trustzone/application-secure/rustfmt.toml create mode 100644 examples/rt685s-trustzone/application-secure/src/main.rs create mode 100644 examples/rt685s-trustzone/bootloader/Cargo.toml create mode 100644 examples/rt685s-trustzone/bootloader/build.rs create mode 100644 examples/rt685s-trustzone/bootloader/memory.x create mode 100644 examples/rt685s-trustzone/bootloader/src/main.rs create mode 100644 examples/rt685s-trustzone/bsp/Cargo.toml create mode 100644 examples/rt685s-trustzone/bsp/src/ext-flash.toml create mode 100644 examples/rt685s-trustzone/bsp/src/lib.rs create mode 100755 examples/rt685s-trustzone/ci.sh create mode 100644 examples/rt685s-trustzone/rust-toolchain.toml diff --git a/bootloader-tool/src/commands/run.rs b/bootloader-tool/src/commands/run.rs index 588d764..32e52d8 100644 --- a/bootloader-tool/src/commands/run.rs +++ b/bootloader-tool/src/commands/run.rs @@ -54,6 +54,7 @@ pub async fn process(config: &Config, command: RunCommands) -> anyhow::Result<() let mut command = std::process::Command::new(&run_args.probe_rs_path); command.args(["attach", "--chip", &run_args.probe_args.chip]); + command.args(["--no-catch-hardfault", "--no-catch-reset"]); if let Some(probe) = run_args.probe_args.probe.as_ref() { command.args(["--probe", probe]); diff --git a/bootloader-tool/src/commands/sign.rs b/bootloader-tool/src/commands/sign.rs index a2d2aab..17005b1 100644 --- a/bootloader-tool/src/commands/sign.rs +++ b/bootloader-tool/src/commands/sign.rs @@ -15,6 +15,51 @@ pub struct SignOutput { pub rkth: Rkth, } +fn perform_checks(config: &Config, is_bootloader: bool, image: &Vec, base_addr: u32) -> anyhow::Result<()> { + struct Values { + run_start: u64, + max_size: u64 + } + + let values = if is_bootloader { + let Some(bootloader) = &config.bootloader else { + return Err(anyhow::anyhow!("Bootloader field not set in config")) + }; + + Values { + run_start: bootloader.run_start, + max_size: bootloader.max_size + } + + } else { + let Some(application) = &config.application else { + return Err(anyhow::anyhow!("Application field not set in config")) + }; + + Values { + run_start: application.run_start, + max_size: application.slot_size + } + }; + + if values.run_start != base_addr as u64 { + return Err(anyhow::anyhow!( + "Image will be run from unexpected address 0x{:x}, should be 0x{:x}", + base_addr, + values.run_start + )); + } + + if values.max_size < image.len() as u64 { + return Err(anyhow::anyhow!( + "Image can not fit in 0x{:x}, actual size is 0x{:x}", + values.max_size, image.len(), + )); + } + + Ok(()) +} + pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result { let (is_bootloader, args) = match command { SignCommands::Bootloader(sign_arguments) => (true, sign_arguments), @@ -35,25 +80,7 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result SHAREDRTT +} diff --git a/examples/rt685s-trustzone/application-nonsecure/rustfmt.toml b/examples/rt685s-trustzone/application-nonsecure/rustfmt.toml new file mode 100644 index 0000000..9eb3c3b --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +max_width = 120 diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/ahb_ctrl_violation.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/ahb_ctrl_violation.rs new file mode 100644 index 0000000..30311f4 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/ahb_ctrl_violation.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_imxrt::pac::AhbSecureCtrl; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, _cp) = nonsecure_app::init(); + + rprintln!("Trying to get CPU access to AHB bus security configuration (should fail with BusFault!)"); + // Is mapped to NonSecure memory in SAU, but PPC should block access. + // Try out alias rule3 (+0x3000), as some configurations set that to non-secure + // that catch writes as an hypervisor interrupt. + let ahb_secure_ctrl = unsafe { &*AhbSecureCtrl::ptr().byte_add(0x3000) }; + rprintln!("buf: {:#010X}", ahb_secure_ctrl.misc_ctrl_reg().read().bits()); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/cpu_violation.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/cpu_violation.rs new file mode 100644 index 0000000..2517a5e --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/cpu_violation.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use nonsecure_app::SECURE_MEMORY; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, _cp) = nonsecure_app::init(); + + rprintln!("Trying to get CPU access to secure memory (should fail with SecureFault!)"); + let buf = unsafe { SECURE_MEMORY.read_volatile() }; + rprintln!("buf: {:X?}", buf); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_read.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_read.rs new file mode 100644 index 0000000..32f8b20 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_read.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_imxrt::dma::transfer::TransferOptions; +use embassy_imxrt::interrupt::Interrupt; +use nonsecure_app::SECURE_MEMORY; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (p, mut cp) = nonsecure_app::init(); + + // Make DMA0 lower priority than SECUREVIOLATION. + unsafe { + cp.NVIC.set_priority(Interrupt::DMA0, 32); + } + + let channel = embassy_imxrt::dma::Dma::reserve_channel(p.DMA0_CH0).unwrap(); + + let mut buf = 0u32; + + rprintln!("Trying to get DMA access to secure memory (should fail with SECUREVIOLATION (ns)!)"); + let mut options = TransferOptions::default(); + options.width = embassy_imxrt::dma::transfer::Width::Bit32; + options.priority = embassy_imxrt::dma::transfer::Priority::Priority0; + + embassy_imxrt::dma::transfer::Transfer::new_raw_transfer( + &channel, + embassy_imxrt::dma::transfer::Direction::MemoryToMemory, + SECURE_MEMORY, + &raw mut buf, + core::mem::size_of_val(&buf), + options, + ) + .await; + rprintln!("buf: {:X?}", buf); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_write.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_write.rs new file mode 100644 index 0000000..56a862f --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/dma_violation_write.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_imxrt::dma::transfer::TransferOptions; +use embassy_imxrt::interrupt::Interrupt; +use nonsecure_app::SECURE_MEMORY; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (p, mut cp) = nonsecure_app::init(); + + // Make DMA0 lower priority than SECUREVIOLATION. + unsafe { + cp.NVIC.set_priority(Interrupt::DMA0, 32); + } + + let channel = embassy_imxrt::dma::Dma::reserve_channel(p.DMA0_CH0).unwrap(); + + let buf = 0x1337u32; + + rprintln!("Trying to get DMA access to secure memory (should fail with SECUREVIOLATION (ns)!)"); + let mut options = TransferOptions::default(); + options.width = embassy_imxrt::dma::transfer::Width::Bit32; + options.priority = embassy_imxrt::dma::transfer::Priority::Priority0; + + embassy_imxrt::dma::transfer::Transfer::new_raw_transfer( + &channel, + embassy_imxrt::dma::transfer::Direction::MemoryToMemory, + &buf, + SECURE_MEMORY, + core::mem::size_of_val(&buf), + options, + ) + .await; + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/otp_violation.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/otp_violation.rs new file mode 100644 index 0000000..e623666 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/otp_violation.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, _cp) = nonsecure_app::init(); + + // Try to access the (undocumented) OTP peripheral address range. + // Should be blocked using aips_bridge1_mem_rule0. + let ptr = 0x40130824 as *const u32; // OTP_STATUS + + rprintln!("Trying to get CPU access to OTP peripheral register (should fail with BusFault!)"); + let buf = unsafe { ptr.read_volatile() }; + rprintln!("buf: {:X?}", buf); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/rom_violation.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/rom_violation.rs new file mode 100644 index 0000000..8e3e8a2 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/rom_violation.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, _cp) = nonsecure_app::init(); + + // ROM region for OTP driver. + // SAU does not have this region configured as NonSecure, + // and we have configured the MPC to emit a BusFault. + let ptr = 0x1303F020 as *const u32; + + rprintln!("Trying to get CPU access to ROM memory (should fail with SecureFault!)"); + let buf = unsafe { ptr.read_volatile() }; + rprintln!("buf: {:X?}", buf); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/sau_violation.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/sau_violation.rs new file mode 100644 index 0000000..e1c3f3b --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/sau_violation.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use cortex_m::peripheral::sau::{SauRegion, SauRegionAttribute}; +use embassy_executor::Spawner; +use nonsecure_app::SECURE_MEMORY; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, mut cp) = nonsecure_app::init(); + + rprintln!("Trying to reconfigure SAU to give access to secure memory (is RAZ/WI, should not do anything)"); + assert_eq!(cp.SAU.region_numbers(), 0); // RAZ/WI + if let Err(e) = cp.SAU.set_region( + 5, + SauRegion { + base_address: SECURE_MEMORY as u32, + limit_address: SECURE_MEMORY as u32 + 3, + attribute: SauRegionAttribute::NonSecure, + }, + ) { + rprintln!("Failed to set: {:?}", e); // As region number is RAZ/WI. + } + + rprintln!("Trying to get CPU access to secure memory (should fail with SecureFault!)"); + let buf = unsafe { SECURE_MEMORY.read_volatile() }; + rprintln!("buf: {:X?}", buf); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/secure_function.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/secure_function.rs new file mode 100644 index 0000000..4f226cd --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/secure_function.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, _cp) = nonsecure_app::init(); + rprintln!("Test whether we can call a secure function (with success)"); + + loop { + rprintln!("Calling secure function: {}", nonsecure_app::do_stuff_secure(5)); + Timer::after_secs(1).await; + } +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/bin/shadow_violation.rs b/examples/rt685s-trustzone/application-nonsecure/src/bin/shadow_violation.rs new file mode 100644 index 0000000..3fd7bc5 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/bin/shadow_violation.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use panic_probe as _; +use rtt_target::rprintln; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let (_p, _cp) = nonsecure_app::init(); + + // Shadow registers are protected using MPC over the aips_bridge1_mem_rule0 register. + // In SAU this region is mapped to NonSecure. + let ptr = 0x40130000 as *const u32; + + rprintln!("Trying to get CPU access to OTP shadow register memory (should fail with BusFault!)"); + let buf = unsafe { ptr.read_volatile() }; + rprintln!("buf: {:X?}", buf); + + rprintln!("!!! SHOULD NOT BE REACHABLE !!!"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/src/lib.rs b/examples/rt685s-trustzone/application-nonsecure/src/lib.rs new file mode 100644 index 0000000..1251f9a --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/src/lib.rs @@ -0,0 +1,47 @@ +#![no_std] + +use core::mem::MaybeUninit; + +use embassy_imxrt::interrupt::Interrupt; +use embassy_imxrt::pac::interrupt; +use embassy_imxrt::Peripherals; +use rtt_target::{rprintln, UpChannel}; + +#[repr(C)] +pub struct RttControlBlock { + header: rtt_target::rtt::RttHeader, + up_channels: [rtt_target::rtt::RttChannel; 1], + down_channels: [rtt_target::rtt::RttChannel; 0], +} + +#[used] +#[export_name = "_SEGGER_RTT"] +#[link_section = ".shared_rtt.header"] +pub static mut CONTROL_BLOCK: MaybeUninit = MaybeUninit::uninit(); + +unsafe extern "C" { + pub safe fn do_stuff_secure(num: u32) -> u32; +} + +#[interrupt] +unsafe fn SECUREVIOLATION() { + rprintln!("SECUREVIOLATION! (ns)"); + loop { + cortex_m::asm::nop(); + } +} + +pub fn init() -> (Peripherals, cortex_m::Peripherals) { + unsafe { cortex_m::peripheral::NVIC::unmask(Interrupt::SECUREVIOLATION) }; + + let p = embassy_imxrt::init(Default::default()); + let cp = cortex_m::Peripherals::take().unwrap(); + + rtt_target::set_print_channel(unsafe { UpChannel::conjure(0) }.unwrap()); + rtt_target::rprintln!("Hello world"); + + (p, cp) +} + +/// Some location in secure memory that we should not be able to touch. +pub const SECURE_MEMORY: *mut u32 = 0x20000000 as *mut u32; diff --git a/examples/rt685s-trustzone/application-secure/Cargo.toml b/examples/rt685s-trustzone/application-secure/Cargo.toml new file mode 100644 index 0000000..3d1513e --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "secure-app" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +cortex-m = { version = "0.7.7", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7.3" +mimxrt685s-pac = { version = "0.5.0", features = [ + "rt", + "critical-section", +] } +rtt-target = "0.6.1" diff --git a/examples/rt685s-trustzone/application-secure/build.rs b/examples/rt685s-trustzone/application-secure/build.rs new file mode 100644 index 0000000..1ede79b --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/build.rs @@ -0,0 +1,54 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + let major = env!("CARGO_PKG_VERSION_MAJOR") + .parse::() + .expect("should have major version"); + + let minor = env!("CARGO_PKG_VERSION_MINOR") + .parse::() + .expect("should have minor version"); + + let inv_major = !major; + let inv_minor = !minor; + + // Inject crate version into the .biv section. + File::create(out.join("biv.rs")) + .unwrap() + .write_all( + format!( + r##" +#[link_section = ".biv"] +#[used] +static BOOT_IMAGE_VERSION: u32 = 0x{:02x}{:02x}{:02x}{:02x}; +"##, + inv_major, inv_minor, major, minor, + ) + .as_bytes(), + ) + .unwrap(); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + + println!("cargo:rustc-link-arg=--cmse-implib"); + println!("cargo:rustc-link-arg=--out-implib=target/veneers.o"); +} diff --git a/examples/rt685s-trustzone/application-secure/memory.x b/examples/rt685s-trustzone/application-secure/memory.x new file mode 100644 index 0000000..4f2bf37 --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/memory.x @@ -0,0 +1,18 @@ +MEMORY { + FLASH : ORIGIN = 0x10020000, LENGTH = 20K + RAM : ORIGIN = 0x30025000, LENGTH = 4K + SHAREDRTT : ORIGIN = 0x20026000, LENGTH = 2K + # APPLICATION : ORIGIN = 0x20026800 + ROM_TABLE (r) : ORIGIN = 0x1303F000, LENGTH = 64 +} + +SECTIONS { + .shared_rtt (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + KEEP(* (.shared_rtt.header)) + KEEP(* (.shared_rtt.buffer)) + . = ALIGN(4); + } > SHAREDRTT AT>FLASH + . = ALIGN(4); +} diff --git a/examples/rt685s-trustzone/application-secure/rustfmt.toml b/examples/rt685s-trustzone/application-secure/rustfmt.toml new file mode 100644 index 0000000..9eb3c3b --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +max_width = 120 diff --git a/examples/rt685s-trustzone/application-secure/src/main.rs b/examples/rt685s-trustzone/application-secure/src/main.rs new file mode 100644 index 0000000..cf59d11 --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -0,0 +1,518 @@ +#![no_std] +#![no_main] +#![feature(abi_cmse_nonsecure_call)] +#![feature(cmse_nonsecure_entry)] + +use core::ops::{Range, RangeInclusive}; +use core::panic::PanicInfo; +use core::sync::atomic::AtomicU32; + +use cortex_m::peripheral::sau::{SauRegion, SauRegionAttribute}; +use cortex_m::peripheral::SCB; +use mimxrt685s_pac::ahb_secure_ctrl::ram00_rule::Rule0; +use mimxrt685s_pac::ahb_secure_ctrl::{self}; +use mimxrt685s_pac::{interrupt, AhbSecureCtrl, Sau, ScnScb}; +use rtt_target::rprintln; +use rtt_target::ChannelMode::NoBlockSkip; + +// Note: all RAM memory is expressed as being part of the non-secure data RAM range (0x2). +// IDAU other classifications with or without cache are disregarded, +// especially for configuring the RAM MPC. +const SECURE_START_RAM: u32 = 0x2002_0000; // Up to NONSECURE_START_RAM is secure. + +// Note: START_RAM address must also contain the NS IVT. +const NONSECURE_START_RAM: u32 = 0x2002_6000; +const NONSECURE_END_RAM: u32 = 0x2FFF_FFFF; + +const NONSECURE_START_PERIPHERALS: u32 = 0x4000_0000; +const NONSECURE_END_PERIPHERALS: u32 = 0x4FFF_FFFF; + +extern "Rust" { + static __veneer_base: (); + static __veneer_limit: (); +} + +const VTOR_NS: *mut u32 = 0xE002ED08 as *mut u32; + +fn test_security(addr: *mut u32) { + fn opt_if(val: bool, some: T) -> Option { + if val { + Some(some) + } else { + None + } + } + + let res = cortex_m::asm::ttat(addr); + let mrvalid = res >> 16 & 0b1 == 0b1; + let srvalid = res >> 17 & 0b1 == 0b1; + let irvalid = res >> 23 & 0b1 == 0b1; + let mregion = opt_if(mrvalid, res & 0xf); + let sregion = opt_if(srvalid, res >> 8 & 0xf); + let iregion = opt_if(irvalid, res >> 24 & 0xf); + let r = res >> 18 & 0b1 == 0b1; + let rw = res >> 19 & 0b1 == 0b1; + let s = res >> 22 & 0b1 == 0b1; + rprintln!( + "ttat({:#010X}): {:#010X} (mregion: {:?}, sregion {:?}, iregion {:?}, r {}, rw {}, s {})", + addr as u32, + res, + mregion, + sregion, + iregion, + r, + rw, + s + ); + unsafe { rtt_target::UpChannel::conjure(0) }.unwrap().flush(); +} + +// A common failure mode when configuring these security registers is that the changes are ignored, +// without ever throwing a fault or an exception. +// Hence we need to check if the changes persisted. +macro_rules! reg_write_checked { + ($reg:expr, $f:expr) => {{ + let v = $reg.write($f); + assert_eq!(v, $reg.read().bits()); + }}; +} + +macro_rules! reg_modify_checked { + ($reg:expr, $f:expr) => {{ + let v = $reg.modify($f); + assert_eq!(v, $reg.read().bits()); + }}; +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let channels = rtt_target::rtt_init! { + up: { + 0: { // channel number + size: 1024, // buffer size in bytes + mode: NoBlockSkip, // mode (optional, default: NoBlockSkip, see enum ChannelMode) + name: "Terminal", // name (optional, default: no name) + section: ".shared_rtt.buffer" // Buffer linker section (optional, default: no section) + } + } + section_cb: ".shared_rtt.header" // Control block linker section (optional, default: no section) + }; + rtt_target::set_print_channel(channels.up.0); + + let mut cp = cortex_m::Peripherals::take().unwrap(); + let _dp = mimxrt685s_pac::Peripherals::take().unwrap(); + + unsafe { + let [nonsecure_sp, nonsecure_reset] = (NONSECURE_START_RAM as *const [u32; 2]).read_volatile(); + + rprintln!("Running. NS SP: {:#010X}, RV: {:#010X}", nonsecure_sp, nonsecure_reset); + + if nonsecure_sp == u32::MAX || nonsecure_reset == u32::MAX { + loop { + cortex_m::asm::nop(); + } + } + + rprintln!("Setting up regions"); + rprintln!( + "Veneers: {:#010X} .. {:#010X}", + &raw const __veneer_base as u32, + &raw const __veneer_limit as u32 + ); + + let ahb_secure_ctrl = &*(AhbSecureCtrl::ptr().byte_add(0x1000_0000)); + + // Set all regions not used by this program to non-secure. + // This only concerns the CM33 access, not any of the other bus masters like DMA + // All memory regions without a SAU region are 'Secure'. + rprintln!("Set SAU regions"); + let sau = &*Sau::ptr(); + + sau.ctrl().write(|w| w.enable().disabled()); + + let regions: [(RangeInclusive, SauRegionAttribute); 3] = [ + ( + &raw const __veneer_base as u32..=&raw const __veneer_limit as u32 - 1, + SauRegionAttribute::NonSecureCallable, + ), + (NONSECURE_START_RAM..=NONSECURE_END_RAM, SauRegionAttribute::NonSecure), + ( + NONSECURE_START_PERIPHERALS..=NONSECURE_END_PERIPHERALS, + SauRegionAttribute::NonSecure, + ), + ]; + + for (region_i, (range, attribute)) in regions.into_iter().enumerate() { + rprintln!( + "SAU region {}: {:#010X}..={:#010X} to {:?}", + region_i, + range.start(), + range.end(), + attribute + ); + cp.SAU + .set_region( + region_i as u8, + SauRegion { + base_address: *range.start(), + limit_address: *range.end(), + attribute, + }, + ) + .unwrap(); + } + + // Enable SAU, marking appropriate regions as NonSecure and NonSecureCallable. + rprintln!("Enabling SAU"); + cp.SAU.enable(); + + rprintln!("Set ROM to secure"); + for rom_mem in ahb_secure_ctrl.rom_mem_rule_iter() { + reg_write_checked!(rom_mem, |w| { + w.rule0().secure_priv_user_allowed(); + w.rule1().secure_priv_user_allowed(); + w.rule2().secure_priv_user_allowed(); + w.rule3().secure_priv_user_allowed(); + w.rule4().secure_priv_user_allowed(); + w.rule5().secure_priv_user_allowed(); + w.rule6().secure_priv_user_allowed(); + w.rule7().secure_priv_user_allowed() + }); + } + + rprintln!("Set secure RAM to secure"); + set_ram_secure(SECURE_START_RAM..NONSECURE_START_RAM, ahb_secure_ctrl); + + rprintln!("Set FlexSPI memory to non-secure"); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region0_rule(0), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region0_rule(1), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region0_rule(2), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region0_rule(3), |w| w.bits(0)); + + reg_write_checked!(ahb_secure_ctrl.flexspi0_region1_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region2_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region3_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.flexspi0_region4_rule0(), |w| w.bits(0)); + + rprintln!("Set all peripherals to non-secure (except for OTP)"); + reg_write_checked!(ahb_secure_ctrl.pif_hifi4_x_mem_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.apb_grp0_mem_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.apb_grp0_mem_rule1(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.apb_grp1_mem_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.apb_grp1_mem_rule1(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.apb_grp1_mem_rule2(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.ahb_periph0_slave_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.aips_bridge0_mem_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.ahb_periph1_slave_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.aips_bridge1_mem_rule0(), |w| { + w.otp_rule0() + .bits(0b11) + .otp_rule1() + .bits(0b11) + .otp_rule2() + .bits(0b11) + .otp_rule3() + .bits(0b11) + }); + reg_write_checked!(ahb_secure_ctrl.aips_bridge1_mem_rule1(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.ahb_periph2_slave_rule0(), |w| w.bits(0)); + reg_write_checked!(ahb_secure_ctrl.ahb_periph3_slave_rule0(), |w| w.bits(0)); + + rprintln!("Configure security ctrl aliases to secure"); + rtt_target::UpChannel::conjure(0).unwrap().flush(); + + reg_write_checked!(ahb_secure_ctrl.security_ctrl_mem_rule0(), |w| { + w.rule0() + .bits(0b11) + .rule1() + .bits(0b11) + .rule2() + .bits(0b11) + .rule3() + .bits(0b11) + }); + + rprintln!("Setting all bus masters to non-secure"); + reg_write_checked!(ahb_secure_ctrl.master_sec_level(), |w| { + w.dma0_sec() + .enum_ns_np() + .dma1_sec() + .enum_ns_np() + .dsp_sec() + .enum_ns_np() + .powerquad_sec() + .enum_ns_np() + .sdio0_sec() + .enum_ns_np() + .sdio1_sec() + .enum_ns_np() + .master_sec_level_lock() + .blocked() + }); + reg_write_checked!(ahb_secure_ctrl.master_sec_level_anti_pol(), |w| { + w.dma0_sec() + .enum_ns_np() + .dma1_sec() + .enum_ns_np() + .dsp_sec() + .enum_ns_np() + .powerquad_sec() + .enum_ns_np() + .sdio0_sec() + .enum_ns_np() + .sdio1_sec() + .enum_ns_np() + .master_sec_level_anti_pole_lock() + .blocked() + }); + + rprintln!("Configure CM33"); + // [3] Disable SYSRESETREQ from non-secure. + // [13] Use secure BusFault, HardFault and NMI. + // [14] Priority ranges of secure and non-secure exceptions are identical. + cp.SCB + .aircr + .write((cp.SCB.aircr.read() & (0xffff ^ (1 << 3 | 1 << 13 | 1 << 14))) | 0x005FA0000); + + // [3] Set secure mode to normal sleep (contrary to deep sleep) + cp.SCB.scr.modify(|w| w & 0x0FFFFFFF7); + + // [17] Enable Bus Fault + // [18] Enable Usage Fault + // [19] Enable Secure fault + cp.SCB.shcsr.modify(|w| w | (1 << 19 | 1 << 18 | 1 << 17)); + + // [0] Enable non-secure access to CP0 + // [1] Enable non-secure access to CP1 + // [10] Enable non-secure access to CP10 - float-point extension + // [11] Enable non-secure access to CP11 - float-point extension (must be set iff CP10 is set) + let nsacr = 0xE000ED8C as *mut u32; + nsacr.write_volatile(0x00000C03); + + // Unset all Coprocessor Power Control power down bits, enabling the FPU. + let scn_scb = &*ScnScb::ptr(); + scn_scb.cppwr().write(|w| w.bits(0)); + + rprintln!("Masking DSP and GPIO"); + // Lock all security configrations for the DPS and the GPIO masks. + reg_write_checked!(ahb_secure_ctrl.sec_mask_lock(), |w| { + w.sec_dsp_int_lock() + .blocked() + .sec_gpio_mask0_lock() + .blocked() + .sec_gpio_mask1_lock() + .blocked() + .sec_gpio_mask2_lock() + .blocked() + .sec_gpio_mask3_lock() + .blocked() + .sec_gpio_mask4_lock() + .blocked() + .sec_gpio_mask5_lock() + .blocked() + .sec_gpio_mask6_lock() + .blocked() + .sec_gpio_mask7_lock() + .blocked() + }); + + rprintln!("Enabling AHB bus checks"); + rtt_target::UpChannel::conjure(0).unwrap().flush(); + + reg_modify_checked!(ahb_secure_ctrl.misc_ctrl_reg(), |_, w| { + w.enable_ns_priv_check() + .disable() + .enable_secure_checking() + .enable() + .enable_s_priv_check() + .disable() + }); + reg_modify_checked!(ahb_secure_ctrl.misc_ctrl_dp_reg(), |_, w| { + w.enable_ns_priv_check() + .disable() + .enable_secure_checking() + .enable() + .enable_s_priv_check() + .disable() + }); + + rprintln!("Lock secure MPU and VTOR (not SAU)"); + // Cannot lock SAU as we still need to enable it. + // 0x5 region seems to be translated to 0x4 when SAU is already enabled. + reg_modify_checked!(ahb_secure_ctrl.cm33_lock_reg(), |_, w| { + w.lock_s_mpu() + .blocked() + .lock_s_vtor() + .blocked() + .cm33_lock_reg_lock() + .blocked() + }); + + rprintln!("Lock all security CTRL"); + reg_modify_checked!(ahb_secure_ctrl.misc_ctrl_reg(), |_, w| w.write_lock().restricted()); + // Cannot set DP register as we have just locked the entire block. + // reg_modify_checked!(ahb_secure_ctrl.misc_ctrl_dp_reg(), |_, w| w.write_lock().restricted()); + + // Set the nonsecure VTOR. + VTOR_NS.write_volatile(NONSECURE_START_RAM as u32); + + // Set all interrupts to non-secure. + for itns in &cp.NVIC.itns { + itns.write(u32::MAX); + } + + // Set the non-secure stack pointer. + cortex_m::register::msp::write_ns(nonsecure_sp); + + rtt_target::UpChannel::conjure(0).unwrap().flush(); + + // Make sure the new settings take effect immediately: + // https://developer.arm.com/documentation/100235/0100/The-Cortex-M33-Peripherals/Security-Attribution-and--Memory-Protection/Updating-protected-memory-regions + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + + test_security(&raw const __veneer_base as u32 as *mut u32); + test_security(nonsecure_reset as *mut u32); + + rprintln!("Jumping to {:#010X}", nonsecure_reset); + rtt_target::UpChannel::conjure(0).unwrap().flush(); + + // Create the right function pointer to the reset vector. + let nonsecure_reset = + core::mem::transmute::<*const u32, extern "cmse-nonsecure-call" fn()>(nonsecure_reset as *const u32); + + loop { + cortex_m::asm::nop(); + } + // Jump. + nonsecure_reset(); + + cortex_m::asm::udf(); + } +} + +fn set_ram_secure(mut region: Range, ahb_secure_ctrl: &ahb_secure_ctrl::RegisterBlock) { + let address_to_block = |address: u32| { + const BLOCK_SIZE_TABLE: &[(u32, u32, u32)] = &[ + ( + 0x2010_0000, + 8192, + 0x4_0000 / 0x400 + 0x4_0000 / 0x800 + 0x8_0000 / 0x1000, + ), + (0x2008_0000, 4096, 0x4_0000 / 0x400 + 0x4_0000 / 0x800), + (0x2004_0000, 2048, 0x4_0000 / 0x400), + (0x2000_0000, 1024, 0), + ]; + + let (block_start, block_size, previous_blocks) = BLOCK_SIZE_TABLE + .iter() + .find(|(block_address, _, _)| address >= *block_address) + .unwrap(); + + ( + *previous_blocks + (address - *block_start) / *block_size, + *block_start + (address - *block_start) / *block_size * *block_size, + *block_size, + ) + }; + + // Make sure the end of the range is at a boundary + let (_, block_start, _) = address_to_block(region.end); + assert_eq!(region.end, block_start); + + while !region.is_empty() { + let (block_index, block_start, block_size) = address_to_block(region.start); + assert_eq!(region.start, block_start); + + let register_index = block_index / 8; + let rule_index = block_index % 8; + + let base_ptr = ahb_secure_ctrl.ram00_rule(0).as_ptr(); + + rprintln!( + "Ram region {:#010X}..={:#010X} to secure ({}, {})", + block_start, + block_start + block_size - 1, + register_index, + rule_index + ); + + unsafe { rtt_target::UpChannel::conjure(0).unwrap().flush() }; + + unsafe { + let target_register = base_ptr.add(register_index as usize); + let current_val = target_register.read_volatile(); + target_register.write_volatile( + current_val & !(0b11 << (rule_index * 4)) | ((Rule0::SecurePrivUserAllowed as u32) << (rule_index * 4)), + ); + } + + region.start += block_size; + } +} + +#[cortex_m_rt::exception(trampoline = false)] +unsafe fn HardFault() -> ! { + let scb = &*SCB::PTR; + rprintln!("HardFault! (S) - SHCSR: {:#010X}", scb.shcsr.read()); + + loop { + cortex_m::asm::nop(); + } +} + +#[cortex_m_rt::exception] +unsafe fn SecureFault() -> ! { + let sau = &*cortex_m::peripheral::SAU::PTR; + rprintln!( + "SecureFault! (S) - SFSR: {:#010X}, SFAR: {:#010X}", + sau.sfsr.read().0, + sau.sfar.read().0 + ); + loop { + cortex_m::asm::nop(); + } +} + +#[cortex_m_rt::exception] +unsafe fn UsageFault() -> ! { + rprintln!("UsageFault! (S)"); + loop { + cortex_m::asm::nop(); + } +} + +#[cortex_m_rt::exception] +unsafe fn BusFault() -> ! { + let scb = &*SCB::PTR; + rprintln!( + "BusFault! (S) - CFSR: {:#010X}, BFAR: {:#010X}", + scb.cfsr.read(), + scb.bfar.read() + ); + loop { + cortex_m::asm::nop(); + } +} + +#[interrupt] +unsafe fn SECUREVIOLATION() { + rprintln!("SECUREVIOLATION! (S)"); + loop { + cortex_m::asm::nop(); + } +} + +#[panic_handler] +fn panic(i: &PanicInfo) -> ! { + rprintln!("{}", i); + cortex_m::asm::udf(); +} + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +#[unsafe(no_mangle)] +extern "cmse-nonsecure-entry" fn do_stuff_secure(num: u32) -> u32 { + let old = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + num * 2 + old +} diff --git a/examples/rt685s-trustzone/bootloader/Cargo.toml b/examples/rt685s-trustzone/bootloader/Cargo.toml new file mode 100644 index 0000000..123bf7c --- /dev/null +++ b/examples/rt685s-trustzone/bootloader/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "example-bootloader" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[features] +defmt = [ + "dep:defmt", + "dep:defmt-rtt", + "defmt-or-log/defmt", + "ec-slimloader/defmt", + "ec-slimloader-imxrt/defmt", + "embassy-imxrt/defmt", + "embassy-executor/defmt", + "partition-manager/defmt", +] + +non-secure = ["ec-slimloader-imxrt/non-secure"] + +[dependencies] +ec-slimloader = { path = "../../../libs/ec-slimloader", default-features = false } +ec-slimloader-imxrt = { path = "../../../libs/ec-slimloader-imxrt", features = [ + "mimxrt685s-evk", +], default-features = false } + +example-bsp = { path = "../bsp", features = ["bootloader"] } + +heapless = "0.8.0" + +cortex-m = { version = "0.7.7", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = { version = "0.7.3" } + +embassy-imxrt = { workspace = true, features = ["mimxrt685s", "unstable-pac"] } + +embassy-executor = { workspace = true, features = [ + "arch-cortex-m", + "executor-thread", +] } + +embassy-sync = { workspace = true } +partition-manager = { workspace = true, features = ["esa", "macros"] } + +defmt = { workspace = true, optional = true } +defmt-or-log = { workspace = true } +defmt-rtt = { workspace = true, optional = true } + +panic-probe = "*" diff --git a/examples/rt685s-trustzone/bootloader/build.rs b/examples/rt685s-trustzone/bootloader/build.rs new file mode 100644 index 0000000..9efb58b --- /dev/null +++ b/examples/rt685s-trustzone/bootloader/build.rs @@ -0,0 +1,46 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put corresponding linker script in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rustc-link-arg=-Tlink.x"); + println!("cargo:rustc-link-arg=--nmagic"); + + #[cfg(feature = "defmt")] + println!("cargo:rustc-link-arg=-Tdefmt.x"); + + println!("cargo:rerun-if-changed=memory.x"); + + // Inject crate version into the .biv section. + File::create(out.join("biv.rs")) + .unwrap() + .write_all( + format!( + r##" +#[link_section = ".biv"] +#[used] +static BOOT_IMAGE_VERSION: u32 = 0x{:02x}{:02x}{:02x}00; +"##, + env!("CARGO_PKG_VERSION_MAJOR") + .parse::() + .expect("should have major version"), + env!("CARGO_PKG_VERSION_MINOR") + .parse::() + .expect("should have minor version"), + env!("CARGO_PKG_VERSION_PATCH") + .parse::() + .expect("should have patch version"), + ) + .as_bytes(), + ) + .unwrap(); +} diff --git a/examples/rt685s-trustzone/bootloader/memory.x b/examples/rt685s-trustzone/bootloader/memory.x new file mode 100644 index 0000000..c30eec0 --- /dev/null +++ b/examples/rt685s-trustzone/bootloader/memory.x @@ -0,0 +1,34 @@ +MEMORY { + PRELUDE_OTFAD : ORIGIN = 0x08000000, LENGTH = 256 + PRELUDE_FCB : ORIGIN = 0x08000400, LENGTH = 512 + PRELUDE_BIV : ORIGIN = 0x08000600, LENGTH = 4 + + RAM : ORIGIN = 0x30176000, LENGTH = 20K + FLASH : ORIGIN = 0x10170000, LENGTH = 24K /* running in xip mode, in RAM */ + ROM_TABLE (r) : ORIGIN = 0x1303F000, LENGTH = 64 +} + +SECTIONS { + .otfad : { + . = ALIGN(4); + KEEP(* (.otfad)) + . = ALIGN(4); + } > PRELUDE_OTFAD + + .fcb : { + . = ALIGN(4); + KEEP(* (.fcb)) + . = ALIGN(4); + } > PRELUDE_FCB + + .biv : { + . = ALIGN(4); + KEEP(* (.biv)) + . = ALIGN(4); + } > PRELUDE_BIV + + .rom_table ORIGIN(ROM_TABLE) (NOLOAD): { + API_TABLE = .; + . += LENGTH(ROM_TABLE); + } > ROM_TABLE +} INSERT AFTER .uninit; diff --git a/examples/rt685s-trustzone/bootloader/src/main.rs b/examples/rt685s-trustzone/bootloader/src/main.rs new file mode 100644 index 0000000..2acf72c --- /dev/null +++ b/examples/rt685s-trustzone/bootloader/src/main.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use ec_slimloader_imxrt::{ExternalStorage, Partitions}; +use embassy_executor::Spawner; +use example_bsp::bootloader::{ExternalStorageConfig, ExternalStorageMap}; +use heapless::Vec; +use panic_probe as _; + +// auto-generated version information from Cargo.toml +include!(concat!(env!("OUT_DIR"), "/biv.rs")); + +struct Config; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TooManySlots; + +const JOURNAL_BUFFER_SIZE: usize = 4096; + +impl ec_slimloader_imxrt::ImxrtConfig for Config { + const SLOT_SIZE_RANGE: core::ops::Range = 64..1024 * 1024; + const LOAD_RANGE: core::ops::Range<*mut u32> = (0x1002_0000 as *mut u32)..0x1018_0000 as *mut u32; + + fn partitions( + &self, + flash: &'static mut partition_manager::PartitionManager< + ExternalStorage, + embassy_sync::blocking_mutex::raw::NoopRawMutex, + >, + ) -> Partitions { + let ExternalStorageMap { + app_slot0, + app_slot1, + bl_state, + } = flash.map(ExternalStorageConfig::new()); + + let mut slots = Vec::new(); + defmt_or_log::unwrap!(slots.push(app_slot0).map_err(|_| TooManySlots)); + defmt_or_log::unwrap!(slots.push(app_slot1).map_err(|_| TooManySlots)); + + Partitions { state: bl_state, slots } + } +} + +impl ec_slimloader::BootStatePolicy for Config {} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + ec_slimloader::start::, JOURNAL_BUFFER_SIZE>(Config).await +} diff --git a/examples/rt685s-trustzone/bsp/Cargo.toml b/examples/rt685s-trustzone/bsp/Cargo.toml new file mode 100644 index 0000000..2a33d7c --- /dev/null +++ b/examples/rt685s-trustzone/bsp/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-bsp" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +partition-manager = { workspace = true, features = ["esa", "macros"] } +embassy-sync = { workspace = true } + +[features] +application = [] +bootloader = [] diff --git a/examples/rt685s-trustzone/bsp/src/ext-flash.toml b/examples/rt685s-trustzone/bsp/src/ext-flash.toml new file mode 100644 index 0000000..6c64f07 --- /dev/null +++ b/examples/rt685s-trustzone/bsp/src/ext-flash.toml @@ -0,0 +1,16 @@ +variants = ["bootloader", "application"] + +[disk] +size = 0x800000 +alignment = 0x1000 + +[partitions] +# prelude = { offset = 0x0, size = 0x1000 } +# bl_code = { offset = 0x1000, size = 0x8000 } +# bl_descriptors = { offset = 0x9000, size = 0x1000 } +# bl_mbi_suffix = { offset = 0xa000, size = 0x1000 } + +bl_state = { offset = 0xb000, size = 0x2000, access = { application = "rw", bootloader = "rw" } } + +app_slot0 = { offset = 0x0d000, size = 0xec000, access = { application = "rw", bootloader = "ro" } } +app_slot1 = { offset = 0xf9000, size = 0xec000, access = { application = "rw", bootloader = "ro" } } diff --git a/examples/rt685s-trustzone/bsp/src/lib.rs b/examples/rt685s-trustzone/bsp/src/lib.rs new file mode 100644 index 0000000..e81225e --- /dev/null +++ b/examples/rt685s-trustzone/bsp/src/lib.rs @@ -0,0 +1,21 @@ +#![no_std] + +#[cfg(feature = "bootloader")] +pub mod bootloader { + partition_manager::macros::create_partition_map!( + name: ExternalStorageConfig, + map_name: ExternalStorageMap, + variant: "bootloader", + manifest: "src/ext-flash.toml" + ); +} + +#[cfg(feature = "application")] +pub mod application { + partition_manager::macros::create_partition_map!( + name: ExternalStorageConfig, + map_name: ExternalStorageMap, + variant: "application", + manifest: "src/ext-flash.toml" + ); +} diff --git a/examples/rt685s-trustzone/ci.sh b/examples/rt685s-trustzone/ci.sh new file mode 100755 index 0000000..a8948cb --- /dev/null +++ b/examples/rt685s-trustzone/ci.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -eo pipefail + +if ! command -v cargo-batch &> /dev/null; then + echo "cargo-batch could not be found. Install it with the following command:" + echo "" + echo " cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked" + echo "" + exit 1 +fi + +export RUSTFLAGS=-Dwarnings +export DEFMT_LOG=trace +if [[ -z "${CARGO_TARGET_DIR}" ]]; then + export CARGO_TARGET_DIR=target_ci +fi + +TARGET="thumbv8m.main-none-eabihf" + +BUILD_EXTRA="" + +FEATURE_COMBINATIONS=( + "defmt" + "non-secure" +) +cargo batch \ + $(for features in "${FEATURE_COMBINATIONS[@]}"; do + echo "--- build --release --manifest-path Cargo.toml --target thumbv8m.main-none-eabihf " + echo "--- build --release --manifest-path Cargo.toml --target thumbv8m.main-none-eabihf --features $features " + done) $BUILD_EXTRA diff --git a/examples/rt685s-trustzone/rust-toolchain.toml b/examples/rt685s-trustzone/rust-toolchain.toml new file mode 100644 index 0000000..b269a36 --- /dev/null +++ b/examples/rt685s-trustzone/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2025-09-27" +targets = ["thumbv8m.main-none-eabihf"] +components = ["rust-src", "rustfmt", "llvm-tools", "clippy"] From f0a5918c5ec4f266294b0a43452e7bb8857e5032 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 17 Nov 2025 11:22:04 +0100 Subject: [PATCH 02/12] Set --no-catch-hardfault --- examples/rt685s-trustzone/.cargo/config.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/rt685s-trustzone/.cargo/config.toml b/examples/rt685s-trustzone/.cargo/config.toml index ae25588..292d581 100644 --- a/examples/rt685s-trustzone/.cargo/config.toml +++ b/examples/rt685s-trustzone/.cargo/config.toml @@ -1,10 +1,5 @@ [target.thumbv8m.main-none-eabihf] -runner = [ - 'probe-rs', - 'run', - '--chip', - 'MIMXRT685SFVKB' -] +runner = "probe-rs run --chip MIMXRT685SFVKB --no-catch-hardfault" rustflags = [ "-C", "linker=arm-none-eabi-ld", From 74e27c8ff9bfe44057727b772438e9f3d347a12d Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 17 Nov 2025 15:19:08 +0100 Subject: [PATCH 03/12] Fixed RAM mapping --- .../application-secure/src/main.rs | 153 +++++++++++++----- 1 file changed, 114 insertions(+), 39 deletions(-) diff --git a/examples/rt685s-trustzone/application-secure/src/main.rs b/examples/rt685s-trustzone/application-secure/src/main.rs index cf59d11..ba85559 100644 --- a/examples/rt685s-trustzone/application-secure/src/main.rs +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -327,6 +327,7 @@ fn main() -> ! { .enable_s_priv_check() .disable() }); + reg_modify_checked!(ahb_secure_ctrl.misc_ctrl_dp_reg(), |_, w| { w.enable_ns_priv_check() .disable() @@ -392,62 +393,136 @@ fn main() -> ! { } fn set_ram_secure(mut region: Range, ahb_secure_ctrl: &ahb_secure_ctrl::RegisterBlock) { - let address_to_block = |address: u32| { - const BLOCK_SIZE_TABLE: &[(u32, u32, u32)] = &[ - ( - 0x2010_0000, - 8192, - 0x4_0000 / 0x400 + 0x4_0000 / 0x800 + 0x8_0000 / 0x1000, - ), - (0x2008_0000, 4096, 0x4_0000 / 0x400 + 0x4_0000 / 0x800), - (0x2004_0000, 2048, 0x4_0000 / 0x400), - (0x2000_0000, 1024, 0), - ]; + // A block of RAM memory for which a rule can be set. + struct Block { + pub index: u32, + pub start: u32, + pub size: u32, + } - let (block_start, block_size, previous_blocks) = BLOCK_SIZE_TABLE - .iter() - .find(|(block_address, _, _)| address >= *block_address) - .unwrap(); + impl Block { + pub fn from_address(address: u32) -> Self { + const BLOCK_SIZE_TABLE: &[(u32, u32, u32)] = &[ + ( + 0x2010_0000, + 8192, + 0x4_0000 / 0x400 + 0x4_0000 / 0x800 + 0x8_0000 / 0x1000, + ), + (0x2008_0000, 4096, 0x4_0000 / 0x400 + 0x4_0000 / 0x800), + (0x2004_0000, 2048, 0x4_0000 / 0x400), + (0x2000_0000, 1024, 0), + ]; + + let (block_start, block_size, previous_blocks) = BLOCK_SIZE_TABLE + .into_iter() + .find(|(block_address, _, _)| address >= *block_address) + .unwrap(); - ( - *previous_blocks + (address - *block_start) / *block_size, - *block_start + (address - *block_start) / *block_size * *block_size, - *block_size, - ) - }; + Block { + index: previous_blocks + (address - block_start) / block_size, + start: block_start + (address - block_start) / block_size * block_size, + size: *block_size, + } + } + + /// Fetch the index number of the register containing the current rule block. + fn register_index(&self) -> u32 { + self.index / 8 + } + + pub fn register(&self) -> Register { + Register { + index: self.register_index(), + } + } + + pub fn rule_index(&self) -> u32 { + self.index % 8 + } + } + + struct Register { + index: u32, + } + + impl Register { + /// RAM region (or RAM peripheral block RAM00 to RAM29) + const fn region(&self) -> u32 { + self.index / 4 + } + + /// RAM region (or RAM peripheral block RAM00 to RAM29) + const fn subregion(&self) -> u32 { + self.index % 4 + } + + /// Compute the register address which contains the rule bits for this block. + pub fn address(&self, ahb_secure_ctrl: &ahb_secure_ctrl::RegisterBlock) -> *mut u32 { + // RAM subregion, each region has 4 denoted by RAMXX_RULE0 to RAMXX_RULES3 + let subregion = self.subregion() % 4; + + // For each region which is not contiguously set after the previous region, the register offset. + // All other not specified regions follow contiguously. + const REGION_TABLE: [(u32, u32); 9] = [ + (28, 0x2D0), + (24, 0x280), + (20, 0x230), + (16, 0x1E0), + (12, 0x190), + (8, 0x140), + (4, 0xF0), + (2, 0xC0), + (0, 0x90), + ]; + + const BASE_OFFSET: u32 = REGION_TABLE.last().unwrap().1; + + let (region_start, offset) = REGION_TABLE + .into_iter() + .find(|(region_start, _)| self.region() >= *region_start) + .unwrap(); + + let offset = offset + (self.region() - region_start) * 0x10 + subregion * 4; + + let real_offset = (offset - BASE_OFFSET) as usize; + let base_ptr = ahb_secure_ctrl.ram00_rule(0).as_ptr(); + unsafe { base_ptr.byte_add(real_offset) } + } + } // Make sure the end of the range is at a boundary - let (_, block_start, _) = address_to_block(region.end); - assert_eq!(region.end, block_start); + let block = Block::from_address(region.end); + assert_eq!(region.end, block.start); while !region.is_empty() { - let (block_index, block_start, block_size) = address_to_block(region.start); - assert_eq!(region.start, block_start); - - let register_index = block_index / 8; - let rule_index = block_index % 8; + let block = Block::from_address(region.start); + assert_eq!(region.start, block.start); - let base_ptr = ahb_secure_ctrl.ram00_rule(0).as_ptr(); + let rule_index = block.rule_index(); + let target_register = block.register(); rprintln!( - "Ram region {:#010X}..={:#010X} to secure ({}, {})", - block_start, - block_start + block_size - 1, - register_index, - rule_index + "Ram region {:#010X}..={:#010X} to secure (RAM{:#02}_RULE{} offset {} @ {:?})", + block.start, + block.start + block.size - 1, + target_register.region(), + target_register.subregion(), + rule_index, + target_register.address(ahb_secure_ctrl) ); unsafe { rtt_target::UpChannel::conjure(0).unwrap().flush() }; unsafe { - let target_register = base_ptr.add(register_index as usize); + let target_register = target_register.address(ahb_secure_ctrl); let current_val = target_register.read_volatile(); - target_register.write_volatile( - current_val & !(0b11 << (rule_index * 4)) | ((Rule0::SecurePrivUserAllowed as u32) << (rule_index * 4)), - ); + let new_val = + current_val & !(0b11 << (rule_index * 4)) | ((Rule0::SecurePrivUserAllowed as u32) << (rule_index * 4)); + target_register.write_volatile(new_val); + assert_eq!(target_register.read_volatile(), new_val); } - region.start += block_size; + region.start += block.size; } } From 9b23d1e5eaa7fe94e9fb2e4af026f8912bf819fd Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 1 Dec 2025 11:46:16 +0100 Subject: [PATCH 04/12] Changed rt685s-trustzone memory mappings --- examples/rt685s-trustzone/Cargo.toml | 7 ++++++- .../rt685s-trustzone/application-nonsecure/Cargo.toml | 10 +++++----- .../rt685s-trustzone/application-nonsecure/memory.x | 6 +++--- .../rt685s-trustzone/application-secure/Cargo.toml | 6 +++--- examples/rt685s-trustzone/application-secure/memory.x | 2 +- .../rt685s-trustzone/application-secure/src/main.rs | 3 --- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/rt685s-trustzone/Cargo.toml b/examples/rt685s-trustzone/Cargo.toml index c3c3fe6..812a59f 100644 --- a/examples/rt685s-trustzone/Cargo.toml +++ b/examples/rt685s-trustzone/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "3" members = [ + "application-nonsecure", "application-secure", "bootloader", "bsp", @@ -16,7 +17,7 @@ repository = "https://github.com/OpenDevicePartnership/ec-slimloader" warnings = "deny" [workspace.dependencies] -embassy-imxrt = { git = "https://github.com/OpenDevicePartnership/embassy-imxrt.git", default-features = false } +embassy-imxrt = { git = "https://github.com/Wassasin/embassy-imxrt.git", rev="cb164dab5066bf3d274f61a481159ec4a337328e", default-features = false } partition-manager = { git = "https://github.com/OpenDevicePartnership/embedded-services.git", default-features = false } embassy-sync = "0.7.2" @@ -26,6 +27,10 @@ defmt = "1.0.1" defmt-or-log = { version = "0.2.2", default-features = false } defmt-rtt = "0.4.0" +cortex-m = "0.7.7" +cortex-m-rt = "0.7.3" +rtt-target = "0.6.1" + [profile.release] lto = true # better optimizations debug = 2 diff --git a/examples/rt685s-trustzone/application-nonsecure/Cargo.toml b/examples/rt685s-trustzone/application-nonsecure/Cargo.toml index ae1bed4..79aeb63 100644 --- a/examples/rt685s-trustzone/application-nonsecure/Cargo.toml +++ b/examples/rt685s-trustzone/application-nonsecure/Cargo.toml @@ -5,20 +5,20 @@ edition = "2021" license = "MIT" [dependencies] -cortex-m = { version = "0.7.7", features = [ +cortex-m = { workspace = true, features = [ "inline-asm", "critical-section-single-core", ] } -cortex-m-rt = "0.7.3" -rtt-target = "0.6.1" +cortex-m-rt.workspace = true +rtt-target.workspace = true panic-probe = { version = "1.0", features = ["print-rtt"] } -embassy-imxrt = { version = "0.1.0", path = "../../../", features = [ +embassy-imxrt = { workspace = true, features = [ "time-driver-os-timer", "time", "mimxrt685s", "unstable-pac", ] } -embassy-executor = { version = "0.9.1", features = [ +embassy-executor = { workspace = true, features = [ "arch-cortex-m", "executor-thread", "executor-interrupt", diff --git a/examples/rt685s-trustzone/application-nonsecure/memory.x b/examples/rt685s-trustzone/application-nonsecure/memory.x index d88f9c1..2ec8bb1 100644 --- a/examples/rt685s-trustzone/application-nonsecure/memory.x +++ b/examples/rt685s-trustzone/application-nonsecure/memory.x @@ -1,7 +1,7 @@ MEMORY { - FLASH : ORIGIN = 0x08040000, LENGTH = 32K - RAM : ORIGIN = 0x20001800, LENGTH = 1536K - 6K - SHAREDRTT : ORIGIN = 0x20001000, LENGTH = 2K + FLASH : ORIGIN = 0x00026800, LENGTH = 32K + RAM : ORIGIN = 0x2002E800, LENGTH = 32K + SHAREDRTT : ORIGIN = 0x20026000, LENGTH = 2K } SECTIONS { diff --git a/examples/rt685s-trustzone/application-secure/Cargo.toml b/examples/rt685s-trustzone/application-secure/Cargo.toml index 3d1513e..ec217c7 100644 --- a/examples/rt685s-trustzone/application-secure/Cargo.toml +++ b/examples/rt685s-trustzone/application-secure/Cargo.toml @@ -5,13 +5,13 @@ edition = "2021" license = "MIT" [dependencies] -cortex-m = { version = "0.7.7", features = [ +cortex-m = { workspace = true, features = [ "inline-asm", "critical-section-single-core", ] } -cortex-m-rt = "0.7.3" +cortex-m-rt.workspace = true mimxrt685s-pac = { version = "0.5.0", features = [ "rt", "critical-section", ] } -rtt-target = "0.6.1" +rtt-target.workspace = true diff --git a/examples/rt685s-trustzone/application-secure/memory.x b/examples/rt685s-trustzone/application-secure/memory.x index 4f2bf37..cdea05d 100644 --- a/examples/rt685s-trustzone/application-secure/memory.x +++ b/examples/rt685s-trustzone/application-secure/memory.x @@ -2,7 +2,7 @@ MEMORY { FLASH : ORIGIN = 0x10020000, LENGTH = 20K RAM : ORIGIN = 0x30025000, LENGTH = 4K SHAREDRTT : ORIGIN = 0x20026000, LENGTH = 2K - # APPLICATION : ORIGIN = 0x20026800 + # APPLICATION : ORIGIN = 0x00026800 ROM_TABLE (r) : ORIGIN = 0x1303F000, LENGTH = 64 } diff --git a/examples/rt685s-trustzone/application-secure/src/main.rs b/examples/rt685s-trustzone/application-secure/src/main.rs index ba85559..01c3dfa 100644 --- a/examples/rt685s-trustzone/application-secure/src/main.rs +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -382,9 +382,6 @@ fn main() -> ! { let nonsecure_reset = core::mem::transmute::<*const u32, extern "cmse-nonsecure-call" fn()>(nonsecure_reset as *const u32); - loop { - cortex_m::asm::nop(); - } // Jump. nonsecure_reset(); From ebd11333bbabedd1e3e0f2ed80cda53ac53ec954 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 1 Dec 2025 11:46:58 +0100 Subject: [PATCH 05/12] Add objcopy stub for multiple files --- bootloader-tool/src/commands/sign.rs | 21 ++-- bootloader-tool/src/config.rs | 18 +++- bootloader-tool/src/processors/objcopy.rs | 123 ++++++++++++++-------- 3 files changed, 109 insertions(+), 53 deletions(-) diff --git a/bootloader-tool/src/commands/sign.rs b/bootloader-tool/src/commands/sign.rs index 17005b1..2768042 100644 --- a/bootloader-tool/src/commands/sign.rs +++ b/bootloader-tool/src/commands/sign.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::path::PathBuf; use anyhow::Context; @@ -18,27 +19,26 @@ pub struct SignOutput { fn perform_checks(config: &Config, is_bootloader: bool, image: &Vec, base_addr: u32) -> anyhow::Result<()> { struct Values { run_start: u64, - max_size: u64 + max_size: u64, } let values = if is_bootloader { let Some(bootloader) = &config.bootloader else { - return Err(anyhow::anyhow!("Bootloader field not set in config")) + return Err(anyhow::anyhow!("Bootloader field not set in config")); }; Values { run_start: bootloader.run_start, - max_size: bootloader.max_size + max_size: bootloader.max_size, } - } else { let Some(application) = &config.application else { - return Err(anyhow::anyhow!("Application field not set in config")) + return Err(anyhow::anyhow!("Application field not set in config")); }; Values { run_start: application.run_start, - max_size: application.slot_size + max_size: application.slot_size, } }; @@ -47,14 +47,15 @@ fn perform_checks(config: &Config, is_bootloader: bool, image: &Vec, base_ad "Image will be run from unexpected address 0x{:x}, should be 0x{:x}", base_addr, values.run_start - )); + )); } if values.max_size < image.len() as u64 { return Err(anyhow::anyhow!( "Image can not fit in 0x{:x}, actual size is 0x{:x}", - values.max_size, image.len(), - )); + values.max_size, + image.len(), + )); } Ok(()) @@ -78,7 +79,7 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result, +} + +#[derive(Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct AddressMapping { + from_start: u64, + from_end: u64, + to_start: u64, } #[derive(Deserialize, Debug, Clone)] diff --git a/bootloader-tool/src/processors/objcopy.rs b/bootloader-tool/src/processors/objcopy.rs index 11019e0..31575c9 100644 --- a/bootloader-tool/src/processors/objcopy.rs +++ b/bootloader-tool/src/processors/objcopy.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::ops::Range; use anyhow::Context; @@ -5,71 +6,109 @@ use object::elf::{SHT_NOBITS, SHT_PROGBITS}; use object::read::elf::{ElfFile32, ProgramHeader}; use object::{Object, ObjectSegment}; +use crate::config::AddressMapping; + const PRELUDE_ADDRESS_RANGE: Range = 0x08000000..0x08001000; -pub fn objcopy(file: &ElfFile32) -> anyhow::Result<(Vec, u32)> { - // Sanity checks - let mut last_paddr = 0; +pub fn objcopy<'a>( + files: impl Iterator>, + mappings: &BTreeSet, +) -> anyhow::Result<(Vec, u32)> { let mut segments = vec![]; - for segment in file.segments() { - let filesz = segment.elf_program_header().p_filesz(file.endianness()); - let memsz = segment.elf_program_header().p_memsz(file.endianness()); - - if filesz == 0 { - // Skip bss to reduce size of image to flash. The bss will be cleared during startup anyway. - continue; + let mut endianness = Option::None; + + // Each file has their own entry point, which we should check. + // Collect all the segments for concattenation later. + for (file_i, file) in files.enumerate() { + if let Some(e) = endianness { + if e != file.endianness() { + return Err(anyhow::anyhow!("Passed ELFs with distinct endianness")); + } + } else { + endianness = Some(file.endianness()); } - - if filesz > memsz { - return Err(anyhow::anyhow!("p_filesz larger than p_memsz")); - } - if memsz > filesz { - return Err(anyhow::anyhow!("Segment only partially a bss segment")); + let endianness = endianness.unwrap(); + + let mut last_paddr = 0; + + for segment in file.segments() { + let filesz = segment.elf_program_header().p_filesz(endianness); + let memsz = segment.elf_program_header().p_memsz(endianness); + + if filesz == 0 { + // Skip bss to reduce size of image to flash. The bss will be cleared during startup anyway. + continue; + } + + if filesz > memsz { + return Err(anyhow::anyhow!("p_filesz larger than p_memsz")); + } + if memsz > filesz { + return Err(anyhow::anyhow!("Segment only partially a bss segment")); + } + + let paddr = segment.elf_program_header().p_paddr(endianness); + if PRELUDE_ADDRESS_RANGE.contains(&paddr) { + continue; + } + if paddr < last_paddr { + return Err(anyhow::anyhow!( + "Segments not in order of physical address or overlapping segments" + )); + } + last_paddr = paddr + segment.elf_program_header().p_memsz(endianness); + + segments.push(segment); } - let paddr = segment.elf_program_header().p_paddr(file.endianness()); - if PRELUDE_ADDRESS_RANGE.contains(&paddr) { - continue; + let base_addr = segments + .iter() + .map(|segment| segment.elf_program_header().p_paddr.get(endianness)) + .min() + .unwrap(); + let top_addr = segments + .iter() + .map(|segment| { + segment.elf_program_header().p_paddr(endianness) + segment.elf_program_header().p_filesz(endianness) + }) + .max() + .unwrap(); + let output_size = top_addr - base_addr; + + log::debug!("Image[{file_i}] base address: 0x{base_addr:0x}"); + log::debug!("Image[{file_i}] entry address: 0x{:0x}", file.entry()); + log::debug!("Image[{file_i}] output size: 0x{output_size:0x}"); + + // The bootrom will start executing at offset 0x130 of the final image. Add 1 for thumb mode. + let expected_entry = (base_addr + 0x131) as u64; + if file.entry() != expected_entry { + return Err(anyhow::anyhow!(format!( + "Image[{file_i}] entrypoint 0x{:0x} not at expected address 0x{:0x}", + file.entry(), + expected_entry + ))); } - if paddr < last_paddr { - return Err(anyhow::anyhow!( - "Segments not in order of physical address or overlapping segments" - )); - } - last_paddr = paddr + segment.elf_program_header().p_memsz(file.endianness()); - - segments.push(segment); } + let endianness = endianness.unwrap(); + let base_addr = segments .iter() - .map(|segment| segment.elf_program_header().p_paddr.get(file.endianness())) + .map(|segment| segment.elf_program_header().p_paddr.get(endianness)) .min() .unwrap(); let top_addr = segments .iter() .map(|segment| { - segment.elf_program_header().p_paddr(file.endianness()) - + segment.elf_program_header().p_filesz(file.endianness()) + segment.elf_program_header().p_paddr(endianness) + segment.elf_program_header().p_filesz(endianness) }) .max() .unwrap(); let output_size = top_addr - base_addr; log::debug!("Image base address: 0x{base_addr:0x}"); - log::debug!("Image entry address: 0x{:0x}", file.entry()); log::debug!("Image output size: 0x{output_size:0x}"); - // The bootrom will start executing at offset 0x130 of the final image. Add 1 for thumb mode. - let expected_entry = (base_addr + 0x131) as u64; - if file.entry() != expected_entry { - return Err(anyhow::anyhow!(format!( - "Image entrypoint 0x{:0x} not at expected address 0x{:0x}", - file.entry(), - expected_entry - ))); - } - // TODO check VTOR // TODO check image size // TODO check execution address @@ -77,7 +116,7 @@ pub fn objcopy(file: &ElfFile32) -> anyhow::Result<(Vec, u32)> { // Assemble BIN image by copying all segments directly let mut image = vec![0; output_size as usize]; for segment in segments { - let paddr = segment.elf_program_header().p_paddr(file.endianness()); + let paddr = segment.elf_program_header().p_paddr(endianness); image[paddr as usize - base_addr as usize..paddr as usize - base_addr as usize + segment.size() as usize] .copy_from_slice(segment.data().unwrap()); From 92577899a47834e0abf727d9c6f8d347f4bd5bf4 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 1 Dec 2025 15:51:10 +0100 Subject: [PATCH 06/12] Implement using multiple images to create a MBI --- bootloader-tool/config.toml | 5 +- bootloader-tool/src/commands/run.rs | 2 +- bootloader-tool/src/commands/sign.rs | 48 ++++-- bootloader-tool/src/config.rs | 9 +- bootloader-tool/src/lib.rs | 21 ++- bootloader-tool/src/processors/objcopy.rs | 193 +++++++++++++--------- 6 files changed, 168 insertions(+), 110 deletions(-) diff --git a/bootloader-tool/config.toml b/bootloader-tool/config.toml index 808a29e..9f8ae6c 100644 --- a/bootloader-tool/config.toml +++ b/bootloader-tool/config.toml @@ -23,6 +23,9 @@ max_size = 0x8000 state = { start = 0x0800B000, size = 0x2000 } [application] -slot_starts = [0x800D000, 0x80F9000] +slot_starts = [0x0800D000, 0x080F9000] run_start = 0x10020000 slot_size = 0xEC000 # 944K +address_mapping = [ + { from_start = 0x00000000, from_end = 0x0FFFFFFF, to_start = 0x10000000 } +] \ No newline at end of file diff --git a/bootloader-tool/src/commands/run.rs b/bootloader-tool/src/commands/run.rs index 32e52d8..c589581 100644 --- a/bootloader-tool/src/commands/run.rs +++ b/bootloader-tool/src/commands/run.rs @@ -60,7 +60,7 @@ pub async fn process(config: &Config, command: RunCommands) -> anyhow::Result<() command.args(["--probe", probe]); } - command.arg(run_args.sign_args.input_path).status().unwrap(); + command.arg(run_args.sign_args.input_paths.last().unwrap()).status().unwrap(); Ok(()) } diff --git a/bootloader-tool/src/commands/sign.rs b/bootloader-tool/src/commands/sign.rs index 2768042..dd107bb 100644 --- a/bootloader-tool/src/commands/sign.rs +++ b/bootloader-tool/src/commands/sign.rs @@ -16,7 +16,7 @@ pub struct SignOutput { pub rkth: Rkth, } -fn perform_checks(config: &Config, is_bootloader: bool, image: &Vec, base_addr: u32) -> anyhow::Result<()> { +fn perform_checks(config: &Config, is_bootloader: bool, image: &[u8], base_addr: u32) -> anyhow::Result<()> { struct Values { run_start: u64, max_size: u64, @@ -67,19 +67,36 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result (false, sign_arguments), }; - let input_data = std::fs::read(&args.input_path)?; + let files: Vec> = args.input_paths.iter().map(|input_path| { + log::info!("Reading ELF from {}", input_path.display()); + std::fs::read(input_path) + }).collect::>, std::io::Error>>()?; - log::info!("Reading ELF from {}", args.input_path.display()); - let file = ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")?; + let files: Vec = files.iter().map(|input_data| { + Ok(ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")?) + }).collect::, anyhow::Error>>()?; if is_bootloader { log::info!("Extracting prelude"); - let out = objcopy::remove_non_prelude(&input_data)?; + let out = objcopy::remove_non_prelude(&files.first().unwrap())?; std::fs::write(args.prelude_path_with_default(), &out).context("Could not write prelude elf file")?; } - log::info!("Generating image for {}", args.input_path.display()); - let (image, base_addr) = objcopy::objcopy(std::iter::once(&file), &BTreeSet::new())?; + let objcopy_config = if is_bootloader { + objcopy::Config { + mappings: &BTreeSet::new(), + max_size: Some(config.bootloader.as_ref().unwrap().max_size as u32), + } + } else { + let app = config.application.as_ref().unwrap(); + objcopy::Config { + mappings: &app.address_mapping, + max_size: Some(app.slot_size as u32), + } + }; + + log::info!("Generating image"); + let (image, base_addr) = objcopy::objcopy(files.iter(), objcopy_config)?; perform_checks(config, is_bootloader, &image, base_addr)?; @@ -106,10 +123,11 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result anyhow::Result, /// Signature file /// /// If present, will be checked against image and merged into output path @@ -106,25 +109,31 @@ impl SignArguments { pub fn output_unsigned_path_with_default(&self) -> PathBuf { self.output_unsigned_path .clone() - .unwrap_or_else(|| self.input_path.clone().with_extension("unsigned.bin")) + .unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("unsigned.bin")) } pub fn output_prestage_path_with_default(&self) -> PathBuf { self.output_prestage_path .clone() - .unwrap_or_else(|| self.input_path.clone().with_extension("mbi-proto.bin")) + .unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("mbi-proto.bin")) } pub fn output_path_with_default(&self) -> PathBuf { self.output_path .clone() - .unwrap_or_else(|| self.input_path.clone().with_extension("signed.bin")) + .unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("signed.bin")) + } + + pub fn signature_path_with_default(&self) -> PathBuf { + self.signature_path + .clone() + .unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("signature.bin")) } pub fn prelude_path_with_default(&self) -> PathBuf { self.prelude_path .clone() - .unwrap_or_else(|| self.input_path.clone().with_extension("prelude.elf")) + .unwrap_or_else(|| self.input_paths.last().unwrap().clone().with_extension("prelude.elf")) } } diff --git a/bootloader-tool/src/processors/objcopy.rs b/bootloader-tool/src/processors/objcopy.rs index 31575c9..d84a42d 100644 --- a/bootloader-tool/src/processors/objcopy.rs +++ b/bootloader-tool/src/processors/objcopy.rs @@ -10,98 +10,122 @@ use crate::config::AddressMapping; const PRELUDE_ADDRESS_RANGE: Range = 0x08000000..0x08001000; -pub fn objcopy<'a>( - files: impl Iterator>, - mappings: &BTreeSet, -) -> anyhow::Result<(Vec, u32)> { - let mut segments = vec![]; - let mut endianness = Option::None; - - // Each file has their own entry point, which we should check. - // Collect all the segments for concattenation later. - for (file_i, file) in files.enumerate() { - if let Some(e) = endianness { - if e != file.endianness() { - return Err(anyhow::anyhow!("Passed ELFs with distinct endianness")); +#[derive(Debug)] +pub struct Config<'a> { + pub mappings: &'a BTreeSet, + pub max_size: Option, +} + +impl Config<'_> { + pub fn map_paddr(&self, paddr: u32) -> u32 { + for mapping in self.mappings { + let range = mapping.from_start..=mapping.from_end; + if range.contains(&(paddr as u64)) { + let offset = paddr - mapping.from_start as u32; + let new_paddr = mapping.to_start as u32 + offset; + log::debug!("Mapped section 0x{paddr:0x} to 0x{new_paddr:0x}"); + return new_paddr; } - } else { - endianness = Some(file.endianness()); } - let endianness = endianness.unwrap(); + paddr + } +} - let mut last_paddr = 0; +struct Segment<'a> { + paddr: u32, + data: &'a [u8], +} - for segment in file.segments() { - let filesz = segment.elf_program_header().p_filesz(endianness); - let memsz = segment.elf_program_header().p_memsz(endianness); +fn get_segments<'a>(file_i: usize, file: &ElfFile32<'a>, config: &Config, last_paddr: &mut u32) -> Result>, anyhow::Error> { + // Segments must be globally ordered. + // If the files are not passed in the correct order, an error is thrown. + let mut segments = vec![]; + let endianness = file.endianness(); - if filesz == 0 { - // Skip bss to reduce size of image to flash. The bss will be cleared during startup anyway. - continue; - } + for segment in file.segments() { + let filesz = segment.elf_program_header().p_filesz(endianness); + let memsz = segment.elf_program_header().p_memsz(endianness); - if filesz > memsz { - return Err(anyhow::anyhow!("p_filesz larger than p_memsz")); - } - if memsz > filesz { - return Err(anyhow::anyhow!("Segment only partially a bss segment")); - } + if filesz == 0 { + // Skip bss to reduce size of image to flash. The bss will be cleared during startup anyway. + continue; + } - let paddr = segment.elf_program_header().p_paddr(endianness); - if PRELUDE_ADDRESS_RANGE.contains(&paddr) { - continue; - } - if paddr < last_paddr { - return Err(anyhow::anyhow!( - "Segments not in order of physical address or overlapping segments" - )); - } - last_paddr = paddr + segment.elf_program_header().p_memsz(endianness); + if filesz > memsz { + return Err(anyhow::anyhow!("p_filesz larger than p_memsz")); + } + if memsz > filesz { + return Err(anyhow::anyhow!("Segment only partially a bss segment")); + } - segments.push(segment); + let paddr = config.map_paddr(segment.elf_program_header().p_paddr(endianness)); + if PRELUDE_ADDRESS_RANGE.contains(&paddr) { + continue; } - let base_addr = segments - .iter() - .map(|segment| segment.elf_program_header().p_paddr.get(endianness)) - .min() - .unwrap(); - let top_addr = segments - .iter() - .map(|segment| { - segment.elf_program_header().p_paddr(endianness) + segment.elf_program_header().p_filesz(endianness) - }) - .max() - .unwrap(); - let output_size = top_addr - base_addr; - - log::debug!("Image[{file_i}] base address: 0x{base_addr:0x}"); - log::debug!("Image[{file_i}] entry address: 0x{:0x}", file.entry()); - log::debug!("Image[{file_i}] output size: 0x{output_size:0x}"); - - // The bootrom will start executing at offset 0x130 of the final image. Add 1 for thumb mode. - let expected_entry = (base_addr + 0x131) as u64; - if file.entry() != expected_entry { - return Err(anyhow::anyhow!(format!( - "Image[{file_i}] entrypoint 0x{:0x} not at expected address 0x{:0x}", - file.entry(), - expected_entry - ))); + if paddr < *last_paddr { + return Err(anyhow::anyhow!( + "Segments not in order of physical address or overlapping segments" + )); } - } - let endianness = endianness.unwrap(); + let data = segment.data().unwrap(); + *last_paddr = paddr + data.len() as u32; + + segments.push(Segment { + data, + paddr, + }); + } - let base_addr = segments + let base_addr = segments.iter().map(|segment| segment.paddr).min().unwrap(); + let top_addr = segments .iter() - .map(|segment| segment.elf_program_header().p_paddr.get(endianness)) - .min() + .map(|segment| segment.paddr + segment.data.len() as u32) + .max() .unwrap(); + let output_size = top_addr - base_addr; + + log::debug!("Image[{file_i}] base address: 0x{base_addr:0x}"); + log::debug!("Image[{file_i}] entry address: 0x{:0x}", file.entry()); + log::debug!("Image[{file_i}] output size: 0x{output_size:0x}"); + + // The bootrom will start executing at offset 0x130 of the final image. Add 1 for thumb mode. + let expected_entry = base_addr + 0x131; + let actual_entry = config.map_paddr(file.entry() as u32); + if actual_entry != expected_entry { + return Err(anyhow::anyhow!(format!( + "Image[{file_i}] entrypoint 0x{:0x} not at expected address 0x{:0x}", + actual_entry, + expected_entry + ))); + } + + Ok(segments) +} + +/// Copy one or more ELF files into a binary format. +/// +/// Optionally moves the physical load address (LMA) according to the [AddressMapping]. +/// +/// Each file must be a valid binary image with a VTOR and file entry. +pub fn objcopy<'a>( + files: impl IntoIterator>, + config: Config<'a>, +) -> anyhow::Result<(Vec, u32)> { + // Segments must be globally ordered. + // If the files are not passed in the correct order, an error is thrown. + let mut segments = vec![]; + let mut last_paddr = 0; + + for (file_i, file) in files.into_iter().enumerate() { + segments.extend(get_segments(file_i, file, &config, &mut last_paddr)?); + } + + let base_addr = segments.iter().map(|segment| segment.paddr).min().unwrap(); let top_addr = segments .iter() - .map(|segment| { - segment.elf_program_header().p_paddr(endianness) + segment.elf_program_header().p_filesz(endianness) - }) + .map(|segment| segment.paddr + segment.data.len() as u32) .max() .unwrap(); let output_size = top_addr - base_addr; @@ -110,23 +134,30 @@ pub fn objcopy<'a>( log::debug!("Image output size: 0x{output_size:0x}"); // TODO check VTOR - // TODO check image size // TODO check execution address + if let Some(max_size) = config.max_size + && output_size > max_size + { + return Err(anyhow::anyhow!( + "Image output size 0x{output_size:0x} exceeded maximum size 0x{max_size:0x}" + )); + } + // Assemble BIN image by copying all segments directly let mut image = vec![0; output_size as usize]; for segment in segments { - let paddr = segment.elf_program_header().p_paddr(endianness); + let paddr = segment.paddr; - image[paddr as usize - base_addr as usize..paddr as usize - base_addr as usize + segment.size() as usize] - .copy_from_slice(segment.data().unwrap()); + image[paddr as usize - base_addr as usize..paddr as usize - base_addr as usize + segment.data.len()] + .copy_from_slice(segment.data); } Ok((image, base_addr)) } -pub fn remove_non_prelude(data: &[u8]) -> anyhow::Result> { - let mut builder = object::build::elf::Builder::read32(data).context("Could not parse ELF")?; +pub fn remove_non_prelude(file: &ElfFile32) -> anyhow::Result> { + let mut builder = object::build::elf::Builder::read32(file.data()).context("Could not parse ELF")?; for section in builder.sections.iter_mut() { if PRELUDE_ADDRESS_RANGE.contains(&(section.sh_addr as u32)) { From 5004d6317b6b1855a9e68976593bdb1d3103efcf Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Mon, 1 Dec 2025 15:53:18 +0100 Subject: [PATCH 07/12] Fix RTT section in nonsecure binary --- examples/rt685s-trustzone/application-nonsecure/memory.x | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/rt685s-trustzone/application-nonsecure/memory.x b/examples/rt685s-trustzone/application-nonsecure/memory.x index 2ec8bb1..61a5bfe 100644 --- a/examples/rt685s-trustzone/application-nonsecure/memory.x +++ b/examples/rt685s-trustzone/application-nonsecure/memory.x @@ -5,10 +5,12 @@ MEMORY { } SECTIONS { - .shared_rtt : { + .shared_rtt (NOLOAD) : ALIGN(4) + { . = ALIGN(4); KEEP(* (.shared_rtt.header)) KEEP(* (.shared_rtt.buffer)) . = ALIGN(4); - } > SHAREDRTT + } > SHAREDRTT AT>FLASH + . = ALIGN(4); } From c972e1660265eab6eb666143db9b2ac1f5b79270 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Tue, 2 Dec 2025 13:27:17 +0100 Subject: [PATCH 08/12] Fix nonsecure examples by tweaking memory mapping; nonsecure ivt and rt feature --- .../application-nonsecure/Cargo.toml | 1 + .../application-secure/memory.x | 4 ++- .../application-secure/src/main.rs | 27 ++++++++++++------- examples/rt685s-trustzone/build.sh | 10 +++++++ 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100755 examples/rt685s-trustzone/build.sh diff --git a/examples/rt685s-trustzone/application-nonsecure/Cargo.toml b/examples/rt685s-trustzone/application-nonsecure/Cargo.toml index 79aeb63..b725947 100644 --- a/examples/rt685s-trustzone/application-nonsecure/Cargo.toml +++ b/examples/rt685s-trustzone/application-nonsecure/Cargo.toml @@ -17,6 +17,7 @@ embassy-imxrt = { workspace = true, features = [ "time", "mimxrt685s", "unstable-pac", + "rt", ] } embassy-executor = { workspace = true, features = [ "arch-cortex-m", diff --git a/examples/rt685s-trustzone/application-secure/memory.x b/examples/rt685s-trustzone/application-secure/memory.x index cdea05d..5f1151f 100644 --- a/examples/rt685s-trustzone/application-secure/memory.x +++ b/examples/rt685s-trustzone/application-secure/memory.x @@ -2,10 +2,12 @@ MEMORY { FLASH : ORIGIN = 0x10020000, LENGTH = 20K RAM : ORIGIN = 0x30025000, LENGTH = 4K SHAREDRTT : ORIGIN = 0x20026000, LENGTH = 2K - # APPLICATION : ORIGIN = 0x00026800 + APPLICATION : ORIGIN = 0x00026800, LENGTH = 32K ROM_TABLE (r) : ORIGIN = 0x1303F000, LENGTH = 64 } +PROVIDE(_application_start = ORIGIN(APPLICATION)); + SECTIONS { .shared_rtt (NOLOAD) : ALIGN(4) { diff --git a/examples/rt685s-trustzone/application-secure/src/main.rs b/examples/rt685s-trustzone/application-secure/src/main.rs index 01c3dfa..04b2584 100644 --- a/examples/rt685s-trustzone/application-secure/src/main.rs +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -18,11 +18,14 @@ use rtt_target::ChannelMode::NoBlockSkip; // Note: all RAM memory is expressed as being part of the non-secure data RAM range (0x2). // IDAU other classifications with or without cache are disregarded, // especially for configuring the RAM MPC. -const SECURE_START_RAM: u32 = 0x2002_0000; // Up to NONSECURE_START_RAM is secure. +const SECURE_START_RAM: u32 = 0x2000_0000; // Up to NONSECURE_START_RAM is secure. // Note: START_RAM address must also contain the NS IVT. -const NONSECURE_START_RAM: u32 = 0x2002_6000; -const NONSECURE_END_RAM: u32 = 0x2FFF_FFFF; +const NONSECURE_DATA_START_RAM: u32 = 0x2002_6000; +const NONSECURE_DATA_END_RAM: u32 = 0x2FFF_FFFF; + +const NONSECURE_CODE_START_RAM: u32 = NONSECURE_DATA_START_RAM - 0x2000_0000; +const NONSECURE_CODE_END_RAM: u32 = NONSECURE_DATA_END_RAM - 0x2000_0000; const NONSECURE_START_PERIPHERALS: u32 = 0x4000_0000; const NONSECURE_END_PERIPHERALS: u32 = 0x4FFF_FFFF; @@ -32,6 +35,10 @@ extern "Rust" { static __veneer_limit: (); } +unsafe extern "C" { + static _application_start: u32; +} + const VTOR_NS: *mut u32 = 0xE002ED08 as *mut u32; fn test_security(addr: *mut u32) { @@ -103,9 +110,10 @@ fn main() -> ! { let _dp = mimxrt685s_pac::Peripherals::take().unwrap(); unsafe { - let [nonsecure_sp, nonsecure_reset] = (NONSECURE_START_RAM as *const [u32; 2]).read_volatile(); + let application_ivt_ptr = core::ptr::from_ref(&_application_start); + let [nonsecure_sp, nonsecure_reset] = (application_ivt_ptr as *const [u32; 2]).read_volatile(); - rprintln!("Running. NS SP: {:#010X}, RV: {:#010X}", nonsecure_sp, nonsecure_reset); + rprintln!("Running {:#010X}. NS SP: {:#010X}, RV: {:#010X}", application_ivt_ptr as u32, nonsecure_sp, nonsecure_reset); if nonsecure_sp == u32::MAX || nonsecure_reset == u32::MAX { loop { @@ -130,12 +138,13 @@ fn main() -> ! { sau.ctrl().write(|w| w.enable().disabled()); - let regions: [(RangeInclusive, SauRegionAttribute); 3] = [ + let regions: [(RangeInclusive, SauRegionAttribute); 4] = [ ( &raw const __veneer_base as u32..=&raw const __veneer_limit as u32 - 1, SauRegionAttribute::NonSecureCallable, ), - (NONSECURE_START_RAM..=NONSECURE_END_RAM, SauRegionAttribute::NonSecure), + (NONSECURE_CODE_START_RAM..=NONSECURE_CODE_END_RAM, SauRegionAttribute::NonSecure), + (NONSECURE_DATA_START_RAM..=NONSECURE_DATA_END_RAM, SauRegionAttribute::NonSecure), ( NONSECURE_START_PERIPHERALS..=NONSECURE_END_PERIPHERALS, SauRegionAttribute::NonSecure, @@ -181,7 +190,7 @@ fn main() -> ! { } rprintln!("Set secure RAM to secure"); - set_ram_secure(SECURE_START_RAM..NONSECURE_START_RAM, ahb_secure_ctrl); + set_ram_secure(SECURE_START_RAM..NONSECURE_DATA_START_RAM, ahb_secure_ctrl); rprintln!("Set FlexSPI memory to non-secure"); reg_write_checked!(ahb_secure_ctrl.flexspi0_region0_rule(0), |w| w.bits(0)); @@ -355,7 +364,7 @@ fn main() -> ! { // reg_modify_checked!(ahb_secure_ctrl.misc_ctrl_dp_reg(), |_, w| w.write_lock().restricted()); // Set the nonsecure VTOR. - VTOR_NS.write_volatile(NONSECURE_START_RAM as u32); + VTOR_NS.write_volatile(application_ivt_ptr as u32); // Set all interrupts to non-secure. for itns in &cp.NVIC.itns { diff --git a/examples/rt685s-trustzone/build.sh b/examples/rt685s-trustzone/build.sh new file mode 100755 index 0000000..a121170 --- /dev/null +++ b/examples/rt685s-trustzone/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +pushd application-secure +cargo build --release +popd + +pushd application-nonsecure +cargo build --release +popd \ No newline at end of file From df9e92375cb7a57ae80104c934b34de6531f85a7 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Tue, 2 Dec 2025 13:48:16 +0100 Subject: [PATCH 09/12] Add runscripts and README to trustzone example --- examples/rt685s-trustzone/README.md | 15 ++++++++++++++ examples/rt685s-trustzone/build.sh | 12 +++++++---- examples/rt685s-trustzone/ci.sh | 31 ----------------------------- examples/rt685s-trustzone/run.sh | 12 +++++++++++ 4 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 examples/rt685s-trustzone/README.md delete mode 100755 examples/rt685s-trustzone/ci.sh create mode 100755 examples/rt685s-trustzone/run.sh diff --git a/examples/rt685s-trustzone/README.md b/examples/rt685s-trustzone/README.md new file mode 100644 index 0000000..04dd542 --- /dev/null +++ b/examples/rt685s-trustzone/README.md @@ -0,0 +1,15 @@ +# RT685S example with Trustzone +This example uses the nightly compiler the `abi_cmse_nonsecure_call` and `cmse_nonsecure_entry` features. + +It is split into three parts: +* a Secure mode true bootloader as part of the ec-slimloader ecosystem. +* a Secure mode mini 'bootloader' that configures all busses and the core. It has a small veneer with the `do_stuff_secure` function that is callable from the NonSecure mode. All peripherals are set to NonSecure, with the notable exception being the OTP peripheral. +* a collection of NonSecure binaries that showcase TrustZone. + +The latter two binaries are merged by the bootloader-tool into a single MBI container for ec-slimloader to verify and boot. + +It is imperative that the secure firmware is compiled first. +It places a `veneers.o` in the target folder. +The nonsecure links that in to be able to call secure functions. + +In order to quickly run this example, call `run.sh secure_function`, or use any other binary in `application-nonsecure/src/bin`. diff --git a/examples/rt685s-trustzone/build.sh b/examples/rt685s-trustzone/build.sh index a121170..c600c10 100755 --- a/examples/rt685s-trustzone/build.sh +++ b/examples/rt685s-trustzone/build.sh @@ -1,10 +1,14 @@ #!/bin/bash set -e -pushd application-secure +pushd bootloader > /dev/null cargo build --release -popd +popd > /dev/null -pushd application-nonsecure +pushd application-secure > /dev/null cargo build --release -popd \ No newline at end of file +popd > /dev/null + +pushd application-nonsecure > /dev/null +cargo build --release +popd > /dev/null diff --git a/examples/rt685s-trustzone/ci.sh b/examples/rt685s-trustzone/ci.sh deleted file mode 100755 index a8948cb..0000000 --- a/examples/rt685s-trustzone/ci.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -if ! command -v cargo-batch &> /dev/null; then - echo "cargo-batch could not be found. Install it with the following command:" - echo "" - echo " cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked" - echo "" - exit 1 -fi - -export RUSTFLAGS=-Dwarnings -export DEFMT_LOG=trace -if [[ -z "${CARGO_TARGET_DIR}" ]]; then - export CARGO_TARGET_DIR=target_ci -fi - -TARGET="thumbv8m.main-none-eabihf" - -BUILD_EXTRA="" - -FEATURE_COMBINATIONS=( - "defmt" - "non-secure" -) -cargo batch \ - $(for features in "${FEATURE_COMBINATIONS[@]}"; do - echo "--- build --release --manifest-path Cargo.toml --target thumbv8m.main-none-eabihf " - echo "--- build --release --manifest-path Cargo.toml --target thumbv8m.main-none-eabihf --features $features " - done) $BUILD_EXTRA diff --git a/examples/rt685s-trustzone/run.sh b/examples/rt685s-trustzone/run.sh new file mode 100755 index 0000000..e71d8da --- /dev/null +++ b/examples/rt685s-trustzone/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +./build.sh + +pushd ../../bootloader-tool > /dev/null +cargo run -- download bootloader \ + -i ../examples/rt685s-trustzone/target/thumbv8m.main-none-eabihf/release/example-bootloader +cargo run -- run application \ + -i ../examples/rt685s-trustzone/target/thumbv8m.main-none-eabihf/release/secure-app \ + -i ../examples/rt685s-trustzone/target/thumbv8m.main-none-eabihf/release/$1 +popd From 3ae1dc074ac67a8bec0029cdfb13d74fff6c8e17 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Tue, 2 Dec 2025 13:48:41 +0100 Subject: [PATCH 10/12] Ran cargo fmt --- bootloader-tool/src/commands/run.rs | 5 ++++- bootloader-tool/src/commands/sign.rs | 21 ++++++++++++------- bootloader-tool/src/lib.rs | 2 +- bootloader-tool/src/processors/objcopy.rs | 17 ++++++++------- .../application-secure/src/main.rs | 17 ++++++++++++--- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/bootloader-tool/src/commands/run.rs b/bootloader-tool/src/commands/run.rs index c589581..8d3a446 100644 --- a/bootloader-tool/src/commands/run.rs +++ b/bootloader-tool/src/commands/run.rs @@ -60,7 +60,10 @@ pub async fn process(config: &Config, command: RunCommands) -> anyhow::Result<() command.args(["--probe", probe]); } - command.arg(run_args.sign_args.input_paths.last().unwrap()).status().unwrap(); + command + .arg(run_args.sign_args.input_paths.last().unwrap()) + .status() + .unwrap(); Ok(()) } diff --git a/bootloader-tool/src/commands/sign.rs b/bootloader-tool/src/commands/sign.rs index dd107bb..271501d 100644 --- a/bootloader-tool/src/commands/sign.rs +++ b/bootloader-tool/src/commands/sign.rs @@ -67,14 +67,19 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result (false, sign_arguments), }; - let files: Vec> = args.input_paths.iter().map(|input_path| { - log::info!("Reading ELF from {}", input_path.display()); - std::fs::read(input_path) - }).collect::>, std::io::Error>>()?; + let files: Vec> = args + .input_paths + .iter() + .map(|input_path| { + log::info!("Reading ELF from {}", input_path.display()); + std::fs::read(input_path) + }) + .collect::>, std::io::Error>>()?; - let files: Vec = files.iter().map(|input_data| { - Ok(ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")?) - }).collect::, anyhow::Error>>()?; + let files: Vec = files + .iter() + .map(|input_data| Ok(ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")?)) + .collect::, anyhow::Error>>()?; if is_bootloader { log::info!("Extracting prelude"); @@ -145,7 +150,7 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result { data: &'a [u8], } -fn get_segments<'a>(file_i: usize, file: &ElfFile32<'a>, config: &Config, last_paddr: &mut u32) -> Result>, anyhow::Error> { +fn get_segments<'a>( + file_i: usize, + file: &ElfFile32<'a>, + config: &Config, + last_paddr: &mut u32, +) -> Result>, anyhow::Error> { // Segments must be globally ordered. // If the files are not passed in the correct order, an error is thrown. let mut segments = vec![]; @@ -71,11 +76,8 @@ fn get_segments<'a>(file_i: usize, file: &ElfFile32<'a>, config: &Config, last_p let data = segment.data().unwrap(); *last_paddr = paddr + data.len() as u32; - - segments.push(Segment { - data, - paddr, - }); + + segments.push(Segment { data, paddr }); } let base_addr = segments.iter().map(|segment| segment.paddr).min().unwrap(); @@ -96,8 +98,7 @@ fn get_segments<'a>(file_i: usize, file: &ElfFile32<'a>, config: &Config, last_p if actual_entry != expected_entry { return Err(anyhow::anyhow!(format!( "Image[{file_i}] entrypoint 0x{:0x} not at expected address 0x{:0x}", - actual_entry, - expected_entry + actual_entry, expected_entry ))); } diff --git a/examples/rt685s-trustzone/application-secure/src/main.rs b/examples/rt685s-trustzone/application-secure/src/main.rs index 04b2584..046e027 100644 --- a/examples/rt685s-trustzone/application-secure/src/main.rs +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -113,7 +113,12 @@ fn main() -> ! { let application_ivt_ptr = core::ptr::from_ref(&_application_start); let [nonsecure_sp, nonsecure_reset] = (application_ivt_ptr as *const [u32; 2]).read_volatile(); - rprintln!("Running {:#010X}. NS SP: {:#010X}, RV: {:#010X}", application_ivt_ptr as u32, nonsecure_sp, nonsecure_reset); + rprintln!( + "Running {:#010X}. NS SP: {:#010X}, RV: {:#010X}", + application_ivt_ptr as u32, + nonsecure_sp, + nonsecure_reset + ); if nonsecure_sp == u32::MAX || nonsecure_reset == u32::MAX { loop { @@ -143,8 +148,14 @@ fn main() -> ! { &raw const __veneer_base as u32..=&raw const __veneer_limit as u32 - 1, SauRegionAttribute::NonSecureCallable, ), - (NONSECURE_CODE_START_RAM..=NONSECURE_CODE_END_RAM, SauRegionAttribute::NonSecure), - (NONSECURE_DATA_START_RAM..=NONSECURE_DATA_END_RAM, SauRegionAttribute::NonSecure), + ( + NONSECURE_CODE_START_RAM..=NONSECURE_CODE_END_RAM, + SauRegionAttribute::NonSecure, + ), + ( + NONSECURE_DATA_START_RAM..=NONSECURE_DATA_END_RAM, + SauRegionAttribute::NonSecure, + ), ( NONSECURE_START_PERIPHERALS..=NONSECURE_END_PERIPHERALS, SauRegionAttribute::NonSecure, From f868d9b1443d6e7333a7a902857ec7e4fa6417e0 Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Tue, 2 Dec 2025 13:56:02 +0100 Subject: [PATCH 11/12] Fixed minor issues, clarified CLI argument help --- bootloader-tool/src/commands/sign.rs | 4 ++-- bootloader-tool/src/lib.rs | 8 ++++---- examples/rt685s-trustzone/application-secure/src/main.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bootloader-tool/src/commands/sign.rs b/bootloader-tool/src/commands/sign.rs index 271501d..785618e 100644 --- a/bootloader-tool/src/commands/sign.rs +++ b/bootloader-tool/src/commands/sign.rs @@ -78,12 +78,12 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result = files .iter() - .map(|input_data| Ok(ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")?)) + .map(|input_data| ElfFile32::parse(&input_data[..]).context("Could not parse ELF file")) .collect::, anyhow::Error>>()?; if is_bootloader { log::info!("Extracting prelude"); - let out = objcopy::remove_non_prelude(&files.first().unwrap())?; + let out = objcopy::remove_non_prelude(files.first().unwrap())?; std::fs::write(args.prelude_path_with_default(), &out).context("Could not write prelude elf file")?; } diff --git a/bootloader-tool/src/lib.rs b/bootloader-tool/src/lib.rs index d187883..d50b9ce 100644 --- a/bootloader-tool/src/lib.rs +++ b/bootloader-tool/src/lib.rs @@ -68,10 +68,10 @@ pub enum GenerateCommands { #[derive(Args, Debug, Clone)] pub struct SignArguments { /// Input file path (ELF) - /// - /// When this argument is provided multiple times, the last argument is used as the basis for non-provided arguments, - /// and when running provided to probe-rs for the RTT defmt catalogue. - #[arg(short, long, value_name = "INPUT_FILES")] + /// When this argument is provided multiple times, the ELF files are merged and turned into a single MBI container. + /// The sections of these ELF files *MUST* be consecutive and non-overlapping. + /// The last provided argument is considered to be the 'primary' . + #[arg(short, long, value_name = "INPUT_FILES", verbatim_doc_comment)] input_paths: Vec, /// Signature file /// diff --git a/examples/rt685s-trustzone/application-secure/src/main.rs b/examples/rt685s-trustzone/application-secure/src/main.rs index 046e027..22d2886 100644 --- a/examples/rt685s-trustzone/application-secure/src/main.rs +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -431,7 +431,7 @@ fn set_ram_secure(mut region: Range, ahb_secure_ctrl: &ahb_secure_ctrl::Reg ]; let (block_start, block_size, previous_blocks) = BLOCK_SIZE_TABLE - .into_iter() + .iter() .find(|(block_address, _, _)| address >= *block_address) .unwrap(); From a8eaf2ebf1892ce8fa3f533b66d64eeb0b83a93c Mon Sep 17 00:00:00 2001 From: Wouter Geraedts Date: Tue, 2 Dec 2025 14:07:52 +0100 Subject: [PATCH 12/12] Ran nightly cargo fmt --- bootloader-tool/src/config.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bootloader-tool/src/config.rs b/bootloader-tool/src/config.rs index 5ed193c..ea1438d 100644 --- a/bootloader-tool/src/config.rs +++ b/bootloader-tool/src/config.rs @@ -1,9 +1,7 @@ #![allow(unused)] -use std::{ - collections::BTreeSet, - path::{Path, PathBuf}, -}; +use std::collections::BTreeSet; +use std::path::{Path, PathBuf}; use serde::Deserialize;