Skip to main content

rpi_hal/
multicore.rs

1//! Bringing up secondary cores (1-3).
2//!
3//! Out of reset, the GPU firmware does *not* release cores 1-3 to the
4//! kernel's entry point the way it does core 0. Instead it leaves each of
5//! them parked in its own stub until explicitly woken, and the wake
6//! mechanism differs by execution state:
7//!
8//! - **AArch32**: each core watches its ARM-local *mailbox 3* register;
9//!   `launch` hands off the stack pointers and entry through that core's
10//!   mailboxes (Device memory, coherent between cores with no cache
11//!   maintenance) and wakes it by writing mailbox 3. (Confirmed on
12//!   hardware: a secondary core made to record its arrival from our own
13//!   `_start` never did -- it never reaches `_start` at all.)
14//! - **AArch64**: the firmware's `armstub8` parks cores in a *spin-table*
15//!   -- each polls a fixed low address (`0xd8 + 8*core`) and jumps to
16//!   whatever 64-bit address is written there, once woken by `sev`. The
17//!   spin-table carries only the entry address, so `launch` passes the
18//!   stack + entry through `SECONDARY_PARAMS` in shared RAM instead,
19//!   cache-cleaning it (the target core reads with its caches still off)
20//!   before writing the core's slot with the address of
21//!   `__secondary_core_entry` (the secondary64.s trampoline).
22//!
23//! Either way the trampoline first leaves the privilege level the firmware
24//! released the core at — Hyp on AArch32, EL2 on AArch64 — then reads back
25//! the handoff, brings up the core's own MMU/caches
26//! ([`rpi_hal_mmu_init`](crate)), and jumps to `entry`.
27//!
28//! That first step is not a formality, and its absence is the reason this
29//! is worth stating here rather than leaving to the assembly. Stacks, the
30//! vector base and the MMU controls are all *banked* by mode: run the
31//! trampoline in Hyp and each of them writes a register nothing will read,
32//! so the core executes with an uninitialized `sp`, takes its exceptions
33//! through a vector base that was never set, and cannot report the fault
34//! that follows. From core 0 the handoff looks perfect — the mailbox is
35//! written and the firmware's stub acknowledges it — and the core simply
36//! never says anything.
37//!
38//! Requires the `mmu` feature (pulled in automatically -- see this crate's
39//! `Cargo.toml`): the secondary core's own `rpi_hal_mmu_init` call is what
40//! gives it Normal/Cacheable/Shareable RAM so `core::sync::atomic` (and
41//! hence `critical_section`) work once it's running alongside core 0.
42//!
43//! Code that ends up running on more than one core -- the entry functions
44//! here, but also the single `__irq_handler` and panic handler all of them
45//! share -- can ask which one it is on with [`crate::cpu::core_id`]. That
46//! lives outside this module on purpose, so it can be read without this
47//! feature.
48
49#[cfg(target_arch = "aarch64")]
50core::arch::global_asm!(include_str!("secondary64.s"));
51
52/// How much of a secondary core's [`Stack`] is carved off the top for
53/// IRQ-mode use, the rest going to main (SVC) mode.
54///
55/// Deliberately not the same number as core 0's `__irq_stack_size`: core
56/// 0's stacks are whole regions the linker script reserves, while this
57/// one is split out of a `Stack<BYTES>` the application declared and
58/// sized for its own work -- some examples pass 8 KiB. Only meaningful
59/// on AArch32; AArch64 takes exceptions on the single SP_EL1 and uses
60/// the whole stack.
61const IRQ_STACK_SIZE: usize = 0x1000;
62
63// ---- AArch32: ARM-local mailbox handoff ----------------------------------
64
65/// Base of the ARM-local mailbox *write-set* registers. Each core owns
66/// four consecutive 32-bit mailboxes starting here, with a `0x10` stride
67/// between cores, so core `C`'s mailbox `M` set register is at
68/// `MAILBOX_SET_BASE + 0x10 * C + 4 * M`. `boot.s`'s
69/// `__secondary_core_entry` reads them back from the matching *read*
70/// registers at `0x4000_00C0` (same layout, `0x40` higher).
71#[cfg(target_arch = "arm")]
72const MAILBOX_SET_BASE: usize = 0x4000_0080;
73
74#[cfg(target_arch = "arm")]
75extern "C" {
76    /// The `boot.s` trampoline a released secondary core starts
77    /// executing -- see this module's doc comment. Only its *address* is
78    /// ever used (written into mailbox 3); it is never called from Rust.
79    fn __secondary_core_entry() -> !;
80}
81
82/// Hands off the stack pointers and `entry` to core `core_id` (1-3)
83/// through its ARM-local mailboxes, then wakes it. Mailbox 3 is written
84/// last, since writing it is what the firmware stub is watching for;
85/// `dsb` orders all four writes ahead of the `sev`, and `sev` wakes the
86/// stub out of its `wfe`.
87#[cfg(target_arch = "arm")]
88fn launch<const BYTES: usize>(
89    core_id: usize,
90    stack: &'static mut Stack<BYTES>,
91    entry: extern "C" fn() -> !,
92) {
93    use core::ptr::write_volatile;
94
95    let sp_irq = stack.top();
96    let sp_main = sp_irq - IRQ_STACK_SIZE;
97    let mailbox = MAILBOX_SET_BASE + 0x10 * core_id;
98
99    // SAFETY: these are the Device-mapped ARM-local mailbox registers for
100    // a real core (1-3); a fresh mailbox reads 0, so writing the full
101    // value through the write-set register lands it exactly. `dsb`/`sev`
102    // are plain side-effect-only barriers.
103    unsafe {
104        write_volatile((mailbox) as *mut u32, sp_main as u32);
105        write_volatile((mailbox + 0x4) as *mut u32, sp_irq as u32);
106        write_volatile((mailbox + 0x8) as *mut u32, entry as usize as u32);
107        write_volatile(
108            (mailbox + 0xC) as *mut u32,
109            __secondary_core_entry as *const () as u32,
110        );
111        core::arch::asm!("dsb", "sev");
112    }
113}
114
115// ---- AArch64: armstub8 spin-table handoff --------------------------------
116
117/// `armstub8` spin-table base: `spin_cpu0` sits at `0xd8` and core `C`
118/// polls `0xd8 + 8 * C`, jumping to the 64-bit address written there once
119/// released by `sev`.
120#[cfg(target_arch = "aarch64")]
121const SPIN_TABLE_BASE: usize = 0xd8;
122
123#[cfg(target_arch = "aarch64")]
124extern "C" {
125    /// The secondary64.s trampoline a released core jumps to; its address
126    /// is written into the core's spin-table slot.
127    fn __secondary_core_entry() -> !;
128
129    /// `{ sp, entry }` (two `u64`s) per core, indexed by core id -- the
130    /// handoff the spin-table can't carry itself. Defined in secondary64.s.
131    static SECONDARY_PARAMS: [u64; 8];
132}
133
134/// Writes `entry` and the core's stack into [`SECONDARY_PARAMS`], cleans
135/// those cache lines (the target core reads them with its caches still
136/// off), then releases the core by writing the trampoline's address into
137/// its spin-table slot and issuing `sev`. `clean_range`'s trailing `dsb`
138/// orders each write ahead of the wake.
139#[cfg(target_arch = "aarch64")]
140fn launch<const BYTES: usize>(
141    core_id: usize,
142    stack: &'static mut Stack<BYTES>,
143    entry: extern "C" fn() -> !,
144) {
145    use core::ptr::write_volatile;
146
147    // AArch64 has no separate banked IRQ stack (exceptions to EL1h run on
148    // SP_EL1), so the whole stack is the core's single stack. Mask to 16
149    // bytes for the AArch64 stack-pointer alignment requirement.
150    let sp = (stack.top() as u64) & !0xF;
151
152    // SAFETY: SECONDARY_PARAMS is a fixed 4-core buffer defined in
153    // secondary64.s; core_id is 1..=3, so the slot is in bounds. The
154    // spin-table slot is the firmware's own release word for this core.
155    unsafe {
156        let params = core::ptr::addr_of!(SECONDARY_PARAMS) as *mut u64;
157        params.add(2 * core_id).write_volatile(sp);
158        params
159            .add(2 * core_id + 1)
160            .write_volatile(entry as usize as u64);
161        crate::cache::clean_range(params.add(2 * core_id) as u32, 16);
162
163        let slot = (SPIN_TABLE_BASE + 8 * core_id) as *mut u64;
164        write_volatile(slot, __secondary_core_entry as *const () as u64);
165        crate::cache::clean_range(slot as u32, 8);
166
167        core::arch::asm!("sev");
168    }
169}
170
171// ---- Shared API ----------------------------------------------------------
172
173/// A statically-allocated stack for a secondary core.
174///
175/// 8-byte aligned, since AAPCS requires the stack pointer to be 8-byte
176/// aligned at public interfaces. On AArch32 `launch` carves the top
177/// `IRQ_STACK_SIZE` bytes off for IRQ-mode use (mirroring how core 0's
178/// own stack is laid out in `boot.s`) and hands the rest to the core's
179/// main (SVC) mode; on AArch64 the whole stack is used. The size is
180/// enforced to fit at compile time, not checked at runtime.
181#[repr(C, align(8))]
182pub struct Stack<const BYTES: usize> {
183    mem: [u8; BYTES],
184}
185
186impl<const BYTES: usize> Stack<BYTES> {
187    /// Compile-time check that `BYTES` is a valid stack size -- evaluated
188    /// (forced) from `Stack::new`, so an invalid `BYTES` fails to build
189    /// rather than misbehaving at runtime.
190    const VALID_SIZE: () = assert!(
191        BYTES.is_multiple_of(8) && BYTES > IRQ_STACK_SIZE,
192        "Stack size must be a multiple of 8 and larger than the reserved IRQ-mode region"
193    );
194
195    /// A new, zeroed stack.
196    pub const fn new() -> Self {
197        const { Self::VALID_SIZE };
198        Self { mem: [0; BYTES] }
199    }
200
201    /// Address one past the last byte -- where a full-descending stack
202    /// starts.
203    fn top(&mut self) -> usize {
204        self.mem.as_mut_ptr() as usize + BYTES
205    }
206}
207
208impl<const BYTES: usize> Default for Stack<BYTES> {
209    /// Same as [`Stack::new`] -- a new, zeroed stack.
210    fn default() -> Self {
211        Self::new()
212    }
213}
214
215/// Grants access to secondary cores 1-3, each of which the firmware leaves
216/// parked until [`spawn`](Core1::spawn) wakes it -- see this module's doc
217/// comment.
218///
219/// The `unsafe`-once-only contract on [`steal`](Cores::steal) plus each
220/// `CoreN::spawn` consuming `self` is what prevents double-launching a
221/// core -- the same ownership-based singleton idiom this crate already
222/// uses for `pac::Peripherals::steal`, rather than a runtime "already
223/// launched" check.
224pub struct Cores {
225    /// Secondary core 1.
226    pub core1: Core1,
227    /// Secondary core 2.
228    pub core2: Core2,
229    /// Secondary core 3.
230    pub core3: Core3,
231}
232
233impl Cores {
234    /// Steals ownership of all three secondary cores.
235    ///
236    /// # Safety
237    ///
238    /// Must be called at most once -- calling it twice hands out two
239    /// `Core1`s (etc.) that could both call `spawn`, racing to launch the
240    /// same physical core. Mirrors `pac::Peripherals::steal`.
241    pub unsafe fn steal() -> Self {
242        Self {
243            core1: Core1(()),
244            core2: Core2(()),
245            core3: Core3(()),
246        }
247    }
248}
249
250/// Handle to secondary core 1. See [`Cores`].
251pub struct Core1(());
252
253/// Handle to secondary core 2. See [`Cores`].
254pub struct Core2(());
255
256/// Handle to secondary core 3. See [`Cores`].
257pub struct Core3(());
258
259macro_rules! impl_spawn {
260    ($core:ident, $core_id:expr) => {
261        impl $core {
262            /// Starts `entry` running on this core, using `stack` as its
263            /// stack.
264            ///
265            /// # Safety
266            ///
267            /// `entry` must never return (already enforced by its `-> !`
268            /// type) and must be safe to run concurrently with whatever
269            /// core 0 (or any other spawned core) does with shared state
270            /// from this point on -- the type system can't check that part.
271            pub unsafe fn spawn<const BYTES: usize>(
272                self,
273                stack: &'static mut Stack<BYTES>,
274                entry: extern "C" fn() -> !,
275            ) {
276                launch($core_id, stack, entry);
277            }
278        }
279    };
280}
281
282impl_spawn!(Core1, 1);
283impl_spawn!(Core2, 2);
284impl_spawn!(Core3, 3);
285
286// ---- Sharing data between cores ------------------------------------------
287
288/// Bytes in a cache line on this SoC's cores — which is also the size of
289/// the *exclusives reservation granule*, the unit an exclusive monitor
290/// reserves.
291///
292/// Both the Cortex-A7 and the Cortex-A53 use 64-byte lines. This is the
293/// *true* size, deliberately unlike the conservative lower bound the
294/// cache-maintenance helpers stride by: a stride smaller than the line is
295/// harmless there, whereas padding smaller than the line is exactly the
296/// bug [`CacheAligned`] exists to prevent.
297pub const CACHE_LINE_BYTES: usize = 64;
298
299/// Pads `T` out to a whole cache line, so that stores to it cannot disturb
300/// an exclusive reservation held on whatever happens to be laid out beside
301/// it.
302///
303/// **Wrap any atomic that two cores touch.** An exclusive monitor reserves
304/// a [granule](CACHE_LINE_BYTES), not an address, so a store by one core to
305/// *anywhere* in that granule clears another core's reservation: two
306/// unrelated atomics that share a line are not independent, and each core's
307/// `compare_exchange` or `swap` makes the other's fail and retry. Two cores
308/// working on neighbouring atomics can starve each other indefinitely —
309/// and since the clearing of an exclusive monitor is itself a `wfe`
310/// wake-up event, a core idling in `wfe` is woken by the very store whose
311/// reservation its own next store will knock down.
312///
313/// The other half of that bargain belongs to the waiting side: a core that
314/// is waiting rather than working should poll with a plain load and attempt
315/// an exclusive only once the lock looks free (test-and-test-and-set, which
316/// is what the `spin` crate's mutexes do), so that waiting costs the other
317/// cores nothing.
318///
319/// ```no_run
320/// use core::sync::atomic::AtomicBool;
321/// use rpi_hal::multicore::CacheAligned;
322///
323/// /// Set by core 0, polled by core 1.
324/// static READY: CacheAligned<AtomicBool> = CacheAligned(AtomicBool::new(false));
325/// ```
326#[repr(align(64))]
327pub struct CacheAligned<T>(
328    /// The value being kept to itself.
329    pub T,
330);
331
332const _: () = assert!(
333    core::mem::align_of::<CacheAligned<u8>>() == CACHE_LINE_BYTES,
334    "CacheAligned's repr(align) has to match CACHE_LINE_BYTES"
335);
336
337/// Wakes every core waiting in [`wait_for_event`] — a `dsb` followed by
338/// `sev`.
339///
340/// The other half of a cross-core handoff: publish the data with a release
341/// store, then call this so a core that has nothing to do until then can
342/// wait instead of spinning.
343///
344/// The `dsb` is not decoration. A release store is *ordered* before the
345/// `sev`, but ordering is not completion: the store can still be in flight
346/// when the event is broadcast, and the woken core — sitting in `wfe`, a
347/// few cycles from its next instruction — then loads the old value, goes
348/// back to sleep, and is never woken again if the producer's next move
349/// depends on seeing the flag consumed. `dsb` waits for the store to be
350/// observable before the wake goes out, which is why
351/// [`spawn`](Core1::spawn)'s own handoff pairs the two the same way.
352///
353/// Waking a core that isn't waiting is harmless: the event is recorded in
354/// each core's event register, so its next [`wait_for_event`] returns
355/// immediately rather than missing the wake-up. A consumer should still
356/// treat a lost wake as possible and re-check its condition — see
357/// [`wait_for_event`].
358pub fn signal_event() {
359    // SAFETY: neither instruction takes operands or touches memory; `dsb`
360    // waits for outstanding accesses and `sev` sets every core's event
361    // register.
362    unsafe { core::arch::asm!("dsb ish", "sev") };
363}
364
365/// Waits until some core calls [`signal_event`] (the `wfe` instruction) —
366/// how a core with nothing to do until another one says so should idle.
367///
368/// Always call this in a loop that re-checks the condition it's waiting
369/// for. `wfe` returns on any event, and several things besides `sev` count
370/// as one: an interrupt, a debug event, or the clearing of this core's
371/// exclusive monitor by another core's store (see [`CacheAligned`]).
372/// A return means "something happened", never "the thing you wanted
373/// happened".
374pub fn wait_for_event() {
375    // SAFETY: `wfe` has no operands; at worst it returns immediately.
376    unsafe { core::arch::asm!("wfe") };
377}