Crate r3_port_arm
source ·Expand description
The Arm-A port for the R3 kernel.
Startup Code
use_startup!
generates an entry point (with a symbol name start
), which is expected to be called by a bootloader. The startup code configures MMU to assign appropriate memory attributes based on the memory map supplied by StartupOptions::MEMORY_MAP
and to map an exception vector table at 0x0000_0000
or 0xffff_0000
.
Linker Scripts
This crate provides linker scripts that define some standard sections, suitable for use with a bootloader that can handle ELF sections. Put files as follows under your crate’s directory:
.cargo/config.toml
:
[target.armv7a-none-eabi]
rustflags = ["-C", "link-arg=-Tlink_ram.x"]
memory.x
:
MEMORY
{
RAM : ORIGIN = 0x20000000, LENGTH = 10240K
}
build.rs
:
fn main() {
// Use the linker script `memory.x` at the crate root
println!(
"cargo:rustc-link-search={}",
std::env::current_dir().unwrap().display()
);
}
The following linker scripts are provided:
link_ram.x
places all sections inRAM
.link_ram_harvard.x
places.text
inRAM_CODE
and all remaining sections inRAM_DATA
. Combined with an approriate MMU configuration, this can be used to implement the W⊕X (write xor execute) memory policy for enhanced security. It might also lead to a performance improvement on a processor having separate buses for instruction and data access.
Kernel Timing
As far as kernel timing is concerned, there is no universal solution for a Cortex-A system. This crate provides a port timer driver for Arm PrimeCell SP804 Dual Timer, which can be instantiated by use_sp804!
.
Interrupt Controller
Your kernel trait type should be combined with an interrupt controller driver by implementing PortInterrupts
and InterruptController
. Most systems are equipped with Arm Generic Interrupt Controller (GIC), whose driver is provided by use_gic!
.
The maximum possible range of valid interrupt numbers is 0..1020
(the upper bound varies across implementations). The range is statically partitioned as follows:
0..16
is used for SGIs (Software-Generated Interrupts), which are used for inter-processor communication. SGIs don’t support enabling, disabling, or changing their trigger modes.16..32
is used for PPIs (Private Peripheral Interrupts), which are peripheral interrupts specific to a single processor.32..
is used for SPIs (Shared Peripheral Interrupts), which are peripheral interrupts that the Distributor can route to a specified set of processors. The current implementation of the GIC driver routes all interrupts to CPU 0, assuming that’s where the application runs.
The valid priority range is 0..255
. All priorities are managed - unmanaged interrupts aren’t supported yet.
The GIC driver exposes additional operations on interrupt lines through Gic
implemented on your kernel trait type.
Implementation
Context state
The state of an interrupted thread is stored to the interrupted thread’s stack in the following form:
#[repr(C)]
struct ContextState {
// Second-level state
//
// Includes everything that is not included in the first-level state. These
// are moved between memory and registers only when switching tasks.
// TODO: Floating-point registers
r4: u32,
r5: u32,
r6: u32,
r7: u32,
r8: u32,
r9: u32,
r10: u32,
r11: u32,
// First-level state
//
// This was designed after Arm-M's exception frame.
//
// The GPR potion is comprised of caller-saved registers. In an exception
// handler, saving/restoring this set of registers at entry and exit allows
// it to call Rust functions.
//
// `{pc, cpsr}` is the sequence of registers that the RFE (return from
// exception) instruction expects to be in memory in this exact order.
r0: u32,
r1: u32,
r2: u32,
r3: u32,
r12: u32,
lr: u32,
pc: u32,
cpsr: u32,
}
sp
is stored in TaskCb::port_task_state
.
When a task is activated, a new context state is created inside the task’s stack. By default, only essential registers are preloaded with known values. The preload-registers
Cargo feature enables preloading for all GPRs, which might help in debugging at the cost of performance and code size.
For the idle task, saving and restoring the context store is essentially replaced with no-op or loads of hard-coded values. In particular, pc
is always “restored” with the entry point of the idle task.
Processor Modes
- System: Task context. The idle task (the implicit task that runs when
*
running_task_ptr
().is_none()
) uses this mode withsp_usr == 0
(no other tasks or non-task contexts usesp == 0
, so this is straightforward to detect). - Supervisor: Non-task context
- IRQ: The processor enters this mode when it takes an exception. This state lasts only briefly because the IRQ handler switches to Supervisor as soon as possible to allow reentry.
sp_irq
is only used as a scratch register.
Modules
- Changelog
Macros
- Implement
PortInterrupts
,InterruptController
, andGic
on the given kernel trait type using the General Interrupt Controller (GIC) on the target. RequiresGicOptions
. - Define a kernel trait type implementing
PortThreading
andEntryPoint
. RequiresThreadingOptions
,InterruptController
, andTimer
. - Attach the implementation of
PortTimer
that is based on Arm PrimeCell SP804 Dual Timer to a given kernel trait type. This macro also implementsTimer
on the kernel trait type. RequiresSp804Options
.
Structs
Enums
- Specifies the type of signal transition that pends an interrupt.
Traits
- Defines the entry points of a port instantiation. Implemented by
use_port!
. - Provides access to a system-global GIC instance. Implemented by
use_gic!
. - The options for
use_gic!
. - An abstract interface to an interrupt controller. Implemented by
use_gic!
. - The options for
use_sp804!
. - The options for
use_startup!
. - The configuration of the port.
- An abstract inferface to a port timer driver. Implemented by
use_sp804!
.