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/// Flushes the data cache line (64 bytes) at the given virtual address
159#[inline]
160pub fn flush_dcache_line(vaddr: VirtAddr) {
161 unsafe { asm!("dc ivac, {0:x}; dsb sy; isb", in(reg) vaddr.as_usize()) };
162}
163
164/// Writes exception vector base address register (`VBAR_EL1`).
165///
166/// # Safety
167///
168/// This function is unsafe as it changes the exception handling behavior of the
169/// current CPU.
170#[inline]
171pub unsafe fn write_exception_vector_base(vbar: usize) {
172 #[cfg(not(feature = "arm-el2"))]
173 VBAR_EL1.set(vbar as _);
174 #[cfg(feature = "arm-el2")]
175 VBAR_EL2.set(vbar as _);
176}
177
178/// Reads the thread pointer of the current CPU (`TPIDR_EL0`).
179///
180/// It is used to implement TLS (Thread Local Storage).
181#[inline]
182pub fn read_thread_pointer() -> usize {
183 TPIDR_EL0.get() as usize
184}
185
186/// Writes the thread pointer of the current CPU (`TPIDR_EL0`).
187///
188/// It is used to implement TLS (Thread Local Storage).
189///
190/// # Safety
191///
192/// This function is unsafe as it changes the current CPU states.
193#[inline]
194pub unsafe fn write_thread_pointer(tpidr_el0: usize) {
195 TPIDR_EL0.set(tpidr_el0 as _)
196}
197
198/// Enable FP/SIMD instructions by setting the `FPEN` field in `CPACR_EL1`.
199#[inline]
200pub fn enable_fp() {
201 CPACR_EL1.write(CPACR_EL1::FPEN::TrapNothing);
202 barrier::isb(barrier::SY);
203}
204
205#[cfg(feature = "uspace")]
206core::arch::global_asm!(include_str!("user_copy.S"));
207
208#[cfg(feature = "uspace")]
209unsafe extern "C" {
210 /// Copies data from source to destination, where addresses may be in user
211 /// space. Equivalent to memcpy.
212 ///
213 /// # Safety
214 /// This function is unsafe because it performs raw memory operations.
215 ///
216 /// # Returns
217 /// Returns the number of bytes not copied. This means 0 indicates success,
218 /// while a value > 0 indicates failure.
219 pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
220}