Crate aarch32_rt

Crate aarch32_rt 

Source
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 +d32 target 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_start but you can override it. The provided default start function will initialise all global variables and then call kmain in 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 _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 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_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 .bss and .data
  • _stack_setup - initialises UND, SVC, ABT, IRQ, FIQ and SYS stacks from the address given in r0. Deprecated, use _stack_setup_preallocated instead
  • _stack_setup_preallocated - initialises UND, SVC, ABT, IRQ, FIQ and SYS stacks from the .stacks section defined in link.x, based on _xxx_stack_size values
  • _xxx_stack and _xxx_stack_end where the former is the top and the latter the bottom of the stack for each mode (und, svc, abt, irq, fiq, sys)
  • _stack_top - the address of the top of the STACKS region that contains the reseved stacks, with eight-byte alignment. Using this symbol is deprecated, stacks should be initialized by their individual _xxx_stack symbols

The assembly language trampolines are required because AArch32 processors do not save a great deal of state on entry to an exception handler, unlike Armv7-M (and other M-Profile) processors. We must therefore save this state to the stack using assembly language, before transferring to an extern "C" function. We do not change modes before entering that extern "C" function - that’s for the handler to deal with as it wishes. Because FIQ is often performance-sensitive, we don’t supply an FIQ trampoline; if you want to use FIQ, you have to write your own assembly routine, allowing you to preserve only whatever state is important to you.

§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.

Attribute Macros§

entry
Creates an unsafe program entry point (i.e. a kmain function).
exception
Creates an unsafe exception handler.
irq
Creates an unsafe interrupt handler.