Expand description
§Run-time support for AArch32 Processors
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 AArch32 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
-
eabi-fpu: Enables the FPU, even if you selected a soft-float ABI target. -
fpu-d32: Make the interrupt context store routines save the upper double-precision registers.If your program is using all 32 double-precision registers (e.g. if you have set the
+d32target feature) then you need to enable this option otherwise important FPU state may be lost when an exception occurs.
§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
__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; will be padded to a multiple of 8._irq_stack_size- the number of bytes to be reserved for stack space when in FIQ mode; will be padded to a multiple of 8._svc_stack_size- the number of bytes to be reserved for stack space when in SVC mode; will be padded to a multiple of 8._und_stack_size- the number of bytes to be reserved for stack space when in Undefined mode; will be padded to a multiple of 8._abt_stack_size- the number of bytes to be reserved for stack space when in Abort mode; will be padded to a multiple of 8._hyp_stack_size- the number of bytes to be reserved for stack space when in Hyp mode; will be padded to a multiple of 8._sys_stack_size- the number of bytes to be reserved for stack space when in System mode; will be padded to 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.
§Stacks
Stacks are located in .stacks section which is mapped to the STACKS
memory region. Per default, the stacks are pushed to the end of the STACKS
by a filler section.
The stacks look like:
+------------------+ <----_stack_top equals ORIGIN(STACKS) + LENGTH(STACKS)
| HYP Stack | } _hyp_stack_size bytes (Armv8-R only)
+------------------+
| 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 | } _sys_stack_size bytes
+------------------+
| filler section | } calculated so that _stack_top equals end of STACKS
+------------------+ <----either ORIGIN(STACKS) or the end of previous
section located in STACKS or its alias.Our linker script PROVIDEs a symbol _pack_stacks. By setting this symbol
to 0 in memory.x, the stacks can be moved to the beginning of the STACKS
region or the end of the previous section located in STACKS or its alias.
§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. The function will be renamed in such a way that the
start-up assembly code can find it, but normal Rust code cannot. Therefore
you can be assured that the function will only be called once (unless someone
resorts to unsafe Rust to import the kmain symbol as an extern "C" fn).
use aarch32_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 aarch32_rt::exception;
#[exception(Undefined)]
fn my_handler(addr: usize) -> ! {
loop { }
}or:
use aarch32_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 an 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 aarch32_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 aarch32_rt::exception;
#[exception(PrefetchAbort)]
fn my_handler(addr: usize) -> ! {
loop { }
}or:
use aarch32_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 aarch32_rt::exception;
#[exception(DataAbort)]
fn my_handler(addr: usize) -> ! {
loop { }
}or:
use aarch32_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 aarch32_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_startbut you can override it. The provided default start function will initialise all global variables and then callkmainin SYS mode. Some SoCs require a chip specific startup for tasks like MPU initialization or chip specific initialization routines, so if our start-up routine doesn’t work for you, supply your own_startfunction (but feel free to call our_default_startas 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_handlerbut you can override it. The provided default handler will call_undefined_handlerin 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_handlerbut you can override it. The provided default handler will call_svc_handlerin 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_handlerbut 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_handlerbut you can override it. The provided default handler will call_data_abort_handlerin 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_handlerbut you can override it. The provided default handler will call_irq_handlerin 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_handlerbut 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._init_segments- initialises.bssand.data_stack_setup- initialises UND, SVC, ABT, IRQ, FIQ and SYS stacks from the address given inr0. Deprecated, use_stack_setup_preallocatedinstead_stack_setup_preallocated- initialises UND, SVC, ABT, IRQ, FIQ and SYS stacks from the.stackssection defined in link.x, based on _xxx_stack_size values_xxx_stackand_xxx_stack_endwhere the former is the top and the latter the bottom of the stack for each mode (und,svc,abt,irq,fiq,sys)_stack_top- the address of the top of the STACKS region that contains the reseved stacks, with eight-byte alignment. Using this symbol is deprecated, stacks should be initialized by their individual _xxx_stack symbols
The assembly language trampolines are required because AArch32
processors do not save a great deal of state on entry to an exception
handler, unlike Armv7-M (and other M-Profile) processors. We must therefore
save this state to the stack using assembly language, before transferring to
an extern "C" function. We do not change modes before entering that
extern "C" function - that’s for the handler to deal with as it wishes.
Because FIQ is often performance-sensitive, we don’t supply an FIQ
trampoline; if you want to use FIQ, you have to write your own assembly
routine, allowing you to preserve only whatever state is important to you.
§Examples
You can find example code using QEMU inside the project repository
Macros§
- restore_
context - This macro expands to code for restoring context on exit from an exception handler. It restores FPU state, assuming 16 DP registers (a ‘D16’ or ‘D16SP’ FPU configuration).
- save_
context - This macro expands to code for restoring context on exit from an exception handler. It saves FPU state, assuming 16 DP registers (a ‘D16’ or ‘D16SP’ FPU configuration). Note that SP-only FPUs still have DP registers
Functions§
- _default_
handler - Our default exception handler.