strat9_kernel/arch/x86_64/
tlb.rs1use core::sync::atomic::{AtomicBool, Ordering};
16use x86_64::VirtAddr;
17
18use crate::sync::SpinLock;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22enum TlbShootdownKind {
23 None,
25 SinglePage,
27 Range,
29 Full,
31}
32
33#[derive(Debug, Clone, Copy)]
35struct TlbOp {
36 kind: TlbShootdownKind,
37 vaddr_start: u64,
38 vaddr_end: u64,
39}
40
41impl TlbOp {
42 const NONE: Self = Self {
43 kind: TlbShootdownKind::None,
44 vaddr_start: 0,
45 vaddr_end: 0,
46 };
47}
48
49struct TlbQueue {
51 ops: [TlbOp; 16],
52 count: usize,
53}
54
55impl TlbQueue {
56 const fn new() -> Self {
57 Self {
58 ops: [TlbOp::NONE; 16],
59 count: 0,
60 }
61 }
62
63 fn push(&mut self, op: TlbOp) {
64 if self.count < 16 {
65 self.ops[self.count] = op;
66 self.count += 1;
67 } else {
68 self.ops[0] = TlbOp {
70 kind: TlbShootdownKind::Full,
71 vaddr_start: 0,
72 vaddr_end: 0,
73 };
74 self.count = 1;
75 }
76 }
77
78 fn clear(&mut self) {
79 self.count = 0;
80 }
81}
82
83static TLB_QUEUES: [SpinLock<TlbQueue>; crate::arch::x86_64::percpu::MAX_CPUS] =
85 [const { SpinLock::new(TlbQueue::new()) }; crate::arch::x86_64::percpu::MAX_CPUS];
86
87static TLB_ACKS: [AtomicBool; crate::arch::x86_64::percpu::MAX_CPUS] =
89 [const { AtomicBool::new(true) }; crate::arch::x86_64::percpu::MAX_CPUS];
90
91pub fn init() {
93 log::debug!(
94 "TLB shootdown initialized (vector {:#x})",
95 crate::arch::x86_64::apic::IPI_TLB_SHOOTDOWN_VECTOR
96 );
97}
98
99pub fn shootdown_page(vaddr: VirtAddr) {
101 let op = TlbOp {
102 kind: TlbShootdownKind::SinglePage,
103 vaddr_start: vaddr.as_u64(),
104 vaddr_end: vaddr.as_u64() + 4096,
105 };
106
107 unsafe { invlpg(vaddr) };
109
110 dispatch_op(op);
111}
112
113pub fn shootdown_range(start: VirtAddr, end: VirtAddr) {
115 if end.as_u64() <= start.as_u64() {
118 log::warn!(
119 "TLB shootdown_range: invalid range [{:#x}, {:#x}), using full flush",
120 start.as_u64(),
121 end.as_u64(),
122 );
123 shootdown_all();
124 return;
125 }
126
127 let page_count = (end.as_u64() - start.as_u64()) / 4096;
128 if page_count > 64 {
129 shootdown_all();
130 return;
131 }
132
133 let op = TlbOp {
134 kind: TlbShootdownKind::Range,
135 vaddr_start: start.as_u64(),
136 vaddr_end: end.as_u64(),
137 };
138
139 for i in 0..page_count {
141 let addr = start + (i * 4096);
142 unsafe { invlpg(addr) };
143 }
144
145 dispatch_op(op);
146}
147
148pub fn shootdown_all() {
150 let op = TlbOp {
151 kind: TlbShootdownKind::Full,
152 vaddr_start: 0,
153 vaddr_end: 0,
154 };
155
156 unsafe { flush_tlb_all() };
158
159 dispatch_op(op);
160}
161
162fn dispatch_op(op: TlbOp) {
164 if !crate::arch::x86_64::apic::is_initialized() {
165 return;
166 }
167
168 let mut targets = [0u32; crate::arch::x86_64::percpu::MAX_CPUS];
169 let count = collect_tlb_targets(&mut targets);
170 if count == 0 {
171 return;
172 }
173
174 let mut queued = [0u32; crate::arch::x86_64::percpu::MAX_CPUS];
179 let mut queued_count = 0usize;
180
181 for i in 0..count {
183 let apic_id = targets[i];
184 let cpu_idx = match crate::arch::x86_64::percpu::cpu_index_by_apic(apic_id) {
188 Some(idx) => idx,
189 None => {
190 log::warn!(
191 "TLB dispatch: APIC {} not in per-CPU table, skipping",
192 apic_id
193 );
194 continue;
195 }
196 };
197 let mut queue = TLB_QUEUES[cpu_idx].lock();
198 queue.push(op);
199 TLB_ACKS[cpu_idx].store(false, Ordering::Release);
200 drop(queue);
201 queued[queued_count] = apic_id;
203 queued_count += 1;
204 }
205
206 if queued_count == 0 {
207 return;
208 }
209
210 for i in 0..queued_count {
212 send_tlb_ipi(queued[i]);
213 }
214
215 wait_for_acks(&queued[..queued_count]);
217}
218
219pub extern "C" fn tlb_shootdown_ipi_handler() {
221 let cpu_idx = current_cpu_index();
222
223 let mut local_ops = [TlbOp::NONE; 16];
225 let mut count = 0;
226 {
227 let mut queue = TLB_QUEUES[cpu_idx].lock();
228 count = queue.count;
229 for i in 0..count {
230 local_ops[i] = queue.ops[i];
231 }
232 queue.clear();
233 }
234
235 for i in 0..count {
237 let op = &local_ops[i];
238 match op.kind {
239 TlbShootdownKind::None => {}
240 TlbShootdownKind::SinglePage => {
241 unsafe { invlpg(VirtAddr::new(op.vaddr_start)) };
242 }
243 TlbShootdownKind::Range => {
244 let start = op.vaddr_start;
245 let end = op.vaddr_end;
246 if end > start {
248 let page_count = (end - start) / 4096;
249 for j in 0..page_count {
250 let addr = VirtAddr::new(start + j * 4096);
251 unsafe { invlpg(addr) };
252 }
253 } else {
254 unsafe { flush_tlb_all() };
255 }
256 }
257 TlbShootdownKind::Full => {
258 unsafe { flush_tlb_all() };
259 }
260 }
261 }
262
263 TLB_ACKS[cpu_idx].store(true, Ordering::Release);
265
266 crate::arch::x86_64::apic::eoi();
268}
269
270#[inline]
272unsafe fn invlpg(vaddr: VirtAddr) {
273 core::arch::asm!("invlpg [{}]", in(reg) vaddr.as_u64(), options(nostack, preserves_flags));
274}
275
276#[inline]
278unsafe fn flush_tlb_all() {
279 use x86_64::registers::control::Cr3;
280 let (frame, flags) = Cr3::read();
281 Cr3::write(frame, flags);
282}
283
284fn send_tlb_ipi(target_apic_id: u32) {
286 let icr_low = crate::arch::x86_64::apic::IPI_TLB_SHOOTDOWN_VECTOR as u32 | (1 << 14);
287 crate::arch::x86_64::apic::send_ipi_raw(target_apic_id, icr_low);
288}
289
290fn collect_tlb_targets(targets: &mut [u32]) -> usize {
292 let my_cpu = crate::arch::x86_64::percpu::current_cpu_index();
293 let mut count = 0;
294 for cpu_idx in 0..crate::arch::x86_64::percpu::MAX_CPUS {
295 if !crate::arch::x86_64::percpu::tlb_ready(cpu_idx) {
296 continue;
297 }
298 if let Some(apic_id) = crate::arch::x86_64::percpu::apic_id_by_cpu_index(cpu_idx) {
299 if cpu_idx != my_cpu {
300 if count < targets.len() {
301 targets[count] = apic_id;
302 count += 1;
303 }
304 }
305 }
306 }
307 count
308}
309
310fn wait_for_acks(targets: &[u32]) {
312 const MAX_WAIT_CYCLES: usize = 10_000_000;
313 for &apic_id in targets {
314 let cpu_idx = match crate::arch::x86_64::percpu::cpu_index_by_apic(apic_id) {
317 Some(idx) => idx,
318 None => {
319 log::warn!("TLB wait_acks: APIC {} disappeared, skipping", apic_id);
320 continue;
321 }
322 };
323 let mut success = false;
324 for _ in 0..MAX_WAIT_CYCLES {
325 if TLB_ACKS[cpu_idx].load(Ordering::Acquire) {
326 success = true;
327 break;
328 }
329 core::hint::spin_loop();
330 }
331 if !success {
332 log::warn!("TLB shootdown timeout on CPU {}", cpu_idx);
333 }
334 }
335}
336
337fn current_cpu_index() -> usize {
339 crate::arch::x86_64::percpu::current_cpu_index()
340}