RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / fpu.rs
8601 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//! The FPU, SSE and AVX registers of user programs (stage 15.1).
//!
//! Besides the 16 general-purpose registers, an x86_64 CPU has a second set
//! for floating point and vector maths: the old x87 FPU stack, the 16 SSE
//! registers xmm0-xmm15 (128 bits each) plus their control register MXCSR,
//! and, on CPUs with AVX, the upper halves that make them 256-bit ymm0-ymm15.
//! Every C compiler uses xmm registers for `double` arithmetic and for fast
//! `memcpy`, so to the kernel they are as much "the program's registers" as
//! rax is: if two threads share a CPU, each must find its own values there
//! when it runs again.
//!
//! The kernel itself is built *soft-float* (see x86_64-rust_kernel.json): it
//! never touches these registers. That makes the job simple: whatever is in
//! them while the kernel runs belongs to the user thread the CPU last ran.
//! So we only
//!
//!   * save them into the thread's `FpuState` when the scheduler switches
//!     away from a user thread, and load the next user thread's (thread.rs,
//!     "eager" switching -- simple, and cheap enough at ~1 KiB);
//!   * copy them into a fork's child / a new thread (they start with the
//!     parent's values, like Linux);
//!   * reset them on execve (a new program starts clean);
//!   * put them in signal frames, so a handler may use floats and the
//!     interrupted code gets its own values back (signal.rs).
//!
//! The CPU has instructions that save/load all of it at once:
//!
//!   * FXSAVE / FXRSTOR (every x86_64 CPU): x87 + SSE, a 512-byte area.
//!   * XSAVE / XRSTOR (newer CPUs): a 512-byte legacy part in the same
//!     format, a 64-byte header (which parts are saved), then one part per
//!     extra feature -- AVX's upper ymm halves are 256 bytes more. Which
//!     features are switched on is the OS's choice, in register XCR0.
//!
//! Before user code may use any of this, the OS must say it will save the
//! state (otherwise the CPU raises "invalid opcode"): control register bits
//! CR4.OSFXSR (SSE allowed), CR4.OSXMMEXCPT (SSE maths errors raise #XM, not
//! #UD) and CR4.OSXSAVE (XSAVE + XCR0 allowed), on every CPU.

use core::arch::asm;
use core::arch::x86_64::{__cpuid, __cpuid_count};
use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};

/// Room for the biggest state we enable: x87 + SSE + AVX = 832 bytes with
/// XSAVE (we don't switch on AVX-512, which would need ~2.7 KiB).
pub const AREA_SIZE: usize = 1024;

/// One thread's saved FPU/SSE/AVX registers, in the FXSAVE/XSAVE memory
/// format. XSAVE needs 64-byte alignment.
#[repr(C, align(64))]
#[derive(Clone)]
pub struct FpuState(pub [u8; AREA_SIZE]);

/// XSAVE is used (else FXSAVE).
static USE_XSAVE: AtomicBool = AtomicBool::new(false);
/// The features in XCR0 (bit 0 x87, bit 1 SSE, bit 2 AVX).
static XCR0: AtomicU64 = AtomicU64::new(0);
/// Bytes of state: 512 (FXSAVE) or what CPUID says for our XCR0.
static SIZE: AtomicU32 = AtomicU32::new(512);
/// MXCSR bits the CPU supports; loading any other bit faults (#GP).
static MXCSR_MASK: AtomicU32 = AtomicU32::new(0xffbf);

/// MXCSR at reset: all six SSE exceptions masked, round to nearest.
pub const MXCSR_DEFAULT: u32 = 0x1f80;
/// x87 control word at reset (FNINIT): all exceptions masked, 64-bit precision.
const FCW_DEFAULT: u16 = 0x037f;

pub const XFEATURE_X87: u64 = 1;
pub const XFEATURE_SSE: u64 = 2;
pub const XFEATURE_AVX: u64 = 4;

