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/aarch32-cpu/src/pmsav8.rs b/aarch32-cpu/src/pmsav8.rs index 0f82f65b..91be34ed 100644 --- a/aarch32-cpu/src/pmsav8.rs +++ b/aarch32-cpu/src/pmsav8.rs @@ -23,7 +23,7 @@ pub enum Error { /// Found an invalid MAIR selector (only 0..=7 is valid) InvalidMair(u8), /// Found a region with invalid alignment - UnalignedRegion(core::ops::RangeInclusive<*mut u8>), + UnalignedRegion(core::ops::RangeInclusive<*const u8>), } /// Represents our PMSAv8-32 EL1 MPU @@ -224,8 +224,8 @@ impl El2Mpu { register::Hprselr::write(register::Hprselr(idx as u32)); let hprbar = register::Hprbar::read(); let hprlar = register::Hprlar::read(); - let start_addr = (hprbar.base().value() << 6) as *mut u8; - let end_addr = ((hprlar.limit().value() << 6) | 0x3F) as *mut u8; + let start_addr = (hprbar.base().value() << 6) as *const u8; + let end_addr = ((hprlar.limit().value() << 6) | 0x3F) as *const u8; Some(El2Region { range: start_addr..=end_addr, shareability: hprbar.shareability(), @@ -385,7 +385,7 @@ pub struct El1Region { /// /// * The first address must be a multiple of 32. /// * The length must be a multiple of 32. - pub range: core::ops::RangeInclusive<*mut u8>, + pub range: core::ops::RangeInclusive<*const u8>, /// Shareability of the region pub shareability: El1Shareability, /// Access for the region @@ -430,7 +430,7 @@ pub struct El2Region { /// /// * The first address must be a multiple of 32. /// * The length must be a multiple of 32. - pub range: core::ops::RangeInclusive<*mut u8>, + pub range: core::ops::RangeInclusive<*const u8>, /// Shareability of the region pub shareability: El2Shareability, /// Access for the region diff --git a/aarch32-rt/link.x b/aarch32-rt/link.x index bff28f54..309e3bca 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,43 +48,90 @@ 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 */ + __svector = .; + *(.vector_table) + + . = ALIGN(_region_alignment); + + __evector = .; } > VECTORS - .text : { - /* Now the rest of the code */ + /* # Text + * + * Our executable code. + */ + .text : ALIGN(_region_alignment) { + __stext = .; + *(.text .text*) + + . = ALIGN(_region_alignment); + + __etext = .; } > CODE - .rodata : { + /* # Text + * + * Our constants. + */ + .rodata : ALIGN(_region_alignment) { + __srodata = .; + *(.rodata .rodata*) + + . = ALIGN(_region_alignment); + + __erodata = .; } > CODE - .data : ALIGN(4) { - . = ALIGN(4); + /* # Data + * + * Our global variables that are not initialised to zero. + */ + .data : ALIGN(_region_alignment) { + . = ALIGN(_region_alignment); __sdata = .; + *(.data .data.*); - . = ALIGN(4); + + . = ALIGN(_region_alignment); + /* 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 * output region or load region in those user sections! */ - . = ALIGN(4); + . = ALIGN(_region_alignment); __edata = .; /* LMA of .data */ __sidata = LOADADDR(.data); - .bss (NOLOAD) : ALIGN(4) { - . = ALIGN(4); + /* # Block Starting Symbol (BSS) + * + * Our global variables that *are* initialised to zero. + */ + .bss (NOLOAD) : ALIGN(_region_alignment) { + . = ALIGN(_region_alignment); __sbss = .; + *(.bss .bss* COMMON) - . = ALIGN(4); + + . = ALIGN(_region_alignment); + /* 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 +139,27 @@ SECTIONS { */ __ebss = .; - .uninit (NOLOAD) : ALIGN(4) + /* # Uninitialised Data + * + * Our global variables that have no defined initial value. + */ + .uninit (NOLOAD) : ALIGN(_region_alignment) { - . = ALIGN(4); + . = ALIGN(_region_alignment); __suninit = .; + *(.uninit .uninit.*); - . = ALIGN(4); + + . = ALIGN(_region_alignment); __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 +169,103 @@ SECTIONS { . = _start_stacks; } > STACKS - .stacks (NOLOAD) : ALIGN(8) + /* # Stacks + * + * Space for all seven stacks. + */ + .stacks (NOLOAD) : ALIGN(_stack_alignment) { - . = ALIGN(8); + . = ALIGN(_stack_alignment); + + /* 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 = .; + + . += _inter_stack_padding; + + /* Stack for SVC mode */ + _svc_stack_low_end = .; + . += (_svc_stack_size * _num_cores); + _svc_stack_high_end = .; + + . += _inter_stack_padding; + + /* Stack for ABT mode */ + _abt_stack_low_end = .; + . += (_abt_stack_size * _num_cores); + _abt_stack_high_end = .; + + . += _inter_stack_padding; + + /* Stack for HYP mode */ + _hyp_stack_low_end = .; + . += (_hyp_stack_size * _num_cores); + _hyp_stack_high_end = .; + + . += _inter_stack_padding; + + /* Stack for IRQ mode */ + _irq_stack_low_end = .; + . += (_irq_stack_size * _num_cores); + _irq_stack_high_end = .; + + . += _inter_stack_padding; + + /* Stack for FIQ mode */ + _fiq_stack_low_end = .; + . += (_fiq_stack_size * _num_cores); + _fiq_stack_high_end = .; + + . += _inter_stack_padding; + + /* 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); + +/* Default stack alignment. You can over-align if you want to set up MPU regions for the stacks */ +PROVIDE(_stack_alignment = 8); + +/* Default region alignment. You can over-align if you want to set up MPU regions for the stacks */ +PROVIDE(_region_alignment = 4); +/* Default to no padding between stacks. You might want padding if you want turn on the MPU and */ +/* only have a single core (so the stacks are otherwise contiguous) */ +PROVIDE(_inter_stack_padding = 0); + +/* 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 +274,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 +284,36 @@ 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 values are all reasonable */ +ASSERT(_region_alignment % 4 == 0, " +ERROR(aarch32-rt): Region alignment (_region_alignment) is not a multiple of 4 bytes"); +ASSERT(_region_alignment >= 4, " +ERROR(aarch32-rt): Region alignment (_region_alignment) is not at least eight bytes"); +ASSERT(_stack_alignment % 8 == 0, " +ERROR(aarch32-rt): Stack alignment (_stack_alignment) is not a multiple of 8 bytes"); +ASSERT(_stack_alignment >= 8, " +ERROR(aarch32-rt): Stack alignment (_stack_alignment) is not at least eight bytes"); +ASSERT(_inter_stack_padding % _stack_alignment == 0, " +ERROR(aarch32-rt): Inter-Stack padding (_inter_stack_padding) is not a multiple of of the stack alignment"); +ASSERT(_und_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): UND stack size (_und_stack_size) is not a multiple of the stack alignment"); +ASSERT(_svc_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): SVC stack size (_svc_stack_size) is not a multiple of the stack alignment"); +ASSERT(_abt_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): ABT stack size (_abt_stack_size) is not a multiple of the stack alignment"); +ASSERT(_hyp_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): HYP stack size (_hyp_stack_size) is not a multiple of the stack alignment"); +ASSERT(_irq_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): IRQ stack size (_irq_stack_size) is not a multiple of the stack alignment"); +ASSERT(_fiq_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): FIQ stack size (_fiq_stack_size) is not a multiple of the stack alignment"); +ASSERT(_sys_stack_size % _stack_alignment == 0, " +ERROR(aarch32-rt): SYS stack size (_sys_stack_size) is not a multiple of the stack alignment"); +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..82a2676c 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,9 @@ mod arch_v7; ))] mod arch_v4; +pub mod regions; +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 +798,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 +858,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 +867,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 +956,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 +1009,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 +1027,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/regions.rs b/aarch32-rt/src/regions.rs new file mode 100644 index 00000000..69a39331 --- /dev/null +++ b/aarch32-rt/src/regions.rs @@ -0,0 +1,130 @@ +//! Code to get the memory regions from the linker script +//! +//! This is useful if you want to set up the MPU + +/// Represents one of the memory regions in the linker script +#[derive(Debug, Copy, Clone)] +pub enum Region { + /// The .vector_table section + /// + /// Contains the reset and exception vectors + VectorTable, + /// The .text section + /// + /// Contains the executable code + Text, + /// The .rodata section + /// + /// Contains read-only static data + Rodata, + /// The .bss section + /// + /// Contains zero-initialised static data + Bss, + /// The .data section + /// + /// Contains non-zero-initialised static data + Data, + /// The .uninit section + /// + /// Contains non-initialised static data + Uninit, +} + +impl core::fmt::Display for Region { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.pad(match self { + Region::VectorTable => ".vector_table", + Region::Text => ".text", + Region::Rodata => ".rodata", + Region::Bss => ".bss", + Region::Data => ".data", + Region::Uninit => ".uninit", + }) + } +} + +impl Region { + /// Create an iterator over all the regions + pub fn iter() -> impl Iterator { + [ + Region::VectorTable, + Region::Text, + Region::Rodata, + Region::Bss, + Region::Data, + Region::Uninit, + ] + .iter() + .cloned() + } + + /// Get the highest address of this region + /// + /// Technically it gets *one past the end* of the region. + pub fn top(&self) -> *const u8 { + use core::ptr::addr_of; + unsafe extern "C" { + static __evector: u8; + static __etext: u8; + static __erodata: u8; + static __ebss: u8; + static __edata: u8; + static __euninit: u8; + } + match self { + Region::VectorTable => addr_of!(__evector), + Region::Text => addr_of!(__etext), + Region::Rodata => addr_of!(__erodata), + Region::Bss => addr_of!(__ebss), + Region::Data => addr_of!(__edata), + Region::Uninit => addr_of!(__euninit), + } + } + + /// Get the lowest address of this region + pub fn bottom(&self) -> *const u8 { + use core::ptr::addr_of; + unsafe extern "C" { + static __svector: u8; + static __stext: u8; + static __srodata: u8; + static __sbss: u8; + static __sdata: u8; + static __suninit: u8; + } + match self { + Region::VectorTable => addr_of!(__svector), + Region::Text => addr_of!(__stext), + Region::Rodata => addr_of!(__srodata), + Region::Bss => addr_of!(__sbss), + Region::Data => addr_of!(__sdata), + Region::Uninit => addr_of!(__suninit), + } + } + + /// Get the range of this region. + pub fn range(&self) -> Option> { + let bottom = self.bottom(); + let top = self.top(); + if bottom != top { + Some(bottom..top) + } else { + None + } + } + + /// Get the inclusive range of this region. + /// + /// This is the range you need to give to the PMSAv8 MPU code. + pub fn mpu_range(&self) -> Option> { + let bottom = self.bottom(); + let top = self.top(); + let top_under = unsafe { top.offset(-1) }; + if bottom != top { + Some(bottom..=top_under) + } else { + None + } + } +} diff --git a/aarch32-rt/src/stacks.rs b/aarch32-rt/src/stacks.rs new file mode 100644 index 00000000..a2e37bd5 --- /dev/null +++ b/aarch32-rt/src/stacks.rs @@ -0,0 +1,146 @@ +//! 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 { + f.pad(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 the inclusive range of this stack, for the given core + /// + /// This is the range you need to give to the PMSAv8 MPU code. + pub fn mpu_range(&self, core: usize) -> Option> { + if let (Some(bottom), Some(top)) = (self.bottom(core), self.top(core)) { + let top = top as *const u8; + let bottom = bottom as *const u8; + let top_under = unsafe { top.offset(-1) }; + Some(bottom..=top_under) + } 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/.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..c4aa51bb --- /dev/null +++ b/examples/mps3-an536-smp/memory.x @@ -0,0 +1,19 @@ +/* +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); + +PROVIDE(_num_cores = 2); +PROVIDE(kmain2 = default_kmain2); 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/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/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); +} 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..e3a798e2 --- /dev/null +++ b/examples/mps3-an536-smp/src/lib.rs @@ -0,0 +1,306 @@ +//! 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, 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: +/// 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 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 + ); + } + } + } +} + +/// 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 + movs r0, #1 + bl _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() + }, +); + +/// 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/memory.x b/examples/mps3-an536/memory.x index 00158077..78257ebb 100644 --- a/examples/mps3-an536/memory.x +++ b/examples/mps3-an536/memory.x @@ -32,8 +32,7 @@ SECTIONS { /* We put this in the header */ __irq_entries_end = .; } > CODE -} INSERT AFTER .text; - +} INSERT AFTER .rodata; PROVIDE(_hyp_stack_size = 16K); PROVIDE(_und_stack_size = 16K); @@ -42,3 +41,6 @@ PROVIDE(_abt_stack_size = 16K); PROVIDE(_irq_stack_size = 64); PROVIDE(_fiq_stack_size = 64); PROVIDE(_sys_stack_size = 16K); +PROVIDE(_stack_alignment = 64); +PROVIDE(_inter_stack_padding = 64); +PROVIDE(_region_alignment = 64K); diff --git a/examples/mps3-an536/reference/mpu_setup-armv8r-none-eabihf.out b/examples/mps3-an536/reference/mpu_setup-armv8r-none-eabihf.out new file mode 100644 index 00000000..7c873eaf --- /dev/null +++ b/examples/mps3-an536/reference/mpu_setup-armv8r-none-eabihf.out @@ -0,0 +1,27 @@ + UND @ 0x1006be00..=0x1006fdff + SVC @ 0x1006fe40..=0x10073e3f + ABT @ 0x10073e80..=0x10077e7f + HYP @ 0x10077ec0..=0x1007bebf + IRQ @ 0x1007bf00..=0x1007bf3f + FIQ @ 0x1007bf80..=0x1007bfbf + SYS @ 0x1007c000..=0x1007ffff +.vector_table @ 0x08000000..=0x0800ffff + .text @ 0x08010000..=0x0801ffff + .rodata @ 0x08020000..=0x0802ffff + .data @ 0x10000000..=0x0fffffff + .bss @ 0x10000000..=0x1000ffff + .uninit @ 0x10010000..=0x1000ffff +Region 00: El1Region { range: 0x1006be00..=0x1006fdff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 01: El1Region { range: 0x1006fe40..=0x10073e3f, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 02: El1Region { range: 0x10073e80..=0x10077e7f, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 03: El1Region { range: 0x10077ec0..=0x1007bebf, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 04: El1Region { range: 0x1007bf00..=0x1007bf3f, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 05: El1Region { range: 0x1007bf80..=0x1007bfbf, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 06: El1Region { range: 0x1007c000..=0x1007ffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 07: El1Region { range: 0x08000000..=0x0800ffff, shareability: NonShareable, access: ReadOnly, no_exec: false , mair: 0000000000, enable: true } +Region 08: El1Region { range: 0x08010000..=0x0801ffff, shareability: NonShareable, access: ReadOnly, no_exec: false , mair: 0000000000, enable: true } +Region 09: El1Region { range: 0x08020000..=0x0802ffff, shareability: NonShareable, access: ReadOnly, no_exec: true , mair: 0000000000, enable: true } +Region 10: El1Region { range: 0x10000000..=0x0fffffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 11: El1Region { range: 0x10000000..=0x1000ffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 12: El1Region { range: 0x10010000..=0x1000ffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 13: El1Region { range: 0xe0000000..=0xffffffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000002, enable: true } diff --git a/examples/mps3-an536/reference/mpu_setup-thumbv8r-none-eabihf.out b/examples/mps3-an536/reference/mpu_setup-thumbv8r-none-eabihf.out new file mode 100644 index 00000000..7c873eaf --- /dev/null +++ b/examples/mps3-an536/reference/mpu_setup-thumbv8r-none-eabihf.out @@ -0,0 +1,27 @@ + UND @ 0x1006be00..=0x1006fdff + SVC @ 0x1006fe40..=0x10073e3f + ABT @ 0x10073e80..=0x10077e7f + HYP @ 0x10077ec0..=0x1007bebf + IRQ @ 0x1007bf00..=0x1007bf3f + FIQ @ 0x1007bf80..=0x1007bfbf + SYS @ 0x1007c000..=0x1007ffff +.vector_table @ 0x08000000..=0x0800ffff + .text @ 0x08010000..=0x0801ffff + .rodata @ 0x08020000..=0x0802ffff + .data @ 0x10000000..=0x0fffffff + .bss @ 0x10000000..=0x1000ffff + .uninit @ 0x10010000..=0x1000ffff +Region 00: El1Region { range: 0x1006be00..=0x1006fdff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 01: El1Region { range: 0x1006fe40..=0x10073e3f, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 02: El1Region { range: 0x10073e80..=0x10077e7f, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 03: El1Region { range: 0x10077ec0..=0x1007bebf, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 04: El1Region { range: 0x1007bf00..=0x1007bf3f, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 05: El1Region { range: 0x1007bf80..=0x1007bfbf, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 06: El1Region { range: 0x1007c000..=0x1007ffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 07: El1Region { range: 0x08000000..=0x0800ffff, shareability: NonShareable, access: ReadOnly, no_exec: false , mair: 0000000000, enable: true } +Region 08: El1Region { range: 0x08010000..=0x0801ffff, shareability: NonShareable, access: ReadOnly, no_exec: false , mair: 0000000000, enable: true } +Region 09: El1Region { range: 0x08020000..=0x0802ffff, shareability: NonShareable, access: ReadOnly, no_exec: true , mair: 0000000000, enable: true } +Region 10: El1Region { range: 0x10000000..=0x0fffffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 11: El1Region { range: 0x10000000..=0x1000ffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 12: El1Region { range: 0x10010000..=0x1000ffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000001, enable: true } +Region 13: El1Region { range: 0xe0000000..=0xffffffff, shareability: NonShareable, access: ReadWrite, no_exec: true , mair: 0000000002, enable: true } 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/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/bin/hvc-a32.rs b/examples/mps3-an536/src/bin/hvc-a32.rs index 2e48231b..7dad76a4 100644 --- a/examples/mps3-an536/src/bin/hvc-a32.rs +++ b/examples/mps3-an536/src/bin/hvc-a32.rs @@ -27,7 +27,7 @@ fn main() -> ! { 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?}, {:08x?}", hsr, frame); - return 0x12345678; + 0x12345678 } #[instruction_set(arm::a32)] diff --git a/examples/mps3-an536/src/bin/hvc-t32.rs b/examples/mps3-an536/src/bin/hvc-t32.rs index 4d0ce587..631bde18 100644 --- a/examples/mps3-an536/src/bin/hvc-t32.rs +++ b/examples/mps3-an536/src/bin/hvc-t32.rs @@ -27,7 +27,7 @@ fn main() -> ! { 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?}, {:08x?}", hsr, frame); - return 0x12345678; + 0x12345678 } #[instruction_set(arm::t32)] diff --git a/examples/mps3-an536/src/bin/mpu_setup.rs b/examples/mps3-an536/src/bin/mpu_setup.rs new file mode 100644 index 00000000..eaadf8f8 --- /dev/null +++ b/examples/mps3-an536/src/bin/mpu_setup.rs @@ -0,0 +1,206 @@ +//! Stack-overflow check for Arm Cortex-R +//! +//! Uses the MPU to protect each stack + +#![no_std] +#![no_main] + +use aarch32_cpu::pmsav8::{ + Cacheable, El1AccessPerms, El1Mpu, El1Region, El1Shareability, MemAttr, RwAllocPolicy, +}; +use aarch32_rt::{entry, regions::Region, stacks::Stack}; +use semihosting::println; + +use mps3_an536 as _; + +unsafe extern "C" { + static __stext: u8; + static __etext: u8; + static __srodata: u8; + static __erodata: u8; + static __sdata: u8; + static __edata: u8; + static __sbss: u8; + static __ebss: u8; +} + +const MAIR_READ_ONLY: u8 = 0; +const MAIR_READ_WRITE: u8 = 1; +const MAIR_DEVICE: u8 = 2; + +static MEM_ATTRS: [MemAttr; 8] = [ + // Read-only Code RAM + MemAttr::NormalMemory { + outer: Cacheable::WriteThroughNonTransient(RwAllocPolicy::R), + inner: Cacheable::WriteThroughNonTransient(RwAllocPolicy::R), + }, + // Read-write RAM + MemAttr::NormalMemory { + outer: Cacheable::WriteBackNonTransient(RwAllocPolicy::W), + inner: Cacheable::WriteBackNonTransient(RwAllocPolicy::W), + }, + // Device Memory + MemAttr::DeviceMemory, + // Spare entries + MemAttr::DeviceMemory, + MemAttr::DeviceMemory, + MemAttr::DeviceMemory, + MemAttr::DeviceMemory, + MemAttr::DeviceMemory, +]; + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up code in `aarch32-rt`. +#[entry] +fn main() -> ! { + let mut mpu = unsafe { El1Mpu::new() }; + + mpu.set_attributes(&MEM_ATTRS); + + let mut last = 0; + for (idx, stack) in Stack::iter().enumerate() { + let range = stack.mpu_range(0).unwrap(); + println!("{:>13} @ {:010x?}", stack, range); + + let region = El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadWrite, + no_exec: true, + mair: MAIR_READ_WRITE, + enable: true, + }; + mpu.set_region(idx as u8, ®ion).unwrap(); + last = idx as u8; + } + + let mut next = last + 1; + + if let Some(range) = Region::VectorTable.mpu_range() { + println!("{:>13} @ {:010x?}", Region::VectorTable, range); + mpu.set_region( + next, + &El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadOnly, + no_exec: false, + mair: MAIR_READ_ONLY, + enable: true, + }, + ) + .unwrap(); + next += 1; + } + + if let Some(range) = Region::Text.mpu_range() { + println!("{:>13} @ {:010x?}", Region::Text, range); + mpu.set_region( + next, + &El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadOnly, + no_exec: false, + mair: MAIR_READ_ONLY, + enable: true, + }, + ) + .unwrap(); + next += 1; + } + + if let Some(range) = Region::Rodata.mpu_range() { + println!("{:>13} @ {:010x?}", Region::Rodata, range); + mpu.set_region( + next, + &El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadOnly, + no_exec: true, + mair: MAIR_READ_ONLY, + enable: true, + }, + ) + .unwrap(); + next += 1; + } + + if let Some(range) = Region::Data.mpu_range() { + println!("{:>13} @ {:010x?}", Region::Data, range); + mpu.set_region( + next, + &El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadWrite, + no_exec: true, + mair: MAIR_READ_WRITE, + enable: true, + }, + ) + .unwrap(); + next += 1; + } + + if let Some(range) = Region::Bss.mpu_range() { + println!("{:>13} @ {:010x?}", Region::Bss, range); + mpu.set_region( + next, + &El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadWrite, + no_exec: true, + mair: MAIR_READ_WRITE, + enable: true, + }, + ) + .unwrap(); + next += 1; + } + + if let Some(range) = Region::Uninit.mpu_range() { + println!("{:>13} @ {:010x?}", Region::Uninit, range); + mpu.set_region( + next, + &El1Region { + range, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadWrite, + no_exec: true, + mair: MAIR_READ_WRITE, + enable: true, + }, + ) + .unwrap(); + next += 1; + } + + mpu.set_region( + next, + &El1Region { + range: 0xE000_0000 as *const u8..=0xFFFF_FFFF as *const u8, + shareability: El1Shareability::NonShareable, + access: El1AccessPerms::ReadWrite, + no_exec: true, + mair: MAIR_DEVICE, + enable: true, + }, + ) + .unwrap(); + + mpu.enable(); + + for idx in 0..mpu.num_regions() { + if let Some(region) = mpu.get_region(idx) + && region.enable + { + println!("Region {:02}: {:010x?}", idx, region); + } + } + + mps3_an536::exit(0); +} 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/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/.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/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 + ); + } } } } 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