aarch64_rt/
lib.rs

1// Copyright 2025 The aarch64-rt Authors.
2// This project is dual-licensed under Apache 2.0 and MIT terms.
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Startup code for aarch64 Cortex-A processors.
6
7#![no_std]
8#![deny(clippy::undocumented_unsafe_blocks)]
9#![deny(unsafe_op_in_unsafe_fn)]
10
11#[cfg(any(
12    all(feature = "el1", feature = "el2"),
13    all(feature = "el1", feature = "el3"),
14    all(feature = "el2", feature = "el3"),
15))]
16compile_error!("Only one `el` feature may be enabled at once.");
17
18#[cfg(feature = "initial-pagetable")]
19mod pagetable;
20
21#[cfg(any(feature = "exceptions", feature = "psci"))]
22use core::arch::asm;
23use core::arch::global_asm;
24#[cfg(feature = "initial-pagetable")]
25pub use pagetable::{DEFAULT_MAIR, DEFAULT_SCTLR, DEFAULT_TCR, InitialPagetable};
26
27global_asm!(include_str!("entry.S"));
28
29#[cfg(not(feature = "initial-pagetable"))]
30global_asm!(include_str!("dummy_enable_mmu.S"),);
31
32#[cfg(feature = "exceptions")]
33global_asm!(include_str!("exceptions.S"));
34
35unsafe extern "C" {
36    /// An assembly entry point for secondary cores.
37    ///
38    /// It will enable the MMU, disable trapping of floating point instructions, initialise the
39    /// stack pointer to `stack_end` and then jump to the function pointer at the bottom of the
40    /// stack with the u64 value second on the stack as a parameter.
41    pub unsafe fn secondary_entry(stack_end: *mut u64) -> !;
42}
43
44/// Sets the appropriate vbar to point to our `vector_table`, if the `exceptions` feature is
45/// enabled.
46#[unsafe(no_mangle)]
47extern "C" fn set_exception_vector() {
48    // SAFETY: We provide a valid vector table.
49    #[cfg(all(feature = "el1", feature = "exceptions"))]
50    unsafe {
51        asm!(
52            "adr x9, vector_table_el1",
53            "msr vbar_el1, x9",
54            options(nomem, nostack),
55            out("x9") _,
56        );
57    }
58    // SAFETY: We provide a valid vector table.
59    #[cfg(all(feature = "el2", feature = "exceptions"))]
60    unsafe {
61        asm!(
62            "adr x9, vector_table_el2",
63            "msr vbar_el2, x9",
64            options(nomem, nostack),
65            out("x9") _,
66        );
67    }
68    // SAFETY: We provide a valid vector table.
69    #[cfg(all(feature = "el3", feature = "exceptions"))]
70    unsafe {
71        asm!(
72            "adr x9, vector_table_el3",
73            "msr vbar_el3, x9",
74            options(nomem, nostack),
75            out("x9") _,
76        );
77    }
78}
79
80#[unsafe(no_mangle)]
81extern "C" fn rust_entry(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> ! {
82    set_exception_vector();
83    __main(arg0, arg1, arg2, arg3)
84}
85
86unsafe extern "Rust" {
87    /// Main function provided by the application using the `main!` macro.
88    safe fn __main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> !;
89}
90
91/// Marks the main function of the binary and reserves space for the boot stack.
92///
93/// Example:
94///
95/// ```rust
96/// use aarch64_rt::entry;
97///
98/// entry!(main);
99/// fn main() -> ! {
100///     info!("Hello world");
101/// }
102/// ```
103///
104/// 40 pages (160 KiB) is reserved for the boot stack by default; a different size may be configured
105/// by passing the number of pages as a second argument to the macro, e.g. `entry!(main, 10);` to
106/// reserve only 10 pages.
107#[macro_export]
108macro_rules! entry {
109    ($name:path) => {
110        entry!($name, 40);
111    };
112    ($name:path, $boot_stack_pages:expr) => {
113        #[unsafe(export_name = "boot_stack")]
114        #[unsafe(link_section = ".stack.boot_stack")]
115        static mut __BOOT_STACK: $crate::Stack<$boot_stack_pages> = $crate::Stack::new();
116
117        // Export a symbol with a name matching the extern declaration above.
118        #[unsafe(export_name = "__main")]
119        fn __main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) -> ! {
120            // Ensure that the main function provided by the application has the correct type.
121            $name(arg0, arg1, arg2, arg3)
122        }
123    };
124}
125
126/// A stack for some CPU core.
127///
128/// This is used by the [`entry!`] macro to reserve space for the boot stack.
129#[repr(C, align(4096))]
130pub struct Stack<const NUM_PAGES: usize>([StackPage; NUM_PAGES]);
131
132impl<const NUM_PAGES: usize> Stack<NUM_PAGES> {
133    /// Creates a new zero-initialised stack.
134    pub const fn new() -> Self {
135        Self([const { StackPage::new() }; NUM_PAGES])
136    }
137}
138
139impl<const NUM_PAGES: usize> Default for Stack<NUM_PAGES> {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145#[repr(C, align(4096))]
146struct StackPage([u8; 4096]);
147
148impl StackPage {
149    const fn new() -> Self {
150        Self([0; 4096])
151    }
152}
153
154#[cfg(feature = "psci")]
155/// Issues a PSCI CPU_ON call to start the CPU core with the given MPIDR.
156///
157/// This starts the core with an assembly entry point which will enable the MMU, disable trapping of
158/// floating point instructions, initialise the stack pointer to the given value, and then jump to
159/// the given Rust entry point function, passing it the given argument value.
160///
161/// # Safety
162///
163/// `stack` must point to a region of memory which is reserved for this core's stack. It must remain
164/// valid as long as the core is running, and there must not be any other access to it during that
165/// time. It must be mapped both for the current core to write to it (to pass initial parameters)
166/// and in the initial page table which the core being started will used, with the same memory
167/// attributes for both.
168pub unsafe fn start_core<C: smccc::Call, const N: usize>(
169    mpidr: u64,
170    stack: *mut Stack<N>,
171    rust_entry: extern "C" fn(arg: u64) -> !,
172    arg: u64,
173) -> Result<(), smccc::psci::Error> {
174    assert!(stack.is_aligned());
175    // The stack grows downwards on aarch64, so get a pointer to the end of the stack.
176    let stack_end = stack.wrapping_add(1);
177
178    // Write Rust entry point to the stack, so the assembly entry point can jump to it.
179    let params = stack_end as *mut u64;
180    // SAFETY: Our caller promised that the stack is valid and nothing else will access it.
181    unsafe {
182        *params.wrapping_sub(1) = rust_entry as _;
183        *params.wrapping_sub(2) = arg;
184    }
185    // Wait for the stores above to complete before starting the secondary CPU core.
186    dsb_st();
187
188    smccc::psci::cpu_on::<C>(mpidr, secondary_entry as _, stack_end as _)
189}
190
191/// Data synchronisation barrier that waits for stores to complete, for the full system.
192#[cfg(feature = "psci")]
193fn dsb_st() {
194    // SAFETY: A synchronisation barrier is always safe.
195    unsafe {
196        asm!("dsb st", options(nostack));
197    }
198}