Skip to main content

ax_cpu/arch/x86_64/
asm.rs

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