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 588d764..8d3a446 100644 --- a/bootloader-tool/src/commands/run.rs +++ b/bootloader-tool/src/commands/run.rs @@ -54,12 +54,16 @@ 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]); } - 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 a2d2aab..785618e 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; @@ -15,45 +16,94 @@ pub struct SignOutput { pub rkth: Rkth, } +fn perform_checks(config: &Config, is_bootloader: bool, image: &[u8], 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), SignCommands::Application(sign_arguments) => (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| 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(&file)?; - - if is_bootloader { - if let Some(bootloader) = &config.bootloader - && bootloader.run_start != base_addr as u64 - { - return Err(anyhow::anyhow!( - "Bootloader image will be run from unexpected address 0x{:x}, should be 0x{:x}", - base_addr, - bootloader.run_start - )); + let objcopy_config = if is_bootloader { + objcopy::Config { + mappings: &BTreeSet::new(), + max_size: Some(config.bootloader.as_ref().unwrap().max_size as u32), } - } else if let Some(application) = &config.application - && application.run_start != base_addr as u64 - { - return Err(anyhow::anyhow!( - "Application image will be run from unexpected address 0x{:x}, should be 0x{:x}", - base_addr, - application.run_start - )); - } + } 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)?; let output_unsigned_path = args.output_unsigned_path_with_default(); log::debug!("Wrote unsigned bare binary image to {}", output_unsigned_path.display()); @@ -78,10 +128,11 @@ pub async fn process(config: &Config, command: SignCommands) -> anyhow::Result anyhow::Result, +} + +#[derive(Deserialize, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct AddressMapping { + /// Start of the address range to be mapped. + pub from_start: u64, + /// End of the address range to be mapped. + pub from_end: u64, + /// Start of the offset the address should be mapped to. + pub to_start: u64, } #[derive(Deserialize, Debug, Clone)] diff --git a/bootloader-tool/src/lib.rs b/bootloader-tool/src/lib.rs index 655916a..d50b9ce 100644 --- a/bootloader-tool/src/lib.rs +++ b/bootloader-tool/src/lib.rs @@ -68,8 +68,11 @@ pub enum GenerateCommands { #[derive(Args, Debug, Clone)] pub struct SignArguments { /// Input file path (ELF) - #[arg(short, long, value_name = "INPUT_FILE")] - input_path: PathBuf, + /// 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 /// /// 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 11019e0..a09858c 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,15 +6,50 @@ 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; +#[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; + } + } + paddr + } +} + +struct Segment<'a> { + paddr: u32, + data: &'a [u8], +} + +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(); + 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()); + 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. @@ -27,67 +63,102 @@ pub fn objcopy(file: &ElfFile32) -> anyhow::Result<(Vec, u32)> { return Err(anyhow::anyhow!("Segment only partially a bss segment")); } - let paddr = segment.elf_program_header().p_paddr(file.endianness()); + let paddr = config.map_paddr(segment.elf_program_header().p_paddr(endianness)); if PRELUDE_ADDRESS_RANGE.contains(&paddr) { continue; } - if paddr < last_paddr { + + 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 data = segment.data().unwrap(); + *last_paddr = paddr + data.len() as u32; + + segments.push(Segment { data, paddr }); } - let base_addr = segments - .iter() - .map(|segment| segment.elf_program_header().p_paddr.get(file.endianness())) - .min() - .unwrap(); + let base_addr = segments.iter().map(|segment| segment.paddr).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()) - }) + .map(|segment| segment.paddr + segment.data.len() as u32) .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}"); + 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 { + 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 entrypoint 0x{:0x} not at expected address 0x{:0x}", - file.entry(), - expected_entry + "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.paddr + segment.data.len() as u32) + .max() + .unwrap(); + let output_size = top_addr - base_addr; + + log::debug!("Image base address: 0x{base_addr:0x}"); + 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(file.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)) { diff --git a/examples/rt685s-trustzone/.cargo/config.toml b/examples/rt685s-trustzone/.cargo/config.toml new file mode 100644 index 0000000..292d581 --- /dev/null +++ b/examples/rt685s-trustzone/.cargo/config.toml @@ -0,0 +1,13 @@ +[target.thumbv8m.main-none-eabihf] +runner = "probe-rs run --chip MIMXRT685SFVKB --no-catch-hardfault" + +rustflags = [ + "-C", "linker=arm-none-eabi-ld", +] + +[build] +target = "thumbv8m.main-none-eabihf" # Cortex-M33 + +[env] +RUST_LOG = "info" +DEFMT_LOG = "info" \ No newline at end of file diff --git a/examples/rt685s-trustzone/.gitignore b/examples/rt685s-trustzone/.gitignore new file mode 100644 index 0000000..0397b4f --- /dev/null +++ b/examples/rt685s-trustzone/.gitignore @@ -0,0 +1,3 @@ +Cargo.lock +/target +/target_ci diff --git a/examples/rt685s-trustzone/Cargo.toml b/examples/rt685s-trustzone/Cargo.toml new file mode 100644 index 0000000..812a59f --- /dev/null +++ b/examples/rt685s-trustzone/Cargo.toml @@ -0,0 +1,43 @@ +[workspace] +resolver = "3" +members = [ + "application-nonsecure", + "application-secure", + "bootloader", + "bsp", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/OpenDevicePartnership/ec-slimloader" + +[workspace.lints.rust] +warnings = "deny" + +[workspace.dependencies] +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" +embassy-executor = "0.9.1" + +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 +opt-level = "s" + +[patch.crates-io] +# Required to get access to unreleased trustzone registers +cortex-m = { git = "https://github.com/rust-embedded/cortex-m.git", rev="a3ff54b" } +# Required to be able to pass a linker section to the init functions +rtt-target = { git = "https://github.com/probe-rs/rtt-target.git", rev = "117d951" } \ No newline at end of file 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/application-nonsecure/Cargo.toml b/examples/rt685s-trustzone/application-nonsecure/Cargo.toml new file mode 100644 index 0000000..b725947 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "nonsecure-app" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[dependencies] +cortex-m = { workspace = true, features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt.workspace = true +rtt-target.workspace = true +panic-probe = { version = "1.0", features = ["print-rtt"] } +embassy-imxrt = { workspace = true, features = [ + "time-driver-os-timer", + "time", + "mimxrt685s", + "unstable-pac", + "rt", +] } +embassy-executor = { workspace = true, features = [ + "arch-cortex-m", + "executor-thread", + "executor-interrupt", +] } +embassy-time = "0.5.0" diff --git a/examples/rt685s-trustzone/application-nonsecure/build.rs b/examples/rt685s-trustzone/application-nonsecure/build.rs new file mode 100644 index 0000000..4e26430 --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/build.rs @@ -0,0 +1,27 @@ +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"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + + println!("cargo:rustc-link-arg=target/veneers.o"); + println!("cargo:rerun-if-changed=target/veneers.o"); +} diff --git a/examples/rt685s-trustzone/application-nonsecure/memory.x b/examples/rt685s-trustzone/application-nonsecure/memory.x new file mode 100644 index 0000000..61a5bfe --- /dev/null +++ b/examples/rt685s-trustzone/application-nonsecure/memory.x @@ -0,0 +1,16 @@ +MEMORY { + FLASH : ORIGIN = 0x00026800, LENGTH = 32K + RAM : ORIGIN = 0x2002E800, LENGTH = 32K + SHAREDRTT : ORIGIN = 0x20026000, LENGTH = 2K +} + +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-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..ec217c7 --- /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 = { workspace = true, features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt.workspace = true +mimxrt685s-pac = { version = "0.5.0", features = [ + "rt", + "critical-section", +] } +rtt-target.workspace = true 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..5f1151f --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/memory.x @@ -0,0 +1,20 @@ +MEMORY { + FLASH : ORIGIN = 0x10020000, LENGTH = 20K + RAM : ORIGIN = 0x30025000, LENGTH = 4K + SHAREDRTT : ORIGIN = 0x20026000, LENGTH = 2K + APPLICATION : ORIGIN = 0x00026800, LENGTH = 32K + ROM_TABLE (r) : ORIGIN = 0x1303F000, LENGTH = 64 +} + +PROVIDE(_application_start = ORIGIN(APPLICATION)); + +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..22d2886 --- /dev/null +++ b/examples/rt685s-trustzone/application-secure/src/main.rs @@ -0,0 +1,610 @@ +#![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 = 0x2000_0000; // Up to NONSECURE_START_RAM is secure. + +// Note: START_RAM address must also contain the NS IVT. +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; + +extern "Rust" { + static __veneer_base: (); + 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) { + 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 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 + ); + + 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); 4] = [ + ( + &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_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_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)); + 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(application_ivt_ptr 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); + + // Jump. + nonsecure_reset(); + + cortex_m::asm::udf(); + } +} + +fn set_ram_secure(mut region: Range, ahb_secure_ctrl: &ahb_secure_ctrl::RegisterBlock) { + // A block of RAM memory for which a rule can be set. + struct Block { + pub index: u32, + pub start: u32, + pub size: u32, + } + + 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 + .iter() + .find(|(block_address, _, _)| address >= *block_address) + .unwrap(); + + 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 = Block::from_address(region.end); + assert_eq!(region.end, block.start); + + while !region.is_empty() { + let block = Block::from_address(region.start); + assert_eq!(region.start, block.start); + + let rule_index = block.rule_index(); + let target_register = block.register(); + + rprintln!( + "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 = target_register.address(ahb_secure_ctrl); + let current_val = target_register.read_volatile(); + 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; + } +} + +#[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/build.sh b/examples/rt685s-trustzone/build.sh new file mode 100755 index 0000000..c600c10 --- /dev/null +++ b/examples/rt685s-trustzone/build.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +pushd bootloader > /dev/null +cargo build --release +popd > /dev/null + +pushd application-secure > /dev/null +cargo build --release +popd > /dev/null + +pushd application-nonsecure > /dev/null +cargo build --release +popd > /dev/null 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 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"]