ostd/mm/vm_space.rs
1// SPDX-License-Identifier: MPL-2.0
2//! Virtual memory space management.
3//!
4//! The [`VmSpace`] struct is provided to manage the virtual memory space of a
5//! user. Cursors are used to traverse and modify over the virtual memory space
6//! concurrently. The VM space cursor [`self::Cursor`] is just a wrapper over
7//! the page table cursor, providing efficient, powerful concurrent accesses
8//! to the page table.
9use alloc::vec::Vec;
10
11use vstd::pervasive::arbitrary;
12use vstd::prelude::*;
13
14use vstd::vpanic;
15
16use crate::arch::mm::{PageTableEntry, PagingConsts, current_page_table_paddr};
17use crate::error::Error;
18use crate::mm::frame::MetaSlot;
19use crate::mm::frame::meta::mapping::meta_to_frame;
20use crate::mm::frame::untyped::UFrame;
21use crate::mm::kspace::KernelPtConfig;
22use crate::mm::page_table::*;
23use crate::mm::{
24 KERNEL_VADDR_RANGE,
25 page_table::{EntryOwner, PageTableFrag, PageTableGuard},
26};
27use crate::specs::arch::*;
28
29use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
30
31use crate::specs::mm::page_table::{cursor::owners::CursorOwner, *};
32use crate::specs::mm::tlb::TlbModel;
33use crate::specs::mm::virt_mem::{MemView, VirtPtr};
34use crate::specs::task::InAtomicMode;
35use crate::sync::RoArc;
36use core::marker::PhantomData;
37use core::{ops::Range, sync::atomic::Ordering};
38use vstd_extra::ghost_tree::*;
39use vstd_extra::panic::may_panic;
40use vstd_extra::prelude::*;
41use vstd_extra::{assert, assert_eq};
42
43use crate::mm::kspace::KERNEL_PAGE_TABLE;
44use crate::mm::tlb::*;
45use crate::specs::mm::cpu::{AtomicCpuSet, CpuSet};
46
47use crate::mm::{
48 MAX_USERSPACE_VADDR, Paddr, PagingConstsTrait, PagingLevel, Vaddr,
49 io::{Fallible, VmReader, VmWriter},
50 page_prop::PageProperty,
51};
52use crate::specs::mm::io::VmIoOwner;
53
54use alloc::sync::Arc;
55
56#[path = "../../specs/mm/vm_space.rs"]
57pub mod vm_space_specs;
58use vm_space_specs::*;
59
60verus! {
61
62/// A virtual address space for user-mode tasks, enabling safe manipulation of user-space memory.
63///
64/// The [`VmSpace`] type provides memory isolation guarantees between user-space and
65/// kernel-space. For example, given an arbitrary user-space pointer, one can read and
66/// write the memory location referred to by the user-space pointer without the risk of
67/// breaking the memory safety of the kernel space.
68///
69/// # Task Association Semantics
70///
71/// As far as OSTD is concerned, a [`VmSpace`] is not necessarily associated with a task. Once a
72/// [`VmSpace`] is activated (see [`VmSpace::activate`]), it remains activated until another
73/// [`VmSpace`] is activated **possibly by another task running on the same CPU**.
74///
75/// This means that it's up to the kernel to ensure that a task's [`VmSpace`] is always activated
76/// while the task is running. This can be done by using the injected post schedule handler
77/// (see [`inject_post_schedule_handler`]) to always activate the correct [`VmSpace`] after each
78/// context switch.
79///
80/// If the kernel otherwise decides not to ensure that the running task's [`VmSpace`] is always
81/// activated, the kernel must deal with race conditions when calling methods that require the
82/// [`VmSpace`] to be activated, e.g., [`UserMode::execute`], [`VmReader`] and [`VmWriter`].
83/// Otherwise, the behavior is unspecified, though it's guaranteed _not_ to compromise the kernel's
84/// memory safety.
85///
86/// # Memory Backing
87///
88/// A newly-created [`VmSpace`] is not backed by any physical memory pages. To
89/// provide memory pages for a [`VmSpace`], one can allocate and map physical
90/// memory ([`UFrame`]s) to the [`VmSpace`] using the cursor.
91///
92/// A [`VmSpace`] can also attach a page fault handler, which will be invoked to
93/// handle page faults generated from user space.
94///
95/// [`inject_post_schedule_handler`]: crate::task::inject_post_schedule_handler
96/// [`UserMode::execute`]: crate::user::UserMode::execute
97/// [`VmReader`]: crate::mm::io::VmWriter
98/// [`VmReader`]: crate::mm::io::VmReader
99/// # Verification Design
100///
101/// A [`VmSpace`] has a corresponding [`VmSpaceOwner`] object that is used to track its state,
102/// and against which its invariants are stated. The [`VmSpaceOwner`] catalogues the readers and writers
103/// that are associated with the [`VmSpace`], and the [`MemView`] which encodes the active page table and
104/// the subset of the TLB that covers the same virtual address space.
105/// All proofs about the correctness of the readers and writers are founded on the well-formedness of the [`MemView`]:
106///
107/// ```rust
108/// open spec fn mem_view_wf(self) -> bool {
109/// &&& self.mem_view is Some <==> self.mv_range@ is Some
110/// // This requires that TotalMapping (mvv) = mv ∪ writer mappings ∪ reader mappings
111/// &&& self.mem_view matches Some(remaining_view)
112/// ==> self.mv_range@ matches Some(total_view)
113/// ==> {
114/// &&& remaining_view.mappings_are_disjoint()
115/// &&& total_view.mappings_are_disjoint()
116/// // ======================
117/// // Remaining Consistency
118/// // ======================
119/// &&& remaining_view.mappings.subset_of(total_view.mappings)
120/// &&& remaining_view.memory.dom().subset_of(
121/// total_view.memory.dom(),
122/// )
123/// // =====================
124/// // Total View Consistency
125/// // =====================
126/// &&& forall|va: usize|
127/// remaining_view.addr_transl(va) == total_view.addr_transl(
128/// va,
129/// )
130/// // =====================
131/// // Writer correctness
132/// // =====================
133/// &&& forall|i: int|
134/// 0 <= i < self.writers.len() ==> {
135/// &&& self.writers[i].inv()
136/// }
137/// }
138/// }
139/// }
140/// ```
141pub struct VmSpace<'a> {
142 pub pt: PageTable<UserPtConfig>,
143 pub cpus: AtomicCpuSet,
144 pub _marker: PhantomData<&'a ()>,
145}
146
147type Result<A> = core::result::Result<A, Error>;
148
149#[verus_verify]
150impl<'a> VmSpace<'a> {
151 #[inline]
152 #[verus_spec(r =>
153 with
154 Tracked(regions): Tracked<&mut MetaRegionOwners>,
155 Tracked(guards): Tracked<&mut Guards<'rcu>>,
156 requires
157 old(regions).inv(),
158 )]
159 #[allow(private_interfaces)]
160 pub fn default<'rcu>() -> Self {
161 proof_with!(Tracked(regions), Tracked(guards));
162 Self::new()
163 }
164
165 /// Creates a new VM address space.
166 ///
167 /// This allocates a new user page table by duplicating the kernel page
168 /// table's top-level entries, and returns a [`VmSpace`] that wraps it.
169 ///
170 /// # Verified Properties
171 /// ## Preconditions
172 /// - **Safety Invariants**: The meta-region invariants must hold.
173 /// ## Postconditions
174 /// - The returned [`VmSpace`] instance satisfies the invariants of [`VmSpace`].
175 #[inline]
176 #[verus_spec(r =>
177 with
178 Tracked(regions): Tracked<&mut MetaRegionOwners>,
179 Tracked(guards): Tracked<&mut Guards<'rcu>>,
180 requires
181 old(regions).inv(),
182 ensures
183 final(regions).inv(),
184 )]
185 #[allow(private_interfaces)]
186 pub fn new<'rcu>() -> Self {
187 proof_decl! {
188 let tracked mut kernel_owner_opt: Option<&PageTableOwner<KernelPtConfig>> = None;
189 }
190 let kpt = {
191 #[verus_spec(with Tracked(&mut kernel_owner_opt), Tracked(regions), Tracked(guards))]
192 crate::mm::kspace::kvirt_area::get_kernel_page_table()
193 };
194 proof_decl! {
195 let tracked kernel_owner = kernel_owner_opt.tracked_take();
196 }
197 let pt = {
198 #[verus_spec(with Tracked(kernel_owner), Tracked(regions), Tracked(guards))]
199 kpt.create_user_page_table::<crate::specs::task::AnyAtomicGuard>()
200 };
201 Self { pt, cpus: AtomicCpuSet::new(CpuSet::new_empty()), _marker: PhantomData }
202 }
203
204 /// Gets an immutable cursor in the virtual address range.
205 ///
206 /// The cursor behaves like a lock guard, exclusively owning a sub-tree of
207 /// the page table, preventing others from creating a cursor in it. So be
208 /// sure to drop the cursor as soon as possible.
209 ///
210 /// The creation of the cursor may block if another cursor having an
211 /// overlapping range is alive.
212 ///
213 /// # Verified Properties
214 /// ## Preconditions
215 /// - **Safety Invariants**: The page table owner must be valid.
216 /// ## Postconditions
217 /// - When the virtual address range satisfies
218 /// [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
219 /// the result is `Ok` and a [`CursorOwner`] is returned.
220 #[verus_spec(r =>
221 with
222 Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
223 Ghost(root_guard): Ghost<PageTableGuard<'a, UserPtConfig>>,
224 Tracked(regions): Tracked<&mut MetaRegionOwners>,
225 Tracked(guards): Tracked<&mut Guards<'a>>,
226 -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
227 requires
228 self.pt.relates_owner(owner, *old(regions)),
229 owner.0.value().node().relate_guard(root_guard),
230 va.end > 0,
231 ensures
232 crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(*va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
233 // On the success branch, the returned cursor owner satisfies
234 // its invariant. Follows from the underlying PT::cursor's
235 // ensures: r is Ok ⇒ cursor_new_success_conditions (by
236 // contrapositive of the !cond ⇒ Err clause) ⇒ invariants
237 // hold ⇒ cursor_owner.inv().
238 cursor_owner@ matches Some(c) ==> c.inv(),
239 )]
240 pub fn cursor<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
241 Cursor<'a, G>,
242 > {
243 proof_decl! {
244 let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
245 }
246 match {
247 #[verus_spec(with Tracked(owner), Ghost(root_guard), Tracked(regions), Tracked(guards))]
248 self.pt.cursor(guard, va)
249 } {
250 Ok((pt_cursor, tracked_owner)) => {
251 proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
252 proof_with!(|= Tracked(out_owner));
253 Ok(Cursor(pt_cursor))
254 },
255 Err(e) => {
256 proof! { out_owner = None; }
257 proof_with!(|= Tracked(out_owner));
258 Err(Error::AccessDenied)
259 },
260 }
261 }
262
263 /// Gets a mutable cursor in the virtual address range.
264 ///
265 /// The same as [`Self::cursor`], the cursor behaves like a lock guard,
266 /// exclusively owning a sub-tree of the page table, preventing others
267 /// from creating a cursor in it. So be sure to drop the cursor as soon as
268 /// possible.
269 ///
270 /// The creation of the cursor may block if another cursor having an
271 /// overlapping range is alive. The modification to the mapping by the
272 /// cursor may also block or be overridden by the mapping of another cursor.
273 ///
274 /// # Verified Properties
275 /// ## Preconditions
276 /// - **Safety Invariants**: The page table owner must be valid.
277 /// ## Postconditions
278 /// - When the virtual address range satisfies
279 /// [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
280 /// the result is `Ok` and a [`CursorOwner`] is returned.
281 #[verus_spec(r =>
282 with
283 Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
284 Ghost(root_guard): Ghost<PageTableGuard<'a, UserPtConfig>>,
285 Tracked(regions): Tracked<&mut MetaRegionOwners>,
286 Tracked(guards): Tracked<&mut Guards<'a>>
287 -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
288 requires
289 self.pt.relates_owner(owner, *old(regions)),
290 owner.0.value().node().relate_guard(root_guard),
291 va.end > 0,
292 ensures
293 crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(*va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
294 // See `cursor` above for the derivation.
295 cursor_owner@ matches Some(c) ==> c.inv(),
296 )]
297 pub fn cursor_mut<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
298 CursorMut<'a, G>,
299 > {
300 proof_decl! {
301 let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
302 }
303 match {
304 #[verus_spec(with Tracked(owner), Ghost(root_guard), Tracked(regions), Tracked(guards))]
305 self.pt.cursor_mut(guard, va)
306 } {
307 Ok((pt_cursor, tracked_owner)) => {
308 proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
309 proof_with!(|= Tracked(out_owner));
310 Ok(CursorMut { pt_cursor, flusher: TlbFlusher::new(&self.cpus) })
311 },
312 Err(e) => {
313 proof! { out_owner = None; }
314 proof_with!(|= Tracked(out_owner));
315 Err(Error::AccessDenied)
316 },
317 }
318 }
319
320 /// Activates the page table on the current CPU.
321 #[verifier::external_body]
322 pub fn activate(this: &RoArc<Self>) {
323 // No support for CPU set semantics; skip now
324 // let preempt_guard = disable_preempt();
325 // let cpu = preempt_guard.current_cpu();
326 // let last_ptr = ACTIVATED_VM_SPACE.load();
327 // if last_ptr == Arc::as_ptr(self) {
328 // return;
329 // }
330 // // Record ourselves in the CPU set and the activated VM space pointer.
331 // // `Acquire` to ensure the modification to the PT is visible by this CPU.
332 // self.cpus.add(cpu, Ordering::Acquire);
333 // let self_ptr = Arc::into_raw(Arc::clone(self)) as *mut VmSpace;
334 // ACTIVATED_VM_SPACE.store(self_ptr);
335 // if !last_ptr.is_null() {
336 // // SAFETY: The pointer is cast from an `Arc` when it's activated
337 // // the last time, so it can be restored and only restored once.
338 // let last = unsafe { Arc::from_raw(last_ptr) };
339 // last.cpus.remove(cpu, Ordering::Relaxed);
340 // }
341 // self.pt.activate();
342 unimplemented!()
343 }
344
345 /// Creates a reader to read data from the user space of the current task.
346 ///
347 /// Returns [`Err`] if this [`VmSpace`] is not the user space of the current task
348 /// or the `vaddr` and `len` do not represent a valid user space memory range.
349 ///
350 /// # Verified Properties
351 /// ## Preconditions
352 /// - The [`VmSpaceOwner`] invariant must hold.
353 /// ## Postconditions
354 /// - When [`Self::reader_success_cond`] holds, the result is `Ok`.
355 /// - On success, the [`VmReader`] and its [`VmIoOwner`] are well-formed with no memory view.
356 /// ## Safety
357 /// - The function does not interact with the lower-level memory system directly.
358 /// By checking that the target (user) page table is not the active (kernel) one,
359 /// we ensure that the resulting reader cannot interact with kernel memory.
360 #[inline]
361 #[verus_spec(r =>
362 with
363 Tracked(owner): Tracked<&'a mut VmSpaceOwner>,
364 -> reader_owner: Tracked<Option<VmIoOwner>>,
365 requires
366 old(owner).inv(),
367 ensures
368 final(owner).inv(),
369 self.reader_success_cond(vaddr, len) ==> r is Ok && reader_owner@ is Some,
370 r is Ok && reader_owner@ is Some ==> {
371 &&& r.unwrap().wf(reader_owner@->0)
372 &&& reader_owner@->0.mem_view is None
373 &&& reader_owner@->0.inv()
374 },
375 // Range bound is necessary for success: the body's `checked_add`
376 // guard rejects any out-of-range request before constructing a
377 // reader. `nat` arithmetic subsumes the overflow case.
378 r is Ok ==> (vaddr as nat) + (len as nat) <= MAX_USERSPACE_VADDR as nat,
379 )]
380 pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result<VmReader<'a, Fallible>> {
381 if current_page_table_paddr() != self.pt.root_paddr() {
382 proof_with!(|= Tracked(None));
383 Err(Error::AccessDenied)
384 } else if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
385 proof_with!(|= Tracked(None));
386 Err(Error::AccessDenied)
387 } else {
388 let ghost id = owner.new_vm_io_id();
389 proof_decl! {
390 let tracked mut vm_reader_owner: VmIoOwner;
391 }
392
393 // SAFETY: The memory range is in user space, as checked above.
394 let reader = unsafe {
395 proof_with!(Ghost(id) => Tracked(vm_reader_owner));
396 VmReader::from_user_space(VirtPtr::from_vaddr(vaddr, len), len)
397 };
398
399 proof_with!(|= Tracked(Some(vm_reader_owner)));
400 Ok(reader)
401 }
402 }
403
404 /// Creates a writer to write data to the user space of the current task.
405 ///
406 /// Returns [`Err`] if this [`VmSpace`] is not the user space of the current task
407 /// or the `vaddr` and `len` do not represent a valid user space memory range.
408 ///
409 /// # Verified Properties
410 /// ## Preconditions
411 /// - The [`VmSpaceOwner`] invariant must hold.
412 /// ## Postconditions
413 /// - When [`Self::writer_success_cond`] holds, the result is `Ok`.
414 /// - On success, the [`VmWriter`] and its [`VmIoOwner`] are well-formed with no memory view.
415 /// ## Safety
416 /// - The function does not interact with the lower-level memory system directly.
417 /// By checking that the target (user) page table is not the active (kernel) one,
418 /// we ensure that the resulting writer cannot interact with kernel memory.
419 #[inline]
420 #[verus_spec(r =>
421 with
422 Tracked(owner): Tracked<&mut VmSpaceOwner>,
423 -> writer_owner: Tracked<Option<VmIoOwner>>,
424 requires
425 old(owner).inv(),
426 ensures
427 final(owner).inv(),
428 self.writer_success_cond(vaddr, len) ==> r is Ok && writer_owner@ is Some,
429 r is Ok && writer_owner@ is Some ==> {
430 &&& r.unwrap().wf(writer_owner@->0)
431 &&& writer_owner@->0.mem_view is None
432 &&& writer_owner@->0.inv()
433 },
434 // Range bound is necessary for success: see `reader` above.
435 r is Ok ==> (vaddr as nat) + (len as nat) <= MAX_USERSPACE_VADDR as nat,
436 )]
437 pub fn writer(self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'a, Fallible>> {
438 if current_page_table_paddr() != self.pt.root_paddr() {
439 proof_with!(|= Tracked(None));
440 Err(Error::AccessDenied)
441 } else if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
442 proof_with!(|= Tracked(None));
443 Err(Error::AccessDenied)
444 } else {
445 let ghost id = owner.new_vm_io_id();
446 proof_decl! {
447 let tracked mut vm_writer_owner: VmIoOwner;
448 }
449
450 // SAFETY: The memory range is in user space, as checked above.
451 let reader = unsafe {
452 proof_with!(Ghost(id) => Tracked(vm_writer_owner));
453 VmWriter::from_user_space(VirtPtr::from_vaddr(vaddr, len), len)
454 };
455
456 proof_with!(|= Tracked(Some(vm_writer_owner)));
457 Ok(reader)
458 }
459 }
460}
461
462/// The cursor for querying over the VM space without modifying it.
463///
464/// It exclusively owns a sub-tree of the page table, preventing others from
465/// reading or modifying the same sub-tree. Two read-only cursors can not be
466/// created from the same virtual address range either.
467pub struct Cursor<'a, A: InAtomicMode>(pub crate::mm::page_table::Cursor<'a, UserPtConfig, A>);
468
469#[verus_verify]
470impl<'rcu, A: InAtomicMode> Cursor<'rcu, A> {
471 /// Queries the mapping at the current virtual address.
472 ///
473 /// If the cursor is pointing to a valid virtual address that is locked,
474 /// it will return the virtual address range and the mapped item.
475 /// ## Preconditions
476 /// - All system invariants must hold
477 /// - **Liveness**: The function will return an error if the cursor is not within the locked range
478 /// ## Postconditions
479 /// - If there is a mapped item at the current virtual address ([`query_some_condition`]),
480 /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
481 /// - The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
482 /// - If there is no mapped item at the current virtual address ([`query_none_condition`]),
483 /// it returns [`None`], and the virtual address range of the cursor's current position.
484 /// ## Safety
485 /// - This function preserves all memory invariants.
486 /// - The locking mechanism prevents data races.
487 #[verus_spec(r =>
488 with
489 Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
490 Tracked(regions): Tracked<&mut MetaRegionOwners>,
491 Tracked(guards): Tracked<&mut Guards<'rcu>>,
492 requires
493 old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
494 // Out-of-range is a graceful `Err`; the sole panic is cloning
495 // the resolved leaf frame when *that specific slot* is
496 // saturated — precisely propagated from
497 // `Cursor::query_panic_condition`.
498 old(self).0.query_panic_condition(*old(owner), *old(regions)) ==> may_panic(),
499 ensures
500 final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
501 !old(self).0.query_panic_condition(*old(owner), *old(regions)),
502 old(owner).in_locked_range() ==> r is Ok,
503 r matches Ok(state) ==>
504 final(self).0.query_some_condition(*final(owner)) ==>
505 final(self).0.query_some_ensures(*final(owner), state),
506 r matches Ok(state) ==>
507 !final(self).0.query_some_condition(*final(owner)) ==>
508 final(self).0.query_none_ensures(*final(owner), state),
509 old(owner)@.mappings == final(owner)@.mappings,
510 )]
511 pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
512 Ok(
513 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
514 self.0.query()?,
515 )
516 }
517
518 /// Moves the cursor forward to the next mapped virtual address.
519 ///
520 /// If there is mapped virtual address following the current address within
521 /// next `len` bytes, it will return that mapped address. In this case,
522 /// the cursor will stop at the mapped address.
523 ///
524 /// Otherwise, it will return `None`. And the cursor may stop at any
525 /// address after `len` bytes.
526 ///
527 /// # Verified Properties
528 /// ## Preconditions
529 /// - **Safety Invariants**: The page table cursor safety invariants
530 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
531 /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
532 /// ## Postconditions
533 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
534 /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
535 /// it will move the cursor to the next mapped address and return it.
536 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
537 /// ## Panics
538 /// This method panics if the length is longer than the remaining range of the cursor.
539 /// ## Safety
540 /// This function preserves all memory invariants.
541 /// Because it panics rather than move the cursor to an invalid address,
542 /// it ensures that the cursor is safe to use after the call.
543 #[verus_spec(res =>
544 with
545 Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
546 Tracked(regions): Tracked<&mut MetaRegionOwners>,
547 Tracked(guards): Tracked<&mut Guards<'rcu>>,
548 requires
549 old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
550 old(self).0.find_next_panic_condition(len) ==> may_panic(),
551 ensures
552 !old(self).0.find_next_panic_condition(len),
553 final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
554 res is Some ==> {
555 &&& res->0 == final(self).0.va
556 &&& final(owner).level <= final(owner).guard_level
557 &&& final(owner).in_locked_range()
558 },
559 )]
560 pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
561 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
562 self.0.find_next(len)
563 }
564
565 // [FIXED] BUG FOUND BY FV: missing panic documentation. https://github.com/asterinas/asterinas/pull/3007
566 /// Jumps to the virtual address.
567 ///
568 /// If the target address is out of the range, this method will return `Err`.
569 ///
570 /// # Panics
571 ///
572 /// This method panics if the address has bad alignment.
573 ///
574 /// # Verified Properties
575 /// ## Preconditions
576 /// - **Safety Invariants**: The page table cursor safety invariants
577 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
578 /// - **Liveness**: The function will panic if the target `va` is not aligned
579 /// to the base page size.
580 /// ## Postconditions
581 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
582 /// - **Correctness**: If the target `va` is within the cursor's locked range,
583 /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
584 /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
585 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
586 /// ## Safety
587 /// This function preserves all memory invariants.
588 /// Because it throws an error rather than move the cursor to an invalid address,
589 /// it ensures that the cursor is safe to use after the call.
590 /// The locking mechanism prevents data races.
591 #[verus_spec(res =>
592 with
593 Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
594 Tracked(regions): Tracked<&mut MetaRegionOwners>,
595 Tracked(guards): Tracked<&mut Guards<'rcu>>,
596 requires
597 old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
598 // `CursorMut::jump` diverges on a misaligned `va` and may panic
599 // in its `pop_level` repositioning ascent.
600 old(self).0.jump_panic_condition(va) ==> may_panic(),
601 ensures
602 !old(self).0.jump_panic_condition(va),
603 final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
604 final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end ==> {
605 &&& res is Ok
606 &&& final(self).0.va == va
607 },
608 !(final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end) ==> res is Err,
609 )]
610 pub fn jump(&mut self, va: Vaddr) -> Result<()> {
611 (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
612 self.0.jump(va))?;
613 Ok(())
614 }
615
616 /// Get the virtual address of the current slot.
617 #[verus_spec(
618 returns
619 self.0.va,
620 )]
621 pub fn virt_addr(&self) -> Vaddr {
622 self.0.virt_addr()
623 }
624}
625
626/// The cursor for modifying the mappings in VM space.
627///
628/// It exclusively owns a sub-tree of the page table, preventing others from
629/// reading or modifying the same sub-tree.
630pub struct CursorMut<'a, A: InAtomicMode> {
631 pub pt_cursor: crate::mm::page_table::CursorMut<'a, UserPtConfig, A>,
632 // We have a read lock so the CPU set in the flusher is always a superset
633 // of actual activated CPUs.
634 pub flusher: TlbFlusher<'a /*, DisabledPreemptGuard*/ >,
635}
636
637#[verus_verify]
638impl<'a, A: InAtomicMode> CursorMut<'a, A> {
639 /// Queries the mapping at the current virtual address.
640 ///
641 /// This is the same as [`Cursor::query`].
642 ///
643 /// If the cursor is pointing to a valid virtual address that is locked,
644 /// it will return the virtual address range and the mapped item.
645 /// ## Preconditions
646 /// - **Safety Invariants**: The page table cursor safety invariants
647 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
648 /// ## Postconditions
649 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
650 /// - **Correctness**: If the cursor is within the locked range, the result is `Ok`.
651 /// - **Correctness**: If there is a mapped item at the current virtual address ([`query_some_condition`]),
652 /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
653 /// - **Correctness**: The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
654 /// - **Correctness**: If there is no mapped item at the current virtual address ([`query_none_condition`]),
655 /// it returns `None`, and the virtual address range of the cursor's current position.
656 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
657 /// - **Safety**: The mappings in the page table are not affected.
658 /// - **Safety**: The soundness of individual entries are not affected.
659 /// ## Safety
660 /// - This function preserves all memory invariants.
661 /// - The locking mechanism prevents data races.
662 #[verus_spec(res =>
663 with
664 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
665 Tracked(regions): Tracked<&mut MetaRegionOwners>,
666 Tracked(guards): Tracked<&mut Guards<'a>>,
667 requires
668 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
669 // Out-of-range → graceful `Err`; the sole panic is cloning the
670 // resolved leaf frame when *that specific slot* is saturated —
671 // precisely propagated from `Cursor::query_panic_condition`.
672 old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions))
673 ==> may_panic(),
674 ensures
675 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
676 !old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions)),
677 old(owner).in_locked_range() ==> res is Ok,
678 res matches Ok(state) ==>
679 final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
680 final(self).pt_cursor.0.query_some_ensures(*final(owner), state),
681 res matches Ok(state) ==>
682 !final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
683 final(self).pt_cursor.0.query_none_ensures(*final(owner), state),
684 old(owner)@.mappings == final(owner)@.mappings,
685 )]
686 pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
687 Ok(
688 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
689 self.pt_cursor.query()?,
690 )
691 }
692
693 /// Moves the cursor forward to the next mapped virtual address.
694 ///
695 /// This is the same as [`Cursor::find_next`].
696 ///
697 /// # Verified Properties
698 /// ## Preconditions
699 /// - **Safety Invariants**: The page table cursor safety invariants
700 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
701 /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
702 /// ## Postconditions
703 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
704 /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
705 /// it will move the cursor to the next mapped address and return it.
706 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
707 /// ## Panics
708 /// This method panics if the length is longer than the remaining range of the cursor.
709 /// ## Safety
710 /// This function preserves all memory invariants.
711 /// Because it panics rather than move the cursor to an invalid address,
712 /// it ensures that the cursor is safe to use after the call.
713 #[verus_spec(res =>
714 with
715 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
716 Tracked(regions): Tracked<&mut MetaRegionOwners>,
717 Tracked(guards): Tracked<&mut Guards<'a>>,
718 requires
719 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
720 old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
721 ensures
722 !old(self).pt_cursor.0.find_next_panic_condition(len),
723 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
724 res is Some ==> {
725 &&& res->0 == final(self).pt_cursor.0.va
726 &&& final(owner).level <= final(owner).guard_level
727 &&& final(owner).in_locked_range()
728 },
729 )]
730 pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
731 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
732 self.pt_cursor.find_next(len)
733 }
734
735 // [FIXED] BUG FOUND BY FV: missing panic documentation. https://github.com/asterinas/asterinas/pull/3007
736 /// Jump to the virtual address.
737 ///
738 /// This is the same as [`Cursor::jump`].
739 ///
740 /// # Panics
741 ///
742 /// This method panics if the address has bad alignment.
743 ///
744 /// # Verified Properties
745 /// ## Preconditions
746 /// - **Safety Invariants**: The page table cursor safety invariants
747 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
748 /// - **Liveness**: The function will panic if the target `va` is not aligned
749 /// to the base page size.
750 /// ## Postconditions
751 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
752 /// - **Correctness**: If the target `va` is within the cursor's locked range,
753 /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
754 /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
755 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
756 /// ## Panics
757 /// This method panics if the target address is not aligned to the page size.
758 /// ## Safety
759 /// This function preserves all memory invariants.
760 /// Because it throws an error rather than move the cursor to an invalid address,
761 /// it ensures that the cursor is safe to use after the call.
762 /// The locking mechanism prevents data races.
763 #[verus_spec(res =>
764 with
765 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
766 Tracked(regions): Tracked<&mut MetaRegionOwners>,
767 Tracked(guards): Tracked<&mut Guards<'a>>
768 requires
769 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
770 // `CursorMut::jump` diverges on a misaligned `va` and may panic
771 // in its `pop_level` repositioning ascent.
772 old(self).pt_cursor.0.jump_panic_condition(va) ==> may_panic(),
773 ensures
774 !old(self).pt_cursor.0.jump_panic_condition(va),
775 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
776 final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end ==> {
777 &&& res is Ok
778 &&& final(self).pt_cursor.0.va == va
779 },
780 !(final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end) ==> res is Err,
781 )]
782 pub fn jump(&mut self, va: Vaddr) -> Result<()> {
783 (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
784 self.pt_cursor.jump(va))?;
785 Ok(())
786 }
787
788 /// Get the virtual address of the current slot.
789 #[verus_spec(r =>
790 returns
791 self.pt_cursor.0.va,
792 )]
793 pub fn virt_addr(&self) -> Vaddr {
794 self.pt_cursor.virt_addr()
795 }
796
797 /// Get the dedicated TLB flusher for this cursor.
798 #[verus_spec(ret =>
799 ensures
800 *ret == old(self).flusher,
801 *final(ret) == final(self).flusher,
802 )]
803 pub fn flusher(&mut self) -> &mut TlbFlusher<'a> {
804 &mut self.flusher
805 }
806
807 /// Map a frame into the current slot.
808 ///
809 /// This method will bring the cursor to the next slot after the modification.
810 /// If there is an existing mapping at the current slot, it will be replaced
811 /// and the TLB will be flushed for that entry.
812 /// # Verified Properties
813 /// ## Preconditions
814 /// - **Safety Invariants**: The page table cursor safety invariants
815 /// ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the TLB invariant
816 /// ([`TlbModel::inv`]) must hold before the call.
817 /// - **Liveness**: The cursor must not be past the end of its locked range,
818 /// and the frame's level must fit within the remaining range, or the function will panic.
819 /// - **Bookkeeping**: The frame must be well-formed with respect to its entry owner
820 /// ([`item_wf`](Self::item_wf)).
821 /// ## Postconditions
822 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
823 /// - **Correctness**: The page table view is updated with the new mapping
824 /// according to [`map_item_ensures`](Self::map_item_ensures).
825 /// - **Correctness**: If the metadata region was well-formed before the call
826 /// and the frame was not already mapped, it will be well-formed after.
827 /// ## Safety
828 /// - For soundness purposes, it doesn't matter if a frame is mapped multiple times
829 /// in the same page table. There is still a clear definition of the behavior.
830 #[verus_spec(
831 with
832 Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
833 Tracked(entry_owner): Tracked<EntryOwner<UserPtConfig>>,
834 Tracked(regions): Tracked<&mut MetaRegionOwners>,
835 Tracked(guards): Tracked<&mut Guards<'a>>,
836 Tracked(tlb_model): Tracked<&mut TlbModel>,
837 requires
838 old(tlb_model).inv(),
839 old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
840 old(self).item_wf(frame, prop, entry_owner, *old(regions)),
841 old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }) ==> may_panic(),
842 ensures
843 !old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }),
844 final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
845 old(self).map_item_ensures(
846 frame,
847 prop,
848 old(cursor_owner)@,
849 final(cursor_owner)@,
850 ),
851 )]
852 pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
853 let start_va = self.virt_addr();
854 let item = MappedItem { frame: frame, prop: prop };
855
856 // SAFETY: It is safe to map untyped memory into the userspace.
857 let Err(frag) = (unsafe {
858 #[verus_spec(with Tracked(cursor_owner), Tracked(entry_owner), Tracked(regions), Tracked(guards))]
859 self.pt_cursor.map(item)
860 }) else {
861 return; // No mapping exists at the current address.
862 };
863
864 match frag {
865 PageTableFrag::Mapped { va, item } => {
866 //debug_assert_eq!(va, start_va);
867 let old_frame = item.frame;
868
869 #[verus_spec(with Tracked(tlb_model))]
870 self.flusher.issue_tlb_flush_with(
871 TlbFlushOp::Address(start_va),
872 old_frame.into_dyn(),
873 );
874 #[verus_spec(with Tracked(tlb_model))]
875 self.flusher.dispatch_tlb_flush();
876 },
877 PageTableFrag::StrayPageTable { .. } => {
878 assert(false) by {
879 assert(UserPtConfig::item_into_raw(item).1 == 1);
880 };
881 #[cfg(feature = "allow_panic")]
882 vpanic!("`UFrame` is base page sized but re-mapping out a child PT");
883 },
884 }
885 }
886
887 /// Clears the mapping starting from the current slot,
888 /// and returns the number of unmapped pages.
889 ///
890 /// This method will bring the cursor forward by `len` bytes in the virtual
891 /// address space after the modification.
892 ///
893 /// Already-absent mappings encountered by the cursor will be skipped. It
894 /// is valid to unmap a range that is not mapped.
895 ///
896 /// It must issue and dispatch a TLB flush after the operation. Otherwise,
897 /// the memory safety will be compromised. Please call this function less
898 /// to avoid the overhead of TLB flush. Using a large `len` is wiser than
899 /// splitting the operation into multiple small ones.
900 ///
901 /// # Verified Properties
902 /// ## Preconditions
903 /// - **Safety Invariants**: The page table cursor safety invariants
904 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
905 /// - **Safety Invariants**: The TLB invariant ([TlbModel::inv]) must hold.
906 /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
907 /// ## Postconditions
908 /// - **Safety Invariants**: The page table cursor safety invariants are preserved.
909 /// - **Safety Invariants**: The TLB invariant is preserved.
910 /// - **Correctness**: Unmaps a range of virtual addresses from the current address up to `len` bytes
911 /// and returns the number of mappings that were removed.
912 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
913 /// ## Panics
914 /// Panics if:
915 /// - the length is longer than the remaining range of the cursor;
916 /// - the length is not page-aligned.
917 /// ## Safety
918 /// - It is always sound to unmap pages. We flush unmapped pages from the TLB to ensure consistency.
919 /// - TODO: formalizing and proving that this function preserves TLB consistency would
920 /// be pretty straightforward and would be a nice addition to the correctness properties.
921 #[verus_spec(r =>
922 with
923 Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
924 Tracked(regions): Tracked<&mut MetaRegionOwners>,
925 Tracked(guards): Tracked<&mut Guards<'a>>,
926 Tracked(tlb_model): Tracked<&mut TlbModel>,
927 requires
928 old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
929 old(tlb_model).inv(),
930 old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
931 ensures
932 !old(self).pt_cursor.0.find_next_panic_condition(len),
933 final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
934 old(cursor_owner)@.unmap_spec(len, final(cursor_owner)@, r),
935 final(tlb_model).inv(),
936 )]
937 #[verifier::spinoff_prover]
938 pub fn unmap(&mut self, len: usize) -> usize {
939 proof {
940 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
941 cursor_owner.view_preserves_inv();
942 }
943
944 assert_eq!(len % PAGE_SIZE, 0);
945
946 // [KNOWN] BUG FOUND BY FV: `self.va + len` could overflow. For now assume that it doesn't. https://github.com/asterinas/asterinas/issues/3159
947 assume(self.pt_cursor.0.va + len <= usize::MAX);
948
949 assert!(self.virt_addr() + len <= self.pt_cursor.0.barrier_va.end);
950
951 assert(!self.pt_cursor.0.find_next_panic_condition(len));
952
953 let end_va = self.virt_addr() + len;
954 let mut num_unmapped: usize = 0;
955
956 let ghost start_va: Vaddr = cursor_owner@.cur_va;
957 // The "adjusted base" accumulates splits: starts as the split-at-boundaries
958 // version of start_mappings and gets updated when take_next splits huge pages.
959 let ghost mut adjusted_base: Set<Mapping> = cursor_owner@.mappings;
960 // Track the set of removed mappings explicitly (not as a VA range filter).
961 let ghost mut removed: Set<Mapping> = Set::empty();
962
963 proof {
964 // end_va <= barrier_va.end == locked_range().end. The cursor invariant
965 // bounds locked_range().end by `vaddr_range_spec::<C>().1 + 1`,
966 // and for UserPtConfig that evaluates to 2^47.
967 lemma_vaddr_range_spec_user();
968 assert((self.pt_cursor.0.va + len) % PAGE_SIZE as int == 0) by (compute);
969 }
970
971 #[verus_spec(
972 invariant
973 self.pt_cursor.0.va % PAGE_SIZE == 0,
974 end_va % PAGE_SIZE == 0,
975 end_va <= 0x0000_8000_0000_0000usize,
976 self.pt_cursor.0.invariants(*cursor_owner, *regions, *guards),
977 end_va <= self.pt_cursor.0.barrier_va.end,
978 tlb_model.inv(),
979 start_va <= cursor_owner@.cur_va,
980 // Split-aware invariant: adjusted_base tracks accumulated splits,
981 // removed tracks the explicitly removed set.
982 cursor_owner@.mappings == adjusted_base.difference(removed),
983 removed.subset_of(adjusted_base),
984 num_unmapped as nat == removed.len(),
985 crate::specs::mm::page_table::mapping_set_lemmas::wf_mapping_set(adjusted_base),
986 // Everything removed is in the [start, end) range.
987 forall |m: Mapping| #[trigger] removed.contains(m) ==>
988 start_va <= m.va_range.start < end_va,
989 // Per-config VA bound: every removed mapping fits within the
990 // user VA space, sourced from the cursor view prior to removal.
991 forall |m: Mapping| #[trigger] removed.contains(m) ==>
992 m.va_range.end <= 0x0000_8000_0000_0000_usize,
993 // Nothing in [start_va, end_va) with start < cursor_va remains,
994 // unless it is a sub-mapping of a boundary-straddling entry.
995 forall |m: Mapping| #![auto] adjusted_base.contains(m) && !removed.contains(m)
996 && start_va <= m.va_range.start && m.va_range.start < end_va ==>
997 m.va_range.start >= cursor_owner@.cur_va
998 || exists |parent: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(parent)
999 && parent.va_range.start < start_va
1000 && parent.va_range.start <= m.va_range.start
1001 && m.va_range.end <= parent.va_range.end
1002 && m.pa_range.start == (parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1003 && m.property == parent.property,
1004 start_va == old(cursor_owner)@.cur_va,
1005 old(cursor_owner)@.inv(),
1006 // Locality: old mappings fully outside [start, end) survive in adjusted_base.
1007 // (Straddling mappings may be split — see refinement.)
1008 forall |m: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(m)
1009 && (m.va_range.end <= start_va || m.va_range.start >= end_va)
1010 ==> #[trigger] adjusted_base.contains(m),
1011 // Refinement: every mapping in adjusted_base is either from the old view
1012 // or a sub-mapping of an old entry (from boundary splits).
1013 forall |m: Mapping| #[trigger] adjusted_base.contains(m) ==>
1014 old(cursor_owner)@.mappings.contains(m)
1015 || exists |parent: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(parent)
1016 && parent.va_range.start <= m.va_range.start
1017 && m.va_range.end <= parent.va_range.end
1018 && m.pa_range.start == (parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1019 && m.property == parent.property,
1020 invariant_except_break
1021 self.pt_cursor.0.va <= end_va,
1022 self.pt_cursor.0.va < end_va ==> cursor_owner.in_locked_range(),
1023 ensures
1024 self.pt_cursor.0.va >= end_va,
1025 decreases end_va - self.pt_cursor.0.va
1026 )]
1027 loop {
1028 let ghost prev_va: Vaddr = cursor_owner@.cur_va;
1029 let ghost prev_mappings: Set<Mapping> = cursor_owner@.mappings;
1030
1031 let ghost prev_view_inv: bool = cursor_owner@.inv();
1032 proof {
1033 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1034 cursor_owner.view_preserves_inv();
1035 // Per-config VA bound on prev_mappings — needed for
1036 // preserving the `removed`-end-bound loop invariant. The user
1037 // cursor's view lies in [0, 0x8000_0000_0000) by the PROVEN
1038 // `lemma_view_in_vaddr_range_user` (no longer the generic
1039 // axiom), which follows from `cursor_owner.inv()`'s isolation
1040 // (borrowed-kernel-half) clause.
1041 crate::specs::mm::page_table::cursor::owners::lemma_view_in_vaddr_range_user(
1042 cursor_owner,
1043 );
1044 lemma_vaddr_range_spec_user();
1045 }
1046
1047 // SAFETY: It is safe to un-map memory in the userspace.
1048 let Some(frag) = (unsafe {
1049 #[verus_spec(with Tracked(cursor_owner), Tracked(regions), Tracked(guards))]
1050 self.pt_cursor.take_next(end_va - self.virt_addr())
1051 }) else {
1052 proof {
1053 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1054 // At break: take_next returned None, so no mappings in [prev_va, end_va).
1055 // Any m with start >= prev_va leads to contradiction via the empty filter.
1056 assert forall|m: Mapping|
1057 #![auto]
1058 adjusted_base.contains(m) && !removed.contains(m) && start_va
1059 <= m.va_range.start && m.va_range.start
1060 < end_va implies m.va_range.start >= cursor_owner@.cur_va || exists|
1061 parent: Mapping,
1062 | #[trigger]
1063 old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1064 < start_va && parent.va_range.start <= m.va_range.start
1065 && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1066 parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1067 && m.property == parent.property by {
1068 if m.va_range.start >= prev_va {
1069 assert(prev_mappings.filter(
1070 |m2: Mapping| prev_va <= m2.va_range.start < end_va,
1071 ).contains(m));
1072 assert(false);
1073 }
1074 };
1075 }
1076 break;
1077 };
1078
1079 let ghost old_adjusted = adjusted_base;
1080 let ghost old_removed = removed;
1081
1082 proof {
1083 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1084 }
1085
1086 let ghost frag_ghost = frag;
1087
1088 match frag {
1089 PageTableFrag::Mapped { va, item, .. } => {
1090 let frame = item.frame;
1091 proof {
1092 lemma_vaddr_range_spec_user();
1093 // `wf_mapping_set(removed)` from the wf adjusted_base
1094 // via subset; `va_range.end <= 2^47` for every removed
1095 // mapping is a loop invariant. Together they give
1096 // |removed| < usize::MAX, so num_unmapped + 1 fits.
1097 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1098 adjusted_base,
1099 removed,
1100 );
1101 crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1102 removed);
1103 }
1104 num_unmapped += 1;
1105 #[verus_spec(with Tracked(tlb_model))]
1106 self.flusher.issue_tlb_flush_with(TlbFlushOp::Address(va), frame.into_dyn());
1107 },
1108 PageTableFrag::StrayPageTable { pt, va, len, num_frames } => {
1109 proof {
1110 let ghost new_removed = old_removed.union(
1111 prev_mappings.filter(
1112 |m2: Mapping|
1113 frag_ghost->StrayPageTable_va <= m2.va_range.start
1114 < frag_ghost->StrayPageTable_va
1115 + frag_ghost->StrayPageTable_len,
1116 ),
1117 );
1118 assert(new_removed.subset_of(old_adjusted)) by {
1119 assert forall|m: Mapping|
1120 new_removed.contains(m) implies old_adjusted.contains(m) by {
1121 if prev_mappings.contains(m) {
1122 // m ∈ prev_mappings = old_adjusted \ old_removed ⊆ old_adjusted.
1123 }
1124 };
1125 };
1126 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1127 old_adjusted,
1128 new_removed,
1129 );
1130 lemma_vaddr_range_spec_user();
1131 crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1132 new_removed);
1133 // |new_removed| = |old_removed| + |subtree| (disjoint).
1134 assert(old_removed.disjoint(
1135 prev_mappings.filter(
1136 |m2: Mapping|
1137 frag_ghost->StrayPageTable_va <= m2.va_range.start
1138 < frag_ghost->StrayPageTable_va
1139 + frag_ghost->StrayPageTable_len,
1140 ),
1141 )) by {
1142 assert forall|m: Mapping|
1143 old_removed.contains(m) implies !prev_mappings.filter(
1144 |m2: Mapping|
1145 frag_ghost->StrayPageTable_va <= m2.va_range.start
1146 < frag_ghost->StrayPageTable_va
1147 + frag_ghost->StrayPageTable_len,
1148 ).contains(m) by {
1149 assert(!prev_mappings.contains(m));
1150 };
1151 };
1152 vstd::set_lib::lemma_set_disjoint_lens(
1153 old_removed,
1154 prev_mappings.filter(
1155 |m2: Mapping|
1156 frag_ghost->StrayPageTable_va <= m2.va_range.start
1157 < frag_ghost->StrayPageTable_va
1158 + frag_ghost->StrayPageTable_len,
1159 ),
1160 );
1161 }
1162 num_unmapped += num_frames;
1163 proof {
1164 assert(0x0000_8000_0000_0000usize < KERNEL_VADDR_RANGE.end as usize)
1165 by (compute_only);
1166 crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_va_plus_page_size_no_overflow(
1167 va, len);
1168 }
1169 #[verus_spec(with Tracked(tlb_model))]
1170 self.flusher.issue_tlb_flush_with(TlbFlushOp::Range(va..va + len), pt);
1171 },
1172 }
1173
1174 proof {
1175 let ghost mm = match frag_ghost {
1176 PageTableFrag::Mapped { va: fv, item: fi, .. } => CursorView::<
1177 UserPtConfig,
1178 >::item_into_mapping(fv, fi),
1179 _ => arbitrary(),
1180 };
1181 let ghost sv = CursorView::<UserPtConfig> {
1182 cur_va: match frag_ghost {
1183 PageTableFrag::Mapped { va: fv, .. } => fv as Vaddr,
1184 _ => 0,
1185 },
1186 mappings: prev_mappings,
1187 phantom: PhantomData,
1188 };
1189 let ghost sm = sv.split_while_huge(mm.page_size).mappings;
1190 let ghost is_mapped = frag_ghost is Mapped;
1191 let ghost subtree = match frag_ghost {
1192 PageTableFrag::StrayPageTable { va: fv, len: fl, .. } => prev_mappings.filter(
1193 |m2: Mapping| fv <= m2.va_range.start < fv + fl,
1194 ),
1195 _ => Set::empty(),
1196 };
1197
1198 // Update ghost tracking variables.
1199 match frag_ghost {
1200 PageTableFrag::StrayPageTable { .. } => {
1201 removed = old_removed.union(subtree);
1202 },
1203 PageTableFrag::Mapped { .. } => {
1204 adjusted_base = sm.union(old_removed);
1205 removed = old_removed.union(set![mm]);
1206 },
1207 }
1208
1209 // Mapped-case setup: establish split_while_huge properties once.
1210 if is_mapped {
1211 assert forall|m: Mapping, x: Mapping| #[trigger]
1212 prev_mappings.contains(m) && #[trigger] old_removed.contains(
1213 x,
1214 ) implies Mapping::disjoint_vaddrs(m, x) by {
1215 assert(old_adjusted.contains(m));
1216 assert(old_adjusted.contains(x));
1217 };
1218 sv.split_while_huge_disjoint(mm.page_size, old_removed);
1219 sv.lemma_split_while_huge_preserves_inv(mm.page_size);
1220 }
1221 assert forall|m: Mapping| #[trigger] removed.contains(m) implies m.va_range.end
1222 <= 0x0000_8000_0000_0000_usize by {
1223 if !old_removed.contains(m) {
1224 if is_mapped {
1225 assert(m == mm);
1226 sv.split_while_huge_refinement(mm.page_size, mm);
1227 if !prev_mappings.contains(mm) {
1228 let parent = choose|p: Mapping| #[trigger]
1229 prev_mappings.contains(p) && p.va_range.start
1230 <= mm.va_range.start && mm.va_range.end <= p.va_range.end
1231 && mm.pa_range.start == (p.pa_range.start + (
1232 mm.va_range.start - p.va_range.start)) as Paddr && mm.property
1233 == p.property;
1234 }
1235 } else {
1236 assert(prev_mappings.contains(m));
1237 }
1238 }
1239 };
1240
1241 // Prove |removed| tracking (disjointness + cardinality).
1242
1243 match frag_ghost {
1244 PageTableFrag::StrayPageTable { .. } => {
1245 assert(old_removed.disjoint(subtree)) by {
1246 assert forall|e: Mapping|
1247 old_removed.contains(e) implies !subtree.contains(e) by {};
1248 };
1249 vstd::set_lib::lemma_set_disjoint_lens(old_removed, subtree);
1250 assert(removed == old_removed + subtree);
1251 },
1252 PageTableFrag::Mapped { .. } => {
1253 assert(old_removed.disjoint(set![mm])) by {
1254 assert forall|e: Mapping| #[trigger]
1255 old_removed.contains(e) implies !set![mm].contains(e) by {};
1256 };
1257 vstd::set_lib::lemma_set_disjoint_lens(old_removed, set![mm]);
1258 assert(removed == old_removed + set![mm]);
1259 vstd::set_lib::lemma_set_empty_equivalency_len(Set::<Mapping>::empty());
1260 vstd::set::lemma_set_insert_len(Set::<Mapping>::empty(), mm);
1261 },
1262 }
1263
1264 // Maintain wf_mapping_set(adjusted_base) — only changes in Mapped case.
1265 if is_mapped {
1266 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1267 old_adjusted,
1268 old_removed,
1269 );
1270 assert forall|m: Mapping, n: Mapping| #[trigger]
1271 sm.contains(m) && #[trigger] old_removed.contains(n) implies m.va_range.end
1272 <= n.va_range.start || n.va_range.end <= m.va_range.start by {
1273 sv.split_while_huge_refinement(mm.page_size, m);
1274 assert(!prev_mappings.contains(n));
1275 if prev_mappings.contains(m) {
1276 } else {
1277 let p = choose|p: Mapping| #[trigger]
1278 prev_mappings.contains(p) && p.va_range.start <= m.va_range.start
1279 && m.va_range.end <= p.va_range.end && m.pa_range.start == (
1280 p.pa_range.start + (m.va_range.start - p.va_range.start)) as Paddr
1281 && m.property == p.property;
1282 assert(old_adjusted.contains(p));
1283 }
1284 };
1285 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_union(
1286 sm,
1287 old_removed,
1288 );
1289 }
1290 // Maintain mappings == adjusted_base \ removed.
1291
1292 assert forall|e: Mapping| #[trigger]
1293 adjusted_base.difference(removed).contains(e)
1294 <==> cursor_owner@.mappings.contains(e) by {};
1295
1296 assert(removed.subset_of(adjusted_base)) by {
1297 assert forall|e: Mapping| #[trigger]
1298 removed.contains(e) implies adjusted_base.contains(e) by {};
1299 };
1300
1301 // Maintain: not-yet-removed mappings in [start, end) are either
1302 // ahead of the cursor or sub-mappings of a boundary-straddling parent.
1303 assert forall|m: Mapping|
1304 #![auto]
1305 adjusted_base.contains(m) && !removed.contains(m) && start_va
1306 <= m.va_range.start && m.va_range.start < end_va implies m.va_range.start
1307 >= cursor_owner@.cur_va || exists|parent: Mapping| #[trigger]
1308 old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start < start_va
1309 && parent.va_range.start <= m.va_range.start && m.va_range.end
1310 <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1311 m.va_range.start - parent.va_range.start)) as Paddr && m.property
1312 == parent.property by {
1313 if m.va_range.start < cursor_owner@.cur_va {
1314 if m.va_range.start >= prev_va {
1315 // m was just processed — contradiction via empty filtered sets.
1316 match frag_ghost {
1317 PageTableFrag::StrayPageTable { va: frag_va, .. } => {
1318 if m.va_range.start >= frag_va {
1319 assert(cursor_owner@.mappings.filter(
1320 |m2: Mapping|
1321 frag_va <= m2.va_range.start < self.pt_cursor.0.va,
1322 ).contains(m));
1323 } else {
1324 assert(prev_mappings.filter(
1325 |m2: Mapping| prev_va <= m2.va_range.start < frag_va,
1326 ).contains(m));
1327 }
1328 },
1329 PageTableFrag::Mapped { va: frag_va, .. } => {
1330 if m.va_range.start >= (frag_va as usize) {
1331 assert(cursor_owner@.mappings.filter(
1332 |m2: Mapping|
1333 (frag_va as usize) <= m2.va_range.start
1334 < self.pt_cursor.0.va,
1335 ).contains(m));
1336 } else {
1337 assert(prev_mappings.filter(
1338 |m2: Mapping|
1339 prev_va <= m2.va_range.start < (frag_va as usize),
1340 ).contains(m));
1341 }
1342 },
1343 }
1344 assert(false);
1345 } else if is_mapped {
1346 // m.start < prev_va, m ∈ sm \ old_removed.
1347 assert(sm.contains(m));
1348 sv.split_while_huge_refinement(mm.page_size, m);
1349 if prev_mappings.contains(m) {
1350 // m ∈ old_adjusted \ old_removed — previous invariant applies directly.
1351 } else {
1352 // m is a sub-mapping of some p ∈ prev_mappings.
1353 let p = choose|p: Mapping| #[trigger]
1354 prev_mappings.contains(p) && p.va_range.start
1355 <= m.va_range.start && m.va_range.end <= p.va_range.end
1356 && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1357 - p.va_range.start)) as Paddr && m.property == p.property;
1358 assert(old_adjusted.contains(p) && !old_removed.contains(p));
1359 if p.va_range.start < start_va {
1360 // p itself or its ancestor is the boundary parent.
1361 if !old(cursor_owner)@.mappings.contains(p) {
1362 let orig = choose|orig: Mapping| #[trigger]
1363 old(cursor_owner)@.mappings.contains(orig)
1364 && orig.va_range.start <= p.va_range.start
1365 && p.va_range.end <= orig.va_range.end
1366 && p.pa_range.start == (orig.pa_range.start + (
1367 p.va_range.start - orig.va_range.start)) as Paddr
1368 && p.property == orig.property;
1369 assert(orig.inv());
1370 assert(m.inv());
1371 crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1372 m, p, orig);
1373 }
1374 } else if p.va_range.start >= end_va {
1375 assert(false); // p.start <= m.start < end_va, contradiction.
1376 } else {
1377 // start_va <= p.start < end_va, p.start < prev_va.
1378 // Previous invariant gives boundary ancestor orig.
1379 let orig = choose|orig: Mapping| #[trigger]
1380 old(cursor_owner)@.mappings.contains(orig)
1381 && orig.va_range.start < start_va && orig.va_range.start
1382 <= p.va_range.start && p.va_range.end
1383 <= orig.va_range.end && p.pa_range.start == (
1384 orig.pa_range.start + (p.va_range.start
1385 - orig.va_range.start)) as Paddr && p.property
1386 == orig.property;
1387 assert(orig.inv());
1388 assert(m.inv());
1389 crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1390 m, p, orig);
1391 }
1392 }
1393 }
1394 }
1395 };
1396
1397 // Maintain: old mappings outside [start, end) survive in adjusted_base.
1398 if is_mapped {
1399 assert forall|m: Mapping|
1400 old(cursor_owner)@.mappings.contains(m) && (m.va_range.end <= start_va
1401 || m.va_range.start
1402 >= end_va) implies #[trigger] adjusted_base.contains(m) by {
1403 if m.va_range.end <= start_va {
1404 assert(m.inv());
1405 }
1406 sv.split_while_huge_locality(mm.page_size, m);
1407 };
1408
1409 // Maintain: refinement — every mapping in adjusted_base comes from
1410 // old mappings or is a sub-mapping of one.
1411 assert forall|m: Mapping| #[trigger] adjusted_base.contains(m) implies old(
1412 cursor_owner,
1413 )@.mappings.contains(m) || exists|parent: Mapping| #[trigger]
1414 old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1415 <= m.va_range.start && m.va_range.end <= parent.va_range.end
1416 && m.pa_range.start == (parent.pa_range.start + (m.va_range.start
1417 - parent.va_range.start)) as Paddr && m.property == parent.property by {
1418 if !old_removed.contains(m) {
1419 sv.split_while_huge_refinement(mm.page_size, m);
1420 if !prev_mappings.contains(m) {
1421 let p = choose|p: Mapping| #[trigger]
1422 prev_mappings.contains(p) && p.va_range.start
1423 <= m.va_range.start && m.va_range.end <= p.va_range.end
1424 && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1425 - p.va_range.start)) as Paddr && m.property == p.property;
1426 assert(old_adjusted.contains(p));
1427 if !old(cursor_owner)@.mappings.contains(p) {
1428 let orig = choose|orig: Mapping| #[trigger]
1429 old(cursor_owner)@.mappings.contains(orig)
1430 && orig.va_range.start <= p.va_range.start
1431 && p.va_range.end <= orig.va_range.end
1432 && p.pa_range.start == (orig.pa_range.start + (
1433 p.va_range.start - orig.va_range.start)) as Paddr
1434 && p.property == orig.property;
1435 assert(orig.inv());
1436 assert(m.inv());
1437 crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1438 m, p, orig);
1439 }
1440 }
1441 }
1442 }
1443 };
1444 }
1445 }
1446 proof {
1447 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1448
1449 let old_view = old(cursor_owner)@;
1450 let new_view = cursor_owner@;
1451
1452 // Bridge from loop invariant to unmap_spec.
1453 let start = old_view.cur_va;
1454 let end = (old_view.cur_va + len) as Vaddr;
1455
1456 assert forall|m: Mapping|
1457 #![auto]
1458 old_view.mappings.contains(m) && (m.va_range.end <= start || m.va_range.start
1459 >= end) implies new_view.mappings.contains(m) by {
1460 assert(adjusted_base.contains(m));
1461 if m.va_range.end <= start {
1462 assert(m.inv());
1463 }
1464 };
1465
1466 assert forall|m: Mapping|
1467 new_view.mappings.contains(m) && start <= m.va_range.start < end implies exists|
1468 parent: Mapping,
1469 | #[trigger]
1470 old_view.mappings.contains(parent) && parent.va_range.start < start
1471 && parent.va_range.start <= m.va_range.start && m.va_range.end
1472 <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1473 m.va_range.start - parent.va_range.start)) as Paddr && m.property
1474 == parent.property by {};
1475
1476 assert forall|m: Mapping|
1477 new_view.mappings.contains(m) implies old_view.mappings.contains(m) || exists|
1478 parent: Mapping,
1479 | #[trigger]
1480 old_view.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
1481 && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1482 parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1483 && m.property == parent.property by {};
1484 }
1485
1486 #[verus_spec(with Tracked(tlb_model))]
1487 self.flusher.dispatch_tlb_flush();
1488
1489 num_unmapped
1490 }
1491
1492 /// Applies the operation to the next slot of mapping within the range.
1493 ///
1494 /// The range to be found in is the current virtual address with the
1495 /// provided length.
1496 ///
1497 /// The function stops and yields the actually protected range if it has
1498 /// actually protected a page, no matter if the following pages are also
1499 /// required to be protected.
1500 ///
1501 /// It also makes the cursor moves forward to the next page after the
1502 /// protected one. If no mapped pages exist in the following range, the
1503 /// cursor will stop at the end of the range and return [`None`].
1504 ///
1505 /// Note that it will **NOT** flush the TLB after the operation. Please
1506 /// make the decision yourself on when and how to flush the TLB using
1507 /// [`Self::flusher`].
1508 ///
1509 /// # Verified Properties
1510 /// ## Preconditions
1511 /// - **Safety Invariants**: The page table cursor safety invariants
1512 /// ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the
1513 /// meta-region invariants must hold before the call.
1514 /// - The cursor must be within the locked range and below the guard level.
1515 /// - The current entry must be a mapped frame (not absent or a page table node).
1516 /// - **Liveness**: The length must be page-aligned and within the remaining cursor range.
1517 /// ## Postconditions
1518 /// - **Correctness**: If the metadata region was well-formed before the call, it will be
1519 /// well-formed after.
1520 /// ## Panics
1521 /// Panics if the length is longer than the remaining range of the cursor.
1522 /// ## Safety
1523 /// - From a soundness perspective changing a userspace page's `prop` field is safe.
1524 #[verus_spec(r =>
1525 with
1526 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
1527 Tracked(regions): Tracked<&mut MetaRegionOwners>,
1528 Tracked(guards): Tracked<&mut Guards<'a>>,
1529 requires
1530 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
1531 forall |p: PageProperty| op.requires((p,)),
1532 // POTENTIALLY UNSOUND PATCH: trackedness preservation. For UserPtConfig
1533 // this is trivially true (tracked is constant). See `Entry::protect`.
1534 forall |pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty| #![auto]
1535 op.ensures((p_in,), p_out) ==>
1536 UserPtConfig::tracked(UserPtConfig::item_from_raw_spec(pa, level, p_out))
1537 == UserPtConfig::tracked(UserPtConfig::item_from_raw_spec(pa, level, p_in)),
1538 forall |pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty| #![auto]
1539 op.ensures((p_in,), p_out)
1540 && <PageTableEntry as PageTableEntryTrait>::new_page_req(pa, level, p_in) ==>
1541 <PageTableEntry as PageTableEntryTrait>::new_page_req(pa, level, p_out),
1542
1543 old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
1544 ensures
1545 !old(self).pt_cursor.0.find_next_panic_condition(len),
1546 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
1547 final(self).pt_cursor.0.barrier_va == old(self).pt_cursor.0.barrier_va,
1548 )]
1549 pub fn protect_next(
1550 &mut self,
1551 len: usize,
1552 op: impl FnOnce(PageProperty) -> PageProperty,
1553 ) -> Option<Range<Vaddr>> {
1554 // SAFETY: It is safe to protect memory in the userspace.
1555 unsafe {
1556 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
1557 self.pt_cursor.protect_next(len, op)
1558 }
1559 }
1560}
1561
1562/*cpu_local_cell! {
1563 /// The `Arc` pointer to the activated VM space on this CPU. If the pointer
1564 /// is NULL, it means that the activated page table is merely the kernel
1565 /// page table.
1566 // TODO: If we are enabling ASID, we need to maintain the TLB state of each
1567 // CPU, rather than merely the activated `VmSpace`. When ASID is enabled,
1568 // the non-active `VmSpace`s can still have their TLB entries in the CPU!
1569 static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null();
1570}*/
1571
1572/*#[cfg(ktest)]
1573pub(super) fn get_activated_vm_space() -> *const VmSpace {
1574 ACTIVATED_VM_SPACE.load()
1575}*/
1576
1577/// The configuration for user page tables.
1578#[verifier::allow(autoderive_clone_without_spec)]
1579#[derive(Clone, Debug)]
1580pub struct UserPtConfig {}
1581
1582/// The item that can be mapped into the [`VmSpace`].
1583pub struct MappedItem {
1584 pub frame: UFrame,
1585 pub prop: PageProperty,
1586}
1587
1588#[verus_verify]
1589impl RCClone for MappedItem {
1590 open spec fn clone_requires(self, perm: MetaRegionOwners) -> bool {
1591 self.frame.clone_requires(perm)
1592 }
1593
1594 open spec fn clone_ensures(
1595 self,
1596 old_perm: MetaRegionOwners,
1597 new_perm: MetaRegionOwners,
1598 res: Self,
1599 ) -> bool {
1600 self.frame.clone_ensures(old_perm, new_perm, res.frame)
1601 }
1602
1603 fn clone(&self, Tracked(perm): Tracked<&mut MetaRegionOwners>) -> (res: Self) {
1604 let frame = self.frame.clone(Tracked(perm));
1605 Self { frame, prop: self.prop }
1606 }
1607}
1608
1609// SAFETY: `item_into_raw` and `item_from_raw` are implemented correctly,
1610unsafe impl PageTableConfig for UserPtConfig {
1611 open spec fn TOP_LEVEL_INDEX_RANGE_spec() -> Range<usize> {
1612 0..256
1613 }
1614
1615 open spec fn TOP_LEVEL_CAN_UNMAP_spec() -> (b: bool) {
1616 true
1617 }
1618
1619 fn TOP_LEVEL_INDEX_RANGE() -> Range<usize> {
1620 0..256
1621 }
1622
1623 fn TOP_LEVEL_CAN_UNMAP() -> (b: bool) {
1624 true
1625 }
1626
1627 type E = PageTableEntry;
1628
1629 type C = PagingConsts;
1630
1631 type Item = MappedItem;
1632
1633 open spec fn item_into_raw_spec(item: Self::Item) -> (Paddr, PagingLevel, PageProperty) {
1634 (item.frame.paddr(), 1, item.prop)
1635 }
1636
1637 #[verifier::external_body]
1638 fn item_into_raw(item: Self::Item) -> (Paddr, PagingLevel, PageProperty) {
1639 let MappedItem { frame, prop } = item;
1640 let level = frame.map_level();
1641 let paddr = frame.into_raw();
1642 (paddr, level, prop)
1643 }
1644
1645 open spec fn item_from_raw_spec(
1646 paddr: Paddr,
1647 _level: PagingLevel,
1648 prop: PageProperty,
1649 ) -> Self::Item {
1650 MappedItem {
1651 frame: UFrame {
1652 ptr: vstd::simple_pptr::PPtr(
1653 crate::mm::frame::meta::mapping::frame_to_meta(paddr),
1654 PhantomData,
1655 ),
1656 _marker: PhantomData,
1657 },
1658 prop,
1659 }
1660 }
1661
1662 #[verifier::external_body]
1663 unsafe fn item_from_raw(paddr: Paddr, level: PagingLevel, prop: PageProperty) -> Self::Item {
1664 let frame = unsafe { UFrame::from_raw(paddr) };
1665 MappedItem { frame, prop }
1666 }
1667
1668 proof fn lemma_item_into_raw_roundtrip(pa: Paddr, level: PagingLevel, prop: PageProperty) {
1669 broadcast use crate::specs::mm::frame::mapping::group_page_meta;
1670
1671 let item = Self::item_from_raw_spec(pa, level, prop);
1672 assert(Self::raw_item_well_formed(pa, level, prop));
1673 assert(item.frame.ptr.addr() == crate::mm::frame::meta::mapping::frame_to_meta(pa));
1674 crate::specs::mm::frame::mapping::lemma_paddr_to_meta_biinjective(pa);
1675 }
1676
1677 proof fn lemma_item_from_raw_roundtrip(
1678 item: Self::Item,
1679 paddr: Paddr,
1680 level: PagingLevel,
1681 prop: PageProperty,
1682 ) {
1683 broadcast use crate::specs::mm::frame::mapping::group_page_meta;
1684
1685 assert(Self::item_well_formed(item));
1686 crate::specs::mm::frame::mapping::lemma_meta_to_paddr_biinjective(item.frame.ptr.addr());
1687 }
1688
1689 open spec fn tracked(_item: Self::Item) -> bool {
1690 // Every UserPt item is a ref-counted UFrame.
1691 true
1692 }
1693
1694 open spec fn item_well_formed(item: Self::Item) -> bool {
1695 item.frame.inv()
1696 }
1697
1698 open spec fn raw_item_well_formed(_pa: Paddr, level: PagingLevel, _prop: PageProperty) -> bool {
1699 level == 1
1700 }
1701
1702 proof fn lemma_raw_item_well_formed_preserved(
1703 pa: Paddr,
1704 level: PagingLevel,
1705 old_prop: PageProperty,
1706 new_prop: PageProperty,
1707 ) {
1708 }
1709
1710 proof fn lemma_raw_item_well_formed_split(
1711 pa: Paddr,
1712 level: PagingLevel,
1713 prop: PageProperty,
1714 child_pa: Paddr,
1715 child_idx: usize,
1716 ) {
1717 }
1718
1719 proof fn lemma_item_from_raw_well_formed(pa: Paddr, level: PagingLevel, prop: PageProperty) {
1720 broadcast use crate::specs::mm::frame::mapping::group_page_meta;
1721
1722 let item = Self::item_from_raw_spec(pa, level, prop);
1723 crate::specs::mm::frame::mapping::lemma_meta_to_paddr_biinjective(item.frame.ptr.addr());
1724 }
1725
1726 proof fn lemma_clone_ensures_concrete(
1727 item: Self::Item,
1728 pa: Paddr,
1729 old_regions: MetaRegionOwners,
1730 new_regions: MetaRegionOwners,
1731 res: Self::Item,
1732 ) {
1733 use crate::specs::mm::frame::mapping::frame_to_index;
1734 let frame_idx = frame_to_index(meta_to_frame(item.frame.ptr.addr()));
1735 assert(frame_to_index(pa) == frame_idx);
1736 assert(<MappedItem as RCClone>::clone_ensures(item, old_regions, new_regions, res));
1737 assert(item.frame.clone_ensures(old_regions, new_regions, res.frame));
1738 }
1739
1740 proof fn lemma_clone_requires_concrete(
1741 item: Self::Item,
1742 pa: Paddr,
1743 level: PagingLevel,
1744 prop: PageProperty,
1745 regions: MetaRegionOwners,
1746 ) {
1747 use crate::specs::mm::frame::mapping::frame_to_index;
1748 broadcast use crate::specs::mm::frame::mapping::group_page_meta;
1749
1750 Self::lemma_item_from_raw_well_formed(pa, level, prop);
1751 assert(meta_to_frame(item.frame.ptr.addr()) == pa);
1752 assert(frame_to_index(meta_to_frame(item.frame.ptr.addr())) == frame_to_index(pa));
1753 }
1754
1755 proof fn lemma_page_table_config_constant_requirements() {
1756 use vstd::arithmetic::power2::{lemma2_to64, lemma2_to64_rest, lemma_pow2_adds};
1757 use vstd_extra::prelude::lemma_usize_pow2_ilog2;
1758
1759 lemma2_to64();
1760 lemma2_to64_rest();
1761 vstd::layout::unsigned_int_max_values();
1762 lemma_usize_pow2_ilog2(12);
1763 lemma_usize_pow2_ilog2(9);
1764 lemma_pow2_adds(9, 39);
1765 PageTableEntry::lemma_layout();
1766 Self::C::lemma_paging_consts_properties();
1767 assert(Self::LEADING_BITS_spec() == 0usize);
1768 }
1769}
1770
1771} // verus!