aarch64_rt/
pagetable.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//! Code to set up an initial pagetable.
6
7use core::arch::naked_asm;
8
9const MAIR_DEV_NGNRE: u64 = 0x04;
10const MAIR_MEM_WBWA: u64 = 0xff;
11/// The default value used for MAIR_ELx.
12pub const DEFAULT_MAIR: u64 = MAIR_DEV_NGNRE | MAIR_MEM_WBWA << 8;
13
14/// 4 KiB granule size for TTBR1_ELx.
15const TCR_TG1_4KB: u64 = 0x2 << 30;
16/// Disable translation table walk for TTBR1_ELx, generating a translation fault instead.
17const TCR_EPD1: u64 = 0x1 << 23;
18/// 40 bits, 1 TiB.
19const TCR_EL1_IPS_1TB: u64 = 0x2 << 32;
20/// 40 bits, 1 TiB.
21const TCR_EL2_PS_1TB: u64 = 0x2 << 16;
22/// 4 KiB granule size for TTBR0_ELx.
23const TCR_TG0_4KB: u64 = 0x0 << 14;
24/// Translation table walks for TTBR0_ELx are inner sharable.
25const TCR_SH_INNER: u64 = 0x3 << 12;
26/// Translation table walks for TTBR0_ELx are outer write-back read-allocate write-allocate
27/// cacheable.
28const TCR_RGN_OWB: u64 = 0x1 << 10;
29/// Translation table walks for TTBR0_ELx are inner write-back read-allocate write-allocate
30/// cacheable.
31const TCR_RGN_IWB: u64 = 0x1 << 8;
32/// Size offset for TTBR0_ELx is 2**39 bytes (512 GiB).
33const TCR_T0SZ_512: u64 = 64 - 39;
34/// The default value used for TCR_EL1.
35pub const DEFAULT_TCR_EL1: u64 = TCR_EL1_IPS_1TB
36    | TCR_TG1_4KB
37    | TCR_EPD1
38    | TCR_TG0_4KB
39    | TCR_SH_INNER
40    | TCR_RGN_OWB
41    | TCR_RGN_IWB
42    | TCR_T0SZ_512;
43/// The default value used for TCR_EL2.
44pub const DEFAULT_TCR_EL2: u64 =
45    TCR_EL2_PS_1TB | TCR_TG0_4KB | TCR_SH_INNER | TCR_RGN_OWB | TCR_RGN_IWB | TCR_T0SZ_512;
46/// The default value used for TCR_EL3.
47pub const DEFAULT_TCR_EL3: u64 =
48    TCR_TG0_4KB | TCR_RGN_OWB | TCR_RGN_IWB | TCR_SH_INNER | TCR_T0SZ_512;
49
50/// Stage 1 instruction access cacheability is unaffected.
51const SCTLR_ELX_I: u64 = 0x1 << 12;
52/// SP alignment fault if SP is not aligned to a 16 byte boundary.
53const SCTLR_ELX_SA: u64 = 0x1 << 3;
54/// Stage 1 data access cacheability is unaffected.
55const SCTLR_ELX_C: u64 = 0x1 << 2;
56/// EL0 and EL1 stage 1 MMU enabled.
57const SCTLR_ELX_M: u64 = 0x1 << 0;
58/// Privileged Access Never is unchanged on taking an exception to ELx.
59const SCTLR_ELX_SPAN: u64 = 0x1 << 23;
60/// SETEND instruction disabled at EL0 in aarch32 mode.
61const SCTLR_ELX_SED: u64 = 0x1 << 8;
62/// Various IT instructions are disabled at EL0 in aarch32 mode.
63const SCTLR_ELX_ITD: u64 = 0x1 << 7;
64const SCTLR_ELX_RES1: u64 = (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29);
65/// The default value used for SCTLR_ELx.
66pub const DEFAULT_SCTLR: u64 = SCTLR_ELX_M
67    | SCTLR_ELX_C
68    | SCTLR_ELX_SA
69    | SCTLR_ELX_ITD
70    | SCTLR_ELX_SED
71    | SCTLR_ELX_I
72    | SCTLR_ELX_SPAN
73    | SCTLR_ELX_RES1;
74
75/// Provides an initial pagetable which can be used before any Rust code is run.
76///
77/// The `initial-pagetable` feature must be enabled for this to be used.
78#[cfg(any(feature = "el1", feature = "el2", feature = "el3"))]
79#[macro_export]
80macro_rules! initial_pagetable {
81    ($value:expr, $mair:expr, $sctlr:expr, $tcr:expr) => {
82        static INITIAL_PAGETABLE: $crate::InitialPagetable = $value;
83
84        $crate::enable_mmu!(INITIAL_PAGETABLE, $mair, $sctlr, $tcr);
85    };
86    ($value:expr, $mair:expr) => {
87        $crate::initial_pagetable!($value, $mair, $crate::DEFAULT_SCTLR, $crate::DEFAULT_TCR);
88    };
89    ($value:expr) => {
90        $crate::initial_pagetable!(
91            $value,
92            $crate::DEFAULT_MAIR,
93            $crate::DEFAULT_SCTLR,
94            $crate::DEFAULT_TCR
95        );
96    };
97}
98
99/// Provides an initial pagetable which can be used before any Rust code is run.
100///
101/// The `initial-pagetable` feature must be enabled for this to be used.
102#[cfg(not(any(feature = "el1", feature = "el2", feature = "el3")))]
103#[macro_export]
104macro_rules! initial_pagetable {
105    ($value:expr, $mair:expr, $sctlr:expr, $tcr_el1:expr, $tcr_el2:expr, $tcr_el3:expr) => {
106        static INITIAL_PAGETABLE: $crate::InitialPagetable = $value;
107
108        $crate::enable_mmu!(
109            INITIAL_PAGETABLE,
110            $mair,
111            $sctlr,
112            $tcr_el1,
113            $tcr_el2,
114            $tcr_el3
115        );
116    };
117    ($value:expr, $mair:expr) => {
118        initial_pagetable!(
119            $value,
120            $mair,
121            $crate::DEFAULT_SCTLR,
122            $crate::DEFAULT_TCR_EL1,
123            $crate::DEFAULT_TCR_EL2,
124            $crate::DEFAULT_TCR_EL3
125        );
126    };
127    ($value:expr) => {
128        initial_pagetable!(
129            $value,
130            $crate::DEFAULT_MAIR,
131            $crate::DEFAULT_SCTLR,
132            $crate::DEFAULT_TCR_EL1,
133            $crate::DEFAULT_TCR_EL2,
134            $crate::DEFAULT_TCR_EL3
135        );
136    };
137}
138
139/// Enables the MMU and caches, assuming that we are running at EL1.
140///
141/// # Safety
142///
143/// This function doesn't follow the standard aarch64 calling convention. It must only be called
144/// from assembly code, early in the boot process.
145///
146/// Expects the MAIR value in x8, the SCTLR value in x9, the TCR value in x10 and the root pagetable
147/// address in x11.
148///
149/// Clobbers x8-x9.
150#[doc(hidden)]
151#[unsafe(naked)]
152pub unsafe extern "C" fn __enable_mmu_el1() {
153    naked_asm!(
154        // Load and apply the memory management configuration, ready to enable MMU and
155        // caches.
156        "msr mair_el1, x8",
157        "msr ttbr0_el1, x11",
158        // Copy the supported PA range into TCR_EL1.IPS.
159        "mrs x8, id_aa64mmfr0_el1",
160        "bfi x10, x8, #32, #4",
161        "msr tcr_el1, x10",
162        // Ensure everything before this point has completed, then invalidate any
163        // potentially stale local TLB entries before they start being used.
164        "isb",
165        "tlbi vmalle1",
166        "ic iallu",
167        "dsb nsh",
168        "isb",
169        // Configure SCTLR_EL1 to enable MMU and cache and don't proceed until this has
170        // completed.
171        "msr sctlr_el1, x9",
172        "isb",
173        "ret"
174    );
175}
176
177/// Enables the MMU and caches, assuming that we are running at EL2.
178///
179/// # Safety
180///
181/// This function doesn't follow the standard aarch64 calling convention. It must only be called
182/// from assembly code, early in the boot process.
183///
184/// Expects the MAIR value in x8, the SCTLR value in x9, the TCR value in x10 and the root pagetable
185/// address in x11.
186///
187/// Clobbers x8-x9.
188#[doc(hidden)]
189#[unsafe(naked)]
190pub unsafe extern "C" fn __enable_mmu_el2() {
191    naked_asm!(
192        // Load and apply the memory management configuration, ready to enable MMU and
193        // caches.
194        "msr mair_el2, x8",
195        "msr ttbr0_el2, x11",
196        // Copy the supported PA range into TCR_EL2.IPS.
197        "mrs x8, id_aa64mmfr0_el1",
198        "bfi x10, x8, #32, #4",
199        "msr tcr_el2, x10",
200        // Ensure everything before this point has completed, then invalidate any
201        // potentially stale local TLB entries before they start being used.
202        "isb",
203        "tlbi vmalle1",
204        "ic iallu",
205        "dsb nsh",
206        "isb",
207        // Configure SCTLR_EL2 to enable MMU and cache and don't proceed until this has
208        // completed.
209        "msr sctlr_el2, x9",
210        "isb",
211        "ret"
212    );
213}
214
215/// Enables the MMU and caches, assuming that we are running at EL3.
216///
217/// # Safety
218///
219/// This function doesn't follow the standard aarch64 calling convention. It must only be called
220/// from assembly code, early in the boot process.
221///
222/// Expects the MAIR value in x8, the SCTLR value in x9, the TCR value in x10 and the root pagetable
223/// address in x11.
224///
225/// Clobbers x8-x9.
226#[doc(hidden)]
227#[unsafe(naked)]
228pub unsafe extern "C" fn __enable_mmu_el3() {
229    naked_asm!(
230        // Load and apply the memory management configuration, ready to enable MMU and
231        // caches.
232        "msr mair_el3, x8",
233        "msr ttbr0_el3, x11",
234        // Copy the supported PA range into TCR_EL3.IPS.
235        "mrs x8, id_aa64mmfr0_el1",
236        "bfi x10, x8, #32, #4",
237        "msr tcr_el3, x10",
238        // Ensure everything before this point has completed, then invalidate any
239        // potentially stale local TLB entries before they start being used.
240        "isb",
241        "tlbi vmalle1",
242        "ic iallu",
243        "dsb nsh",
244        "isb",
245        // Configure SCTLR_EL3 to enable MMU and cache and don't proceed until this has
246        // completed.
247        "msr sctlr_el3, x9",
248        "isb",
249        "ret"
250    );
251}
252
253/// Generates assembly code to enable the MMU and caches with the given initial pagetable before any
254/// Rust code is run.
255///
256/// This may be used indirectly via the [`initial_pagetable!`] macro.
257#[cfg(feature = "el1")]
258#[macro_export]
259macro_rules! enable_mmu {
260    ($pagetable:path, $mair:expr, $sctlr:expr, $tcr:expr) => {
261        core::arch::global_asm!(
262            r".macro mov_i, reg:req, imm:req",
263                r"movz \reg, :abs_g3:\imm",
264                r"movk \reg, :abs_g2_nc:\imm",
265                r"movk \reg, :abs_g1_nc:\imm",
266                r"movk \reg, :abs_g0_nc:\imm",
267            r".endm",
268
269            ".section .init, \"ax\"",
270            ".global enable_mmu",
271            "enable_mmu:",
272                "mov_i x8, {MAIR_VALUE}",
273                "mov_i x9 {SCTLR_VALUE}",
274                "mov_i x10, {TCR_VALUE}",
275                "adrp x11, {pagetable}",
276
277                "b {enable_mmu_el1}",
278
279            ".purgem mov_i",
280            MAIR_VALUE = const $mair,
281            SCTLR_VALUE = const $sctlr,
282            TCR_VALUE = const $tcr,
283            pagetable = sym $pagetable,
284            enable_mmu_el1 = sym $crate::__private::__enable_mmu_el1,
285        );
286    };
287    ($pagetable:path) => {
288        $crate::enable_mmu!($pagetable, $crate::DEFAULT_MAIR, $crate::DEFAULT_SCTLR, $crate::DEFAULT_TCR_EL1);
289    };
290}
291
292/// Generates assembly code to enable the MMU and caches with the given initial pagetable before any
293/// Rust code is run.
294///
295/// This may be used indirectly via the [`initial_pagetable!`] macro.
296#[cfg(feature = "el2")]
297#[macro_export]
298macro_rules! enable_mmu {
299    ($pagetable:path, $mair:expr, $sctlr:expr, $tcr:expr) => {
300        core::arch::global_asm!(
301            r".macro mov_i, reg:req, imm:req",
302                r"movz \reg, :abs_g3:\imm",
303                r"movk \reg, :abs_g2_nc:\imm",
304                r"movk \reg, :abs_g1_nc:\imm",
305                r"movk \reg, :abs_g0_nc:\imm",
306            r".endm",
307
308            ".section .init, \"ax\"",
309            ".global enable_mmu",
310            "enable_mmu:",
311                "mov_i x8, {MAIR_VALUE}",
312                "mov_i x9, {SCTLR_VALUE}",
313                "mov_i x10, {TCR_VALUE}",
314                "adrp x11, {pagetable}",
315
316                "b {enable_mmu_el2}",
317
318            ".purgem mov_i",
319            MAIR_VALUE = const $mair,
320            SCTLR_VALUE = const $sctlr,
321            TCR_VALUE = const $tcr,
322            pagetable = sym $pagetable,
323            enable_mmu_el2 = sym $crate::__private::__enable_mmu_el2,
324        );
325    };
326    ($pagetable:path) => {
327        $crate::enable_mmu!($pagetable, $crate::DEFAULT_MAIR, $crate::DEFAULT_SCTLR, $crate::DEFAULT_TCR_EL2);
328    };
329}
330
331/// Generates assembly code to enable the MMU and caches with the given initial pagetable before any
332/// Rust code is run.
333///
334/// This may be used indirectly via the [`initial_pagetable!`] macro.
335#[cfg(feature = "el3")]
336#[macro_export]
337macro_rules! enable_mmu {
338    ($pagetable:path, $mair:expr, $sctlr:expr, $tcr:expr) => {
339        core::arch::global_asm!(
340            r".macro mov_i, reg:req, imm:req",
341                r"movz \reg, :abs_g3:\imm",
342                r"movk \reg, :abs_g2_nc:\imm",
343                r"movk \reg, :abs_g1_nc:\imm",
344                r"movk \reg, :abs_g0_nc:\imm",
345            r".endm",
346
347            ".section .init, \"ax\"",
348            ".global enable_mmu",
349            "enable_mmu:",
350                "mov_i x8, {MAIR_VALUE}",
351                "mov_i x9, {SCTLR_VALUE}",
352                "mov_i x10, {TCR_VALUE}",
353                "adrp x11, {pagetable}",
354
355                "b {enable_mmu_el3}",
356
357            ".purgem mov_i",
358            MAIR_VALUE = const $mair,
359            SCTLR_VALUE = const $sctlr,
360            TCR_VALUE = const $tcr,
361            pagetable = sym $pagetable,
362            enable_mmu_el3 = sym $crate::__private::__enable_mmu_el3,
363        );
364    };
365    ($pagetable:path) => {
366        $crate::enable_mmu!($pagetable, $crate::DEFAULT_MAIR, $crate::DEFAULT_SCTLR, $crate::DEFAULT_TCR_EL3);
367    };
368}
369
370/// Generates assembly code to enable the MMU and caches with the given initial pagetable before any
371/// Rust code is run.
372///
373/// This may be used indirectly via the [`initial_pagetable!`] macro.
374#[cfg(not(any(feature = "el1", feature = "el2", feature = "el3")))]
375#[macro_export]
376macro_rules! enable_mmu {
377    ($pagetable:path, $mair:expr, $sctlr:expr, $tcr_el1:expr, $tcr_el2:expr, $tcr_el3:expr) => {
378        core::arch::global_asm!(
379            r".macro mov_i, reg:req, imm:req",
380                r"movz \reg, :abs_g3:\imm",
381                r"movk \reg, :abs_g2_nc:\imm",
382                r"movk \reg, :abs_g1_nc:\imm",
383                r"movk \reg, :abs_g0_nc:\imm",
384            r".endm",
385
386            ".section .init, \"ax\"",
387            ".global enable_mmu",
388            "enable_mmu:",
389                "mov_i x8, {MAIR_VALUE}",
390                "mov_i x9, {SCTLR_VALUE}",
391                "adrp x11, {pagetable}",
392
393                "mrs x12, CurrentEL",
394                "ubfx x12, x12, #2, #2",
395
396                "cmp x12, #3",
397                "b.ne 0f",
398                "mov_i x10, {TCR_EL3_VALUE}",
399                "b {enable_mmu_el3}",
400            "0:",
401                "cmp x12, #2",
402                "b.ne 1f",
403                "mov_i x10, {TCR_EL2_VALUE}",
404                "b {enable_mmu_el2}",
405            "1:",
406                "mov_i x10, {TCR_EL1_VALUE}",
407                "b {enable_mmu_el1}",
408
409            ".purgem mov_i",
410            MAIR_VALUE = const $mair,
411            SCTLR_VALUE = const $sctlr,
412            TCR_EL1_VALUE = const $tcr_el1,
413            TCR_EL2_VALUE = const $tcr_el2,
414            TCR_EL3_VALUE = const $tcr_el3,
415            pagetable = sym $pagetable,
416            enable_mmu_el1 = sym $crate::__private::__enable_mmu_el1,
417            enable_mmu_el2 = sym $crate::__private::__enable_mmu_el2,
418            enable_mmu_el3 = sym $crate::__private::__enable_mmu_el3,
419        );
420    };
421    ($pagetable:path) => {
422        $crate::enable_mmu!(
423            $pagetable,
424            $crate::DEFAULT_MAIR,
425            $crate::DEFAULT_SCTLR,
426            $crate::DEFAULT_TCR_EL1,
427            $crate::DEFAULT_TCR_EL2,
428            $crate::DEFAULT_TCR_EL3
429        );
430    };
431}
432
433/// A hardcoded pagetable.
434#[repr(C, align(4096))]
435pub struct InitialPagetable(pub [usize; 512]);