diff --git a/cortex-a-rt/Cargo.toml b/cortex-a-rt/Cargo.toml index 96e41bf..40729f6 100644 --- a/cortex-a-rt/Cargo.toml +++ b/cortex-a-rt/Cargo.toml @@ -16,7 +16,6 @@ version = "0.1.0" [dependencies] cortex-ar = {version = "0.1.0", path = "../cortex-ar"} -semihosting = {version = "0.1.18", features = ["stdio"]} [features] # Enable the FPU on start-up, even on a soft-float EABI target diff --git a/cortex-a-rt/link.x b/cortex-a-rt/link.x index 3bef5c9..0ba73fc 100644 --- a/cortex-a-rt/link.x +++ b/cortex-a-rt/link.x @@ -92,10 +92,16 @@ ASSERT(_abt_stack_size % 8 == 0, "ERROR(cortex-a-rt): size of ABT stack is not 8 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"); -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(_asm_undefined_handler =_asm_default_undefined_handler); +PROVIDE(_asm_prefetch_handler =_asm_default_prefetch_handler); +PROVIDE(_asm_abort_handler =_asm_default_abort_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(_prefetch_handler =_default_handler); PROVIDE(_irq_handler =_default_handler); PROVIDE(_svc_handler =_default_handler); PROVIDE(_start =_default_start); diff --git a/cortex-a-rt/src/lib.rs b/cortex-a-rt/src/lib.rs index 8846d1d..4a6cc09 100644 --- a/cortex-a-rt/src/lib.rs +++ b/cortex-a-rt/src/lib.rs @@ -1,4 +1,4 @@ -//! Run-time support for Arm Cortex-A +//! # Run-time support for Arm Cortex-A //! //! 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. @@ -22,47 +22,116 @@ //! //! We assume the following global symbols exist: //! -//! * `__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. +//! ### Constants +//! //! * `_stack_top` - the address of the top of some region of RAM that we can //! use as stack space, with eight-byte alignment. Our linker script PROVIDEs //! a default pointing at the top of RAM. +//! * `__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 +//! aligned. //! * `_fiq_stack_size` - the number of bytes to be reserved for stack space //! when in FIQ mode; must be a multiple of 8. //! * `_irq_stack_size` - the number of bytes to be reserved for stack space //! when in FIQ mode; must be a multiple of 8. //! * `_svc_stack_size` - the number of bytes to be reserved for stack space -//! when in SVC mode; must be a multiple of 8.F +//! when in SVC mode; must be a multiple of 8. +//! * `__sdata` - the start of initialised data in RAM. Must be 4-byte aligned. +//! * `__edata` - the end of initialised data in RAM. Must be 4-byte aligned. +//! * `__sidata` - the start of the initialisation values for data, in read-only +//! memory. Must be 4-byte aligned. +//! +//! ### Functions +//! +//! * `boot_core` - the `extern "C"` entry point to your application. The CPU ID +//! will be passed as the first argument to this function. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn boot_core(cpu_id: u32) -> !; +//! ``` +//! //! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _svc_handler(svc: u32); +//! ``` +//! //! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _undefined_handler(faulting_instruction: u32); +//! ``` +//! +//! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _abort_handler(faulting_instruction: u32); +//! ``` +//! +//! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _prefetch_handler(faulting_instruction: u32); +//! ``` +//! +//! ### 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. //! * `_asm_undefined_handler` - a naked function to call when an Undefined //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_handler` but you can override it. +//! `_asm_default_undefined_handler` but you can override it. //! * `_asm_prefetch_handler` - a naked function to call when an Prefetch //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_handler` but you can override it. +//! `_asm_default_prefetch_handler` but you can override it. The provided default +//! handler will perform an exception return to the faulting address. //! * `_asm_abort_handler` - a naked function to call when an Abort Exception //! occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_handler` but you can override it. -//! * `boot_core` - the `extern "C"` entry point to your application. The CPU ID -//! will be passed as the first argument to this fumction. -//! * `__sdata` - the start of initialised data in RAM. Must be 4-byte aligned. -//! * `__edata` - the end of initialised data in RAM. Must be 4-byte aligned. -//! * `__sidata` - the start of the initialisation values for data, in read-only -//! memory. Must be 4-byte aligned. -//! * `__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 -//! aligned. +//! `_asm_default_abort_handler` but you can override it. The provided default handler +//! will perform an exception return to the faulting address. //! //! 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 @@ -95,10 +164,18 @@ //! 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) #![no_std] -use cortex_ar::register::{cpsr::ProcessorMode, Cpsr}; +use cortex_ar::{ + asm::nop, + register::{cpsr::ProcessorMode, Cpsr}, +}; /// Our default exception handler. /// @@ -106,8 +183,9 @@ use cortex_ar::register::{cpsr::ProcessorMode, Cpsr}; /// file hasn't been over-ridden. #[no_mangle] pub extern "C" fn _default_handler() { - semihosting::eprintln!("Unhandled exception!"); - semihosting::process::abort(); + loop { + nop(); + } } // The Interrupt Vector Table, and some default assembly-language handler. @@ -319,6 +397,7 @@ core::arch::global_asm!( rfefd sp! .size _asm_svc_handler, . - _asm_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();` @@ -337,9 +416,98 @@ core::arch::global_asm!( r#" rfefd sp! .size _asm_irq_handler, . - _asm_irq_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: + // state save from compiled code + srsfd sp!, {und_mode} + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _undefined_handler + "#, + 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 + + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _prefetch_handler();` + .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 + // state save from compiled code + srsfd sp!, {abt_mode} + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _prefetch_handler + "#, + restore_context!(), + r#" + // Return to the failing instruction which is the recommended approach by ARM. + rfefd sp! + .size _asm_default_prefetch_handler, . - _asm_default_prefetch_handler + + + // Called from the vector table when we have an undefined exception. + // 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} + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _abort_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 "#, svc_mode = const ProcessorMode::Svc as u8, irq_mode = const ProcessorMode::Irq as u8, + und_mode = const ProcessorMode::Und as u8, + abt_mode = const ProcessorMode::Abt as u8, t_bit = const { Cpsr::new_with_raw_value(0) .with_t(true) diff --git a/cortex-ar/Cargo.toml b/cortex-ar/Cargo.toml index 527e23e..b2ed0b1 100644 --- a/cortex-ar/Cargo.toml +++ b/cortex-ar/Cargo.toml @@ -27,6 +27,7 @@ version = "0.1.0" [dependencies] arbitrary-int = "1.3.0" bitbybit = "1.3.3" +num_enum = { version = "0.7", default-features = false } critical-section = {version = "1.2.0", features = ["restore-state-bool"], optional = true} defmt = {version = "0.3", optional = true} diff --git a/cortex-ar/src/register/dfar.rs b/cortex-ar/src/register/dfar.rs index 34f80ad..e07a1e4 100644 --- a/cortex-ar/src/register/dfar.rs +++ b/cortex-ar/src/register/dfar.rs @@ -3,6 +3,7 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// DFAR (*Data Fault Address Register*) +#[derive(Debug)] pub struct Dfar(pub u32); impl SysReg for Dfar { const CP: u32 = 15; diff --git a/cortex-ar/src/register/dfsr.rs b/cortex-ar/src/register/dfsr.rs index 92302fa..88ca604 100644 --- a/cortex-ar/src/register/dfsr.rs +++ b/cortex-ar/src/register/dfsr.rs @@ -1,9 +1,52 @@ //! Code for managing DFSR (*Data Fault Status Register*) +use arbitrary_int::{u4, u5, Number}; + use crate::register::{SysReg, SysRegRead, SysRegWrite}; +use super::ifsr::FsrStatus; + +#[derive(Debug)] +#[repr(u8)] +pub enum DfsrStatus { + AlignmentFault = 0b00001, + FaultOnInstructionCacheMaintenance = 0b00100, + AsyncExternalAbort = 0b10110, + AsyncParityErrorOnMemAccess = 0b11000, + CommonFsr(FsrStatus), +} + +impl TryFrom for DfsrStatus { + type Error = u8; + fn try_from(value: u8) -> Result { + match value { + 0b00001 => Ok(DfsrStatus::AlignmentFault), + 0b00100 => Ok(DfsrStatus::FaultOnInstructionCacheMaintenance), + 0b10110 => Ok(DfsrStatus::AsyncExternalAbort), + 0b11000 => Ok(DfsrStatus::AsyncParityErrorOnMemAccess), + _ => FsrStatus::try_from(value) + .map(DfsrStatus::CommonFsr) + .map_err(|_| value), + } + } +} + /// DFSR (*Data Fault Status Register*) -pub struct Dfsr(pub u32); +#[bitbybit::bitfield(u32)] +pub struct Dfsr { + /// External abort qualifier + #[bit(12, rw)] + ext: bool, + /// Write Not Read bit. + #[bit(11, rw)] + wnr: bool, + #[bits(4..=7, rw)] + domain: u4, + /// Status bitfield. + #[bits([0..=3, 10], rw)] + status_raw: u5, +} + impl SysReg for Dfsr { const CP: u32 = 15; const CRN: u32 = 5; @@ -13,10 +56,15 @@ impl SysReg for Dfsr { } impl crate::register::SysRegRead for Dfsr {} impl Dfsr { + pub fn status(&self) -> Result { + let status = self.status_raw().as_u8(); + DfsrStatus::try_from(status).map_err(|_| status) + } + #[inline] /// Reads DFSR (*Data Fault Status Register*) pub fn read() -> Dfsr { - unsafe { Self(::read_raw()) } + unsafe { Self::new_with_raw_value(::read_raw()) } } } impl crate::register::SysRegWrite for Dfsr {} @@ -29,7 +77,20 @@ impl Dfsr { /// Ensure that this value is appropriate for this register pub unsafe fn write(value: Self) { unsafe { - ::write_raw(value.0); + ::write_raw(value.raw_value()); } } } + +impl core::fmt::Debug for Dfsr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "DFSR {{ ext={} wnr={} Domain={:#06b} Status={:#07b} }}", + self.ext(), + self.wnr(), + self.domain(), + self.status_raw() + ) + } +} diff --git a/cortex-ar/src/register/ifar.rs b/cortex-ar/src/register/ifar.rs index e17f1d7..66bc59e 100644 --- a/cortex-ar/src/register/ifar.rs +++ b/cortex-ar/src/register/ifar.rs @@ -3,6 +3,7 @@ use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// IFAR (*Instruction Fault Address Register*) +#[derive(Debug)] pub struct Ifar(pub u32); impl SysReg for Ifar { const CP: u32 = 15; diff --git a/cortex-ar/src/register/ifsr.rs b/cortex-ar/src/register/ifsr.rs index 68fb374..5dc9e25 100644 --- a/cortex-ar/src/register/ifsr.rs +++ b/cortex-ar/src/register/ifsr.rs @@ -1,9 +1,53 @@ //! Code for managing IFSR (*Instruction Fault Status Register*) +use arbitrary_int::{u4, u5, Number}; + use crate::register::{SysReg, SysRegRead, SysRegWrite}; /// IFSR (*Instruction Fault Status Register*) -pub struct Ifsr(pub u32); +#[bitbybit::bitfield(u32)] +pub struct Ifsr { + /// External abort qualifier + #[bit(12, rw)] + ext: bool, + #[bits(4..=7, rw)] + domain: u4, + /// Status bitfield. + #[bits([0..=3, 10], rw)] + status_raw: u5, +} + +/// Fault status register enumeration for IFSR, which is also part of the DFSR +#[derive(Debug, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum FsrStatus { + SyncExtAbortOnTranslationTableWalkFirstLevel = 0b01100, + SyncExtAbortOnTranslationTableWalkSecondLevel = 0b01110, + SyncParErrorOnTranslationTableWalkFirstLevel = 0b11100, + SyncParErrorOnTranslationTableWalkSecondLevel = 0b11110, + TranslationFaultFirstLevel = 0b00101, + TranslationFaultSecondLevel = 0b00111, + AccessFlagFaultFirstLevel = 0b00011, + AccessFlagFaultSecondLevel = 0b00110, + DomainFaultFirstLevel = 0b01001, + DomainFaultSecondLevel = 0b01011, + PermissionFaultFirstLevel = 0b01101, + PermissionFaultSecondLevel = 0b01111, + DebugEvent = 0b00010, + SyncExtAbort = 0b01000, + TlbConflictAbort = 0b10000, + Lockdown = 0b10100, + CoprocessorAbort = 0b11010, + SyncParErrorOnMemAccess = 0b11001, +} + +impl Ifsr { + pub fn status(&self) -> Result { + let status = self.status_raw().as_u8(); + FsrStatus::try_from(status).map_err(|_| status) + } +} + impl SysReg for Ifsr { const CP: u32 = 15; const CRN: u32 = 5; @@ -16,7 +60,7 @@ impl Ifsr { #[inline] /// Reads IFSR (*Instruction Fault Status Register*) pub fn read() -> Ifsr { - unsafe { Self(::read_raw()) } + unsafe { Self::new_with_raw_value(::read_raw()) } } } impl crate::register::SysRegWrite for Ifsr {} @@ -29,7 +73,19 @@ impl Ifsr { /// Ensure that this value is appropriate for this register pub unsafe fn write(value: Self) { unsafe { - ::write_raw(value.0); + ::write_raw(value.raw_value()); } } } + +impl core::fmt::Debug for Ifsr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "IFSR {{ ext={} Domain={:#06b} Status={:#07b} }}", + self.ext(), + self.domain(), + self.status_raw() + ) + } +} diff --git a/cortex-r-rt/CHANGELOG.md b/cortex-r-rt/CHANGELOG.md index c676239..7deeca6 100644 --- a/cortex-r-rt/CHANGELOG.md +++ b/cortex-r-rt/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Added - Added ABT und UND mode stack setup. +- Default exception handlers for undefined, prefetch and data abort exceptions + +## Changed + +- Default Rust exception handler is now an empty permanent loop instead of a semihosting exit. ## [v0.1.0] diff --git a/cortex-r-rt/link.x b/cortex-r-rt/link.x index a45c0b0..6e0b553 100644 --- a/cortex-r-rt/link.x +++ b/cortex-r-rt/link.x @@ -96,6 +96,10 @@ PROVIDE(_asm_undefined_handler =_asm_default_handler); PROVIDE(_asm_prefetch_handler =_asm_default_handler); PROVIDE(_asm_abort_handler =_asm_default_handler); PROVIDE(_asm_fiq_handler =_asm_default_fiq_handler); + +PROVIDE(_undefined_handler =_default_handler); +PROVIDE(_abort_handler =_default_handler); +PROVIDE(_prefetch_handler =_default_handler); PROVIDE(_irq_handler =_default_handler); PROVIDE(_svc_handler =_default_handler); PROVIDE(_start =_default_start); diff --git a/cortex-r-rt/src/lib.rs b/cortex-r-rt/src/lib.rs index 899fdf5..3312eda 100644 --- a/cortex-r-rt/src/lib.rs +++ b/cortex-r-rt/src/lib.rs @@ -1,4 +1,4 @@ -//! Run-time support for Arm Cortex-R +//! # Run-time support for Arm Cortex-R //! //! This library implements a simple Arm vector table, suitable for getting into //! a Rust application running in System Mode. @@ -12,44 +12,115 @@ //! //! We assume the following global symbols exist: //! -//! * `__start` - a Reset handler. Our linker script PROVIDEs a default function -//! at `_default_start` but you can override it. +//! ### Constants +//! //! * `_stack_top` - the address of the top of some region of RAM that we can //! use as stack space, with eight-byte alignment. Our linker script PROVIDEs //! a default pointing at the top of RAM. +//! * `__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 +//! aligned. //! * `_fiq_stack_size` - the number of bytes to be reserved for stack space //! when in FIQ mode; must be a multiple of 8. //! * `_irq_stack_size` - the number of bytes to be reserved for stack space //! when in FIQ mode; must be a multiple of 8. //! * `_svc_stack_size` - the number of bytes to be reserved for stack space -//! when in SVC mode; must be a multiple of 8.F +//! when in SVC mode; must be a multiple of 8. +//! * `__sdata` - the start of initialised data in RAM. Must be 4-byte aligned. +//! * `__edata` - the end of initialised data in RAM. Must be 4-byte aligned. +//! * `__sidata` - the start of the initialisation values for data, in read-only +//! memory. Must be 4-byte aligned. +//! +//! ### Functions +//! +//! * `kmain` - the `extern "C"` entry point to your application. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn kmain() -> !; +//! ``` +//! //! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _svc_handler(svc: u32); +//! ``` +//! //! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _undefined_handler(faulting_instruction: u32); +//! ``` +//! +//! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _abort_handler(faulting_instruction: u32); +//! ``` +//! +//! * `_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. +//! +//! Expected prototype: +//! +//! ```rust +//! #[unsafe(no_mangle)] +//! extern "C" fn _prefetch_handler(faulting_instruction: u32); +//! ``` +//! +//! ### 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. //! * `_asm_undefined_handler` - a naked function to call when an Undefined //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_handler` but you can override it. +//! `_asm_default_undefined_handler` but you can override it. //! * `_asm_prefetch_handler` - a naked function to call when an Prefetch //! Exception occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_handler` but you can override it. +//! `_asm_default_prefetch_handler` but you can override it. The provided default +//! handler will perform an exception return to the faulting address. //! * `_asm_abort_handler` - a naked function to call when an Abort Exception //! occurs. Our linker script PROVIDEs a default function at -//! `_asm_default_handler` but you can override it. -//! * `kmain` - the `extern "C"` entry point to your application. -//! * `__sdata` - the start of initialised data in RAM. Must be 4-byte aligned. -//! * `__edata` - the end of initialised data in RAM. Must be 4-byte aligned. -//! * `__sidata` - the start of the initialisation values for data, in read-only -//! memory. Must be 4-byte aligned. -//! * `__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 -//! aligned. +//! `_asm_default_abort_handler` but you can override it. The provided default handler +//! will perform an exception return to the faulting address. //! //! 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 @@ -82,10 +153,18 @@ //! 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) #![no_std] -use cortex_ar::register::{cpsr::ProcessorMode, Cpsr}; +use cortex_ar::{ + asm::nop, + register::{cpsr::ProcessorMode, Cpsr}, +}; #[cfg(arm_architecture = "v8-r")] use cortex_ar::register::Hactlr; @@ -96,8 +175,9 @@ use cortex_ar::register::Hactlr; /// file hasn't been over-ridden. #[no_mangle] pub extern "C" fn _default_handler() { - semihosting::eprintln!("Unhandled exception!"); - semihosting::process::abort(); + loop { + nop(); + } } // The Interrupt Vector Table, and some default assembly-language handler. @@ -273,9 +353,98 @@ core::arch::global_asm!( r#" rfefd sp! .size _asm_irq_handler, . - _asm_irq_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: + // state save from compiled code + srsfd sp!, {und_mode} + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _undefined_handler + "#, + 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 + + + // Called from the vector table when we have an undefined exception. + // Saves state and calls a C-compatible handler like + // `extern "C" fn _prefetch_handler();` + .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 + // state save from compiled code + srsfd sp!, {abt_mode} + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _prefetch_handler + "#, + restore_context!(), + r#" + // Return to the failing instruction which is the recommended approach by ARM. + rfefd sp! + .size _asm_default_prefetch_handler, . - _asm_default_prefetch_handler + + + // Called from the vector table when we have an undefined exception. + // 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} + "#, + save_context!(), + r#" + // Pass the faulting instruction address to the handler. + mov r0, lr + // call C handler + bl _abort_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 "#, svc_mode = const ProcessorMode::Svc as u8, irq_mode = const ProcessorMode::Irq as u8, + und_mode = const ProcessorMode::Und as u8, + abt_mode = const ProcessorMode::Abt as u8, t_bit = const { Cpsr::new_with_raw_value(0) .with_t(true) diff --git a/examples/versatileab/reference/abt-exception-armv7a-none-eabi.out b/examples/versatileab/reference/abt-exception-armv7a-none-eabi.out new file mode 100644 index 0000000..703128d --- /dev/null +++ b/examples/versatileab/reference/abt-exception-armv7a-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/abt-exception-armv7r-none-eabihf.out b/examples/versatileab/reference/abt-exception-armv7r-none-eabihf.out new file mode 100644 index 0000000..703128d --- /dev/null +++ b/examples/versatileab/reference/abt-exception-armv7r-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=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-armv7a-none-eabi.out new file mode 100644 index 0000000..9744364 --- /dev/null +++ b/examples/versatileab/reference/prefetch-exception-armv7a-none-eabi.out @@ -0,0 +1,9 @@ +Hello, this is an 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) +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) diff --git a/examples/versatileab/reference/prefetch-exception-armv7r-none-eabihf.out b/examples/versatileab/reference/prefetch-exception-armv7r-none-eabihf.out new file mode 100644 index 0000000..9744364 --- /dev/null +++ b/examples/versatileab/reference/prefetch-exception-armv7r-none-eabihf.out @@ -0,0 +1,9 @@ +Hello, this is an 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) +prefetch abort occurred +IFSR (Fault Status Register): IFSR { ext=false Domain=0b0000 Status=0b00010 } +IFSR Status: Ok(DebugEvent) +IFAR (Faulting Address Register): Ifar(0) diff --git a/examples/versatileab/reference/undef-exception-armv7a-none-eabi.out b/examples/versatileab/reference/undef-exception-armv7a-none-eabi.out new file mode 100644 index 0000000..b149c88 --- /dev/null +++ b/examples/versatileab/reference/undef-exception-armv7a-none-eabi.out @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..b149c88 --- /dev/null +++ b/examples/versatileab/reference/undef-exception-armv7r-none-eabihf.out @@ -0,0 +1,3 @@ +Hello, this is an undefined exception example +undefined exception occurred +undefined exception occurred diff --git a/examples/versatileab/src/bin/abt-exception.rs b/examples/versatileab/src/bin/abt-exception.rs new file mode 100644 index 0000000..864bcd5 --- /dev/null +++ b/examples/versatileab/src/bin/abt-exception.rs @@ -0,0 +1,80 @@ +//! 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 versatileab as _; + +use semihosting::println; + +versatileab::entry_point!(); + +static COUNTER: AtomicU32 = AtomicU32::new(0); + +/// 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(_faulting_instruction: u32) { + panic!("unexpected undefined exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _prefetch_handler(_faulting_instruction: u32) { + panic!("unexpected prefetch exception"); +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn _abort_handler(_faulting_instruction: u32) { + println!("data abort occurred"); + let dfsr = Dfsr::read(); + println!("DFSR (Fault Status Register): {:?}", dfsr); + println!("DFSR Status: {:?}", dfsr.status()); + // If this is not disabled, reading DFAR will trigger an alignment fault on Armv8-R, leading + // to a loop. + disable_alignment_check(); + 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. + let counter_val = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed) + 1; + if counter_val == 2 { + semihosting::process::exit(0); + } +} diff --git a/examples/versatileab/src/bin/prefetch-exception.rs b/examples/versatileab/src/bin/prefetch-exception.rs new file mode 100644 index 0000000..9b1866f --- /dev/null +++ b/examples/versatileab/src/bin/prefetch-exception.rs @@ -0,0 +1,56 @@ +//! 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; + +versatileab::entry_point!(); + +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.rs b/examples/versatileab/src/bin/undef-exception.rs new file mode 100644 index 0000000..b98eca8 --- /dev/null +++ b/examples/versatileab/src/bin/undef-exception.rs @@ -0,0 +1,36 @@ +//! 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; + +versatileab::entry_point!(); + +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 dc0d8bc..6b4ba05 100755 --- a/tests.sh +++ b/tests.sh @@ -27,13 +27,13 @@ for binary in hello registers svc; do done # armv7r-none-eabihf tests -for binary in hello registers svc; do +for binary in hello registers svc undef-exception prefetch-exception abt-exception; do cargo run ${versatile_ab_cargo} --target=armv7r-none-eabihf --bin $binary | tee ./target/$binary-armv7r-none-eabihf.out 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; do +for binary in hello registers svc undef-exception prefetch-exception abt-exception; do cargo run ${versatile_ab_cargo} --target=armv7a-none-eabi --bin $binary | tee ./target/$binary-armv7a-none-eabi.out diff ./examples/versatileab/reference/$binary-armv7a-none-eabi.out ./target/$binary-armv7a-none-eabi.out || fail $binary "armv7a-none-eabi" done