/// Switch the FPU/SSE (and AVX) on for this CPU. Every CPU calls it once at
/// boot: CR0/CR4/XCR0 are per CPU.
pub fn init_cpu() {
    // CPUID leaf 1: ECX bit 26 = XSAVE, bit 28 = AVX.
    let leaf1 = __cpuid(1);
    let has_xsave = leaf1.ecx & (1 << 26) != 0;
    let has_avx = leaf1.ecx & (1 << 28) != 0;
    unsafe {
        // CR0: EM (bit 2, "no FPU: trap every FPU instruction") and TS (bit
        // 3, "trap the next one", for lazy switching) off; MP (bit 1) and NE
        // (bit 5, report x87 errors as exception #MF, not via the old PC IRQ 13) on.
        let mut cr0: u64;
        asm!("mov {}, cr0", out(reg) cr0);
        cr0 = (cr0 & !(1 << 2 | 1 << 3)) | 1 << 1 | 1 << 5;
        asm!("mov cr0, {}", in(reg) cr0);
        // CR4: OSFXSR (bit 9) + OSXMMEXCPT (bit 10), and OSXSAVE (bit 18).
        let mut cr4: u64;
        asm!("mov {}, cr4", out(reg) cr4);
        cr4 |= 1 << 9 | 1 << 10;
        if has_xsave {
            cr4 |= 1 << 18;
        }
        asm!("mov cr4, {}", in(reg) cr4);
    }
    let xcr0 = XFEATURE_X87 | XFEATURE_SSE | if has_avx && has_xsave { XFEATURE_AVX } else { 0 };
    if has_xsave {
        // XSETBV ecx=0: write XCR0 (edx:eax).
        unsafe { asm!("xsetbv", in("ecx") 0, in("eax") xcr0 as u32, in("edx") (xcr0 >> 32) as u32) };
        // CPUID leaf 0xD, sub-leaf 0: EBX = bytes XSAVE needs for the
        // features currently on in XCR0.
        let size = __cpuid_count(0xd, 0).ebx;
        assert!(size as usize <= AREA_SIZE, "XSAVE area too big: {}", size);
        SIZE.store(size, Ordering::Relaxed);
        XCR0.store(xcr0, Ordering::Relaxed);
        USE_XSAVE.store(true, Ordering::Relaxed);
    } else {
        XCR0.store(XFEATURE_X87 | XFEATURE_SSE, Ordering::Relaxed);
    }
    // Clean registers, and find out which MXCSR bits are allowed: FXSAVE
    // stores that mask at byte 28 (0 means the old default, 0xffbf).
    unsafe { asm!("fninit") };
    let mut probe = FpuState([0; AREA_SIZE]);
    unsafe { asm!("fxsave64 [{}]", in(reg) probe.0.as_mut_ptr()) };
    let mask = u32::from_le_bytes(probe.0[28..32].try_into().unwrap());
    MXCSR_MASK.store(if mask == 0 { 0xffbf } else { mask }, Ordering::Relaxed);
    restore(&initial());
}

pub fn uses_xsave() -> bool {
    USE_XSAVE.load(Ordering::Relaxed)
}
pub fn xfeatures() -> u64 {
    XCR0.load(Ordering::Relaxed)
}
/// Bytes of saved state (512 with FXSAVE).
pub fn size() -> usize {
    SIZE.load(Ordering::Relaxed) as usize
}

/// The state a new program starts with: everything zero, the x87 control
/// word and MXCSR at their reset values. The XSAVE header (bytes 512..576)
/// is all zero, which tells XRSTOR "every extra feature is in its initial
/// state" (zero).
pub fn initial() -> FpuState {
    let mut s = FpuState([0; AREA_SIZE]);
    s.0[0..2].copy_from_slice(&FCW_DEFAULT.to_le_bytes());
    s.0[24..28].copy_from_slice(&MXCSR_DEFAULT.to_le_bytes());
    if uses_xsave() {
        // ... except x87 + SSE, which we say are valid (the values above).
        s.0[512..520].copy_from_slice(&(XFEATURE_X87 | XFEATURE_SSE).to_le_bytes());
    }
    s
}

/// Save this CPU's registers into `s`.
pub fn save(s: &mut FpuState) {
    let p = s.0.as_mut_ptr();
    unsafe {
        if uses_xsave() {
            let m = xfeatures();
            asm!("xsave64 [{}]", in(reg) p, in("eax") m as u32, in("edx") (m >> 32) as u32);
        } else {
            asm!("fxsave64 [{}]", in(reg) p);
        }
    }
}

/// The registers right now (for fork, clone and signal frames).
pub fn save_live() -> FpuState {
    let mut s = FpuState([0; AREA_SIZE]);
    save(&mut s);
    s
}

/// Load `s` into this CPU's registers. `s` must be valid (see `sanitize`):
/// a bad MXCSR or XSAVE header would fault in the kernel.
pub fn restore(s: &FpuState) {
    let p = s.0.as_ptr();
    unsafe {
        if uses_xsave() {
            let m = xfeatures();
            asm!("xrstor64 [{}]", in(reg) p, in("eax") m as u32, in("edx") (m >> 32) as u32);
        } else {
            asm!("fxrstor64 [{}]", in(reg) p);
        }
    }
}

/// execve and signal handlers start from clean registers.
pub fn reset_live() {
    restore(&initial());
}

/// Make state that came from user memory (a signal frame the program may
/// have edited) safe to load: false if MXCSR has bits the CPU doesn't
/// support (XRSTOR would fault). Fixes the XSAVE header: only features we
/// enabled, standard (not compacted) format, reserved bytes zero.
pub fn sanitize(s: &mut FpuState) -> bool {
    let mxcsr = u32::from_le_bytes(s.0[24..28].try_into().unwrap());
    if mxcsr & !MXCSR_MASK.load(Ordering::Relaxed) != 0 {
        return false;
    }
    if uses_xsave() {
        let bv = u64::from_le_bytes(s.0[512..520].try_into().unwrap()) & xfeatures();
        s.0[512..520].copy_from_slice(&bv.to_le_bytes());
        s.0[520..576].fill(0); // XCOMP_BV + reserved
    }
    true
}

/// The SSE status flags of the last SIMD exception (#XM), for SIGFPE's
/// si_code: MXCSR bits 0-5 = invalid, denormal, divide by zero, overflow,
/// underflow, precision.
pub fn mxcsr() -> u32 {
    let mut v: u32 = 0;
    unsafe { asm!("stmxcsr [{}]", in(reg) &mut v as *mut u32) };
    v
}