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}