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