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}