Skip to main content

ax_cpu/x86_64/
asm.rs

1//! Wrapper functions for assembly instructions.
2
3use core::arch::asm;
4#[cfg(not(feature = "host-test"))]
5use core::arch::x86_64::{__cpuid, __cpuid_count};
6#[cfg(all(feature = "host-test", not(target_os = "none")))]
7use core::cell::Cell;
8#[cfg(feature = "host-test")]
9use core::sync::atomic::{AtomicUsize, Ordering};
10
11#[cfg(not(feature = "host-test"))]
12use ax_memory_addr::MemoryAddr;
13use ax_memory_addr::{PhysAddr, VirtAddr};
14#[cfg(kernel_tls)]
15use x86::msr;
16#[cfg(not(feature = "host-test"))]
17use x86::{controlregs, tlb};
18#[cfg(not(all(feature = "host-test", not(target_os = "none"))))]
19use x86_64::instructions::interrupts;
20#[cfg(all(feature = "uspace", not(feature = "host-test")))]
21use x86_64::instructions::tlb::Pcid;
22#[cfg(not(feature = "host-test"))]
23use x86_64::instructions::tlb::{InvPcidCommand, flush_pcid};
24
25#[cfg(feature = "uspace")]
26use crate::InstalledAddressSpace;
27#[cfg(all(feature = "uspace", not(feature = "host-test")))]
28use crate::InstalledAddressSpaceMode;
29#[cfg(kernel_tls)]
30use crate::KernelTlsBase;
31
32#[cfg(not(feature = "host-test"))]
33const PCID_CAPACITY: u32 = 1 << 12;
34#[cfg(all(feature = "uspace", not(feature = "host-test")))]
35const CR3_NOFLUSH: u64 = 1 << 63;
36
37#[cfg(not(feature = "host-test"))]
38fn pcid_invpcid_supported() -> bool {
39    let basic = __cpuid(1);
40    let maximum = __cpuid(0).eax;
41    let extended = (maximum >= 7).then(|| __cpuid_count(7, 0));
42    basic.ecx & (1 << 17) != 0 && extended.is_some_and(|features| features.ebx & (1 << 10) != 0)
43}
44
45#[cfg(not(feature = "host-test"))]
46fn pcid_enabled() -> bool {
47    // SAFETY: this backend executes at CPL0.
48    unsafe { controlregs::cr4() }.contains(controlregs::Cr4::CR4_ENABLE_PCID)
49}
50
51#[cfg(all(feature = "uspace", not(feature = "host-test")))]
52fn ensure_pcid_enabled() -> bool {
53    if !pcid_invpcid_supported() {
54        return false;
55    }
56    // SAFETY: this backend executes at CPL0 with scheduling serialized.
57    let mut cr4 = unsafe { controlregs::cr4() };
58    if cr4.contains(controlregs::Cr4::CR4_ENABLE_PCID) {
59        return true;
60    }
61    if !cr4.contains(controlregs::Cr4::CR4_ENABLE_GLOBAL_PAGES) {
62        return false;
63    }
64    // Intel requires CR3[11:0] == 0 while CR4.PCIDE changes from 0 to 1.
65    // SAFETY: reading CR3 at CPL0 is well-defined.
66    if unsafe { controlregs::cr3() } & 0xfff != 0 {
67        return false;
68    }
69    cr4.insert(controlregs::Cr4::CR4_ENABLE_PCID);
70    // SAFETY: CPUID confirmed PCID and the CR3/PGE prerequisites above hold.
71    unsafe { controlregs::cr4_write(cr4) };
72    true
73}
74
75/// Returns the number of usable x86 PCID values, including reserved PCID 0.
76///
77/// Linux enables PCID only when PCID, INVPCID, and global pages are all
78/// available. Returning one selects the architecture-neutral full-flush path.
79pub fn address_space_tag_capacity(_cpu_count: usize) -> u32 {
80    #[cfg(feature = "host-test")]
81    {
82        1
83    }
84    #[cfg(not(feature = "host-test"))]
85    {
86        // SAFETY: this capability is queried after privileged CPU initialization.
87        let pge = unsafe { controlregs::cr4() }.contains(controlregs::Cr4::CR4_ENABLE_GLOBAL_PAGES);
88        if pge && pcid_invpcid_supported() {
89            PCID_CAPACITY
90        } else {
91            1
92        }
93    }
94}
95
96/// Installs one complete userspace identity into CR3.
97///
98/// Tagged installation invalidates the incoming PCID before a no-flush CR3
99/// write. This conservative per-install invalidation is the ownership boundary
100/// for tag reuse: an inactive stale translation can never become reachable
101/// when its address space is scheduled again. Unsupported CPUs use PCID 0 and
102/// a complete invalidation.
103///
104/// # Safety
105///
106/// The caller must own the current CPU with interrupts disabled and the root
107/// must remain alive for the complete activation lease.
108#[cfg(feature = "uspace")]
109pub unsafe fn install_user_address_space(address_space: InstalledAddressSpace) {
110    address_space.validate_architecture_support();
111    #[cfg(feature = "host-test")]
112    HOST_PAGE_TABLE_ROOT.store(address_space.root().as_usize(), Ordering::Release);
113    #[cfg(not(feature = "host-test"))]
114    {
115        let root = address_space.root().as_usize() as u64;
116        let tagged = matches!(address_space.mode(), InstalledAddressSpaceMode::Tagged)
117            && u32::from(address_space.hardware_tag()) < PCID_CAPACITY
118            && ensure_pcid_enabled();
119        if tagged {
120            let Ok(pcid) = Pcid::new(address_space.hardware_tag()) else {
121                // Constructor validation and the capacity check make this branch
122                // unreachable, but the fallback keeps an injected identity safe.
123                unsafe { controlregs::cr3_write(root) };
124                return;
125            };
126            // SAFETY: `ensure_pcid_enabled` confirmed INVPCID and CR4.PCIDE.
127            unsafe { flush_pcid(InvPcidCommand::Single(pcid)) };
128            // SAFETY: the root is aligned, PCID is 12-bit, and CR4.PCIDE is set.
129            unsafe {
130                controlregs::cr3_write(root | u64::from(address_space.hardware_tag()) | CR3_NOFLUSH)
131            };
132        } else {
133            if pcid_enabled() && pcid_invpcid_supported() {
134                // SAFETY: CPUID confirmed INVPCID; this also discharges CPU-offline
135                // and generation-rollover obligations for inactive PCIDs.
136                unsafe { flush_pcid(InvPcidCommand::All) };
137            }
138            // SAFETY: a zero-PCID CR3 write installs the validated aligned root.
139            unsafe { controlregs::cr3_write(root) };
140        }
141    }
142}
143
144#[cfg(feature = "host-test")]
145static HOST_PAGE_TABLE_ROOT: AtomicUsize = AtomicUsize::new(0);
146
147#[cfg(all(feature = "host-test", not(target_os = "none")))]
148std::thread_local! {
149    static HOST_IRQS_ENABLED: Cell<bool> = const { Cell::new(true) };
150}
151
152/// Allows the current CPU to respond to interrupts.
153#[inline]
154pub fn enable_irqs() {
155    #[cfg(all(feature = "host-test", not(target_os = "none")))]
156    HOST_IRQS_ENABLED.set(true);
157    #[cfg(not(all(feature = "host-test", not(target_os = "none"))))]
158    interrupts::enable();
159}
160
161/// Makes the current CPU to ignore interrupts.
162#[inline]
163pub fn disable_irqs() {
164    #[cfg(all(feature = "host-test", not(target_os = "none")))]
165    HOST_IRQS_ENABLED.set(false);
166    #[cfg(not(all(feature = "host-test", not(target_os = "none"))))]
167    interrupts::disable();
168}
169
170/// Returns whether the current CPU is allowed to respond to interrupts.
171#[inline]
172pub fn irqs_enabled() -> bool {
173    #[cfg(all(feature = "host-test", not(target_os = "none")))]
174    return HOST_IRQS_ENABLED.get();
175    #[cfg(not(all(feature = "host-test", not(target_os = "none"))))]
176    interrupts::are_enabled()
177}
178
179/// Relaxes the current CPU and waits for interrupts.
180///
181/// It must be called with interrupts enabled, otherwise it will never return.
182#[inline]
183pub fn wait_for_irqs() {
184    unsafe { asm!("hlt") }
185}
186
187/// Waits for an interrupt after the caller masks local IRQ delivery.
188///
189/// `STI` delays recognition of maskable interrupts until after the following
190/// `HLT`, so a pending wake cannot be consumed between enabling IRQs and
191/// entering the idle state. The function returns with local IRQs enabled.
192#[inline]
193pub fn wait_for_irqs_disabled() {
194    debug_assert!(!irqs_enabled());
195    unsafe { asm!("sti; hlt", options(nostack)) }
196}
197
198/// Halt the current CPU.
199#[inline]
200pub fn halt() {
201    disable_irqs();
202    wait_for_irqs(); // should never return
203}
204
205/// Reads the current page table root register for user space (`CR3`).
206///
207/// x86_64 does not have a separate page table root register for user and
208/// kernel space, so this operation is the same as [`read_kernel_page_table`].
209///
210/// Returns the physical address of the page table root.
211#[inline]
212pub fn read_user_page_table() -> PhysAddr {
213    #[cfg(feature = "host-test")]
214    return PhysAddr::from(HOST_PAGE_TABLE_ROOT.load(Ordering::Acquire));
215
216    #[cfg(not(feature = "host-test"))]
217    pa!(unsafe { controlregs::cr3() } as usize).align_down_4k()
218}
219
220/// Reads the current page table root register for kernel space (`CR3`).
221///
222/// x86_64 does not have a separate page table root register for user and
223/// kernel space, so this operation is the same as [`read_user_page_table`].
224///
225/// Returns the physical address of the page table root.
226#[inline]
227pub fn read_kernel_page_table() -> PhysAddr {
228    read_user_page_table()
229}
230
231/// Writes the register to update the current page table root for user space
232/// (`CR3`).
233///
234/// x86_64 does not have a separate page table root register for user
235/// and kernel space, so this operation is the same as [`write_kernel_page_table`].
236///
237/// Note that the TLB will be **flushed** after this operation.
238///
239/// # Safety
240///
241/// This function is unsafe as it changes the virtual memory address space.
242#[inline]
243pub unsafe fn write_user_page_table(root_paddr: PhysAddr) {
244    #[cfg(feature = "host-test")]
245    {
246        HOST_PAGE_TABLE_ROOT.store(root_paddr.as_usize(), Ordering::Release);
247    }
248    #[cfg(not(feature = "host-test"))]
249    unsafe {
250        controlregs::cr3_write(root_paddr.as_usize() as _)
251    }
252}
253
254/// Writes the register to update the current page table root for kernel space
255/// (`CR3`).
256///
257/// x86_64 does not have a separate page table root register for user
258/// and kernel space, so this operation is the same as [`write_user_page_table`].
259///
260/// Note that the TLB will be **flushed** after this operation.
261///
262/// # Safety
263///
264/// This function is unsafe as it changes the virtual memory address space.
265#[inline]
266pub unsafe fn write_kernel_page_table(root_paddr: PhysAddr) {
267    unsafe { write_user_page_table(root_paddr) }
268}
269
270/// Flushes the entire instruction cache.
271#[inline]
272pub fn flush_icache_all() {}
273
274/// Flushes the TLB.
275///
276/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB
277/// entry that maps the given virtual address.
278#[inline]
279pub fn flush_tlb(vaddr: Option<VirtAddr>) {
280    #[cfg(feature = "host-test")]
281    let _ = vaddr;
282    #[cfg(not(feature = "host-test"))]
283    {
284        if let Some(vaddr) = vaddr {
285            unsafe { tlb::flush(vaddr.into()) }
286        } else if pcid_enabled() && pcid_invpcid_supported() {
287            // SAFETY: CPUID confirmed INVPCID and CR4.PCIDE is enabled.
288            unsafe { flush_pcid(InvPcidCommand::All) }
289        } else {
290            unsafe { tlb::flush_all() }
291        }
292    }
293}
294
295/// Makes a page-table entry installed by the local page-fault handler visible
296/// before retrying the faulting instruction.
297///
298/// x86 does not cache invalid leaf entries, so the page-table write is enough.
299#[inline]
300pub fn update_mmu_cache(_vaddr: VirtAddr) {}
301
302/// Reads the current kernel task's TLS base (`FS_BASE`).
303///
304/// It is used to implement TLS (Thread Local Storage).
305#[inline]
306#[cfg(kernel_tls)]
307pub fn read_thread_pointer() -> KernelTlsBase {
308    KernelTlsBase::new(unsafe { msr::rdmsr(msr::IA32_FS_BASE) as usize })
309}
310
311/// Writes the current kernel task's TLS base (`FS_BASE`).
312///
313/// It is used to implement TLS (Thread Local Storage).
314///
315/// # Safety
316///
317/// This function is unsafe as it changes the CPU states.
318#[inline]
319#[cfg(kernel_tls)]
320pub unsafe fn write_thread_pointer(kernel_tls: KernelTlsBase) {
321    unsafe { msr::wrmsr(msr::IA32_FS_BASE, kernel_tls.as_usize() as u64) }
322}
323
324#[cfg(feature = "uspace")]
325core::arch::global_asm!(include_str!("user_copy.S"), include_str!("user_atomic.S"),);
326
327#[cfg(feature = "uspace")]
328unsafe extern "C" {
329    /// Copies data from source to destination, where addresses may be in user
330    /// space. Equivalent to memcpy.
331    ///
332    /// # Safety
333    /// This function is unsafe because it performs raw memory operations.
334    ///
335    /// # Returns
336    /// Returns the number of bytes not copied. This means 0 indicates success,
337    /// while a value > 0 indicates failure.
338    pub fn user_copy(dst: *mut u8, src: *const u8, size: usize) -> usize;
339}
340
341/// Lock-free EL0/user access probe. No hardware address-translation probe is
342/// wired up on this architecture yet, so always report a present-page probe miss
343/// and let the caller take the locked slow path (correctness preserved).
344///
345/// # Safety
346///
347/// No precondition — this stub reads nothing and always returns `false`. It is
348/// `unsafe` only to share the signature of the aarch64 EL1 probe (which requires
349/// IRQs-off), so callers can use one `unsafe` block across all targets.
350#[cfg(feature = "uspace")]
351#[inline]
352pub unsafe fn user_access_ok_page(_vaddr: usize, _access: crate::UserAccessType) -> bool {
353    false
354}
355
356#[cfg(all(test, feature = "host-test"))]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn host_irq_mask_is_isolated_per_execution_thread() {
362        assert!(irqs_enabled());
363        disable_irqs();
364        assert!(!irqs_enabled());
365
366        std::thread::spawn(|| {
367            assert!(irqs_enabled());
368            disable_irqs();
369            assert!(!irqs_enabled());
370            enable_irqs();
371            assert!(irqs_enabled());
372        })
373        .join()
374        .unwrap();
375
376        assert!(!irqs_enabled());
377        enable_irqs();
378        assert!(irqs_enabled());
379    }
380}