Skip to main content

ostd/vm/
gpm_space.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Guest physical memory space.
4
5use core::{mem::ManuallyDrop, ops::Range};
6
7use super::Gpaddr;
8use crate::{
9    arch::vm::{
10        ept::{EptItem, EptPtConfig},
11        vmx::{VmxGuard, invept},
12    },
13    mm::{
14        AnyUFrameMeta, Frame, PageFlags, PageProperty, UFrame,
15        frame::FrameRef,
16        page_table::{self, PageTable, PageTableFrag},
17    },
18    prelude::*,
19    smp::PendingIpis,
20    sync::RcuDrop,
21    task::atomic_mode::AsAtomicModeGuard,
22};
23
24/// Guest physical memory space of a VM.
25///
26/// This type owns the page table that maps guest physical addresses to
27/// host physical frames. Its cursors manage 4-KiB mappings within a 48-bit
28/// guest physical address space. Each mapping must permit reads; write and
29/// execute permissions can be added independently.
30///
31/// This address space must be dropped outside [atomic mode](crate::task::atomic_mode).
32pub struct GuestPhysMemSpace {
33    pt: PageTable<EptPtConfig>,
34    vmx_guard: VmxGuard,
35}
36
37impl GuestPhysMemSpace {
38    /// Creates a new guest physical memory space.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the CPU does not support second-stage address
43    /// translation or the required invalidation operations.
44    ///
45    /// # Panics
46    ///
47    /// Panics if called in [atomic mode](crate::task::atomic_mode).
48    pub fn new() -> Result<Self> {
49        let vmx_guard = VmxGuard::acquire_vmx()?;
50        let pt = PageTable::<EptPtConfig>::empty();
51
52        // A reused EPT root-table address may still have cached translations.
53        invept::invalidate(&vmx_guard, &[]).wait();
54
55        Ok(Self { pt, vmx_guard })
56    }
57
58    /// Gets an immutable cursor over a guest physical address range.
59    ///
60    /// The cursor behaves like a lock guard, exclusively owning a sub-tree of
61    /// the page table, preventing others from creating a cursor in it. So be
62    /// sure to drop the cursor as soon as possible.
63    ///
64    /// The creation of the cursor may block if another cursor having an
65    /// overlapping range is alive.
66    pub fn cursor<'a, G: AsAtomicModeGuard>(
67        &'a self,
68        guard: &'a G,
69        gpa: &Range<Gpaddr>,
70    ) -> Result<Cursor<'a>> {
71        Ok(Cursor(self.pt.cursor(guard, gpa)?))
72    }
73
74    /// Gets a mutable cursor over a guest physical address range.
75    ///
76    /// The same as [`Self::cursor`], the cursor behaves like a lock guard,
77    /// exclusively owning a sub-tree of the page table, preventing others
78    /// from creating a cursor in it. So be sure to drop the cursor as soon as
79    /// possible.
80    ///
81    /// The creation of the cursor may block if another cursor having an
82    /// overlapping range is alive.
83    pub fn cursor_mut<'a, G: AsAtomicModeGuard>(
84        &'a self,
85        guard: &'a G,
86        gpa: &Range<Gpaddr>,
87    ) -> Result<CursorMut<'a>> {
88        Ok(CursorMut {
89            pt_cursor: self.pt.cursor_mut(guard, gpa)?,
90            vmx_guard: &self.vmx_guard,
91            pending_ipis: PendingIpis::new_empty(),
92        })
93    }
94
95    /// Returns the EPT pointer value for this guest memory space.
96    ///
97    /// The caller must keep this address space borrowed while its EPTP is in use.
98    pub(crate) fn eptp(&self) -> u64 {
99        const EPT_MEM_TYPE_WB: u64 = 6;
100        const EPT_PAGE_WALK_LENGTH_4_LEVELS: u64 = 3 << 3;
101
102        self.pt.root_paddr() as u64 | EPT_MEM_TYPE_WB | EPT_PAGE_WALK_LENGTH_4_LEVELS
103    }
104}
105
106/// A borrowed backing frame and its page properties.
107pub type QueriedItem<'a> = (FrameRef<'a, dyn AnyUFrameMeta>, PageProperty);
108
109/// The cursor for querying over the guest physical memory space without modifying it.
110///
111/// It exclusively owns a sub-tree of the page table, preventing others from
112/// reading or modifying the same sub-tree. Two read-only cursors can not be
113/// created from the same guest physical address range either.
114pub struct Cursor<'a>(page_table::Cursor<'a, EptPtConfig>);
115
116impl Cursor<'_> {
117    /// Queries the mapping at the current guest physical address.
118    ///
119    /// If the cursor is pointing to a valid guest physical address that is
120    /// locked, it will return the guest physical address range, the borrowed
121    /// backing frame, and its page properties.
122    pub fn query(&mut self) -> Result<(Range<Gpaddr>, Option<QueriedItem<'_>>)> {
123        Ok(self.0.query()?)
124    }
125
126    /// Moves the cursor forward to the next mapped guest physical address.
127    ///
128    /// If there is a mapped guest physical address following the current
129    /// address within next `len` bytes, it will return that mapped address. In
130    /// this case, the cursor will stop at the mapped address.
131    ///
132    /// Otherwise, it will return `None`. And the cursor may stop at any
133    /// address after `len` bytes.
134    ///
135    /// # Panics
136    ///
137    /// Panics if:
138    ///  - the length is longer than the remaining range of the cursor;
139    ///  - the length is not page-aligned.
140    pub fn find_next(&mut self, len: usize) -> Option<Gpaddr> {
141        self.0.find_next(len)
142    }
143
144    /// Jumps to the guest physical address.
145    ///
146    /// If the target address is out of the range, this method will return `Err`.
147    ///
148    /// # Panics
149    ///
150    /// This method panics if the address has bad alignment.
151    pub fn jump(&mut self, gpa: Gpaddr) -> Result<()> {
152        self.0.jump(gpa)?;
153        Ok(())
154    }
155
156    /// Gets the guest physical address of the current slot.
157    pub fn gpa(&self) -> Gpaddr {
158        self.0.virt_addr()
159    }
160}
161
162/// The cursor for modifying the mappings in guest physical memory space.
163///
164/// It exclusively owns a sub-tree of the page table, preventing others from
165/// reading or modifying the same sub-tree.
166pub struct CursorMut<'a> {
167    pt_cursor: page_table::CursorMut<'a, EptPtConfig>,
168    vmx_guard: &'a VmxGuard,
169    pending_ipis: PendingIpis,
170}
171
172impl<'a> CursorMut<'a> {
173    /// Queries the mapping at the current guest physical address.
174    ///
175    /// This is the same as [`Cursor::query`].
176    ///
177    /// If the cursor is pointing to a valid guest physical address that is
178    /// locked, it will return the guest physical address range, the borrowed
179    /// backing frame, and its page properties.
180    pub fn query(&mut self) -> Result<(Range<Gpaddr>, Option<QueriedItem<'_>>)> {
181        Ok(self.pt_cursor.query()?)
182    }
183
184    /// Moves the cursor forward to the next mapped guest physical address.
185    ///
186    /// This is the same as [`Cursor::find_next`].
187    pub fn find_next(&mut self, len: usize) -> Option<Gpaddr> {
188        self.pt_cursor.find_next(len)
189    }
190
191    /// Jumps to the guest physical address.
192    ///
193    /// This is the same as [`Cursor::jump`].
194    ///
195    /// # Panics
196    ///
197    /// This method panics if the address has bad alignment.
198    pub fn jump(&mut self, gpa: Gpaddr) -> Result<()> {
199        self.pt_cursor.jump(gpa)?;
200        Ok(())
201    }
202
203    /// Gets the guest physical address of the current slot.
204    pub fn gpa(&self) -> Gpaddr {
205        self.pt_cursor.virt_addr()
206    }
207
208    /// Maps a frame into the current slot.
209    ///
210    /// This method will bring the cursor to the next slot after the modification.
211    ///
212    /// # Panics
213    ///
214    /// Panics if:
215    ///  - the current guest physical address is already mapped;
216    ///  - the current guest physical address is outside the cursor's range.
217    pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
218        let item: EptItem = (frame, prop);
219
220        // SAFETY: It is safe to map untyped memory into guest physical memory.
221        unsafe { self.pt_cursor.map(item) };
222    }
223
224    /// Applies the operation to the next slot of mapping within the range.
225    ///
226    /// The range to be found in is the current guest physical address with the
227    /// provided length.
228    ///
229    /// The function stops and yields the actually protected range if it has
230    /// actually protected a page, no matter if the following pages are also
231    /// required to be protected.
232    ///
233    /// It also makes the cursor moves forward to the next page after the
234    /// protected one. If no mapped pages exist in the following range, the
235    /// cursor will stop at the end of the range and return [`None`].
236    ///
237    /// Cached translations are invalidated on the current CPU before returning,
238    /// and asynchronously on remote CPUs. Use [`Self::sync_tlb_flush`] to
239    /// wait for remote invalidations to complete.
240    ///
241    /// # Panics
242    ///
243    /// Panics if:
244    ///  - the length is longer than the remaining range of the cursor;
245    ///  - the length is not page-aligned.
246    pub fn protect_next(
247        &mut self,
248        len: usize,
249        op: &mut impl FnMut(&mut PageFlags),
250    ) -> Option<Range<Gpaddr>> {
251        // SAFETY: It is safe to set `PageFlags` of guest physical memory.
252        let range = unsafe {
253            self.pt_cursor
254                .protect_next(len, &mut |prop| op(&mut prop.flags))
255        }?;
256        self.pending_ipis
257            .extend(&invept::invalidate(self.vmx_guard, &[]));
258        Some(range)
259    }
260
261    /// Clears the mapping starting from the current slot,
262    /// and returns the number of unmapped pages.
263    ///
264    /// This method brings the cursor forward by at least `len` bytes in the
265    /// guest physical address space, but not past the end of the cursor's range.
266    ///
267    /// Already-absent mappings encountered by the cursor will be skipped.
268    /// It is valid to unmap a range that is not mapped.
269    ///
270    /// This method issues and dispatches EPT invalidation for removed mappings.
271    /// Cached translations are invalidated on the current CPU before returning,
272    /// and asynchronously on remote CPUs. Removed frames are retained until
273    /// invalidation completes on all CPUs. Use [`Self::sync_tlb_flush`] to
274    /// wait for remote invalidations to complete. Using a large `len` avoids
275    /// the overhead of multiple invalidations.
276    ///
277    /// # Panics
278    ///
279    /// Panics if:
280    ///  - the length is longer than the remaining range of the cursor;
281    ///  - the length is not page-aligned.
282    pub fn unmap(&mut self, len: usize) -> usize {
283        let end_gpa = self.gpa() + len;
284        let mut num_unmapped: usize = 0;
285        // Retain removed frames even if unwinding happens before dispatch.
286        let mut frames = ManuallyDrop::new(Vec::new());
287        loop {
288            frames.reserve(1);
289            // SAFETY:
290            // 1. It is safe to unmap guest physical memory.
291            // 2. Removed frames are retained below, then cloned into each CPU's
292            //    invalidation queue before their references here are released.
293            let Some(frag) = (unsafe { self.pt_cursor.take_next(end_gpa - self.gpa()) }) else {
294                break; // No more mappings in the range.
295            };
296
297            match frag {
298                PageTableFrag::Mapped { item, .. } => {
299                    // SAFETY: The frame will be dropped after the RCU grace
300                    // period (see the following `RcuDrop::new`).
301                    let ((frame, _), panic_guard) = unsafe { RcuDrop::into_inner(item) };
302                    frames.push(Frame::rcu_from_unsized(RcuDrop::new(frame)));
303                    panic_guard.forget();
304                    num_unmapped += 1;
305                }
306                PageTableFrag::StrayPageTable { pt, num_frames, .. } => {
307                    frames.push(Frame::rcu_from_unsized(pt));
308                    num_unmapped += num_frames;
309                }
310            }
311        }
312
313        if !frames.is_empty() {
314            self.pending_ipis
315                .extend(&invept::invalidate(self.vmx_guard, &frames));
316        }
317        drop(ManuallyDrop::into_inner(frames));
318        num_unmapped
319    }
320
321    /// Waits for this cursor's previous EPT invalidations to complete on all CPUs.
322    ///
323    /// This synchronizes invalidations issued by [`Self::unmap`] and
324    /// [`Self::protect_next`]. Dropping the cursor does not wait for completion;
325    /// removed frames are retained until invalidation completes regardless.
326    ///
327    /// # Panics
328    ///
329    /// Panics if local IRQs are disabled.
330    pub fn sync_tlb_flush(&mut self) {
331        self.pending_ipis.wait();
332        self.pending_ipis = PendingIpis::new_empty();
333    }
334}
335
336#[cfg(ktest)]
337mod test {
338    use super::*;
339    use crate::{
340        Error,
341        mm::{CachePolicy, FrameAllocOptions, PAGE_SIZE},
342        task::disable_preempt,
343    };
344
345    #[ktest]
346    fn guest_mapping_lifecycle() {
347        let space = match GuestPhysMemSpace::new() {
348            Ok(space) => space,
349            Err(Error::NotEnoughResources | Error::AccessDenied) => {
350                crate::early_print!(" [skipped: VMX/EPT invalidation unavailable]");
351                return;
352            }
353            Err(err) => panic!("failed to create guest physical memory: {:?}", err),
354        };
355
356        let first = FrameAllocOptions::new().alloc_frame().unwrap();
357        let second = FrameAllocOptions::new().alloc_frame().unwrap();
358        let first_paddr = first.paddr();
359        let second_paddr = second.paddr();
360        let prop = PageProperty::new_guest(PageFlags::RWX, CachePolicy::Writeback);
361        let range = PAGE_SIZE..4 * PAGE_SIZE;
362
363        let guard = disable_preempt();
364        let mut cursor = space.cursor_mut(&guard, &range).unwrap();
365
366        // Test `map`.
367        cursor.map(first.into(), prop);
368        cursor.map(second.into(), prop);
369        assert_eq!(cursor.gpa(), 3 * PAGE_SIZE);
370
371        // Test `query`.
372        cursor.jump(2 * PAGE_SIZE).unwrap();
373        let (queried_range, item) = cursor.query().unwrap();
374        assert_eq!(queried_range, 2 * PAGE_SIZE..3 * PAGE_SIZE);
375        assert_eq!(
376            item.map(|(frame, prop)| (frame.paddr(), prop)),
377            Some((second_paddr, prop))
378        );
379        cursor.jump(PAGE_SIZE).unwrap();
380        let (queried_range, item) = cursor.query().unwrap();
381        assert_eq!(queried_range, PAGE_SIZE..2 * PAGE_SIZE);
382        assert_eq!(
383            item.map(|(frame, prop)| (frame.paddr(), prop)),
384            Some((first_paddr, prop))
385        );
386
387        // Test `protect`.
388        assert_eq!(
389            cursor.protect_next(PAGE_SIZE, &mut |flags| *flags = PageFlags::RX),
390            Some(PAGE_SIZE..2 * PAGE_SIZE)
391        );
392        cursor.jump(PAGE_SIZE).unwrap();
393        assert_eq!(cursor.query().unwrap().1.unwrap().1.flags, PageFlags::RX);
394
395        // Test `unmap`.
396        assert_eq!(cursor.unmap(2 * PAGE_SIZE), 2);
397        cursor.jump(PAGE_SIZE).unwrap();
398        assert!(cursor.find_next(3 * PAGE_SIZE).is_none());
399    }
400}