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    #[expect(dead_code)]
99    pub(crate) fn eptp(&self) -> u64 {
100        const EPT_MEM_TYPE_WB: u64 = 6;
101        const EPT_PAGE_WALK_LENGTH_4_LEVELS: u64 = 3 << 3;
102
103        self.pt.root_paddr() as u64 | EPT_MEM_TYPE_WB | EPT_PAGE_WALK_LENGTH_4_LEVELS
104    }
105}
106
107/// A borrowed backing frame and its page properties.
108pub type QueriedItem<'a> = (FrameRef<'a, dyn AnyUFrameMeta>, PageProperty);
109
110/// The cursor for querying over the guest physical memory space without modifying it.
111///
112/// It exclusively owns a sub-tree of the page table, preventing others from
113/// reading or modifying the same sub-tree. Two read-only cursors can not be
114/// created from the same guest physical address range either.
115pub struct Cursor<'a>(page_table::Cursor<'a, EptPtConfig>);
116
117impl Cursor<'_> {
118    /// Queries the mapping at the current guest physical address.
119    ///
120    /// If the cursor is pointing to a valid guest physical address that is
121    /// locked, it will return the guest physical address range, the borrowed
122    /// backing frame, and its page properties.
123    pub fn query(&mut self) -> Result<(Range<Gpaddr>, Option<QueriedItem<'_>>)> {
124        Ok(self.0.query()?)
125    }
126
127    /// Moves the cursor forward to the next mapped guest physical address.
128    ///
129    /// If there is a mapped guest physical address following the current
130    /// address within next `len` bytes, it will return that mapped address. In
131    /// this case, the cursor will stop at the mapped address.
132    ///
133    /// Otherwise, it will return `None`. And the cursor may stop at any
134    /// address after `len` bytes.
135    ///
136    /// # Panics
137    ///
138    /// Panics if:
139    ///  - the length is longer than the remaining range of the cursor;
140    ///  - the length is not page-aligned.
141    pub fn find_next(&mut self, len: usize) -> Option<Gpaddr> {
142        self.0.find_next(len)
143    }
144
145    /// Jumps to the guest physical address.
146    ///
147    /// If the target address is out of the range, this method will return `Err`.
148    ///
149    /// # Panics
150    ///
151    /// This method panics if the address has bad alignment.
152    pub fn jump(&mut self, gpa: Gpaddr) -> Result<()> {
153        self.0.jump(gpa)?;
154        Ok(())
155    }
156
157    /// Gets the guest physical address of the current slot.
158    pub fn gpa(&self) -> Gpaddr {
159        self.0.virt_addr()
160    }
161}
162
163/// The cursor for modifying the mappings in guest physical memory space.
164///
165/// It exclusively owns a sub-tree of the page table, preventing others from
166/// reading or modifying the same sub-tree.
167pub struct CursorMut<'a> {
168    pt_cursor: page_table::CursorMut<'a, EptPtConfig>,
169    vmx_guard: &'a VmxGuard,
170    pending_ipis: PendingIpis,
171}
172
173impl<'a> CursorMut<'a> {
174    /// Queries the mapping at the current guest physical address.
175    ///
176    /// This is the same as [`Cursor::query`].
177    ///
178    /// If the cursor is pointing to a valid guest physical address that is
179    /// locked, it will return the guest physical address range, the borrowed
180    /// backing frame, and its page properties.
181    pub fn query(&mut self) -> Result<(Range<Gpaddr>, Option<QueriedItem<'_>>)> {
182        Ok(self.pt_cursor.query()?)
183    }
184
185    /// Moves the cursor forward to the next mapped guest physical address.
186    ///
187    /// This is the same as [`Cursor::find_next`].
188    pub fn find_next(&mut self, len: usize) -> Option<Gpaddr> {
189        self.pt_cursor.find_next(len)
190    }
191
192    /// Jumps to the guest physical address.
193    ///
194    /// This is the same as [`Cursor::jump`].
195    ///
196    /// # Panics
197    ///
198    /// This method panics if the address has bad alignment.
199    pub fn jump(&mut self, gpa: Gpaddr) -> Result<()> {
200        self.pt_cursor.jump(gpa)?;
201        Ok(())
202    }
203
204    /// Gets the guest physical address of the current slot.
205    pub fn gpa(&self) -> Gpaddr {
206        self.pt_cursor.virt_addr()
207    }
208
209    /// Maps a frame into the current slot.
210    ///
211    /// This method will bring the cursor to the next slot after the modification.
212    ///
213    /// # Panics
214    ///
215    /// Panics if:
216    ///  - the current guest physical address is already mapped;
217    ///  - the current guest physical address is outside the cursor's range.
218    pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
219        let item: EptItem = (frame, prop);
220
221        // SAFETY: It is safe to map untyped memory into guest physical memory.
222        unsafe { self.pt_cursor.map(item) };
223    }
224
225    /// Applies the operation to the next slot of mapping within the range.
226    ///
227    /// The range to be found in is the current guest physical address with the
228    /// provided length.
229    ///
230    /// The function stops and yields the actually protected range if it has
231    /// actually protected a page, no matter if the following pages are also
232    /// required to be protected.
233    ///
234    /// It also makes the cursor moves forward to the next page after the
235    /// protected one. If no mapped pages exist in the following range, the
236    /// cursor will stop at the end of the range and return [`None`].
237    ///
238    /// Cached translations are invalidated on the current CPU before returning,
239    /// and asynchronously on remote CPUs. Use [`Self::sync_tlb_flush`] to
240    /// wait for remote invalidations to complete.
241    ///
242    /// # Panics
243    ///
244    /// Panics if:
245    ///  - the length is longer than the remaining range of the cursor;
246    ///  - the length is not page-aligned.
247    pub fn protect_next(
248        &mut self,
249        len: usize,
250        op: &mut impl FnMut(&mut PageFlags),
251    ) -> Option<Range<Gpaddr>> {
252        // SAFETY: It is safe to set `PageFlags` of guest physical memory.
253        let range = unsafe {
254            self.pt_cursor
255                .protect_next(len, &mut |prop| op(&mut prop.flags))
256        }?;
257        self.pending_ipis
258            .extend(&invept::invalidate(self.vmx_guard, &[]));
259        Some(range)
260    }
261
262    /// Clears the mapping starting from the current slot,
263    /// and returns the number of unmapped pages.
264    ///
265    /// This method brings the cursor forward by at least `len` bytes in the
266    /// guest physical address space, but not past the end of the cursor's range.
267    ///
268    /// Already-absent mappings encountered by the cursor will be skipped.
269    /// It is valid to unmap a range that is not mapped.
270    ///
271    /// This method issues and dispatches EPT invalidation for removed mappings.
272    /// Cached translations are invalidated on the current CPU before returning,
273    /// and asynchronously on remote CPUs. Removed frames are retained until
274    /// invalidation completes on all CPUs. Use [`Self::sync_tlb_flush`] to
275    /// wait for remote invalidations to complete. Using a large `len` avoids
276    /// the overhead of multiple invalidations.
277    ///
278    /// # Panics
279    ///
280    /// Panics if:
281    ///  - the length is longer than the remaining range of the cursor;
282    ///  - the length is not page-aligned.
283    pub fn unmap(&mut self, len: usize) -> usize {
284        let end_gpa = self.gpa() + len;
285        let mut num_unmapped: usize = 0;
286        // Retain removed frames even if unwinding happens before dispatch.
287        let mut frames = ManuallyDrop::new(Vec::new());
288        loop {
289            frames.reserve(1);
290            // SAFETY:
291            // 1. It is safe to unmap guest physical memory.
292            // 2. Removed frames are retained below, then cloned into each CPU's
293            //    invalidation queue before their references here are released.
294            let Some(frag) = (unsafe { self.pt_cursor.take_next(end_gpa - self.gpa()) }) else {
295                break; // No more mappings in the range.
296            };
297
298            match frag {
299                PageTableFrag::Mapped { item, .. } => {
300                    // SAFETY: The frame will be dropped after the RCU grace
301                    // period (see the following `RcuDrop::new`).
302                    let ((frame, _), panic_guard) = unsafe { RcuDrop::into_inner(item) };
303                    frames.push(Frame::rcu_from_unsized(RcuDrop::new(frame)));
304                    panic_guard.forget();
305                    num_unmapped += 1;
306                }
307                PageTableFrag::StrayPageTable { pt, num_frames, .. } => {
308                    frames.push(Frame::rcu_from_unsized(pt));
309                    num_unmapped += num_frames;
310                }
311            }
312        }
313
314        if !frames.is_empty() {
315            self.pending_ipis
316                .extend(&invept::invalidate(self.vmx_guard, &frames));
317        }
318        drop(ManuallyDrop::into_inner(frames));
319        num_unmapped
320    }
321
322    /// Waits for this cursor's previous EPT invalidations to complete on all CPUs.
323    ///
324    /// This synchronizes invalidations issued by [`Self::unmap`] and
325    /// [`Self::protect_next`]. Dropping the cursor does not wait for completion;
326    /// removed frames are retained until invalidation completes regardless.
327    ///
328    /// # Panics
329    ///
330    /// Panics if local IRQs are disabled.
331    pub fn sync_tlb_flush(&mut self) {
332        self.pending_ipis.wait();
333        self.pending_ipis = PendingIpis::new_empty();
334    }
335}
336
337#[cfg(ktest)]
338mod test {
339    use super::*;
340    use crate::{
341        Error,
342        mm::{CachePolicy, FrameAllocOptions, PAGE_SIZE},
343        task::disable_preempt,
344    };
345
346    #[ktest]
347    fn guest_mapping_lifecycle() {
348        let space = match GuestPhysMemSpace::new() {
349            Ok(space) => space,
350            Err(Error::NotEnoughResources | Error::AccessDenied) => {
351                crate::early_print!(" [skipped: VMX/EPT invalidation unavailable]");
352                return;
353            }
354            Err(err) => panic!("failed to create guest physical memory: {:?}", err),
355        };
356
357        let first = FrameAllocOptions::new().alloc_frame().unwrap();
358        let second = FrameAllocOptions::new().alloc_frame().unwrap();
359        let first_paddr = first.paddr();
360        let second_paddr = second.paddr();
361        let prop = PageProperty::new_user(PageFlags::RWX, CachePolicy::Writeback);
362        let range = PAGE_SIZE..4 * PAGE_SIZE;
363
364        let guard = disable_preempt();
365        let mut cursor = space.cursor_mut(&guard, &range).unwrap();
366
367        // Test `map`.
368        cursor.map(first.into(), prop);
369        cursor.map(second.into(), prop);
370        assert_eq!(cursor.gpa(), 3 * PAGE_SIZE);
371
372        // Test `query`.
373        cursor.jump(2 * PAGE_SIZE).unwrap();
374        let (queried_range, item) = cursor.query().unwrap();
375        assert_eq!(queried_range, 2 * PAGE_SIZE..3 * PAGE_SIZE);
376        assert_eq!(
377            item.map(|(frame, prop)| (frame.paddr(), prop)),
378            Some((second_paddr, prop))
379        );
380        cursor.jump(PAGE_SIZE).unwrap();
381        let (queried_range, item) = cursor.query().unwrap();
382        assert_eq!(queried_range, PAGE_SIZE..2 * PAGE_SIZE);
383        assert_eq!(
384            item.map(|(frame, prop)| (frame.paddr(), prop)),
385            Some((first_paddr, prop))
386        );
387
388        // Test `protect`.
389        assert_eq!(
390            cursor.protect_next(PAGE_SIZE, &mut |flags| *flags = PageFlags::RX),
391            Some(PAGE_SIZE..2 * PAGE_SIZE)
392        );
393        cursor.jump(PAGE_SIZE).unwrap();
394        assert_eq!(cursor.query().unwrap().1.unwrap().1.flags, PageFlags::RX);
395
396        // Test `unmap`.
397        assert_eq!(cursor.unmap(2 * PAGE_SIZE), 2);
398        cursor.jump(PAGE_SIZE).unwrap();
399        assert!(cursor.find_next(3 * PAGE_SIZE).is_none());
400    }
401}