Skip to main content

x86_64/instructions/
tlb.rs

1//! Functions to flush the translation lookaside buffer (TLB).
2
3use bit_field::BitField;
4
5use crate::{
6    instructions::segmentation::{Segment, CS},
7    structures::paging::{
8        page::{NotGiantPageSize, PageRange},
9        Page, PageSize, Size2MiB, Size4KiB,
10    },
11    PrivilegeLevel, VirtAddr,
12};
13use core::{arch::asm, cmp, convert::TryFrom, fmt};
14
15/// Invalidate the given address in the TLB using the `invlpg` instruction.
16#[inline]
17pub fn flush(addr: VirtAddr) {
18    unsafe {
19        asm!("invlpg [{}]", in(reg) addr.as_u64(), options(nostack, preserves_flags));
20    }
21}
22
23/// Invalidate the TLB completely by reloading the CR3 register.
24#[inline]
25pub fn flush_all() {
26    use crate::registers::control::Cr3;
27    let (frame, flags) = Cr3::read();
28    unsafe { Cr3::write(frame, flags) }
29}
30
31/// The Invalidate PCID Command to execute.
32#[derive(Debug)]
33pub enum InvPcidCommand {
34    /// The logical processor invalidates mappings—except global translations—for the linear address and PCID specified.
35    Address(VirtAddr, Pcid),
36
37    /// The logical processor invalidates all mappings—except global translations—associated with the PCID.
38    Single(Pcid),
39
40    /// The logical processor invalidates all mappings—including global translations—associated with any PCID.
41    All,
42
43    /// The logical processor invalidates all mappings—except global translations—associated with any PCID.
44    AllExceptGlobal,
45}
46
47// TODO: Remove this in the next breaking release.
48#[deprecated = "please use `InvPcidCommand` instead"]
49#[doc(hidden)]
50pub type InvPicdCommand = InvPcidCommand;
51
52/// The INVPCID descriptor comprises 128 bits and consists of a PCID and a linear address.
53/// For INVPCID type 0, the processor uses the full 64 bits of the linear address even outside 64-bit mode; the linear address is not used for other INVPCID types.
54#[repr(C)]
55#[derive(Debug)]
56struct InvpcidDescriptor {
57    pcid: u64,
58    address: u64,
59}
60
61/// Structure of a PCID. A PCID has to be <= 4096 for x86_64.
62#[repr(transparent)]
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct Pcid(u16);
65
66impl Pcid {
67    /// Create a new PCID. Will result in a failure if the value of
68    /// PCID is out of expected bounds.
69    pub const fn new(pcid: u16) -> Result<Pcid, PcidTooBig> {
70        if pcid >= 4096 {
71            Err(PcidTooBig(pcid))
72        } else {
73            Ok(Pcid(pcid))
74        }
75    }
76
77    /// Get the value of the current PCID.
78    pub const fn value(&self) -> u16 {
79        self.0
80    }
81}
82
83/// A passed `u16` was not a valid PCID.
84///
85/// A PCID has to be <= 4096 for x86_64.
86#[derive(Debug)]
87pub struct PcidTooBig(u16);
88
89impl fmt::Display for PcidTooBig {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(f, "PCID should be < 4096, got {}", self.0)
92    }
93}
94
95/// Invalidate the given address in the TLB using the `invpcid` instruction.
96///
97/// ## Safety
98///
99/// This function is unsafe as it requires CPUID.(EAX=07H, ECX=0H):EBX.INVPCID to be 1.
100#[inline]
101pub unsafe fn flush_pcid(command: InvPcidCommand) {
102    let mut desc = InvpcidDescriptor {
103        pcid: 0,
104        address: 0,
105    };
106
107    let kind: u64;
108    match command {
109        InvPcidCommand::Address(addr, pcid) => {
110            kind = 0;
111            desc.pcid = pcid.value().into();
112            desc.address = addr.as_u64()
113        }
114        InvPcidCommand::Single(pcid) => {
115            kind = 1;
116            desc.pcid = pcid.0.into()
117        }
118        InvPcidCommand::All => kind = 2,
119        InvPcidCommand::AllExceptGlobal => kind = 3,
120    }
121
122    unsafe {
123        asm!("invpcid {0}, [{1}]", in(reg) kind, in(reg) &desc, options(nostack, preserves_flags));
124    }
125}
126
127/// Used to broadcast flushes to all logical processors.
128///
129/// ```no_run
130/// use x86_64::VirtAddr;
131/// use x86_64::structures::paging::Page;
132/// use x86_64::instructions::tlb::Invlpgb;
133///
134/// // Check that `invlpgb` and `tlbsync` are supported.
135/// let invlpgb = Invlpgb::new().unwrap();
136///
137/// // Broadcast flushing some pages to all logical processors.
138/// let start: Page = Page::from_start_address(VirtAddr::new(0xf000_0000)).unwrap();
139/// let pages = Page::range(start, start + 3);
140/// invlpgb.build().pages(pages).include_global().flush();
141///
142/// // Wait for all logical processors to respond.
143/// invlpgb.tlbsync();
144/// ```
145#[derive(Debug, Clone, Copy)]
146pub struct Invlpgb {
147    invlpgb_count_max: u16,
148    tlb_flush_nested: bool,
149    nasid: u32,
150}
151
152impl Invlpgb {
153    /// Check that `invlpgb` and `tlbsync` are supported and query limits.
154    ///
155    /// # Panics
156    ///
157    /// Panics if the CPL is not 0.
158    pub fn new() -> Option<Self> {
159        let cs = CS::get_reg();
160        assert_eq!(cs.rpl(), PrivilegeLevel::Ring0);
161
162        // Check if the `INVLPGB` and `TLBSYNC` instruction are supported.
163        #[allow(unused_unsafe)]
164        let cpuid = unsafe { core::arch::x86_64::__cpuid(0x8000_0008) };
165        if !cpuid.ebx.get_bit(3) {
166            return None;
167        }
168
169        let tlb_flush_nested = cpuid.ebx.get_bit(21);
170        let invlpgb_count_max = cpuid.edx.get_bits(0..=15) as u16;
171
172        // Figure out the number of supported ASIDs.
173        #[allow(unused_unsafe)]
174        let cpuid = unsafe { core::arch::x86_64::__cpuid(0x8000_000a) };
175        let nasid = cpuid.ebx;
176
177        Some(Self {
178            tlb_flush_nested,
179            invlpgb_count_max,
180            nasid,
181        })
182    }
183
184    /// Returns the maximum count of pages to be flushed supported by the processor.
185    #[inline]
186    pub fn invlpgb_count_max(&self) -> u16 {
187        self.invlpgb_count_max
188    }
189
190    /// Returns whether the processor supports flushing translations used for guest translation.
191    #[inline]
192    pub fn tlb_flush_nested(&self) -> bool {
193        self.tlb_flush_nested
194    }
195
196    /// Returns the number of available address space identifiers.
197    #[inline]
198    pub fn nasid(&self) -> u32 {
199        self.nasid
200    }
201
202    /// Create a `InvlpgbFlushBuilder`.
203    pub fn build(&self) -> InvlpgbFlushBuilder<'_> {
204        InvlpgbFlushBuilder {
205            invlpgb: self,
206            page_range: None,
207            pcid: None,
208            asid: None,
209            include_global: false,
210            final_translation_only: false,
211            include_nested_translations: false,
212        }
213    }
214
215    /// Wait for all previous `invlpgb` instruction executed on the current
216    /// logical processor to be acknowledged by all other logical processors.
217    #[inline]
218    pub fn tlbsync(&self) {
219        unsafe {
220            asm!("tlbsync", options(nomem, preserves_flags));
221        }
222    }
223}
224
225/// A builder struct to construct the parameters for the `invlpgb` instruction.
226#[derive(Debug, Clone)]
227#[must_use]
228pub struct InvlpgbFlushBuilder<'a, S = Size4KiB>
229where
230    S: NotGiantPageSize,
231{
232    invlpgb: &'a Invlpgb,
233    page_range: Option<PageRange<S>>,
234    pcid: Option<Pcid>,
235    asid: Option<u16>,
236    include_global: bool,
237    final_translation_only: bool,
238    include_nested_translations: bool,
239}
240
241impl<'a, S> InvlpgbFlushBuilder<'a, S>
242where
243    S: NotGiantPageSize,
244{
245    /// Flush a range of pages.
246    ///
247    /// If the range doesn't fit within `invlpgb_count_max`, `invlpgb` is
248    /// executed multiple times.
249    pub fn pages<T>(self, page_range: PageRange<T>) -> InvlpgbFlushBuilder<'a, T>
250    where
251        T: NotGiantPageSize,
252    {
253        InvlpgbFlushBuilder {
254            invlpgb: self.invlpgb,
255            page_range: Some(page_range),
256            pcid: self.pcid,
257            asid: self.asid,
258            include_global: self.include_global,
259            final_translation_only: self.final_translation_only,
260            include_nested_translations: self.include_nested_translations,
261        }
262    }
263
264    /// Only flush TLB entries with the given PCID.
265    ///
266    /// # Safety
267    ///
268    /// The caller has to ensure that PCID is enabled in CR4 when the flush is executed.
269    pub unsafe fn pcid(&mut self, pcid: Pcid) -> &mut Self {
270        self.pcid = Some(pcid);
271        self
272    }
273
274    /// Only flush TLB entries with the given ASID.
275    ///
276    /// # Safety
277    ///
278    /// The caller has to ensure that SVM is enabled in EFER when the flush is executed.
279    // FIXME: Make ASID a type and remove error type.
280    pub unsafe fn asid(&mut self, asid: u16) -> Result<&mut Self, AsidOutOfRangeError> {
281        if u32::from(asid) >= self.invlpgb.nasid {
282            return Err(AsidOutOfRangeError {
283                asid,
284                nasid: self.invlpgb.nasid,
285            });
286        }
287
288        self.asid = Some(asid);
289        Ok(self)
290    }
291
292    /// Also flush global pages.
293    pub fn include_global(&mut self) -> &mut Self {
294        self.include_global = true;
295        self
296    }
297
298    /// Only flush the final translation and not the cached upper level TLB entries.
299    pub fn final_translation_only(&mut self) -> &mut Self {
300        self.final_translation_only = true;
301        self
302    }
303
304    /// Also flush nestred translations that could be used for guest translation.
305    pub fn include_nested_translations(mut self) -> Self {
306        assert!(
307            self.invlpgb.tlb_flush_nested,
308            "flushing all nested translations is not supported"
309        );
310
311        self.include_nested_translations = true;
312        self
313    }
314
315    /// Execute the flush.
316    pub fn flush(&self) {
317        if let Some(mut pages) = self.page_range {
318            while !pages.is_empty() {
319                // Calculate out how many pages we still need to flush.
320                let count = Page::<S>::steps_between_impl(&pages.start, &pages.end).0;
321
322                // Make sure that we never jump the gap in the address space when flushing.
323                let second_half_start =
324                    Page::<S>::containing_address(VirtAddr::new(0xffff_8000_0000_0000));
325                let count = if pages.start < second_half_start {
326                    let count_to_second_half =
327                        Page::steps_between_impl(&pages.start, &second_half_start).0;
328                    cmp::min(count, count_to_second_half)
329                } else {
330                    count
331                };
332
333                // We can flush at most u16::MAX pages at once.
334                let count = u16::try_from(count).unwrap_or(u16::MAX);
335
336                // Cap the count by the maximum supported count of the processor.
337                let count = cmp::min(count, self.invlpgb.invlpgb_count_max);
338
339                unsafe {
340                    flush_broadcast(
341                        Some((pages.start, count)),
342                        self.pcid,
343                        self.asid,
344                        self.include_global,
345                        self.final_translation_only,
346                        self.include_nested_translations,
347                    );
348                }
349
350                // Even if the count is zero, one page is still flushed and so
351                // we need to advance by at least one.
352                let inc_count = cmp::max(count, 1);
353                pages.start =
354                    Page::forward_checked_impl(pages.start, usize::from(inc_count)).unwrap();
355            }
356        } else {
357            unsafe {
358                flush_broadcast::<S>(
359                    None,
360                    self.pcid,
361                    self.asid,
362                    self.include_global,
363                    self.final_translation_only,
364                    self.include_nested_translations,
365                );
366            }
367        }
368    }
369}
370
371/// An error returned when trying to use an invalid ASID.
372#[derive(Debug)]
373pub struct AsidOutOfRangeError {
374    /// The requested ASID.
375    pub asid: u16,
376    /// The number of valid ASIDS.
377    pub nasid: u32,
378}
379
380impl fmt::Display for AsidOutOfRangeError {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        write!(
383            f,
384            "{} is out of the range of available ASIDS ({})",
385            self.asid, self.nasid
386        )
387    }
388}
389
390/// See `INVLPGB` in AMD64 Architecture Programmer's Manual Volume 3
391#[inline]
392unsafe fn flush_broadcast<S>(
393    va_and_count: Option<(Page<S>, u16)>,
394    pcid: Option<Pcid>,
395    asid: Option<u16>,
396    include_global: bool,
397    final_translation_only: bool,
398    include_nested_translations: bool,
399) where
400    S: NotGiantPageSize,
401{
402    let mut rax = 0;
403    let mut ecx = 0;
404    let mut edx = 0;
405
406    if let Some((va, count)) = va_and_count {
407        rax.set_bit(0, true);
408        rax.set_bits(12.., va.start_address().as_u64().get_bits(12..));
409
410        ecx.set_bits(0..=15, u32::from(count));
411        ecx.set_bit(31, S::SIZE == Size2MiB::SIZE);
412    }
413
414    if let Some(pcid) = pcid {
415        rax.set_bit(1, true);
416        edx.set_bits(16..=27, u32::from(pcid.value()));
417    }
418
419    if let Some(asid) = asid {
420        rax.set_bit(2, true);
421        edx.set_bits(0..=15, u32::from(asid));
422    }
423
424    rax.set_bit(3, include_global);
425    rax.set_bit(4, final_translation_only);
426    rax.set_bit(5, include_nested_translations);
427
428    unsafe {
429        asm!(
430            "invlpgb",
431            in("rax") rax,
432            in("ecx") ecx,
433            in("edx") edx,
434            options(nostack, preserves_flags),
435        );
436    }
437}