Skip to main content

strat9_kernel/arch/x86_64/
mod.rs

1//! x86_64 architecture-specific code
2//!
3//! Inspired by MaestroOS `arch/x86/mod.rs`
4
5pub mod apic;
6pub mod boot_timestamp;
7pub mod cpuid;
8pub mod gdt;
9pub mod idt;
10pub mod io;
11pub mod ioapic;
12pub mod keyboard;
13pub mod keyboard_layout;
14pub mod keyboard_us;
15pub mod mouse;
16pub mod msi;
17pub mod pci;
18pub mod percpu;
19pub mod pic;
20pub mod ring3_diag;
21pub mod serial;
22pub mod smp;
23pub mod speaker;
24pub mod syscall;
25pub mod timer;
26pub mod tlb;
27pub mod tss;
28pub mod vga;
29pub mod vgabuf;
30pub mod x2apic;
31
32use core::arch::asm;
33
34/// Initialize FPU, SSE, and optionally XSAVE for the current CPU.
35pub fn init_cpu_extensions() {
36    unsafe {
37        let mut cr4: u64;
38        asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack));
39        // OSFXSR (9) + OSXMMEXCPT (10)
40        cr4 |= (1 << 9) | (1 << 10);
41
42        if cpuid::host_uses_xsave() {
43            // OSXSAVE (18) : required before xsetbv/xgetbv
44            cr4 |= 1 << 18;
45        }
46
47        // SMEP (20): Supervisor Mode Execution Prevention
48        // Prevents the kernel from executing code mapped in user-space pages.
49        // Requires CPUID leaf 7, ECX bit 7.
50        if crate::arch::x86_64::cpuid::host()
51            .features
52            .contains(cpuid::CpuFeatures::SMEP)
53        {
54            cr4 |= 1 << 20;
55            log::info!("[init] SMEP enabled (CR4 bit 20)");
56        }
57
58        // SMAP (21): Supervisor Mode Access Prevention
59        // Prevents the kernel from reading/writing user-space data pages
60        // unless EFLAGS.AC is set (via stac/clac).
61        // Requires CPUID leaf 7, ECX bit 20.
62        if crate::arch::x86_64::cpuid::host()
63            .features
64            .contains(cpuid::CpuFeatures::SMAP)
65        {
66            cr4 |= 1 << 21;
67            log::info!("[init] SMAP enabled (CR4 bit 21)");
68        }
69
70        asm!("mov cr4, {}", in(reg) cr4, options(nomem, nostack));
71
72        let mut cr0: u64;
73        asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack));
74        cr0 &= !(1 << 2); // clear EM
75        cr0 |= (1 << 1) | (1 << 5); // set MP + NE
76        asm!("mov cr0, {}", in(reg) cr0, options(nomem, nostack));
77
78        asm!("fninit", options(nomem, nostack));
79
80        if cpuid::host_uses_xsave() {
81            let xcr0 = cpuid::host_default_xcr0();
82            // Validate: requested XCR0 must be a subset of hardware support.
83            // On APs, HOST_CPU contains the BSP's feature set; mask to be safe.
84            let supported = cpuid::host().supported_xcr0;
85            debug_assert_eq!(
86                xcr0 & !supported,
87                0,
88                "XCR0 {:#x} has bits outside hardware support {:#x}",
89                xcr0,
90                supported
91            );
92            xsetbv(0, xcr0 & supported);
93        }
94    }
95}
96
97/// Whether CR4.OSXSAVE is currently enabled on this CPU.
98///
99/// AVX/AVX-512 are only *usable* when the kernel has enabled the state
100/// (XSAVE in CR4 + XCR0 bits); CPUID alone only reports hardware capability.
101pub fn cpuid_osxsave_enabled() -> bool {
102    let cr4: u64;
103    unsafe { asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack)) };
104    cr4 & (1 << 18) != 0
105}
106
107#[inline]
108/// Read an Extended Control Register (XGETBV).
109pub fn xgetbv(xcr: u32) -> u64 {
110    let eax: u32;
111    let edx: u32;
112    unsafe {
113        asm!(
114            "xgetbv",
115            in("ecx") xcr,
116            out("eax") eax,
117            out("edx") edx,
118            options(nomem, nostack),
119        );
120    }
121    ((edx as u64) << 32) | eax as u64
122}
123
124/// Write an Extended Control Register (XSETBV).
125///
126/// # Safety
127/// Caller must ensure CR4.OSXSAVE is set and the value is valid for XCR0.
128#[inline]
129pub unsafe fn xsetbv(xcr: u32, value: u64) {
130    asm!(
131        "xsetbv",
132        in("ecx") xcr,
133        in("eax") value as u32,
134        in("edx") (value >> 32) as u32,
135        options(nomem, nostack),
136    );
137}
138
139/// Halt the CPU until the next interrupt
140#[inline]
141pub fn hlt() {
142    unsafe {
143        asm!("hlt", options(nomem, nostack, preserves_flags));
144    }
145}
146
147/// Disable interrupts
148#[inline]
149pub fn cli() {
150    unsafe {
151        asm!("cli", options(nomem, nostack));
152    }
153}
154
155/// Enable interrupts
156#[inline]
157pub fn sti() {
158    unsafe {
159        asm!("sti", options(nomem, nostack));
160    }
161}
162
163/// Set AC flag in RFLAGS to temporarily disable SMAP.
164///
165/// Must be paired with `clac()` after the user-memory access is complete.
166/// Only needed when CR4.SMAP is set.
167///
168/// #CPUID guard: QEMU TCG raises #UD on CLAC/STAC when the CPU model does
169/// not advertise CPUID.(7,0):EBX.SMAP (e.g. plain `-cpu qemu64`), even
170/// though real Ivy Bridge+ CPUs execute them unconditionally. Gate both
171/// instructions on the detected feature so kernels built with SMAP-aware
172/// user accessors boot on any CPU model.
173#[inline]
174pub fn stac() {
175    if !cpu_has_smap() {
176        return;
177    }
178    unsafe {
179        asm!("stac", options(nomem, nostack, preserves_flags));
180    }
181}
182
183/// Clear AC flag in RFLAGS to re-enable SMAP protection.
184///
185/// Paired with `stac()`.
186#[inline]
187pub fn clac() {
188    if !cpu_has_smap() {
189        return;
190    }
191    unsafe {
192        asm!("clac", options(nomem, nostack, preserves_flags));
193    }
194}
195
196/// Whether CPUID.(7,0):EBX.SMAP is available. Cached on first use; the
197/// `cpu()` call is cheap (single Relaxed load) once `cpuid::init()` ran.
198#[inline]
199fn cpu_has_smap() -> bool {
200    crate::arch::x86_64::cpuid::host()
201        .features
202        .contains(crate::arch::x86_64::cpuid::CpuFeatures::SMAP)
203}
204
205/// Check if interrupts are enabled
206#[inline]
207pub fn interrupts_enabled() -> bool {
208    let rflags: u64;
209    unsafe {
210        asm!("pushfq; pop {}", out(reg) rflags, options(nomem));
211    }
212    rflags & 0x200 != 0
213}
214
215/// Save RFLAGS and disable interrupts. Returns saved flags.
216///
217/// Used to protect critical sections (e.g., scheduler lock) from
218/// being interrupted by the timer, which would cause deadlock on
219/// single-core systems.
220#[inline]
221pub fn save_flags_and_cli() -> u64 {
222    let flags: u64;
223    // SAFETY: pushfq/pop reads RFLAGS, cli disables interrupts.
224    // This is safe and required for single-core mutual exclusion.
225    unsafe {
226        asm!("pushfq; pop {0}; cli", out(reg) flags);
227    }
228    flags
229}
230
231/// Restore RFLAGS (including interrupt flag) from a previous save.
232///
233/// Pairs with `save_flags_and_cli()` to restore the previous interrupt state.
234#[inline]
235pub fn restore_flags(flags: u64) {
236    // SAFETY: push/popfq restores RFLAGS to a previously-saved valid state.
237    unsafe {
238        asm!("push {0}; popfq", in(reg) flags);
239    }
240}
241
242/// Read from a Model Specific Register
243#[inline]
244pub fn rdmsr(msr: u32) -> u64 {
245    let edx: u32;
246    let eax: u32;
247    unsafe {
248        asm!(
249            "rdmsr",
250            in("ecx") msr,
251            out("edx") edx,
252            out("eax") eax,
253            options(nostack)
254        );
255    }
256    ((edx as u64) << 32) | eax as u64
257}
258
259/// Write to a Model Specific Register
260#[inline]
261pub fn wrmsr(msr: u32, val: u64) {
262    let edx = (val >> 32) as u32;
263    let eax = val as u32;
264    unsafe {
265        asm!(
266            "wrmsr",
267            in("ecx") msr,
268            in("edx") edx,
269            in("eax") eax,
270            options(nostack)
271        );
272    }
273}
274
275/// Execute CPUID instruction.
276///
277/// Damn, rbx is reserved by LLVM, so we save/restore it manually.
278#[inline(never)]
279pub fn cpuid(leaf: u32, sub_leaf: u32) -> (u32, u32, u32, u32) {
280    let eax: u32;
281    let ebx: u32;
282    let ecx: u32;
283    let edx: u32;
284    unsafe {
285        asm!(
286            "push rbx",
287            "cpuid",
288            "mov r11d, ebx",
289            "pop rbx",
290            inout("eax") leaf => eax,
291            inout("ecx") sub_leaf => ecx,
292            out("r11") ebx,
293            out("edx") edx,
294            options(nostack, preserves_flags),
295        );
296    }
297    (eax, ebx, ecx, edx)
298}
299
300/// Read the Time Stamp Counter (TSC).
301///
302/// Returns the number of CPU cycles since reset. Available from the
303/// very first instruction : use this as the sole timing source during
304/// early boot (before APIC/PIT timers are configured).
305///
306/// LFENCE is emitted before RDTSC to ensure all prior instructions
307/// have completed (RDTSC is non-serializing).
308#[inline]
309pub fn rdtsc() -> u64 {
310    let eax: u32;
311    let edx: u32;
312    unsafe {
313        asm!(
314            "lfence",
315            "rdtsc",
316            out("eax") eax,
317            out("edx") edx,
318            options(nomem, nostack),
319        );
320    }
321    ((edx as u64) << 32) | eax as u64
322}