1use alloc::vec::Vec;
6use core::{mem::MaybeUninit, ops::Range, sync::atomic::Ordering};
7
8use super::{
9 PAGE_SIZE, Vaddr,
10 frame::{Frame, meta::AnyFrameMeta},
11};
12use crate::{
13 const_assert,
14 cpu::{AtomicCpuSet, PinCurrentCpu},
15 cpu_local,
16 smp::{IpiSender, PendingIpis},
17 sync::{LocalIrqDisabled, RcuDrop, SpinLock},
18};
19
20pub struct TlbFlusher<'a, G: PinCurrentCpu> {
24 target_cpus: &'a AtomicCpuSet,
25 pending_ipis: PendingIpis,
26 ops_stack: OpsStack,
27 ipi_sender: Option<&'static IpiSender>,
28 _pin_current: G,
29}
30
31impl<'a, G: PinCurrentCpu> TlbFlusher<'a, G> {
32 pub fn new(target_cpus: &'a AtomicCpuSet, pin_current_guard: G) -> Self {
40 Self {
41 target_cpus,
42 pending_ipis: PendingIpis::new_empty(),
43 ops_stack: OpsStack::new(),
44 ipi_sender: crate::smp::IPI_SENDER.get(),
45 _pin_current: pin_current_guard,
46 }
47 }
48
49 pub fn issue_tlb_flush(&mut self, op: TlbFlushOp) {
55 self.ops_stack.push(op, None);
56 }
57
58 pub fn issue_tlb_flush_with(
69 &mut self,
70 op: TlbFlushOp,
71 drop_after_flush: RcuDrop<Frame<dyn AnyFrameMeta>>,
72 ) {
73 self.ops_stack.push(op, Some(drop_after_flush));
74 }
75
76 pub fn dispatch_tlb_flush(&mut self) {
83 let irq_guard = crate::irq::disable_local();
84
85 if self.ops_stack.is_empty() {
86 return;
87 }
88
89 let mut target_cpus = self.target_cpus.load(Ordering::Release);
92
93 let cur_cpu = irq_guard.current_cpu();
94 let mut need_flush_on_self = false;
95
96 if target_cpus.contains(cur_cpu) {
97 target_cpus.remove(cur_cpu);
98 need_flush_on_self = true;
99 }
100
101 if let Some(ipi_sender) = self.ipi_sender {
102 for cpu in target_cpus.iter() {
103 let mut flush_ops = FLUSH_OPS.get_on_cpu(cpu).lock();
104 flush_ops.push_from(&self.ops_stack);
105 }
106
107 let pending_ipis = ipi_sender.inter_processor_call(&target_cpus, do_remote_flush);
108 self.pending_ipis.extend(&pending_ipis);
109 }
110
111 if need_flush_on_self {
113 self.ops_stack.flush_all();
114 } else {
115 self.ops_stack.clear_without_flush();
116 }
117 }
118
119 pub fn sync_tlb_flush(&mut self) {
135 if self.ipi_sender.is_none() {
136 return;
139 }
140
141 self.pending_ipis.wait();
142 self.pending_ipis = PendingIpis::new_empty();
143 }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct TlbFlushOp(Vaddr);
158
159const_assert!(TlbFlushOp::FLUSH_RANGE_NPAGES_MASK | (PAGE_SIZE - 1) == PAGE_SIZE - 1);
163
164impl TlbFlushOp {
165 const FLUSH_ALL_VAL: Vaddr = Vaddr::MAX;
166 const FLUSH_RANGE_NPAGES_MASK: Vaddr =
167 (1 << (usize::BITS - FLUSH_ALL_PAGES_THRESHOLD.leading_zeros())) - 1;
168
169 pub fn perform_on_current(&self) {
171 use crate::arch::mm;
172
173 match self.0 {
174 Self::FLUSH_ALL_VAL => mm::tlb_flush_all_excluding_global(),
175 addr => {
176 let start = addr & !Self::FLUSH_RANGE_NPAGES_MASK;
177 let num_pages = addr & Self::FLUSH_RANGE_NPAGES_MASK;
178
179 debug_assert!((addr & (PAGE_SIZE - 1)) < FLUSH_ALL_PAGES_THRESHOLD);
180 debug_assert!(num_pages != 0);
181
182 if num_pages == 1 {
183 mm::tlb_flush_addr(start);
184 } else {
185 mm::tlb_flush_addr_range(&(start..start + num_pages * PAGE_SIZE));
186 }
187 }
188 }
189 }
190
191 pub const fn for_all() -> Self {
194 TlbFlushOp(Self::FLUSH_ALL_VAL)
195 }
196
197 pub const fn for_single(addr: Vaddr) -> Self {
200 TlbFlushOp(addr | 1)
201 }
202
203 pub const fn for_range(range: Range<Vaddr>) -> Self {
213 assert!(
214 range.start.is_multiple_of(PAGE_SIZE),
215 "range start must be page-aligned"
216 );
217 assert!(
218 range.end.is_multiple_of(PAGE_SIZE),
219 "range end must be page-aligned"
220 );
221 assert!(range.start < range.end, "range must not be empty");
222 let num_pages = (range.end - range.start) / PAGE_SIZE;
223 if num_pages >= FLUSH_ALL_PAGES_THRESHOLD {
224 return TlbFlushOp::for_all();
225 }
226 TlbFlushOp(range.start | (num_pages as Vaddr))
227 }
228
229 fn num_pages(&self) -> u32 {
235 if self.0 == Self::FLUSH_ALL_VAL {
236 u32::MAX
237 } else {
238 debug_assert!((self.0 & (PAGE_SIZE - 1)) < FLUSH_ALL_PAGES_THRESHOLD);
239 let num_pages = (self.0 & Self::FLUSH_RANGE_NPAGES_MASK) as u32;
240 debug_assert!(num_pages != 0);
241 num_pages
242 }
243 }
244}
245
246cpu_local! {
248 static FLUSH_OPS: SpinLock<OpsStack, LocalIrqDisabled> = SpinLock::new(OpsStack::new());
249}
250
251fn do_remote_flush() {
252 let current_cpu = crate::cpu::CpuId::current_racy();
254
255 let mut new_op_queue = OpsStack::new();
256 {
257 let mut op_queue = FLUSH_OPS.get_on_cpu(current_cpu).lock();
258
259 core::mem::swap(&mut *op_queue, &mut new_op_queue);
260 }
261 new_op_queue.flush_all();
263}
264
265const FLUSH_ALL_PAGES_THRESHOLD: usize = 32;
268
269struct OpsStack {
270 ops: [MaybeUninit<TlbFlushOp>; FLUSH_ALL_PAGES_THRESHOLD],
272 num_ops: u32,
273 num_pages_to_flush: u32,
278 frame_keeper: Vec<Frame<dyn AnyFrameMeta>>,
283}
284
285impl OpsStack {
286 const fn new() -> Self {
287 Self {
288 ops: [const { MaybeUninit::uninit() }; FLUSH_ALL_PAGES_THRESHOLD],
289 num_ops: 0,
290 num_pages_to_flush: 0,
291 frame_keeper: Vec::new(),
292 }
293 }
294
295 fn is_empty(&self) -> bool {
296 self.num_ops == 0 && self.num_pages_to_flush == 0
297 }
298
299 fn need_flush_all(&self) -> bool {
300 self.num_pages_to_flush == u32::MAX
301 }
302
303 fn push(&mut self, op: TlbFlushOp, drop_after_flush: Option<RcuDrop<Frame<dyn AnyFrameMeta>>>) {
304 if let Some(frame) = drop_after_flush {
305 self.frame_keeper.reserve(1);
306 let (frame, panic_guard) = unsafe { RcuDrop::into_inner(frame) };
309 self.frame_keeper.push(frame);
310 panic_guard.forget();
311 }
312
313 if self.need_flush_all() {
314 return;
315 }
316 let op_num_pages = op.num_pages();
317 if op == TlbFlushOp::for_all()
318 || self.num_pages_to_flush + op_num_pages >= FLUSH_ALL_PAGES_THRESHOLD as u32
319 {
320 self.num_pages_to_flush = u32::MAX;
321 self.num_ops = 0;
322 return;
323 }
324
325 self.ops[self.num_ops as usize].write(op);
326 self.num_ops += 1;
327 self.num_pages_to_flush += op_num_pages;
328 }
329
330 fn push_from(&mut self, other: &OpsStack) {
331 self.frame_keeper.extend(other.frame_keeper.iter().cloned());
332
333 if self.need_flush_all() {
334 return;
335 }
336 if other.need_flush_all()
337 || self.num_pages_to_flush + other.num_pages_to_flush
338 >= FLUSH_ALL_PAGES_THRESHOLD as u32
339 {
340 self.num_pages_to_flush = u32::MAX;
341 self.num_ops = 0;
342 return;
343 }
344
345 for other_op in other.ops_iter() {
346 self.ops[self.num_ops as usize].write(other_op.clone());
347 self.num_ops += 1;
348 }
349 self.num_pages_to_flush += other.num_pages_to_flush;
350 }
351
352 fn flush_all(&mut self) {
353 if self.need_flush_all() {
354 crate::arch::mm::tlb_flush_all_excluding_global();
355 } else {
356 self.ops_iter().for_each(|op| {
357 op.perform_on_current();
358 });
359 }
360
361 self.clear_without_flush();
362 }
363
364 fn clear_without_flush(&mut self) {
365 self.num_pages_to_flush = 0;
366 self.num_ops = 0;
367 if !self.frame_keeper.is_empty() {
368 let _ = RcuDrop::new(core::mem::take(&mut self.frame_keeper));
369 }
370 }
371
372 fn ops_iter(&self) -> impl Iterator<Item = &TlbFlushOp> {
373 self.ops.iter().take(self.num_ops as usize).map(|op| {
374 unsafe { op.assume_init_ref() }
376 })
377 }
378}
379
380impl Drop for OpsStack {
381 fn drop(&mut self) {
382 if !self.frame_keeper.is_empty() {
383 let _ = RcuDrop::new(core::mem::take(&mut self.frame_keeper));
384 }
385 }
386}