Skip to main content

ax_cpu/arch/aarch64/
mmu.rs

1// SPDX-License-Identifier: MPL-2.0
2// Stage-one setup migrated from someboot/src/arch/aarch64/el1 and el2.
3// Original package author: 周睿 <zrufo747@outlook.com>.
4//! Explicit AArch64 translation and exception regimes.
5
6/// EL1 stage-one translation and native exception registers.
7#[derive(Clone, Copy, Debug, Default)]
8pub struct El1;
9
10/// Non-VHE EL2 stage-one translation and native exception registers.
11/// Guest stage-two translation is a separate VTTBR/VTCR capability.
12#[derive(Clone, Copy, Debug, Default)]
13pub struct El2;
14
15impl El1 {
16    /// Invalidates an EL1 stage-one translation on this CPU, across all ASIDs.
17    pub fn flush_tlb(address: Option<crate::VirtAddr>) {
18        // SAFETY: these privileged operations touch only local translation
19        // caches; table stores precede invalidation and completion precedes fetch.
20        unsafe {
21            if let Some(address) = address {
22                let operand = (address.as_usize() >> 12) & ((1usize << 44) - 1);
23                core::arch::asm!("dsb nshst; tlbi vaae1, {}; dsb nsh; isb", in(reg) operand);
24            } else {
25                core::arch::asm!("dsb nshst; tlbi vmalle1; dsb nsh; isb");
26            }
27        }
28    }
29}
30
31impl El2 {
32    /// Invalidates a non-VHE EL2 stage-one translation on this CPU.
33    /// This does not invalidate guest stage-two translations.
34    pub fn flush_tlb(address: Option<crate::VirtAddr>) {
35        // SAFETY: local EL2 translation maintenance has the same publication
36        // and completion ordering as EL1 but names its own translation regime.
37        unsafe {
38            if let Some(address) = address {
39                let operand = (address.as_usize() >> 12) & ((1usize << 44) - 1);
40                core::arch::asm!("dsb nshst; tlbi vae2, {}; dsb nsh; isb", in(reg) operand);
41            } else {
42                core::arch::asm!("dsb nshst; tlbi alle2; dsb nsh; isb");
43            }
44        }
45    }
46}
47
48impl El1 {
49    /// Returns the EL1 kernel's TTBR1 base, excluding its ASID field.
50    pub fn read_kernel_page_table() -> crate::PhysAddr {
51        use aarch64_cpu::registers::{Readable, TTBR1_EL1};
52        crate::PhysAddr::from_usize((TTBR1_EL1.get() & 0x0000_ffff_ffff_f000) as usize)
53    }
54    /// Replaces the EL1 kernel root without invalidating cached translations.
55    ///
56    /// # Safety
57    /// The caller must retain the new tables and all current code, stack and
58    /// data mappings at EL1, and perform required translation synchronization.
59    pub unsafe fn write_kernel_page_table(root: crate::PhysAddr) {
60        use aarch64_cpu::registers::{TTBR1_EL1, Writeable};
61        TTBR1_EL1.set(root.as_usize() as u64);
62    }
63    /// Returns the configured EL1 ASID capacity.
64    pub fn address_space_tag_capacity() -> u32 {
65        super::asm::address_space_tag_capacity()
66    }
67    /// Invalidates all EL1 translations intersecting a local byte range.
68    pub fn flush_tlb_range(start: crate::VirtAddr, size: usize) {
69        crate::mmu::flush_range_with(start, size, Self::flush_tlb);
70    }
71}
72
73impl El2 {
74    /// Returns the non-VHE EL2 kernel's TTBR0 base.
75    pub fn read_kernel_page_table() -> crate::PhysAddr {
76        use aarch64_cpu::registers::{Readable, TTBR0_EL2};
77        crate::PhysAddr::from_usize((TTBR0_EL2.get() & 0x0000_ffff_ffff_f000) as usize)
78    }
79    /// Replaces the non-VHE EL2 stage-one root without invalidation.
80    ///
81    /// # Safety
82    /// The caller must retain the tables and all active EL2 code, stack and
83    /// data mappings and perform required translation synchronization.
84    pub unsafe fn write_kernel_page_table(root: crate::PhysAddr) {
85        use aarch64_cpu::registers::{TTBR0_EL2, Writeable};
86        TTBR0_EL2.set(root.as_usize() as u64);
87    }
88    /// Non-VHE EL2 native translations have no userspace ASID allocation.
89    pub const fn address_space_tag_capacity() -> u32 {
90        1
91    }
92    /// Invalidates all non-VHE EL2 translations intersecting a local byte range.
93    pub fn flush_tlb_range(start: crate::VirtAddr, size: usize) {
94        crate::mmu::flush_range_with(start, size, Self::flush_tlb);
95    }
96}
97
98impl El1 {
99    /// Programs the existing four-level, 4-KiB stage-one geometry and MAIR.
100    ///
101    /// # Safety
102    /// The caller must own this CPU with IRQs masked before enabling this
103    /// translation regime. Existing translations must not depend on the old
104    /// configuration. MAIR slots must agree with every installed descriptor.
105    pub unsafe fn configure_stage1(mair: u64) {
106        use aarch64_cpu::{asm::barrier, registers::*};
107        MAIR_EL1.set(mair);
108        // Enable 4-KiB, 48-bit virtual geometry with a supported physical range.
109        const VADDR_SIZE: u64 = 48;
110        const T0SZ: u64 = 64 - VADDR_SIZE;
111
112        let tcr_flags0 = TCR_EL1::EPD0::EnableTTBR0Walks
113            + TCR_EL1::TG0::KiB_4
114            + TCR_EL1::SH0::Inner
115            + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
116            + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
117            + TCR_EL1::T0SZ.val(T0SZ);
118        let tcr_flags1 = TCR_EL1::EPD1::EnableTTBR1Walks
119            + TCR_EL1::TG1::KiB_4
120            + TCR_EL1::SH1::Inner
121            + TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
122            + TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable
123            + TCR_EL1::T1SZ.val(T0SZ);
124        // Configure the widest ASID mode implemented by this CPU. Runtime tag
125        // allocation observes this TCR field instead of assuming that a hardware
126        // capability has already been enabled by the boot path.
127        let asid_size = if ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::ASIDBits) == 2 {
128            TCR_EL1::AS::ASID16Bits
129        } else {
130            TCR_EL1::AS::ASID8Bits
131        };
132        // The descriptors implemented here encode at most 48 physical bits.
133        // Do not request a larger output size than this CPU implements.
134        let physical_range = (ID_AA64MMFR0_EL1.get() & 15).min(5);
135        TCR_EL1.write(TCR_EL1::IPS.val(physical_range) + asid_size + tcr_flags0 + tcr_flags1);
136
137        Self::flush_tlb(None);
138        barrier::dsb(barrier::SY);
139        barrier::isb(barrier::SY);
140    }
141}
142
143impl El1 {
144    /// Returns whether this translation regime's MMU is enabled.
145    pub fn is_mmu_enabled() -> bool {
146        use aarch64_cpu::registers::*;
147        SCTLR_EL1.is_set(SCTLR_EL1::M)
148    }
149    /// Enables translation and native cache access for this regime.
150    ///
151    /// # Safety
152    /// Valid roots, MAIR and geometry must be installed. The new mappings
153    /// must retain current code, stack and data until the owner's next handoff.
154    pub unsafe fn enable_mmu_and_caches() {
155        use aarch64_cpu::{asm::barrier, registers::*};
156
157        SCTLR_EL1.modify(
158            SCTLR_EL1::M::Enable
159                + SCTLR_EL1::C::Cacheable
160                + SCTLR_EL1::I::Cacheable
161                + SCTLR_EL1::UCT::DontTrap
162                + SCTLR_EL1::DZE::DontTrap
163                + SCTLR_EL1::UCI::DontTrap,
164        );
165        SCTLR_EL1.set(SCTLR_EL1.get() | (1 << 23));
166        Self::flush_tlb(None);
167        barrier::dsb(barrier::SY);
168        barrier::isb(barrier::SY);
169    }
170}
171
172impl El2 {
173    /// Programs the existing four-level, 4-KiB stage-one geometry and MAIR.
174    ///
175    /// # Safety
176    /// The caller must own this CPU with IRQs masked before enabling this
177    /// translation regime. Existing translations must not depend on the old
178    /// configuration. MAIR slots must agree with every installed descriptor.
179    pub unsafe fn configure_stage1(mair: u64) {
180        use aarch64_cpu::{asm::barrier, registers::*};
181        MAIR_EL2.set(mair);
182        // Enable 4-KiB, 48-bit virtual geometry with a supported physical range.
183        const VADDR_SIZE: u64 = 48;
184        const T0SZ: u64 = 64 - VADDR_SIZE;
185
186        // Note: TCR_EL2 only has one set of translation controls (T0SZ, TG0)
187        // TTBR1_EL2 does not exist in ARMv8 architecture
188        let tcr_flags0 = TCR_EL2::T0SZ.val(T0SZ)
189            + TCR_EL2::TG0::KiB_4
190            + TCR_EL2::SH0::Inner
191            + TCR_EL2::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
192            + TCR_EL2::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable;
193
194        let physical_range = (ID_AA64MMFR0_EL1.get() & 15).min(5);
195        TCR_EL2.write(TCR_EL2::PS.val(physical_range) + tcr_flags0);
196
197        Self::flush_tlb_inner_shareable(None);
198        barrier::dsb(barrier::SY);
199        barrier::isb(barrier::SY);
200    }
201}
202
203impl El2 {
204    /// Returns whether this translation regime's MMU is enabled.
205    pub fn is_mmu_enabled() -> bool {
206        use aarch64_cpu::registers::*;
207        SCTLR_EL2.is_set(SCTLR_EL2::M)
208    }
209    /// Enables translation and native cache access for this regime.
210    ///
211    /// # Safety
212    /// Valid roots, MAIR and geometry must be installed. The new mappings
213    /// must retain current code, stack and data until the owner's next handoff.
214    pub unsafe fn enable_mmu_and_caches() {
215        use aarch64_cpu::{asm::barrier, registers::*};
216
217        SCTLR_EL2.modify(SCTLR_EL2::M::Enable + SCTLR_EL2::C::Cacheable + SCTLR_EL2::I::Cacheable);
218        Self::flush_tlb(None);
219        barrier::dsb(barrier::SY);
220        barrier::isb(barrier::SY);
221    }
222}
223
224impl El1 {
225    /// Invalidates stage-one translations throughout the inner-shareable domain.
226    pub fn flush_tlb_inner_shareable(address: Option<crate::VirtAddr>) {
227        // SAFETY: publish table stores before the broadcast operation and wait
228        // for all affected walks before permitting subsequent instruction fetch.
229        unsafe {
230            if let Some(address) = address {
231                let operand = (address.as_usize() >> 12) & ((1usize << 44) - 1);
232                core::arch::asm!("dsb ishst; tlbi vaae1is, {}; dsb ish; isb", in(reg) operand);
233            } else {
234                core::arch::asm!("dsb ishst; tlbi vmalle1is; dsb ish; isb");
235            }
236        }
237    }
238}
239
240impl El2 {
241    /// Invalidates stage-one translations throughout the inner-shareable domain.
242    pub fn flush_tlb_inner_shareable(address: Option<crate::VirtAddr>) {
243        // SAFETY: publish table stores before the broadcast operation and wait
244        // for all affected walks before permitting subsequent instruction fetch.
245        unsafe {
246            if let Some(address) = address {
247                let operand = (address.as_usize() >> 12) & ((1usize << 44) - 1);
248                core::arch::asm!("dsb ishst; tlbi vae2is, {}; dsb ish; isb", in(reg) operand);
249            } else {
250                core::arch::asm!("dsb ishst; tlbi alle2is; dsb ish; isb");
251            }
252        }
253    }
254}
255
256fn decode_ttbr(value: u64) -> crate::mmu::HardwareAddressSpace {
257    crate::mmu::HardwareAddressSpace::new(
258        crate::PhysAddr::from_usize((value & 0x0000_ffff_ffff_f000) as usize),
259        (value >> 48) as u16,
260    )
261}
262
263fn encode_ttbr(space: crate::mmu::HardwareAddressSpace) -> u64 {
264    space.root().as_usize() as u64 | (u64::from(space.hardware_tag()) << 48)
265}
266
267impl El1 {
268    /// Reads the kernel TTBR1 base and ASID together.
269    pub fn read_kernel_address_space() -> crate::mmu::HardwareAddressSpace {
270        use aarch64_cpu::registers::*;
271        decode_ttbr(TTBR1_EL1.get())
272    }
273    /// Reads the lower-address TTBR0 base and ASID together.
274    pub fn read_user_address_space() -> crate::mmu::HardwareAddressSpace {
275        use aarch64_cpu::registers::*;
276        decode_ttbr(TTBR0_EL1.get())
277    }
278    /// Installs a kernel TTBR1 root and ASID without implicit invalidation.
279    ///
280    /// # Safety
281    /// The aligned root and supported ASID must remain owned while active.
282    /// Current mappings must survive the write; the owner arranges TLB ordering.
283    pub unsafe fn write_kernel_address_space(space: crate::mmu::HardwareAddressSpace) {
284        use aarch64_cpu::registers::*;
285        TTBR1_EL1.set(encode_ttbr(space));
286    }
287    /// Installs a lower-address TTBR0 root and ASID without implicit invalidation.
288    ///
289    /// # Safety
290    /// The aligned root and supported ASID must remain owned while active.
291    /// No in-flight lower-address access may use the retired mapping.
292    pub unsafe fn write_user_address_space(space: crate::mmu::HardwareAddressSpace) {
293        use aarch64_cpu::registers::*;
294        TTBR0_EL1.set(encode_ttbr(space));
295    }
296}