Skip to main content

vck_loader/
cpu.rs

1// SPDX-FileCopyrightText: 2026 JC-Lab <joseph@jc-lab.net>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Loader-side CPU diagnostics and SSE/XMM enablement.
6//!
7//! The Block IO decrypt hook runs AES-256-XTS during the boot window, which on
8//! AES-NI-capable CPUs executes SSE/AES-NI instructions (XMM registers). Those
9//! require the OS-managed control-register bits to be set:
10//!
11//! - `CR0.MP[1] = 1`        — monitor coprocessor
12//! - `CR0.EM[2] = 0`        — disable x87 emulation (else SSE faults with #UD)
13//! - `CR4.OSFXSR[9] = 1`    — enable FXSAVE/FXRSTOR + SSE
14//! - `CR4.OSXMMEXCPT[10] = 1` — route unmasked SSE FP exceptions to #XF
15//!
16//! UEFI firmware normally sets these already, but we verify and (when AES-NI is
17//! present) set them defensively before any AES-NI code runs. The loader is a
18//! ring-0 UEFI application, so writing CR0/CR4 is permitted.
19
20use log::info;
21use vck_common::cpu::has_aes_ni;
22
23const CR0_MP: u64 = 1 << 1;
24const CR0_EM: u64 = 1 << 2;
25const CR4_OSFXSR: u64 = 1 << 9;
26const CR4_OSXMMEXCPT: u64 = 1 << 10;
27
28#[inline]
29fn read_cr0() -> u64 {
30    let v: u64;
31    // SAFETY: reading CR0 is a privileged but side-effect-free register read;
32    // the loader runs at ring 0.
33    unsafe {
34        core::arch::asm!("mov {}, cr0", out(reg) v, options(nomem, nostack, preserves_flags));
35    }
36    v
37}
38
39#[inline]
40fn read_cr4() -> u64 {
41    let v: u64;
42    // SAFETY: as read_cr0, for CR4.
43    unsafe {
44        core::arch::asm!("mov {}, cr4", out(reg) v, options(nomem, nostack, preserves_flags));
45    }
46    v
47}
48
49#[inline]
50fn write_cr0(v: u64) {
51    // SAFETY: ring-0 control-register write. `nomem` is intentionally omitted:
52    // toggling CR0 affects how the CPU executes, so the compiler must not move
53    // memory accesses across it.
54    unsafe {
55        core::arch::asm!("mov cr0, {}", in(reg) v, options(nostack, preserves_flags));
56    }
57}
58
59#[inline]
60fn write_cr4(v: u64) {
61    // SAFETY: as write_cr0, for CR4.
62    unsafe {
63        core::arch::asm!("mov cr4, {}", in(reg) v, options(nostack, preserves_flags));
64    }
65}
66
67#[inline]
68fn bit(v: u64, b: u64) -> u32 {
69    ((v & b) != 0) as u32
70}
71
72/// Log AES-NI support and the SSE/XMM control bits, then (only when AES-NI is
73/// supported) set them to the values required for AES-NI execution.
74pub fn report_and_enable_xmm() {
75    let aes = has_aes_ni();
76    info!(
77        "cpu: AES-NI {}",
78        if aes { "supported" } else { "not supported" }
79    );
80
81    let cr0 = read_cr0();
82    let cr4 = read_cr4();
83    info!(
84        "cpu: current CR0.MP[1]={} CR0.EM[2]={} CR4.OSFXSR[9]={} CR4.OSXMMEXCPT[10]={}",
85        bit(cr0, CR0_MP),
86        bit(cr0, CR0_EM),
87        bit(cr4, CR4_OSFXSR),
88        bit(cr4, CR4_OSXMMEXCPT),
89    );
90
91    if !aes {
92        // No AES-NI: the aes crate uses its software backend; leave the control
93        // registers as firmware configured them.
94        return;
95    }
96
97    let new_cr0 = (cr0 | CR0_MP) & !CR0_EM;
98    let new_cr4 = cr4 | CR4_OSFXSR | CR4_OSXMMEXCPT;
99    if new_cr0 != cr0 {
100        write_cr0(new_cr0);
101    }
102    if new_cr4 != cr4 {
103        write_cr4(new_cr4);
104    }
105    info!(
106        "cpu: enabled XMM CR0 {:#x}->{:#x} CR4 {:#x}->{:#x} (MP=1 EM=0 OSFXSR=1 OSXMMEXCPT=1)",
107        cr0, new_cr0, cr4, new_cr4,
108    );
109}