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}