diff --git a/cortex-a-rt/link.x b/cortex-a-rt/link.x index 0ba73fc..8ee325f 100644 --- a/cortex-a-rt/link.x +++ b/cortex-a-rt/link.x @@ -17,8 +17,6 @@ SECTIONS { .text : { /* The vector table must come first */ *(.vector_table) - /* Our exception handling routines */ - *(.text.handlers) /* Now the rest of the code */ *(.text .text*) } > CODE @@ -72,36 +70,38 @@ SECTIONS { } /* -We reserve some space at the top of the RAM for our stacks. We have an IRQ stack -and a FIQ stack, plus the remainder is our system stack. +We reserve some space at the top of the RAM for our exception stacks. The +remainder is our system mode stack. You must keep _stack_top and the stack sizes aligned to eight byte boundaries. */ PROVIDE(_stack_top = ORIGIN(DATA) + LENGTH(DATA)); -PROVIDE(_fiq_stack_size = 0x400); -PROVIDE(_irq_stack_size = 0x1000); -PROVIDE(_abt_stack_size = 0x400); PROVIDE(_und_stack_size = 0x400); -PROVIDE(_svc_stack_size = 0x1000); +PROVIDE(_svc_stack_size = 0x400); +PROVIDE(_abt_stack_size = 0x400); +PROVIDE(_irq_stack_size = 0x400); +PROVIDE(_fiq_stack_size = 0x400); -ASSERT(_stack_top % 8 == 0, "ERROR(cortex-a-rt): top of stack is not 8-byte aligned"); -ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of FIQ stack is not 8-byte aligned"); -ASSERT(_irq_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of IRQ stack is not 8-byte aligned"); -ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of FIQ stack is not 8-byte aligned"); -ASSERT(_abt_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of ABT stack is not 8-byte aligned"); -ASSERT(_und_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of UND stack is not 8-byte aligned"); -ASSERT(_svc_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of SVC stack is not 8-byte aligned"); +ASSERT(_stack_top % 8 == 0, "ERROR(cortex-r-rt): top of stack is not 8-byte aligned"); +ASSERT(_und_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of UND stack is not 8-byte aligned"); +ASSERT(_svc_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of SVC stack is not 8-byte aligned"); +ASSERT(_abt_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of ABT stack is not 8-byte aligned"); +ASSERT(_irq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of IRQ stack is not 8-byte aligned"); +ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of FIQ stack is not 8-byte aligned"); /* Weak aliases for ASM default handlers */ +PROVIDE(_start =_default_start); PROVIDE(_asm_undefined_handler =_asm_default_undefined_handler); +PROVIDE(_asm_svc_handler =_asm_default_svc_handler); PROVIDE(_asm_prefetch_handler =_asm_default_prefetch_handler); PROVIDE(_asm_abort_handler =_asm_default_abort_handler); +PROVIDE(_asm_irq_handler =_asm_default_irq_handler); PROVIDE(_asm_fiq_handler =_asm_default_fiq_handler); /* Weak aliases for C default handlers */ PROVIDE(_undefined_handler =_default_handler); -PROVIDE(_abort_handler =_default_handler); +PROVIDE(_svc_handler =_default_handler); PROVIDE(_prefetch_handler =_default_handler); +PROVIDE(_abort_handler =_default_handler); PROVIDE(_irq_handler =_default_handler); -PROVIDE(_svc_handler =_default_handler); -PROVIDE(_start =_default_start); +/* There is no default C-language FIQ handler */ diff --git a/cortex-a-rt/src/lib.rs b/cortex-a-rt/src/lib.rs index a425435..a59b3bd 100644 --- a/cortex-a-rt/src/lib.rs +++ b/cortex-a-rt/src/lib.rs @@ -1,22 +1,25 @@ -//! # Run-time support for Arm Cortex-A +//! # Run-time support for Arm Cortex-A (AArch32) //! //! This library implements a simple Arm vector table, suitable for getting into -//! a Rust application running in System Mode. It also provides a reference start up method. -//! Usually, most Cortex-A based systems will require chip specific start-up code, so the -//! start-up method can over overriden. -//! -//! The default startup routine provided by this crate does not include any special handling -//! for multi-core support because this is oftentimes implementation defined and the exact -//! handling depends on the specific chip in use. Many implementations only -//! run the startup routine with one core and will keep other cores in reset until they are woken -//! up by an implementation specific mechanism. For other implementations where multi-core -//! specific startup adaptions are necessary, the startup routine can be overwritten by the user. +//! a Rust application running in System Mode. It also provides a reference +//! start up method. Most Cortex-A based systems will require chip specific +//! start-up code, so the start-up method can over overriden. +//! +//! The default startup routine provided by this crate does not include any +//! special handling for multi-core support because this is oftentimes +//! implementation defined and the exact handling depends on the specific chip +//! in use. Many implementations only run the startup routine with one core and +//! will keep other cores in reset until they are woken up by an implementation +//! specific mechanism. For other implementations where multi-core specific +//! startup adaptions are necessary, the startup routine can be overwritten by +//! the user. //! //! ## Features //! -//! - `vfp-dp`: Enables support for the double-precision VFP floating point support. If your target -//! CPU has this feature or support for NEON which also implies double-precision support, this -//! feature should be activated. +//! - `vfp-dp`: Enables support for the double-precision VFP floating point +//! support. If your target CPU has this feature or support for NEON which +//! also implies double-precision support, this feature should be activated. +//! - `eabi-fpu`: Enables the FPU, even if you selected a soft-float ABI target. //! //! ## Information about the Run-Time //! @@ -49,7 +52,11 @@ //! * `__sidata` - the start of the initialisation values for data, in read-only //! memory. Must be 4-byte aligned. //! -//! ### Functions +//! Using our default start-up function `_default_start`, the memory between +//! `__sbss` and `__ebss` is zeroed, and the memory between `__sdata` and +//! `__edata` is initialised with the data found at `__sidata`. +//! +//! ### C-Compatible Functions //! //! * `kmain` - the `extern "C"` entry point to your application. //! @@ -62,7 +69,9 @@ //! //! * `_svc_handler` - an `extern "C"` function to call when an SVC Exception //! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. +//! `_default_handler` but you can override it. Returning from this function +//! will cause execution to resume from the function the triggered the +//! exception, immediately after the SVC instruction. //! //! Expected prototype: //! @@ -73,108 +82,155 @@ //! //! * `_irq_handler` - an `extern "C"` function to call when an Interrupt //! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. +//! `_default_handler` but you can override it. Returning from this function +//! will cause execution to resume from the function the triggered the +//! exception. //! //! Expected prototype: //! //! ```rust +//! /// Upon return, the interrupt handler will end and execution +//! /// will continue at the interrupted instruction. //! #[unsafe(no_mangle)] //! extern "C" fn _irq_handler(); //! ``` //! -//! * `_undefined_handler` - an `extern "C"` function to call when an Undefined Exception -//! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. It will be called by the -//! `_asm_default_undefined_handler` unless that function is overriden as well. +//! * `_undefined_handler` - an `extern "C"` function to call when an Undefined +//! Exception occurs. Our linker script PROVIDEs a default implementation at +//! `_default_handler` which is used if `_undefined_handler` is missing. //! -//! Expected prototype: +//! The expected prototype for `_undefined_handler` is either: //! //! ```rust +//! /// Does not return //! #[unsafe(no_mangle)] -//! extern "C" fn _undefined_handler(faulting_instruction: u32); +//! extern "C" fn _undefined_handler(addr: usize) -> !; //! ``` +//! +//! or: //! -//! * `_abort_handler` - an `extern "C"` function to call when a Data Abort Exception -//! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. It will be called by the -//! `_asm_default_abort_handler` unless that function is overriden as well. +//! ```rust +//! /// Execution will continue from the returned address. +//! /// +//! /// Return `addr` to go back and execute the faulting instruction again. +//! #[unsafe(no_mangle)] +//! extern "C" fn _undefined_handler(addr: usize) -> usize; +//! ``` //! -//! Expected prototype: +//! * `_abort_handler` - an `extern "C"` function to call when an Data Abort +//! occurs. Our linker script PROVIDEs a default implementation at +//! `_default_handler` which is used if `_abort_handler` is missing. +//! +//! The expected prototype for `_abort_handler` is either: //! //! ```rust +//! /// Does not return //! #[unsafe(no_mangle)] -//! extern "C" fn _abort_handler(faulting_instruction: u32); +//! extern "C" fn _abort_handler(addr: usize) -> !; //! ``` +//! +//! or: //! -//! * `_prefetch_handler` - an `extern "C"` function to call when a Prefetch Abort Exception -//! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. It will be called by the -//! `_asm_default_prefetch_handler` unless that function is overriden as well. +//! ```rust +//! /// Execution will continue from the returned address. +//! /// +//! /// Return `addr` to go back and execute the faulting instruction again. +//! #[unsafe(no_mangle)] +//! extern "C" fn _abort_handler(addr: usize) -> usize; +//! ``` //! -//! Expected prototype: +//! * `_prefetch_handler` - an `extern "C"` function to call when an Prefetch +//! Abort occurs. Our linker script PROVIDEs a default implementation at +//! `_default_handler` which is used if `_prefetch_handler` is missing. +//! +//! The expected prototype for `_prefetch_handler` is either: //! //! ```rust +//! /// Does not return //! #[unsafe(no_mangle)] -//! extern "C" fn _prefetch_handler(faulting_instruction: u32); +//! extern "C" fn _prefetch_handler(addr: usize) -> !; +//! ``` +//! +//! or: +//! +//! ```rust +//! /// Execution will continue from the returned address. +//! /// +//! /// Return `addr` to go back and execute the faulting instruction again. +//! #[unsafe(no_mangle)] +//! extern "C" fn _prefetch_handler(addr: usize) -> usize; //! ``` //! //! ### ASM functions //! -//! * `__start` - a Reset handler. Our linker script PROVIDEs a default function -//! at `_default_start` but you can override it. Most Cortex-A SoCs require -//! a chip specific startup for tasks like MMU initialization or chip specific -//! initialization routines. -//! * `_asm_fiq_handler` - a naked function to call when a Fast Interrupt -//! Request (FIQ) occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_fiq_handler` but you can override it. +//! * `_start` - a Reset handler. Our linker script PROVIDEs a default function +//! at `_default_start` but you can override it. Some SoCs require a chip +//! specific startup for tasks like MMU initialization or chip specific +//! initialization routines, so if our start-up routine doesn't work for you, +//! supply your own `_start` function (but feel free to call our +//! `_default_start` as part of it). //! * `_asm_undefined_handler` - a naked function to call when an Undefined //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_undefined_handler` but you can override it. -//! * `_asm_prefetch_handler` - a naked function to call when an Prefetch +//! `_asm_default_undefined_handler` but you can override it. The provided +//! default handler will call `_undefined_handler`, saving state as required. +//! * `_asm_svc_handler` - a naked function to call when an SVC Exception +//! occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_svc_handler` but you can override it. The provided default +//! handler will call `_svc_handler`, saving state as required. +//! * `_asm_prefetch_handler` - a naked function to call when a Prefetch //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_prefetch_handler` but you can override it. The provided default -//! handler will perform an exception return to the faulting address. +//! `_asm_default_prefetch_handler` but you can override it. The provided +//! default handler will call `_prefetch_handler`, saving state as required. +//! Note that Prefetch Exceptions are handled in Abort Mode, Monitor Mode or +//! Hyp Mode, depending on CPU configuration. There is no Prefetch Abort mode, +//! so there is no Prefetch Abort Mode stack. //! * `_asm_abort_handler` - a naked function to call when an Abort Exception //! occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_abort_handler` but you can override it. The provided default handler -//! will perform an exception return to the faulting address. +//! `_asm_default_abort_handler` but you can override it. The provided default +//! handler will call `_abort_handler`, saving state as required. +//! * `_asm_irq_handler` - a naked function to call when an Undefined Exception +//! occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_irq_handler` but you can override it. The provided default +//! handler will call `_irq_handler`, saving state as required. +//! * `_asm_fiq_handler` - a naked function to call when a Fast Interrupt +//! Request (FIQ) occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_fiq_handler` but you can override it. The provided default +//! just spins forever. //! -//! On start-up, the memory between `__sbss` and `__ebss` is zeroed, and the -//! memory between `__sdata` and `__edata` is initialised with the data found at -//! `__sidata`. +//! ## Outputs //! //! This library produces global symbols called: //! //! * `_vector_table` - the start of the interrupt vector table //! * `_default_start` - the default Reset handler, that sets up some stacks and //! calls an `extern "C"` function called `kmain`. +//! * `_asm_default_undefined_handler` - assembly language trampoline that calls +//! `_undefined_handler` +//! * `_asm_default_svc_handler` - assembly language trampoline that calls +//! `_svc_handler` +//! * `_asm_default_prefetch_handler` - assembly language trampoline that calls +//! `_prefetch_handler` +//! * `_asm_default_abort_handler` - assembly language trampoline that calls +//! `_abort_handler` +//! * `_asm_default_irq_handler` - assembly language trampoline that calls +//! `_irq_handler` //! * `_asm_default_fiq_handler` - an FIQ handler that just spins -//! * `_asm_default_handler` - an exception handler that just spins -//! * `_asm_svc_handler` - assembly language trampoline for SVC Exceptions that -//! calls `_svc_handler` -//! * `_asm_irq_handler` - assembly language trampoline for Interrupts that -//! calls `_irq_handler` -//! -//! The assembly language trampolines are required because Armv7-R (and Armv8-R) -//! 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. We -//! supply a default handler that prints an error message to Semihosting so you -//! know if you hit an unexpected exception. Because FIQ is often +//! * `_default_handler` - a C compatible function that spins forever. +//! +//! The assembly language trampolines are required because Armv7-A 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. //! -//! If our start-up routine doesn't work for you (e.g. if you have to initialise -//! your memory controller before you touch RAM), supply your own `_start` -//! function (but feel free to call our `_default_start` as part of it). -//! //! ## Examples //! -//! You can find example code using QEMU inside the -//! [project repository](https://github.com/rust-embedded/cortex-ar/tree/main/examples) +//! You can find example code using QEMU inside the [project +//! repository](https://github.com/rust-embedded/cortex-ar/tree/main/examples) #![no_std] @@ -197,8 +253,7 @@ pub extern "C" fn _default_handler() { // The Interrupt Vector Table, and some default assembly-language handler. core::arch::global_asm!( r#" - .section .vector_table - .align 0 + .section .vector_table,"ax",%progbits .global _vector_table .type _vector_table, %function @@ -212,20 +267,6 @@ core::arch::global_asm!( ldr pc, =_asm_irq_handler ldr pc, =_asm_fiq_handler .size _vector_table, . - _vector_table - - .section .text.handlers - - .global _asm_default_fiq_handler - .type _asm_default_fiq_handler, %function - _asm_default_fiq_handler: - b _asm_default_fiq_handler - .size _asm_default_fiq_handler, . - _asm_default_fiq_handler - - .global _asm_default_handler - .type _asm_default_handler, %function - _asm_default_handler: - b _asm_default_handler - .size _asm_default_handler, . - _asm_default_handler "# ); @@ -376,15 +417,65 @@ macro_rules! restore_context { // Our assembly language exception handlers core::arch::global_asm!( r#" - .section .text.handlers - .align 0 + .section .text._asm_default_undefined_handler + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _undefined_handler(addr: usize) -> usize;` + // or + // `extern "C" fn _undefined_handler(addr: usize) -> !;` + .global _asm_default_undefined_handler + .type _asm_default_undefined_handler, %function + _asm_default_undefined_handler: + // state save from compiled code + srsfd sp!, {und_mode} + // to work out what mode we're in, we need R0 + push {{r0}} + // First adjust LR for two purposes: Passing the faulting instruction to the C handler, + // and to return to the failing instruction after the C handler returns. + // Load processor status for the calling code + mrs r0, spsr + // Was the code that triggered the exception in Thumb state? + tst r0, {t_bit} + // Subtract 2 in Thumb Mode, 4 in Arm Mode - see p.1206 of the ARMv7-A architecture manual. + ite eq + subeq lr, lr, #4 + subne lr, lr, #2 + // save the newly computed LR + push {{lr}} + // now do our standard exception save + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _undefined_handler + // if we get back here, assume they returned a new LR in r0 + mov lr, r0 + // do our standard restore + "#, + restore_context!(), + r#" + // get our saved LR + pop {{lr}} + // get our real saved R0 + pop {{r0}} + // overwrite the saved LR with the adjusted one + str lr, [sp] + // Return to the failing instruction which is the recommended approach by ARM. + rfefd sp! + .size _asm_default_undefined_handler, . - _asm_default_undefined_handler + + + .section .text._asm_default_svc_handler // Called from the vector table when we have an software interrupt. // Saves state and calls a C-compatible handler like - // `extern "C" fn svc_handler(svc: u32, context: *const u32);` - .global _asm_svc_handler - .type _asm_svc_handler, %function - _asm_svc_handler: + // `extern "C" fn svc_handler(svc: u32);` + .global _asm_default_svc_handler + .type _asm_default_svc_handler, %function + _asm_default_svc_handler: srsfd sp!, {svc_mode} "#, save_context!(), @@ -401,69 +492,43 @@ core::arch::global_asm!( restore_context!(), r#" rfefd sp! - .size _asm_svc_handler, . - _asm_svc_handler + .size _asm_default_svc_handler, . - _asm_default_svc_handler - // Called from the vector table when we have an interrupt. - // Saves state and calls a C-compatible handler like - // `extern "C" fn irq_handler();` - .global _asm_irq_handler - .type _asm_irq_handler, %function - _asm_irq_handler: - sub lr, lr, 4 - srsfd sp!, {irq_mode} - "#, - save_context!(), - r#" - // call C handler - bl _irq_handler - "#, - restore_context!(), - r#" - rfefd sp! - .size _asm_irq_handler, . - _asm_irq_handler - + .section .text._asm_default_abort_handler // Called from the vector table when we have an undefined exception. // Saves state and calls a C-compatible handler like - // `extern "C" fn _undefined_handler();` - .global _asm_default_undefined_handler - .type _asm_default_undefined_handler, %function - _asm_default_undefined_handler: - // First adjust LR for two purposes: Passing the faulting instruction to the C handler, - // and to return to the failing instruction after the C handler returns. - // Load processor status - mrs r4, cpsr - // Occurred in Thumb state? - tst r4, {t_bit} - // If not in Thumb mode, branch to not_thumb - beq not_thumb - subs lr, lr, #2 - b done -not_thumb: - // Subtract 4 from LR (ARM mode) - subs lr, lr, #4 -done: + // `extern "C" fn _abort_handler(addr: usize);` + .global _asm_default_abort_handler + .type _asm_default_abort_handler, %function + _asm_default_abort_handler: + // Subtract 8 from the stored LR, see p.1214 of the ARMv7-A architecture manual. + subs lr, lr, #8 // state save from compiled code - srsfd sp!, {und_mode} + srsfd sp!, {abt_mode} "#, save_context!(), r#" // Pass the faulting instruction address to the handler. - mov r0, lr + mov r0, lr // call C handler - bl _undefined_handler + bl _abort_handler + // if we get back here, assume they returned a new LR in r0 + mov lr, r0 "#, restore_context!(), r#" // Return to the failing instruction which is the recommended approach by ARM. rfefd sp! - .size _asm_default_undefined_handler, . - _asm_default_undefined_handler + .size _asm_default_abort_handler, . - _asm_default_abort_handler - // Called from the vector table when we have an undefined exception. + .section .text._asm_default_prefetch_handler + + // Called from the vector table when we have a prefetch exception. // Saves state and calls a C-compatible handler like - // `extern "C" fn _prefetch_handler();` + // `extern "C" fn _prefetch_handler(addr: usize);` .global _asm_default_prefetch_handler .type _asm_default_prefetch_handler, %function _asm_default_prefetch_handler: @@ -478,6 +543,8 @@ done: mov r0, lr // call C handler bl _prefetch_handler + // if we get back here, assume they returned a new LR in r0 + mov lr, r0 "#, restore_context!(), r#" @@ -486,29 +553,36 @@ done: .size _asm_default_prefetch_handler, . - _asm_default_prefetch_handler - // Called from the vector table when we have an undefined exception. + .section .text._asm_default_irq_handler + + // Called from the vector table when we have an interrupt. // Saves state and calls a C-compatible handler like - // `extern "C" fn _abort_handler();` - .global _asm_default_abort_handler - .type _asm_default_abort_handler, %function - _asm_default_abort_handler: - // Subtract 8 from the stored LR, see p.1214 of the ARMv7-A architecture manual. - subs lr, lr, #8 - // state save from compiled code - srsfd sp!, {abt_mode} + // `extern "C" fn irq_handler();` + .global _asm_default_irq_handler + .type _asm_default_irq_handler, %function + _asm_default_irq_handler: + sub lr, lr, 4 + srsfd sp!, {irq_mode} "#, save_context!(), r#" - // Pass the faulting instruction address to the handler. - mov r0, lr // call C handler - bl _abort_handler + bl _irq_handler "#, restore_context!(), r#" - // Return to the failing instruction which is the recommended approach by ARM. rfefd sp! - .size _asm_default_abort_handler, . - _asm_default_abort_handler + .size _asm_default_irq_handler, . - _asm_default_irq_handler + + + .section .text._asm_default_fiq_handler + + // Our default FIQ handler + .global _asm_default_fiq_handler + .type _asm_default_fiq_handler, %function + _asm_default_fiq_handler: + b _asm_default_fiq_handler + .size _asm_default_fiq_handler, . - _asm_default_fiq_handler "#, svc_mode = const ProcessorMode::Svc as u8, irq_mode = const ProcessorMode::Irq as u8, @@ -552,7 +626,7 @@ macro_rules! fpu_enable { // We set up our stacks and `kmain` in system mode. core::arch::global_asm!( r#" - .section .text.startup + .section .text.default_start .align 0 .global _default_start @@ -592,7 +666,6 @@ core::arch::global_asm!( mrc p15, 0, r0, c1, c0, 0 bic r0, #{te_bit} mcr p15, 0, r0, c1, c0, 0 - "#, fpu_enable!(), r#" diff --git a/cortex-r-rt/link.x b/cortex-r-rt/link.x index 6e0b553..309d1bf 100644 --- a/cortex-r-rt/link.x +++ b/cortex-r-rt/link.x @@ -17,8 +17,6 @@ SECTIONS { .text : { /* The vector table must come first */ *(.vector_table) - /* Our exception handling routines */ - *(.text.handlers) /* Now the rest of the code */ *(.text .text*) } > CODE @@ -72,34 +70,38 @@ SECTIONS { } /* -We reserve some space at the top of the RAM for our stacks. We have an IRQ stack -and a FIQ stack, plus the remainder is our system stack. +We reserve some space at the top of the RAM for our exception stacks. The +remainder is our system mode stack. You must keep _stack_top and the stack sizes aligned to eight byte boundaries. */ PROVIDE(_stack_top = ORIGIN(DATA) + LENGTH(DATA)); -PROVIDE(_fiq_stack_size = 0x400); -PROVIDE(_irq_stack_size = 0x1000); -PROVIDE(_abt_stack_size = 0x400); PROVIDE(_und_stack_size = 0x400); -PROVIDE(_svc_stack_size = 0x1000); +PROVIDE(_svc_stack_size = 0x400); +PROVIDE(_abt_stack_size = 0x400); +PROVIDE(_irq_stack_size = 0x400); +PROVIDE(_fiq_stack_size = 0x400); ASSERT(_stack_top % 8 == 0, "ERROR(cortex-r-rt): top of stack is not 8-byte aligned"); -ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of FIQ stack is not 8-byte aligned"); -ASSERT(_irq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of IRQ stack is not 8-byte aligned"); -ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of FIQ stack is not 8-byte aligned"); -ASSERT(_abt_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of ABT stack is not 8-byte aligned"); ASSERT(_und_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of UND stack is not 8-byte aligned"); ASSERT(_svc_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of SVC stack is not 8-byte aligned"); +ASSERT(_abt_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of ABT stack is not 8-byte aligned"); +ASSERT(_irq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of IRQ stack is not 8-byte aligned"); +ASSERT(_fiq_stack_size % 8 == 0, "ERROR(cortex-r-rt): size of FIQ stack is not 8-byte aligned"); -PROVIDE(_asm_undefined_handler =_asm_default_handler); -PROVIDE(_asm_prefetch_handler =_asm_default_handler); -PROVIDE(_asm_abort_handler =_asm_default_handler); +/* Weak aliases for ASM default handlers */ +PROVIDE(_start =_default_start); +PROVIDE(_asm_undefined_handler =_asm_default_undefined_handler); +PROVIDE(_asm_svc_handler =_asm_default_svc_handler); +PROVIDE(_asm_prefetch_handler =_asm_default_prefetch_handler); +PROVIDE(_asm_abort_handler =_asm_default_abort_handler); +PROVIDE(_asm_irq_handler =_asm_default_irq_handler); PROVIDE(_asm_fiq_handler =_asm_default_fiq_handler); +/* Weak aliases for C default handlers */ PROVIDE(_undefined_handler =_default_handler); -PROVIDE(_abort_handler =_default_handler); +PROVIDE(_svc_handler =_default_handler); PROVIDE(_prefetch_handler =_default_handler); +PROVIDE(_abort_handler =_default_handler); PROVIDE(_irq_handler =_default_handler); -PROVIDE(_svc_handler =_default_handler); -PROVIDE(_start =_default_start); +/* There is no default C-language FIQ handler */ diff --git a/cortex-r-rt/src/lib.rs b/cortex-r-rt/src/lib.rs index 3312eda..75b5f34 100644 --- a/cortex-r-rt/src/lib.rs +++ b/cortex-r-rt/src/lib.rs @@ -1,7 +1,24 @@ -//! # Run-time support for Arm Cortex-R +//! # Run-time support for Arm Cortex-R (AArch32) //! //! This library implements a simple Arm vector table, suitable for getting into -//! a Rust application running in System Mode. +//! a Rust application running in System Mode. It also provides a reference +//! start up method. Most Cortex-R based systems will require chip specific +//! start-up code, so the start-up method can over overriden. +//! +//! The default startup routine provided by this crate does not include any +//! special handling for multi-core support because this is oftentimes +//! implementation defined and the exact handling depends on the specific chip +//! in use. Many implementations only run the startup routine with one core and +//! will keep other cores in reset until they are woken up by an implementation +//! specific mechanism. For other implementations where multi-core specific +//! startup adaptions are necessary, the startup routine can be overwritten by +//! the user. +//! +//! ## Features +//! +//! - `eabi-fpu`: Enables the FPU, even if you selected a soft-float ABI target. +//! +//! ## Information about the Run-Time //! //! Transferring from System Mode to User Mode (i.e. implementing an RTOS) is //! not handled here. @@ -32,7 +49,11 @@ //! * `__sidata` - the start of the initialisation values for data, in read-only //! memory. Must be 4-byte aligned. //! -//! ### Functions +//! Using our default start-up function `_default_start`, the memory between +//! `__sbss` and `__ebss` is zeroed, and the memory between `__sdata` and +//! `__edata` is initialised with the data found at `__sidata`. +//! +//! ### C-Compatible Functions //! //! * `kmain` - the `extern "C"` entry point to your application. //! @@ -45,7 +66,9 @@ //! //! * `_svc_handler` - an `extern "C"` function to call when an SVC Exception //! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. +//! `_default_handler` but you can override it. Returning from this function +//! will cause execution to resume from the function the triggered the +//! exception, immediately after the SVC instruction. //! //! Expected prototype: //! @@ -56,108 +79,155 @@ //! //! * `_irq_handler` - an `extern "C"` function to call when an Interrupt //! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. +//! `_default_handler` but you can override it. Returning from this function +//! will cause execution to resume from the function the triggered the +//! exception. //! //! Expected prototype: //! //! ```rust +//! /// Upon return, the interrupt handler will end and execution +//! /// will continue at the interrupted instruction. //! #[unsafe(no_mangle)] //! extern "C" fn _irq_handler(); //! ``` //! -//! * `_undefined_handler` - an `extern "C"` function to call when an Undefined Exception -//! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. It will be called by the -//! `_asm_default_undefined_handler` unless that function is overriden as well. +//! * `_undefined_handler` - an `extern "C"` function to call when an Undefined +//! Exception occurs. Our linker script PROVIDEs a default implementation at +//! `_default_handler` which is used if `_undefined_handler` is missing. //! -//! Expected prototype: +//! The expected prototype for `_undefined_handler` is either: //! //! ```rust +//! /// Does not return //! #[unsafe(no_mangle)] -//! extern "C" fn _undefined_handler(faulting_instruction: u32); +//! extern "C" fn _undefined_handler(addr: usize) -> !; //! ``` +//! +//! or: //! -//! * `_abort_handler` - an `extern "C"` function to call when a Data Abort Exception -//! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. It will be called by the -//! `_asm_default_abort_handler` unless that function is overriden as well. +//! ```rust +//! /// Execution will continue from the returned address. +//! /// +//! /// Return `addr` to go back and execute the faulting instruction again. +//! #[unsafe(no_mangle)] +//! extern "C" fn _undefined_handler(addr: usize) -> usize; +//! ``` //! -//! Expected prototype: +//! * `_abort_handler` - an `extern "C"` function to call when an Data Abort +//! occurs. Our linker script PROVIDEs a default implementation at +//! `_default_handler` which is used if `_abort_handler` is missing. +//! +//! The expected prototype for `_abort_handler` is either: //! //! ```rust +//! /// Does not return //! #[unsafe(no_mangle)] -//! extern "C" fn _abort_handler(faulting_instruction: u32); +//! extern "C" fn _abort_handler(addr: usize) -> !; //! ``` +//! +//! or: //! -//! * `_prefetch_handler` - an `extern "C"` function to call when a Prefetch Abort Exception -//! occurs. Our linker script PROVIDEs a default function at -//! `_default_handler` but you can override it. It will be called by the -//! `_asm_default_prefetch_handler` unless that function is overriden as well. +//! ```rust +//! /// Execution will continue from the returned address. +//! /// +//! /// Return `addr` to go back and execute the faulting instruction again. +//! #[unsafe(no_mangle)] +//! extern "C" fn _abort_handler(addr: usize) -> usize; +//! ``` //! -//! Expected prototype: +//! * `_prefetch_handler` - an `extern "C"` function to call when an Prefetch +//! Abort occurs. Our linker script PROVIDEs a default implementation at +//! `_default_handler` which is used if `_prefetch_handler` is missing. +//! +//! The expected prototype for `_prefetch_handler` is either: +//! +//! ```rust +//! /// Does not return +//! #[unsafe(no_mangle)] +//! extern "C" fn _prefetch_handler(addr: usize) -> !; +//! ``` +//! +//! or: //! //! ```rust +//! /// Execution will continue from the returned address. +//! /// +//! /// Return `addr` to go back and execute the faulting instruction again. //! #[unsafe(no_mangle)] -//! extern "C" fn _prefetch_handler(faulting_instruction: u32); +//! extern "C" fn _prefetch_handler(addr: usize) -> usize; //! ``` //! //! ### ASM functions //! -//! * `__start` - a Reset handler. Our linker script PROVIDEs a default function -//! at `_default_start` but you can override it. Most Cortex-A SoCs require -//! a chip specific startup for tasks like MMU initialization or chip specific -//! initialization routines. -//! * `_asm_fiq_handler` - a naked function to call when a Fast Interrupt -//! Request (FIQ) occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_fiq_handler` but you can override it. +//! * `_start` - a Reset handler. Our linker script PROVIDEs a default function +//! at `_default_start` but you can override it. Some SoCs require a chip +//! specific startup for tasks like MMU initialization or chip specific +//! initialization routines, so if our start-up routine doesn't work for you, +//! supply your own `_start` function (but feel free to call our +//! `_default_start` as part of it). //! * `_asm_undefined_handler` - a naked function to call when an Undefined //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_undefined_handler` but you can override it. -//! * `_asm_prefetch_handler` - a naked function to call when an Prefetch +//! `_asm_default_undefined_handler` but you can override it. The provided +//! default handler will call `_undefined_handler`, saving state as required. +//! * `_asm_svc_handler` - a naked function to call when an SVC Exception +//! occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_svc_handler` but you can override it. The provided default +//! handler will call `_svc_handler`, saving state as required. +//! * `_asm_prefetch_handler` - a naked function to call when a Prefetch //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_prefetch_handler` but you can override it. The provided default -//! handler will perform an exception return to the faulting address. +//! `_asm_default_prefetch_handler` but you can override it. The provided +//! default handler will call `_prefetch_handler`, saving state as required. +//! Note that Prefetch Exceptions are handled in Abort Mode, Monitor Mode or +//! Hyp Mode, depending on CPU configuration. There is no Prefetch Abort mode, +//! so there is no Prefetch Abort Mode stack. //! * `_asm_abort_handler` - a naked function to call when an Abort Exception //! occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_abort_handler` but you can override it. The provided default handler -//! will perform an exception return to the faulting address. +//! `_asm_default_abort_handler` but you can override it. The provided default +//! handler will call `_abort_handler`, saving state as required. +//! * `_asm_irq_handler` - a naked function to call when an Undefined Exception +//! occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_irq_handler` but you can override it. The provided default +//! handler will call `_irq_handler`, saving state as required. +//! * `_asm_fiq_handler` - a naked function to call when a Fast Interrupt +//! Request (FIQ) occurs. Our linker script PROVIDEs a default function at +//! `_asm_default_fiq_handler` but you can override it. The provided default +//! just spins forever. //! -//! On start-up, the memory between `__sbss` and `__ebss` is zeroed, and the -//! memory between `__sdata` and `__edata` is initialised with the data found at -//! `__sidata`. +//! ## Outputs //! //! This library produces global symbols called: //! //! * `_vector_table` - the start of the interrupt vector table //! * `_default_start` - the default Reset handler, that sets up some stacks and //! calls an `extern "C"` function called `kmain`. +//! * `_asm_default_undefined_handler` - assembly language trampoline that calls +//! `_undefined_handler` +//! * `_asm_default_svc_handler` - assembly language trampoline that calls +//! `_svc_handler` +//! * `_asm_default_prefetch_handler` - assembly language trampoline that calls +//! `_prefetch_handler` +//! * `_asm_default_abort_handler` - assembly language trampoline that calls +//! `_abort_handler` +//! * `_asm_default_irq_handler` - assembly language trampoline that calls +//! `_irq_handler` //! * `_asm_default_fiq_handler` - an FIQ handler that just spins -//! * `_asm_default_handler` - an exception handler that just spins -//! * `_asm_svc_handler` - assembly language trampoline for SVC Exceptions that -//! calls `_svc_handler` -//! * `_asm_irq_handler` - assembly language trampoline for Interrupts that -//! calls `_irq_handler` +//! * `_default_handler` - a C compatible function that spins forever. //! //! The assembly language trampolines are required because Armv7-R (and Armv8-R) //! 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. We -//! supply a default handler that prints an error message to Semihosting so you -//! know if you hit an unexpected exception. 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. -//! -//! If our start-up routine doesn't work for you (e.g. if you have to initialise -//! your memory controller before you touch RAM), supply your own `_start` -//! function (but feel free to call our `_default_start` as part of it). +//! `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 //! -//! You can find example code using QEMU inside the -//! [project repository](https://github.com/rust-embedded/cortex-ar/tree/main/examples) +//! You can find example code using QEMU inside the [project +//! repository](https://github.com/rust-embedded/cortex-ar/tree/main/examples) #![no_std] @@ -183,9 +253,7 @@ pub extern "C" fn _default_handler() { // The Interrupt Vector Table, and some default assembly-language handler. core::arch::global_asm!( r#" - .section .vector_table - .align 0 - + .section .vector_table,"ax",%progbits .global _vector_table .type _vector_table, %function _vector_table: @@ -198,20 +266,6 @@ core::arch::global_asm!( ldr pc, =_asm_irq_handler ldr pc, =_asm_fiq_handler .size _vector_table, . - _vector_table - - .section .text.handlers - - .global _asm_default_fiq_handler - .type _asm_default_fiq_handler, %function - _asm_default_fiq_handler: - b _asm_default_fiq_handler - .size _asm_default_fiq_handler, . - _asm_default_fiq_handler - - .global _asm_default_handler - .type _asm_default_handler, %function - _asm_default_handler: - b _asm_default_handler - .size _asm_default_handler, . - _asm_default_handler "# ); @@ -306,17 +360,67 @@ macro_rules! restore_context { // Our assembly language exception handlers core::arch::global_asm!( r#" - .section .text.handlers // Work around https://github.com/rust-lang/rust/issues/127269 .fpu vfp3-d16 - .align 0 + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _undefined_handler(addr: usize) -> usize;` + // or + // `extern "C" fn _undefined_handler(addr: usize) -> !;` + .section .text._asm_default_undefined_handler + .global _asm_default_undefined_handler + .type _asm_default_undefined_handler, %function + _asm_default_undefined_handler: + // state save from compiled code + srsfd sp!, {und_mode} + // to work out what mode we're in, we need R0 + push {{r0}} + // First adjust LR for two purposes: Passing the faulting instruction to the C handler, + // and to return to the failing instruction after the C handler returns. + // Load processor status for the calling code + mrs r0, spsr + // Was the code that triggered the exception in Thumb state? + tst r0, {t_bit} + // Subtract 2 in Thumb Mode, 4 in Arm Mode - see p.1206 of the ARMv7-A architecture manual. + ite eq + subeq lr, lr, #4 + subne lr, lr, #2 + // save the newly computed LR + push {{lr}} + // now do our standard exception save + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _undefined_handler + // if we get back here, assume they returned a new LR in r0 + mov lr, r0 + // do our standard restore + "#, + restore_context!(), + r#" + // get our saved LR + pop {{lr}} + // get our real saved R0 + pop {{r0}} + // overwrite the saved LR with the adjusted one + str lr, [sp] + // Return to the failing instruction which is the recommended approach by ARM. + rfefd sp! + .size _asm_default_undefined_handler, . - _asm_default_undefined_handler + + + .section .text._asm_default_svc_handler // Called from the vector table when we have an software interrupt. // Saves state and calls a C-compatible handler like - // `extern "C" fn svc_handler(svc: u32, context: *const u32);` - .global _asm_svc_handler - .type _asm_svc_handler, %function - _asm_svc_handler: + // `extern "C" fn svc_handler(svc: u32);` + .global _asm_default_svc_handler + .type _asm_default_svc_handler, %function + _asm_default_svc_handler: srsfd sp!, {svc_mode} "#, save_context!(), @@ -333,82 +437,59 @@ core::arch::global_asm!( restore_context!(), r#" rfefd sp! - .size _asm_svc_handler, . - _asm_svc_handler + .size _asm_default_svc_handler, . - _asm_default_svc_handler - // Called from the vector table when we have an interrupt. - // Saves state and calls a C-compatible handler like - // `extern "C" fn irq_handler();` - .global _asm_irq_handler - .type _asm_irq_handler, %function - _asm_irq_handler: - sub lr, lr, 4 - srsfd sp!, {irq_mode} - "#, - save_context!(), - r#" - // call C handler - bl _irq_handler - "#, - restore_context!(), - r#" - rfefd sp! - .size _asm_irq_handler, . - _asm_irq_handler + .section .text._asm_default_abort_handler // Called from the vector table when we have an undefined exception. // Saves state and calls a C-compatible handler like - // `extern "C" fn _undefined_handler();` - .global _asm_default_undefined_handler - .type _asm_default_undefined_handler, %function - _asm_default_undefined_handler: - // First adjust LR for two purposes: Passing the faulting instruction to the C handler, - // and to return to the failing instruction after the C handler returns. - // Load processor status - mrs r4, cpsr - // Occurred in Thumb state? - tst r4, {t_bit} - // If not in Thumb mode, branch to not_thumb - beq not_thumb - subs lr, lr, #2 - b done -not_thumb: - // Subtract 4 from LR (ARM mode) - subs lr, lr, #4 -done: + // `extern "C" fn _abort_handler(addr: usize);` + .global _asm_default_abort_handler + .type _asm_default_abort_handler, %function + _asm_default_abort_handler: + // Subtract 8 from the stored LR, see p.1214 of the ARMv7-A architecture manual. + subs lr, lr, #8 // state save from compiled code - srsfd sp!, {und_mode} + srsfd sp!, {abt_mode} "#, save_context!(), r#" // Pass the faulting instruction address to the handler. mov r0, lr // call C handler - bl _undefined_handler + bl _abort_handler + // if we get back here, assume they returned a new LR in r0 + mov lr, r0 "#, restore_context!(), r#" // Return to the failing instruction which is the recommended approach by ARM. rfefd sp! - .size _asm_default_undefined_handler, . - _asm_default_undefined_handler + .size _asm_default_abort_handler, . - _asm_default_abort_handler - // Called from the vector table when we have an undefined exception. + .section .text._asm_default_prefetch_handler + + // Called from the vector table when we have a prefetch exception. // Saves state and calls a C-compatible handler like - // `extern "C" fn _prefetch_handler();` + // `extern "C" fn _prefetch_handler(addr: usize);` .global _asm_default_prefetch_handler .type _asm_default_prefetch_handler, %function _asm_default_prefetch_handler: // Subtract 4 from the stored LR, see p.1212 of the ARMv7-A architecture manual. - subs lr, lr, #4 + subs lr, lr, #4 // state save from compiled code srsfd sp!, {abt_mode} "#, save_context!(), r#" // Pass the faulting instruction address to the handler. - mov r0, lr + mov r0, lr // call C handler bl _prefetch_handler + // if we get back here, assume they returned a new LR in r0 + mov lr, r0 "#, restore_context!(), r#" @@ -417,29 +498,36 @@ done: .size _asm_default_prefetch_handler, . - _asm_default_prefetch_handler - // Called from the vector table when we have an undefined exception. + .section .text._asm_default_irq_handler + + // Called from the vector table when we have an interrupt. // Saves state and calls a C-compatible handler like - // `extern "C" fn _abort_handler();` - .global _asm_default_abort_handler - .type _asm_default_abort_handler, %function - _asm_default_abort_handler: - // Subtract 8 from the stored LR, see p.1214 of the ARMv7-A architecture manual. - subs lr, lr, #8 - // state save from compiled code - srsfd sp!, {abt_mode} + // `extern "C" fn irq_handler();` + .global _asm_default_irq_handler + .type _asm_default_irq_handler, %function + _asm_default_irq_handler: + sub lr, lr, 4 + srsfd sp!, {irq_mode} "#, save_context!(), r#" - // Pass the faulting instruction address to the handler. - mov r0, lr // call C handler - bl _abort_handler + bl _irq_handler "#, restore_context!(), r#" - // Return to the failing instruction which is the recommended approach by ARM. rfefd sp! - .size _asm_default_abort_handler, . - _asm_default_abort_handler + .size _asm_default_irq_handler, . - _asm_default_irq_handler + + + .section .text._asm_default_fiq_handler + + // Our default FIQ handler + .global _asm_default_fiq_handler + .type _asm_default_fiq_handler, %function + _asm_default_fiq_handler: + b _asm_default_fiq_handler + .size _asm_default_fiq_handler, . - _asm_default_fiq_handler "#, svc_mode = const ProcessorMode::Svc as u8, irq_mode = const ProcessorMode::Irq as u8, @@ -447,8 +535,8 @@ done: abt_mode = const ProcessorMode::Abt as u8, t_bit = const { Cpsr::new_with_raw_value(0) - .with_t(true) - .raw_value() + .with_t(true) + .raw_value() }, ); @@ -483,11 +571,10 @@ macro_rules! fpu_enable { // We set up our stacks and `kmain` in system mode. core::arch::global_asm!( r#" - .section .text.startup - .align 0 // Work around https://github.com/rust-lang/rust/issues/127269 .fpu vfp3-d16 - + + .section .text.el1_start .type _el1_start, %function _el1_start: // Set up stacks. @@ -609,9 +696,10 @@ core::arch::global_asm!( #[cfg(arm_architecture = "v7-r")] core::arch::global_asm!( r#" - .section .text.startup - .align 0 + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3-d16 + .section .text.default_start .global _default_start .type _default_start, %function _default_start: @@ -629,8 +717,10 @@ core::arch::global_asm!( #[cfg(arm_architecture = "v8-r")] core::arch::global_asm!( r#" - .section .text.startup - .align 0 + // Work around https://github.com/rust-lang/rust/issues/127269 + .fpu vfp3-d16 + + .section .text.default_start .global _default_start .type _default_start, %function diff --git a/examples/mps3-an536/reference/abt-exception-armv8r-none-eabihf.out b/examples/mps3-an536/reference/abt-exception-armv8r-none-eabihf.out new file mode 100644 index 0000000..b12baf8 --- /dev/null +++ b/examples/mps3-an536/reference/abt-exception-armv8r-none-eabihf.out @@ -0,0 +1,9 @@ +Hello, this is an data abort exception example +data abort occurred +DFSR (Fault Status Register): DFSR { ext=false wnr=false Domain=0b0010 Status=0b00001 } +DFSR Status: Ok(AlignmentFault) +DFAR (Faulting Address Register): Dfar(4097) +data abort occurred +DFSR (Fault Status Register): DFSR { ext=false wnr=false Domain=0b0010 Status=0b00001 } +DFSR Status: Ok(AlignmentFault) +DFAR (Faulting Address Register): Dfar(4097) diff --git a/examples/mps3-an536/reference/prefetch-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536/reference/prefetch-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 0000000..fab7e13 --- /dev/null +++ b/examples/mps3-an536/reference/prefetch-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,11 @@ +Hello, this is a prefetch exception example +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0010 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0010 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 diff --git a/examples/mps3-an536/reference/prefetch-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536/reference/prefetch-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 0000000..f00a35c --- /dev/null +++ b/examples/mps3-an536/reference/prefetch-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,11 @@ +Hello, this is a prefetch exception example +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0010 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0010 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 diff --git a/examples/mps3-an536/reference/undef-exception-a32-armv8r-none-eabihf.out b/examples/mps3-an536/reference/undef-exception-a32-armv8r-none-eabihf.out new file mode 100644 index 0000000..a176992 --- /dev/null +++ b/examples/mps3-an536/reference/undef-exception-a32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_a32 +undefined abort occurred +caught udf_from_a32 diff --git a/examples/mps3-an536/reference/undef-exception-t32-armv8r-none-eabihf.out b/examples/mps3-an536/reference/undef-exception-t32-armv8r-none-eabihf.out new file mode 100644 index 0000000..f15c47e --- /dev/null +++ b/examples/mps3-an536/reference/undef-exception-t32-armv8r-none-eabihf.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_t32 +undefined abort occurred +caught udf_from_t32 diff --git a/examples/mps3-an536/src/bin/abt-exception.rs b/examples/mps3-an536/src/bin/abt-exception.rs new file mode 100644 index 0000000..ddf599c --- /dev/null +++ b/examples/mps3-an536/src/bin/abt-exception.rs @@ -0,0 +1,87 @@ +//! Example triggering an data abort exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::AtomicU32; + +use cortex_ar::register::{Dfar, Dfsr, Sctlr}; +// pull in our start-up code +use mps3_an536 as _; + +use semihosting::println; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + main(); +} + +/// The main function of our Rust application. +#[export_name = "main"] +#[allow(unreachable_code)] +fn main() -> ! { + // Enable alignment check for Armv7-R. Was not required + // on Cortex-A for some reason, even though the bit was not set. + enable_alignment_check(); + + println!("Hello, this is an data abort exception example"); + // Unaligned read + unsafe { + let addr: *const u32 = 0x1001 as *const u32; // Unaligned address (not 4-byte aligned) + core::arch::asm!( + "ldr r0, [{addr}]", // Attempt unaligned load (should trigger Data Abort) + addr = in(reg) addr, // Pass unaligned pointer + options(nostack, preserves_flags) // No stack usage, preserves flags + ); + } + + unreachable!("should never be here!"); +} + +fn enable_alignment_check() { + let mut sctrl = Sctlr::read(); + sctrl.set_a(true); + Sctlr::write(sctrl); +} + +fn disable_alignment_check() { + let mut sctrl = Sctlr::read(); + sctrl.set_a(false); + Sctlr::write(sctrl); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(_addr: u32) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(_addr: u32) -> ! { + panic!("unexpected prefetch exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(addr: usize) -> usize { + println!("data abort occurred"); + // If this is not disabled, reading DFAR will trigger an alignment fault on Armv8-R, leading + // to a loop. + disable_alignment_check(); + let dfsr = Dfsr::read(); + println!("DFSR (Fault Status Register): {:?}", dfsr); + println!("DFSR Status: {:?}", dfsr.status()); + let dfar = Dfar::read(); + println!("DFAR (Faulting Address Register): {:?}", dfar); + enable_alignment_check(); + // For the first iteration, we do a regular exception return, which should + // trigger the exception again. The second time around we quit. + if COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed) == 1 { + semihosting::process::exit(0); + } + + addr +} diff --git a/examples/mps3-an536/src/bin/prefetch-exception-a32.rs b/examples/mps3-an536/src/bin/prefetch-exception-a32.rs new file mode 100644 index 0000000..b561ce1 --- /dev/null +++ b/examples/mps3-an536/src/bin/prefetch-exception-a32.rs @@ -0,0 +1,87 @@ +//! Example triggering a prefetch exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use cortex_ar::register::{Ifar, Ifsr}; +use semihosting::println; + +// pull in our start-up code +use mps3_an536 as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a prefetch exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + unsafe { + // trigger an prefetch exception, from A32 (Arm) mode + bkpt_from_a32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn bkpt_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_a32(); + .arm + .global bkpt_from_a32 + .type bkpt_from_a32, %function + bkpt_from_a32: + bkpt #0 + bx lr + .size bkpt_from_a32, . - bkpt_from_a32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(addr: usize) -> usize { + println!("prefetch abort occurred"); + let ifsr = Ifsr::read(); + println!("IFSR (Fault Status Register): {:?}", ifsr); + println!("IFSR Status: {:?}", ifsr.status()); + let ifar = Ifar::read(); + println!("IFAR (Faulting Address Register): {:?}", ifar); + + if addr == bkpt_from_a32 as usize { + println!("caught bkpt_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_a32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/mps3-an536/src/bin/prefetch-exception-t32.rs b/examples/mps3-an536/src/bin/prefetch-exception-t32.rs new file mode 100644 index 0000000..12249cb --- /dev/null +++ b/examples/mps3-an536/src/bin/prefetch-exception-t32.rs @@ -0,0 +1,90 @@ +//! Example triggering a prefetch exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use cortex_ar::register::{Ifar, Ifsr}; +use semihosting::println; + +// pull in our start-up code +use mps3_an536 as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a prefetch exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + unsafe { + // trigger an prefetch exception, from T32 (Thumb) mode + bkpt_from_t32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn bkpt_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_t32(); + .thumb + .global bkpt_from_t32 + .type bkpt_from_t32, %function + bkpt_from_t32: + bkpt #0 + bx lr + .size bkpt_from_t32, . - bkpt_from_t32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(addr: usize) -> usize { + println!("prefetch abort occurred"); + let ifsr = Ifsr::read(); + println!("IFSR (Fault Status Register): {:?}", ifsr); + println!("IFSR Status: {:?}", ifsr.status()); + let ifar = Ifar::read(); + println!("IFAR (Faulting Address Register): {:?}", ifar); + + if (addr + 1) == bkpt_from_t32 as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught bkpt_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_t32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/mps3-an536/src/bin/undef-exception-a32.rs b/examples/mps3-an536/src/bin/undef-exception-a32.rs new file mode 100644 index 0000000..ff672f6 --- /dev/null +++ b/examples/mps3-an536/src/bin/undef-exception-a32.rs @@ -0,0 +1,83 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use semihosting::println; + +// pull in our start-up code +use mps3_an536 as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a undef exception example"); + + unsafe { + // trigger an Undefined exception, from A32 (Thumb) mode + udf_from_a32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn udf_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_a32(); + .arm + .global udf_from_a32 + .type udf_from_a32, %function + udf_from_a32: + udf #0 + bx lr + .size udf_from_a32, . - udf_from_a32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(addr: usize) -> usize { + println!("undefined abort occurred"); + + if addr == udf_from_a32 as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in a32 + // machine code. + println!("caught udf_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_a32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + // otherwise go back to where we came from + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/mps3-an536/src/bin/undef-exception-t32.rs b/examples/mps3-an536/src/bin/undef-exception-t32.rs new file mode 100644 index 0000000..1cd70fd --- /dev/null +++ b/examples/mps3-an536/src/bin/undef-exception-t32.rs @@ -0,0 +1,82 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use semihosting::println; + +// pull in our start-up code +use mps3_an536 as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a undef exception example"); + + unsafe { + // trigger an Undefined exception, from T32 (Thumb) mode + udf_from_t32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn udf_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_t32(); + .thumb + .global udf_from_t32 + .type udf_from_t32, %function + udf_from_t32: + udf #0 + bx lr + .size udf_from_t32, . - udf_from_t32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(addr: usize) -> usize { + println!("undefined abort occurred"); + + if (addr + 1) == udf_from_t32 as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught udf_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_t32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/versatileab/reference/abt-exception-armv7r-none-eabi.out b/examples/versatileab/reference/abt-exception-armv7r-none-eabi.out new file mode 100644 index 0000000..703128d --- /dev/null +++ b/examples/versatileab/reference/abt-exception-armv7r-none-eabi.out @@ -0,0 +1,9 @@ +Hello, this is an data abort exception example +data abort occurred +DFSR (Fault Status Register): DFSR { ext=false wnr=false Domain=0b0000 Status=0b00001 } +DFSR Status: Ok(AlignmentFault) +DFAR (Faulting Address Register): Dfar(4097) +data abort occurred +DFSR (Fault Status Register): DFSR { ext=false wnr=false Domain=0b0000 Status=0b00001 } +DFSR Status: Ok(AlignmentFault) +DFAR (Faulting Address Register): Dfar(4097) diff --git a/examples/versatileab/reference/prefetch-exception-armv7a-none-eabi.out b/examples/versatileab/reference/prefetch-exception-a32-armv7a-none-eabi.out similarity index 80% rename from examples/versatileab/reference/prefetch-exception-armv7a-none-eabi.out rename to examples/versatileab/reference/prefetch-exception-a32-armv7a-none-eabi.out index 9744364..5aff8c4 100644 --- a/examples/versatileab/reference/prefetch-exception-armv7a-none-eabi.out +++ b/examples/versatileab/reference/prefetch-exception-a32-armv7a-none-eabi.out @@ -1,9 +1,11 @@ -Hello, this is an prefetch exception example +Hello, this is a prefetch exception example prefetch abort occurred IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } IFSR Status: Ok(DebugEvent) IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 prefetch abort occurred IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } IFSR Status: Ok(DebugEvent) IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 diff --git a/examples/versatileab/reference/prefetch-exception-armv7r-none-eabihf.out b/examples/versatileab/reference/prefetch-exception-a32-armv7r-none-eabi.out similarity index 80% rename from examples/versatileab/reference/prefetch-exception-armv7r-none-eabihf.out rename to examples/versatileab/reference/prefetch-exception-a32-armv7r-none-eabi.out index 9744364..5aff8c4 100644 --- a/examples/versatileab/reference/prefetch-exception-armv7r-none-eabihf.out +++ b/examples/versatileab/reference/prefetch-exception-a32-armv7r-none-eabi.out @@ -1,9 +1,11 @@ -Hello, this is an prefetch exception example +Hello, this is a prefetch exception example prefetch abort occurred IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } IFSR Status: Ok(DebugEvent) IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 prefetch abort occurred IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } IFSR Status: Ok(DebugEvent) IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 diff --git a/examples/versatileab/reference/prefetch-exception-a32-armv7r-none-eabihf.out b/examples/versatileab/reference/prefetch-exception-a32-armv7r-none-eabihf.out new file mode 100644 index 0000000..5aff8c4 --- /dev/null +++ b/examples/versatileab/reference/prefetch-exception-a32-armv7r-none-eabihf.out @@ -0,0 +1,11 @@ +Hello, this is a prefetch exception example +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_a32 diff --git a/examples/versatileab/reference/prefetch-exception-t32-armv7a-none-eabi.out b/examples/versatileab/reference/prefetch-exception-t32-armv7a-none-eabi.out new file mode 100644 index 0000000..dc73215 --- /dev/null +++ b/examples/versatileab/reference/prefetch-exception-t32-armv7a-none-eabi.out @@ -0,0 +1,11 @@ +Hello, this is a prefetch exception example +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 diff --git a/examples/versatileab/reference/prefetch-exception-t32-armv7r-none-eabi.out b/examples/versatileab/reference/prefetch-exception-t32-armv7r-none-eabi.out new file mode 100644 index 0000000..dc73215 --- /dev/null +++ b/examples/versatileab/reference/prefetch-exception-t32-armv7r-none-eabi.out @@ -0,0 +1,11 @@ +Hello, this is a prefetch exception example +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 diff --git a/examples/versatileab/reference/prefetch-exception-t32-armv7r-none-eabihf.out b/examples/versatileab/reference/prefetch-exception-t32-armv7r-none-eabihf.out new file mode 100644 index 0000000..dc73215 --- /dev/null +++ b/examples/versatileab/reference/prefetch-exception-t32-armv7r-none-eabihf.out @@ -0,0 +1,11 @@ +Hello, this is a prefetch exception example +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) +caught bkpt_from_t32 diff --git a/examples/versatileab/reference/undef-exception-a32-armv7a-none-eabi.out b/examples/versatileab/reference/undef-exception-a32-armv7a-none-eabi.out new file mode 100644 index 0000000..a176992 --- /dev/null +++ b/examples/versatileab/reference/undef-exception-a32-armv7a-none-eabi.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_a32 +undefined abort occurred +caught udf_from_a32 diff --git a/examples/versatileab/reference/undef-exception-a32-armv7r-none-eabi.out b/examples/versatileab/reference/undef-exception-a32-armv7r-none-eabi.out new file mode 100644 index 0000000..a176992 --- /dev/null +++ b/examples/versatileab/reference/undef-exception-a32-armv7r-none-eabi.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_a32 +undefined abort occurred +caught udf_from_a32 diff --git a/examples/versatileab/reference/undef-exception-a32-armv7r-none-eabihf.out b/examples/versatileab/reference/undef-exception-a32-armv7r-none-eabihf.out new file mode 100644 index 0000000..a176992 --- /dev/null +++ b/examples/versatileab/reference/undef-exception-a32-armv7r-none-eabihf.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_a32 +undefined abort occurred +caught udf_from_a32 diff --git a/examples/versatileab/reference/undef-exception-armv7a-none-eabi.out b/examples/versatileab/reference/undef-exception-armv7a-none-eabi.out deleted file mode 100644 index b149c88..0000000 --- a/examples/versatileab/reference/undef-exception-armv7a-none-eabi.out +++ /dev/null @@ -1,3 +0,0 @@ -Hello, this is an undefined exception example -undefined exception occurred -undefined exception occurred diff --git a/examples/versatileab/reference/undef-exception-armv7r-none-eabihf.out b/examples/versatileab/reference/undef-exception-armv7r-none-eabihf.out deleted file mode 100644 index b149c88..0000000 --- a/examples/versatileab/reference/undef-exception-armv7r-none-eabihf.out +++ /dev/null @@ -1,3 +0,0 @@ -Hello, this is an undefined exception example -undefined exception occurred -undefined exception occurred diff --git a/examples/versatileab/reference/undef-exception-t32-armv7a-none-eabi.out b/examples/versatileab/reference/undef-exception-t32-armv7a-none-eabi.out new file mode 100644 index 0000000..f15c47e --- /dev/null +++ b/examples/versatileab/reference/undef-exception-t32-armv7a-none-eabi.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_t32 +undefined abort occurred +caught udf_from_t32 diff --git a/examples/versatileab/reference/undef-exception-t32-armv7r-none-eabi.out b/examples/versatileab/reference/undef-exception-t32-armv7r-none-eabi.out new file mode 100644 index 0000000..f15c47e --- /dev/null +++ b/examples/versatileab/reference/undef-exception-t32-armv7r-none-eabi.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_t32 +undefined abort occurred +caught udf_from_t32 diff --git a/examples/versatileab/reference/undef-exception-t32-armv7r-none-eabihf.out b/examples/versatileab/reference/undef-exception-t32-armv7r-none-eabihf.out new file mode 100644 index 0000000..f15c47e --- /dev/null +++ b/examples/versatileab/reference/undef-exception-t32-armv7r-none-eabihf.out @@ -0,0 +1,5 @@ +Hello, this is a undef exception example +undefined abort occurred +caught udf_from_t32 +undefined abort occurred +caught udf_from_t32 diff --git a/examples/versatileab/src/bin/abt-exception.rs b/examples/versatileab/src/bin/abt-exception.rs index d8427bc..ab03ecd 100644 --- a/examples/versatileab/src/bin/abt-exception.rs +++ b/examples/versatileab/src/bin/abt-exception.rs @@ -56,17 +56,17 @@ fn disable_alignment_check() { } #[unsafe(no_mangle)] -unsafe extern "C" fn _undefined_handler(_faulting_instruction: u32) { +unsafe extern "C" fn _undefined_handler(_addr: u32) -> ! { panic!("unexpected undefined exception"); } #[unsafe(no_mangle)] -unsafe extern "C" fn _prefetch_handler(_faulting_instruction: u32) { +unsafe extern "C" fn _prefetch_handler(_addr: u32) -> ! { panic!("unexpected prefetch exception"); } #[unsafe(no_mangle)] -unsafe extern "C" fn _abort_handler(_faulting_instruction: u32) { +unsafe extern "C" fn _abort_handler(addr: usize) -> usize { println!("data abort occurred"); let dfsr = Dfsr::read(); println!("DFSR (Fault Status Register): {:?}", dfsr); @@ -78,9 +78,10 @@ unsafe extern "C" fn _abort_handler(_faulting_instruction: u32) { println!("DFAR (Faulting Address Register): {:?}", dfar); enable_alignment_check(); // For the first iteration, we do a regular exception return, which should - // trigger the exception again. - let counter_val = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1; - if counter_val == 2 { + // trigger the exception again. The second time around we quit. + if COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed) == 1 { semihosting::process::exit(0); } + + addr } diff --git a/examples/versatileab/src/bin/prefetch-exception-a32.rs b/examples/versatileab/src/bin/prefetch-exception-a32.rs new file mode 100644 index 0000000..d773dd3 --- /dev/null +++ b/examples/versatileab/src/bin/prefetch-exception-a32.rs @@ -0,0 +1,87 @@ +//! Example triggering a prefetch exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use cortex_ar::register::{Ifar, Ifsr}; +use semihosting::println; + +// pull in our start-up code +use versatileab as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a prefetch exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + unsafe { + // trigger an prefetch exception, from A32 (Arm) mode + bkpt_from_a32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn bkpt_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_a32(); + .arm + .global bkpt_from_a32 + .type bkpt_from_a32, %function + bkpt_from_a32: + bkpt #0 + bx lr + .size bkpt_from_a32, . - bkpt_from_a32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(addr: usize) -> usize { + println!("prefetch abort occurred"); + let ifsr = Ifsr::read(); + println!("IFSR (Fault Status Register): {:?}", ifsr); + println!("IFSR Status: {:?}", ifsr.status()); + let ifar = Ifar::read(); + println!("IFAR (Faulting Address Register): {:?}", ifar); + + if addr == bkpt_from_a32 as usize { + println!("caught bkpt_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_a32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/versatileab/src/bin/prefetch-exception-t32.rs b/examples/versatileab/src/bin/prefetch-exception-t32.rs new file mode 100644 index 0000000..acea6c8 --- /dev/null +++ b/examples/versatileab/src/bin/prefetch-exception-t32.rs @@ -0,0 +1,90 @@ +//! Example triggering a prefetch exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use cortex_ar::register::{Ifar, Ifsr}; +use semihosting::println; + +// pull in our start-up code +use versatileab as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a prefetch exception example"); + + // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. + // See p. 2038 of ARMv7-M Architecture Reference Manual + unsafe { + // trigger an prefetch exception, from T32 (Thumb) mode + bkpt_from_t32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn bkpt_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn bkpt_from_t32(); + .thumb + .global bkpt_from_t32 + .type bkpt_from_t32, %function + bkpt_from_t32: + bkpt #0 + bx lr + .size bkpt_from_t32, . - bkpt_from_t32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(addr: usize) -> usize { + println!("prefetch abort occurred"); + let ifsr = Ifsr::read(); + println!("IFSR (Fault Status Register): {:?}", ifsr); + println!("IFSR Status: {:?}", ifsr.status()); + let ifar = Ifar::read(); + println!("IFAR (Faulting Address Register): {:?}", ifar); + + if (addr + 1) == bkpt_from_t32 as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught bkpt_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, bkpt_from_t32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/versatileab/src/bin/prefetch-exception.rs b/examples/versatileab/src/bin/prefetch-exception.rs deleted file mode 100644 index 091a75f..0000000 --- a/examples/versatileab/src/bin/prefetch-exception.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Example triggering an prefetch exception. - -#![no_std] -#![no_main] - -use core::sync::atomic::AtomicU32; - -use cortex_ar::register::{Ifar, Ifsr}; -// pull in our start-up code -use versatileab as _; - -use semihosting::println; - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up. -#[no_mangle] -pub extern "C" fn kmain() -> ! { - main(); -} - -static COUNTER: AtomicU32 = AtomicU32::new(0); - -/// The main function of our Rust application. -#[export_name = "main"] -fn main() -> ! { - println!("Hello, this is an prefetch exception example"); - - // A BKPT instruction triggers a Prefetch Abort except when Halting debug-mode is enabled. - // See p. 2038 of ARMv7-M Architecture Reference Manual - unsafe { - core::arch::asm!("bkpt"); - } - - unreachable!("should never be here!"); -} - -#[unsafe(no_mangle)] -unsafe extern "C" fn _undefined_handler(_faulting_instruction: u32) { - panic!("unexpected undefined exception"); -} - -#[unsafe(no_mangle)] -unsafe extern "C" fn _prefetch_handler(_faulting_instruction: u32) { - println!("prefetch abort occurred"); - let ifsr = Ifsr::read(); - println!("IFSR (Fault Status Register): {:?}", ifsr); - println!("IFSR Status: {:?}", ifsr.status()); - let ifar = Ifar::read(); - println!("IFAR (Faulting Address Register): {:?}", ifar); - // For the first iteration, we do a regular exception return, which should - // trigger the exception again. - let counter_val = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1; - if counter_val == 2 { - semihosting::process::exit(0); - } -} - -#[unsafe(no_mangle)] -unsafe extern "C" fn _abort_handler(_faulting_instruction: u32) { - panic!("unexpected abort exception"); -} diff --git a/examples/versatileab/src/bin/undef-exception-a32.rs b/examples/versatileab/src/bin/undef-exception-a32.rs new file mode 100644 index 0000000..6679d2d --- /dev/null +++ b/examples/versatileab/src/bin/undef-exception-a32.rs @@ -0,0 +1,82 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use semihosting::println; + +// pull in our start-up code +use versatileab as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a undef exception example"); + + unsafe { + // trigger an Undefined exception, from A32 (Thumb) mode + udf_from_a32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn udf_from_a32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_a32(); + .arm + .global udf_from_a32 + .type udf_from_a32, %function + udf_from_a32: + udf #0 + bx lr + .size udf_from_a32, . - udf_from_a32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(addr: usize) -> usize { + println!("undefined abort occurred"); + + if addr == udf_from_a32 as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in a32 + // machine code. + println!("caught udf_from_a32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_a32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/versatileab/src/bin/undef-exception-t32.rs b/examples/versatileab/src/bin/undef-exception-t32.rs new file mode 100644 index 0000000..c9869b4 --- /dev/null +++ b/examples/versatileab/src/bin/undef-exception-t32.rs @@ -0,0 +1,82 @@ +//! Example triggering a undef exception. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, Ordering}; +use semihosting::println; + +// pull in our start-up code +use versatileab as _; + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// The entry-point to the Rust application. +/// +/// It is called by the start-up. +#[no_mangle] +pub extern "C" fn kmain() -> ! { + println!("Hello, this is a undef exception example"); + + unsafe { + // trigger an Undefined exception, from T32 (Thumb) mode + udf_from_t32(); + } + + // this should be impossible because returning from the fault handler will + // immediately trigger the fault again. + + unreachable!("should never be here!"); +} + +// These functions are written in assembly +extern "C" { + fn udf_from_t32(); +} + +core::arch::global_asm!( + r#" + // fn udf_from_t32(); + .thumb + .global udf_from_t32 + .type udf_from_t32, %function + udf_from_t32: + udf #0 + bx lr + .size udf_from_t32, . - udf_from_t32 +"# +); + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(_addr: usize) -> ! { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _undefined_handler(addr: usize) -> usize { + println!("undefined abort occurred"); + + if (addr + 1) == udf_from_t32 as usize { + // note that thumb functions have their LSB set, despite always being a + // multiple of two - that's how the CPU knows they are written in T32 + // machine code. + println!("caught udf_from_t32"); + } else { + println!( + "Bad fault address {:08x} is not {:08x}", + addr, udf_from_t32 as usize + ); + } + + if COUNTER.fetch_add(1, Ordering::Relaxed) == 1 { + // we've faulted twice - time to quit + semihosting::process::exit(0); + } + + addr +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_addr: usize) -> ! { + panic!("unexpected abort exception"); +} diff --git a/examples/versatileab/src/bin/undef-exception.rs b/examples/versatileab/src/bin/undef-exception.rs deleted file mode 100644 index a87b73f..0000000 --- a/examples/versatileab/src/bin/undef-exception.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Example triggering an undefined exception. - -#![no_std] -#![no_main] - -use core::sync::atomic::AtomicU32; - -// pull in our start-up code -use versatileab as _; - -use semihosting::println; - -/// The entry-point to the Rust application. -/// -/// It is called by the start-up. -#[no_mangle] -pub extern "C" fn kmain() -> ! { - main(); -} - -static COUNTER: AtomicU32 = AtomicU32::new(0); - -/// The main function of our Rust application. -#[export_name = "main"] -fn main() -> ! { - println!("Hello, this is an undefined exception example"); - unsafe { - core::arch::asm!("udf #0"); - } - unreachable!("should never be here!"); -} - -#[no_mangle] -unsafe extern "C" fn _undefined_handler(_faulting_instruction: u32) { - println!("undefined exception occurred"); - // For the first iteration, we do a regular exception return, which should - // trigger the exception again. - let counter_val = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1; - if counter_val == 2 { - semihosting::process::exit(0); - } -} diff --git a/tests.sh b/tests.sh index 7fe2653..19d4c13 100755 --- a/tests.sh +++ b/tests.sh @@ -34,19 +34,25 @@ my_diff() { } # armv7r-none-eabi tests -for binary in hello registers svc; do +for bin_path in $(ls examples/versatileab/src/bin/*.rs); do + filename=${bin_path##*/} + binary=${filename%.rs} cargo run ${versatile_ab_cargo} --target=armv7r-none-eabi --bin $binary | tee ./target/$binary-armv7r-none-eabi.out my_diff ./examples/versatileab/reference/$binary-armv7r-none-eabi.out ./target/$binary-armv7r-none-eabi.out || fail $binary "armv7r-none-eabi" done # armv7r-none-eabihf tests -for binary in hello registers svc undef-exception prefetch-exception abt-exception; do +for bin_path in $(ls examples/versatileab/src/bin/*.rs); do + filename=${bin_path##*/} + binary=${filename%.rs} cargo run ${versatile_ab_cargo} --target=armv7r-none-eabihf --bin $binary | tee ./target/$binary-armv7r-none-eabihf.out my_diff ./examples/versatileab/reference/$binary-armv7r-none-eabihf.out ./target/$binary-armv7r-none-eabihf.out || fail $binary "armv7r-none-eabihf" done # armv7a-none-eabi tests -for binary in hello registers svc undef-exception prefetch-exception abt-exception; do +for bin_path in $(ls examples/versatileab/src/bin/*.rs); do + filename=${bin_path##*/} + binary=${filename%.rs} cargo run ${versatile_ab_cargo} --target=armv7a-none-eabi --bin $binary | tee ./target/$binary-armv7a-none-eabi.out my_diff ./examples/versatileab/reference/$binary-armv7a-none-eabi.out ./target/$binary-armv7a-none-eabi.out || fail $binary "armv7a-none-eabi" done @@ -55,9 +61,11 @@ done # Ubuntu 24.04 supplies QEMU 8, which doesn't support the machine we have configured for this target if qemu-system-arm --version | grep "version 9"; then # armv8r-none-eabihf tests - for binary in hello registers svc gic generic_timer; do + for bin_path in $(ls examples/mps3-an536/src/bin/*.rs); do + filename=${bin_path##*/} + binary=${filename%.rs} cargo +nightly run ${mps3_an536_cargo} --target=armv8r-none-eabihf --bin $binary --features=gic -Zbuild-std=core | tee ./target/$binary-armv8r-none-eabihf.out - my_diff ./examples/mps3-an536/reference/$binary-armv8r-none-eabihf.out ./target/$binary-armv8r-none-eabihf.out || fail $binary "armv8r-none-eabihf" + my_diff ./examples/mps3-an536/reference/$binary-armv8r-none-eabihf.out ./target/$binary-armv8r-none-eabihf.out || fail $binary "armv8r-none-eabihf" done fi