Skip to main content

strat9_kernel/arch/x86_64/
x2apic.rs

1//! x2APIC (Extended xAPIC) driver
2//!
3//! Provides MSR-based access to x2APIC registers instead of MMIO.
4//! x2APIC extends the APIC ID to 32 bits and uses MSRs for all register access.
5//!
6//! Key differences from xAPIC:
7//! - All registers accessed via MSRs (no MMIO).
8//! - ICR writes are atomic single MSR writes (no delivery status polling needed).
9//! - APIC ID is 32 bits (not just 8).
10//! - ESR is write-1-to-clear (write 0 to ack, read for errors).
11
12use crate::arch::x86_64::{rdmsr, wrmsr};
13
14// IA32_APIC_BASE MSR
15const IA32_APIC_BASE_MSR: u32 = 0x1B;
16const APIC_BASE_EN: u64 = 1 << 11;
17const APIC_BASE_EXTD: u64 = 1 << 10;
18
19// x2APIC register MSR addresses
20const IA32_X2APIC_APICID: u32 = 0x802;
21const IA32_X2APIC_VERSION: u32 = 0x803;
22const IA32_X2APIC_EOI: u32 = 0x80B;
23const IA32_X2APIC_SIVR: u32 = 0x80F;
24const IA32_X2APIC_ESR: u32 = 0x828;
25const IA32_X2APIC_ICR: u32 = 0x830;
26const IA32_X2APIC_LVT_TIMER: u32 = 0x832;
27const IA32_X2APIC_TIMER_INIT: u32 = 0x838;
28const IA32_X2APIC_TIMER_DIV: u32 = 0x83E;
29
30// ICR field constants (Intel SDM Vol. 3A, Table 10-1)
31//
32// ICR low 32 bits:
33//   [0:7]   Vector
34//   [8:10]  Delivery Mode
35//   [11]    Destination Mode (0=physical, 1=logical)
36//   [12]    Delivery Status (read-only in x2APIC : write is ignored)
37//   [13]    Reserved
38//   [14]    Level (0=de-assert, 1=assert : only meaningful for INIT/ExtINT)
39//   [15]    Trigger Mode (0=edge, 1=level)
40//   [16:17] Reserved
41//   [18:19] Destination Shorthand
42//   [20:31] Reserved
43//
44// ICR high 32 bits:
45//   [32:63] Destination Field (x2APIC: full 32-bit APIC ID)
46
47const DELIVERY_FIXED: u64 = 0b000 << 8;
48const DELIVERY_INIT: u64 = 0b101 << 8;
49const DELIVERY_STARTUP: u64 = 0b110 << 8;
50const DELIVERY_SIPI: u64 = 0b110 << 8; // STARTUP and SIPI share mode 110
51
52const TRIGGER_EDGE: u64 = 0 << 15;
53const TRIGGER_LEVEL: u64 = 1 << 15;
54
55const LEVEL_ASSERT: u64 = 1 << 14;
56const LEVEL_DEASSERT: u64 = 0 << 14;
57
58const DEST_PHYSICAL: u64 = 0 << 11;
59const DEST_SHORTHAND_NONE: u64 = 0b00 << 18;
60
61/// Timer divide configuration values (IA32_X2APIC_TIMER_DIV, MSR 0x83E).
62///
63/// The timer frequency is the bus clock (or core crystal clock) divided by
64/// the value encoded here.  After reset the register is 0 (= divide by 2).
65#[repr(u64)]
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum TimerDivide {
68    /// Divide by 2 (reset default).
69    Div2 = 0b000,
70    /// Divide by 4.
71    Div4 = 0b001,
72    /// Divide by 8.
73    Div8 = 0b010,
74    /// Divide by 16.
75    Div16 = 0b011,
76    /// Divide by 32.
77    Div32 = 0b100,
78    /// Divide by 64.
79    Div64 = 0b101,
80    /// Divide by 128.
81    Div128 = 0b110,
82    /// Divide by 1.
83    Div1 = 0b111,
84}
85
86pub struct X2Apic {
87    _private: (),
88}
89
90impl X2Apic {
91    pub fn new() -> Option<Self> {
92        if !Self::is_supported() {
93            return None;
94        }
95        let base = rdmsr(IA32_APIC_BASE_MSR);
96        if base & (APIC_BASE_EN | APIC_BASE_EXTD) == (APIC_BASE_EN | APIC_BASE_EXTD) {
97            Some(Self { _private: () })
98        } else {
99            None
100        }
101    }
102
103    pub fn is_supported() -> bool {
104        let (_eax, _ebx, ecx, _edx) = super::cpuid(1, 0);
105        ecx & (1 << 21) != 0
106    }
107
108    /// Enable x2APIC mode on this CPU.
109    ///
110    /// Performs the two-step enable sequence:
111    /// 1. Ensure APIC global enable (EN=1) in IA32_APIC_BASE.
112    /// 2. Set x2APIC enable (EXTD=1).
113    ///
114    /// Writes the Spurious Interrupt Vector Register to finalize.
115    ///
116    /// Returns `Err` if the CPU refuses to enter x2APIC mode (e.g. BIOS
117    /// locked the APIC base, or the CPU doesn't actually support x2APIC).
118    pub fn enable(&self) -> Result<(), &'static str> {
119        let base = rdmsr(IA32_APIC_BASE_MSR);
120        let already_extd = base & APIC_BASE_EXTD != 0;
121
122        if !already_extd {
123            if base & APIC_BASE_EN == 0 {
124                wrmsr(IA32_APIC_BASE_MSR, base | APIC_BASE_EN);
125            }
126            wrmsr(IA32_APIC_BASE_MSR, base | APIC_BASE_EN | APIC_BASE_EXTD);
127        }
128
129        let base_after = rdmsr(IA32_APIC_BASE_MSR);
130        if base_after & (APIC_BASE_EN | APIC_BASE_EXTD) != (APIC_BASE_EN | APIC_BASE_EXTD) {
131            return Err("failed to enter x2APIC mode");
132        }
133
134        // SVR: APIC software enable (bit 8) + spurious vector 0xFF.
135        let svr: u64 = (1 << 8) | 0xFF;
136        wrmsr(IA32_X2APIC_SIVR, svr);
137        Ok(())
138    }
139
140    /// Read and clear the Error Status Register.
141    ///
142    /// After a send_ipi() the ESR should be read to detect delivery errors
143    /// (illegal vector, accept error, etc.).  Writes-1-to-clear, so we write 0
144    /// then read to snapshot.
145    pub fn read_and_clear_esr(&self) -> u32 {
146        wrmsr(IA32_X2APIC_ESR, 0);
147        rdmsr(IA32_X2APIC_ESR) as u32
148    }
149
150    pub fn id(&self) -> u32 {
151        rdmsr(IA32_X2APIC_APICID) as u32
152    }
153
154    pub fn version(&self) -> u32 {
155        rdmsr(IA32_X2APIC_VERSION) as u32
156    }
157
158    pub fn eoi(&self) {
159        wrmsr(IA32_X2APIC_EOI, 0);
160    }
161
162    /// Send an IPI to a specific x2APIC destination.
163    ///
164    /// Uses Fixed delivery mode, physical destination, edge-triggered,
165    /// no shorthand.  This is the common-case IPI (reschedule, TLB shootdown, etc.).
166    pub fn send_ipi(&self, target_id: u32, vector: u8) {
167        // Clear ESR before sending (Intel SDM requirement).
168        wrmsr(IA32_X2APIC_ESR, 0);
169
170        let icr = ((target_id as u64) << 32)
171            | DEST_SHORTHAND_NONE
172            | TRIGGER_EDGE
173            | LEVEL_ASSERT
174            | DEST_PHYSICAL
175            | DELIVERY_FIXED
176            | (vector as u64);
177
178        wrmsr(IA32_X2APIC_ICR, icr);
179    }
180
181    /// Send an INIT IPI to a specific x2APIC destination.
182    ///
183    /// INIT IPIs use Delivery Mode = 101 (INIT).  In x2APIC mode the Level
184    /// bit is not meaningful for INIT delivery, but is set to 1 (assert) by
185    /// convention to match xAPIC-compatible INIT sequences.
186    pub fn send_init_ipi(&self, target_id: u32) {
187        wrmsr(IA32_X2APIC_ESR, 0);
188
189        let icr = ((target_id as u64) << 32)
190            | DEST_SHORTHAND_NONE
191            | TRIGGER_EDGE
192            | LEVEL_ASSERT
193            | DEST_PHYSICAL
194            | DELIVERY_INIT;
195
196        wrmsr(IA32_X2APIC_ICR, icr);
197    }
198
199    /// Send a STARTUP IPI (SIPI) to a specific x2APIC destination.
200    ///
201    /// `start_page` is the start-up vector (page frame number of the real-mode
202    /// entry point, e.g. 0x08 for physical 0x0800).  The CPU begins execution
203    /// at `start_page << 12` in real mode after receiving SIPI.
204    pub fn send_startup_ipi(&self, target_id: u32, start_page: u8) {
205        wrmsr(IA32_X2APIC_ESR, 0);
206
207        let icr = ((target_id as u64) << 32)
208            | DEST_SHORTHAND_NONE
209            | TRIGGER_EDGE
210            | DEST_PHYSICAL
211            | DELIVERY_STARTUP
212            | (start_page as u64);
213
214        wrmsr(IA32_X2APIC_ICR, icr);
215    }
216
217    // ── LVT Timer ──────────────────────────────────────────────────────
218
219    /// Configure the LVT timer.
220    ///
221    /// Programs the divide configuration, LVT entry, and initial count in the
222    /// correct order (SDM requires: DIV → LVT → INIT_COUNT).
223    pub fn configure_timer(
224        &self,
225        initial_count: u32,
226        vector: u8,
227        periodic: bool,
228        divide: TimerDivide,
229    ) {
230        let mut lvt = vector as u64;
231        if periodic {
232            lvt |= 1 << 17;
233        }
234        wrmsr(IA32_X2APIC_TIMER_DIV, divide as u64);
235        wrmsr(IA32_X2APIC_LVT_TIMER, lvt);
236        wrmsr(IA32_X2APIC_TIMER_INIT, initial_count as u64);
237    }
238
239    /// Mask the LVT timer (set mask bit = 1 → timer interrupt suppressed).
240    pub fn mask_timer(&self) {
241        let lvt = rdmsr(IA32_X2APIC_LVT_TIMER);
242        wrmsr(IA32_X2APIC_LVT_TIMER, lvt | (1 << 16));
243    }
244
245    /// Unmask the LVT timer (clear mask bit = 0 → timer interrupt delivered).
246    pub fn unmask_timer(&self) {
247        let lvt = rdmsr(IA32_X2APIC_LVT_TIMER);
248        wrmsr(IA32_X2APIC_LVT_TIMER, lvt & !(1 << 16));
249    }
250}