Skip to main content

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