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
83pub const TLB_RANGE_THRESHOLD_PAGES: usize = 64;
97
98static TLB_QUEUES: [SpinLock<TlbQueue>; crate::arch::x86_64::percpu::MAX_CPUS] =
100 [const { SpinLock::new(TlbQueue::new()) }; crate::arch::x86_64::percpu::MAX_CPUS];
101
102static TLB_ACKS: [AtomicBool; crate::arch::x86_64::percpu::MAX_CPUS] =
104 [const { AtomicBool::new(true) }; crate::arch::x86_64::percpu::MAX_CPUS];
105
106pub fn init() {
108 log::debug!(
109 "TLB shootdown initialized (vector {:#x})",
110 crate::arch::x86_64::apic::IPI_TLB_SHOOTDOWN_VECTOR
111 );
112}
113
114pub fn shootdown_page(vaddr: VirtAddr) {
116 let op = TlbOp {
117 kind: TlbShootdownKind::SinglePage,
118 vaddr_start: vaddr.as_u64(),
119 vaddr_end: vaddr.as_u64() + 4096,
120 };
121
122 unsafe { invlpg(vaddr) };
124
125 dispatch_op(op);
126}
127
128#[inline]
130pub fn local_page(vaddr: VirtAddr) {
131 unsafe { invlpg(vaddr) };
132}
133
134pub fn local_range(start: VirtAddr, end: VirtAddr) {
136 if end.as_u64() <= start.as_u64() {
137 unsafe { flush_tlb_all() };
138 return;
139 }
140
141 let page_count = (end.as_u64() - start.as_u64()) / 4096;
142 if page_count > TLB_RANGE_THRESHOLD_PAGES as u64 {
143 unsafe { flush_tlb_all() };
144 return;
145 }
146
147 for i in 0..page_count {
148 let addr = start + (i * 4096);
149 unsafe { invlpg(addr) };
150 }
151}
152
153pub fn shootdown_range(start: VirtAddr, end: VirtAddr) {
160 if end.as_u64() <= start.as_u64() {
163 log::warn!(
164 "TLB shootdown_range: invalid range [{:#x}, {:#x}), using full flush",
165 start.as_u64(),
166 end.as_u64(),
167 );
168 shootdown_all();
169 return;
170 }
171
172 let page_count = (end.as_u64() - start.as_u64()) / 4096;
173 if page_count > TLB_RANGE_THRESHOLD_PAGES as u64 {
174 shootdown_all();
175 return;
176 }
177
178 let op = TlbOp {
179 kind: TlbShootdownKind::Range,
180 vaddr_start: start.as_u64(),
181 vaddr_end: end.as_u64(),
182 };
183
184 for i in 0..page_count {
186 let addr = start + (i * 4096);
187 unsafe { invlpg(addr) };
188 }
189
190 dispatch_op(op);
191}
192
193pub fn shootdown_all() {
195 let op = TlbOp {
196 kind: TlbShootdownKind::Full,
197 vaddr_start: 0,
198 vaddr_end: 0,
199 };
200
201 unsafe { flush_tlb_all() };
203
204 dispatch_op(op);
205}
206
207fn dispatch_op(op: TlbOp) {
209 if !crate::arch::x86_64::apic::is_initialized() {
210 return;
211 }
212
213 let mut targets = [0u32; crate::arch::x86_64::percpu::MAX_CPUS];
214 let count = collect_tlb_targets(&mut targets);
215 if count == 0 {
216 return;
217 }
218
219 let mut queued = [0u32; crate::arch::x86_64::percpu::MAX_CPUS];
224 let mut queued_count = 0usize;
225
226 for i in 0..count {
228 let apic_id = targets[i];
229 let cpu_idx = match crate::arch::x86_64::percpu::cpu_index_by_apic(apic_id) {
233 Some(idx) => idx,
234 None => {
235 log::warn!(
236 "TLB dispatch: APIC {} not in per-CPU table, skipping",
237 apic_id
238 );
239 continue;
240 }
241 };
242 let mut queue = TLB_QUEUES[cpu_idx].lock();
243 queue.push(op);
244 TLB_ACKS[cpu_idx].store(false, Ordering::Release);
245 drop(queue);
246 queued[queued_count] = apic_id;
248 queued_count += 1;
249 }
250
251 if queued_count == 0 {
252 return;
253 }
254
255 for i in 0..queued_count {
257 send_tlb_ipi(queued[i]);
258 }
259
260 wait_for_acks(&queued[..queued_count]);
262}
263
264pub extern "C" fn tlb_shootdown_ipi_handler() {
266 let cpu_idx = current_cpu_index();
267
268 let mut local_ops = [TlbOp::NONE; 16];
270 let count = {
271 let mut queue = TLB_QUEUES[cpu_idx].lock();
272 let c = queue.count;
273 for i in 0..c {
274 local_ops[i] = queue.ops[i];
275 }
276 queue.clear();
277 c
278 };
279
280 for i in 0..count {
282 let op = &local_ops[i];
283 match op.kind {
284 TlbShootdownKind::None => {}
285 TlbShootdownKind::SinglePage => {
286 unsafe { invlpg(VirtAddr::new(op.vaddr_start)) };
287 }
288 TlbShootdownKind::Range => {
289 let start = op.vaddr_start;
290 let end = op.vaddr_end;
291 if end > start {
296 let page_count = (end - start) / 4096;
297 if page_count > TLB_RANGE_THRESHOLD_PAGES as u64 {
298 unsafe { flush_tlb_all() };
299 } else {
300 for j in 0..page_count {
301 let addr = VirtAddr::new(start + j * 4096);
302 unsafe { invlpg(addr) };
303 }
304 }
305 } else {
306 unsafe { flush_tlb_all() };
307 }
308 }
309 TlbShootdownKind::Full => {
310 unsafe { flush_tlb_all() };
311 }
312 }
313 }
314
315 TLB_ACKS[cpu_idx].store(true, Ordering::Release);
317
318 crate::arch::x86_64::apic::eoi();
320}
321
322#[inline]
324unsafe fn invlpg(vaddr: VirtAddr) {
325 core::arch::asm!("invlpg [{}]", in(reg) vaddr.as_u64(), options(nostack, preserves_flags));
326}
327
328#[inline]
330unsafe fn flush_tlb_all() {
331 use x86_64::registers::control::Cr3;
332 let (frame, flags) = Cr3::read();
333 Cr3::write(frame, flags);
334}
335
336fn send_tlb_ipi(target_apic_id: u32) {
338 let icr_low = crate::arch::x86_64::apic::IPI_TLB_SHOOTDOWN_VECTOR as u32 | (1 << 14);
339 crate::arch::x86_64::apic::send_ipi_raw(target_apic_id, icr_low);
340}
341
342fn collect_tlb_targets(targets: &mut [u32]) -> usize {
344 let my_cpu = crate::arch::x86_64::percpu::current_cpu_index();
345 let mut count = 0;
346 for cpu_idx in 0..crate::arch::x86_64::percpu::MAX_CPUS {
347 if !crate::arch::x86_64::percpu::tlb_ready(cpu_idx) {
348 continue;
349 }
350 if let Some(apic_id) = crate::arch::x86_64::percpu::apic_id_by_cpu_index(cpu_idx) {
351 if cpu_idx != my_cpu {
352 if count < targets.len() {
353 targets[count] = apic_id;
354 count += 1;
355 }
356 }
357 }
358 }
359 count
360}
361
362fn wait_for_acks(targets: &[u32]) {
364 const MAX_WAIT_CYCLES: usize = 10_000_000;
365 for &apic_id in targets {
366 let cpu_idx = match crate::arch::x86_64::percpu::cpu_index_by_apic(apic_id) {
369 Some(idx) => idx,
370 None => {
371 log::warn!("TLB wait_acks: APIC {} disappeared, skipping", apic_id);
372 continue;
373 }
374 };
375 let mut success = false;
376 for _ in 0..MAX_WAIT_CYCLES {
377 if TLB_ACKS[cpu_idx].load(Ordering::Acquire) {
378 success = true;
379 break;
380 }
381 core::hint::spin_loop();
382 }
383 if !success {
384 log::warn!("TLB shootdown timeout on CPU {}", cpu_idx);
385 }
386 }
387}
388
389fn current_cpu_index() -> usize {
391 crate::arch::x86_64::percpu::current_cpu_index()
392}