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}