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
//! MMU bring-up: enables address translation so RAM can be marked
//! Normal memory, needed for `core::sync::atomic`'s exclusive-access
//! instructions (`ldrex`/`strex` on AArch32, `ldxr`/`stxr` on AArch64)
//! to behave correctly -- those are architecturally UNPREDICTABLE on the
//! Strongly-Ordered/Device memory every address is with the MMU off.
//! Every peripheral MMIO region keeps behaving exactly as it does today
//! (Device memory, same ordering guarantees raw register access already
//! relies on everywhere else in this crate).
//!
//! Identity-mapped only: every virtual address equals its physical
//! address. This exists purely to change memory *attributes*, not
//! layout -- no relocation, no higher-half kernel.
//!
//! The data cache is enabled and RAM is mapped Cacheable -- confirmed
//! necessary on real hardware (AArch32), not a nicety: with RAM mapped
//! Normal-but-Non-cacheable, the exclusive monitor never let `strex`
//! succeed, because this core ties the monitor to cache-line state.
//!
//! The implementation differs enough between execution states to live in
//! two files (as `boot.s`/`boot64.s` do), selected by target
//! architecture:
//!
//! - [`mmu32`](mod@self) -> `mmu32.rs`: VMSA short-descriptor
//! first-level table (1MB sections), CP15-programmed, run at PL1.
//! Called from `rt`'s `boot.s` after its HYP->SVC drop, or from
//! `boot6.s` on ARMv6, where there is no Hyp mode to drop out of.
//! - `mmu64.rs`: VMSAv8-64 long-descriptor tables (2MB blocks),
//! programmed via `MAIR_EL1`/`TCR_EL1`/`TTBR0_EL1`, run at EL1. The
//! caller must already be at EL1 (a consumer's boot stub drops
//! EL2->EL1 first).
//!
//! Both define the same `#[no_mangle] extern "C" fn rpi_hal_mmu_init`
//! entry point. It is a *strong* symbol; `rt`'s own boot sequence
//! (`boot.s`) calls it unconditionally, falling back to a weak no-op
//! (`mmu_fallback.s`, only included when the `mmu` feature is off -- see
//! `boot.rs`) when this feature is off. A consumer wanting a different
//! memory map turns this feature off and defines their own
//! `#[no_mangle] extern "C" fn rpi_hal_mmu_init()` instead.
// SoC memory map, shared by both implementations. The BCM2836/2837
// values are confirmed against this crate's own peripheral access types
// (e.g. the System Timer at `0x3f00_3000`, the legacy interrupt
// controller at `0x3f00_b000`, out to `0x3f98_0e00` for USB), not assumed
// from a datasheet. The BCM2711 values are the "low peripheral mode"
// addresses from Linux's `bcm2711.dtsi` (the peripheral block's `ranges`
// entry and `local_intc`/GIC-400 nodes), not yet cross-checked against
// this crate's own drivers the way the BCM2836/2837 base was -- there's
// no BCM2711 target to build until these consts exist, which is what
// they're for. `PERIPHERAL_BASE` itself lives in `crate::soc`, not here
// -- `crate::dma`/`crate::watchdog` need it regardless of whether this
// (`mmu`-gated) module is even compiled.
//
// The BCM2712 is the odd one out: every peripheral it has sits above 4 GiB,
// and the whole low 4 GiB is RAM. So it has none of the low-memory
// peripheral consts below, and instead names its MMIO as whole 1 GiB
// regions in `HIGH_DEVICE` (from `bcm2712.dtsi`'s `soc` and `pcie2`
// `ranges`).
use cratePERIPHERAL_BASE;
/// Exclusive top of the RAM the identity map marks Normal: everything
/// below the peripheral block on chips that keep it in the low 4 GiB, and
/// the whole low 4 GiB on the BCM2712. Also the bound [`set_uncached`]
/// checks a region against.
///
/// On the BCM2712 that is 4 GiB of address space whatever the board's RAM:
/// RAM above 4 GiB (8 GB and 16 GB boards) is not mapped, and on a 2 GB
/// board the top half of what is mapped has nothing behind it.
const RAM_END: u64 = PERIPHERAL_BASE as u64;
const RAM_END: u64 = 1 << 32;
/// 1 GiB regions above 4 GiB to map as Device, by base address.
///
/// On the BCM2712: the two that hold the SoC's own peripheral bus
/// (`0x10_0000_0000`, 2 GiB, which includes the System Timer, the always-on
/// GPIO and the GIC), and the one RP1's peripheral window opens into
/// (`0x1f_0000_0000`). Empty on every other chip.
const HIGH_DEVICE: & = &;
const HIGH_DEVICE: & = &;
/// End of the mapped peripheral block (inclusive) -- a round,
/// 1MB-aligned number comfortably covering every peripheral address this
/// crate can reach.
const PERIPHERAL_END: u32 = 0xFEFF_FFFF;
const PERIPHERAL_END: u32 = 0x3FFF_FFFF;
/// BCM2835's peripheral block is 16MB, from `PERIPHERAL_BASE` out past
/// USB at `0x2098_0e00`. Same "no chip selected" arm as
/// [`PERIPHERAL_BASE`] -- see `soc.rs`.
const PERIPHERAL_END: u32 = 0x20FF_FFFF;
/// The ARM-local peripheral block (per-core timers, IRQ routing, and the
/// inter-core mailbox registers on BCM2836/2837; the same block plus
/// GIC-400 on BCM2711) as an inclusive `(base, end)` range -- a
/// physically separate MMIO region from [`PERIPHERAL_BASE`]. Needed
/// device-mapped so [`crate::multicore`] can reach the mailbox registers
/// with the MMU on.
///
/// `None` on a chip that has no such block at all, in which case the
/// identity map has no device region above the peripheral block.
const LOCAL_PERIPHERAL: = Some;
/// BCM2711 low-peripheral-mode base (`bcm2711.dtsi`'s `ranges` entry
/// mapping bus address `0x4000_0000` to this physical address; GIC-400's
/// distributor/CPU-interface registers live at `0xFF84_1000`/`0xFF84_2000`
/// within this block), for the 8MB that entry's size covers -- which runs
/// to the top of the 32-bit address space.
const LOCAL_PERIPHERAL: = Some;
/// The BCM2835 has no ARM-local peripheral block. It is a uniprocessor
/// part: the block first appears on the BCM2836 to carry what a second
/// core needs -- per-core timers and interrupt routing, and the mailboxes
/// cores signal each other through -- none of which exists here. Nothing
/// above the peripheral block is mapped at all.
const LOCAL_PERIPHERAL: = None;
/// Whether the 2MB block at `base`, somewhere at or above [`RAM_END`] in
/// the low 4 GiB, is MMIO to map as Device rather than leave invalid.
/// The BCM2712 has no MMIO in the low 4 GiB, all of which is below
/// [`RAM_END`] anyway.
use crateclean_invalidate_range;
/// Granularity of [`set_uncached`]: 1MB (AArch32's section) or 2MB
/// (AArch64's level-2 block), whichever this build's table uses. A region
/// handed to that function must be aligned to this and a whole multiple of
/// it, since a translation table entry is the smallest thing whose memory
/// type can be changed.
///
/// Sized for whichever implementation is compiled, so a `static` intended to
/// be remapped has to be aligned and padded to the larger of the two to
/// build for both targets — 2MB, the value below on AArch64.
pub const UNCACHED_GRANULE: usize = UNCACHED_GRANULE;
/// Bytes one translation-table descriptor covers.
///
/// The same number [`UNCACHED_GRANULE`] publishes, named again because
/// [`install_stack_guard`] cares about the shape of the table rather than
/// about `set_uncached`'s contract — and the day those stop being the same
/// number, each wants to move on its own.
const DESCRIPTOR_BYTES: usize = UNCACHED_GRANULE;
extern "C"
/// Leaves the reserved margin below the main stack with no translation, so
/// that an overflow faults on the instruction that causes it.
///
/// Without this a stack overflow is not an error at all. The map is an
/// identity map of every address below the peripheral base, so `sp`
/// descending past `__stack_bottom` crosses nothing the hardware objects
/// to: it walks the margin, reaches `.data` and `.text` below it, and
/// overwrites the running program. What that looks like is not a fault but
/// a board that behaves strangely and later reboots, arbitrarily far from
/// the call that actually went too deep.
///
/// The margin is reserved by this crate's linker scripts (`__stack_slack`,
/// 2 MiB) and is 2 MiB-aligned precisely so it can be given up here: 2 MiB
/// is the coarser of the two granules in play, so the region is a whole
/// number of descriptors on either architecture and no real memory shares
/// one with it. It holds nothing — `.stack` is a `NOLOAD` region between
/// `.data` and `.bss` — so nothing is lost by making it unreachable, and
/// the 2 MiB of address space is noise on a board with at least a
/// gigabyte.
///
/// Rounded inward, so a margin that has been overridden to something not a
/// multiple of a descriptor gives up only the descriptors lying entirely
/// inside it. A margin smaller than one descriptor yields no guard at all
/// rather than an approximate one.
///
/// # What this does not cover
///
/// Only the main stack of the core that boots. The AArch32 exception-mode
/// stacks sit *above* `__stack_top`, and a secondary core runs on a
/// [`Stack`](crate::multicore::Stack) the application placed in `.bss`;
/// both are ordinary mapped memory with their neighbours right below them,
/// exactly as this one used to be.
///
/// # Safety
///
/// Called from `rpi_hal_mmu_init` on each core, after the table is built
/// and before the MMU is enabled. That ordering is what makes it free of
/// cache and TLB maintenance: the descriptor write lands in RAM with the
/// caches off, and there is no stale translation to invalidate because
/// there are no translations yet. Writing the same zeroes on every core is
/// why calling it more than once is harmless.
pub unsafe
/// Why a [`set_uncached`] call was rejected.
/// Remaps `len` bytes at `base` as Normal **Non-cacheable** memory, and
/// drops whatever this core had cached of it.
///
/// This is what makes a shared-memory protocol with the VideoCore possible.
/// The rest of this crate's bus-master traffic gets by with explicit cache
/// maintenance (`cache.rs`) because each buffer has one owner at a time: the
/// ARM writes it, cleans it, and hands it over. VCHIQ's shared state
/// (`crate::vchiq`) is not like that — both sides write *different fields of
/// the same cache line* concurrently, so there is no correct maintenance
/// sequence at all. Cleaning the line to publish this core's field writes
/// the stale copy of the peer's field back over it; invalidating it to read
/// the peer's field discards this core's not-yet-written-back one. Only
/// taking the line out of the picture fixes that, which is why Linux
/// allocates the same region with `dma_alloc_coherent`.
///
/// Both the descriptor write and the TLB invalidation are broadcast to the
/// inner-shareable domain, so secondary cores (which share this one table)
/// see the new memory type too rather than keeping a stale cached
/// translation.
///
/// # Safety
///
/// The region must be memory this caller owns outright — the entire
/// [`UNCACHED_GRANULE`]-sized block gets a new memory type, so anything else
/// that happens to live in it silently becomes non-cacheable and much
/// slower. Nothing may be concurrently accessing the region on any core
/// during the call: its cached copies are discarded as part of it.
pub unsafe