Skip to main content

ostd/mm/
tlb.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! TLB flush operations.
4
5use 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
20/// A TLB flusher that is aware of which CPUs are needed to be flushed.
21///
22/// The flusher needs to stick to the current CPU.
23pub 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    /// Creates a new TLB flusher with the specified CPUs to be flushed.
33    ///
34    /// The target CPUs should be a reference to an [`AtomicCpuSet`] that will
35    /// be loaded upon [`Self::dispatch_tlb_flush`].
36    ///
37    /// The flusher needs to stick to the current CPU. So please provide a
38    /// guard that implements [`PinCurrentCpu`].
39    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    /// Issues a pending TLB flush request.
50    ///
51    /// This function does not guarantee to flush the TLB entries on either
52    /// this CPU or remote CPUs. The flush requests are only performed when
53    /// [`Self::dispatch_tlb_flush`] is called.
54    pub fn issue_tlb_flush(&mut self, op: TlbFlushOp) {
55        self.ops_stack.push(op, None);
56    }
57
58    /// Issues a TLB flush request that must happen before dropping the page.
59    ///
60    /// If we need to remove a mapped page from the page table, we can only
61    /// recycle the page after all the relevant TLB entries in all CPUs are
62    /// flushed. Otherwise if the page is recycled for other purposes, the user
63    /// space program can still access the page through the TLB entries. This
64    /// method is designed to be used in such cases.
65    ///
66    /// Furthermore, the frames will be dropped after the RCU grace period to
67    /// ensure that no RCU references are held to the frames.
68    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    /// Dispatches all the pending TLB flush requests.
77    ///
78    /// All previous pending requests issued by [`Self::issue_tlb_flush`] or
79    /// [`Self::issue_tlb_flush_with`] starts to be processed after this
80    /// function. But it may not be synchronous. Upon the return of this
81    /// function, the TLB entries may not be coherent.
82    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        // `Release` to make sure our modification on the PT is visible to CPUs
90        // that are going to activate the PT.
91        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        // Flush ourselves after sending all IPIs to save some time.
112        if need_flush_on_self {
113            self.ops_stack.flush_all();
114        } else {
115            self.ops_stack.clear_without_flush();
116        }
117    }
118
119    /// Waits for all the previous TLB flush requests to be completed.
120    ///
121    /// After this function, all TLB entries corresponding to previous
122    /// dispatched TLB flush requests are guaranteed to be coherent.
123    ///
124    /// The TLB flush requests are issued with [`Self::issue_tlb_flush`] and
125    /// dispatched with [`Self::dispatch_tlb_flush`]. This method will not
126    /// dispatch any issued requests so it will not guarantee TLB coherence
127    /// of requests that are not dispatched.
128    ///
129    /// # Panics
130    ///
131    /// This method panics if the IRQs are disabled. Since the remote flush are
132    /// processed in IRQs, two CPUs may deadlock if they are waiting for each
133    /// other's TLB coherence.
134    pub fn sync_tlb_flush(&mut self) {
135        if self.ipi_sender.is_none() {
136            // We performed some TLB flushes in the boot context. The AP's boot
137            // process should take care of them.
138            return;
139        }
140
141        self.pending_ipis.wait();
142        self.pending_ipis = PendingIpis::new_empty();
143    }
144}
145
146/// The operation to flush TLB entries.
147///
148/// The variants of this structure are:
149///  - Flushing all TLB entries except for the global entries;
150///  - Flushing the TLB entry associated with an address;
151///  - Flushing the TLB entries for a specific range of virtual addresses;
152///
153/// This is a `struct` instead of an `enum` because if trivially representing
154/// the three variants with an `enum`, it would be 24 bytes. To minimize the
155/// memory footprint, we encode all three variants into an 8-byte integer.
156#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct TlbFlushOp(Vaddr);
158
159// We require the address to be page-aligned, so the in-page offset part of the
160// address can be used to store the length. A sanity check to ensure that we
161// don't allow ranged flush operations with a too long length.
162const_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    /// Performs the TLB flush operation on the current CPU.
170    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    /// Creates a new TLB flush operation that flushes all TLB entries except
192    /// for the global entries.
193    pub const fn for_all() -> Self {
194        TlbFlushOp(Self::FLUSH_ALL_VAL)
195    }
196
197    /// Creates a new TLB flush operation that flushes the TLB entry associated
198    /// with the provided virtual address.
199    pub const fn for_single(addr: Vaddr) -> Self {
200        TlbFlushOp(addr | 1)
201    }
202
203    /// Creates a new TLB flush operation that flushes the TLB entries for the
204    /// specified virtual address range.
205    ///
206    /// If the range is too large, the resulting [`TlbFlushOp`] will flush all
207    /// TLB entries instead.
208    ///
209    /// # Panics
210    ///
211    /// Panics if the range is not page-aligned or if the range is empty.
212    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    /// Returns the number of pages to flush.
230    ///
231    /// If it returns `u32::MAX`, it means to flush all the entries. Otherwise
232    /// the return value should be less than [`FLUSH_ALL_PAGES_THRESHOLD`] and
233    /// non-zero.
234    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
246// The queues of pending requests on each CPU.
247cpu_local! {
248    static FLUSH_OPS: SpinLock<OpsStack, LocalIrqDisabled> = SpinLock::new(OpsStack::new());
249}
250
251fn do_remote_flush() {
252    // No races because we are in IRQs or have disabled preemption.
253    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    // Unlock the locks quickly to avoid contention.
262    new_op_queue.flush_all();
263}
264
265/// If the number of pending pages to flush exceeds this threshold, we flush all the
266/// TLB entries instead of flushing them one by one.
267const FLUSH_ALL_PAGES_THRESHOLD: usize = 32;
268
269struct OpsStack {
270    /// From 0 to `num_ops`, the array entry must be initialized.
271    ops: [MaybeUninit<TlbFlushOp>; FLUSH_ALL_PAGES_THRESHOLD],
272    num_ops: u32,
273    /// If this is `u32::MAX`, we should flush all entries irrespective of the
274    /// contents of `ops`. And in this case `num_ops` must be zero.
275    ///
276    /// Otherwise, it counts the number of pages to flush in `ops`.
277    num_pages_to_flush: u32,
278    /// Keeps all the to-be-dropped frames.
279    ///
280    /// The elements cannot be modified after being pushed. And they must be
281    /// dropped after the RCU grace period and the TLB flushes.
282    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            // SAFETY: By pushing into the `frame_keeper`, the frame will be
307            // dropped after the RCU grace period.
308            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            // SAFETY: From 0 to `num_ops`, the array entry must be initialized.
375            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}