Expand description
§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. Most Cortex-A based systems will require chip specific start-up code, so the start-up method can be overridden.
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.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.
If your processor starts in Hyp mode, this runtime will be transfer it to System mode. If you wish to write a hypervisor, you will need to replace this library with something more advanced.
We assume that a set of symbols exist, either for constants or for C compatible functions or for naked raw-assembly functions. They are described in the next three sections.
§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.__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.
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
.
The stacks look like:
+------------------+ <----_stack_top
| UND Stack | } _und_stack_size bytes
+------------------+
| SVC Stack | } _svc_stack_size bytes
+------------------+
| ABT Stack | } _abt_stack_size bytes
+------------------+
| IRQ Stack | } _irq_stack_size bytes
+------------------+
| FIQ Stack | } _fiq_stack_size bytes
+------------------+
| SYS Stack | } No specific size
+------------------+
§C-Compatible Functions
§Main Function
The symbol kmain
should be an extern "C"
function. It is called in SYS
mode after all the global variables have been initialised. There is no
default - this function is mandatory.
#[unsafe(no_mangle)]
extern "C" fn kmain() -> ! {
loop { }
}
You can also create a ‘kmain’ function by using the #[entry]
attribute on
a normal Rust function.
use cortex_a_rt::entry;
#[entry]
fn my_main() -> ! {
loop { }
}
§Undefined Handler
The symbol _undefined_handler
should be an extern "C"
function. It is
called in UND mode when an Undefined Instruction Exception occurs.
Our linker script PROVIDEs a default _undefined_handler
symbol which is an
alias for the _default_handler
function. You can override it by defining
your own _undefined_handler
function, like:
/// Does not return
#[unsafe(no_mangle)]
extern "C" fn _undefined_handler(addr: usize) -> ! {
loop { }
}
or:
/// Execution will continue from the returned address.
///
/// Return `addr` to go back and execute the faulting instruction again.
#[unsafe(no_mangle)]
unsafe extern "C" fn _undefined_handler(addr: usize) -> usize {
// do stuff here, then return to the address *after* the one
// that failed
addr + 4
}
You can create a _undefined_handler
function by using the
#[exception(Undefined)]
attribute on a Rust function with the appropriate
arguments and return type.
use cortex_a_rt::exception;
#[exception(Undefined)]
fn my_handler(addr: usize) -> ! {
loop { }
}
or:
use cortex_a_rt::exception;
#[exception(Undefined)]
unsafe fn my_handler(addr: usize) -> usize {
// do stuff here, then return the address to return to
addr + 4
}
§Supervisor Call Handler
The symbol _svc_handler
should be an extern "C"
function. It is called
in SVC mode when a Supervisor Call Exception occurs.
Returning from this function will cause execution to resume at the function
the triggered the exception, immediately after the SVC instruction. You
cannot control where execution resumes. The function is passed the literal
integer argument to the svc
instruction, which is extracted from the
machine code for you by the default assembly trampoline.
Our linker script PROVIDEs a default _svc_handler
symbol which is an alias
for the _default_handler
function. You can override it by defining your
own _svc_handler
function, like:
#[unsafe(no_mangle)]
extern "C" fn _svc_handler(svc: u32) {
// do stuff here
}
You can also create a _svc_handler
function by using the
#[exception(SupervisorCall)]
attribute on a normal Rust function.
use cortex_a_rt::exception;
#[exception(SupervisorCall)]
fn my_svc_handler(arg: u32) {
// do stuff here
}
§Prefetch Abort Handler
The symbol _prefetch_abort_handler
should be an extern "C"
function. It
is called in ABT mode when a Prefetch Abort Exception occurs.
Our linker script PROVIDEs a default _prefetch_abort_handler
symbol which
is an alias for the _default_handler
function. You can override it by
defining your own _undefined_handler
function.
This function takes the address of faulting instruction, and can either not return:
#[unsafe(no_mangle)]
extern "C" fn _prefetch_abort_handler(addr: usize) -> ! {
loop { }
}
Or it can return an address where execution should resume after the Exception handler is complete (which is unsafe):
#[unsafe(no_mangle)]
unsafe extern "C" fn _prefetch_abort_handler(addr: usize) -> usize {
// do stuff, then go back to the instruction after the one that failed
addr + 4
}
You can create a _prefetch_abort_handler
function by using the
#[exception(PrefetchAbort)]
macro on a Rust function with the appropriate
arguments and return type.
use cortex_a_rt::exception;
#[exception(PrefetchAbort)]
fn my_handler(addr: usize) -> ! {
loop { }
}
or:
use cortex_a_rt::exception;
#[exception(PrefetchAbort)]
unsafe fn my_handler(addr: usize) -> usize {
// do stuff, then go back to the instruction after the one that failed
addr + 4
}
§Data Abort Handler
The symbol _data_abort_handler
should be an extern "C"
function. It is
called in ABT mode when a Data Abort Exception occurs.
Our linker script PROVIDEs a default _data_abort_handler
symbol which is
an alias for the _default_handler
function. You can override it by
defining your own _undefined_handler
function.
This function takes the address of faulting instruction, and can either not return:
#[unsafe(no_mangle)]
extern "C" fn _data_abort_handler(addr: usize) -> ! {
loop { }
}
Or it can return an address where execution should resume after the Exception handler is complete (which is unsafe):
#[unsafe(no_mangle)]
unsafe extern "C" fn _data_abort_handler(addr: usize) -> usize {
// do stuff, then go back to the instruction after the one that failed
addr + 4
}
You can create a _data_abort_handler
function by using the
#[exception(DataAbort)]
macro on a Rust function with the appropriate
arguments and return type.
use cortex_a_rt::exception;
#[exception(DataAbort)]
fn my_handler(addr: usize) -> ! {
loop { }
}
or:
use cortex_a_rt::exception;
#[exception(DataAbort)]
unsafe fn my_handler(addr: usize) -> usize {
// do stuff, then go back to the instruction after the one that failed
addr + 4
}
§IRQ Handler
The symbol _irq_handler
should be an extern "C"
function. It is called
in SYS mode (not IRQ mode!) when an Interrupt occurs.
Returning from this function will cause execution to resume at wherever it was interrupted. You cannot control where execution resumes.
This function is entered with interrupts masked, but you may unmask (i.e. enable) interrupts inside this function if desired. You will probably want to talk to your interrupt controller first, otherwise you’ll just keep re-entering this interrupt handler recursively until you stack overflow.
Our linker script PROVIDEs a default _irq_handler
symbol which is an alias
for _default_handler
. You can override it by defining your own
_irq_handler
function.
Expected prototype:
#[unsafe(no_mangle)]
extern "C" fn _irq_handler() {
// 1. Talk to interrupt controller
// 2. Handle interrupt
// 3. Clear interrupt
}
You can also create a _irq_handler
function by using the #[irq]
attribute on a normal Rust function.
use cortex_a_rt::irq;
#[irq]
fn my_irq_handler() {
// 1. Talk to interrupt controller
// 2. Handle interrupt
// 3. Clear interrupt
}
§ASM functions
These are the naked ‘raw’ assembly functions the run-time requires:
-
_start
- a Reset handler. Our linker script PROVIDEs a default function at_default_start
but you can override it. The provided default start function will initialise all global variables and then callkmain
in SYS mode. 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. The provided default handler will call_undefined_handler
in UND mode, saving state as required. -
_asm_svc_handler
- a naked function to call when an Supervisor Call (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
in SVC mode, saving state as required. -
_asm_prefetch_abort_handler
- a naked function to call when a Prefetch Abort Exception occurs. Our linker script PROVIDEs a default function at_asm_default_prefetch_abort_handler
but you can override it. The provided default handler will call_prefetch_abort_handler
, saving state as required. Note that Prefetch Abort Exceptions are handled in Abort Mode (ABT), Monitor Mode (MON) or Hyp Mode (HYP), depending on CPU configuration. -
_asm_data_abort_handler
- a naked function to call when a Data Abort Exception occurs. Our linker script PROVIDEs a default function at_asm_default_data_abort_handler
but you can override it. The provided default handler will call_data_abort_handler
in ABT mode, 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
in SYS mode (not IRQ mode), 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.
§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 anextern "C"
function calledkmain
._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_abort_handler
- assembly language trampoline that calls_prefetch_abort_handler
_asm_default_data_abort_handler
- assembly language trampoline that calls_data_abort_handler
_asm_default_irq_handler
- assembly language trampoline that calls_irq_handler
_asm_default_fiq_handler
- an FIQ handler that just spins_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.
§Examples
You can find example code using QEMU inside the project repository
Functions§
- _default_
handler - Our default exception handler.