Crate cortex_a_rt

Crate cortex_a_rt 

Source
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 call kmain 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 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.

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.

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.