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}