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