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, HasPaddrRange, 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            || !super::is_in_user_space(vaddr, len)
163        {
164            return Err(Error::AccessDenied);
165        }
166
167        // SAFETY: The memory range is in user space, as checked above.
168        Ok(unsafe { VmReader::<Fallible>::from_user_space(vaddr as *const u8, len) })
169    }
170
171    /// Creates a writer to write data into the user space.
172    ///
173    /// Returns `Err` if this `VmSpace` doesn't belong to the user space of the current task
174    /// or the `vaddr` and `len` do not represent a user space memory range.
175    ///
176    /// Users must ensure that no other page table is activated in the current task during the
177    /// lifetime of the created `VmWriter`. This guarantees that the `VmWriter` can operate correctly.
178    pub fn writer(&self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'_, Fallible>> {
179        if current_page_table_paddr() != self.pt.root_paddr()
180            || !super::is_in_user_space(vaddr, len)
181        {
182            return Err(Error::AccessDenied);
183        }
184
185        // `VmWriter` is neither `Sync` nor `Send`, so it will not live longer than the current
186        // task. This ensures that the correct page table is activated during the usage period of
187        // the `VmWriter`.
188        //
189        // SAFETY: The memory range is in user space, as checked above.
190        Ok(unsafe { VmWriter::<Fallible>::from_user_space(vaddr as *mut u8, len) })
191    }
192
193    /// Creates a reader/writer pair to read data from and write data into the user space.
194    ///
195    /// Returns `Err` if this `VmSpace` doesn't belong to the user space of the current task
196    /// or the `vaddr` and `len` do not represent a user space memory range.
197    ///
198    /// Users must ensure that no other page table is activated in the current task during the
199    /// lifetime of the created `VmReader` and `VmWriter`. This guarantees that the `VmReader`
200    /// and the `VmWriter` can operate correctly.
201    ///
202    /// This method is semantically equivalent to calling [`Self::reader`] and [`Self::writer`]
203    /// separately, but it avoids double checking the validity of the memory region.
204    pub fn reader_writer(
205        &self,
206        vaddr: Vaddr,
207        len: usize,
208    ) -> Result<(VmReader<'_, Fallible>, VmWriter<'_, Fallible>)> {
209        if current_page_table_paddr() != self.pt.root_paddr()
210            || !super::is_in_user_space(vaddr, len)
211        {
212            return Err(Error::AccessDenied);
213        }
214
215        // SAFETY: The memory range is in user space, as checked above.
216        let reader = unsafe { VmReader::<Fallible>::from_user_space(vaddr as *const u8, len) };
217
218        // `VmWriter` is neither `Sync` nor `Send`, so it will not live longer than the current
219        // task. This ensures that the correct page table is activated during the usage period of
220        // the `VmWriter`.
221        //
222        // SAFETY: The memory range is in user space, as checked above.
223        let writer = unsafe { VmWriter::<Fallible>::from_user_space(vaddr as *mut u8, len) };
224
225        Ok((reader, writer))
226    }
227}
228
229impl Default for VmSpace {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235impl VmSpace {
236    /// Finds the [`IoMem`] that contains the given physical address.
237    ///
238    /// It is a private method for internal use only. Please refer to
239    /// [`CursorMut::find_iomem_by_paddr`] for more details.
240    fn find_iomem_by_paddr(&self, paddr: Paddr) -> Option<(IoMem, usize)> {
241        let iomems = self.iomems.lock();
242        for iomem in iomems.iter() {
243            let start = iomem.paddr();
244            let end = start + iomem.size();
245            if paddr >= start && paddr < end {
246                let offset = paddr - start;
247                return Some((iomem.clone(), offset));
248            }
249        }
250        None
251    }
252}
253
254/// The cursor for querying over the VM space without modifying it.
255///
256/// It exclusively owns a sub-tree of the page table, preventing others from
257/// reading or modifying the same sub-tree. Two read-only cursors can not be
258/// created from the same virtual address range either.
259pub struct Cursor<'a>(page_table::Cursor<'a, UserPtConfig>);
260
261impl Cursor<'_> {
262    /// Queries the mapping at the current virtual address.
263    ///
264    /// If the cursor is pointing to a valid virtual address that is locked,
265    /// it will return the virtual address range and the mapped item.
266    pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<VmQueriedItem<'_>>)> {
267        let (range, item) = self.0.query()?;
268        Ok((range, item.map(VmQueriedItem::from)))
269    }
270
271    /// Moves the cursor forward to the next mapped virtual address.
272    ///
273    /// If there is mapped virtual address following the current address within
274    /// next `len` bytes, it will return that mapped address. In this case,
275    /// the cursor will stop at the mapped address.
276    ///
277    /// Otherwise, it will return `None`. And the cursor may stop at any
278    /// address after `len` bytes.
279    ///
280    /// # Panics
281    ///
282    /// Panics if:
283    ///  - the length is longer than the remaining range of the cursor;
284    ///  - the length is not page-aligned.
285    pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
286        self.0.find_next(len)
287    }
288
289    /// Jumps to the virtual address.
290    ///
291    /// If the target address is out of the range, this method will return `Err`.
292    ///
293    /// # Panics
294    ///
295    /// This method panics if the address has bad alignment.
296    pub fn jump(&mut self, va: Vaddr) -> Result<()> {
297        self.0.jump(va)?;
298        Ok(())
299    }
300
301    /// Gets the virtual address of the current slot.
302    pub fn virt_addr(&self) -> Vaddr {
303        self.0.virt_addr()
304    }
305}
306
307/// The cursor for modifying the mappings in VM space.
308///
309/// It exclusively owns a sub-tree of the page table, preventing others from
310/// reading or modifying the same sub-tree.
311pub struct CursorMut<'a> {
312    pt_cursor: page_table::CursorMut<'a, UserPtConfig>,
313    // We have a read lock so the CPU set in the flusher is always a superset
314    // of actual activated CPUs.
315    flusher: TlbFlusher<'a, DisabledPreemptGuard>,
316    // References to the `VmSpace`
317    vmspace: &'a VmSpace,
318}
319
320impl<'a> CursorMut<'a> {
321    /// Queries the mapping at the current virtual address.
322    ///
323    /// This is the same as [`Cursor::query`].
324    ///
325    /// If the cursor is pointing to a valid virtual address that is locked,
326    /// it will return the virtual address range and the mapped item.
327    pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<VmQueriedItem<'_>>)> {
328        let (range, item) = self.pt_cursor.query()?;
329        Ok((range, item.map(VmQueriedItem::from)))
330    }
331
332    /// Moves the cursor forward to the next mapped virtual address.
333    ///
334    /// This is the same as [`Cursor::find_next`].
335    pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
336        self.pt_cursor.find_next(len)
337    }
338
339    /// Jumps to the virtual address.
340    ///
341    /// This is the same as [`Cursor::jump`].
342    ///
343    /// # Panics
344    ///
345    /// This method panics if the address has bad alignment.
346    pub fn jump(&mut self, va: Vaddr) -> Result<()> {
347        self.pt_cursor.jump(va)?;
348        Ok(())
349    }
350
351    /// Gets the virtual address of the current slot.
352    pub fn virt_addr(&self) -> Vaddr {
353        self.pt_cursor.virt_addr()
354    }
355
356    /// Gets the dedicated TLB flusher for this cursor.
357    pub fn flusher(&mut self) -> &mut TlbFlusher<'a, DisabledPreemptGuard> {
358        &mut self.flusher
359    }
360
361    /// Maps a frame into the current slot.
362    ///
363    /// This method will bring the cursor to the next slot after the modification.
364    ///
365    /// # Panics
366    ///
367    /// Panics if:
368    ///  - the current virtual address is already mapped;
369    ///  - the current virtual address is outside the cursor's range.
370    pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
371        let item = VmItem::new_tracked(frame, prop);
372
373        // SAFETY: It is safe to map untyped memory into the userspace.
374        unsafe { self.pt_cursor.map(item) };
375    }
376
377    /// Maps a range of [`IoMem`] into the current slot.
378    ///
379    /// The memory region to be mapped is the [`IoMem`] range starting at
380    /// `offset` and extending to `offset + len`, or to the end of [`IoMem`],
381    /// whichever comes first. This method will bring the cursor to the next
382    /// slot after the modification.
383    ///
384    /// # Limitations
385    ///
386    /// Once an instance of `IoMem` is mapped to a `VmSpace`,
387    /// then the `IoMem` instance will only be dropped when the `VmSpace` is
388    /// dropped, not when all the mappings backed by the `IoMem` are destroyed
389    /// with the `unmap` method.
390    ///
391    /// # Panics
392    ///
393    /// Panics if
394    ///  - `len`, `offset`, or the address range of the `IoMem` instance is
395    ///    not aligned to the page size;
396    ///  - the current virtual address is already mapped.
397    pub fn map_iomem(&mut self, io_mem: IoMem, prop: PageProperty, len: usize, offset: usize) {
398        assert_eq!(len % PAGE_SIZE, 0);
399        assert_eq!(offset % PAGE_SIZE, 0);
400
401        let io_mem_paddr = io_mem.paddr();
402        let io_mem_size = io_mem.size();
403        assert_eq!(io_mem_paddr % PAGE_SIZE, 0);
404        assert_eq!(io_mem_size % PAGE_SIZE, 0);
405
406        if offset >= io_mem_size {
407            return;
408        }
409
410        let paddr_begin = io_mem_paddr + offset;
411        let paddr_end = if io_mem_size - offset < len {
412            io_mem_paddr + io_mem_size
413        } else {
414            io_mem_paddr + len + offset
415        };
416
417        for current_paddr in (paddr_begin..paddr_end).step_by(PAGE_SIZE) {
418            // SAFETY: It is safe to map I/O memory into the userspace.
419            unsafe {
420                self.pt_cursor
421                    .map(VmItem::new_untracked_io(current_paddr, prop))
422            };
423        }
424
425        fn io_mem_contains(parent: &IoMem, child: &IoMem) -> bool {
426            parent.paddr() <= child.paddr() && child.end_paddr() <= parent.end_paddr()
427        }
428
429        // If the `iomems` list in `VmSpace` does not contain the current I/O
430        // memory, push it to maintain the correct reference count.
431        let mut iomems = self.vmspace.iomems.lock();
432        if !iomems.iter().any(|iomem| io_mem_contains(iomem, &io_mem)) {
433            iomems.retain(|iomem| !io_mem_contains(&io_mem, iomem));
434            iomems.push(io_mem);
435        }
436    }
437
438    /// Finds an [`IoMem`] that was previously mapped to by [`Self::map_iomem`] and contains the
439    /// physical address.
440    ///
441    /// This method can recover the originally mapped `IoMem` from the physical address returned by
442    /// [`Self::query`]. If the query returns a [`VmQueriedItem::MappedIoMem`], this method is
443    /// guaranteed to succeed with the specific physical address. However, if the corresponding
444    /// mapping is subsequently unmapped, it is unspecified whether this method will still succeed
445    /// or not.
446    ///
447    /// On success, this method returns the `IoMem` and the offset from the `IoMem` start to the
448    /// given physical address. Otherwise, this method returns `None`.
449    pub fn find_iomem_by_paddr(&self, paddr: Paddr) -> Option<(IoMem, usize)> {
450        self.vmspace.find_iomem_by_paddr(paddr)
451    }
452
453    /// Clears the mapping starting from the current slot,
454    /// and returns the number of unmapped pages.
455    ///
456    /// This method will bring the cursor forward by at least `len` bytes
457    /// in the virtual address space, but not past the end of the cursor's range.
458    ///
459    /// Already-absent mappings encountered by the cursor will be skipped. It
460    /// is valid to unmap a range that is not mapped.
461    ///
462    /// It must issue and dispatch a TLB flush after the operation. Otherwise,
463    /// the memory safety will be compromised. Please call this function less
464    /// to avoid the overhead of TLB flush. Using a large `len` is wiser than
465    /// splitting the operation into multiple small ones.
466    ///
467    /// # Panics
468    ///
469    /// Panics if:
470    ///  - the length is longer than the remaining range of the cursor;
471    ///  - the length is not page-aligned.
472    pub fn unmap(&mut self, len: usize) -> usize {
473        let end_va = self.virt_addr() + len;
474        let mut num_unmapped: usize = 0;
475        loop {
476            // SAFETY:
477            // 1. It is safe to unmap memory in the userspace.
478            // 2. We drop the unmapped items only after flushing TLB entries, which is safe.
479            let Some(frag) = (unsafe { self.pt_cursor.take_next(end_va - self.virt_addr()) })
480            else {
481                break; // No more mappings in the range.
482            };
483
484            match frag {
485                PageTableFrag::Mapped { va, item, .. } => {
486                    // SAFETY: If the item is not a scalar (e.g., a frame
487                    // pointer), we will drop it after the RCU grace period
488                    // (see `issue_tlb_flush_with`).
489                    let (item, panic_guard) = unsafe { RcuDrop::into_inner(item) };
490
491                    match item {
492                        VmItem {
493                            mapped_item: MappedItem::TrackedFrame(old_frame),
494                            ..
495                        } => {
496                            num_unmapped += 1;
497
498                            let rcu_frame = RcuDrop::new(old_frame);
499                            panic_guard.forget();
500                            let rcu_frame = Frame::rcu_from_unsized(rcu_frame);
501                            self.flusher
502                                .issue_tlb_flush_with(TlbFlushOp::for_single(va), rcu_frame);
503                        }
504                        VmItem {
505                            mapped_item: MappedItem::UntrackedIoMem { .. },
506                            ..
507                        } => {
508                            panic_guard.forget();
509
510                            // Flush the TLB entry for the current address, but
511                            // in the current design, we cannot drop the
512                            // corresponding `IoMem`. This is because we manage
513                            // the range of I/O as a whole, but the frames
514                            // handled here might be one segment of it.
515                            self.flusher.issue_tlb_flush(TlbFlushOp::for_single(va));
516                        }
517                    }
518                }
519                PageTableFrag::StrayPageTable {
520                    pt,
521                    va,
522                    len,
523                    num_frames,
524                } => {
525                    num_unmapped += num_frames;
526
527                    self.flusher.issue_tlb_flush_with(
528                        TlbFlushOp::for_range(va..va + len),
529                        Frame::rcu_from_unsized(pt),
530                    );
531                }
532            }
533        }
534
535        self.flusher.dispatch_tlb_flush();
536
537        num_unmapped
538    }
539
540    /// Applies the operation to the next slot of mapping within the range.
541    ///
542    /// The range to be found in is the current virtual address with the
543    /// provided length.
544    ///
545    /// The function stops and yields the actually protected range if it has
546    /// actually protected a page, no matter if the following pages are also
547    /// required to be protected.
548    ///
549    /// It also makes the cursor moves forward to the next page after the
550    /// protected one. If no mapped pages exist in the following range, the
551    /// cursor will stop at the end of the range and return [`None`].
552    ///
553    /// Note that it will **NOT** flush the TLB after the operation. Please
554    /// make the decision yourself on when and how to flush the TLB using
555    /// [`Self::flusher`].
556    ///
557    /// # Panics
558    ///
559    /// Panics if:
560    ///  - the length is longer than the remaining range of the cursor;
561    ///  - the length is not page-aligned.
562    pub fn protect_next(
563        &mut self,
564        len: usize,
565        mut op: impl FnMut(&mut PageFlags, &mut CachePolicy),
566    ) -> Option<Range<Vaddr>> {
567        // SAFETY: It is safe to set `PageFlags` and `CachePolicy` of memory
568        // in the userspace.
569        unsafe {
570            self.pt_cursor.protect_next(len, &mut |prop| {
571                op(&mut prop.flags, &mut prop.cache);
572            })
573        }
574    }
575}
576
577cpu_local_cell! {
578    /// The `Arc` pointer to the activated VM space on this CPU. If the pointer
579    /// is NULL, it means that the activated page table is merely the kernel
580    /// page table.
581    // TODO: If we are enabling ASID, we need to maintain the TLB state of each
582    // CPU, rather than merely the activated `VmSpace`. When ASID is enabled,
583    // the non-active `VmSpace`s can still have their TLB entries in the CPU!
584    static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null();
585}
586
587#[cfg(ktest)]
588pub(super) fn get_activated_vm_space() -> *const VmSpace {
589    ACTIVATED_VM_SPACE.load()
590}
591
592/// The result of a query over the VM space.
593pub enum VmQueriedItem<'a> {
594    /// The current slot is mapped, the frame within is allocated from the
595    /// physical memory.
596    MappedRam {
597        /// The mapped frame.
598        frame: FrameRef<'a, dyn AnyUFrameMeta>,
599        /// The property of the slot.
600        prop: PageProperty,
601    },
602    /// The current slot is mapped, the frame within is allocated from the
603    /// MMIO memory.
604    MappedIoMem {
605        /// The physical address of the corresponding I/O memory.
606        paddr: Paddr,
607        /// The property of the slot.
608        prop: PageProperty,
609    },
610}
611
612impl VmQueriedItem<'_> {
613    /// Returns the page property of the mapped item.
614    pub fn prop(&self) -> &PageProperty {
615        match self {
616            Self::MappedRam { prop, .. } => prop,
617            Self::MappedIoMem { prop, .. } => prop,
618        }
619    }
620}
621
622/// Internal representation of a VM item.
623///
624/// This is kept private to ensure memory safety. The public interface
625/// should use `VmQueriedItem` for querying mapping information.
626#[derive(Clone, Debug, PartialEq)]
627pub(crate) struct VmItem {
628    prop: PageProperty,
629    mapped_item: MappedItem,
630}
631
632/// A reference to a VM item.
633#[derive(Debug)]
634pub(crate) struct VmItemRef<'a> {
635    prop: PageProperty,
636    mapped_item: MappedItemRef<'a>,
637}
638
639#[derive(Clone, Debug, PartialEq)]
640enum MappedItem {
641    TrackedFrame(UFrame),
642    UntrackedIoMem { paddr: Paddr, level: PagingLevel },
643}
644
645#[derive(Debug)]
646enum MappedItemRef<'a> {
647    TrackedFrame(FrameRef<'a, dyn AnyUFrameMeta>),
648    UntrackedIoMem { paddr: Paddr, level: PagingLevel },
649}
650
651impl VmItem {
652    /// Creates a new `VmItem` that maps a tracked frame.
653    pub(super) fn new_tracked(frame: UFrame, prop: PageProperty) -> Self {
654        Self {
655            prop,
656            mapped_item: MappedItem::TrackedFrame(frame),
657        }
658    }
659
660    /// Creates a new `VmItem` that maps an untracked I/O memory.
661    fn new_untracked_io(paddr: Paddr, prop: PageProperty) -> Self {
662        Self {
663            prop,
664            mapped_item: MappedItem::UntrackedIoMem { paddr, level: 1 },
665        }
666    }
667}
668
669impl<'a> From<VmItemRef<'a>> for VmQueriedItem<'a> {
670    fn from(item: VmItemRef<'a>) -> Self {
671        match item.mapped_item {
672            MappedItemRef::TrackedFrame(frame) => VmQueriedItem::MappedRam {
673                frame,
674                prop: item.prop,
675            },
676            MappedItemRef::UntrackedIoMem { paddr, level } => {
677                debug_assert_eq!(level, 1);
678                VmQueriedItem::MappedIoMem {
679                    paddr,
680                    prop: item.prop,
681                }
682            }
683        }
684    }
685}
686
687#[derive(Clone, Debug)]
688pub(crate) struct UserPtConfig {}
689
690// SAFETY: `item_raw_info`, `item_into_raw`, `item_from_raw`, and
691// `item_ref_from_raw` are correctly implemented with respect to the `Item` and
692// `ItemRef` types.
693unsafe impl PageTableConfig for UserPtConfig {
694    const TOP_LEVEL_INDEX_RANGE: Range<usize> = 0..256;
695
696    type E = PageTableEntry;
697    type C = PagingConsts;
698
699    type Item = VmItem;
700    type ItemRef<'a> = VmItemRef<'a>;
701
702    fn item_raw_info(item: &Self::Item) -> (Paddr, PagingLevel, PageProperty) {
703        match &item.mapped_item {
704            MappedItem::TrackedFrame(frame) => {
705                let mut prop = item.prop;
706                prop.priv_flags -= PrivilegedPageFlags::AVAIL1; // Clear AVAIL1 for tracked frames
707                let level = frame.map_level();
708                let paddr = frame.paddr();
709                (paddr, level, prop)
710            }
711            MappedItem::UntrackedIoMem { paddr, level } => {
712                let mut prop = item.prop;
713                prop.priv_flags |= PrivilegedPageFlags::AVAIL1; // Set AVAIL1 for I/O memory
714                (*paddr, *level, prop)
715            }
716        }
717    }
718
719    unsafe fn item_from_raw(paddr: Paddr, level: PagingLevel, prop: PageProperty) -> Self::Item {
720        debug_assert_eq!(level, 1);
721        if prop.priv_flags.contains(PrivilegedPageFlags::AVAIL1) {
722            // `AVAIL1` is set, this is I/O memory.
723            VmItem::new_untracked_io(paddr, prop)
724        } else {
725            // `AVAIL1` is clear, this is tracked memory.
726            // SAFETY: The caller ensures safety.
727            let frame = unsafe { Frame::<dyn AnyUFrameMeta>::from_raw(paddr) };
728            VmItem::new_tracked(frame, prop)
729        }
730    }
731
732    unsafe fn item_ref_from_raw<'a>(
733        paddr: Paddr,
734        level: PagingLevel,
735        prop: PageProperty,
736    ) -> Self::ItemRef<'a> {
737        debug_assert_eq!(level, 1);
738        if prop.priv_flags.contains(PrivilegedPageFlags::AVAIL1) {
739            // `AVAIL1` is set, this is I/O memory.
740            VmItemRef {
741                prop,
742                mapped_item: MappedItemRef::UntrackedIoMem { paddr, level },
743            }
744        } else {
745            // `AVAIL1` is clear, this is tracked memory.
746            // SAFETY: The caller ensures that the frame outlives `'a` and that
747            // the type matches the frame.
748            let frame_ref = unsafe { FrameRef::<dyn AnyUFrameMeta>::borrow_paddr(paddr) };
749            VmItemRef {
750                prop,
751                mapped_item: MappedItemRef::TrackedFrame(frame_ref),
752            }
753        }
754    }
755}