From e624ec6f7f57a28414cc13bd67c1031d23c89376 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Mon, 16 Feb 2026 21:48:23 +0000 Subject: [PATCH 1/5] Split out SMP tests into their own folder. Will make it easier to add other tests, like sharing the GIC across cores. --- .cargo/config.toml | 56 --- .gitignore | 2 + .vscode/settings.json | 4 +- Cargo.toml | 1 + examples/mps3-an536-smp/.cargo/config.toml | 10 + examples/mps3-an536-smp/Cargo.toml | 31 ++ examples/mps3-an536-smp/README.md | 106 +++++ examples/mps3-an536-smp/build.rs | 26 ++ examples/mps3-an536-smp/commands.gdb | 13 + examples/mps3-an536-smp/memory.x | 82 ++++ .../reference/smp-test-armv8r-none-eabihf.out | 4 + .../smp-test-thumbv8r-none-eabihf.out | 4 + examples/mps3-an536-smp/rust-toolchain.toml | 6 + examples/mps3-an536-smp/src/bin/smp-test.rs | 113 +++++ examples/mps3-an536-smp/src/lib.rs | 433 ++++++++++++++++++ examples/mps3-an536/.cargo/config.toml | 8 + .../reference/smp_test-armv8r-none-eabihf.out | 1 - .../smp_test-armv8r-none-eabihf_smp2.out | 2 - .../smp_test-thumbv8r-none-eabihf.out | 1 - .../smp_test-thumbv8r-none-eabihf_smp2.out | 1 - examples/mps3-an536/src/bin/smp_test.rs | 354 -------------- examples/versatileab/.cargo/config.toml | 49 ++ justfile | 20 +- 23 files changed, 903 insertions(+), 424 deletions(-) create mode 100644 examples/mps3-an536-smp/.cargo/config.toml create mode 100644 examples/mps3-an536-smp/Cargo.toml create mode 100644 examples/mps3-an536-smp/README.md create mode 100644 examples/mps3-an536-smp/build.rs create mode 100644 examples/mps3-an536-smp/commands.gdb create mode 100644 examples/mps3-an536-smp/memory.x create mode 100644 examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-smp/rust-toolchain.toml create mode 100644 examples/mps3-an536-smp/src/bin/smp-test.rs create mode 100644 examples/mps3-an536-smp/src/lib.rs delete mode 100644 examples/mps3-an536/reference/smp_test-armv8r-none-eabihf.out delete mode 100644 examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out delete mode 100644 examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out delete mode 100644 examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out delete mode 100644 examples/mps3-an536/src/bin/smp_test.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index d4d345ef..e69de29b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,56 +0,0 @@ -[target.armv8r-none-eabihf] -# Note, this requires QEMU 9 or higher -runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" - -[target.thumbv8r-none-eabihf] -# Note, this requires QEMU 9 or higher -runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" - -[target.armv7r-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" - -[target.thumbv7r-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" - -[target.armv7r-none-eabi] -# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise -# qemu-system-arm will lock up -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" - -[target.thumbv7r-none-eabi] -# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise -# qemu-system-arm will lock up -runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" - -[target.armv7a-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.thumbv7a-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.armv7a-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.thumbv7a-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" - -[target.armv6-none-eabihf] -runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" - -[target.armv6-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" - -[target.thumbv6-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" - -[target.armv5te-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" - -[target.thumbv5te-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" - -[target.armv4t-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" - -[target.thumbv4t-none-eabi] -runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" diff --git a/.gitignore b/.gitignore index a139b23f..666f29a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ target examples/mps3-an536/target examples/mps3-an536/target-d32 +examples/mps3-an536-smp/target +examples/mps3-an536-smp/target-d32 examples/versatileab/target examples/versatileab/target-d32 Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index 29d282c9..fbd3ad9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,9 @@ "rust-analyzer.checkOnSave": true, "rust-analyzer.linkedProjects": [ "./Cargo.toml", + "./arm-targets/Cargo.toml", "examples/versatileab/Cargo.toml", - "examples/mps3-an536/Cargo.toml" + "examples/mps3-an536/Cargo.toml", + "examples/mps3-an536-smp/Cargo.toml", ] } diff --git a/Cargo.toml b/Cargo.toml index 243b915a..2209a19b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ exclude = [ "arm-targets", "examples/versatileab", "examples/mps3-an536", + "examples/mps3-an536-smp", ] members = [ "aarch32-cpu", diff --git a/examples/mps3-an536-smp/.cargo/config.toml b/examples/mps3-an536-smp/.cargo/config.toml new file mode 100644 index 00000000..b3ab14b1 --- /dev/null +++ b/examples/mps3-an536-smp/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.armv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -smp 2 -kernel" + +[target.thumbv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -smp 2 -kernel" + +[build] +target = "armv8r-none-eabihf" diff --git a/examples/mps3-an536-smp/Cargo.toml b/examples/mps3-an536-smp/Cargo.toml new file mode 100644 index 00000000..6bf5e110 --- /dev/null +++ b/examples/mps3-an536-smp/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = [ + "Jonathan Pallant ", + "The Embedded Devices Working Group Arm Team " +] +default-run = "smp-test" +description = "Examples for SMP MPS3-AN536 device (2x Arm Cortex-R52)" +edition = "2024" +homepage = "https://github.com/rust-embedded/aarch32" +license = "MIT OR Apache-2.0" +name = "mps3-an536-smp" +publish = false +readme = "README.md" +repository = "https://github.com/rust-embedded/aarch32.git" +version = "0.0.0" + +[dependencies] +aarch32-cpu = { path = "../../aarch32-cpu", features = ["critical-section-multi-core"] } +aarch32-rt = { path = "../../aarch32-rt" } +arm-gic = { version = "0.7.1" } +critical-section = "1.2.0" +heapless = "0.9.1" +libm = "0.2.15" +semihosting = { version = "0.1.18", features = ["stdio"] } + +[build-dependencies] +arm-targets = {version = "0.4.0", path = "../../arm-targets"} + +[features] +eabi-fpu = ["aarch32-rt/eabi-fpu"] +fpu-d32 = ["aarch32-rt/fpu-d32"] diff --git a/examples/mps3-an536-smp/README.md b/examples/mps3-an536-smp/README.md new file mode 100644 index 00000000..d43a504e --- /dev/null +++ b/examples/mps3-an536-smp/README.md @@ -0,0 +1,106 @@ +# Examples for Arm MPS3-AN536 + +This package contains example binaries for the Arm MPS3-AN536 evaluation system, +featuring one or two Arm Cortex-R52 processor cores. This crate is tested on the +following targets: + +- `armv8r-none-eabihf` - ARMv8-R AArch32, hard-float, Arm mode +- `thumbv8r-none-eabihf` - ARMv8-R AArch32, hard-float, Thumb mode + +The repo-level [`.cargo/config.toml`] will ensure the code runs on the +appropriate QEMU configuration. + +As of Rust 1.92, `armv8r-none-eabihf` is a Tier 2 target and so any stable +release from 1.92 or newer should work for that target. However, +`thumbv8r-none-eabihf` is still a Tier 3 target, which means Nightly Rust is +required. This folder contains a [`rust-toolchain.toml`] which pins us to a +specific release of nightly that is known to work. + +We have only tested this crate on `qemu-system-arm` emulating the Arm +MPS3-AN536, not the real thing. + +[`.cargo/config.toml`]: ../../.cargo/config.toml +[`rust-toolchain.toml`]: ./rust-toolchain.toml + +## Running + +Run these examples as follows: + +```console +$ cargo run + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -smp 2 -kernel target/armv8r-none-eabihf/debug/smp-test` +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed +Stack usage report: +SYS0 Stack = 2680 used of 16384 bytes (016%) @ 0x1006bf80..0x1006ff80 +FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 +IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 +ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 +SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 +UND0 Stack = 0 used of 16384 bytes (000%) @ 0x10078000..0x1007c000 +HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +SYS1 Stack = 680 used of 16384 bytes (004%) @ 0x10000018..0x10004018 +FIQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004018..0x10004058 +IRQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004058..0x10004098 +ABT1 Stack = 0 used of 16384 bytes (000%) @ 0x10004098..0x10008098 +SVC1 Stack = 0 used of 16384 bytes (000%) @ 0x10008098..0x1000c098 +UND1 Stack = 0 used of 16384 bytes (000%) @ 0x1000c098..0x10010098 +HYP1 Stack = 0 used of 16384 bytes (000%) @ 0x10010098..0x10014098 +$ cargo run --target thumbv8r-none-eabihf -Zbuild-std=core + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -smp 2 -kernel target/thumbv8r-none-eabihf/debug/smp-test` +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed +Stack usage report: +SYS0 Stack = 4840 used of 16384 bytes (029%) @ 0x1006bf80..0x1006ff80 +FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 +IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 +ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 +SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 +UND0 Stack = 0 used of 16384 bytes (000%) @ 0x10078000..0x1007c000 +HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +SYS1 Stack = 1568 used of 16384 bytes (009%) @ 0x10000018..0x10004018 +FIQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004018..0x10004058 +IRQ1 Stack = 0 used of 64 bytes (000%) @ 0x10004058..0x10004098 +ABT1 Stack = 0 used of 16384 bytes (000%) @ 0x10004098..0x10008098 +SVC1 Stack = 0 used of 16384 bytes (000%) @ 0x10008098..0x1000c098 +UND1 Stack = 0 used of 16384 bytes (000%) @ 0x1000c098..0x10010098 +HYP1 Stack = 0 used of 16384 bytes (000%) @ 0x10010098..0x10014098 +``` + +## Debugging + +You can start a GDB server by adding `-- -s -S` to the end of the `cargo run` +command, and the connect with GDB as follows: + +```console +$ cargo run --bin hello -- -s -S +# QEMU runs and hangs waiting for a connection. In another terminal run: +$ arm-none-eabi-gdb -x commands.gdb target/armv8r-none-eabihf/debug/hello +# GDB will start and connect to QEMU's GDB server. The commands.gdb file sets up some useful defaults. +``` + +## Minimum Supported Rust Version (MSRV) + +These examples are guaranteed to compile on the version of Rust given in the +[`rust-toolchain.toml`] file. These examples are not version controlled and we +may change the MSRV at any time. + +## Licence + +- Copyright (c) Ferrous Systems +- Copyright (c) The Rust Embedded Devices Working Group developers + +Licensed under either [MIT](../LICENSE-MIT) or [Apache-2.0](../LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/examples/mps3-an536-smp/build.rs b/examples/mps3-an536-smp/build.rs new file mode 100644 index 00000000..b0b9f098 --- /dev/null +++ b/examples/mps3-an536-smp/build.rs @@ -0,0 +1,26 @@ +//! # Build script for the MPS3-AN536 Examples +//! +//! This script only executes when using `cargo` to build the project. +//! +//! Copyright (c) Ferrous Systems, 2025 + +use std::io::Write; + +fn main() { + arm_targets::process(); + write("memory.x", include_bytes!("memory.x")); + // Use the cortex-m-rt linker script + println!("cargo:rustc-link-arg=-Tlink.x"); +} + +fn write(file: &str, contents: &[u8]) { + // Put linker file in our output directory and ensure it's on the + // linker search path. + let out = &std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + std::fs::File::create(out.join(file)) + .unwrap() + .write_all(contents) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed={}", file); +} diff --git a/examples/mps3-an536-smp/commands.gdb b/examples/mps3-an536-smp/commands.gdb new file mode 100644 index 00000000..0634363b --- /dev/null +++ b/examples/mps3-an536-smp/commands.gdb @@ -0,0 +1,13 @@ +target extended-remote :1234 +break kmain +break _asm_undefined_handler +break _asm_svc_handler +break _asm_prefetch_abort_handler +break _asm_data_abort_handler +break _asm_irq_handler +break _asm_fiq_handler +layout asm +layout regs +set logging file ./target/debug.log +set logging enabled on +stepi diff --git a/examples/mps3-an536-smp/memory.x b/examples/mps3-an536-smp/memory.x new file mode 100644 index 00000000..0b784d24 --- /dev/null +++ b/examples/mps3-an536-smp/memory.x @@ -0,0 +1,82 @@ +/* +Memory configuration for the MPS3-AN536 machine. + +See https://github.com/qemu/qemu/blob/master/hw/arm/mps3r.c +*/ + +MEMORY { + QSPI : ORIGIN = 0x08000000, LENGTH = 8M + BRAM : ORIGIN = 0x10000000, LENGTH = 512K + DDR : ORIGIN = 0x20000000, LENGTH = 1536M +} + +REGION_ALIAS("VECTORS", QSPI); +REGION_ALIAS("CODE", QSPI); +REGION_ALIAS("DATA", BRAM); +REGION_ALIAS("STACKS", BRAM); + +SECTIONS { + /* ### Interrupt Handler Entries + * + * The IRQ handler walks this section to find registered + * interrupt handlers + */ + .irq_entries : ALIGN(4) + { + /* We put this in the header */ + __irq_entries_start = .; + /* Here are the entries */ + KEEP(*(.irq_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __irq_entries_end = .; + } > CODE +} INSERT AFTER .text; + +SECTIONS { + .core1_stacks (NOLOAD) : ALIGN(8) + { + . = ALIGN(8); + _core1_stacks_low_end = .; + _core1_sys_stack_end = .; + . += _sys_stack_size; + . = ALIGN(8); + _core1_sys_stack = .; + _core1_fiq_stack_end = .; + . += _fiq_stack_size; + . = ALIGN(8); + _core1_fiq_stack = .; + _core1_irq_stack_end = .; + . += _irq_stack_size; + . = ALIGN(8); + _core1_irq_stack = .; + _core1_abt_stack_end = .; + . += _abt_stack_size; + . = ALIGN(8); + _core1_abt_stack = .; + _core1_svc_stack_end = .; + . += _svc_stack_size; + . = ALIGN(8); + _core1_svc_stack = .; + _core1_und_stack_end = .; + . += _und_stack_size; + . = ALIGN(8); + _core1_und_stack = .; + _core1_hyp_stack_end = .; + . += _hyp_stack_size; + . = ALIGN(8); + _core1_hyp_stack = .; + _core1_stacks_high_end = .; + } > STACKS +} INSERT BEFORE .filler; + +PROVIDE(kmain2 = default_kmain2); + +PROVIDE(_hyp_stack_size = 16K); +PROVIDE(_und_stack_size = 16K); +PROVIDE(_svc_stack_size = 16K); +PROVIDE(_abt_stack_size = 16K); +PROVIDE(_irq_stack_size = 64); +PROVIDE(_fiq_stack_size = 64); +PROVIDE(_sys_stack_size = 16K); diff --git a/examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out new file mode 100644 index 00000000..b157d323 --- /dev/null +++ b/examples/mps3-an536-smp/reference/smp-test-armv8r-none-eabihf.out @@ -0,0 +1,4 @@ +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed diff --git a/examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..b157d323 --- /dev/null +++ b/examples/mps3-an536-smp/reference/smp-test-thumbv8r-none-eabihf.out @@ -0,0 +1,4 @@ +I am core 0 - Mpidr(80000000) +I am core 1 - Mpidr(80000001) +CAS test passed +CS Mutex test passed diff --git a/examples/mps3-an536-smp/rust-toolchain.toml b/examples/mps3-an536-smp/rust-toolchain.toml new file mode 100644 index 00000000..7e4d9e6c --- /dev/null +++ b/examples/mps3-an536-smp/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "nightly-2026-01-26" +targets = [ + "armv8r-none-eabihf", +] +components = ["rust-src", "clippy", "rustfmt"] diff --git a/examples/mps3-an536-smp/src/bin/smp-test.rs b/examples/mps3-an536-smp/src/bin/smp-test.rs new file mode 100644 index 00000000..4364fbbe --- /dev/null +++ b/examples/mps3-an536-smp/src/bin/smp-test.rs @@ -0,0 +1,113 @@ +//! Multi-core hello-world for Arm Cortex-R +//! +//! Runs code on two cores, checking that atomic fetch_add works. + +#![no_std] +#![no_main] + +use core::cell::RefCell; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + +use aarch32_rt::entry; +use semihosting::println; + +static CORE1_BOOTED: AtomicBool = AtomicBool::new(false); + +static SHARED_VARIABLE: AtomicU32 = AtomicU32::new(0); + +static SHARED_VARIABLE_2: critical_section::Mutex> = + critical_section::Mutex::new(RefCell::new(0)); + +/// How long core 0 waits for core 1 +const CORE0_WILL_WAIT: usize = 1_000_000; + +/// How many CAS loops to run? +const CAS_LOOPS: u32 = 1000; + +/// How many CS Mutex loops to run? +const CS_MUTEX_LOOPS: u32 = 1000; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `aarch32-rt`. +#[entry] +fn main() -> ! { + println!("I am core 0 - {:08x?}", aarch32_cpu::register::Mpidr::read()); + + mps3_an536_smp::start_core1(); + + // wait some time for core 1 to start + for counter in 0..=CORE0_WILL_WAIT { + if CORE1_BOOTED.load(Ordering::SeqCst) { + break; + } + if counter == CORE0_WILL_WAIT { + println!("CPU 1 is missing?!"); + + mps3_an536_smp::exit(0); + } + } + + for _ in 0..CAS_LOOPS { + SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); + } + + for _ in 0..CS_MUTEX_LOOPS { + critical_section::with(|cs| { + let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); + *value_ref += 1; + }) + } + + // let the other core finish + for _ in 0..CORE0_WILL_WAIT { + aarch32_cpu::asm::nop(); + } + + let mut code = 0; + let total_a = SHARED_VARIABLE.load(Ordering::Relaxed); + if total_a == CAS_LOOPS * 2 { + println!("CAS test passed"); + } else { + println!("CAS test failed, got {} not 2000", total_a); + code = 1; + } + + let total_b = critical_section::with(|cs| { + let value_ref = SHARED_VARIABLE_2.borrow_ref(cs); + *value_ref + }); + + if total_b == CS_MUTEX_LOOPS * 2 { + println!("CS Mutex test passed"); + } else { + println!("CS Mutex test failed, got {} not 2000", total_b); + code = 1; + } + + mps3_an536_smp::exit(code); +} + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code below, on Core 1. +#[unsafe(no_mangle)] +pub extern "C" fn kmain2() { + println!("I am core 1 - {:08x?}", aarch32_cpu::register::Mpidr::read()); + CORE1_BOOTED.store(true, Ordering::SeqCst); + + for _ in 0..CAS_LOOPS { + SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); + } + + for _ in 0..CS_MUTEX_LOOPS { + critical_section::with(|cs| { + let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); + *value_ref += 1; + }) + } + + loop { + aarch32_cpu::asm::wfi(); + } +} diff --git a/examples/mps3-an536-smp/src/lib.rs b/examples/mps3-an536-smp/src/lib.rs new file mode 100644 index 00000000..2af619ed --- /dev/null +++ b/examples/mps3-an536-smp/src/lib.rs @@ -0,0 +1,433 @@ +//! Common code for all examples +//! +//! ## Interrupt Map +//! +//! | Interrupt ID | Description | +//! |--------------|------------------------------| +//! | `EXTPPI0[0]` | UART 0 Receive Interrupt | +//! | `EXTPPI0[1]` | UART 0 Transmit Interrupt | +//! | `EXTPPI0[2]` | UART 0 Combined Interrupt | +//! | `EXTPPI0[3]` | UART 0 Overflow | +//! | `EXTPPI1[0]` | UART 1 Receive Interrupt | +//! | `EXTPPI1[1]` | UART 1 Transmit Interrupt | +//! | `EXTPPI1[2]` | UART 1 Combined Interrupt | +//! | `EXTPPI1[3]` | UART 1 Overflow | +//! | `SP[0]` | WDG | +//! | `SP[1]` | DualTimer 1 | +//! | `SP[2]` | DualTimer 2 | +//! | `SP[3]` | DualTimer Combined | +//! | `SP[4]` | RTC | +//! | `SP[5]` | UART 2 Receive Interrupt | +//! | `SP[6]` | UART 2 Transmit Interrupt | +//! | `SP[7]` | UART 3 Receive Interrupt | +//! | `SP[8]` | UART 3 Transmit Interrupt | +//! | `SP[9]` | UART 4 Receive Interrupt | +//! | `SP[10]` | UART 4 Transmit Interrupt | +//! | `SP[11]` | UART 5 Receive Interrupt | +//! | `SP[12]` | UART 5 Transmit Interrupt | +//! | `SP[13]` | UART 2 Combined Interrupt | +//! | `SP[14]` | UART 3 Combined Interrupt | +//! | `SP[15]` | UART 4 Combined Interrupt | +//! | `SP[16]` | UART 5 Combined Interrupt | +//! | `SP[17]` | UART Overflow (2, 3, 4 & 5) | +//! | `SP[18]` | Ethernet | +//! | `SP[19]` | USB | +//! | `SP[20]` | FPGA Audio I2S | +//! | `SP[21]` | Touch Screen | +//! | `SP[22]` | SPI ADC | +//! | `SP[23]` | SPI Shield 0 | +//! | `SP[24]` | SPI Shield 1 | +//! | `SP[25]` | HDCLCD Interrupt | +//! | `SP[26]` | GPIO 0 Combined Interrupt | +//! | `SP[27]` | GPIO 1 Combined Interrupt | +//! | `SP[28]` | GPIO 2 Combined Interrupt | +//! | `SP[29]` | GPIO 3 Combined Interrupt | +//! | `SP[30..=45]`| GPIO 0.x Interrupt | +//! | `SP[46..=61]`| GPIO 1.x Interrupt | +//! | `SP[62..=77]`| GPIO 2.x Interrupt | +//! | `SP[78..=93]`| GPIO 3.x Interrupt | +//! +//! * Interrupt ID `SP[x]` are shared across cores +//! * Interrupt ID `EXTPPI0[x]` is only available on Core 0 +//! * Interrupt ID `EXTPPI1[x]` is only available on Core 1 + +#![no_std] + +use aarch32_cpu::register::{Hactlr, Sctlr, Cpsr, cpsr::ProcessorMode}; + +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The PPI for the virutal timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 27. +pub const VIRTUAL_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(11); + +#[cfg(not(arm_architecture = "v8-r"))] +compile_error!("This example is only compatible to the ARMv8-R architecture"); + +static WANT_PANIC: AtomicBool = AtomicBool::new(false); + +/// Called when the application raises an unrecoverable `panic!`. +/// +/// Prints the panic to the console and then exits QEMU using a semihosting +/// breakpoint. +#[panic_handler] +#[cfg(target_os = "none")] +fn panic(info: &core::panic::PanicInfo) -> ! { + semihosting::println!("PANIC: {:#?}", info); + if WANT_PANIC.load(Ordering::Relaxed) { + exit(0); + } else { + exit(1); + } +} + +/// Set the panic function as no longer returning a failure code via semihosting +pub fn want_panic() { + WANT_PANIC.store(true, Ordering::Relaxed); +} + +/// Exit from QEMU with code +pub fn exit(code: i32) -> ! { + stack_dump(); + semihosting::process::exit(code) +} + +/// Print stack using to semihosting output for each stack +/// +/// Produces output like: +/// +/// ```text +/// Stack usage report: +/// SYS Stack = 332 used of 16384 bytes (002%) @ 0x1006bf80..0x1006ff80 +/// FIQ Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 +/// IRQ Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 +/// ABT Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 +/// SVC Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 +/// UND Stack = 244 used of 16384 bytes (001%) @ 0x10078000..0x1007c000 +/// HYP Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +/// ``` +fn stack_dump() { + use aarch32_cpu::stacks::stack_used_bytes; + use core::ptr::addr_of; + + unsafe extern "C" { + static _sys_stack_end: u32; + static _sys_stack: u32; + static _fiq_stack_end: u32; + static _fiq_stack: u32; + static _irq_stack_end: u32; + static _irq_stack: u32; + static _abt_stack_end: u32; + static _abt_stack: u32; + static _svc_stack_end: u32; + static _svc_stack: u32; + static _und_stack_end: u32; + static _und_stack: u32; + static _hyp_stack_end: u32; + static _hyp_stack: u32; + + static _core1_sys_stack_end: u32; + static _core1_sys_stack: u32; + static _core1_fiq_stack_end: u32; + static _core1_fiq_stack: u32; + static _core1_irq_stack_end: u32; + static _core1_irq_stack: u32; + static _core1_abt_stack_end: u32; + static _core1_abt_stack: u32; + static _core1_svc_stack_end: u32; + static _core1_svc_stack: u32; + static _core1_und_stack_end: u32; + static _core1_und_stack: u32; + static _core1_hyp_stack_end: u32; + static _core1_hyp_stack: u32; + } + + // these are placed in the order they are in aarch32-rt/link.x + let stacks = [ + ("SYS0", addr_of!(_sys_stack_end)..addr_of!(_sys_stack)), + ("FIQ0", addr_of!(_fiq_stack_end)..addr_of!(_fiq_stack)), + ("IRQ0", addr_of!(_irq_stack_end)..addr_of!(_irq_stack)), + ("ABT0", addr_of!(_abt_stack_end)..addr_of!(_abt_stack)), + ("SVC0", addr_of!(_svc_stack_end)..addr_of!(_svc_stack)), + ("UND0", addr_of!(_und_stack_end)..addr_of!(_und_stack)), + ("HYP0", addr_of!(_hyp_stack_end)..addr_of!(_hyp_stack)), + ("SYS1", addr_of!(_core1_sys_stack_end)..addr_of!(_core1_sys_stack)), + ("FIQ1", addr_of!(_core1_fiq_stack_end)..addr_of!(_core1_fiq_stack)), + ("IRQ1", addr_of!(_core1_irq_stack_end)..addr_of!(_core1_irq_stack)), + ("ABT1", addr_of!(_core1_abt_stack_end)..addr_of!(_core1_abt_stack)), + ("SVC1", addr_of!(_core1_svc_stack_end)..addr_of!(_core1_svc_stack)), + ("UND1", addr_of!(_core1_und_stack_end)..addr_of!(_core1_und_stack)), + ("HYP1", addr_of!(_core1_hyp_stack_end)..addr_of!(_core1_hyp_stack)), + ]; + + semihosting::eprintln!("Stack usage report:"); + + unsafe { + for (name, range) in stacks { + let (total, used) = stack_used_bytes(range.clone()); + let percent = used * 100 / total; + // Send to stderr, so it doesn't mix with expected output on stdout + semihosting::eprintln!( + "{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", + name, + used, + total, + percent, + range + ); + } + } +} + +/// Create the ARM GIC driver +/// +/// # Safety +/// +/// Only call this function once, from Core 0. +pub unsafe fn make_gic() -> arm_gic::gicv3::GicV3<'static> { + /// Offset from PERIPHBASE for GIC Distributor + const GICD_BASE_OFFSET: usize = 0x0000_0000usize; + + /// Offset from PERIPHBASE for the first GIC Redistributor + const GICR_BASE_OFFSET: usize = 0x0010_0000usize; + + // Get the GIC address by reading CBAR + let periphbase = aarch32_cpu::register::ImpCbar::read().periphbase(); + semihosting::println!("Found PERIPHBASE {:010p}", periphbase); + let gicd_base = periphbase.wrapping_byte_add(GICD_BASE_OFFSET); + let gicr_base = periphbase.wrapping_byte_add(GICR_BASE_OFFSET); + + // Initialise the GIC. + semihosting::println!( + "Creating GIC driver @ {:010p} / {:010p}", + gicd_base, + gicr_base + ); + // SAFETY: `gicd_base` points to the valid GICD MMIO region as obtained from the + // hardware CBAR register. This pointer is used exclusively by this GIC instance. + let gicd = unsafe { + arm_gic::UniqueMmioPointer::new(core::ptr::NonNull::new(gicd_base.cast()).unwrap()) + }; + let gicr_base = core::ptr::NonNull::new(gicr_base.cast()).unwrap(); + // SAFETY: The GICD and GICR base addresses point to valid GICv3 MMIO regions as + // obtained from the hardware CBAR register. This function is only called once + // (via Board::new()'s atomic guard), ensuring exclusive ownership of the GIC. + let mut gic = unsafe { arm_gic::gicv3::GicV3::new(gicd, gicr_base, 2, false) }; + semihosting::println!("Calling git.setup(0)"); + gic.setup(0); + arm_gic::gicv3::GicCpuInterface::set_priority_mask(0xFF); + gic +} + +/// Release core1 from spin loop +pub fn start_core1() { + let fpga_led = 0xE020_2000 as *mut u32; + unsafe { + // Activate second core by writing to FPGA LEDs. + // We needed a shared register that wasn't in RAM, and this will do. + fpga_led.write_volatile(1); + } +} + +// Start-up code for multi-core Armv8-R, as implemented on the MPS3-AN536. +// +// We boot into EL2, set up a stack pointer, init .data on .bss on core0, and +// run `kmain` in EL1 on all cores. +#[cfg(arm_architecture = "v8-r")] +core::arch::global_asm!( + r#" + .section .text.startup + .align 4 + .arm + + .global _start + .global core1_released + .type _start, %function + _start: + // Read MPIDR into R0 + mrc p15, 0, r0, c0, c0, 5 + ands r0, r0, 0xFF + bne core1 + core0: + ldr pc, =_default_start + core1: + // LED GPIO register base address + ldr r0, =0xE0202000 + mov r1, #0 + core1_spin: + wfe + // spin until an LED0 is on. We use the LED because unlike RAM this register resets to a known value. + ldr r2, [r0] + cmp r1, r2 + beq core1_spin + core1_released: + // now an LED is on, we assume _core1_stack_pointer contains our stack pointer + // First we must exit EL2... + // Set the HVBAR (for EL2) to _vector_table + ldr r0, =_vector_table + mcr p15, 4, r0, c12, c0, 0 + // Configure HACTLR to let us enter EL1 + mrc p15, 4, r0, c1, c0, 1 + mov r1, {hactlr_bits} + orr r0, r0, r1 + mcr p15, 4, r0, c1, c0, 1 + // Program the SPSR - enter system mode (0x1F) in Arm mode with IRQ, FIQ masked + mov r0, {sys_mode} + msr spsr_hyp, r0 + adr r0, 1f + msr elr_hyp, r0 + dsb + isb + eret + 1: + // Allow VFP coprocessor access + mrc p15, 0, r0, c1, c0, 2 + orr r0, r0, #0xF00000 + mcr p15, 0, r0, c1, c0, 2 + // Enable VFP + mov r0, #0x40000000 + vmsr fpexc, r0 + // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on + // Armv7-R because that only supports 'low' (default) or 'high'. + ldr r0, =_vector_table + mcr p15, 0, r0, c12, c0, 0 + // set up our stacks - also switches to SYS mode + bl _core1_stack_setup_preallocated + // Zero all registers before calling kmain2 + mov r0, 0 + mov r1, 0 + mov r2, 0 + mov r3, 0 + mov r4, 0 + mov r5, 0 + mov r6, 0 + mov r7, 0 + mov r8, 0 + mov r9, 0 + mov r10, 0 + mov r11, 0 + mov r12, 0 + // call our kmain2 for core 1 + bl kmain2 + .size _start, . - _start + "#, + hactlr_bits = const { + Hactlr::new_with_raw_value(0) + .with_cpuactlr(true) + .with_cdbgdci(true) + .with_flashifregionr(true) + .with_periphpregionr(true) + .with_qosr(true) + .with_bustimeoutr(true) + .with_intmonr(true) + .with_err(true) + .with_testr1(true) + .raw_value() + }, + sys_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Sys) + .with_i(true) + .with_f(true) + .raw_value() + }, +); + +// Initialise the stack for Core 1 for each mode +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp2 + + // Configure a stack for every mode. Leaves you in sys mode. + // + .section .text._core1_stack_setup_preallocated + .arm + .global _core1_stack_setup_preallocated + .type _core1_stack_setup_preallocated, %function + _core1_stack_setup_preallocated: + // Save LR from whatever mode we're currently in + mov r2, lr + // (we might not be in the same mode when we return). + // Set stack pointer and mask interrupts for UND mode (Mode 0x1B) + msr cpsr_c, {und_mode} + ldr r13, =_core1_und_stack + // Set stack pointer (right after) and mask interrupts for SVC mode (Mode 0x13) + msr cpsr_c, {svc_mode} + ldr r13, =_core1_svc_stack + // Set stack pointer (right after) and mask interrupts for ABT mode (Mode 0x17) + msr cpsr_c, {abt_mode} + ldr r13, =_core1_abt_stack + // Set stack pointer (right after) and mask interrupts for IRQ mode (Mode 0x12) + msr cpsr_c, {irq_mode} + ldr r13, =_core1_irq_stack + // Set stack pointer (right after) and mask interrupts for FIQ mode (Mode 0x11) + msr cpsr_c, {fiq_mode} + ldr r13, =_core1_fiq_stack + // Set stack pointer (right after) and mask interrupts for System mode (Mode 0x1F) + msr cpsr_c, {sys_mode} + ldr r13, =_core1_sys_stack + // Clear the Thumb Exception bit because all vector table is written in Arm assembly + // even on Thumb targets. + mrc p15, 0, r1, c1, c0, 0 + bic r1, #{te_bit} + mcr p15, 0, r1, c1, c0, 0 + // return to caller + bx r2 + .size _core1_stack_setup_preallocated, . - _core1_stack_setup_preallocated + "#, + und_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Und) + .with_i(true) + .with_f(true) + .raw_value() + }, + svc_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Svc) + .with_i(true) + .with_f(true) + .raw_value() + }, + abt_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Abt) + .with_i(true) + .with_f(true) + .raw_value() + }, + fiq_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Fiq) + .with_i(true) + .with_f(true) + .raw_value() + }, + irq_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Irq) + .with_i(true) + .with_f(true) + .raw_value() + }, + sys_mode = const { + Cpsr::new_with_raw_value(0) + .with_mode(ProcessorMode::Sys) + .with_i(true) + .with_f(true) + .raw_value() + }, + te_bit = const { Sctlr::new_with_raw_value(0).with_te(true).raw_value() } +); + +/// What a second core does when no `kmain2` is supplied. +#[unsafe(no_mangle)] +pub extern "C" fn default_kmain2() { + loop { + aarch32_cpu::asm::wfe(); + } +} diff --git a/examples/mps3-an536/.cargo/config.toml b/examples/mps3-an536/.cargo/config.toml index ec698529..6960eb34 100644 --- a/examples/mps3-an536/.cargo/config.toml +++ b/examples/mps3-an536/.cargo/config.toml @@ -1,2 +1,10 @@ +[target.armv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" + +[target.thumbv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" + [build] target = "armv8r-none-eabihf" \ No newline at end of file diff --git a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf.out b/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf.out deleted file mode 100644 index 7fe30b87..00000000 --- a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf.out +++ /dev/null @@ -1 +0,0 @@ -CPU 1 is missing?! diff --git a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out b/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out deleted file mode 100644 index 08c8c794..00000000 --- a/examples/mps3-an536/reference/smp_test-armv8r-none-eabihf_smp2.out +++ /dev/null @@ -1,2 +0,0 @@ -CAS test passed -CS Mutex test passed diff --git a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out b/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out deleted file mode 100644 index 7fe30b87..00000000 --- a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf.out +++ /dev/null @@ -1 +0,0 @@ -CPU 1 is missing?! diff --git a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out b/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out deleted file mode 100644 index 7fe30b87..00000000 --- a/examples/mps3-an536/reference/smp_test-thumbv8r-none-eabihf_smp2.out +++ /dev/null @@ -1 +0,0 @@ -CPU 1 is missing?! diff --git a/examples/mps3-an536/src/bin/smp_test.rs b/examples/mps3-an536/src/bin/smp_test.rs deleted file mode 100644 index 01e1b809..00000000 --- a/examples/mps3-an536/src/bin/smp_test.rs +++ /dev/null @@ -1,354 +0,0 @@ -//! Multi-core hello-world for Arm Cortex-R -//! -//! Runs code on two cores, checking that atomic fetch_add works. -//! -//! Abuses the FPGA LED register as a place to record whether Core 0 has -//! started. -//! -//! Run with `cargo run --bin smp_test --target=armv8r-none-eabihf -- -smp 2`. - -#![no_std] -#![no_main] - -use core::cell::{RefCell, UnsafeCell}; -use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; - -use aarch32_cpu::register::{Cpsr, Hactlr, Sctlr, cpsr::ProcessorMode}; -use aarch32_rt::entry; -use semihosting::println; - -use mps3_an536 as _; - -#[repr(align(16))] -struct Stack { - contents: UnsafeCell<[u8; LEN_BYTES]>, -} - -impl Stack { - const fn new() -> Self { - Self { - contents: UnsafeCell::new([0u8; LEN_BYTES]), - } - } - - fn stack_top(&self) -> usize { - let stack_start = self.contents.get() as usize; - stack_start + LEN_BYTES - } -} - -unsafe impl Sync for Stack {} - -static CORE1_STACK: Stack<{ 256 * 1024 }> = Stack::new(); - -static CORE1_BOOTED: AtomicBool = AtomicBool::new(false); - -static SHARED_VARIABLE: AtomicU32 = AtomicU32::new(0); - -static SHARED_VARIABLE_2: critical_section::Mutex> = - critical_section::Mutex::new(RefCell::new(0)); - -/// How long core 0 waits for core 1 -const CORE0_WILL_WAIT: usize = 1_000_000; - -/// How many CAS loops to run? -const CAS_LOOPS: u32 = 1000; - -/// How many CS Mutex loops to run? -const CS_MUTEX_LOOPS: u32 = 1000; - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up code in `aarch32-rt`. -#[entry] -fn main() -> ! { - let fpga_led = 0xE020_2000 as *mut u32; - unsafe extern "C" { - static mut _core1_stack_pointer: usize; - } - unsafe { - let p = &raw mut _core1_stack_pointer; - p.write(CORE1_STACK.stack_top()); - } - unsafe { - // Activate second core by writing to FPGA LEDs. - // We needed a shared register that wasn't in RAM, and this will do. - fpga_led.write_volatile(1); - } - - // wait some time for core 1 to start - for counter in 0..=CORE0_WILL_WAIT { - if CORE1_BOOTED.load(Ordering::SeqCst) { - break; - } - if counter == CORE0_WILL_WAIT { - println!("CPU 1 is missing?!"); - - mps3_an536::exit(0); - } - } - - for _ in 0..CAS_LOOPS { - SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); - } - - for _ in 0..CS_MUTEX_LOOPS { - critical_section::with(|cs| { - let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); - *value_ref += 1; - }) - } - - // let the other core finish - for _ in 0..CORE0_WILL_WAIT { - aarch32_cpu::asm::nop(); - } - - let mut code = 0; - let total_a = SHARED_VARIABLE.load(Ordering::Relaxed); - if total_a == CAS_LOOPS * 2 { - println!("CAS test passed"); - } else { - println!("CAS test failed, got {} not 2000", total_a); - code = 1; - } - - let total_b = critical_section::with(|cs| { - let value_ref = SHARED_VARIABLE_2.borrow_ref(cs); - *value_ref - }); - - if total_b == CS_MUTEX_LOOPS * 2 { - println!("CS Mutex test passed"); - } else { - println!("CS Mutex test failed, got {} not 2000", total_b); - code = 1; - } - - mps3_an536::exit(code); -} - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up code below, on Core 1. -#[unsafe(no_mangle)] -pub extern "C" fn kmain2() { - CORE1_BOOTED.store(true, Ordering::SeqCst); - - for _ in 0..CAS_LOOPS { - SHARED_VARIABLE.fetch_add(1, Ordering::Relaxed); - } - - for _ in 0..CS_MUTEX_LOOPS { - critical_section::with(|cs| { - let mut value_ref = SHARED_VARIABLE_2.borrow_ref_mut(cs); - *value_ref += 1; - }) - } - - loop { - core::hint::spin_loop(); - } -} - -// Start-up code for multi-core Armv8-R, as implemented on the MPS3-AN536. -// -// We boot into EL2, set up a stack pointer, init .data on .bss on core0, and -// run `kmain` in EL1 on all cores. -#[cfg(arm_architecture = "v8-r")] -core::arch::global_asm!( - r#" - .section .bss - .align 4 - _core1_stack_pointer: - .word 0 - - .section .text.startup - .align 4 - - .global _start - .global core1_released - .type _start, %function - _start: - // Read MPIDR into R0 - mrc p15, 0, r0, c0, c0, 5 - ands r0, r0, 0xFF - bne core1 - core0: - ldr pc, =_default_start - core1: - ldr r0, =0xE0202000 - mov r1, #0 - core1_spin: - wfe - // spin until an LED0 is on - ldr r2, [r0] - cmp r1, r2 - beq core1_spin - core1_released: - // now an LED is on, we assume _core1_stack_pointer contains our stack pointer - // First we must exit EL2... - // Set the HVBAR (for EL2) to _vector_table - ldr r0, =_vector_table - mcr p15, 4, r0, c12, c0, 0 - // Configure HACTLR to let us enter EL1 - mrc p15, 4, r0, c1, c0, 1 - mov r1, {hactlr_bits} - orr r0, r0, r1 - mcr p15, 4, r0, c1, c0, 1 - // Program the SPSR - enter system mode (0x1F) in Arm mode with IRQ, FIQ masked - mov r0, {sys_mode} - msr spsr_hyp, r0 - adr r0, 1f - msr elr_hyp, r0 - dsb - isb - eret - 1: - // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on - // Armv7-R because that only supports 'low' (default) or 'high'. - ldr r0, =_vector_table - mcr p15, 0, r0, c12, c0, 0 - ldr r0, =_core1_stack_pointer - ldr r0, [r0] - // set up our stacks using that stack pointer - also switches to SYS mode - bl _stack_setup - // Zero all registers before calling kmain2 - mov r0, 0 - mov r1, 0 - mov r2, 0 - mov r3, 0 - mov r4, 0 - mov r5, 0 - mov r6, 0 - mov r7, 0 - mov r8, 0 - mov r9, 0 - mov r10, 0 - mov r11, 0 - mov r12, 0 - // call our kmain2 for core 1 - bl kmain2 - .size _start, . - _start - "#, - hactlr_bits = const { - Hactlr::new_with_raw_value(0) - .with_cpuactlr(true) - .with_cdbgdci(true) - .with_flashifregionr(true) - .with_periphpregionr(true) - .with_qosr(true) - .with_bustimeoutr(true) - .with_intmonr(true) - .with_err(true) - .with_testr1(true) - .raw_value() - }, - sys_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Sys) - .with_i(true) - .with_f(true) - .raw_value() - } -); - -// Initialise the stack for each mode -#[cfg(target_arch = "arm")] -core::arch::global_asm!( - r#" - // Work around https://github.com/rust-lang/rust/issues/127269 - .fpu vfp2 - - // Configure a stack for every mode. Leaves you in sys mode. - // - // Pass in stack top in r0. - .section .text._stack_setup - .arm - .global _stack_setup - .type _stack_setup, %function - _stack_setup: - // Save LR from whatever mode we're currently in - mov r2, lr - // (we might not be in the same mode when we return). - // Set stack pointer (right after) and mask interrupts for for UND mode (Mode 0x1B) - msr cpsr_c, {und_mode} - mov sp, r0 - ldr r1, =_und_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for SVC mode (Mode 0x13) - msr cpsr_c, {svc_mode} - mov sp, r0 - ldr r1, =_svc_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for ABT mode (Mode 0x17) - msr cpsr_c, {abt_mode} - mov sp, r0 - ldr r1, =_abt_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for IRQ mode (Mode 0x12) - msr cpsr_c, {irq_mode} - mov sp, r0 - ldr r1, =_irq_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for FIQ mode (Mode 0x11) - msr cpsr_c, {fiq_mode} - mov sp, r0 - ldr r1, =_fiq_stack_size - sub r0, r0, r1 - // Set stack pointer (right after) and mask interrupts for for System mode (Mode 0x1F) - msr cpsr_c, {sys_mode} - mov sp, r0 - // Clear the Thumb Exception bit because all our targets are currently - // for Arm (A32) mode - mrc p15, 0, r1, c1, c0, 0 - bic r1, #{te_bit} - mcr p15, 0, r1, c1, c0, 0 - // return to caller - bx r2 - .size _stack_setup, . - _stack_setup - "#, - und_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Und) - .with_i(true) - .with_f(true) - .raw_value() - }, - svc_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Svc) - .with_i(true) - .with_f(true) - .raw_value() - }, - abt_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Abt) - .with_i(true) - .with_f(true) - .raw_value() - }, - fiq_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Fiq) - .with_i(true) - .with_f(true) - .raw_value() - }, - irq_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Irq) - .with_i(true) - .with_f(true) - .raw_value() - }, - sys_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Sys) - .with_i(true) - .with_f(true) - .raw_value() - }, - te_bit = const { Sctlr::new_with_raw_value(0).with_te(true).raw_value() } -); diff --git a/examples/versatileab/.cargo/config.toml b/examples/versatileab/.cargo/config.toml index cb76cb6e..41913a6c 100644 --- a/examples/versatileab/.cargo/config.toml +++ b/examples/versatileab/.cargo/config.toml @@ -1,2 +1,51 @@ +[target.armv7r-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" + +[target.thumbv7r-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5f -semihosting -nographic -audio none -kernel" + +[target.armv7r-none-eabi] +# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise +# qemu-system-arm will lock up +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" + +[target.thumbv7r-none-eabi] +# change '-mcpu=cortex-r5' to '-mcpu=cortex-r5f' if you use eabi-fpu feature, otherwise +# qemu-system-arm will lock up +runner = "qemu-system-arm -machine versatileab -cpu cortex-r5 -semihosting -nographic -audio none -kernel" + +[target.armv7a-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.thumbv7a-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.armv7a-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.thumbv7a-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu cortex-a8 -semihosting -nographic -audio none -kernel" + +[target.armv6-none-eabihf] +runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" + +[target.armv6-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" + +[target.thumbv6-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm1176 -semihosting -nographic -audio none -kernel" + +[target.armv5te-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + +[target.thumbv5te-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + +[target.armv4t-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + +[target.thumbv4t-none-eabi] +runner = "qemu-system-arm -machine versatileab -cpu arm926 -semihosting -nographic -audio none -kernel" + [build] target = "armv7r-none-eabihf" diff --git a/justfile b/justfile index cf8f315f..9990aab9 100644 --- a/justfile +++ b/justfile @@ -27,6 +27,8 @@ clean: rm -rf examples/versatileab/target-d32 cd examples/mps3-an536 && cargo clean rm -rf examples/mps3-an536/target-d32 + cd examples/mps3-an536-smp && cargo clean + rm -rf examples/mps3-an536-smp/target-d32 # Builds our workspace for all targets build-all: \ @@ -151,7 +153,7 @@ clippy-host: cd arm-targets && cargo clippy {{verbose}} # Run all the tests -test: test-cargo test-qemu test-smp +test: test-cargo test-qemu # Run the unit tests with cargo test-cargo: @@ -161,7 +163,7 @@ test-cargo: cd arm-targets && cargo test {{verbose}} # Run the integration tests in QEMU -test-qemu: test-qemu-v4t test-qemu-v5te test-qemu-v6 test-qemu-v7a test-qemu-v7r test-qemu-v8r +test-qemu: test-qemu-v4t test-qemu-v5te test-qemu-v6 test-qemu-v7a test-qemu-v7r test-qemu-v8r test-qemu-v8r-smp test-qemu-v4t: #!/bin/bash @@ -214,9 +216,11 @@ test-qemu-v8r: RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536 thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 if [ "${FAIL}" == "1" ]; then exit 1; fi -# Run the special SMP test -# -# You can't run the normal examples with two CPUs because nothing stops the second CPU from running :/. So we have -# a special test for SMP mode on the MPS3-AN536 -test-smp: - cd examples/mps3-an536 && cargo run --target=armv8r-none-eabihf --bin smp_test {{verbose}} -- --smp 2 +test-qemu-v8r-smp: + #!/bin/bash + FAIL=0 + ./tests.sh examples/mps3-an536-smp armv8r-none-eabihf {{verbose}} --release || FAIL=1 + ./tests.sh examples/mps3-an536-smp thumbv8r-none-eabihf -Zbuild-std=core {{verbose}} --release || FAIL=1 + RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-smp armv8r-none-eabihf --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 + RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-smp thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 + if [ "${FAIL}" == "1" ]; then exit 1; fi From e50781a6acc65f7777cf6b363b5e0356da5572a6 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Mon, 16 Feb 2026 22:40:42 +0000 Subject: [PATCH 2/5] Add GIC SGI SMP example. We send a Software Generated Interrupt from Core 0 to Core 1, and then another one back the other way. --- .../reference/gic-armv8r-none-eabihf.out | 16 ++ .../reference/gic-thumbv8r-none-eabihf.out | 16 ++ examples/mps3-an536-smp/src/bin/gic.rs | 168 ++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-smp/src/bin/gic.rs diff --git a/examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out new file mode 100644 index 00000000..6a49ec52 --- /dev/null +++ b/examples/mps3-an536-smp/reference/gic-armv8r-none-eabihf.out @@ -0,0 +1,16 @@ +I am core 0 - Mpidr(80000000) +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Configure SGI on both cores... +I am core 1 - Mpidr(80000001) +Calling git.init_cpu(1) +Send SGI to other core +> IRQ on Mpidr(80000001) +- handle_interrupt_with_id(SGI 3) +- send SGI back to first core +> IRQ on Mpidr(80000000) +- handle_interrupt_with_id(SGI 3) +< IRQ on Mpidr(80000001) +< IRQ on Mpidr(80000000) +Got pong diff --git a/examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out b/examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..6a49ec52 --- /dev/null +++ b/examples/mps3-an536-smp/reference/gic-thumbv8r-none-eabihf.out @@ -0,0 +1,16 @@ +I am core 0 - Mpidr(80000000) +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Configure SGI on both cores... +I am core 1 - Mpidr(80000001) +Calling git.init_cpu(1) +Send SGI to other core +> IRQ on Mpidr(80000001) +- handle_interrupt_with_id(SGI 3) +- send SGI back to first core +> IRQ on Mpidr(80000000) +- handle_interrupt_with_id(SGI 3) +< IRQ on Mpidr(80000001) +< IRQ on Mpidr(80000000) +Got pong diff --git a/examples/mps3-an536-smp/src/bin/gic.rs b/examples/mps3-an536-smp/src/bin/gic.rs new file mode 100644 index 00000000..af2ad191 --- /dev/null +++ b/examples/mps3-an536-smp/src/bin/gic.rs @@ -0,0 +1,168 @@ +//! # Cross-core GIC example for Arm Cortex-R52 on an MPS2-AN536 + +#![no_std] +#![no_main] + +use core::cell::RefCell; +use core::sync::atomic::{AtomicBool, Ordering}; + +use arm_gic::{ + IntId, + gicv3::{GicCpuInterface, Group, InterruptGroup, SgiTarget, SgiTargetGroup}, +}; +use critical_section::Mutex; +use semihosting::println; + +use aarch32_rt::entry; + +static CORE1_BOOTED: AtomicBool = AtomicBool::new(false); + +static PING_PONG_COMPLETE: AtomicBool = AtomicBool::new(false); + +/// How long core 0 waits for core 1 +const CORE0_WILL_WAIT: usize = 100_000_000; + +/// Shared interrupt controller driver +static GLOBAL_GIC: Mutex>>> = + Mutex::new(RefCell::new(None)); + +const SGI_INTID: IntId = IntId::sgi(3); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `aarch32-rt`. +#[entry] +fn main() -> ! { + println!( + "I am core 0 - {:08x?}", + aarch32_cpu::register::Mpidr::read() + ); + + let mut gic = unsafe { mps3_an536_smp::make_gic() }; + + // Configure two Software Generated Interrupts for Core 0 + println!("Configure SGI on both cores..."); + gic.set_interrupt_priority(SGI_INTID, Some(0), 0x31) + .unwrap(); + gic.set_group(SGI_INTID, Some(0), Group::Group1NS).unwrap(); + gic.enable_interrupt(SGI_INTID, Some(0), true).unwrap(); + + gic.set_interrupt_priority(SGI_INTID, Some(1), 0x31) + .unwrap(); + gic.set_group(SGI_INTID, Some(1), Group::Group1NS).unwrap(); + gic.enable_interrupt(SGI_INTID, Some(1), true).unwrap(); + + unsafe { + aarch32_cpu::interrupt::enable(); + } + + critical_section::with(|cs| { + let mut global_gic = GLOBAL_GIC.borrow_ref_mut(cs); + global_gic.replace(gic); + }); + + mps3_an536_smp::start_core1(); + + // wait some time for core 1 to start + for counter in 0..=CORE0_WILL_WAIT { + if CORE1_BOOTED.load(Ordering::SeqCst) { + break; + } + if counter == CORE0_WILL_WAIT { + println!("CPU 1 is missing?!"); + + mps3_an536_smp::exit(0); + } + } + + // Send it + println!("Send SGI to other core"); + GicCpuInterface::send_sgi( + SGI_INTID, + SgiTarget::List { + affinity3: 0, + affinity2: 0, + affinity1: 0, + target_list: 0b10, + }, + SgiTargetGroup::CurrentGroup1, + ) + .unwrap(); + + // let the other core finish + for _ in 0..CORE0_WILL_WAIT { + aarch32_cpu::asm::wfi(); + if PING_PONG_COMPLETE.load(Ordering::Relaxed) { + println!("Got pong"); + break; + } + } + + mps3_an536_smp::exit(0); +} + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code below, on Core 1. +#[unsafe(no_mangle)] +pub extern "C" fn kmain2() { + println!( + "I am core 1 - {:08x?}", + aarch32_cpu::register::Mpidr::read() + ); + + unsafe { + aarch32_cpu::interrupt::enable(); + } + + critical_section::with(|cs| { + let mut global_gic = GLOBAL_GIC.borrow_ref_mut(cs); + let global_gic = global_gic.as_mut().unwrap(); + semihosting::println!("Calling git.init_cpu(1)"); + global_gic.init_cpu(1); + }); + GicCpuInterface::enable_group1(true); + GicCpuInterface::set_priority_mask(0xFF); + + CORE1_BOOTED.store(true, Ordering::SeqCst); + + loop { + aarch32_cpu::asm::wfi(); + } +} + +/// Called when either Arm CPU gets an IRQ +/// +/// Talks to the GICv3 to find out which interrupts are pending, +/// handles the interrupt, and then tells the GICv3 it has been handled. +#[aarch32_rt::irq] +fn irq_handler() { + let id = aarch32_cpu::register::Mpidr::read(); + println!("> IRQ on {:08x?}", id); + while let Some(next_int_id) = + GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1) + { + // handle the interrupt + println!("- handle_interrupt_with_id({:?})", next_int_id); + + if id.0 == 0x8000_0001 { + println!("- send SGI back to first core"); + GicCpuInterface::send_sgi( + SGI_INTID, + SgiTarget::List { + affinity3: 0, + affinity2: 0, + affinity1: 0, + target_list: 0b01, + }, + SgiTargetGroup::CurrentGroup1, + ) + .unwrap(); + } else { + PING_PONG_COMPLETE.store(true, Ordering::Relaxed); + } + + GicCpuInterface::end_interrupt(next_int_id, InterruptGroup::Group1); + } + println!("< IRQ on {:08x?}", id); +} From feddd77917fc02960e1258d3f9d1ee4f40c58b3e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 21 Feb 2026 12:03:42 +0000 Subject: [PATCH 3/5] Allocate stacks for all cores * The `_num_cores` symbol controls how many copies of each stack you get * Added better docs to link.x * Added `enum Stack` to aarch32-rt, which can fetch information about each stack * Simplified the SMP start-up code * Stacks and stack related code are now all in the same order If you want to put stacks into TCM, you'll need custom routines. This code assumes stacks all live in the same region of RAM. --- aarch32-rt/link.x | 212 ++++++++++++++++++----- aarch32-rt/src/lib.rs | 126 +++++++++----- aarch32-rt/src/stacks.rs | 136 +++++++++++++++ examples/mps3-an536-smp/memory.x | 65 +------ examples/mps3-an536-smp/src/lib.rs | 195 ++++----------------- examples/mps3-an536/src/bin/el2_hello.rs | 2 +- examples/mps3-an536/src/lib.rs | 72 +++----- examples/versatileab/src/lib.rs | 72 +++----- 8 files changed, 467 insertions(+), 413 deletions(-) create mode 100644 aarch32-rt/src/stacks.rs diff --git a/aarch32-rt/link.x b/aarch32-rt/link.x index bff28f54..09f77f0f 100644 --- a/aarch32-rt/link.x +++ b/aarch32-rt/link.x @@ -1,11 +1,41 @@ /* Basic AArch32 linker script. -You must supply a file called `memory.x` which defines the memory regions -'VECTORS', 'CODE', 'DATA', 'STACKS'. +You must supply a file called `memory.x` in your linker search path. It must +define Region Aliases 'VECTORS', 'CODE', 'DATA', 'STACKS'. -The stacks will be at the top of the STACKS region by default, use `_pack_stacks` -to overwrite default behaviour. +Here is an example `memory.x` file: + +------------- +MEMORY { + FLASH : ORIGIN = 0x08000000, LENGTH = 2M + SRAM : ORIGIN = 0x10000000, LENGTH = 512K +} + +REGION_ALIAS("VECTORS", FLASH); +REGION_ALIAS("CODE", FLASH); +REGION_ALIAS("DATA", SRAM); +REGION_ALIAS("STACKS", SRAM); +------------- + +The AArch32 platform uses seven separate stacks. The default sizes for each are +given at the bottom of this file. However, your `memory.x` can provide an +alternative size for any (or all) of them, provided that size is a multiple of +eight bytes. For example, your `memory.x` might include: + +------------- +PROVIDE(_und_stack_size = 3456); +PROVIDE(_svc_stack_size = 3456); +PROVIDE(_abt_stack_size = 3456); +PROVIDE(_hyp_stack_size = 3456); +PROVIDE(_irq_stack_size = 3456); +PROVIDE(_fiq_stack_size = 3456); +PROVIDE(_sys_stack_size = 3456); +------------- + +The stacks will be located at the top of the STACKS region by default. Use +`PROVIDE(_pack_stacks = 0)` to remove the padding and locate the stacks at the +bottom of that region instead. Based upon the linker script from https://github.com/rust-embedded/cortex-m */ @@ -18,26 +48,55 @@ EXTERN(_start); EXTERN(_default_handler); SECTIONS { + /* # Vector Table + * + * Our ARM interrupt vector table, consisting of branch instructions to + * each exception handler. + * + * May include FIQ handler code at the end. + */ .vector_table ORIGIN(VECTORS) : { - /* The vector table must come first */ *(.vector_table) } > VECTORS + /* # Text + * + * Our executable code. + */ .text : { - /* Now the rest of the code */ + __stext = .; + *(.text .text*) + + __etext = .; } > CODE + /* # Text + * + * Our constants. + */ .rodata : { + __srodata = .; + *(.rodata .rodata*) + + __erodata = .; } > CODE + /* # Data + * + * Our global variables that are not initialised to zero. + */ .data : ALIGN(4) { . = ALIGN(4); __sdata = .; + *(.data .data.*); + . = ALIGN(4); + /* NB: __edata defined lower down */ } > DATA AT>CODE + /* * Allow sections from user `memory.x` injected using `INSERT AFTER .data` to * use the .data loading mechanism by pushing __edata. Note: do not change @@ -49,12 +108,20 @@ SECTIONS { /* LMA of .data */ __sidata = LOADADDR(.data); + /* # Block Starting Symbol (BSS) + * + * Our global variables that *are* initialised to zero. + */ .bss (NOLOAD) : ALIGN(4) { . = ALIGN(4); __sbss = .; + *(.bss .bss* COMMON) + . = ALIGN(4); + /* NB: __ebss defined lower down */ } > DATA + /* * Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to * use the .bss zeroing mechanism by pushing __ebss. Note: do not change @@ -62,15 +129,28 @@ SECTIONS { */ __ebss = .; + /* # Uninitialised Data + * + * Our global variables that have no defined initial value. + */ .uninit (NOLOAD) : ALIGN(4) { . = ALIGN(4); __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); __euninit = .; } > DATA + + /* # Stack Padding + * + * A padding region to push the stacks to the top of the STACKS region. + * If `_pack_stacks == 0`, this is forced to be zero size, putting the + * stacks at the bottom of the STACK region. + */ .filler (NOLOAD) : { /* Move the .stacks section to the end of the STACKS memory region */ _next_region = ORIGIN(STACKS) + LENGTH(STACKS); @@ -80,61 +160,81 @@ SECTIONS { . = _start_stacks; } > STACKS + /* # Stacks + * + * Space for all seven stacks. + */ .stacks (NOLOAD) : ALIGN(8) { . = ALIGN(8); + + /* Lowest address of allocated stack */ _stacks_low_end = .; - _sys_stack_end = .; - . += _sys_stack_size; - . = ALIGN(8); - _sys_stack = .; - _fiq_stack_end = .; - . += _fiq_stack_size; - . = ALIGN(8); - _fiq_stack = .; - _irq_stack_end = .; - . += _irq_stack_size; - . = ALIGN(8); - _irq_stack = .; - _abt_stack_end = .; - . += _abt_stack_size; - . = ALIGN(8); - _abt_stack = .; - _svc_stack_end = .; - . += _svc_stack_size; - . = ALIGN(8); - _svc_stack = .; - _und_stack_end = .; - . += _und_stack_size; - . = ALIGN(8); - _und_stack = .; - _hyp_stack_end = .; - . += _hyp_stack_size; - . = ALIGN(8); - _hyp_stack = .; + + /* Stack for UND mode */ + _und_stack_low_end = .; + . += (_und_stack_size * _num_cores); + _und_stack_high_end = .; + + /* Stack for SVC mode */ + _svc_stack_low_end = .; + . += (_svc_stack_size * _num_cores); + _svc_stack_high_end = .; + + /* Stack for ABT mode */ + _abt_stack_low_end = .; + . += (_abt_stack_size * _num_cores); + _abt_stack_high_end = .; + + /* Stack for HYP mode */ + _hyp_stack_low_end = .; + . += (_hyp_stack_size * _num_cores); + _hyp_stack_high_end = .; + + /* Stack for IRQ mode */ + _irq_stack_low_end = .; + . += (_irq_stack_size * _num_cores); + _irq_stack_high_end = .; + + /* Stack for FIQ mode */ + _fiq_stack_low_end = .; + . += (_fiq_stack_size * _num_cores); + _fiq_stack_high_end = .; + + /* Stack for SYS mode */ + _sys_stack_low_end = .; + . += (_sys_stack_size * _num_cores); + _sys_stack_high_end = .; + + /* Highest address of allocated stack */ _stacks_high_end = .; } > STACKS /DISCARD/ : { + /* Discard any notes */ *(.note .note*) + /* Discard these unwinding/exception related symbols, they are not used */ *(.ARM.exidx* .gnu.linkonce.armexidx.*) + /* Discard these exception related symbols, they are not used */ *(.ARM.extab* .gnu.linkonce.armextab.*) } } -/* We provide default sizes for the stacks to be overwritten in memory.x */ -PROVIDE(_stack_top = _stacks_high_end); /* deprecated, use _xxx_stack labels as defined in .stacks section */ -PROVIDE(_hyp_stack_size = 0x400); -PROVIDE(_und_stack_size = 0x400); -PROVIDE(_svc_stack_size = 0x400); -PROVIDE(_abt_stack_size = 0x400); -PROVIDE(_irq_stack_size = 0x400); -PROVIDE(_fiq_stack_size = 0x400); -PROVIDE(_sys_stack_size = 0x2000); -PROVIDE(_pack_stacks = 0); /* set this to 1 to remove the filler section pushing the stacks to the end of STACKS. */ +/* We provide default sizes for the stacks for any not specified in memory.x (which was loaded first) */ +PROVIDE(_und_stack_size = 16K); +PROVIDE(_svc_stack_size = 16K); +PROVIDE(_abt_stack_size = 16K); +PROVIDE(_hyp_stack_size = 16K); +PROVIDE(_irq_stack_size = 64); +PROVIDE(_fiq_stack_size = 64); +PROVIDE(_sys_stack_size = 16K); +/* Default to one CPU core (i.e. one copy of each stack) */ +PROVIDE(_num_cores = 1); +/* Set this to 1 in memory.x to remove the filler section pushing the stacks to the end of STACKS. */ +PROVIDE(_pack_stacks = 0); /* Weak aliases for ASM default handlers */ PROVIDE(_start = _default_start); @@ -143,6 +243,7 @@ PROVIDE(_asm_svc_handler = _asm_default_svc_handler); PROVIDE(_asm_hvc_handler = _asm_default_hvc_handler); PROVIDE(_asm_prefetch_abort_handler = _asm_default_prefetch_abort_handler); PROVIDE(_asm_data_abort_handler = _asm_default_data_abort_handler); +/* TODO: Hyp handler goes here */ PROVIDE(_asm_irq_handler = _asm_default_irq_handler); PROVIDE(_asm_fiq_handler = _asm_default_fiq_handler); @@ -152,5 +253,26 @@ PROVIDE(_svc_handler = _default_handler); PROVIDE(_hvc_handler = _default_handler); PROVIDE(_prefetch_abort_handler = _default_handler); PROVIDE(_data_abort_handler = _default_handler); +/* TODO: Hyp handler goes here */ PROVIDE(_irq_handler = _default_handler); -/* There is no default C-language FIQ handler */ +/* NB: There is no default C-language FIQ handler */ + +/* Check the stack sizes are all a multiple of eight bytes */ +ASSERT(_und_stack_size % 8 == 0, " +ERROR(aarch32-rt): UND stack size (_und_stack_size) is not a multiple of 8 bytes"); +ASSERT(_svc_stack_size % 8 == 0, " +ERROR(aarch32-rt): SVC stack size (_svc_stack_size) is not a multiple of 8 bytes"); +ASSERT(_abt_stack_size % 8 == 0, " +ERROR(aarch32-rt): ABT stack size (_abt_stack_size) is not a multiple of 8 bytes"); +ASSERT(_hyp_stack_size % 8 == 0, " +ERROR(aarch32-rt): HYP stack size (_hyp_stack_size) is not a multiple of 8 bytes"); +ASSERT(_irq_stack_size % 8 == 0, " +ERROR(aarch32-rt): IRQ stack size (_irq_stack_size) is not a multiple of 8 bytes"); +ASSERT(_fiq_stack_size % 8 == 0, " +ERROR(aarch32-rt): FIQ stack size (_fiq_stack_size) is not a multiple of 8 bytes"); +ASSERT(_sys_stack_size % 8 == 0, " +ERROR(aarch32-rt): SYS stack size (_sys_stack_size) is not a multiple of 8 bytes"); +ASSERT(_num_cores != 0, " +ERROR(aarch32-rt): Number of cores cannot be zero"); + +/* End of file */ diff --git a/aarch32-rt/src/lib.rs b/aarch32-rt/src/lib.rs index 0ca26461..a6445963 100644 --- a/aarch32-rt/src/lib.rs +++ b/aarch32-rt/src/lib.rs @@ -39,6 +39,8 @@ //! //! ## Constants //! +//! * `_num_cores` - the number of CPU core (and hence the number of copies of +//! each stack). Must be > 0. //! * `__sbss` - the start of zero-initialised data in RAM. Must be 4-byte //! aligned. //! * `__ebss` - the end of zero-initialised data in RAM. Must be 4-byte @@ -70,29 +72,29 @@ //! //! Stacks are located in `.stacks` section which is mapped to the `STACKS` //! memory region. Per default, the stacks are pushed to the end of the `STACKS` -//! by a filler section. +//! by a filler section. We allocate stacks for each core, based on the +//! `_num_cores` linker symbol. //! //! The stacks look like: //! //! ```text -//! +------------------+ <----_stack_top equals ORIGIN(STACKS) + LENGTH(STACKS) -//! | HYP Stack | } _hyp_stack_size bytes (Armv8-R only) +//! +------------------+ <---- ORIGIN(STACKS) + LENGTH(STACKS) +//! | SYS Stack | } _sys_stack_size * _num_cores bytes //! +------------------+ -//! | UND Stack | } _und_stack_size bytes +//! | FIQ Stack | } _fiq_stack_size * _num_cores bytes //! +------------------+ -//! | SVC Stack | } _svc_stack_size bytes +//! | IRQ Stack | } _irq_stack_size * _num_cores bytes //! +------------------+ -//! | ABT Stack | } _abt_stack_size bytes +//! | HYP Stack | } _hyp_stack_size * _num_cores bytes (only used on Armv8-R) //! +------------------+ -//! | IRQ Stack | } _irq_stack_size bytes +//! | ABT Stack | } _abt_stack_size * _num_cores bytes //! +------------------+ -//! | FIQ Stack | } _fiq_stack_size bytes +//! | SVC Stack | } _svc_stack_size * _num_cores bytes //! +------------------+ -//! | SYS Stack | } _sys_stack_size bytes +//! | UND Stack | } _und_stack_size * _num_cores bytes //! +------------------+ -//! | filler section | } calculated so that _stack_top equals end of STACKS -//! +------------------+ <----either ORIGIN(STACKS) or the end of previous -//! section located in STACKS or its alias. +//! | filler section | +//! +------------------+ <---- ORIGIN(STACKS) //! ``` //! //! Our linker script PROVIDEs a symbol `_pack_stacks`. By setting this symbol @@ -117,8 +119,9 @@ //! You can also create a 'kmain' function by using the `#[entry]` attribute on //! a normal Rust function. The function will be renamed in such a way that the //! start-up assembly code can find it, but normal Rust code cannot. Therefore -//! you can be assured that the function will only be called once (unless someone -//! resorts to `unsafe` Rust to import the `kmain` symbol as an `extern "C" fn`). +//! you can be assured that the function will only be called once (unless +//! someone resorts to `unsafe` Rust to import the `kmain` symbol as an `extern +//! "C" fn`). //! //! ```rust //! use aarch32_rt::entry; @@ -502,26 +505,23 @@ //! `_irq_handler` //! * `_asm_default_fiq_handler` - an FIQ handler that just spins //! * `_default_handler` - a C compatible function that spins forever. -//! * `_init_segments` - initialises `.bss` and `.data` +//! * `_init_segments` - initialises `.bss` and `.data` and zeroes the stacks //! * `_stack_setup_preallocated` - initialises UND, SVC, ABT, IRQ, FIQ and SYS //! stacks from the `.stacks` section defined in link.x, based on -//! _xxx_stack_size values -//! * `_xxx_stack` and `_xxx_stack_end` where the former is the top and the latter -//! the bottom of the stack for each mode (`und`, `svc`, `abt`, `irq`, `fiq`, `sys`) -//! * `_stack_top` - the address of the top of the STACKS region that contains -//! the reseved stacks, with eight-byte alignment. -//! Using this symbol is deprecated, stacks should be initialized by their -//! individual _xxx_stack symbols -//! -//! The assembly language trampolines are required because AArch32 -//! processors do not save a great deal of state on entry to an exception -//! handler, unlike Armv7-M (and other M-Profile) processors. We must therefore -//! save this state to the stack using assembly language, before transferring to -//! an `extern "C"` function. We do not change modes before entering that -//! `extern "C"` function - that's for the handler to deal with as it wishes. -//! Because FIQ is often performance-sensitive, we don't supply an FIQ -//! trampoline; if you want to use FIQ, you have to write your own assembly -//! routine, allowing you to preserve only whatever state is important to you. +//! _xxx_stack_size values, and the core number given in `r0` +//! * `_xxx_stack_high_end` and `_xxx_stack_low_end` where the former is the top +//! and the latter the bottom of the stack for each mode (`und`, `svc`, `abt`, +//! `irq`, `fiq`, `sys`) +//! +//! The assembly language trampolines are required because AArch32 processors do +//! not save a great deal of state on entry to an exception handler, unlike +//! Armv7-M (and other M-Profile) processors. We must therefore save this state +//! to the stack using assembly language, before transferring to an `extern "C"` +//! function. We do not change modes before entering that `extern "C"` function +//! - that's for the handler to deal with as it wishes. Because FIQ is often +//! performance-sensitive, we don't supply an FIQ trampoline; if you want to use +//! FIQ, you have to write your own assembly routine, allowing you to preserve +//! only whatever state is important to you. //! //! ## Examples //! @@ -558,6 +558,8 @@ mod arch_v7; ))] mod arch_v4; +pub mod stacks; + /// Our default exception handler. /// /// We end up here if an exception fires and the weak 'PROVIDE' in the link.x @@ -795,38 +797,58 @@ core::arch::global_asm!( // Configure a stack for every mode. Leaves you in sys mode. // + // Pass the core number in r0 .section .text._stack_setup_preallocated .global _stack_setup_preallocated + .arm .type _stack_setup_preallocated, %function _stack_setup_preallocated: // Save LR from whatever mode we're currently in - mov r2, lr + mov r3, lr // (we might not be in the same mode when we return). // Set stack pointer and mask interrupts for UND mode (Mode 0x1B) msr cpsr_c, {und_mode} - ldr r13, =_und_stack + ldr r2, =_und_stack_high_end + ldr r1, =_und_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for SVC mode (Mode 0x13) msr cpsr_c, {svc_mode} - ldr r13, =_svc_stack + ldr r2, =_svc_stack_high_end + ldr r1, =_svc_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for ABT mode (Mode 0x17) msr cpsr_c, {abt_mode} - ldr r13, =_abt_stack + ldr r2, =_abt_stack_high_end + ldr r1, =_abt_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for IRQ mode (Mode 0x12) msr cpsr_c, {irq_mode} - ldr r13, =_irq_stack + ldr r2, =_irq_stack_high_end + ldr r1, =_irq_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for FIQ mode (Mode 0x11) msr cpsr_c, {fiq_mode} - ldr r13, =_fiq_stack + ldr r2, =_fiq_stack_high_end + ldr r1, =_fiq_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Set stack pointer (right after) and mask interrupts for System mode (Mode 0x1F) msr cpsr_c, {sys_mode} - ldr r13, =_sys_stack + ldr r2, =_sys_stack_high_end + ldr r1, =_sys_stack_size + muls r1, r1, r0 + subs sp, r2, r1 // Clear the Thumb Exception bit because all vector table is written in Arm assembly // even on Thumb targets. mrc p15, 0, r1, c1, c0, 0 bic r1, #{te_bit} mcr p15, 0, r1, c1, c0, 0 // return to caller - bx r2 + bx r3 .size _stack_setup_preallocated, . - _stack_setup_preallocated // Initialises stacks, .data and .bss @@ -835,7 +857,7 @@ core::arch::global_asm!( .global _init_segments .type _init_segments, %function _init_segments: - // Initialise .bss + // Zero .bss ldr r0, =__sbss ldr r1, =__ebss mov r2, 0 @@ -844,6 +866,16 @@ core::arch::global_asm!( beq 1f stm r0!, {{r2}} b 0b + 1: + // Zero the stacks + ldr r0, =_stacks_low_end + ldr r1, =_stacks_high_end + mov r2, 0 + 0: + cmp r1, r0 + beq 1f + stm r0!, {{r2}} + b 0b 1: // Initialise .data ldr r0, =__sdata @@ -923,10 +955,11 @@ core::arch::global_asm!( .global _default_start .type _default_start, %function _default_start: - // Set up stacks. - bl _stack_setup_preallocated // Init .data and .bss bl _init_segments + // Set up stacks. + mov r0, #0 + bl _stack_setup_preallocated "#, fpu_enable!(), r#" @@ -975,7 +1008,7 @@ core::arch::global_asm!( cmp r0, {cpsr_mode_hyp} bne 1f // Set stack pointer - ldr sp, =_hyp_stack + ldr sp, =_hyp_stack_high_end // Set the HVBAR (for EL2) to _vector_table ldr r1, =_vector_table mcr p15, 4, r1, c12, c0, 0 @@ -993,14 +1026,15 @@ core::arch::global_asm!( isb eret 1: - // Set up stacks. - bl _stack_setup_preallocated // Set the VBAR (for EL1) to _vector_table. NB: This isn't required on // Armv7-R because that only supports 'low' (default) or 'high'. ldr r0, =_vector_table mcr p15, 0, r0, c12, c0, 0 // Init .data and .bss bl _init_segments + // Set up stacks. + mov r0, #0 + bl _stack_setup_preallocated "#, fpu_enable!(), r#" diff --git a/aarch32-rt/src/stacks.rs b/aarch32-rt/src/stacks.rs new file mode 100644 index 00000000..6de2b2f5 --- /dev/null +++ b/aarch32-rt/src/stacks.rs @@ -0,0 +1,136 @@ +//! Code for examining linker allocated stacks + +/// Represents one of the AArch32 stacks +#[derive(Debug, Copy, Clone)] +pub enum Stack { + /// UND mode stack + Und, + /// SVC mode stack + Svc, + /// ABT mode stack, for data abort and prefetch abort + Abt, + /// HYP mode stack, for EL2 + Hyp, + /// IRQ mode stack, for interrupts + Irq, + /// FIQ mode stack, for fast interrupts + Fiq, + /// SYS mode stack, for the main thread + Sys, +} + +impl core::fmt::Display for Stack { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "{}", + match self { + Stack::Und => "UND", + Stack::Svc => "SVC", + Stack::Abt => "ABT", + Stack::Hyp => "HYP", + Stack::Irq => "IRQ", + Stack::Fiq => "FIQ", + Stack::Sys => "SYS", + } + ) + } +} + +impl Stack { + /// Create an iterator over all the stacks + pub fn iter() -> impl Iterator { + [ + Stack::Und, + Stack::Svc, + Stack::Abt, + Stack::Hyp, + Stack::Irq, + Stack::Fiq, + Stack::Sys, + ] + .iter() + .cloned() + } + + /// Get the highest address of this stack, for the given core + pub fn top(&self, core: usize) -> Option<*const u32> { + if core > Self::num_cores() { + return None; + } + let top = self.stack_top(); + let core = core as isize; + let per_core_size_words = self.per_core_size_words(); + Some(unsafe { top.offset(-per_core_size_words * core) }) + } + + /// Get the lowest address of this stack, for the given core + pub fn bottom(&self, core: usize) -> Option<*const u32> { + self.top(core) + .map(|p| unsafe { p.offset(-self.per_core_size_words()) }) + } + + /// Get the range of this stack, for the given core + pub fn range(&self, core: usize) -> Option> { + if let (Some(bottom), Some(top)) = (self.bottom(core), self.top(core)) { + Some(bottom..top) + } else { + None + } + } + + /// Get number of cores in this system + pub fn num_cores() -> usize { + unsafe extern "C" { + safe static _num_cores: u8; + } + core::ptr::addr_of!(_num_cores) as usize + } + + /// Get the total size of this stack across all cores + pub fn per_core_size_words(&self) -> isize { + use core::ptr::addr_of; + unsafe extern "C" { + static _und_stack_size: u8; + static _svc_stack_size: u8; + static _abt_stack_size: u8; + static _hyp_stack_size: u8; + static _irq_stack_size: u8; + static _fiq_stack_size: u8; + static _sys_stack_size: u8; + } + let size_bytes = match self { + Stack::Und => addr_of!(_und_stack_size) as isize, + Stack::Svc => addr_of!(_svc_stack_size) as isize, + Stack::Abt => addr_of!(_abt_stack_size) as isize, + Stack::Hyp => addr_of!(_hyp_stack_size) as isize, + Stack::Irq => addr_of!(_irq_stack_size) as isize, + Stack::Fiq => addr_of!(_fiq_stack_size) as isize, + Stack::Sys => addr_of!(_sys_stack_size) as isize, + }; + size_bytes / 4 + } + + /// Get the top address for this stack + fn stack_top(&self) -> *const u32 { + use core::ptr::addr_of; + unsafe extern "C" { + static _und_stack_high_end: u32; + static _svc_stack_high_end: u32; + static _abt_stack_high_end: u32; + static _hyp_stack_high_end: u32; + static _irq_stack_high_end: u32; + static _fiq_stack_high_end: u32; + static _sys_stack_high_end: u32; + } + match self { + Stack::Und => addr_of!(_und_stack_high_end), + Stack::Svc => addr_of!(_svc_stack_high_end), + Stack::Abt => addr_of!(_abt_stack_high_end), + Stack::Hyp => addr_of!(_hyp_stack_high_end), + Stack::Irq => addr_of!(_irq_stack_high_end), + Stack::Fiq => addr_of!(_fiq_stack_high_end), + Stack::Sys => addr_of!(_sys_stack_high_end), + } + } +} diff --git a/examples/mps3-an536-smp/memory.x b/examples/mps3-an536-smp/memory.x index 0b784d24..c4aa51bb 100644 --- a/examples/mps3-an536-smp/memory.x +++ b/examples/mps3-an536-smp/memory.x @@ -15,68 +15,5 @@ REGION_ALIAS("CODE", QSPI); REGION_ALIAS("DATA", BRAM); REGION_ALIAS("STACKS", BRAM); -SECTIONS { - /* ### Interrupt Handler Entries - * - * The IRQ handler walks this section to find registered - * interrupt handlers - */ - .irq_entries : ALIGN(4) - { - /* We put this in the header */ - __irq_entries_start = .; - /* Here are the entries */ - KEEP(*(.irq_entries)); - /* Keep this block a nice round size */ - . = ALIGN(4); - /* We put this in the header */ - __irq_entries_end = .; - } > CODE -} INSERT AFTER .text; - -SECTIONS { - .core1_stacks (NOLOAD) : ALIGN(8) - { - . = ALIGN(8); - _core1_stacks_low_end = .; - _core1_sys_stack_end = .; - . += _sys_stack_size; - . = ALIGN(8); - _core1_sys_stack = .; - _core1_fiq_stack_end = .; - . += _fiq_stack_size; - . = ALIGN(8); - _core1_fiq_stack = .; - _core1_irq_stack_end = .; - . += _irq_stack_size; - . = ALIGN(8); - _core1_irq_stack = .; - _core1_abt_stack_end = .; - . += _abt_stack_size; - . = ALIGN(8); - _core1_abt_stack = .; - _core1_svc_stack_end = .; - . += _svc_stack_size; - . = ALIGN(8); - _core1_svc_stack = .; - _core1_und_stack_end = .; - . += _und_stack_size; - . = ALIGN(8); - _core1_und_stack = .; - _core1_hyp_stack_end = .; - . += _hyp_stack_size; - . = ALIGN(8); - _core1_hyp_stack = .; - _core1_stacks_high_end = .; - } > STACKS -} INSERT BEFORE .filler; - +PROVIDE(_num_cores = 2); PROVIDE(kmain2 = default_kmain2); - -PROVIDE(_hyp_stack_size = 16K); -PROVIDE(_und_stack_size = 16K); -PROVIDE(_svc_stack_size = 16K); -PROVIDE(_abt_stack_size = 16K); -PROVIDE(_irq_stack_size = 64); -PROVIDE(_fiq_stack_size = 64); -PROVIDE(_sys_stack_size = 16K); diff --git a/examples/mps3-an536-smp/src/lib.rs b/examples/mps3-an536-smp/src/lib.rs index 2af619ed..e3a798e2 100644 --- a/examples/mps3-an536-smp/src/lib.rs +++ b/examples/mps3-an536-smp/src/lib.rs @@ -53,7 +53,7 @@ #![no_std] -use aarch32_cpu::register::{Hactlr, Sctlr, Cpsr, cpsr::ProcessorMode}; +use aarch32_cpu::register::{Hactlr, Cpsr, cpsr::ProcessorMode}; use core::sync::atomic::{AtomicBool, Ordering}; @@ -100,83 +100,44 @@ pub fn exit(code: i32) -> ! { /// /// ```text /// Stack usage report: -/// SYS Stack = 332 used of 16384 bytes (002%) @ 0x1006bf80..0x1006ff80 -/// FIQ Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 -/// IRQ Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 -/// ABT Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 -/// SVC Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 -/// UND Stack = 244 used of 16384 bytes (001%) @ 0x10078000..0x1007c000 -/// HYP Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +/// UND1 Stack = 0 used of 16384 bytes (000%) @ 0x10057f00..0x1005bf00 +/// UND0 Stack = 0 used of 16384 bytes (000%) @ 0x1005bf00..0x1005ff00 +/// SVC1 Stack = 0 used of 16384 bytes (000%) @ 0x1005ff00..0x10063f00 +/// SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x10063f00..0x10067f00 +/// ABT1 Stack = 0 used of 16384 bytes (000%) @ 0x10067f00..0x1006bf00 +/// ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x1006bf00..0x1006ff00 +/// HYP1 Stack = 0 used of 16384 bytes (000%) @ 0x1006ff00..0x10073f00 +/// HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x10073f00..0x10077f00 +/// IRQ1 Stack = 0 used of 64 bytes (000%) @ 0x10077f00..0x10077f40 +/// IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x10077f40..0x10077f80 +/// FIQ1 Stack = 0 used of 64 bytes (000%) @ 0x10077f80..0x10077fc0 +/// FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x10077fc0..0x10078000 +/// SYS1 Stack = 808 used of 16384 bytes (004%) @ 0x10078000..0x1007c000 +/// SYS0 Stack = 1432 used of 16384 bytes (008%) @ 0x1007c000..0x10080000 /// ``` fn stack_dump() { use aarch32_cpu::stacks::stack_used_bytes; - use core::ptr::addr_of; - - unsafe extern "C" { - static _sys_stack_end: u32; - static _sys_stack: u32; - static _fiq_stack_end: u32; - static _fiq_stack: u32; - static _irq_stack_end: u32; - static _irq_stack: u32; - static _abt_stack_end: u32; - static _abt_stack: u32; - static _svc_stack_end: u32; - static _svc_stack: u32; - static _und_stack_end: u32; - static _und_stack: u32; - static _hyp_stack_end: u32; - static _hyp_stack: u32; - - static _core1_sys_stack_end: u32; - static _core1_sys_stack: u32; - static _core1_fiq_stack_end: u32; - static _core1_fiq_stack: u32; - static _core1_irq_stack_end: u32; - static _core1_irq_stack: u32; - static _core1_abt_stack_end: u32; - static _core1_abt_stack: u32; - static _core1_svc_stack_end: u32; - static _core1_svc_stack: u32; - static _core1_und_stack_end: u32; - static _core1_und_stack: u32; - static _core1_hyp_stack_end: u32; - static _core1_hyp_stack: u32; - } - - // these are placed in the order they are in aarch32-rt/link.x - let stacks = [ - ("SYS0", addr_of!(_sys_stack_end)..addr_of!(_sys_stack)), - ("FIQ0", addr_of!(_fiq_stack_end)..addr_of!(_fiq_stack)), - ("IRQ0", addr_of!(_irq_stack_end)..addr_of!(_irq_stack)), - ("ABT0", addr_of!(_abt_stack_end)..addr_of!(_abt_stack)), - ("SVC0", addr_of!(_svc_stack_end)..addr_of!(_svc_stack)), - ("UND0", addr_of!(_und_stack_end)..addr_of!(_und_stack)), - ("HYP0", addr_of!(_hyp_stack_end)..addr_of!(_hyp_stack)), - ("SYS1", addr_of!(_core1_sys_stack_end)..addr_of!(_core1_sys_stack)), - ("FIQ1", addr_of!(_core1_fiq_stack_end)..addr_of!(_core1_fiq_stack)), - ("IRQ1", addr_of!(_core1_irq_stack_end)..addr_of!(_core1_irq_stack)), - ("ABT1", addr_of!(_core1_abt_stack_end)..addr_of!(_core1_abt_stack)), - ("SVC1", addr_of!(_core1_svc_stack_end)..addr_of!(_core1_svc_stack)), - ("UND1", addr_of!(_core1_und_stack_end)..addr_of!(_core1_und_stack)), - ("HYP1", addr_of!(_core1_hyp_stack_end)..addr_of!(_core1_hyp_stack)), - ]; + use aarch32_rt::stacks::Stack; semihosting::eprintln!("Stack usage report:"); unsafe { - for (name, range) in stacks { - let (total, used) = stack_used_bytes(range.clone()); - let percent = used * 100 / total; - // Send to stderr, so it doesn't mix with expected output on stdout - semihosting::eprintln!( - "{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", - name, - used, - total, - percent, - range - ); + for stack in Stack::iter() { + for core in (0..Stack::num_cores()).rev() { + let core_range = stack.range(core).unwrap(); + let (total, used) = stack_used_bytes(core_range.clone()); + let percent = used * 100 / total; + // Send to stderr, so it doesn't mix with expected output on stdout + semihosting::eprintln!( + "{}{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", + stack, + core, + used, + total, + percent, + core_range + ); + } } } } @@ -294,7 +255,8 @@ core::arch::global_asm!( ldr r0, =_vector_table mcr p15, 0, r0, c12, c0, 0 // set up our stacks - also switches to SYS mode - bl _core1_stack_setup_preallocated + movs r0, #1 + bl _stack_setup_preallocated // Zero all registers before calling kmain2 mov r0, 0 mov r1, 0 @@ -335,95 +297,6 @@ core::arch::global_asm!( }, ); -// Initialise the stack for Core 1 for each mode -#[cfg(target_arch = "arm")] -core::arch::global_asm!( - r#" - // Work around https://github.com/rust-lang/rust/issues/127269 - .fpu vfp2 - - // Configure a stack for every mode. Leaves you in sys mode. - // - .section .text._core1_stack_setup_preallocated - .arm - .global _core1_stack_setup_preallocated - .type _core1_stack_setup_preallocated, %function - _core1_stack_setup_preallocated: - // Save LR from whatever mode we're currently in - mov r2, lr - // (we might not be in the same mode when we return). - // Set stack pointer and mask interrupts for UND mode (Mode 0x1B) - msr cpsr_c, {und_mode} - ldr r13, =_core1_und_stack - // Set stack pointer (right after) and mask interrupts for SVC mode (Mode 0x13) - msr cpsr_c, {svc_mode} - ldr r13, =_core1_svc_stack - // Set stack pointer (right after) and mask interrupts for ABT mode (Mode 0x17) - msr cpsr_c, {abt_mode} - ldr r13, =_core1_abt_stack - // Set stack pointer (right after) and mask interrupts for IRQ mode (Mode 0x12) - msr cpsr_c, {irq_mode} - ldr r13, =_core1_irq_stack - // Set stack pointer (right after) and mask interrupts for FIQ mode (Mode 0x11) - msr cpsr_c, {fiq_mode} - ldr r13, =_core1_fiq_stack - // Set stack pointer (right after) and mask interrupts for System mode (Mode 0x1F) - msr cpsr_c, {sys_mode} - ldr r13, =_core1_sys_stack - // Clear the Thumb Exception bit because all vector table is written in Arm assembly - // even on Thumb targets. - mrc p15, 0, r1, c1, c0, 0 - bic r1, #{te_bit} - mcr p15, 0, r1, c1, c0, 0 - // return to caller - bx r2 - .size _core1_stack_setup_preallocated, . - _core1_stack_setup_preallocated - "#, - und_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Und) - .with_i(true) - .with_f(true) - .raw_value() - }, - svc_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Svc) - .with_i(true) - .with_f(true) - .raw_value() - }, - abt_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Abt) - .with_i(true) - .with_f(true) - .raw_value() - }, - fiq_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Fiq) - .with_i(true) - .with_f(true) - .raw_value() - }, - irq_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Irq) - .with_i(true) - .with_f(true) - .raw_value() - }, - sys_mode = const { - Cpsr::new_with_raw_value(0) - .with_mode(ProcessorMode::Sys) - .with_i(true) - .with_f(true) - .raw_value() - }, - te_bit = const { Sctlr::new_with_raw_value(0).with_te(true).raw_value() } -); - /// What a second core does when no `kmain2` is supplied. #[unsafe(no_mangle)] pub extern "C" fn default_kmain2() { diff --git a/examples/mps3-an536/src/bin/el2_hello.rs b/examples/mps3-an536/src/bin/el2_hello.rs index c1a52365..e10a979c 100644 --- a/examples/mps3-an536/src/bin/el2_hello.rs +++ b/examples/mps3-an536/src/bin/el2_hello.rs @@ -44,7 +44,7 @@ core::arch::global_asm!( .type _start, %function _start: // Set stack pointer - ldr sp, =_hyp_stack + ldr sp, =_hyp_stack_high_end // Set the HVBAR (for EL2) to _vector_table ldr r1, =_vector_table mcr p15, 4, r1, c12, c0, 0 diff --git a/examples/mps3-an536/src/lib.rs b/examples/mps3-an536/src/lib.rs index 72596eba..e3eb26b5 100644 --- a/examples/mps3-an536/src/lib.rs +++ b/examples/mps3-an536/src/lib.rs @@ -105,61 +105,37 @@ pub fn exit(code: i32) -> ! { /// /// ```text /// Stack usage report: -/// SYS Stack = 332 used of 16384 bytes (002%) @ 0x1006bf80..0x1006ff80 -/// FIQ Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 -/// IRQ Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 -/// ABT Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 -/// SVC Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 -/// UND Stack = 244 used of 16384 bytes (001%) @ 0x10078000..0x1007c000 -/// HYP Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +/// UND0 Stack = 0 used of 16384 bytes (000%) @ 0x1006bf80..0x1006ff80 +/// SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x1006ff80..0x10073f80 +/// ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10073f80..0x10077f80 +/// HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x10077f80..0x1007bf80 +/// IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bf80..0x1007bfc0 +/// FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bfc0..0x1007c000 +/// SYS0 Stack = 2416 used of 16384 bytes (014%) @ 0x1007c000..0x10080000 /// ``` fn stack_dump() { use aarch32_cpu::stacks::stack_used_bytes; - use core::ptr::addr_of; - - unsafe extern "C" { - static _sys_stack_end: u32; - static _sys_stack: u32; - static _fiq_stack_end: u32; - static _fiq_stack: u32; - static _irq_stack_end: u32; - static _irq_stack: u32; - static _abt_stack_end: u32; - static _abt_stack: u32; - static _svc_stack_end: u32; - static _svc_stack: u32; - static _und_stack_end: u32; - static _und_stack: u32; - static _hyp_stack_end: u32; - static _hyp_stack: u32; - } - - // these are placed in the order they are in aarch32-rt/link.x - let stacks = [ - ("SYS", addr_of!(_sys_stack_end)..addr_of!(_sys_stack)), - ("FIQ", addr_of!(_fiq_stack_end)..addr_of!(_fiq_stack)), - ("IRQ", addr_of!(_irq_stack_end)..addr_of!(_irq_stack)), - ("ABT", addr_of!(_abt_stack_end)..addr_of!(_abt_stack)), - ("SVC", addr_of!(_svc_stack_end)..addr_of!(_svc_stack)), - ("UND", addr_of!(_und_stack_end)..addr_of!(_und_stack)), - ("HYP", addr_of!(_hyp_stack_end)..addr_of!(_hyp_stack)), - ]; + use aarch32_rt::stacks::Stack; semihosting::eprintln!("Stack usage report:"); unsafe { - for (name, range) in stacks { - let (total, used) = stack_used_bytes(range.clone()); - let percent = (used * 100).checked_div(total).unwrap_or(999); - // Send to stderr, so it doesn't mix with expected output on stdout - semihosting::eprintln!( - "{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", - name, - used, - total, - percent, - range - ); + for stack in Stack::iter() { + for core in (0..Stack::num_cores()).rev() { + let core_range = stack.range(core).unwrap(); + let (total, used) = stack_used_bytes(core_range.clone()); + let percent = used * 100 / total; + // Send to stderr, so it doesn't mix with expected output on stdout + semihosting::eprintln!( + "{}{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", + stack, + core, + used, + total, + percent, + core_range + ); + } } } } diff --git a/examples/versatileab/src/lib.rs b/examples/versatileab/src/lib.rs index 7b3f9969..3d6ff6e9 100644 --- a/examples/versatileab/src/lib.rs +++ b/examples/versatileab/src/lib.rs @@ -69,61 +69,37 @@ pub fn exit(code: i32) -> ! { /// /// ```text /// Stack usage report: -/// SYS Stack = 332 used of 16384 bytes (002%) @ 0x1006bf80..0x1006ff80 -/// FIQ Stack = 0 used of 64 bytes (000%) @ 0x1006ff80..0x1006ffc0 -/// IRQ Stack = 0 used of 64 bytes (000%) @ 0x1006ffc0..0x10070000 -/// ABT Stack = 0 used of 16384 bytes (000%) @ 0x10070000..0x10074000 -/// SVC Stack = 0 used of 16384 bytes (000%) @ 0x10074000..0x10078000 -/// UND Stack = 244 used of 16384 bytes (001%) @ 0x10078000..0x1007c000 -/// HYP Stack = 0 used of 16384 bytes (000%) @ 0x1007c000..0x10080000 +/// UND0 Stack = 0 used of 16384 bytes (000%) @ 0x1006bf80..0x1006ff80 +/// SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x1006ff80..0x10073f80 +/// ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10073f80..0x10077f80 +/// HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x10077f80..0x1007bf80 +/// IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bf80..0x1007bfc0 +/// FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bfc0..0x1007c000 +/// SYS0 Stack = 2416 used of 16384 bytes (014%) @ 0x1007c000..0x10080000 /// ``` fn stack_dump() { use aarch32_cpu::stacks::stack_used_bytes; - use core::ptr::addr_of; - - unsafe extern "C" { - static _sys_stack_end: u32; - static _sys_stack: u32; - static _fiq_stack_end: u32; - static _fiq_stack: u32; - static _irq_stack_end: u32; - static _irq_stack: u32; - static _abt_stack_end: u32; - static _abt_stack: u32; - static _svc_stack_end: u32; - static _svc_stack: u32; - static _und_stack_end: u32; - static _und_stack: u32; - static _hyp_stack_end: u32; - static _hyp_stack: u32; - } - - // these are placed in the order they are in aarch32-rt/link.x - let stacks = [ - ("SYS", addr_of!(_sys_stack_end)..addr_of!(_sys_stack)), - ("FIQ", addr_of!(_fiq_stack_end)..addr_of!(_fiq_stack)), - ("IRQ", addr_of!(_irq_stack_end)..addr_of!(_irq_stack)), - ("ABT", addr_of!(_abt_stack_end)..addr_of!(_abt_stack)), - ("SVC", addr_of!(_svc_stack_end)..addr_of!(_svc_stack)), - ("UND", addr_of!(_und_stack_end)..addr_of!(_und_stack)), - ("HYP", addr_of!(_hyp_stack_end)..addr_of!(_hyp_stack)), - ]; + use aarch32_rt::stacks::Stack; semihosting::eprintln!("Stack usage report:"); unsafe { - for (name, range) in stacks { - let (total, used) = stack_used_bytes(range.clone()); - let percent = (used * 100).checked_div(total).unwrap_or(999); - // Send to stderr, so it doesn't mix with expected output on stdout - semihosting::eprintln!( - "{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", - name, - used, - total, - percent, - range - ); + for stack in Stack::iter() { + for core in (0..Stack::num_cores()).rev() { + let core_range = stack.range(core).unwrap(); + let (total, used) = stack_used_bytes(core_range.clone()); + let percent = used * 100 / total; + // Send to stderr, so it doesn't mix with expected output on stdout + semihosting::eprintln!( + "{}{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", + stack, + core, + used, + total, + percent, + core_range + ); + } } } } From 740a71da048ef0b82874f28d87d11a4e62bbb5af Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 6 Mar 2026 20:27:03 +0000 Subject: [PATCH 4/5] Adjust comment text to avoid markdown warning The parser thought the hyphen started a bullet list. --- aarch32-rt/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aarch32-rt/src/lib.rs b/aarch32-rt/src/lib.rs index a6445963..03d49290 100644 --- a/aarch32-rt/src/lib.rs +++ b/aarch32-rt/src/lib.rs @@ -517,8 +517,8 @@ //! not save a great deal of state on entry to an exception handler, unlike //! Armv7-M (and other M-Profile) processors. We must therefore save this state //! to the stack using assembly language, before transferring to an `extern "C"` -//! function. We do not change modes before entering that `extern "C"` function -//! - that's for the handler to deal with as it wishes. Because FIQ is often +//! function. We do not change modes before entering that `extern "C"` function - +//! that's for the handler to deal with as it wishes. Because FIQ is often //! performance-sensitive, we don't supply an FIQ trampoline; if you want to use //! FIQ, you have to write your own assembly routine, allowing you to preserve //! only whatever state is important to you. From 8139e70d0dcaf42224a4b6a1784ddb58a89e5aba Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 19 Feb 2026 22:34:25 +0000 Subject: [PATCH 5/5] Dedicated EL2 examples. * Adds more Hyp register details. * A bunch of fixes to the docs * Makes sure CI actually runs these and the SMP examples --- .github/workflows/build.yml | 46 +++ .gitignore | 2 + .vscode/settings.json | 1 + Cargo.toml | 1 + aarch32-cpu/src/generic_timer/el2.rs | 4 +- aarch32-cpu/src/register/armv8r/hcptr.rs | 39 ++- aarch32-cpu/src/register/armv8r/hcr.rs | 121 +++++++- aarch32-cpu/src/register/armv8r/hsr.rs | 270 +++++++++++++++++- aarch32-rt/Cargo.toml | 2 + aarch32-rt/src/arch_v4/interrupt.rs | 2 +- aarch32-rt/src/arch_v7/hvc.rs | 2 +- aarch32-rt/src/arch_v7/interrupt.rs | 2 +- aarch32-rt/src/arch_v8_hyp/abort.rs | 77 +++++ aarch32-rt/src/arch_v8_hyp/hvc.rs | 46 +++ aarch32-rt/src/arch_v8_hyp/interrupt.rs | 38 +++ aarch32-rt/src/arch_v8_hyp/mod.rs | 7 + aarch32-rt/src/arch_v8_hyp/svc.rs | 52 ++++ aarch32-rt/src/arch_v8_hyp/undefined.rs | 41 +++ aarch32-rt/src/lib.rs | 76 ++++- examples/mps3-an536-el2/.cargo/config.toml | 10 + examples/mps3-an536-el2/Cargo.toml | 31 ++ examples/mps3-an536-el2/README.md | 90 ++++++ examples/mps3-an536-el2/build.rs | 26 ++ examples/mps3-an536-el2/commands.gdb | 13 + examples/mps3-an536-el2/memory.x | 44 +++ .../abt-exception-a32-armv8r-none-eabihf.out | 8 + ...abt-exception-a32-thumbv8r-none-eabihf.out | 8 + .../abt-exception-t32-armv8r-none-eabihf.out | 8 + ...abt-exception-t32-thumbv8r-none-eabihf.out | 8 + .../generic-timer-armv8r-none-eabihf.out | 40 +++ .../generic-timer-thumbv8r-none-eabihf.out | 40 +++ .../reference/hello-armv8r-none-eabihf.out | 19 ++ .../reference/hello-thumbv8r-none-eabihf.out | 19 ++ .../reference/hvc-a32-armv8r-none-eabihf.out | 5 + .../hvc-a32-thumbv8r-none-eabihf.out | 5 + .../reference/hvc-t32-armv8r-none-eabihf.out | 5 + .../hvc-t32-thumbv8r-none-eabihf.out | 5 + ...fetch-exception-a32-armv8r-none-eabihf.out | 8 + ...tch-exception-a32-thumbv8r-none-eabihf.out | 8 + ...fetch-exception-t32-armv8r-none-eabihf.out | 8 + ...tch-exception-t32-thumbv8r-none-eabihf.out | 8 + .../reference/svc-a32-armv8r-none-eabihf.out | 5 + .../svc-a32-thumbv8r-none-eabihf.out | 5 + .../reference/svc-t32-armv8r-none-eabihf.out | 5 + .../svc-t32-thumbv8r-none-eabihf.out | 5 + ...undef-exception-a32-armv8r-none-eabihf.out | 8 + ...def-exception-a32-thumbv8r-none-eabihf.out | 8 + ...undef-exception-t32-armv8r-none-eabihf.out | 8 + ...def-exception-t32-thumbv8r-none-eabihf.out | 8 + examples/mps3-an536-el2/rust-toolchain.toml | 6 + .../src/bin/abt-exception-a32.rs | 120 ++++++++ .../src/bin/abt-exception-t32.rs | 120 ++++++++ .../mps3-an536-el2/src/bin/generic-timer.rs | 115 ++++++++ examples/mps3-an536-el2/src/bin/hello.rs | 28 ++ examples/mps3-an536-el2/src/bin/hvc-a32.rs | 64 +++++ examples/mps3-an536-el2/src/bin/hvc-t32.rs | 64 +++++ .../src/bin/prefetch-exception-a32.rs | 93 ++++++ .../src/bin/prefetch-exception-t32.rs | 95 ++++++ examples/mps3-an536-el2/src/bin/svc-a32.rs | 64 +++++ examples/mps3-an536-el2/src/bin/svc-t32.rs | 64 +++++ .../src/bin/undef-exception-a32.rs | 94 ++++++ .../src/bin/undef-exception-t32.rs | 97 +++++++ examples/mps3-an536-el2/src/lib.rs | 259 +++++++++++++++++ examples/mps3-an536-smp/src/bin/smp-test.rs | 10 +- examples/mps3-an536-smp/src/lib.rs | 2 +- examples/mps3-an536/src/bin/el2_hello.rs | 98 ------- .../mps3-an536/src/bin/generic_timer_irq.rs | 3 - examples/mps3-an536/src/bin/gic-map.rs | 3 - .../src/bin/gic-priority-ceiling.rs | 3 - .../src/bin/gic-static-section-irq.rs | 3 - examples/mps3-an536/src/lib.rs | 8 +- justfile | 19 ++ 72 files changed, 2579 insertions(+), 150 deletions(-) create mode 100644 aarch32-rt/src/arch_v8_hyp/abort.rs create mode 100644 aarch32-rt/src/arch_v8_hyp/hvc.rs create mode 100644 aarch32-rt/src/arch_v8_hyp/interrupt.rs create mode 100644 aarch32-rt/src/arch_v8_hyp/mod.rs create mode 100644 aarch32-rt/src/arch_v8_hyp/svc.rs create mode 100644 aarch32-rt/src/arch_v8_hyp/undefined.rs create mode 100644 examples/mps3-an536-el2/.cargo/config.toml create mode 100644 examples/mps3-an536-el2/Cargo.toml create mode 100644 examples/mps3-an536-el2/README.md create mode 100644 examples/mps3-an536-el2/build.rs create mode 100644 examples/mps3-an536-el2/commands.gdb create mode 100644 examples/mps3-an536-el2/memory.x create mode 100644 examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out create mode 100644 examples/mps3-an536-el2/rust-toolchain.toml create mode 100644 examples/mps3-an536-el2/src/bin/abt-exception-a32.rs create mode 100644 examples/mps3-an536-el2/src/bin/abt-exception-t32.rs create mode 100644 examples/mps3-an536-el2/src/bin/generic-timer.rs create mode 100644 examples/mps3-an536-el2/src/bin/hello.rs create mode 100644 examples/mps3-an536-el2/src/bin/hvc-a32.rs create mode 100644 examples/mps3-an536-el2/src/bin/hvc-t32.rs create mode 100644 examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs create mode 100644 examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs create mode 100644 examples/mps3-an536-el2/src/bin/svc-a32.rs create mode 100644 examples/mps3-an536-el2/src/bin/svc-t32.rs create mode 100644 examples/mps3-an536-el2/src/bin/undef-exception-a32.rs create mode 100644 examples/mps3-an536-el2/src/bin/undef-exception-t32.rs create mode 100644 examples/mps3-an536-el2/src/lib.rs delete mode 100644 examples/mps3-an536/src/bin/el2_hello.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index afc8b3ff..7e0be5d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -406,6 +406,50 @@ jobs: export PATH=/opt/qemu/bin:$PATH just test-qemu-v8r + # Run some SMP programs in QEMU 9 for Armv8-R + # These tests build with nightly as pinned by the rust-toolchain.toml file, because they include Tier 3 targets + test-qemu-v8r-smp: + runs-on: ubuntu-24.04 + needs: [build-all] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Just + uses: taiki-e/install-action@just + - name: Install Dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install libpixman-1-0 libfdt1 libglib2.0-0t64 + - name: Install custom QEMU into /opt + run: | + curl -sSL https://github.com/jonathanpallant/qemu9-for-ubuntu-2404/releases/download/qemu-9.2.3%2Bbuild0/qemu-9.2.3-ubuntu-24.04.tar.gz | sudo tar xvzf - -C / + - name: Run tests in QEMU + run: | + export PATH=/opt/qemu/bin:$PATH + just test-qemu-v8r-smp + + # Run some EL2 programs in QEMU 9 for Armv8-R + # These tests build with nightly as pinned by the rust-toolchain.toml file, because they include Tier 3 targets + test-qemu-v8r-el2: + runs-on: ubuntu-24.04 + needs: [build-all] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Just + uses: taiki-e/install-action@just + - name: Install Dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install libpixman-1-0 libfdt1 libglib2.0-0t64 + - name: Install custom QEMU into /opt + run: | + curl -sSL https://github.com/jonathanpallant/qemu9-for-ubuntu-2404/releases/download/qemu-9.2.3%2Bbuild0/qemu-9.2.3-ubuntu-24.04.tar.gz | sudo tar xvzf - -C / + - name: Run tests in QEMU + run: | + export PATH=/opt/qemu/bin:$PATH + just test-qemu-v8r-el2 + # Gather all the above QEMU jobs together for the purposes of getting an overall pass-fail test-qemu-all: runs-on: ubuntu-24.04 @@ -417,6 +461,8 @@ jobs: test-qemu-v7a, test-qemu-v7r, test-qemu-v8r, + test-qemu-v8r-smp, + test-qemu-v8r-el2, ] steps: - run: /bin/true diff --git a/.gitignore b/.gitignore index 666f29a1..e58f52da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ examples/mps3-an536/target examples/mps3-an536/target-d32 examples/mps3-an536-smp/target examples/mps3-an536-smp/target-d32 +examples/mps3-an536-el2/target +examples/mps3-an536-el2/target-d32 examples/versatileab/target examples/versatileab/target-d32 Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index fbd3ad9b..1c0f63d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,6 @@ "examples/versatileab/Cargo.toml", "examples/mps3-an536/Cargo.toml", "examples/mps3-an536-smp/Cargo.toml", + "examples/mps3-an536-el2/Cargo.toml" ] } diff --git a/Cargo.toml b/Cargo.toml index 2209a19b..61edd8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ exclude = [ "examples/versatileab", "examples/mps3-an536", "examples/mps3-an536-smp", + "examples/mps3-an536-el2", ] members = [ "aarch32-cpu", diff --git a/aarch32-cpu/src/generic_timer/el2.rs b/aarch32-cpu/src/generic_timer/el2.rs index 89a56e95..fd34c915 100644 --- a/aarch32-cpu/src/generic_timer/el2.rs +++ b/aarch32-cpu/src/generic_timer/el2.rs @@ -8,7 +8,7 @@ use super::{El1PhysicalTimer, El1VirtualTimer, GenericTimer}; pub struct El2PhysicalTimer(El1PhysicalTimer); impl El2PhysicalTimer { - /// Create an EL1 Generic Timer handle + /// Create an EL2 Physical Timer handle /// /// # Safety /// @@ -79,7 +79,7 @@ impl GenericTimer for El2PhysicalTimer { pub struct El2VirtualTimer(El1VirtualTimer); impl El2VirtualTimer { - /// Create an EL1 Generic Timer handle + /// Create an EL2 Generic Timer handle /// /// # Safety /// diff --git a/aarch32-cpu/src/register/armv8r/hcptr.rs b/aarch32-cpu/src/register/armv8r/hcptr.rs index b4057aac..864adea7 100644 --- a/aarch32-cpu/src/register/armv8r/hcptr.rs +++ b/aarch32-cpu/src/register/armv8r/hcptr.rs @@ -3,10 +3,22 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// HCPTR (*Hyp Architectural Feature Trap Register*) -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Hcptr(pub u32); +pub struct Hcptr { + /// TCPAC - Traps EL1 accesses to the CPACR to Hyp mode + #[bit(31, rw)] + tcpac: bool, + /// TTA - Traps System register accesses to all implemented trace registers to Hyp mode + #[bit(20, rw)] + tta: bool, + /// TASE - Traps execution of Advanced SIMD instructions to Hyp mode when the value of HCPTR.TCP10 is 0. + #[bit(15, rw)] + tase: bool, + /// TCP - Trap accesses to Advanced SIMD and floating-point functionality to Hyp mode + #[bit(10, rw)] + tcp: bool, +} impl SysReg for Hcptr { const CP: u32 = 15; @@ -22,7 +34,18 @@ impl Hcptr { #[inline] /// Reads HCPTR (*Hyp Architectural Feature Trap Register*) pub fn read() -> Hcptr { - unsafe { Self(::read_raw()) } + unsafe { Self::new_with_raw_value(::read_raw()) } + } + + /// Modify HCPTR (*Hyp Architectural Feature Trap Register*) + #[inline] + pub fn modify(f: F) + where + F: FnOnce(&mut Self), + { + let mut value = Self::read(); + f(&mut value); + Self::write(value); } } @@ -31,13 +54,9 @@ impl crate::register::SysRegWrite for Hcptr {} impl Hcptr { #[inline] /// Writes HCPTR (*Hyp Architectural Feature Trap Register*) - /// - /// # Safety - /// - /// Ensure that this value is appropriate for this register - pub unsafe fn write(value: Self) { + pub fn write(value: Self) { unsafe { - ::write_raw(value.0); + ::write_raw(value.raw_value()); } } } diff --git a/aarch32-cpu/src/register/armv8r/hcr.rs b/aarch32-cpu/src/register/armv8r/hcr.rs index 9da41175..9424f92b 100644 --- a/aarch32-cpu/src/register/armv8r/hcr.rs +++ b/aarch32-cpu/src/register/armv8r/hcr.rs @@ -3,10 +3,106 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// HCR (*Hyp Configuration Register*) -#[derive(Debug, Copy, Clone)] +#[bitbybit::bitfield(u32, debug, defmt_fields(feature = "defmt"))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Hcr { + /// TCPAC - Traps EL1 accesses to the CPACR to Hyp mode + #[bit(31, rw)] + tcpac: bool, + /// TRVM - Trap Reads of Memory controls + #[bit(30, rw)] + trvm: bool, + /// HCD - HVC instruction disable + #[bit(29, rw)] + hcd: bool, + /// TGE - Trap General Exceptions from EL0 + #[bit(27, rw)] + tge: bool, + /// TVM - Trap Memory controls + #[bit(26, rw)] + tvm: bool, + /// TPU - Trap cache maintenance instructions that operate to the Point of Unification + #[bit(24, rw)] + tpu: bool, + /// TPC - Trap data or unified cache maintenance instructions that operate to the Point of Coherency + #[bit(23, rw)] + tpc: bool, + /// TSW - Trap data or unified cache maintenance instructions that operate by Set/Way + #[bit(22, rw)] + tsw: bool, + /// TAC - Trap Auxiliary Control Registers + #[bit(21, rw)] + tac: bool, + /// TIDCP - Trap IMPLEMENTATION DEFINED functionality + #[bit(20, rw)] + tidcp: bool, + /// TID3 - Trap ID group 3 + #[bit(18, rw)] + tid3: bool, + /// TID2 - Trap ID group 2 + #[bit(17, rw)] + tid2: bool, + /// TID1 - Trap ID group 1 + #[bit(16, rw)] + tid1: bool, + /// TID0 - Trap ID group 0 + #[bit(15, rw)] + tid0: bool, + /// TWE - Traps EL0 and EL1 execution of WFE instructions to Hyp mode + #[bit(14, rw)] + twe: bool, + /// TWI - Traps EL0 and EL1 execution of WFI instructions to Hyp mode + #[bit(13, rw)] + twi: bool, + /// DC - Default Cacheability + #[bit(12, rw)] + dc: bool, + /// BSU - Barrier Shareability upgrade. + #[bits(10..=11, rw)] + bsu: Bsu, + /// FB - Force broadcast + #[bit(9, rw)] + fb: bool, + /// VA - Virtual SError interrupt exception + #[bit(8, rw)] + va: bool, + /// VI - Virtual IRQ exception + #[bit(7, rw)] + vi: bool, + /// VF - Virtual FIQ exception + #[bit(6, rw)] + vf: bool, + /// AMO - SError interrupt Mask Override + #[bit(5, rw)] + amo: bool, + /// IMO - IRQ Mask Override + #[bit(4, rw)] + imo: bool, + /// FMO - FIQ Mask Override + #[bit(3, rw)] + fmo: bool, + /// SWIO - Set/Way Invalidation Override + #[bit(1, rw)] + swio: bool, + /// VM - Virtualization enable + #[bit(0, rw)] + vm: bool, +} + +/// Barrier Shareability upgrade +/// +/// This field determines the minimum Shareability domain that is applied to any +/// barrier instruction executed from EL1 or EL0 +#[bitbybit::bitenum(u2, exhaustive = true)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Hcr(pub u32); +#[derive(Debug, PartialEq, Eq)] +pub enum Bsu { + NoEffect = 0b00, + InnerShareable = 0b01, + OuterShareable = 0b10, + FullSystem = 0b11, +} impl SysReg for Hcr { const CP: u32 = 15; @@ -22,7 +118,7 @@ impl Hcr { #[inline] /// Reads HCR (*Hyp Configuration Register*) pub fn read() -> Hcr { - unsafe { Self(::read_raw()) } + unsafe { Self::new_with_raw_value(::read_raw()) } } } @@ -31,13 +127,20 @@ impl crate::register::SysRegWrite for Hcr {} impl Hcr { #[inline] /// Writes HCR (*Hyp Configuration Register*) - /// - /// # Safety - /// - /// Ensure that this value is appropriate for this register - pub unsafe fn write(value: Self) { + pub fn write(value: Self) { unsafe { - ::write_raw(value.0); + ::write_raw(value.raw_value()); } } + + #[inline] + /// Modify HCR (*Hyp Configuration Register*) + pub fn modify(f: F) + where + F: FnOnce(&mut Self), + { + let mut value = Self::read(); + f(&mut value); + Self::write(value); + } } diff --git a/aarch32-cpu/src/register/armv8r/hsr.rs b/aarch32-cpu/src/register/armv8r/hsr.rs index 40c2fe8f..b499db95 100644 --- a/aarch32-cpu/src/register/armv8r/hsr.rs +++ b/aarch32-cpu/src/register/armv8r/hsr.rs @@ -2,7 +2,7 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; -use arbitrary_int::u25; +use arbitrary_int::{u2, u25, u3, u4, u6}; /// HSR (*Hyp Syndrome Register*) #[bitbybit::bitfield(u32, debug, defmt_bitfields(feature = "defmt"))] @@ -27,6 +27,17 @@ pub struct Hsr { iss: u25, } +impl Hsr { + /// Get the ISS value from the HSR + pub fn get_iss(&self) -> Option { + if let Ok(ec) = self.ec() { + Some(ec.decode_iss(self.iss())) + } else { + None + } + } +} + #[bitbybit::bitenum(u6, exhaustive = false)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -52,6 +63,263 @@ pub enum ExceptionClass { DataAbortFromCurrent = 0b10_0101, } +/// A decoded ISS +/// +/// ISS is a 25 bit field whose meaning varies depending on the value of the EC field. +#[derive(Debug, Clone)] +pub enum Iss { + Unknown(IssUnknown), + TrappedWfiWfe(IssTrappedWfiWfe), + TrappedCp15McrMrc(IssTrappedMcrMrc), + TrappedCp15McrrMrrc(IssTrappedMcrrMrrc), + TrappedCp14McrMrc(IssTrappedMcrMrc), + TrappedLdcStc(IssTrappedLdcStc), + TrappedFpu(IssTrappedFpu), + TrappedVmrs(IssTrappedVmrs), + TrappedCp14McrrMrrc(IssTrappedMcrrMrrc), + IllegalAArch32Eret, + Svc(IssCall), + Hvc(IssCall), + Smc(IssSmc), + PrefetchAbortFromLower(IssPrefetchAbort), + PrefetchAbortFromCurrent(IssPrefetchAbort), + PcAlignment, + DataAbortFromLower(IssDataAbort), + DataAbortFromCurrent(IssDataAbort), +} + +impl ExceptionClass { + pub fn decode_iss(&self, iss: u25) -> Iss { + match self { + ExceptionClass::Unknown => Iss::Unknown(IssUnknown(iss.value())), + ExceptionClass::TrappedWfiWfe => { + Iss::TrappedWfiWfe(IssTrappedWfiWfe::new_with_raw_value(iss)) + } + ExceptionClass::TrappedCp15McrMrc => { + Iss::TrappedCp15McrMrc(IssTrappedMcrMrc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedCp15McrrMrrc => { + Iss::TrappedCp15McrrMrrc(IssTrappedMcrrMrrc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedCp14McrMrc => { + Iss::TrappedCp14McrMrc(IssTrappedMcrMrc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedLdcStc => { + Iss::TrappedLdcStc(IssTrappedLdcStc::new_with_raw_value(iss)) + } + ExceptionClass::TrappedFpu => Iss::TrappedFpu(IssTrappedFpu::new_with_raw_value(iss)), + ExceptionClass::TrappedVmrs => Iss::TrappedVmrs(IssTrappedVmrs(iss.value())), + ExceptionClass::TrappedCp14McrrMrrc => { + Iss::TrappedCp14McrrMrrc(IssTrappedMcrrMrrc::new_with_raw_value(iss)) + } + ExceptionClass::IllegalAArch32Eret => Iss::IllegalAArch32Eret, + ExceptionClass::Svc => Iss::Svc(IssCall::new_with_raw_value(iss)), + ExceptionClass::Hvc => Iss::Hvc(IssCall::new_with_raw_value(iss)), + ExceptionClass::Smc => Iss::Smc(IssSmc(iss.value())), + ExceptionClass::PrefetchAbortFromLower => { + Iss::PrefetchAbortFromLower(IssPrefetchAbort::new_with_raw_value(iss)) + } + ExceptionClass::PrefetchAbortFromCurrent => { + Iss::PrefetchAbortFromCurrent(IssPrefetchAbort::new_with_raw_value(iss)) + } + ExceptionClass::PcAlignment => Iss::PcAlignment, + ExceptionClass::DataAbortFromLower => { + Iss::DataAbortFromLower(IssDataAbort::new_with_raw_value(iss)) + } + ExceptionClass::DataAbortFromCurrent => { + Iss::DataAbortFromCurrent(IssDataAbort::new_with_raw_value(iss)) + } + } + } +} + +/// The ISS field when EC = ExceptionClass::Unknown +/// +/// All bits are reserved +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IssUnknown(pub u32); + +/// The ISS field when EC = ExceptionClass::TrappedWfiWfe +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedWfiWfe { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// Trapped Instruction is WFE + #[bit(0, r)] + ti: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedCp15McrMrc or ExceptionClass::TrappedCp14McrMrc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedMcrMrc { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// OPC2 value from instruction + #[bits(17..=19, r)] + opc2: u3, + /// OPC1 value from instruction + #[bits(14..=16, r)] + opc1: u3, + /// CRn value from instruction + #[bits(10..=13, r)] + crn: u4, + /// Rt value from instruction + #[bits(5..=8, r)] + rt: u4, + /// CRm value from instruction + #[bits(1..=4, r)] + crm: u4, + /// Direction (true = read, false = write) + #[bit(0, r)] + is_read: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedCp15McrrMrrc or ExceptionClass::TrappedCp14McrrMrrc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedMcrrMrrc { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// OPC2 value from instruction + #[bits(16..=19, r)] + opc2: u4, + /// Rt2 value from instruction + #[bits(10..=13, r)] + rt2: u4, + /// Rt value from instruction + #[bits(5..=8, r)] + rt: u4, + /// CRm value from instruction + #[bits(1..=4, r)] + crm: u4, + /// Direction (true = read, false = write) + #[bit(0, r)] + is_read: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedLdcStc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedLdcStc { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// The immediate value from the instruction + #[bits(12..=19, r)] + imm8: u8, + /// Rn value from instruction + #[bits(5..=8, r)] + rn: u4, + /// Whether offset is added (true) or subtracted (false) + #[bit(4, r)] + offset: bool, + /// Addressing Mode + #[bits(1..=3, r)] + am: u3, + /// Direction (true = read, false = write) + #[bit(0, r)] + is_read: bool, +} + +/// The ISS field when EC = ExceptionClass::TrappedFpu +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssTrappedFpu { + /// Condition code valid + #[bit(24, r)] + cv: bool, + /// Condition code + #[bits(20..=23, r)] + cc: u4, + /// Trapped Advanced SIMD + #[bit(5, r)] + ta: bool, + /// CoProc Bits + #[bits(0..=3, r)] + coproc: u4, +} + +/// The ISS field when EC = ExceptionClass::TrappedVmrs +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IssTrappedVmrs(pub u32); + +/// The ISS field when EC = ExceptionClass::Svc +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssCall { + /// Immediate value from instruction + #[bits(0..=15, r)] + imm16: u16, +} + +/// The ISS field when EC = ExceptionClass::Smc +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct IssSmc(pub u32); + +/// The ISS field when EC = ExceptionClass::PrefetchAbortFromLower or ExceptionClass::PrefetchAbortFromCurrent +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssPrefetchAbort { + /// FAR not Valid, for a Synchronous External abort. + #[bit(10, r)] + fnv: bool, + /// External Abort Type. + /// + /// External = true, anything else = false + #[bit(9, r)] + ea: bool, + /// Instruction Fault Status Code + #[bits(0..=5, r)] + ifsc: u6, +} + +/// The ISS field when EC = ExceptionClass::DataAbortFromLower or ExceptionClass::DataAbortFromCurrent +#[bitbybit::bitfield(u25, debug, defmt_bitfields(feature = "defmt"))] +pub struct IssDataAbort { + /// Instruction Syndrome Valid + #[bit(24, r)] + isv: bool, + /// Syndrome Access Size + #[bits(22..=23, r)] + sas: u2, + /// Syndrome Sign Extend + #[bit(21, r)] + sae: bool, + /// Syndrome Register transfer + #[bits(16..=19, r)] + srt: u4, + /// Acquire/Release + #[bit(14, r)] + ar: bool, + /// FAR not Valid + #[bit(10, r)] + fnv: bool, + /// External Abort Type. + /// + /// External = true, anything else = false + #[bit(9, r)] + ea: bool, + /// Cache maintenance + #[bit(8, r)] + cm: bool, + /// Write not Read + #[bit(6, r)] + wnr: bool, + /// Data Fault Status Code + #[bits(0..=5, r)] + dfsc: u6, +} + #[bitbybit::bitenum(u1, exhaustive = true)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/aarch32-rt/Cargo.toml b/aarch32-rt/Cargo.toml index adc057fc..0069dd51 100644 --- a/aarch32-rt/Cargo.toml +++ b/aarch32-rt/Cargo.toml @@ -35,6 +35,8 @@ eabi-fpu = [] # if you have set the `+d32` target feature) then you need to enable this # option otherwise important FPU state may be lost when an exception occurs. fpu-d32 = [] +# Leave the CPU in HYP mode (EL2), and handle exceptions in HYP mode +el2-mode = [] [build-dependencies] arm-targets = { version = "0.4.0", path = "../arm-targets" } diff --git a/aarch32-rt/src/arch_v4/interrupt.rs b/aarch32-rt/src/arch_v4/interrupt.rs index a82296b3..db178f6f 100644 --- a/aarch32-rt/src/arch_v4/interrupt.rs +++ b/aarch32-rt/src/arch_v4/interrupt.rs @@ -20,7 +20,7 @@ core::arch::global_asm!( push {{ lr }} // save adjusted LR to IRQ stack mrs lr, spsr // The hardware has copied the interrupted task's CPSR to SPSR_irq - grab it and push {{ lr }} // save it to IRQ stack using LR - msr cpsr_c, {sys_mode} // switch to system mode so we can handle another interrupt (because if we interrupt irq mode we trash our own shadow registers) + msr cpsr_c, {sys_mode} // switch to system mode (because if we interrupt in IRQ mode we trash IRQ mode's LR) push {{ lr }} // Save LR of system mode before using it for stack alignment mov lr, sp // align SP down to eight byte boundary using LR and lr, lr, 7 // diff --git a/aarch32-rt/src/arch_v7/hvc.rs b/aarch32-rt/src/arch_v7/hvc.rs index 35474984..0840d698 100644 --- a/aarch32-rt/src/arch_v7/hvc.rs +++ b/aarch32-rt/src/arch_v7/hvc.rs @@ -15,7 +15,7 @@ core::arch::global_asm!( .global _asm_default_hvc_handler .type _asm_default_hvc_handler, %function _asm_default_hvc_handler: - push {{ r12, lr }} // push state to stack + push {{ r12, lr }} // give us R12 and LR to work with push {{ r0-r5 }} // push frame to stack mov r12, sp // r12 = pointer to Frame "#, diff --git a/aarch32-rt/src/arch_v7/interrupt.rs b/aarch32-rt/src/arch_v7/interrupt.rs index dab9b3c6..3a07c659 100644 --- a/aarch32-rt/src/arch_v7/interrupt.rs +++ b/aarch32-rt/src/arch_v7/interrupt.rs @@ -16,7 +16,7 @@ core::arch::global_asm!( _asm_default_irq_handler: sub lr, lr, 4 // make sure we jump back to the right place srsfd sp!, #{sys_mode} // store return state to SYS stack - cps #{sys_mode} // switch to system mode so we can handle another interrupt (because if we interrupt irq mode we trash our own shadow registers) + cps #{sys_mode} // switch to system mode (because if we interrupt in IRQ mode we trash IRQ mode's LR) push {{ lr }} // save adjusted LR to SYS stack mov lr, sp // align SP down to eight byte boundary using LR and lr, lr, 7 // diff --git a/aarch32-rt/src/arch_v8_hyp/abort.rs b/aarch32-rt/src/arch_v8_hyp/abort.rs new file mode 100644 index 00000000..8a281228 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/abort.rs @@ -0,0 +1,77 @@ +//! Data and Prefetch Abort handlers for Armv8-R at EL2 + +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_data_abort_handler + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _data_abort_handler(addr: usize);` + .global _asm_default_data_abort_handler + .type _asm_default_data_abort_handler, %function + _asm_default_data_abort_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + mrs r0, elr_hyp // Pass the faulting instruction address to the handler. + bl _data_abort_handler // call C handler + msr elr_hyp, r0 // if we get back here, assume they returned a new LR in r0 + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_data_abort_handler, . - _asm_default_data_abort_handler + "#, +); + +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_prefetch_abort_handler + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _prefetch_abort_handler(addr: usize);` + .global _asm_default_prefetch_abort_handler + .type _asm_default_prefetch_abort_handler, %function + _asm_default_prefetch_abort_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + mrs r0, elr_hyp // Pass the faulting instruction address to the handler. + bl _prefetch_abort_handler // call C handler + msr elr_hyp, r0 // if we get back here, assume they returned a new LR in r0 + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_prefetch_abort_handler, . - _asm_default_prefetch_abort_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/hvc.rs b/aarch32-rt/src/arch_v8_hyp/hvc.rs new file mode 100644 index 00000000..171f165f --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/hvc.rs @@ -0,0 +1,46 @@ +//! HVC handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_hvc_handler + + // Called from the vector table when we have an hypervisor call. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _hvc_handler(hsr: u32, frame: &Frame) -> u32;` + .global _asm_default_hvc_handler + .type _asm_default_hvc_handler, %function + _asm_default_hvc_handler: + push {{ r12, lr }} // give us R12 and LR to work with + mrs lr, elr_hyp // grab elr + mrs r12, spsr_hyp // grab spsr + push {{ r12, lr }} // push them to stack + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r6, r12 }} // push frame and alignment amount to stack + mov r12, sp // r12 = pointer to Frame + "#, + crate::save_fpu_context!(), + r#" + mrc p15, 4, r0, c5, c2, 0 // r0 = HSR value + mov r1, r12 // r1 = frame pointer + bl _hvc_handler + mov lr, r0 // copy return value into LR, because we're about to use r0 in the FPU restore + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r6, r12 }} // restore frame and alignment + mov r0, lr // copy return value from lr back to r0, overwriting saved r0 + add sp, r12 // restore SP alignment using R12 + pop {{ r12, lr }} // pop elr and spsr from stack + msr elr_hyp, lr // restore elr + msr spsr_hyp, r12 // restore spsr + pop {{ r12, lr }} // pop R12 and LR from stack + eret // Return from the asm handler + .size _asm_default_hvc_handler, . - _asm_default_hvc_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/interrupt.rs b/aarch32-rt/src/arch_v8_hyp/interrupt.rs new file mode 100644 index 00000000..8295aaf1 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/interrupt.rs @@ -0,0 +1,38 @@ +//! IRQ handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text._asm_default_irq_handler + + // Called from the vector table when we have an interrupt. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _irq_handler();` + .global _asm_default_irq_handler + .type _asm_default_irq_handler, %function + _asm_default_irq_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + bl _irq_handler // call C handler (they may choose to re-enable interrupts) + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_irq_handler, . - _asm_default_irq_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/mod.rs b/aarch32-rt/src/arch_v8_hyp/mod.rs new file mode 100644 index 00000000..144f7494 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/mod.rs @@ -0,0 +1,7 @@ +//! ASM routines for Armv8-R at EL2 + +mod abort; +mod hvc; +mod interrupt; +mod svc; +mod undefined; diff --git a/aarch32-rt/src/arch_v8_hyp/svc.rs b/aarch32-rt/src/arch_v8_hyp/svc.rs new file mode 100644 index 00000000..65e9a337 --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/svc.rs @@ -0,0 +1,52 @@ +//! SVC handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + // Called from the vector table when we have an hypervisor call from Hyp + // mode (which seems to end up in this SVC handler). + // + // Saves state and calls a C-compatible handler like `extern "C" fn + // _hvc_handler(hsr: u32, frame: &Frame) -> u32;` + // + // NOTE: We call '_hvc_handler' rather than '_svc_handler', because we are + // passing the Hypervisor Syndrome Register contents, rather trying to parse + // the HVC instruction. + .section .text._asm_default_svc_handler + .arm + .global _asm_default_svc_handler + .type _asm_default_svc_handler, %function + _asm_default_svc_handler: + push {{ r12, lr }} // give us R12 and LR to work with + mrs lr, elr_hyp // grab elr + mrs r12, spsr_hyp // grab spsr + push {{ r12, lr }} // push them to stack + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r6, r12 }} // push frame and alignment amount to stack + mov r12, sp // r12 = pointer to Frame + "#, + crate::save_fpu_context!(), + r#" + mrc p15, 4, r0, c5, c2, 0 // r0 = HSR value + mov r1, r12 // r1 = frame pointer + bl _hvc_handler + mov lr, r0 // copy return value into LR, because we're about to use r0 in the FPU restore + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r6, r12 }} // restore frame and alignment + mov r0, lr // copy return value from lr back to r0, overwriting saved r0 + add sp, r12 // restore SP alignment using R12 + pop {{ r12, lr }} // pop elr and spsr from stack + msr elr_hyp, lr // restore elr + msr spsr_hyp, r12 // restore spsr + pop {{ r12, lr }} // pop R12 and LR from stack + eret // Return from the asm handler + .size _asm_default_svc_handler, . - _asm_default_svc_handler + "#, +); diff --git a/aarch32-rt/src/arch_v8_hyp/undefined.rs b/aarch32-rt/src/arch_v8_hyp/undefined.rs new file mode 100644 index 00000000..8c4b0d5e --- /dev/null +++ b/aarch32-rt/src/arch_v8_hyp/undefined.rs @@ -0,0 +1,41 @@ +//! Undefined handler for Armv8-R at EL2 + +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _undefined_handler(addr: usize) -> usize;` + // or + // `extern "C" fn _undefined_handler(addr: usize) -> !;` + .section .text._asm_default_undefined_handler + .global _asm_default_undefined_handler + .type _asm_default_undefined_handler, %function + _asm_default_undefined_handler: + push {{ r0-r3, r12, lr }} // preserve state that C function won't save + mrs r0, elr_hyp // grab ELR_hyp + mrs r1, spsr_hyp // grab SPSR_hyp + mov r12, sp // align SP down to eight byte boundary using R12 + and r12, r12, 7 // + sub sp, r12 // SP now aligned - only push 64-bit values from here + push {{ r0-r2, r12 }} // save ELR, SPSR, padding and alignment amount + "#, + crate::save_fpu_context!(), + r#" + mrs r0, elr_hyp // Pass the faulting instruction address to the handler. + bl _undefined_handler // call C handler + msr elr_hyp, r0 // if we get back here, assume they returned a new LR in r0 + "#, + crate::restore_fpu_context!(), + r#" + pop {{ r0-r2, r12 }} // restore ELR, SPSR, padding and alignment amount + add sp, r12 // restore SP alignment + msr spsr_hyp, r1 // restore SPSR + pop {{ r0-r3, r12, lr }} // restore state that C function didn't save + eret // Return from the asm handler + .size _asm_default_undefined_handler, . - _asm_default_undefined_handler + "#, +); diff --git a/aarch32-rt/src/lib.rs b/aarch32-rt/src/lib.rs index 03d49290..8cd6940b 100644 --- a/aarch32-rt/src/lib.rs +++ b/aarch32-rt/src/lib.rs @@ -517,11 +517,9 @@ //! not save a great deal of state on entry to an exception handler, unlike //! Armv7-M (and other M-Profile) processors. We must therefore save this state //! to the stack using assembly language, before transferring to an `extern "C"` -//! function. We do not change modes before entering that `extern "C"` function - -//! that's for the handler to deal with as it wishes. Because FIQ is often -//! performance-sensitive, we don't supply an FIQ trampoline; if you want to use -//! FIQ, you have to write your own assembly routine, allowing you to preserve -//! only whatever state is important to you. +//! function. Because FIQ is often performance-sensitive, we don't supply an FIQ +//! trampoline; if you want to use FIQ, you have to write your own assembly +//! routine, allowing you to preserve only whatever state is important to you. //! //! ## Examples //! @@ -533,18 +531,21 @@ #[cfg(target_arch = "arm")] use aarch32_cpu::register::{cpsr::ProcessorMode, Cpsr}; -#[cfg(arm_architecture = "v8-r")] +#[cfg(all(arm_architecture = "v8-r", not(feature = "el2-mode")))] use aarch32_cpu::register::Hactlr; pub use aarch32_rt_macros::{entry, exception, irq}; +#[cfg(all(target_arch = "arm", arm_architecture = "v8-r", feature = "el2-mode"))] +mod arch_v8_hyp; + #[cfg(all( target_arch = "arm", any( arm_architecture = "v7-a", arm_architecture = "v7-r", - arm_architecture = "v8-r" - ) + all(arm_architecture = "v8-r", not(feature = "el2-mode")) + ), ))] mod arch_v7; @@ -960,7 +961,7 @@ core::arch::global_asm!( // Set up stacks. mov r0, #0 bl _stack_setup_preallocated - "#, + "#, fpu_enable!(), r#" // Zero all registers before calling kmain @@ -985,13 +986,13 @@ core::arch::global_asm!( "# ); -// Start-up code for Armv8-R. +// Start-up code for Armv8-R to switch to EL1. // // There's only one Armv8-R CPU (the Cortex-R52) and the FPU is mandatory, so we // always enable it. // // We boot into EL2, set up a stack pointer, and run `kmain` in EL1. -#[cfg(arm_architecture = "v8-r")] +#[cfg(all(arm_architecture = "v8-r", not(feature = "el2-mode")))] core::arch::global_asm!( r#" // Work around https://github.com/rust-lang/rust/issues/127269 @@ -1080,3 +1081,56 @@ core::arch::global_asm!( .raw_value() } ); + +// Start-up code for Armv8-R to stay in EL2. +// +// There's only one Armv8-R CPU (the Cortex-R52) and the FPU is mandatory, so we +// always enable it. +// +// We boot into EL2, set up a HYP stack pointer, and run `kmain` in EL2. +#[cfg(all(arm_architecture = "v8-r", feature = "el2-mode"))] +core::arch::global_asm!( + r#" + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3 + + .section .text.default_start + .global _default_start + .type _default_start, %function + _default_start: + // Init .data and .bss + bl _init_segments + // Set stack pointer + ldr sp, =_hyp_stack_high_end + // Set the HVBAR (for EL2) to _vector_table + ldr r1, =_vector_table + mcr p15, 4, r1, c12, c0, 0 + // Mask IRQ and FIQ + mrs r0, CPSR + orr r0, {irq_fiq} + msr CPSR, r0 + "#, + fpu_enable!(), + r#" + // Zero all registers before calling kmain + mov r0, 0 + mov r1, 0 + mov r2, 0 + mov r3, 0 + mov r4, 0 + mov r5, 0 + mov r6, 0 + mov r7, 0 + mov r8, 0 + mov r9, 0 + mov r10, 0 + mov r11, 0 + mov r12, 0 + // Jump to application + bl kmain + // In case the application returns, loop forever + b . + .size _default_start, . - _default_start + "#, + irq_fiq = const aarch32_cpu::register::Cpsr::new_with_raw_value(0).with_i(true).with_f(true).raw_value() +); diff --git a/examples/mps3-an536-el2/.cargo/config.toml b/examples/mps3-an536-el2/.cargo/config.toml new file mode 100644 index 00000000..6960eb34 --- /dev/null +++ b/examples/mps3-an536-el2/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.armv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" + +[target.thumbv8r-none-eabihf] +# Note, this requires QEMU 9 or higher +runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel" + +[build] +target = "armv8r-none-eabihf" \ No newline at end of file diff --git a/examples/mps3-an536-el2/Cargo.toml b/examples/mps3-an536-el2/Cargo.toml new file mode 100644 index 00000000..5b30c467 --- /dev/null +++ b/examples/mps3-an536-el2/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = [ + "Jonathan Pallant ", + "The Embedded Devices Working Group Arm Team " +] +default-run = "hello" +description = "Examples for MPS3-AN536 device (Arm Cortex-R52)" +edition = "2024" +homepage = "https://github.com/rust-embedded/aarch32" +license = "MIT OR Apache-2.0" +name = "mps3-an536-el2" +publish = false +readme = "README.md" +repository = "https://github.com/rust-embedded/aarch32.git" +version = "0.0.0" + +[dependencies] +aarch32-cpu = { path = "../../aarch32-cpu", features = ["critical-section-multi-core"] } +aarch32-rt = { path = "../../aarch32-rt", features = ["el2-mode"] } +arm-gic = { version = "0.7.1" } +critical-section = "1.2.0" +heapless = "0.9.1" +libm = "0.2.15" +semihosting = { version = "0.1.18", features = ["stdio"] } + +[build-dependencies] +arm-targets = {version = "0.4.0", path = "../../arm-targets"} + +[features] +eabi-fpu = ["aarch32-rt/eabi-fpu"] +fpu-d32 = ["aarch32-rt/fpu-d32"] diff --git a/examples/mps3-an536-el2/README.md b/examples/mps3-an536-el2/README.md new file mode 100644 index 00000000..1f71befe --- /dev/null +++ b/examples/mps3-an536-el2/README.md @@ -0,0 +1,90 @@ +# Examples for Arm MPS3-AN536 + +This package contains example binaries for the Arm MPS3-AN536 evaluation system, +featuring one or two Arm Cortex-R52 processor cores. This crate is tested on the +following targets: + +- `armv8r-none-eabihf` - ARMv8-R AArch32, hard-float, Arm mode +- `thumbv8r-none-eabihf` - ARMv8-R AArch32, hard-float, Thumb mode + +The repo-level [`.cargo/config.toml`] will ensure the code runs on the +appropriate QEMU configuration. + +As of Rust 1.92, `armv8r-none-eabihf` is a Tier 2 target and so any stable +release from 1.92 or newer should work for that target. However, +`thumbv8r-none-eabihf` is still a Tier 3 target, which means Nightly Rust is +required. This folder contains a [`rust-toolchain.toml`] which pins us to a +specific release of nightly that is known to work. + +We have only tested this crate on `qemu-system-arm` emulating the Arm +MPS3-AN536, not the real thing. + +[`.cargo/config.toml`]: ../../.cargo/config.toml +[`rust-toolchain.toml`]: ./rust-toolchain.toml + +## Running + +Run these examples as follows: + +```console +$ cargo run --bin hello + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel target/armv8r-none-eabihf/debug/hello` +Hello, this is semihosting! x = 1.000, y = 2.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "src/bin/hello.rs", + line: 20, + column: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} +$ cargo run --bin hello --target thumbv8r-none-eabihf -Zbuild-std=core + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s + Running `qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -audio none -kernel target/thumbv8r-none-eabihf/debug/hello` +Hello, this is semihosting! x = 1.000, y = 2.000 +PANIC: PanicInfo { + message: I am an example panic, + location: Location { + file: "src/bin/hello.rs", + line: 20, + column: 5, + }, + can_unwind: true, + force_no_backtrace: false, +} +``` + +## Debugging + +You can start a GDB server by adding `-- -s -S` to the end of the `cargo run` +command, and the connect with GDB as follows: + +```console +$ cargo run --bin hello -- -s -S +# QEMU runs and hangs waiting for a connection. In another terminal run: +$ arm-none-eabi-gdb -x commands.gdb target/armv8r-none-eabihf/debug/hello +# GDB will start and connect to QEMU's GDB server. The commands.gdb file sets up some useful defaults. +``` + +## Minimum Supported Rust Version (MSRV) + +These examples are guaranteed to compile on the version of Rust given in the +[`rust-toolchain.toml`] file. These examples are not version controlled and we +may change the MSRV at any time. + +## Licence + +- Copyright (c) Ferrous Systems +- Copyright (c) The Rust Embedded Devices Working Group developers + +Licensed under either [MIT](../LICENSE-MIT) or [Apache-2.0](../LICENSE-APACHE) at +your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you shall be licensed as above, without any +additional terms or conditions. diff --git a/examples/mps3-an536-el2/build.rs b/examples/mps3-an536-el2/build.rs new file mode 100644 index 00000000..b0b9f098 --- /dev/null +++ b/examples/mps3-an536-el2/build.rs @@ -0,0 +1,26 @@ +//! # Build script for the MPS3-AN536 Examples +//! +//! This script only executes when using `cargo` to build the project. +//! +//! Copyright (c) Ferrous Systems, 2025 + +use std::io::Write; + +fn main() { + arm_targets::process(); + write("memory.x", include_bytes!("memory.x")); + // Use the cortex-m-rt linker script + println!("cargo:rustc-link-arg=-Tlink.x"); +} + +fn write(file: &str, contents: &[u8]) { + // Put linker file in our output directory and ensure it's on the + // linker search path. + let out = &std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + std::fs::File::create(out.join(file)) + .unwrap() + .write_all(contents) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed={}", file); +} diff --git a/examples/mps3-an536-el2/commands.gdb b/examples/mps3-an536-el2/commands.gdb new file mode 100644 index 00000000..0634363b --- /dev/null +++ b/examples/mps3-an536-el2/commands.gdb @@ -0,0 +1,13 @@ +target extended-remote :1234 +break kmain +break _asm_undefined_handler +break _asm_svc_handler +break _asm_prefetch_abort_handler +break _asm_data_abort_handler +break _asm_irq_handler +break _asm_fiq_handler +layout asm +layout regs +set logging file ./target/debug.log +set logging enabled on +stepi diff --git a/examples/mps3-an536-el2/memory.x b/examples/mps3-an536-el2/memory.x new file mode 100644 index 00000000..00158077 --- /dev/null +++ b/examples/mps3-an536-el2/memory.x @@ -0,0 +1,44 @@ +/* +Memory configuration for the MPS3-AN536 machine. + +See https://github.com/qemu/qemu/blob/master/hw/arm/mps3r.c +*/ + +MEMORY { + QSPI : ORIGIN = 0x08000000, LENGTH = 8M + BRAM : ORIGIN = 0x10000000, LENGTH = 512K + DDR : ORIGIN = 0x20000000, LENGTH = 1536M +} + +REGION_ALIAS("VECTORS", QSPI); +REGION_ALIAS("CODE", QSPI); +REGION_ALIAS("DATA", BRAM); +REGION_ALIAS("STACKS", BRAM); + +SECTIONS { + /* ### Interrupt Handler Entries + * + * The IRQ handler walks this section to find registered + * interrupt handlers + */ + .irq_entries : ALIGN(4) + { + /* We put this in the header */ + __irq_entries_start = .; + /* Here are the entries */ + KEEP(*(.irq_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __irq_entries_end = .; + } > CODE +} INSERT AFTER .text; + + +PROVIDE(_hyp_stack_size = 16K); +PROVIDE(_und_stack_size = 16K); +PROVIDE(_svc_stack_size = 16K); +PROVIDE(_abt_stack_size = 16K); +PROVIDE(_irq_stack_size = 64); +PROVIDE(_fiq_stack_size = 64); +PROVIDE(_sys_stack_size = 16K); diff --git a/examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..1d3d93c4 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..1d3d93c4 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..f53b10f6 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..f53b10f6 --- /dev/null +++ b/examples/mps3-an536-el2/reference/abt-exception-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is an data abort exception example +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Doing it again +data abort occurred Hsr { ec: Ok(DataAbortFromCurrent), il: ThirtyTwoBit, iss: 33 } Some(DataAbortFromCurrent(IssDataAbort { isv: false, sas: 0, sae: false, srt: 0, ar: false, fnv: false, ea: false, cm: false, wnr: false, dfsc: 21 })) +caught unaligned_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out new file mode 100644 index 00000000..9f58641c --- /dev/null +++ b/examples/mps3-an536-el2/reference/generic-timer-armv8r-none-eabihf.out @@ -0,0 +1,40 @@ +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Hcr { tcpac: false, trvm: false, hcd: false, tge: false, tvm: false, tpu: false, tpc: false, tsw: false, tac: false, tidcp: false, tid3: false, tid2: false, tid1: false, tid0: false, twe: false, twi: false, dc: false, bsu: NoEffect, fb: false, va: false, vi: false, vf: false, amo: false, imo: false, fmo: false, swio: false, vm: false } +Configure Timer Interrupt... +Timer Hz = 62500000 +Enabling interrupts... +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +EL2 timer test completed OK diff --git a/examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..9f58641c --- /dev/null +++ b/examples/mps3-an536-el2/reference/generic-timer-thumbv8r-none-eabihf.out @@ -0,0 +1,40 @@ +Found PERIPHBASE 0xf0000000 +Creating GIC driver @ 0xf0000000 / 0xf0100000 +Calling git.setup(0) +Hcr { tcpac: false, trvm: false, hcd: false, tge: false, tvm: false, tpu: false, tpc: false, tsw: false, tac: false, tidcp: false, tid3: false, tid2: false, tid1: false, tid0: false, twe: false, twi: false, dc: false, bsu: NoEffect, fb: false, va: false, vi: false, vf: false, amo: false, imo: false, fmo: false, swio: false, vm: false } +Configure Timer Interrupt... +Timer Hz = 62500000 +Enabling interrupts... +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +> IRQ +Hyp timer tick! +< IRQ +EL2 timer test completed OK diff --git a/examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out new file mode 100644 index 00000000..3238d13c --- /dev/null +++ b/examples/mps3-an536-el2/reference/hello-armv8r-none-eabihf.out @@ -0,0 +1,19 @@ +Hello, this is semihosting! x = 1.000, y = 2.000 +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +CPSR { N=0 Z=1 C=1 V=0 Q=0 J=0 E=0 A=1 I=1 F=1 T=0 MODE=Ok(Hyp) } +Region 0: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 1: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 2: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 3: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 4: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 5: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 6: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 7: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 8: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 9: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 10: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 11: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 12: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 13: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 14: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 15: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } diff --git a/examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..3238d13c --- /dev/null +++ b/examples/mps3-an536-el2/reference/hello-thumbv8r-none-eabihf.out @@ -0,0 +1,19 @@ +Hello, this is semihosting! x = 1.000, y = 2.000 +SCTLR { IE=0 TE=0 NMFI=0 EE=0 U=1 FI=0 DZ=1 BR=0 RR=0 V=0 I=0 Z=1 SW=0 C=0 A=0 M=0 } +CPSR { N=0 Z=1 C=1 V=0 Q=0 J=0 E=0 A=1 I=1 F=1 T=0 MODE=Ok(Hyp) } +Region 0: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 1: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 2: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 3: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 4: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 5: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 6: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 7: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 8: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 9: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 10: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 11: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 12: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 13: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 14: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } +Region 15: El2Region { range: 0x0..=0x3f, shareability: NonShareable, access: ReadWriteNoEL10, no_exec: false, mair: 0, enable: false } diff --git a/examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-a32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-t32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..0800ffcf --- /dev/null +++ b/examples/mps3-an536-el2/reference/hvc-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Hvc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Hvc), il: ThirtyTwoBit, iss: 00009876 }, Some(Hvc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..50cd4e78 --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..50cd4e78 --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..fa18811d --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..fa18811d --- /dev/null +++ b/examples/mps3-an536-el2/reference/prefetch-exception-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a prefetch abort exception example +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Doing it again +prefetch abort occurred Hsr { ec: Ok(PrefetchAbortFromCurrent), il: ThirtyTwoBit, iss: 00000022 }, Some(PrefetchAbortFromCurrent(IssPrefetchAbort { fnv: false, ea: false, ifsc: 22 })) +caught bkpt_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..f5d7e128 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-a32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Svc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 00009876 }, Some(Svc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..f5d7e128 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 0000abcd }, Some(Svc(IssCall { imm16: abcd })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: ThirtyTwoBit, iss: 00009876 }, Some(Svc(IssCall { imm16: 9876 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..92fda701 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-t32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000012 }, Some(Svc(IssCall { imm16: 12 })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000032 }, Some(Svc(IssCall { imm16: 32 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..92fda701 --- /dev/null +++ b/examples/mps3-an536-el2/reference/svc-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,5 @@ +x = 1, y = 2, z = 3.000 +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000012 }, Some(Svc(IssCall { imm16: 12 })), Frame { r0: 10000000, r1: 10000001, r2: 10000002, r3: 10000003, r4: 10000004, r5: 10000005 } +In hvc_handler, with Hsr { ec: Ok(Svc), il: SixteenBit, iss: 00000032 }, Some(Svc(IssCall { imm16: 32 })), Frame { r0: 20000000, r1: 20000001, r2: 20000002, r3: 20000003, r4: 20000004, r5: 20000005 } +Got 12345678 +x = 1, y = 2, z = 3.000 diff --git a/examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 00000000..7258e7b2 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..7258e7b2 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-a32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_a32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 00000000..c0f85065 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out b/examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..c0f85065 --- /dev/null +++ b/examples/mps3-an536-el2/reference/undef-exception-t32-thumbv8r-none-eabihf.out @@ -0,0 +1,8 @@ +Hello, this is a undef exception example +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Doing it again +undefined abort occurred Hsr { ec: Ok(Unknown), il: ThirtyTwoBit, iss: 00000000 }, Some(Unknown(IssUnknown(0))) +caught udf_from_t32 +Skipping instruction +Recovered from fault OK! diff --git a/examples/mps3-an536-el2/rust-toolchain.toml b/examples/mps3-an536-el2/rust-toolchain.toml new file mode 100644 index 00000000..7e4d9e6c --- /dev/null +++ b/examples/mps3-an536-el2/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "nightly-2026-01-26" +targets = [ + "armv8r-none-eabihf", +] +components = ["rust-src", "clippy", "rustfmt"] diff --git a/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs b/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs new file mode 100644 index 00000000..9bf95d4c --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/abt-exception-a32.rs @@ -0,0 +1,120 @@ +//! Example triggering an data abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_cpu::register::Hsctlr; +use aarch32_rt::{entry, exception}; +use semihosting::println; + +#[unsafe(no_mangle)] +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + // Enable alignment check for Armv7-R. Was not required + // on Cortex-A for some reason, even though the bit was not set. + enable_alignment_check(); + + println!("Hello, this is an data abort exception example"); + unsafe { + // Unaligned read + unaligned_from_a32(); + } + + // turn it off before we do the stack dump on exit, because println! has been + // observed to do unaligned reads. + disable_alignment_check(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn unaligned_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn unaligned_from_a32(); + .arm + .global unaligned_from_a32 + .type unaligned_from_a32, %function + unaligned_from_a32: + ldr r0, =COUNTER + add r0, r0, 1 + ldr r0, [r0] + bx lr + .size unaligned_from_a32, . - unaligned_from_a32 +"# +); + +fn enable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(true); + Hsctlr::write(hsctrl); +} + +fn disable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(false); + Hsctlr::write(hsctrl); +} + +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected prefetch abort"); +} + +#[exception(DataAbort)] +unsafe fn data_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + disable_alignment_check(); + println!("data abort occurred {:?} {:x?}", hsr, hsr.get_iss()); + enable_alignment_check(); + + // note the fault isn't at the start of the function + let expect_fault_at = unaligned_from_a32 as unsafe extern "C" fn() as usize + 8; + + if addr == expect_fault_at { + println!("caught unaligned_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, expect_fault_at + ); + semihosting::process::abort(); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 4 + } + _ => { + // we've faulted thrice - time to quit + println!("We triple faulted"); + semihosting::process::abort(); + } + } +} diff --git a/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs b/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs new file mode 100644 index 00000000..9ac310f1 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/abt-exception-t32.rs @@ -0,0 +1,120 @@ +//! Example triggering an data abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_cpu::register::Hsctlr; +use aarch32_rt::{entry, exception}; +use semihosting::println; + +#[unsafe(no_mangle)] +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + // Enable alignment check for Armv7-R. Was not required + // on Cortex-A for some reason, even though the bit was not set. + enable_alignment_check(); + + println!("Hello, this is an data abort exception example"); + unsafe { + // Unaligned read + unaligned_from_t32(); + } + + // turn it off before we do the stack dump on exit, because println! has been + // observed to do unaligned reads. + disable_alignment_check(); + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn unaligned_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn unaligned_from_t32(); + .thumb + .global unaligned_from_t32 + .type unaligned_from_t32, %function + unaligned_from_t32: + ldr r0, =COUNTER + add r0, r0, 1 + ldr r0, [r0] + bx lr + .size unaligned_from_t32, . - unaligned_from_t32 +"# +); + +fn enable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(true); + Hsctlr::write(hsctrl); +} + +fn disable_alignment_check() { + let mut hsctrl = Hsctlr::read(); + hsctrl.set_a(false); + Hsctlr::write(hsctrl); +} + +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected prefetch abort"); +} + +#[exception(DataAbort)] +unsafe fn data_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + disable_alignment_check(); + println!("data abort occurred {:?} {:x?}", hsr, hsr.get_iss()); + enable_alignment_check(); + + // note the fault isn't at the start of the function + let expect_fault_at = unaligned_from_t32 as unsafe extern "C" fn() as usize + 5; + + if addr == expect_fault_at { + println!("caught unaligned_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, expect_fault_at + ); + semihosting::process::abort(); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 2 + } + _ => { + // we've faulted thrice - time to quit + println!("We triple faulted"); + semihosting::process::abort(); + } + } +} diff --git a/examples/mps3-an536-el2/src/bin/generic-timer.rs b/examples/mps3-an536-el2/src/bin/generic-timer.rs new file mode 100644 index 00000000..102a3220 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/generic-timer.rs @@ -0,0 +1,115 @@ +//! Hyp Timer Test Arm Cortex-R52 running in EL2 (Hyp Mode) + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; + +use aarch32_cpu::generic_timer::GenericTimer; +use aarch32_rt::{entry, exception, irq}; +use arm_gic::gicv3::{GicCpuInterface, Group, InterruptGroup}; +use semihosting::println; + +use mps3_an536_el2::HYP_TIMER_PPI; + +static TICK_COUNT: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code at the bottom of this file. +#[entry] +fn main() -> ! { + let mut board = mps3_an536_el2::Board::new().unwrap(); + + println!("{:?}", aarch32_cpu::register::Hcr::read()); + + println!("Configure Timer Interrupt..."); + board + .gic + .set_interrupt_priority(HYP_TIMER_PPI, Some(0), 0x31) + .unwrap(); + board + .gic + .set_group(HYP_TIMER_PPI, Some(0), Group::Group1NS) + .unwrap(); + board + .gic + .enable_interrupt(HYP_TIMER_PPI, Some(0), true) + .unwrap(); + + let mut hyp_timer = board.hyp_timer; + + println!("Timer Hz = {}", hyp_timer.frequency_hz()); + hyp_timer.interrupt_mask(false); + hyp_timer.countdown_set(hyp_timer.frequency_hz() / 5); + hyp_timer.enable(true); + // used in interrupt handler + drop(hyp_timer); + + println!("Enabling interrupts..."); + dump_sctlr(); + unsafe { + aarch32_cpu::interrupt::enable(); + } + dump_sctlr(); + + loop { + aarch32_cpu::asm::wfi(); + let tick_count = TICK_COUNT.load(Relaxed); + // println!("Main loop wake up {}", tick_count); + if tick_count >= 10 { + break; + } + } + + println!("EL2 timer test completed OK"); + + mps3_an536_el2::exit(0); +} + +fn dump_sctlr() { + let sctlr = aarch32_cpu::register::Sctlr::read(); + println!("{:?}", sctlr); +} + +#[irq] +fn irq_handler() { + println!("> IRQ"); + while let Some(int_id) = GicCpuInterface::get_and_acknowledge_interrupt(InterruptGroup::Group1) + { + match int_id { + HYP_TIMER_PPI => { + println!("Hyp timer tick!"); + handle_timer_irq(); + } + _ => { + println!("Interrupt {:?}?", int_id); + } + } + GicCpuInterface::end_interrupt(int_id, InterruptGroup::Group1); + } + println!("< IRQ"); +} + +/// Run when the timer IRQ fires +fn handle_timer_irq() { + // SAFETY: We drop en other time handle in main, this is the only active handle. + let mut el2_timer = unsafe { aarch32_cpu::generic_timer::El2HypPhysicalTimer::new() }; + // trigger a timer in 0.2 seconds + el2_timer.countdown_set(el2_timer.frequency_hz() / 5); + // tell the main loop the timer went tick + TICK_COUNT.fetch_add(1, Relaxed); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + return frame.r0; +} diff --git a/examples/mps3-an536-el2/src/bin/hello.rs b/examples/mps3-an536-el2/src/bin/hello.rs new file mode 100644 index 00000000..7f1c01df --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/hello.rs @@ -0,0 +1,28 @@ +//! Semihosting hello-world for Arm Cortex-R52 running in EL2 (Hyp Mode) + +#![no_std] +#![no_main] + +use aarch32_rt::entry; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code at the bottom of this file. +#[entry] +fn main() -> ! { + let x = 1.0f64; + let y = x * 2.0; + println!("Hello, this is semihosting! x = {:0.3}, y = {:0.3}", x, y); + println!("{:?}", aarch32_cpu::register::Sctlr::read()); + println!("{:?}", aarch32_cpu::register::Cpsr::read()); + + let mut mpu = unsafe { aarch32_cpu::pmsav8::El2Mpu::new() }; + for idx in 0..mpu.num_regions() { + if let Some(region) = mpu.get_region(idx) { + println!("Region {}: {:?}", idx, region); + } + } + + mps3_an536_el2::exit(0); +} diff --git a/examples/mps3-an536-el2/src/bin/hvc-a32.rs b/examples/mps3-an536-el2/src/bin/hvc-a32.rs new file mode 100644 index 00000000..8d12237b --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/hvc-a32.rs @@ -0,0 +1,64 @@ +//! HVC (hypervisor call) example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_hvc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0xABCD { + do_hvc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::a32)] +fn do_hvc1() -> u32 { + aarch32_cpu::hvc6!( + 0xABCD, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::a32)] +fn do_hvc2() -> u32 { + aarch32_cpu::hvc6!( + 0x9876, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/hvc-t32.rs b/examples/mps3-an536-el2/src/bin/hvc-t32.rs new file mode 100644 index 00000000..98ad5808 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/hvc-t32.rs @@ -0,0 +1,64 @@ +//! HVC (hypervisor call) example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_hvc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0xABCD { + do_hvc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::t32)] +fn do_hvc1() -> u32 { + aarch32_cpu::hvc6!( + 0xABCD, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::t32)] +fn do_hvc2() -> u32 { + aarch32_cpu::hvc6!( + 0x9876, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs b/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs new file mode 100644 index 00000000..f660baca --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/prefetch-exception-a32.rs @@ -0,0 +1,93 @@ +//! Example triggering a prefetch abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a prefetch abort exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + unsafe { + // trigger an prefetch abort exception, from A32 (Arm) mode + bkpt_from_a32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn bkpt_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_a32(); + .arm + .global bkpt_from_a32 + .type bkpt_from_a32, %function + bkpt_from_a32: + bkpt #0 + bx lr + .size bkpt_from_a32, . - bkpt_from_a32 +"# +); + +// Custom link sections are allowed as well. +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +unsafe fn prefetch_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!("prefetch abort occurred {:08x?}, {:x?}", hsr, hsr.get_iss()); + + if addr == bkpt_from_a32 as unsafe extern "C" fn() as usize { + println!("caught bkpt_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_a32 as unsafe extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 4 + } + _ => { + // we've faulted thrice - time to quit + panic!("prefetch_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs b/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs new file mode 100644 index 00000000..83270378 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/prefetch-exception-t32.rs @@ -0,0 +1,95 @@ +//! Example triggering a prefetch abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a prefetch abort exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + unsafe { + // trigger an prefetch abort exception, from T32 (Thumb) mode + bkpt_from_t32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn bkpt_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_t32(); + .thumb + .global bkpt_from_t32 + .type bkpt_from_t32, %function + bkpt_from_t32: + bkpt #0 + bx lr + .size bkpt_from_t32, . - bkpt_from_t32 +"# +); + +#[exception(Undefined)] +fn undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(PrefetchAbort)] +unsafe fn prefetch_abort_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!("prefetch abort occurred {:08x?}, {:x?}", hsr, hsr.get_iss()); + + if (addr + 1) == bkpt_from_t32 as unsafe extern "C" fn() as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught bkpt_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_t32 as unsafe extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 2 + } + _ => { + // we've faulted thrice - time to quit + panic!("prefetch_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/bin/svc-a32.rs b/examples/mps3-an536-el2/src/bin/svc-a32.rs new file mode 100644 index 00000000..e131b768 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/svc-a32.rs @@ -0,0 +1,64 @@ +//! SVC (supervisor call) at EL2 example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_svc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0xABCD { + do_svc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::a32)] +fn do_svc1() -> u32 { + aarch32_cpu::svc6!( + 0xABCD, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::a32)] +fn do_svc2() -> u32 { + aarch32_cpu::svc6!( + 0x9876, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/svc-t32.rs b/examples/mps3-an536-el2/src/bin/svc-t32.rs new file mode 100644 index 00000000..6957aea0 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/svc-t32.rs @@ -0,0 +1,64 @@ +//! SVC (supervisor call) at EL2 example + +#![no_std] +#![no_main] + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + let x = 1; + let y = x + 1; + let z = (y as f64) * 1.5; + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + let value = do_svc1(); + println!("Got {:08x}", value); + println!("x = {}, y = {}, z = {:0.3}", x, y, z); + mps3_an536_el2::exit(0); +} + +/// This is our HVC exception handler +#[exception(HypervisorCall)] +fn hvc_handler(hsr: u32, frame: &aarch32_rt::Frame) -> u32 { + let hsr = aarch32_cpu::register::Hsr::new_with_raw_value(hsr); + println!( + "In hvc_handler, with {:08x?}, {:x?}, {:08x?}", + hsr, + hsr.get_iss(), + frame + ); + if hsr.iss().value() == 0x12 { + do_svc2(); + } + return 0x12345678; +} + +#[instruction_set(arm::t32)] +fn do_svc1() -> u32 { + aarch32_cpu::svc6!( + 0x12, + 0x1000_0000, + 0x1000_0001, + 0x1000_0002, + 0x1000_0003, + 0x1000_0004, + 0x1000_0005 + ) +} + +#[instruction_set(arm::t32)] +fn do_svc2() -> u32 { + aarch32_cpu::svc6!( + 0x32, + 0x2000_0000, + 0x2000_0001, + 0x2000_0002, + 0x2000_0003, + 0x2000_0004, + 0x2000_0005 + ) +} diff --git a/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs b/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs new file mode 100644 index 00000000..bc1f34ed --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/undef-exception-a32.rs @@ -0,0 +1,94 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a undef exception example"); + + unsafe { + // trigger an Undefined exception, from A32 (Arm) mode + udf_from_a32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn udf_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_a32(); + .arm + .global udf_from_a32 + .type udf_from_a32, %function + udf_from_a32: + udf #0 + bx lr + .size udf_from_a32, . - udf_from_a32 +"# +); + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(Undefined)] +unsafe fn undefined_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!( + "undefined abort occurred {:08x?}, {:x?}", + hsr, + hsr.get_iss() + ); + + if addr == udf_from_a32 as unsafe extern "C" fn() as usize { + println!("caught udf_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_a32 as unsafe extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 4 + } + _ => { + // we've faulted thrice - time to quit + panic!("_undefined_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs b/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs new file mode 100644 index 00000000..01b57ea2 --- /dev/null +++ b/examples/mps3-an536-el2/src/bin/undef-exception-t32.rs @@ -0,0 +1,97 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use aarch32_rt::{entry, exception}; +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[entry] +fn main() -> ! { + println!("Hello, this is a undef exception example"); + + unsafe { + // trigger an Undefined exception, from T32 (Thumb) mode + udf_from_t32(); + } + + println!("Recovered from fault OK!"); + + mps3_an536_el2::exit(0); +} + +// These functions are written in assembly +unsafe extern "C" { + fn udf_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_t32(); + .thumb + .global udf_from_t32 + .type udf_from_t32, %function + udf_from_t32: + udf #0 + bx lr + .size udf_from_t32, . - udf_from_t32 +"# +); + +#[exception(PrefetchAbort)] +fn prefetch_abort_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[exception(Undefined)] +unsafe fn undefined_handler(addr: usize) -> usize { + let hsr = aarch32_cpu::register::Hsr::read(); + println!( + "undefined abort occurred {:08x?}, {:x?}", + hsr, + hsr.get_iss() + ); + + if (addr + 1) == udf_from_t32 as unsafe extern "C" fn() as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught udf_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_t32 as unsafe extern "C" fn() as usize + ); + } + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + // first time, huh? + // go back and do it again + println!("Doing it again"); + addr + } + 1 => { + // second time, huh? + // go back but skip the instruction + println!("Skipping instruction"); + addr + 2 + } + _ => { + // we've faulted thrice - time to quit + panic!("_undefined_handler called too often"); + } + } +} + +#[exception(DataAbort)] +fn data_abort_handler(_addr: usize) -> ! { + panic!("unexpected data abort exception"); +} diff --git a/examples/mps3-an536-el2/src/lib.rs b/examples/mps3-an536-el2/src/lib.rs new file mode 100644 index 00000000..0b1669f9 --- /dev/null +++ b/examples/mps3-an536-el2/src/lib.rs @@ -0,0 +1,259 @@ +//! Common code for all examples +//! +//! ## Interrupt Map +//! +//! | Interrupt ID | Description | +//! |--------------|------------------------------| +//! | `EXTPPI0[0]` | UART 0 Receive Interrupt | +//! | `EXTPPI0[1]` | UART 0 Transmit Interrupt | +//! | `EXTPPI0[2]` | UART 0 Combined Interrupt | +//! | `EXTPPI0[3]` | UART 0 Overflow | +//! | `EXTPPI1[0]` | UART 1 Receive Interrupt | +//! | `EXTPPI1[1]` | UART 1 Transmit Interrupt | +//! | `EXTPPI1[2]` | UART 1 Combined Interrupt | +//! | `EXTPPI1[3]` | UART 1 Overflow | +//! | `SP[0]` | WDG | +//! | `SP[1]` | DualTimer 1 | +//! | `SP[2]` | DualTimer 2 | +//! | `SP[3]` | DualTimer Combined | +//! | `SP[4]` | RTC | +//! | `SP[5]` | UART 2 Receive Interrupt | +//! | `SP[6]` | UART 2 Transmit Interrupt | +//! | `SP[7]` | UART 3 Receive Interrupt | +//! | `SP[8]` | UART 3 Transmit Interrupt | +//! | `SP[9]` | UART 4 Receive Interrupt | +//! | `SP[10]` | UART 4 Transmit Interrupt | +//! | `SP[11]` | UART 5 Receive Interrupt | +//! | `SP[12]` | UART 5 Transmit Interrupt | +//! | `SP[13]` | UART 2 Combined Interrupt | +//! | `SP[14]` | UART 3 Combined Interrupt | +//! | `SP[15]` | UART 4 Combined Interrupt | +//! | `SP[16]` | UART 5 Combined Interrupt | +//! | `SP[17]` | UART Overflow (2, 3, 4 & 5) | +//! | `SP[18]` | Ethernet | +//! | `SP[19]` | USB | +//! | `SP[20]` | FPGA Audio I2S | +//! | `SP[21]` | Touch Screen | +//! | `SP[22]` | SPI ADC | +//! | `SP[23]` | SPI Shield 0 | +//! | `SP[24]` | SPI Shield 1 | +//! | `SP[25]` | HDCLCD Interrupt | +//! | `SP[26]` | GPIO 0 Combined Interrupt | +//! | `SP[27]` | GPIO 1 Combined Interrupt | +//! | `SP[28]` | GPIO 2 Combined Interrupt | +//! | `SP[29]` | GPIO 3 Combined Interrupt | +//! | `SP[30..=45]`| GPIO 0.x Interrupt | +//! | `SP[46..=61]`| GPIO 1.x Interrupt | +//! | `SP[62..=77]`| GPIO 2.x Interrupt | +//! | `SP[78..=93]`| GPIO 3.x Interrupt | +//! +//! * Interrupt ID `SP[x]` are shared across cores +//! * Interrupt ID `EXTPPI0[x]` is only available on Core 0 +//! * Interrupt ID `EXTPPI1[x]` is only available on Core 1 + +#![no_std] + +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The PPI for the virtual timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 27. +pub const VIRTUAL_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(11); + +/// The PPI for the EL2 timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 26. +pub const HYP_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(10); + +#[cfg(not(arm_architecture = "v8-r"))] +compile_error!("This example is only compatible to the ARMv8-R architecture"); + +static WANT_PANIC: AtomicBool = AtomicBool::new(false); + +/// Track if we're already in the exit routine. +/// +/// Stops us doing infinite recursion if we panic whilst doing the stack reporting. +static IN_EXIT: AtomicBool = AtomicBool::new(false); + +/// Called when the application raises an unrecoverable `panic!`. +/// +/// Prints the panic to the console and then exits QEMU using a semihosting +/// breakpoint. +#[panic_handler] +#[cfg(target_os = "none")] +fn panic(info: &core::panic::PanicInfo) -> ! { + semihosting::println!("PANIC: {:#?}", info); + if WANT_PANIC.load(Ordering::Relaxed) { + exit(0); + } else { + exit(1); + } +} + +/// Set the panic function as no longer returning a failure code via semihosting +pub fn want_panic() { + WANT_PANIC.store(true, Ordering::Relaxed); +} + +/// Exit from QEMU with code +pub fn exit(code: i32) -> ! { + if !IN_EXIT.swap(true, Ordering::Relaxed) { + stack_dump(); + } + semihosting::process::exit(code) +} + +/// Print stack using to semihosting output for each stack +/// +/// Produces output like: +/// +/// ```text +/// Stack usage report: +/// UND0 Stack = 0 used of 16384 bytes (000%) @ 0x1006bf80..0x1006ff80 +/// SVC0 Stack = 0 used of 16384 bytes (000%) @ 0x1006ff80..0x10073f80 +/// ABT0 Stack = 0 used of 16384 bytes (000%) @ 0x10073f80..0x10077f80 +/// HYP0 Stack = 0 used of 16384 bytes (000%) @ 0x10077f80..0x1007bf80 +/// IRQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bf80..0x1007bfc0 +/// FIQ0 Stack = 0 used of 64 bytes (000%) @ 0x1007bfc0..0x1007c000 +/// SYS0 Stack = 2416 used of 16384 bytes (014%) @ 0x1007c000..0x10080000 +/// ``` +fn stack_dump() { + use aarch32_cpu::stacks::stack_used_bytes; + use aarch32_rt::stacks::Stack; + + semihosting::eprintln!("Stack usage report:"); + + unsafe { + for stack in Stack::iter() { + for core in (0..Stack::num_cores()).rev() { + let core_range = stack.range(core).unwrap(); + let (total, used) = stack_used_bytes(core_range.clone()); + let percent = used * 100 / total; + // Send to stderr, so it doesn't mix with expected output on stdout + semihosting::eprintln!( + "{}{} Stack = {:6} used of {:6} bytes ({:03}%) @ {:08x?}", + stack, + core, + used, + total, + percent, + core_range + ); + } + } + } +} + +#[derive(Clone, Debug)] +/// Represents a handler for an interrupt +pub struct InterruptHandler { + int_id: arm_gic::IntId, + function: fn(arm_gic::IntId), +} + +impl InterruptHandler { + /// Create a new `InterruptHandler`, associating an `IntId` with a function to call + pub const fn new(int_id: arm_gic::IntId, function: fn(arm_gic::IntId)) -> InterruptHandler { + InterruptHandler { int_id, function } + } + + /// Get the [`arm_gic::IntId`] this handler is for + pub const fn int_id(&self) -> arm_gic::IntId { + self.int_id + } + + /// Is this handler for this [`arm_gic::IntId`]? + pub fn matches(&self, int_id: arm_gic::IntId) -> bool { + self.int_id == int_id + } + + /// Execute the handler + pub fn execute(&self) { + (self.function)(self.int_id); + } +} + +/// Represents all the hardware we support in our MPS3-AN536 system +pub struct Board { + /// The Arm Generic Interrupt Controller (v3) + pub gic: arm_gic::gicv3::GicV3<'static>, + /// The Arm Virtual Generic Timer + pub virtual_timer: aarch32_cpu::generic_timer::El2VirtualTimer, + /// The Arm Physical Generic Timer + pub physical_timer: aarch32_cpu::generic_timer::El2PhysicalTimer, + /// The Arm EL2-specific Physical Generic Timer + pub hyp_timer: aarch32_cpu::generic_timer::El2HypPhysicalTimer, +} + +impl Board { + /// Create a new board structure. + /// + /// Returns `Some(board)` the first time you call it, and None thereafter, + /// so you cannot have two copies of the [`Board`] structure. + pub fn new() -> Option { + static TAKEN: AtomicBool = AtomicBool::new(false); + if TAKEN + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + { + Some(Board { + // SAFETY: This is the first and only call to `make_gic()` as guaranteed by + // the atomic flag check above, ensuring no aliasing of GIC register access. + gic: unsafe { make_gic() }, + // SAFETY: This is the first and only time we create the virtual timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + virtual_timer: unsafe { aarch32_cpu::generic_timer::El2VirtualTimer::new() }, + // SAFETY: This is the first and only time we create the physical timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + physical_timer: unsafe { aarch32_cpu::generic_timer::El2PhysicalTimer::new() }, + // SAFETY: This is the first and only time we create the physical timer instance + // as guaranteed by the atomic flag check above, ensuring exclusive access. + hyp_timer: unsafe { aarch32_cpu::generic_timer::El2HypPhysicalTimer::new() }, + }) + } else { + None + } + } +} + +/// Create the ARM GIC driver +/// +/// # Safety +/// +/// Only call this function once. +unsafe fn make_gic() -> arm_gic::gicv3::GicV3<'static> { + /// Offset from PERIPHBASE for GIC Distributor + const GICD_BASE_OFFSET: usize = 0x0000_0000usize; + + /// Offset from PERIPHBASE for the first GIC Redistributor + const GICR_BASE_OFFSET: usize = 0x0010_0000usize; + + // Get the GIC address by reading CBAR + let periphbase = aarch32_cpu::register::ImpCbar::read().periphbase(); + semihosting::println!("Found PERIPHBASE {:010p}", periphbase); + let gicd_base = periphbase.wrapping_byte_add(GICD_BASE_OFFSET); + let gicr_base = periphbase.wrapping_byte_add(GICR_BASE_OFFSET); + + // Initialise the GIC. + semihosting::println!( + "Creating GIC driver @ {:010p} / {:010p}", + gicd_base, + gicr_base + ); + // SAFETY: `gicd_base` points to the valid GICD MMIO region as obtained from the + // hardware CBAR register. This pointer is used exclusively by this GIC instance. + let gicd = unsafe { + arm_gic::UniqueMmioPointer::new(core::ptr::NonNull::new(gicd_base.cast()).unwrap()) + }; + let gicr_base = core::ptr::NonNull::new(gicr_base.cast()).unwrap(); + // SAFETY: The GICD and GICR base addresses point to valid GICv3 MMIO regions as + // obtained from the hardware CBAR register. This function is only called once + // (via Board::new()'s atomic guard), ensuring exclusive ownership of the GIC. + let mut gic = unsafe { arm_gic::gicv3::GicV3::new(gicd, gicr_base, 1, false) }; + semihosting::println!("Calling git.setup(0)"); + gic.setup(0); + arm_gic::gicv3::GicCpuInterface::set_priority_mask(0x80); + gic +} diff --git a/examples/mps3-an536-smp/src/bin/smp-test.rs b/examples/mps3-an536-smp/src/bin/smp-test.rs index 4364fbbe..9fb75435 100644 --- a/examples/mps3-an536-smp/src/bin/smp-test.rs +++ b/examples/mps3-an536-smp/src/bin/smp-test.rs @@ -32,7 +32,10 @@ const CS_MUTEX_LOOPS: u32 = 1000; /// It is called by the start-up code in `aarch32-rt`. #[entry] fn main() -> ! { - println!("I am core 0 - {:08x?}", aarch32_cpu::register::Mpidr::read()); + println!( + "I am core 0 - {:08x?}", + aarch32_cpu::register::Mpidr::read() + ); mps3_an536_smp::start_core1(); @@ -93,7 +96,10 @@ fn main() -> ! { /// It is called by the start-up code below, on Core 1. #[unsafe(no_mangle)] pub extern "C" fn kmain2() { - println!("I am core 1 - {:08x?}", aarch32_cpu::register::Mpidr::read()); + println!( + "I am core 1 - {:08x?}", + aarch32_cpu::register::Mpidr::read() + ); CORE1_BOOTED.store(true, Ordering::SeqCst); for _ in 0..CAS_LOOPS { diff --git a/examples/mps3-an536-smp/src/lib.rs b/examples/mps3-an536-smp/src/lib.rs index e3a798e2..a187a6ba 100644 --- a/examples/mps3-an536-smp/src/lib.rs +++ b/examples/mps3-an536-smp/src/lib.rs @@ -53,7 +53,7 @@ #![no_std] -use aarch32_cpu::register::{Hactlr, Cpsr, cpsr::ProcessorMode}; +use aarch32_cpu::register::{Cpsr, Hactlr, cpsr::ProcessorMode}; use core::sync::atomic::{AtomicBool, Ordering}; diff --git a/examples/mps3-an536/src/bin/el2_hello.rs b/examples/mps3-an536/src/bin/el2_hello.rs deleted file mode 100644 index e10a979c..00000000 --- a/examples/mps3-an536/src/bin/el2_hello.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Semihosting hello-world for Arm Cortex-R52 running in EL2 (Hyp Mode) - -#![no_std] -#![no_main] - -use aarch32_cpu::register::Hactlr; -use aarch32_rt::entry; -use mps3_an536 as _; -use semihosting::println; - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up code at the bottom of this file. -#[entry] -fn main() -> ! { - let x = 1.0f64; - let y = x * 2.0; - println!("Hello, this is semihosting! x = {:0.3}, y = {:0.3}", x, y); - - let mut mpu = unsafe { aarch32_cpu::pmsav8::El2Mpu::new() }; - for idx in 0..mpu.num_regions() { - if let Some(region) = mpu.get_region(idx) { - println!("Region {}: {:?}", idx, region); - } - } - - mps3_an536::want_panic(); - panic!("I am an example panic"); -} - -// Provide a custom `_start` function that sets us up in EL2 mode, with a -// stack. -// -// Unlike the default routine, it does not initialise any other stacks, or -// switch to EL1 mode. -core::arch::global_asm!( - r#" - // Work around https://github.com/rust-lang/rust/issues/127269 - .fpu vfp3-d16 - - .section .text.start - - .global _start - .type _start, %function - _start: - // Set stack pointer - ldr sp, =_hyp_stack_high_end - // Set the HVBAR (for EL2) to _vector_table - ldr r1, =_vector_table - mcr p15, 4, r1, c12, c0, 0 - // Configure HACTLR to let us enter EL1 - mrc p15, 4, r1, c1, c0, 1 - mov r2, {hactlr_bits} - orr r1, r1, r2 - mcr p15, 4, r1, c1, c0, 1 - // Init .data and .bss - bl _init_segments - // Allow VFP coprocessor access - mrc p15, 0, r0, c1, c0, 2 - orr r0, r0, #0xF00000 - mcr p15, 0, r0, c1, c0, 2 - // Enable VFP - mov r0, #0x40000000 - vmsr fpexc, r0 - // Zero all registers before calling kmain - mov r0, 0 - mov r1, 0 - mov r2, 0 - mov r3, 0 - mov r4, 0 - mov r5, 0 - mov r6, 0 - mov r7, 0 - mov r8, 0 - mov r9, 0 - mov r10, 0 - mov r11, 0 - mov r12, 0 - // Jump to application - bl kmain - // In case the application returns, loop forever - b . - .size _start, . - _start - "#, - hactlr_bits = const { - Hactlr::new_with_raw_value(0) - .with_cpuactlr(true) - .with_cdbgdci(true) - .with_flashifregionr(true) - .with_periphpregionr(true) - .with_qosr(true) - .with_bustimeoutr(true) - .with_intmonr(true) - .with_err(true) - .with_testr1(true) - .raw_value() - }, -); diff --git a/examples/mps3-an536/src/bin/generic_timer_irq.rs b/examples/mps3-an536/src/bin/generic_timer_irq.rs index f870ddea..2f2f4566 100644 --- a/examples/mps3-an536/src/bin/generic_timer_irq.rs +++ b/examples/mps3-an536/src/bin/generic_timer_irq.rs @@ -16,9 +16,6 @@ use semihosting::println; fn main() -> ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - println!("Configure Timer Interrupt..."); board .gic diff --git a/examples/mps3-an536/src/bin/gic-map.rs b/examples/mps3-an536/src/bin/gic-map.rs index e45db841..f10edf92 100644 --- a/examples/mps3-an536/src/bin/gic-map.rs +++ b/examples/mps3-an536/src/bin/gic-map.rs @@ -29,9 +29,6 @@ static INTERRUPT_HANDLERS: critical_section::Mutex ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - // Configure two Software Generated Interrupts for Core 0 println!("Configure low-prio SGI..."); board diff --git a/examples/mps3-an536/src/bin/gic-priority-ceiling.rs b/examples/mps3-an536/src/bin/gic-priority-ceiling.rs index 30e14cc3..c3064254 100644 --- a/examples/mps3-an536/src/bin/gic-priority-ceiling.rs +++ b/examples/mps3-an536/src/bin/gic-priority-ceiling.rs @@ -26,9 +26,6 @@ const HIGH_PRIORITY: u8 = 0x10; fn main() -> ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - // Configure a Software Generated Interrupt for Core 0 println!("Configure low-prio SGI..."); board diff --git a/examples/mps3-an536/src/bin/gic-static-section-irq.rs b/examples/mps3-an536/src/bin/gic-static-section-irq.rs index 340bfc24..b3869885 100644 --- a/examples/mps3-an536/src/bin/gic-static-section-irq.rs +++ b/examples/mps3-an536/src/bin/gic-static-section-irq.rs @@ -23,9 +23,6 @@ const SGI_INTID_HI: IntId = IntId::sgi(4); fn main() -> ! { let mut board = mps3_an536::Board::new().unwrap(); - // Only interrupts with a higher priority (numerically lower) will be signalled. - GicCpuInterface::set_priority_mask(0x80); - // Configure two Software Generated Interrupts for Core 0 println!("Configure low-prio SGI..."); board diff --git a/examples/mps3-an536/src/lib.rs b/examples/mps3-an536/src/lib.rs index e3eb26b5..3f5ba82f 100644 --- a/examples/mps3-an536/src/lib.rs +++ b/examples/mps3-an536/src/lib.rs @@ -55,12 +55,18 @@ use core::sync::atomic::{AtomicBool, Ordering}; -/// The PPI for the virutal timer, according to the Cortex-R52 Technical Reference Manual, +/// The PPI for the virtual timer, according to the Cortex-R52 Technical Reference Manual, /// Table 10-3: PPI assignments. /// /// This corresponds to Interrupt ID 27. pub const VIRTUAL_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(11); +/// The PPI for the EL2 timer, according to the Cortex-R52 Technical Reference Manual, +/// Table 10-3: PPI assignments. +/// +/// This corresponds to Interrupt ID 26. +pub const HYP_TIMER_PPI: arm_gic::IntId = arm_gic::IntId::ppi(10); + #[cfg(not(arm_architecture = "v8-r"))] compile_error!("This example is only compatible to the ARMv8-R architecture"); diff --git a/justfile b/justfile index 9990aab9..e556bd90 100644 --- a/justfile +++ b/justfile @@ -103,10 +103,14 @@ build-versatileab-tier2 target: # Builds the MPS3-AN536 examples, building core from source build-mps3-tier3 target: cd examples/mps3-an536 && cargo build --target={{target}} -Zbuild-std=core {{verbose}} + cd examples/mps3-an536-smp && cargo build --target={{target}} -Zbuild-std=core {{verbose}} + cd examples/mps3-an536-el2 && cargo build --target={{target}} -Zbuild-std=core {{verbose}} # Builds the MPS3-AN536 examples, assuming core has been prebuilt build-mps3-tier2 target: cd examples/mps3-an536 && cargo build --target={{target}} {{verbose}} + cd examples/mps3-an536-smp && cargo build --target={{target}} {{verbose}} + cd examples/mps3-an536-el2 && cargo build --target={{target}} {{verbose}} # Formats all the code fmt: @@ -117,6 +121,8 @@ fmt: # The cross-compiled examples cargo fmt cd examples/versatileab && cargo fmt {{verbose}} cd examples/mps3-an536 && cargo fmt {{verbose}} + cd examples/mps3-an536-smp && cargo fmt {{verbose}} + cd examples/mps3-an536-el2 && cargo fmt {{verbose}} # Checks all the code is formatted fmt-check: @@ -127,6 +133,8 @@ fmt-check: # The cross-compiled examples cargo fmt cd examples/versatileab && cargo fmt --check {{verbose}} cd examples/mps3-an536 && cargo fmt --check {{verbose}} + cd examples/mps3-an536-smp && cargo fmt --check {{verbose}} + cd examples/mps3-an536-el2 && cargo fmt --check {{verbose}} # Checks all the cross-compiled workspace passes the clippy lints clippy-targets: \ @@ -144,6 +152,8 @@ clippy-target target: clippy-examples: cd examples/versatileab && cargo clippy --target=armv7r-none-eabi {{verbose}} cd examples/mps3-an536 && cargo clippy --target=armv8r-none-eabihf {{verbose}} + cd examples/mps3-an536-smp && cargo clippy --target=armv8r-none-eabihf {{verbose}} + cd examples/mps3-an536-el2 && cargo clippy --target=armv8r-none-eabihf {{verbose}} # Checks the host code passes the clippy lints clippy-host: @@ -224,3 +234,12 @@ test-qemu-v8r-smp: RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-smp armv8r-none-eabihf --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-smp thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 if [ "${FAIL}" == "1" ]; then exit 1; fi + +test-qemu-v8r-el2: + #!/bin/bash + FAIL=0 + ./tests.sh examples/mps3-an536-el2 armv8r-none-eabihf {{verbose}} --release || FAIL=1 + ./tests.sh examples/mps3-an536-el2 thumbv8r-none-eabihf -Zbuild-std=core {{verbose}} --release || FAIL=1 + RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-el2 armv8r-none-eabihf --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 + RUSTFLAGS=-Ctarget-cpu=cortex-r52 ./tests.sh examples/mps3-an536-el2 thumbv8r-none-eabihf -Zbuild-std=core --features=fpu-d32 --target-dir=target-d32 {{verbose}} --release || FAIL=1 + if [ "${FAIL}" == "1" ]; then exit 1; fi