Skip to main content

ax_cpu/aarch64/
asm.rs

1//! Wrapper functions for assembly instructions.
2
3use core::arch::asm;
4
5use aarch64_cpu::{asm::barrier, registers::*};
6use ax_memory_addr::{PhysAddr, VirtAddr};
7
8/// Allows the current CPU to respond to interrupts.
9///
10/// In AArch64, it unmasks IRQs by clearing the I bit in the `DAIF` register.
11#[inline]
12pub fn enable_irqs() {
13    unsafe { asm!("msr daifclr, #2") };
14}
15
16/// Makes the current CPU to ignore interrupts.
17///
18/// In AArch64, it masks IRQs by setting the I bit in the `DAIF` register.
19#[inline]
20pub fn disable_irqs() {
21    unsafe { asm!("msr daifset, #2") };
22}
23
24/// Returns whether the current CPU is allowed to respond to interrupts.
25///
26/// In AArch64, it checks the I bit in the `DAIF` register.
27#[inline]
28pub fn irqs_enabled() -> bool {
29    !DAIF.matches_all(DAIF::I::Masked)
30}
31
32/// Relaxes the current CPU and waits for interrupts.
33///
34/// It must be called with interrupts enabled, otherwise it will never return.
35#[inline]
36pub fn wait_for_irqs() {
37    aarch64_cpu::asm::wfi();
38}
39
40/// Halt the current CPU.
41#[inline]
42pub fn halt() {
43    disable_irqs();
44    aarch64_cpu::asm::wfi(); // should never return
45}
46
47/// Reads the current page table root register for kernel space (`TTBR1_EL1`).
48///
49/// When the "arm-el2" feature is enabled,
50/// TTBR0_EL2 is dedicated to the Hypervisor's Stage-2 page table base address.
51///
52/// Returns the physical address of the page table root.
53#[inline]
54pub fn read_kernel_page_table() -> PhysAddr {
55    #[cfg(not(feature = "arm-el2"))]
56    let root = TTBR1_EL1.get();
57
58    #[cfg(feature = "arm-el2")]
59    let root = TTBR0_EL2.get();
60
61    pa!(root as usize)
62}
63
64/// Reads the current page table root register for user space (`TTBR0_EL1`).
65///
66/// When the "arm-el2" feature is enabled, for user-mode programs,
67/// virtualization is completely transparent to them, so there is no need to modify
68///
69/// Returns the physical address of the page table root.
70#[inline]
71pub fn read_user_page_table() -> PhysAddr {
72    let root = TTBR0_EL1.get();
73    pa!(root as usize)
74}
75
76/// Writes the register to update the current page table root for kernel space
77/// (`TTBR1_EL1`).
78///
79/// When the "arm-el2" feature is enabled,
80/// TTBR0_EL2 is dedicated to the Hypervisor's Stage-2 page table base address.
81///
82/// Note that the TLB is **NOT** flushed after this operation.
83///
84/// # Safety
85///
86/// This function is unsafe as it changes the virtual memory address space.
87#[inline]
88pub unsafe fn write_kernel_page_table(root_paddr: PhysAddr) {
89    #[cfg(not(feature = "arm-el2"))]
90    {
91        // kernel space page table use TTBR1 (0xffff_0000_0000_0000..0xffff_ffff_ffff_ffff)
92        TTBR1_EL1.set(root_paddr.as_usize() as _);
93    }
94
95    #[cfg(feature = "arm-el2")]
96    {
97        // kernel space page table at EL2 use TTBR0_EL2 (0x0000_0000_0000_0000..0x0000_ffff_ffff_ffff)
98        TTBR0_EL2.set(root_paddr.as_usize() as _);
99    }
100}
101
102/// Writes the register to update the current page table root for user space
103/// (`TTBR1_EL0`).
104/// When the "arm-el2" feature is enabled, for user-mode programs,
105/// virtualization is completely transparent to them, so there is no need to modify
106///
107/// Note that the TLB is **NOT** flushed after this operation.
108///
109/// # Safety
110///
111/// This function is unsafe as it changes the virtual memory address space.
112#[inline]
113pub unsafe fn write_user_page_table(root_paddr: PhysAddr) {
114    TTBR0_EL1.set(root_paddr.as_usize() as _);
115}
116
117/// Flushes the TLB.
118///
119/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB
120/// entry that maps the given virtual address.
121#[inline]
122pub fn flush_tlb(vaddr: Option<VirtAddr>) {
123    if let Some(vaddr) = vaddr {
124        const VA_MASK: usize = (1 << 44) - 1; // VA[55:12] => bits[43:0]
125        let operand = (vaddr.as_usize() >> 12) & VA_MASK;
126
127        #[cfg(not(feature = "arm-el2"))]
128        unsafe {
129            // TLB Invalidate by VA, All ASID, EL1, Inner Shareable
130            asm!("tlbi vaae1is, {}; dsb sy; isb", in(reg) operand)
131        }
132        #[cfg(feature = "arm-el2")]
133        unsafe {
134            // TLB Invalidate by VA, EL2, Inner Shareable
135            asm!("tlbi vae2is, {}; dsb sy; isb", in(reg) operand)
136        }
137    } else {
138        // flush the entire TLB
139        #[cfg(not(feature = "arm-el2"))]
140        unsafe {
141            // TLB Invalidate by VMID, All at stage 1, EL1
142            asm!("dsb sy; isb; tlbi vmalle1; dsb sy; isb")
143        }
144        #[cfg(feature = "arm-el2")]
145        unsafe {
146            // TLB Invalidate All, EL2
147            asm!("tlbi alle2; dsb sy; isb")
148        }
149    }
150}
151
152/// Flushes the entire instruction cache.
153#[inline]
154pub fn flush_icache_all() {
155    unsafe { asm!("ic iallu; dsb sy; isb") };
156}
157
158#[inline]
159fn read_ctr_el0() -> u64 {
160    let value;
161    unsafe {
162        asm!("mrs {}, ctr_el0", out(reg) value);
163    }
164    value
165}
166
167/// Reads the data cache line size from `CTR_EL0` and returns it in bytes.
168#[inline]
169pub fn dcache_line_size_from_ctr() -> usize {
170    let ctr = read_ctr_el0();
171
172    // CTR_EL0.DminLine: bits [19:16]
173    // bytes = 4 << DminLine
174    let dminline = ((ctr >> 16) & 0xf) as usize;
175
176    4usize << dminline
177}
178
179/// Reads the instruction cache line size from `CTR_EL0` and returns it in bytes.
180#[inline]
181pub fn icache_line_size_from_ctr() -> usize {
182    let ctr = read_ctr_el0();
183
184    // CTR_EL0.IminLine: bits [3:0]
185    // bytes = 4 << IminLine
186    let iminline = (ctr & 0xf) as usize;
187
188    4usize << iminline
189}
190
191/// Cleans a data cache range to the point of unification.
192#[inline]
193pub fn clean_dcache_range_to_pou(vaddr: VirtAddr, size: usize) {
194    if size == 0 {
195        return;
196    }
197
198    let line_size = dcache_line_size_from_ctr();
199    let start = vaddr.as_usize() & !(line_size - 1);
200    let end = (vaddr.as_usize() + size + line_size - 1) & !(line_size - 1);
201
202    for line in (start..end).step_by(line_size) {
203        unsafe { asm!("dc cvau, {0:x}", in(reg) line) };
204    }
205
206    unsafe { asm!("dsb sy") };
207}
208
209/// Cleans and invalidates the data cache line that covers the given address.
210///
211/// This is useful for publishing small pieces of data to other agents that may
212/// observe memory outside the local D-cache, such as spin tables used to start
213/// secondary CPUs.
214#[inline]
215pub fn flush_dcache_line(vaddr: VirtAddr) {
216    unsafe { asm!("dc ivac, {0:x}; dsb sy; isb", in(reg) vaddr.as_usize()) };
217}
218
219/// Writes exception vector base address register (`VBAR_EL1`).
220///
221/// # Safety
222///
223/// This function is unsafe as it changes the exception handling behavior of the
224/// current CPU.
225#[inline]
226pub unsafe fn write_exception_vector_base(vbar: usize) {
227    #[cfg(not(feature = "arm-el2"))]
228    VBAR_EL1.set(vbar as _);
229    #[cfg(feature = "arm-el2")]
230    VBAR_EL2.set(vbar as _);
231}
232
233/// Reads the thread pointer of the current CPU (`TPIDR_EL0`).
234///
235/// It is used to implement TLS (Thread Local Storage).
236#[inline]
237pub fn read_thread_pointer() -> usize {
238    TPIDR_EL0.get() as usize
239}
240
241/// Writes the thread pointer of the current CPU (`TPIDR_EL0`).
242///
243/// It is used to implement TLS (Thread Local Storage).
244///
245/// # Safety
246///
247/// This function is unsafe as it changes the current CPU states.
248#[inline]
249pub unsafe fn write_thread_pointer(tpidr_el0: usize) {
250    TPIDR_EL0.set(tpidr_el0 as _)
251}
252
253/// Enable FP/SIMD instructions by setting the `FPEN` field in `CPACR_EL1`.
254#[inline]
255pub fn enable_fp() {
256    CPACR_EL1.write(CPACR_EL1::FPEN::TrapNothing);
257    barrier::isb(barrier::SY);
258}
259
260#[cfg(feature = "uspace")]
261core::arch::global_asm!(include_str!("user_copy.S"));
262
263#[cfg(feature = "uspace")]
264unsafe extern "C" {
265    /// Copies data from source to destination, where addresses may be in user
266    /// space. Equivalent to memcpy.
267    ///
268    /// # Safety
269    /// This function is unsafe because it performs raw memory operations.
270    ///
271    /// # Returns
272    /// Returns the number of bytes not copied. This means 0 indicates success,
273    /// while a value > 0 indicates failure.
274    pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
275}