ostd/mm/vm_space.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! Virtual memory space management.
4//!
5//! The [`VmSpace`] struct is provided to manage the virtual memory space of a
6//! user. Cursors are used to traverse and modify over the virtual memory space
7//! concurrently. The VM space cursor [`self::Cursor`] is just a wrapper over
8//! the page table cursor, providing efficient, powerful concurrent accesses
9//! to the page table.
10
11use core::{ops::Range, sync::atomic::Ordering};
12
13use super::{AnyUFrameMeta, PagingLevel, page_table::PageTableConfig};
14use crate::{
15 Error,
16 arch::mm::{PageTableEntry, PagingConsts, current_page_table_paddr},
17 cpu::{AtomicCpuSet, CpuSet, PinCurrentCpu},
18 cpu_local_cell,
19 io::IoMem,
20 mm::{
21 Frame, MAX_USERSPACE_VADDR, PAGE_SIZE, PageProperty, PrivilegedPageFlags, UFrame, VmReader,
22 VmWriter,
23 frame::FrameRef,
24 io::Fallible,
25 kspace::KERNEL_PAGE_TABLE,
26 page_prop::{CachePolicy, PageFlags},
27 page_table::{self, PageTable, PageTableFrag},
28 tlb::{TlbFlushOp, TlbFlusher},
29 },
30 prelude::*,
31 sync::{RcuDrop, SpinLock},
32 task::{DisabledPreemptGuard, atomic_mode::AsAtomicModeGuard, disable_preempt},
33};
34
35/// A virtual address space for user-mode tasks, enabling safe manipulation of user-space memory.
36///
37/// The `VmSpace` type provides memory isolation guarantees between user-space and
38/// kernel-space. For example, given an arbitrary user-space pointer, one can read and
39/// write the memory location referred to by the user-space pointer without the risk of
40/// breaking the memory safety of the kernel space.
41///
42/// # Task Association Semantics
43///
44/// As far as OSTD is concerned, a `VmSpace` is not necessarily associated with a task. Once a
45/// `VmSpace` is activated (see [`VmSpace::activate`]), it remains activated until another
46/// `VmSpace` is activated **possibly by another task running on the same CPU**.
47///
48/// This means that it's up to the kernel to ensure that a task's `VmSpace` is always activated
49/// while the task is running. This can be done by using the injected post schedule handler
50/// (see [`inject_post_schedule_handler`]) to always activate the correct `VmSpace` after each
51/// context switch.
52///
53/// If the kernel otherwise decides not to ensure that the running task's `VmSpace` is always
54/// activated, the kernel must deal with race conditions when calling methods that require the
55/// `VmSpace` to be activated, e.g., [`UserMode::execute`], [`VmSpace::reader`],
56/// [`VmSpace::writer`]. Otherwise, the behavior is unspecified, though it's guaranteed _not_ to
57/// compromise the kernel's memory safety.
58///
59/// # Memory Backing
60///
61/// A newly-created `VmSpace` is not backed by any physical memory pages. To
62/// provide memory pages for a `VmSpace`, one can allocate and map physical
63/// memory ([`UFrame`]s) to the `VmSpace` using the cursor.
64///
65/// A `VmSpace` can also attach a page fault handler, which will be invoked to
66/// handle page faults generated from user space.
67///
68/// [`inject_post_schedule_handler`]: crate::task::inject_post_schedule_handler
69/// [`UserMode::execute`]: crate::user::UserMode::execute
70#[derive(Debug)]
71pub struct VmSpace {
72 pt: PageTable<UserPtConfig>,
73 cpus: AtomicCpuSet,
74 iomems: SpinLock<Vec<IoMem>>,
75}
76
77impl VmSpace {
78 /// Creates a new VM address space.
79 pub fn new() -> Self {
80 Self {
81 pt: KERNEL_PAGE_TABLE.get().unwrap().create_user_page_table(),
82 cpus: AtomicCpuSet::new(CpuSet::new_empty()),
83 iomems: SpinLock::new(Vec::new()),
84 }
85 }
86
87 /// Gets an immutable cursor in the virtual address range.
88 ///
89 /// The cursor behaves like a lock guard, exclusively owning a sub-tree of
90 /// the page table, preventing others from creating a cursor in it. So be
91 /// sure to drop the cursor as soon as possible.
92 ///
93 /// The creation of the cursor may block if another cursor having an
94 /// overlapping range is alive.
95 pub fn cursor<'a, G: AsAtomicModeGuard>(
96 &'a self,
97 guard: &'a G,
98 va: &Range<Vaddr>,
99 ) -> Result<Cursor<'a>> {
100 Ok(Cursor(self.pt.cursor(guard, va)?))
101 }
102
103 /// Gets an mutable cursor in the virtual address range.
104 ///
105 /// The same as [`Self::cursor`], the cursor behaves like a lock guard,
106 /// exclusively owning a sub-tree of the page table, preventing others
107 /// from creating a cursor in it. So be sure to drop the cursor as soon as
108 /// possible.
109 ///
110 /// The creation of the cursor may block if another cursor having an
111 /// overlapping range is alive. The modification to the mapping by the
112 /// cursor may also block or be overridden the mapping of another cursor.
113 pub fn cursor_mut<'a, G: AsAtomicModeGuard>(
114 &'a self,
115 guard: &'a G,
116 va: &Range<Vaddr>,
117 ) -> Result<CursorMut<'a>> {
118 Ok(CursorMut {
119 pt_cursor: self.pt.cursor_mut(guard, va)?,
120 flusher: TlbFlusher::new(&self.cpus, disable_preempt()),
121 vmspace: self,
122 })
123 }
124
125 /// Activates the page table on the current CPU.
126 pub fn activate(self: &Arc<Self>) {
127 let preempt_guard = disable_preempt();
128 let cpu = preempt_guard.current_cpu();
129
130 let last_ptr = ACTIVATED_VM_SPACE.load();
131
132 if last_ptr == Arc::as_ptr(self) {
133 return;
134 }
135
136 // Record ourselves in the CPU set and the activated VM space pointer.
137 // `Acquire` to ensure the modification to the PT is visible by this CPU.
138 self.cpus.add(cpu, Ordering::Acquire);
139
140 let self_ptr = Arc::into_raw(Arc::clone(self)) as *mut VmSpace;
141 ACTIVATED_VM_SPACE.store(self_ptr);
142
143 if !last_ptr.is_null() {
144 // SAFETY: The pointer is cast from an `Arc` when it's activated
145 // the last time, so it can be restored and only restored once.
146 let last = unsafe { Arc::from_raw(last_ptr) };
147 last.cpus.remove(cpu, Ordering::Relaxed);
148 }
149
150 self.pt.activate();
151 }
152
153 /// Creates a reader to read data from the user space of the current task.
154 ///
155 /// Returns `Err` if this `VmSpace` doesn't belong to the user space of the current task
156 /// or the `vaddr` and `len` do not represent a user space memory range.
157 ///
158 /// Users must ensure that no other page table is activated in the current task during the
159 /// lifetime of the created `VmReader`. This guarantees that the `VmReader` can operate correctly.
160 pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result<VmReader<'_, Fallible>> {
161 if current_page_table_paddr() != self.pt.root_paddr() {
162 return Err(Error::AccessDenied);
163 }
164
165 if vaddr.saturating_add(len) > MAX_USERSPACE_VADDR {
166 return Err(Error::AccessDenied);
167 }
168
169 // SAFETY: The memory range is in user space, as checked above.
170 Ok(unsafe { VmReader::<Fallible>::from_user_space(vaddr as *const u8, len) })
171 }
172
173 /// Creates a writer to write data into the user space.
174 ///
175 /// Returns `Err` if this `VmSpace` doesn't belong to the user space of the current task
176 /// or the `vaddr` and `len` do not represent a user space memory range.
177 ///
178 /// Users must ensure that no other page table is activated in the current task during the
179 /// lifetime of the created `VmWriter`. This guarantees that the `VmWriter` can operate correctly.
180 pub fn writer(&self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'_, Fallible>> {
181 if current_page_table_paddr() != self.pt.root_paddr() {
182 return Err(Error::AccessDenied);
183 }
184
185 if vaddr.saturating_add(len) > MAX_USERSPACE_VADDR {
186 return Err(Error::AccessDenied);
187 }
188
189 // `VmWriter` is neither `Sync` nor `Send`, so it will not live longer than the current
190 // task. This ensures that the correct page table is activated during the usage period of
191 // the `VmWriter`.
192 //
193 // SAFETY: The memory range is in user space, as checked above.
194 Ok(unsafe { VmWriter::<Fallible>::from_user_space(vaddr as *mut u8, len) })
195 }
196
197 /// Creates a reader/writer pair to read data from and write data into the user space.
198 ///
199 /// Returns `Err` if this `VmSpace` doesn't belong to the user space of the current task
200 /// or the `vaddr` and `len` do not represent a user space memory range.
201 ///
202 /// Users must ensure that no other page table is activated in the current task during the
203 /// lifetime of the created `VmReader` and `VmWriter`. This guarantees that the `VmReader`
204 /// and the `VmWriter` can operate correctly.
205 ///
206 /// This method is semantically equivalent to calling [`Self::reader`] and [`Self::writer`]
207 /// separately, but it avoids double checking the validity of the memory region.
208 pub fn reader_writer(
209 &self,
210 vaddr: Vaddr,
211 len: usize,
212 ) -> Result<(VmReader<'_, Fallible>, VmWriter<'_, Fallible>)> {
213 if current_page_table_paddr() != self.pt.root_paddr() {
214 return Err(Error::AccessDenied);
215 }
216
217 if vaddr.saturating_add(len) > MAX_USERSPACE_VADDR {
218 return Err(Error::AccessDenied);
219 }
220
221 // SAFETY: The memory range is in user space, as checked above.
222 let reader = unsafe { VmReader::<Fallible>::from_user_space(vaddr as *const u8, len) };
223
224 // `VmWriter` is neither `Sync` nor `Send`, so it will not live longer than the current
225 // task. This ensures that the correct page table is activated during the usage period of
226 // the `VmWriter`.
227 //
228 // SAFETY: The memory range is in user space, as checked above.
229 let writer = unsafe { VmWriter::<Fallible>::from_user_space(vaddr as *mut u8, len) };
230
231 Ok((reader, writer))
232 }
233}
234
235impl Default for VmSpace {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl VmSpace {
242 /// Finds the [`IoMem`] that contains the given physical address.
243 ///
244 /// It is a private method for internal use only. Please refer to
245 /// [`CursorMut::find_iomem_by_paddr`] for more details.
246 fn find_iomem_by_paddr(&self, paddr: Paddr) -> Option<(IoMem, usize)> {
247 let iomems = self.iomems.lock();
248 for iomem in iomems.iter() {
249 let start = iomem.paddr();
250 let end = start + iomem.size();
251 if paddr >= start && paddr < end {
252 let offset = paddr - start;
253 return Some((iomem.clone(), offset));
254 }
255 }
256 None
257 }
258}
259
260/// The cursor for querying over the VM space without modifying it.
261///
262/// It exclusively owns a sub-tree of the page table, preventing others from
263/// reading or modifying the same sub-tree. Two read-only cursors can not be
264/// created from the same virtual address range either.
265pub struct Cursor<'a>(page_table::Cursor<'a, UserPtConfig>);
266
267impl Cursor<'_> {
268 /// Queries the mapping at the current virtual address.
269 ///
270 /// If the cursor is pointing to a valid virtual address that is locked,
271 /// it will return the virtual address range and the mapped item.
272 pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<VmQueriedItem<'_>>)> {
273 let (range, item) = self.0.query()?;
274 Ok((range, item.map(VmQueriedItem::from)))
275 }
276
277 /// Moves the cursor forward to the next mapped virtual address.
278 ///
279 /// If there is mapped virtual address following the current address within
280 /// next `len` bytes, it will return that mapped address. In this case,
281 /// the cursor will stop at the mapped address.
282 ///
283 /// Otherwise, it will return `None`. And the cursor may stop at any
284 /// address after `len` bytes.
285 ///
286 /// # Panics
287 ///
288 /// Panics if the length is longer than the remaining range of the cursor.
289 pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
290 self.0.find_next(len)
291 }
292
293 /// Jumps to the virtual address.
294 ///
295 /// If the target address is out of the range, this method will return `Err`.
296 ///
297 /// # Panics
298 ///
299 /// This method panics if the address has bad alignment.
300 pub fn jump(&mut self, va: Vaddr) -> Result<()> {
301 self.0.jump(va)?;
302 Ok(())
303 }
304
305 /// Gets the virtual address of the current slot.
306 pub fn virt_addr(&self) -> Vaddr {
307 self.0.virt_addr()
308 }
309}
310
311/// The cursor for modifying the mappings in VM space.
312///
313/// It exclusively owns a sub-tree of the page table, preventing others from
314/// reading or modifying the same sub-tree.
315pub struct CursorMut<'a> {
316 pt_cursor: page_table::CursorMut<'a, UserPtConfig>,
317 // We have a read lock so the CPU set in the flusher is always a superset
318 // of actual activated CPUs.
319 flusher: TlbFlusher<'a, DisabledPreemptGuard>,
320 // References to the `VmSpace`
321 vmspace: &'a VmSpace,
322}
323
324impl<'a> CursorMut<'a> {
325 /// Queries the mapping at the current virtual address.
326 ///
327 /// This is the same as [`Cursor::query`].
328 ///
329 /// If the cursor is pointing to a valid virtual address that is locked,
330 /// it will return the virtual address range and the mapped item.
331 pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<VmQueriedItem<'_>>)> {
332 let (range, item) = self.pt_cursor.query()?;
333 Ok((range, item.map(VmQueriedItem::from)))
334 }
335
336 /// Moves the cursor forward to the next mapped virtual address.
337 ///
338 /// This is the same as [`Cursor::find_next`].
339 pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
340 self.pt_cursor.find_next(len)
341 }
342
343 /// Jumps to the virtual address.
344 ///
345 /// This is the same as [`Cursor::jump`].
346 ///
347 /// # Panics
348 ///
349 /// This method panics if the address has bad alignment.
350 pub fn jump(&mut self, va: Vaddr) -> Result<()> {
351 self.pt_cursor.jump(va)?;
352 Ok(())
353 }
354
355 /// Gets the virtual address of the current slot.
356 pub fn virt_addr(&self) -> Vaddr {
357 self.pt_cursor.virt_addr()
358 }
359
360 /// Gets the dedicated TLB flusher for this cursor.
361 pub fn flusher(&mut self) -> &mut TlbFlusher<'a, DisabledPreemptGuard> {
362 &mut self.flusher
363 }
364
365 /// Maps a frame into the current slot.
366 ///
367 /// This method will bring the cursor to the next slot after the modification.
368 ///
369 /// # Panics
370 ///
371 /// Panics if the current virtual address is already mapped.
372 pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
373 let item = VmItem::new_tracked(frame, prop);
374
375 // SAFETY: It is safe to map untyped memory into the userspace.
376 unsafe { self.pt_cursor.map(item) };
377 }
378
379 /// Maps a range of [`IoMem`] into the current slot.
380 ///
381 /// The memory region to be mapped is the [`IoMem`] range starting at
382 /// `offset` and extending to `offset + len`, or to the end of [`IoMem`],
383 /// whichever comes first. This method will bring the cursor to the next
384 /// slot after the modification.
385 ///
386 /// # Limitations
387 ///
388 /// Once an instance of `IoMem` is mapped to a `VmSpace`,
389 /// then the `IoMem` instance will only be dropped when the `VmSpace` is
390 /// dropped, not when all the mappings backed by the `IoMem` are destroyed
391 /// with the `unmap` method.
392 ///
393 /// # Panics
394 ///
395 /// Panics if
396 /// - `len` or `offset` is not aligned to the page size;
397 /// - the current virtual address is already mapped.
398 pub fn map_iomem(&mut self, io_mem: IoMem, prop: PageProperty, len: usize, offset: usize) {
399 assert_eq!(len % PAGE_SIZE, 0);
400 assert_eq!(offset % PAGE_SIZE, 0);
401
402 if offset >= io_mem.size() {
403 return;
404 }
405
406 let paddr_begin = io_mem.paddr() + offset;
407 let paddr_end = if io_mem.size() - offset < len {
408 io_mem.paddr() + io_mem.size()
409 } else {
410 io_mem.paddr() + len + offset
411 };
412
413 for current_paddr in (paddr_begin..paddr_end).step_by(PAGE_SIZE) {
414 // SAFETY: It is safe to map I/O memory into the userspace.
415 unsafe {
416 self.pt_cursor
417 .map(VmItem::new_untracked_io(current_paddr, prop))
418 };
419 }
420
421 // If the `iomems` list in `VmSpace` does not contain the current I/O
422 // memory, push it to maintain the correct reference count.
423 let mut iomems = self.vmspace.iomems.lock();
424 if !iomems
425 .iter()
426 .any(|iomem| iomem.paddr() == io_mem.paddr() && iomem.size() == io_mem.size())
427 {
428 iomems.push(io_mem);
429 }
430 }
431
432 /// Finds an [`IoMem`] that was previously mapped to by [`Self::map_iomem`] and contains the
433 /// physical address.
434 ///
435 /// This method can recover the originally mapped `IoMem` from the physical address returned by
436 /// [`Self::query`]. If the query returns a [`VmQueriedItem::MappedIoMem`], this method is
437 /// guaranteed to succeed with the specific physical address. However, if the corresponding
438 /// mapping is subsequently unmapped, it is unspecified whether this method will still succeed
439 /// or not.
440 ///
441 /// On success, this method returns the `IoMem` and the offset from the `IoMem` start to the
442 /// given physical address. Otherwise, this method returns `None`.
443 pub fn find_iomem_by_paddr(&self, paddr: Paddr) -> Option<(IoMem, usize)> {
444 self.vmspace.find_iomem_by_paddr(paddr)
445 }
446
447 /// Clears the mapping starting from the current slot,
448 /// and returns the number of unmapped pages.
449 ///
450 /// This method will bring the cursor forward by `len` bytes in the virtual
451 /// address space after the modification.
452 ///
453 /// Already-absent mappings encountered by the cursor will be skipped. It
454 /// is valid to unmap a range that is not mapped.
455 ///
456 /// It must issue and dispatch a TLB flush after the operation. Otherwise,
457 /// the memory safety will be compromised. Please call this function less
458 /// to avoid the overhead of TLB flush. Using a large `len` is wiser than
459 /// splitting the operation into multiple small ones.
460 ///
461 /// # Panics
462 ///
463 /// Panics if:
464 /// - the length is longer than the remaining range of the cursor;
465 /// - the length is not page-aligned.
466 pub fn unmap(&mut self, len: usize) -> usize {
467 let end_va = self.virt_addr() + len;
468 let mut num_unmapped: usize = 0;
469 loop {
470 // SAFETY:
471 // 1. It is safe to unmap memory in the userspace.
472 // 2. We drop the unmapped items only after flushing TLB entries, which is safe.
473 let Some(frag) = (unsafe { self.pt_cursor.take_next(end_va - self.virt_addr()) })
474 else {
475 break; // No more mappings in the range.
476 };
477
478 match frag {
479 PageTableFrag::Mapped { va, item, .. } => {
480 // SAFETY: If the item is not a scalar (e.g., a frame
481 // pointer), we will drop it after the RCU grace period
482 // (see `issue_tlb_flush_with`).
483 let (item, panic_guard) = unsafe { RcuDrop::into_inner(item) };
484
485 match item {
486 VmItem {
487 mapped_item: MappedItem::TrackedFrame(old_frame),
488 ..
489 } => {
490 num_unmapped += 1;
491
492 let rcu_frame = RcuDrop::new(old_frame);
493 panic_guard.forget();
494 let rcu_frame = Frame::rcu_from_unsized(rcu_frame);
495 self.flusher
496 .issue_tlb_flush_with(TlbFlushOp::for_single(va), rcu_frame);
497 }
498 VmItem {
499 mapped_item: MappedItem::UntrackedIoMem { .. },
500 ..
501 } => {
502 panic_guard.forget();
503
504 // Flush the TLB entry for the current address, but
505 // in the current design, we cannot drop the
506 // corresponding `IoMem`. This is because we manage
507 // the range of I/O as a whole, but the frames
508 // handled here might be one segment of it.
509 self.flusher.issue_tlb_flush(TlbFlushOp::for_single(va));
510 }
511 }
512 }
513 PageTableFrag::StrayPageTable {
514 pt,
515 va,
516 len,
517 num_frames,
518 } => {
519 num_unmapped += num_frames;
520
521 self.flusher.issue_tlb_flush_with(
522 TlbFlushOp::for_range(va..va + len),
523 Frame::rcu_from_unsized(pt),
524 );
525 }
526 }
527 }
528
529 self.flusher.dispatch_tlb_flush();
530
531 num_unmapped
532 }
533
534 /// Applies the operation to the next slot of mapping within the range.
535 ///
536 /// The range to be found in is the current virtual address with the
537 /// provided length.
538 ///
539 /// The function stops and yields the actually protected range if it has
540 /// actually protected a page, no matter if the following pages are also
541 /// required to be protected.
542 ///
543 /// It also makes the cursor moves forward to the next page after the
544 /// protected one. If no mapped pages exist in the following range, the
545 /// cursor will stop at the end of the range and return [`None`].
546 ///
547 /// Note that it will **NOT** flush the TLB after the operation. Please
548 /// make the decision yourself on when and how to flush the TLB using
549 /// [`Self::flusher`].
550 ///
551 /// # Panics
552 ///
553 /// Panics if the length is longer than the remaining range of the cursor.
554 pub fn protect_next(
555 &mut self,
556 len: usize,
557 mut op: impl FnMut(&mut PageFlags, &mut CachePolicy),
558 ) -> Option<Range<Vaddr>> {
559 // SAFETY: It is safe to set `PageFlags` and `CachePolicy` of memory
560 // in the userspace.
561 unsafe {
562 self.pt_cursor.protect_next(len, &mut |prop| {
563 op(&mut prop.flags, &mut prop.cache);
564 })
565 }
566 }
567}
568
569cpu_local_cell! {
570 /// The `Arc` pointer to the activated VM space on this CPU. If the pointer
571 /// is NULL, it means that the activated page table is merely the kernel
572 /// page table.
573 // TODO: If we are enabling ASID, we need to maintain the TLB state of each
574 // CPU, rather than merely the activated `VmSpace`. When ASID is enabled,
575 // the non-active `VmSpace`s can still have their TLB entries in the CPU!
576 static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null();
577}
578
579#[cfg(ktest)]
580pub(super) fn get_activated_vm_space() -> *const VmSpace {
581 ACTIVATED_VM_SPACE.load()
582}
583
584/// The result of a query over the VM space.
585pub enum VmQueriedItem<'a> {
586 /// The current slot is mapped, the frame within is allocated from the
587 /// physical memory.
588 MappedRam {
589 /// The mapped frame.
590 frame: FrameRef<'a, dyn AnyUFrameMeta>,
591 /// The property of the slot.
592 prop: PageProperty,
593 },
594 /// The current slot is mapped, the frame within is allocated from the
595 /// MMIO memory.
596 MappedIoMem {
597 /// The physical address of the corresponding I/O memory.
598 paddr: Paddr,
599 /// The property of the slot.
600 prop: PageProperty,
601 },
602}
603
604impl VmQueriedItem<'_> {
605 /// Returns the page property of the mapped item.
606 pub fn prop(&self) -> &PageProperty {
607 match self {
608 Self::MappedRam { prop, .. } => prop,
609 Self::MappedIoMem { prop, .. } => prop,
610 }
611 }
612}
613
614/// Internal representation of a VM item.
615///
616/// This is kept private to ensure memory safety. The public interface
617/// should use `VmQueriedItem` for querying mapping information.
618#[derive(Clone, Debug, PartialEq)]
619pub(crate) struct VmItem {
620 prop: PageProperty,
621 mapped_item: MappedItem,
622}
623
624/// A reference to a VM item.
625#[derive(Debug)]
626pub(crate) struct VmItemRef<'a> {
627 prop: PageProperty,
628 mapped_item: MappedItemRef<'a>,
629}
630
631#[derive(Clone, Debug, PartialEq)]
632enum MappedItem {
633 TrackedFrame(UFrame),
634 UntrackedIoMem { paddr: Paddr, level: PagingLevel },
635}
636
637#[derive(Debug)]
638enum MappedItemRef<'a> {
639 TrackedFrame(FrameRef<'a, dyn AnyUFrameMeta>),
640 UntrackedIoMem { paddr: Paddr, level: PagingLevel },
641}
642
643impl VmItem {
644 /// Creates a new `VmItem` that maps a tracked frame.
645 pub(super) fn new_tracked(frame: UFrame, prop: PageProperty) -> Self {
646 Self {
647 prop,
648 mapped_item: MappedItem::TrackedFrame(frame),
649 }
650 }
651
652 /// Creates a new `VmItem` that maps an untracked I/O memory.
653 fn new_untracked_io(paddr: Paddr, prop: PageProperty) -> Self {
654 Self {
655 prop,
656 mapped_item: MappedItem::UntrackedIoMem { paddr, level: 1 },
657 }
658 }
659}
660
661impl<'a> From<VmItemRef<'a>> for VmQueriedItem<'a> {
662 fn from(item: VmItemRef<'a>) -> Self {
663 match item.mapped_item {
664 MappedItemRef::TrackedFrame(frame) => VmQueriedItem::MappedRam {
665 frame,
666 prop: item.prop,
667 },
668 MappedItemRef::UntrackedIoMem { paddr, level } => {
669 debug_assert_eq!(level, 1);
670 VmQueriedItem::MappedIoMem {
671 paddr,
672 prop: item.prop,
673 }
674 }
675 }
676 }
677}
678
679#[derive(Clone, Debug)]
680pub(crate) struct UserPtConfig {}
681
682// SAFETY: `item_raw_info`, `item_into_raw`, `item_from_raw`, and
683// `item_ref_from_raw` are correctly implemented with respect to the `Item` and
684// `ItemRef` types.
685unsafe impl PageTableConfig for UserPtConfig {
686 const TOP_LEVEL_INDEX_RANGE: Range<usize> = 0..256;
687
688 type E = PageTableEntry;
689 type C = PagingConsts;
690
691 type Item = VmItem;
692 type ItemRef<'a> = VmItemRef<'a>;
693
694 fn item_raw_info(item: &Self::Item) -> (Paddr, PagingLevel, PageProperty) {
695 match &item.mapped_item {
696 MappedItem::TrackedFrame(frame) => {
697 let mut prop = item.prop;
698 prop.priv_flags -= PrivilegedPageFlags::AVAIL1; // Clear AVAIL1 for tracked frames
699 let level = frame.map_level();
700 let paddr = frame.paddr();
701 (paddr, level, prop)
702 }
703 MappedItem::UntrackedIoMem { paddr, level } => {
704 let mut prop = item.prop;
705 prop.priv_flags |= PrivilegedPageFlags::AVAIL1; // Set AVAIL1 for I/O memory
706 (*paddr, *level, prop)
707 }
708 }
709 }
710
711 unsafe fn item_from_raw(paddr: Paddr, level: PagingLevel, prop: PageProperty) -> Self::Item {
712 debug_assert_eq!(level, 1);
713 if prop.priv_flags.contains(PrivilegedPageFlags::AVAIL1) {
714 // `AVAIL1` is set, this is I/O memory.
715 VmItem::new_untracked_io(paddr, prop)
716 } else {
717 // `AVAIL1` is clear, this is tracked memory.
718 // SAFETY: The caller ensures safety.
719 let frame = unsafe { Frame::<dyn AnyUFrameMeta>::from_raw(paddr) };
720 VmItem::new_tracked(frame, prop)
721 }
722 }
723
724 unsafe fn item_ref_from_raw<'a>(
725 paddr: Paddr,
726 level: PagingLevel,
727 prop: PageProperty,
728 ) -> Self::ItemRef<'a> {
729 debug_assert_eq!(level, 1);
730 if prop.priv_flags.contains(PrivilegedPageFlags::AVAIL1) {
731 // `AVAIL1` is set, this is I/O memory.
732 VmItemRef {
733 prop,
734 mapped_item: MappedItemRef::UntrackedIoMem { paddr, level },
735 }
736 } else {
737 // `AVAIL1` is clear, this is tracked memory.
738 // SAFETY: The caller ensures that the frame outlives `'a` and that
739 // the type matches the frame.
740 let frame_ref = unsafe { FrameRef::<dyn AnyUFrameMeta>::borrow_paddr(paddr) };
741 VmItemRef {
742 prop,
743 mapped_item: MappedItemRef::TrackedFrame(frame_ref),
744 }
745 }
746 }
747}