Skip to main content

ostd/mm/page_table/node/
mod.rs

1// SPDX-License-Identifier: MPL-2.0
2//! This module defines page table node abstractions and the handle.
3//!
4//! The page table node is also frequently referred to as a page table in many architectural
5//! documentations. It is essentially a page that contains page table entries (PTEs) that map
6//! to child page tables nodes or mapped pages.
7//!
8//! This module leverages the page metadata to manage the page table pages, which makes it
9//! easier to provide the following guarantees:
10//!
11//! The page table node is not freed when it is still in use by:
12//!    - a parent page table node,
13//!    - or a handle to a page table node,
14//!    - or a processor.
15//!
16//! This is implemented by using a reference counter in the page metadata. If the above
17//! conditions are not met, the page table node is ensured to be freed upon dropping the last
18//! reference.
19//!
20//! One can acquire exclusive access to a page table node using merely the physical address of
21//! the page table node. This is implemented by a lock in the page metadata. Here the
22//! exclusiveness is only ensured for kernel code, and the processor's MMU is able to access the
23//! page table node while a lock is held. So the modification to the PTEs should be done after
24//! the initialization of the entity that the PTE points to. This is taken care in this module.
25//!
26mod child;
27mod entry;
28
29#[path = "../../../../specs/mm/page_table/node/child.rs"]
30mod child_specs;
31#[path = "../../../../specs/mm/page_table/node/entry.rs"]
32mod entry_specs;
33
34pub use crate::specs::mm::page_table::node::{entry_owners::*, owners::*};
35pub use child::*;
36pub use entry::*;
37
38use vstd::cell::pcell_maybe_uninit;
39use vstd::prelude::*;
40
41use vstd::atomic::PAtomicU8;
42use vstd_extra::array_ptr;
43use vstd_extra::cast_ptr::*;
44use vstd_extra::drop_tracking::{Drop as VerifiedDrop, TrackDrop};
45use vstd_extra::ghost_tree::*;
46use vstd_extra::ownership::*;
47
48use crate::mm::frame::{
49    allocator::FrameAllocOptions,
50    meta::{
51        META_SLOT_SIZE, MetaSlot, REF_COUNT_MAX, REF_COUNT_UNUSED,
52        mapping::{frame_to_meta, meta_to_frame},
53    },
54};
55
56use crate::mm::page_table::*;
57use crate::mm::{Paddr, Vaddr};
58use crate::specs::mm::{
59    frame::{
60        mapping::{frame_to_index, lemma_frame_to_index_injective},
61        meta_owners::{MetaSlotOwner, MetaSlotStorage, Metadata},
62        meta_region_owners::MetaRegionOwners,
63    },
64    page_table::node::owners::*,
65};
66
67use core::{marker::PhantomData, ops::Deref, sync::atomic::Ordering};
68
69use super::{PageTableConfig, PageTableEntryTrait, nr_subpage_per_huge};
70
71use crate::{
72    mm::{
73        PagingConstsTrait,
74        PagingLevel,
75        //        FrameAllocOptions, Infallible,
76        //        VmReader,
77        frame::{Frame, FrameRef, meta::AnyFrameMeta},
78        paddr_to_vaddr,
79        page_table::{load_pte, store_pte},
80    },
81    specs::task::InAtomicMode,
82};
83
84verus! {
85
86/// The metadata of any kinds of page table pages.
87/// Make sure the the generic parameters don't effect the memory layout.
88pub struct PageTablePageMeta<C: PageTableConfig> {
89    /// The number of valid PTEs. It is mutable if the lock is held.
90    pub nr_children: pcell_maybe_uninit::PCell<u16>,
91    /// If the page table is detached from its parent.
92    ///
93    /// A page table can be detached from its parent while still being accessed,
94    /// since we use a RCU scheme to recycle page tables. If this flag is set,
95    /// it means that the parent is recycling the page table.
96    pub stray: pcell_maybe_uninit::PCell<bool>,
97    /// The level of the page table page. A page table page cannot be
98    /// referenced by page tables of different levels.
99    pub level: PagingLevel,
100    /// The lock for the page table page.
101    pub lock: PAtomicU8,
102    pub _phantom: core::marker::PhantomData<C>,
103}
104
105/// A smart pointer to a page table node.
106///
107/// This smart pointer is an owner of a page table node. Thus creating and
108/// dropping it will affect the reference count of the page table node. If
109/// dropped it as the last reference, the page table node and subsequent
110/// children will be freed.
111///
112/// [`PageTableNode`] is read-only. To modify the page table node, lock and use
113/// [`PageTableGuard`].
114pub type PageTableNode<C> = Frame<PageTablePageMeta<C>>;
115
116unsafe impl<C: PageTableConfig> AnyFrameMeta for PageTablePageMeta<C> {
117    /// Caller invariants the PT-node `on_drop` body relies on:
118    /// - Reader well-formedness + `vm_io_owner` matching + read view
119    ///   initialized + at least `PAGE_SIZE` bytes remaining for the
120    ///   PT-node walk.
121    /// - Global region table invariant.
122    /// - Embedding ([`child_perms_embedding`]): for every paddr in
123    ///   `child_perms.dom()`, the slot and perm match `from_raw` /
124    ///   `VerifiedDrop::drop`'s expected shape.
125    /// - Walk coverage ([`walk_coverage_from_view`]): for every present
126    ///   non-last PTE in the page bytes, `frame_to_index(pte.paddr()) ∈
127    ///   child_perms.dom()`.
128    /// - Walk uniqueness ([`walk_uniqueness_from_view`]): distinct PTE
129    ///   positions with present non-last PTEs have distinct paddrs.
130    ///
131    /// The body now discharges the dom-membership obligation in full via
132    /// byte-level chaining (`decode_pod` + `read_once`'s strengthened
133    /// ensures + the byte-preservation loop invariant) plus the two
134    /// walk-* preconditions; see [`lemma_coverage_at`] and
135    /// [`lemma_uniqueness_at_pair`].
136    open spec fn on_drop_pre(
137        &self,
138        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
139        regions: crate::specs::mm::frame::meta_region_owners::MetaRegionOwners,
140        vm_io_owner: crate::specs::mm::io::VmIoOwner,
141    ) -> bool {
142        &&& reader.inv()
143        &&& reader.wf(vm_io_owner)
144        &&& reader.remain_spec() >= crate::specs::arch::PAGE_SIZE
145        &&& reader.cursor.vaddr % core::mem::align_of::<C::E>() == 0
146        &&& vm_io_owner.inv()
147        &&& vm_io_owner.read_view_initialized()
148        &&& regions.inv()
149        &&& Self::child_perms_embedding(regions, vstd::set::Set::empty())
150        &&& self.walk_coverage_from_view(reader, vm_io_owner.read_view_of(), regions.slots.dom())
151        &&& self.walk_items_well_formed_from_view(reader, vm_io_owner.read_view_of())
152        &&& self.walk_uniqueness_from_view(reader, vm_io_owner.read_view_of())
153    }
154
155    /// Drops the children of a page-table node: walks each present PTE and
156    /// drops the referenced child page-table-node frame or mapped item.
157    #[verifier::spinoff_prover]
158    fn on_drop(
159        &mut self,
160        reader: &mut crate::mm::VmReader<'_, crate::mm::Infallible>,
161        Tracked(regions): Tracked<
162            &mut crate::specs::mm::frame::meta_region_owners::MetaRegionOwners,
163        >,
164        Tracked(vm_io_owner): Tracked<&mut crate::specs::mm::io::VmIoOwner>,
165    ) {
166        let level = self.level;
167        let range = if level == C::NR_LEVELS() {
168            C::TOP_LEVEL_INDEX_RANGE()
169        } else {
170            0..nr_subpage_per_huge::<C>()
171        };
172
173        proof {
174            C::lemma_paging_consts_properties();
175            C::lemma_page_table_config_constant_properties();
176            vstd::arithmetic::mul::lemma_mul_inequality(
177                range.start as int,
178                NR_ENTRIES as int,
179                core::mem::size_of::<C::E>() as int,
180            );
181        }
182
183        let ghost size_of_e: int = core::mem::size_of::<C::E>() as int;
184        let ghost align_of_e: int = core::mem::align_of::<C::E>() as int;
185        let ghost pre_skip_cursor: int = reader.cursor.vaddr as int;
186
187        let ghost initial_view: crate::specs::mm::virt_mem::MemView = vm_io_owner.read_view_of();
188        let ghost initial_dom: vstd::set::Set<int> = regions.slots.dom();
189        let ghost initial_reader: crate::mm::VmReader<'_, crate::mm::Infallible> = *reader;
190
191        #[verus_spec(with Tracked(vm_io_owner))]
192        reader.skip_in_place(range.start * core::mem::size_of::<C::E>());
193
194        proof {
195            C::E::lemma_page_table_entry_properties();
196            let k = size_of_e / align_of_e;
197            vstd::arithmetic::div_mod::lemma_fundamental_div_mod(size_of_e, align_of_e);
198            vstd::arithmetic::mul::lemma_mul_is_commutative(align_of_e, k);
199            vstd::arithmetic::mul::lemma_mul_is_associative(range.start as int, k, align_of_e);
200            vstd::arithmetic::div_mod::lemma_mod_multiples_basic(range.start * k, align_of_e);
201            vstd::arithmetic::div_mod::lemma_mod_adds(
202                pre_skip_cursor,
203                range.start * size_of_e,
204                align_of_e,
205            );
206        }
207
208        let ghost post_skip_remain: int = reader.remain_spec() as int;
209        let ghost range_start: int = range.start as int;
210        let ghost range_end: int = range.end as int;
211        let n_iters: usize = range.end - range.start;
212        let mut iter_count: usize = 0;
213        let ghost mut removed_indices: vstd::set::Set<int> = vstd::set::Set::empty();
214
215        proof {
216            C::lemma_page_table_config_constant_properties();
217            C::lemma_paging_consts_properties();
218            vstd::arithmetic::mul::lemma_mul_is_distributive_sub_other_way(
219                size_of_e,
220                NR_ENTRIES as int,
221                range_start,
222            );
223            vstd::arithmetic::mul::lemma_mul_inequality(
224                range_end - range_start,
225                NR_ENTRIES - range_start,
226                size_of_e,
227            );
228        }
229
230        while iter_count < n_iters
231            invariant
232                reader.inv(),
233                reader.wf(*vm_io_owner),
234                vm_io_owner.inv(),
235                vm_io_owner.read_view_initialized(),
236                regions.inv(),
237                reader.cursor.vaddr as int % align_of_e == 0,
238                size_of_e == core::mem::size_of::<C::E>(),
239                align_of_e == core::mem::align_of::<C::E>(),
240                size_of_e % align_of_e == 0,
241                align_of_e > 0,
242                size_of_e > 0,
243                iter_count <= n_iters,
244                n_iters == range_end - range_start,
245                // Verus loses non-negativity of `range_start` / `range_end`
246                // across the loop boundary; pin it via these invariants so
247                // `lemma_mul_nonnegative` preconditions discharge in the body.
248                0 <= range_start,
249                range_start <= range_end,
250                range_end <= NR_ENTRIES,
251                reader.remain_spec() == post_skip_remain - iter_count * size_of_e,
252                post_skip_remain >= (range_end - range_start) * size_of_e,
253                regions.slots.dom() == initial_dom,
254                Self::child_perms_embedding(*regions, removed_indices),
255                self.walk_coverage_from_view(initial_reader, initial_view, initial_dom),
256                self.walk_items_well_formed_from_view(initial_reader, initial_view),
257                self.walk_uniqueness_from_view(initial_reader, initial_view),
258                // Without this, Verus treats `self.level` as potentially
259                // mutated by `&mut self` and the level-comparison facts go
260                // missing inside walk_coverage / walk_uniqueness instances.
261                self.level == level,
262                reader.end == initial_reader.end,
263                reader.cursor.vaddr == initial_reader.cursor.vaddr + range_start * size_of_e
264                    + iter_count * size_of_e,
265                forall|i: usize|
266                    #![trigger initial_view.addr_transl(i)]
267                    initial_reader.cursor.vaddr <= i < initial_reader.end.vaddr ==> {
268                        &&& initial_view.addr_transl(i) is Some
269                        &&& initial_view.memory.contains_key(initial_view.addr_transl(i).unwrap().0)
270                    },
271                forall|va: usize|
272                    #![trigger vm_io_owner.read_view_of().read(va)]
273                    reader.cursor.vaddr <= va < initial_reader.end.vaddr ==> {
274                        &&& initial_view.addr_transl(va) == vm_io_owner.read_view_of().addr_transl(
275                            va,
276                        )
277                        &&& initial_view.read(va) == vm_io_owner.read_view_of().read(va)
278                    },
279                removed_indices.subset_of(initial_dom),
280                // Witness past iter for each removed idx — the discharge
281                // proof picks it up via `choose|j|` and invokes
282                // `walk_uniqueness` at (current_cursor, witness_cursor).
283                forall|idx: int| #[trigger]
284                    removed_indices.contains(idx) ==> exists|j: int|
285                        #![trigger Self::walk_pte_at_view(
286                            initial_view,
287                            (initial_reader.cursor.vaddr
288                                + range_start * size_of_e
289                                + j * size_of_e) as usize,
290                        )]
291                        0 <= j < iter_count && {
292                            let cj = (initial_reader.cursor.vaddr + range_start * size_of_e + j
293                                * size_of_e) as usize;
294                            let pte_j = Self::walk_pte_at_view(initial_view, cj);
295                            &&& pte_j.is_present()
296                            &&& !pte_j.is_last(self.level)
297                            &&& idx == frame_to_index(pte_j.paddr())
298                        },
299            decreases n_iters - iter_count,
300        {
301            proof {
302                vstd::arithmetic::mul::lemma_mul_is_distributive_sub(
303                    size_of_e,
304                    range_end - range_start,
305                    iter_count as int,
306                );
307                vstd::arithmetic::mul::lemma_mul_inequality(
308                    1,
309                    range_end - range_start - iter_count,
310                    size_of_e,
311                );
312            }
313            let ghost cursor_pre_read: usize = reader.cursor.vaddr;
314            let ghost pre_view: crate::specs::mm::virt_mem::MemView = vm_io_owner.read_view_of();
315            proof {
316                crate::specs::mm::virt_mem::MemView::lemma_read_bytes_eq_pointwise(
317                    pre_view,
318                    initial_view,
319                    cursor_pre_read,
320                    core::mem::size_of::<C::E>(),
321                );
322            }
323            let pte = #[verus_spec(with Tracked(vm_io_owner))]
324            reader.read_once::<C::E>();
325            let pte = pte.unwrap();
326            proof {
327                ostd_pod::lemma_decode_pod_inverse::<C::E>(pte);
328                vstd::arithmetic::mul::lemma_mul_nonnegative(range_start, size_of_e);
329                vstd::arithmetic::mul::lemma_mul_nonnegative(iter_count as int, size_of_e);
330            }
331            if pte.is_present() {
332                let paddr = pte.paddr();
333                if !pte.is_last(level) {
334                    proof {
335                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
336                            size_of_e,
337                            range_start,
338                            iter_count as int,
339                        );
340                        vstd::arithmetic::div_mod::lemma_mod_multiples_basic(
341                            range_start + iter_count,
342                            size_of_e,
343                        );
344                        Self::lemma_coverage_at(
345                            *self,
346                            initial_reader,
347                            initial_view,
348                            initial_dom,
349                            cursor_pre_read,
350                        );
351                        broadcast use lemma_frame_to_index_injective;
352
353                        assert forall|idx: int| #[trigger] removed_indices.contains(idx) implies idx
354                            != frame_to_index(pte.paddr()) by {
355                            let j = choose|j: int|
356                                #![trigger Self::walk_pte_at_view(
357                                    initial_view,
358                                    (initial_reader.cursor.vaddr
359                                        + range_start * size_of_e
360                                        + j * size_of_e) as usize,
361                                )]
362                                0 <= j < iter_count && {
363                                    let cj = (initial_reader.cursor.vaddr + range_start * size_of_e
364                                        + j * size_of_e) as usize;
365                                    let pte_j = Self::walk_pte_at_view(initial_view, cj);
366                                    &&& pte_j.is_present()
367                                    &&& !pte_j.is_last(self.level)
368                                    &&& idx == frame_to_index(pte_j.paddr())
369                                };
370                            let cj: usize = (initial_reader.cursor.vaddr + range_start * size_of_e
371                                + j * size_of_e) as usize;
372                            let pte_j = Self::walk_pte_at_view(initial_view, cj);
373                            vstd::arithmetic::mul::lemma_mul_nonnegative(range_start, size_of_e);
374                            vstd::arithmetic::mul::lemma_mul_nonnegative(j, size_of_e);
375                            vstd::arithmetic::mul::lemma_mul_strict_inequality(
376                                j,
377                                iter_count as int,
378                                size_of_e,
379                            );
380                            vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
381                                size_of_e,
382                                range_start,
383                                j,
384                            );
385                            vstd::arithmetic::mul::lemma_mul_inequality(
386                                range_start + j + 1,
387                                range_end,
388                                size_of_e,
389                            );
390                            vstd::arithmetic::mul::lemma_mul_is_distributive_sub_other_way(
391                                size_of_e,
392                                range_end,
393                                range_start,
394                            );
395                            vstd::arithmetic::div_mod::lemma_mod_multiples_basic(
396                                range_start + j,
397                                size_of_e,
398                            );
399                            Self::lemma_uniqueness_at_pair(
400                                *self,
401                                initial_reader,
402                                initial_view,
403                                cursor_pre_read,
404                                cj,
405                            );
406                            pte.lemma_paddr_is_page_aligned();
407                            pte_j.lemma_paddr_is_page_aligned();
408                        };
409                        // Pinning these in SMT context lets `tracked_remove`'s
410                        // dom-containment precondition and `from_raw`'s
411                        // `from_raw_requires_safety` (via embedding) discharge.
412                    }
413                    proof {
414                        removed_indices = removed_indices.insert(frame_to_index(paddr));
415                        assert({
416                            let cj = (initial_reader.cursor.vaddr + range_start * size_of_e
417                                + iter_count * size_of_e) as usize;
418                            let pte_j = Self::walk_pte_at_view(initial_view, cj);
419                            &&& cj == cursor_pre_read
420                            &&& pte_j == pte
421                            &&& pte_j.is_present()
422                            &&& !pte_j.is_last(self.level)
423                            &&& frame_to_index(paddr) == frame_to_index(pte_j.paddr())
424                        });
425                    }
426                    proof_decl! {
427                        let tracked from_raw_obl: vstd_extra::drop_tracking::DropObligation<int>;
428                    }
429                    let frame = unsafe {
430                        #[verus_spec(with Tracked(regions) => Tracked(from_raw_obl))]
431                        Frame::<Self>::from_raw(paddr)
432                    };
433                    // `from_raw` minted the obligation; `frame.drop`
434                    // consumes it directly. No redeem dance needed.
435                    VerifiedDrop::drop(frame, Tracked(regions), Tracked(from_raw_obl));
436                } else {
437                    // SAFETY: The PTE points to a mapped item. The ownership
438                    // of the item is transferred here then dropped.
439                    proof {
440                        vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
441                            size_of_e,
442                            range_start,
443                            iter_count as int,
444                        );
445                        vstd::arithmetic::div_mod::lemma_mod_multiples_basic(
446                            range_start + iter_count,
447                            size_of_e,
448                        );
449                        Self::lemma_item_well_formed_at(
450                            *self,
451                            initial_reader,
452                            initial_view,
453                            cursor_pre_read,
454                        );
455                        assert(C::raw_item_well_formed(paddr, level, pte.prop()));
456                    }
457                    let _item = unsafe { C::item_from_raw(paddr, level, pte.prop()) };
458                }
459            }
460            proof {
461                vstd::arithmetic::div_mod::lemma_mod_adds(
462                    reader.cursor.vaddr - size_of_e,
463                    size_of_e,
464                    align_of_e,
465                );
466            }
467            let ghost iter_count_old: int = iter_count as int;
468            iter_count = iter_count + 1;
469            proof {
470                vstd::arithmetic::mul::lemma_mul_is_distributive_add_other_way(
471                    size_of_e,
472                    iter_count_old,
473                    1,
474                );
475            }
476        }
477    }
478
479    fn is_untyped(&self) -> bool {
480        false
481    }
482
483    uninterp spec fn vtable_ptr(&self) -> usize;
484}
485
486#[verus_verify]
487impl<C: PageTableConfig> PageTableNode<C> {
488    /// Gets the level of a page table node.
489    /// # Verified Properties
490    /// ## Preconditions
491    /// - The node must be well-formed, and the caller must provide a permission token for its metadata.
492    /// ## Postconditions
493    /// - Returns the level of the node.
494    /// ## Safety
495    /// - We require the caller to provide a permission token to ensure that this function is only called on a valid page table node.
496    #[verus_spec(
497        with Tracked(perm) : Tracked<&PointsTo<MetaSlot, Metadata<PageTablePageMeta<C>>>>
498    )]
499    pub(super) fn level(&self) -> PagingLevel
500        requires
501            self.ptr.addr() == perm.addr(),
502            self.ptr.addr() == perm.points_to.addr(),
503            perm.is_init(),
504            perm.wf(&perm.inner_perms),
505        returns
506            perm.value().metadata.level,
507    {
508        #[verus_spec(with Tracked(perm))]
509        let meta = self.meta();
510        meta.level
511    }
512
513    /// Allocates a new empty page table node.
514    #[verus_spec(res =>
515        with Tracked(parent_owner): Tracked<&mut NodeOwner<C>>,
516             Tracked(regions): Tracked<&mut MetaRegionOwners>,
517             Tracked(guards): Tracked<&Guards<'rcu>>,
518             Ghost(idx): Ghost<usize>,
519                 -> owner: Tracked<OwnerSubtree<C>>,
520        requires
521            1 <= level < NR_LEVELS,
522            idx < NR_ENTRIES,
523            old(regions).inv(),
524            old(parent_owner).inv(),
525        ensures
526            final(regions).inv(),
527            final(parent_owner).inv(),
528            allocated_empty_node_owner(owner@, level),
529            allocated_empty_node_grandchildren_none(owner@),
530            res.ptr.addr() == owner@.value().node().meta_vaddr(),
531            guards.unlocked(owner@.value().node().meta_vaddr()),
532            MetaSlot::get_node_from_unused_spec(meta_to_frame(owner@.value().node().meta_vaddr()), *old(regions), *final(regions)),
533            MetaSlot::slot_perm_reparked_spec(meta_to_frame(owner@.value().node().meta_vaddr()), *old(regions), *final(regions)),
534
535            final(regions).frame_obligations == old(regions).frame_obligations.insert(
536                frame_to_index(meta_to_frame(owner@.value().node().meta_vaddr()))),
537            old(regions).slots.contains_key(frame_to_index(meta_to_frame(owner@.value().node().meta_vaddr()))),
538
539            !crate::specs::mm::frame::meta_owners::is_mmio_paddr(
540                meta_to_frame(owner@.value().node().meta_vaddr())),
541            owner@.value().metaregion_sound(*final(regions)),
542            forall|i: int|
543                #[trigger] old(regions).slot_owners[i].inner_perms.ref_count.value() != REF_COUNT_UNUSED
544                ==> i != frame_to_index(meta_to_frame(owner@.value().node().meta_vaddr())),
545            owner@.value().match_pte(C::E::new_pt_spec(meta_to_frame(owner@.value().node().meta_vaddr())), level as PagingLevel),
546            final(parent_owner).meta_own == old(parent_owner).meta_own,
547            final(parent_owner).slot_index == old(parent_owner).slot_index,
548            final(parent_owner).level == old(parent_owner).level,
549            final(parent_owner).tree_level == old(parent_owner).tree_level,
550            final(parent_owner).children_perm.addr() == old(parent_owner).children_perm.addr(),
551            final(parent_owner).children_perm.value() == old(parent_owner).children_perm.value().update(
552                idx as int,
553                C::E::new_pt_spec(meta_to_frame(owner@.value().node().meta_vaddr())),
554            ),
555            final(regions).slots.contains_key(owner@.value().node().slot_index),
556            owner@.value().node().metaregion_sound_node(*final(regions)),
557    )]
558    #[verifier::external_body]
559    pub fn alloc<'rcu>(level: PagingLevel) -> Self {
560        let tracked entry_owner = EntryOwner::tracked_new_absent(
561            TreePath::new(Seq::empty()),
562            level,
563        );
564
565        let tracked mut owner = OwnerSubtree::<C>::tracked_new_val(entry_owner, level as nat);
566        let meta = PageTablePageMeta::new(level);
567        let mut frame = FrameAllocOptions::new();
568        frame.zeroed(true);
569        let allocated_frame = frame.alloc_frame_with(meta).expect(
570            "Failed to allocate a page table node",
571        );
572        // The allocated frame is zeroed. Make sure zero is absent PTE.
573        //debug_assert_eq!(C::E::new_absent().as_usize(), 0);
574
575        proof_with!(|= Tracked(owner));
576
577        allocated_frame
578    }/*
579    /// Activates the page table assuming it is a root page table.
580    ///
581    /// Here we ensure not dropping an active page table by making a
582    /// processor a page table owner. When activating a page table, the
583    /// reference count of the last activated page table is decremented.
584    /// And that of the current page table is incremented.
585    ///
586    /// # Safety
587    ///
588    /// The caller must ensure that the page table to be activated has
589    /// proper mappings for the kernel and has the correct const parameters
590    /// matching the current CPU.
591    ///
592    /// # Panics
593    ///
594    /// Only top-level page tables can be activated using this function.
595    pub(crate) unsafe fn activate(&self) {
596        use crate::{
597            arch::mm::{activate_page_table, current_page_table_paddr},
598            mm::page_prop::CachePolicy,
599        };
600
601        #[cfg(feature = "allow_panic")]
602        assert_eq!(self.level(), C::NR_LEVELS());
603
604        let last_activated_paddr = current_page_table_paddr();
605        if last_activated_paddr == self.start_paddr() {
606            return;
607        }
608
609        // SAFETY: The safety is upheld by the caller.
610        unsafe { activate_page_table(self.clone().into_raw(), CachePolicy::Writeback) };
611
612        // Restore and drop the last activated page table.
613        // SAFETY: The physical address is valid and points to a forgotten page table node.
614        drop(unsafe { Self::from_raw(last_activated_paddr) });
615    }
616
617    /// Activates the (root) page table assuming it is the first activation.
618    ///
619    /// It will not try dropping the last activate page table. It is the same
620    /// with [`Self::activate()`] in other senses.
621    pub(super) unsafe fn first_activate(&self) {
622        use crate::{arch::mm::activate_page_table, mm::page_prop::CachePolicy};
623
624        // SAFETY: The safety is upheld by the caller.
625        unsafe { activate_page_table(self.clone().into_raw(), CachePolicy::Writeback) };
626    }*/
627
628}
629
630#[verus_verify]
631impl<'a, C: PageTableConfig> PageTableNodeRef<'a, C> {
632    pub open spec fn locks_preserved_except<'rcu>(
633        addr: usize,
634        guards0: Guards<'rcu>,
635        guards1: Guards<'rcu>,
636    ) -> bool {
637        &&& OwnerSubtree::implies(
638            CursorOwner::<'rcu, C>::node_unlocked(guards0),
639            CursorOwner::<'rcu, C>::node_unlocked_except(guards1, addr),
640        )
641        &&& forall|i: usize| guards0.lock_held(i) ==> guards1.lock_held(i)
642        &&& forall|i: usize| guards0.unlocked(i) && i != addr ==> guards1.unlocked(i)
643    }
644
645    /// Locks the page table node.
646    ///
647    /// An atomic mode guard is required to
648    ///  1. prevent deadlocks;
649    ///  2. provide a lifetime (`'rcu`) that the nodes are guaranteed to outlive.
650    /// # Verification Design
651    /// As of when we verified this library, we didn't have a spin lock implementation, so we axiomatize
652    /// what happens when it's successful.
653    #[verifier::external_body]
654    #[verus_spec(res =>
655        with Tracked(owner): Tracked<&NodeOwner<C>>,
656            Tracked(guards): Tracked<&mut Guards<'rcu>>
657        requires
658            self.inner@.invariants(*owner),
659            old(guards).unlocked(owner.meta_vaddr()),
660        ensures
661            final(guards).lock_held(owner.meta_vaddr()),
662            Self::locks_preserved_except(owner.meta_vaddr(), *old(guards), *final(guards)),
663            owner.relate_guard(res),
664    )]
665    pub fn lock<'rcu, A: InAtomicMode>(self, _guard: &'rcu A) -> PageTableGuard<'rcu, C> where
666        'a: 'rcu,
667     {
668        unimplemented!()
669    }
670
671    /// Creates a new [`PageTableGuard`] without checking if the page table lock is held.
672    ///
673    /// # Safety
674    ///
675    /// This function must be called if this task logically holds the lock.
676    ///
677    /// Calling this function when a guard is already created is undefined behavior
678    /// unless that guard was already forgotten.
679    #[verus_spec(res =>
680        with Tracked(owner): Tracked<&NodeOwner<C>>,
681             Tracked(guards): Tracked<&mut Guards<'rcu>>,
682        requires
683            self.inner@.invariants(*owner),
684            old(guards).unlocked(owner.meta_vaddr()),
685        ensures
686            final(guards).lock_held(owner.meta_vaddr()),
687            Self::locks_preserved_except(owner.meta_vaddr(), *old(guards), *final(guards)),
688            owner.relate_guard(res),
689    )]
690    pub unsafe fn make_guard_unchecked<'rcu, A: InAtomicMode>(
691        self,
692        _guard: &'rcu A,
693    ) -> PageTableGuard<'rcu, C> where 'a: 'rcu {
694        let guard = PageTableGuard { inner: self };
695
696        proof {
697            let ghost guards0 = *guards;
698            guards.guards = guards.guards.insert(owner.meta_vaddr());
699
700        }
701
702        guard
703    }
704}
705
706impl<'rcu, C: PageTableConfig> PageTableGuard<'rcu, C> {
707    /// Borrows an entry in the node at a given index.
708    ///
709    /// # Panics
710    ///
711    /// Panics if the index is not within the bound of
712    /// [`nr_subpage_per_huge<C>`].
713    #[verus_spec(res =>
714        with Tracked(owner): Tracked<&NodeOwner<C>>,
715             Tracked(child_owner): Tracked<&EntryOwner<C>>,
716             Tracked(regions): Tracked<&MetaRegionOwners>,
717        requires
718            owner.inv(),
719            child_owner.inv(),
720            owner.relate_guard(*old(self)),
721            child_owner.match_pte(
722                owner.children_perm.value()[idx as int],
723                child_owner.parent_level,
724            ),
725            regions.inv(),
726            regions.slots.contains_key(owner.slot_index),
727            // Panic condition
728            idx < NR_ENTRIES,
729        ensures
730            res.wf(*child_owner),
731            res.idx == idx,
732            *res.node == *old(self),
733            *final(self) == *final(res.node),
734            owner.relate_guard(*res.node),
735    )]
736    pub fn entry<'a>(&'a mut self, idx: usize) -> Entry<'a, 'rcu, C> {
737        #[cfg(feature = "allow_panic")]
738        assert!(idx < nr_subpage_per_huge::<C>());
739        // SAFETY: The index is within the bound. `Entry::new_at` returns an
740        // entry whose node is the guard value we were handed.
741        unsafe {
742            #[verus_spec(with Tracked(child_owner), Tracked(owner), Tracked(regions))]
743            Entry::new_at(self, idx)
744        }
745    }
746
747    /// Gets the number of valid PTEs in a page table node.
748    /// # Verified Properties
749    /// ## Preconditions
750    /// - The node must be well-formed.
751    /// ## Postconditions
752    /// - Returns the number of valid PTEs in the node.
753    /// ## Safety
754    /// - We require the caller to provide a permission token to ensure that this function is only called on a valid page table node.
755    #[verus_spec(nr =>
756        with Tracked(owner) : Tracked<&NodeOwner<C>>,
757             Tracked(regions): Tracked<&MetaRegionOwners>,
758        requires
759            self.inner.inner@.invariants(*owner),
760            regions.inv(),
761            owner.metaregion_sound_node(*regions),
762        returns
763            owner.meta_own.nr_children.value(),
764    )]
765    pub fn nr_children(&self) -> u16 {
766        let tracked owner_meta_perm = regions.borrow_typed_perm::<PageTablePageMeta<C>>(
767            owner.slot_index,
768        );
769        #[verus_spec(with Tracked(owner_meta_perm))]
770        let meta = self.meta();
771
772        *meta.nr_children.borrow(Tracked(&owner.meta_own.nr_children))
773    }
774
775    /// Returns if the page table node is detached from its parent.
776    #[verus_spec(res =>
777        with
778            Tracked(meta_perm): Tracked<&'a PointsTo<MetaSlot, Metadata<PageTablePageMeta<C>>>>,
779        requires
780            old(self).inner.inner@.ptr.addr() == meta_perm.addr(),
781            old(self).inner.inner@.ptr.addr() == meta_perm.points_to.addr(),
782            meta_perm.is_init(),
783            meta_perm.wf(&meta_perm.inner_perms),
784        ensures
785            res.id() == meta_perm.value().metadata.stray.id(),
786            *final(self) == *old(self),
787    )]
788    pub(super) fn stray_mut<'a>(&'a mut self) -> &'a pcell_maybe_uninit::PCell<bool> {
789        // SAFETY: The lock is held so we have an exclusive access.
790        #[verus_spec(with Tracked(meta_perm))]
791        let meta = self.meta();
792        &meta.stray
793    }
794
795    /// Reads a non-owning PTE at the given index.
796    ///
797    /// A non-owning PTE means that it does not account for a reference count
798    /// of the a page if the PTE points to a page. The original PTE still owns
799    /// the child page.
800    ///
801    /// # Safety
802    ///
803    /// The caller must ensure that the index is within the bound.
804    #[verus_spec(pte =>
805        with Tracked(owner): Tracked<&NodeOwner<C>>,
806             Tracked(regions): Tracked<&MetaRegionOwners>,
807        requires
808            self.inner.inner@.invariants(*owner),
809            regions.inv(),
810            regions.slots.contains_key(owner.slot_index),
811            idx < NR_ENTRIES,
812        ensures
813            pte == owner.children_perm.value()[idx as int],
814    )]
815    pub unsafe fn read_pte(&self, idx: usize) -> C::E {
816        // debug_assert!(idx < nr_subpage_per_huge::<C>());
817        let tracked owner_slot_perm = regions.slots.tracked_borrow(owner.slot_index);
818        let ptr = vstd_extra::array_ptr::ArrayPtr::<C::E, NR_ENTRIES>::from_addr(
819            paddr_to_vaddr(
820                #[verus_spec(with Tracked(owner_slot_perm))]
821                self.start_paddr(),
822            ),
823        );
824
825        // SAFETY:
826        // - The page table node is alive. The index is inside the bound, so the page table entry is valid.
827        // - All page table entries are aligned and accessed with atomic operations only.
828        unsafe {
829            #[verus_spec(with Tracked(&owner.children_perm))]
830            load_pte(ptr.add(idx), Ordering::Relaxed)
831        }
832    }
833
834    /// Writes a page table entry at a given index.
835    ///
836    /// This operation will leak the old child if the old PTE is present.
837    ///
838    /// # Safety
839    ///
840    /// The caller must ensure that:
841    ///  1. The index must be within the bound;
842    ///  2. The PTE must represent a valid [`Child`] whose level is compatible
843    ///     with the page table node.
844    ///  3. The page table node will have the ownership of the [`Child`]
845    ///     after this method.
846    #[verus_spec(
847        with Tracked(owner): Tracked<&mut NodeOwner<C>>,
848             Tracked(regions): Tracked<&MetaRegionOwners>,
849        requires
850            old(self).inner.inner@.invariants(*old(owner)),
851            regions.inv(),
852            regions.slots.contains_key(old(owner).slot_index),
853            idx < NR_ENTRIES,
854        ensures
855            final(owner).inv(),
856            final(owner).level == old(owner).level,
857            final(owner).meta_own == old(owner).meta_own,
858            final(owner).slot_index == old(owner).slot_index,
859            final(owner).children_perm.value() == old(owner).children_perm.value().update(
860                idx as int,
861                pte,
862            ),
863            *final(self) == *old(self),
864    )]
865    pub unsafe fn write_pte(&mut self, idx: usize, pte: C::E) {
866        // debug_assert!(idx < nr_subpage_per_huge::<C>());
867        let tracked owner_slot_perm = regions.slots.tracked_borrow(owner.slot_index);
868        #[verusfmt::skip]
869        let ptr = vstd_extra::array_ptr::ArrayPtr::<C::E, NR_ENTRIES>::from_addr(
870            paddr_to_vaddr(
871                #[verus_spec(with Tracked(owner_slot_perm))]
872                self.start_paddr()
873            ),
874        );
875
876        // SAFETY:
877        // - The page table node is alive. The index is inside the bound, so the page table entry is valid.
878        // - All page table entries are aligned and accessed with atomic operations only.
879        unsafe {
880            #[verus_spec(with Tracked(&mut owner.children_perm))]
881            store_pte(ptr.add(idx), pte, Ordering::Release)
882        }
883    }
884
885    /// Gets the mutable reference to the number of valid PTEs in the node.
886    #[verus_spec(res =>
887        with
888            Tracked(meta_perm): Tracked<&'a PointsTo<MetaSlot, Metadata<PageTablePageMeta<C>>>>,
889        requires
890            old(self).inner.inner@.ptr.addr() == meta_perm.addr(),
891            old(self).inner.inner@.ptr.addr() == meta_perm.points_to.addr(),
892            meta_perm.is_init(),
893            meta_perm.wf(&meta_perm.inner_perms),
894        ensures
895            res.id() == meta_perm.value().metadata.nr_children.id(),
896            *final(self) == *old(self),
897    )]
898    fn nr_children_mut<'a>(&'a mut self) -> &'a pcell_maybe_uninit::PCell<u16> {
899        // SAFETY: The lock is held so we have an exclusive access.
900        #[verus_spec(with Tracked(meta_perm))]
901        let meta = self.meta();
902        &meta.nr_children
903    }
904}
905
906/*impl<C: PageTableConfig> Drop for PageTableGuard<'_, C> {
907    fn drop(&mut self) {
908        self.inner.meta().lock.store(0, Ordering::Release);
909    }
910}*/
911
912impl<C: PageTableConfig> PageTablePageMeta<C> {
913    pub fn new(level: PagingLevel) -> Self {
914        Self {
915            nr_children: pcell_maybe_uninit::PCell::new(0).0,
916            stray: pcell_maybe_uninit::PCell::new(false).0,
917            level,
918            lock: PAtomicU8::new(0).0,
919            _phantom: PhantomData,
920        }
921    }
922
923    /// The PTE value that `read_once::<C::E>` would produce at cursor `c`
924    /// against the given memory view. Linked to `read_once` via
925    /// `pod_bytes(v) == read_view.read_bytes(...)` (strengthened ensures)
926    /// + [`lemma_decode_pod_inverse`].
927    pub open spec fn walk_pte_at_view(view: crate::specs::mm::virt_mem::MemView, c: usize) -> C::E {
928        ostd_pod::decode_pod::<C::E>(view.read_bytes(c, core::mem::size_of::<C::E>()))
929    }
930
931    /// Single-cursor projection of [`walk_coverage_from_view`]. Extracting
932    /// the forall body to a named predicate lets the body invoke
933    /// [`lemma_coverage_at`] for one specific `c` instead of relying on
934    /// auto-trigger matching across the loop invariant's `forall|c|`.
935    pub open spec fn walk_coverage_at(
936        self,
937        view: crate::specs::mm::virt_mem::MemView,
938        dom: vstd::set::Set<int>,
939        c: usize,
940    ) -> bool {
941        let pte = Self::walk_pte_at_view(view, c);
942        pte.is_present() && !pte.is_last(self.level) ==> dom.contains(frame_to_index(pte.paddr()))
943    }
944
945    /// Instantiate [`walk_coverage_from_view`]'s forall at one cursor.
946    pub proof fn lemma_coverage_at(
947        self,
948        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
949        view: crate::specs::mm::virt_mem::MemView,
950        dom: vstd::set::Set<int>,
951        c: usize,
952    )
953        requires
954            self.walk_coverage_from_view(reader, view, dom),
955            reader.cursor.vaddr <= c,
956            c + core::mem::size_of::<C::E>() <= reader.cursor.vaddr + reader.remain_spec(),
957            (c - reader.cursor.vaddr) % core::mem::size_of::<C::E>() as int == 0,
958        ensures
959            self.walk_coverage_at(view, dom, c),
960    {
961    }
962
963    /// Instantiate [`walk_items_well_formed_from_view`]'s forall at one cursor.
964    pub proof fn lemma_item_well_formed_at(
965        self,
966        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
967        view: crate::specs::mm::virt_mem::MemView,
968        c: usize,
969    )
970        requires
971            self.walk_items_well_formed_from_view(reader, view),
972            reader.cursor.vaddr <= c,
973            c + core::mem::size_of::<C::E>() <= reader.cursor.vaddr + reader.remain_spec(),
974            (c - reader.cursor.vaddr) % core::mem::size_of::<C::E>() as int == 0,
975        ensures
976            ({
977                let pte = Self::walk_pte_at_view(view, c);
978                pte.is_present() && pte.is_last(self.level) ==> C::raw_item_well_formed(
979                    pte.paddr(),
980                    self.level,
981                    pte.prop(),
982                )
983            }),
984    {
985    }
986
987    /// Instantiate [`walk_uniqueness_from_view`]'s forall at one cursor pair.
988    pub proof fn lemma_uniqueness_at_pair(
989        self,
990        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
991        view: crate::specs::mm::virt_mem::MemView,
992        c1: usize,
993        c2: usize,
994    )
995        requires
996            self.walk_uniqueness_from_view(reader, view),
997            reader.cursor.vaddr <= c1,
998            c1 + core::mem::size_of::<C::E>() <= reader.cursor.vaddr + reader.remain_spec(),
999            (c1 - reader.cursor.vaddr) % core::mem::size_of::<C::E>() as int == 0,
1000            reader.cursor.vaddr <= c2,
1001            c2 + core::mem::size_of::<C::E>() <= reader.cursor.vaddr + reader.remain_spec(),
1002            (c2 - reader.cursor.vaddr) % core::mem::size_of::<C::E>() as int == 0,
1003            c1 != c2,
1004            Self::walk_pte_at_view(view, c1).is_present(),
1005            !Self::walk_pte_at_view(view, c1).is_last(self.level),
1006            Self::walk_pte_at_view(view, c2).is_present(),
1007            !Self::walk_pte_at_view(view, c2).is_last(self.level),
1008        ensures
1009            Self::walk_pte_at_view(view, c1).paddr() != Self::walk_pte_at_view(view, c2).paddr(),
1010    {
1011    }
1012
1013    /// Caller-side dom-membership obligation: every present non-last PTE
1014    /// position in the walk (over `view`) has its child-frame index in
1015    /// `dom`. Phrased over a frozen `(view, dom)` pair so the body can
1016    /// carry it as a loop invariant against an entry-state snapshot
1017    /// while `vm_io_owner` advances per iteration.
1018    pub open spec fn walk_coverage_from_view(
1019        self,
1020        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
1021        view: crate::specs::mm::virt_mem::MemView,
1022        dom: vstd::set::Set<int>,
1023    ) -> bool {
1024        forall|c: usize|
1025            #![trigger Self::walk_pte_at_view(view, c)]
1026            reader.cursor.vaddr <= c && c + core::mem::size_of::<C::E>() <= reader.cursor.vaddr
1027                + reader.remain_spec() && (c - reader.cursor.vaddr) % core::mem::size_of::<
1028                C::E,
1029            >() as int == 0 ==> {
1030                let pte = Self::walk_pte_at_view(view, c);
1031                pte.is_present() && !pte.is_last(self.level) ==> dom.contains(
1032                    frame_to_index(pte.paddr()),
1033                )
1034            }
1035    }
1036
1037    /// Every present leaf PTE encountered by the drop walk contains a canonical
1038    /// raw item for the node's paging level.
1039    pub open spec fn walk_items_well_formed_from_view(
1040        self,
1041        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
1042        view: crate::specs::mm::virt_mem::MemView,
1043    ) -> bool {
1044        forall|c: usize|
1045            #![trigger Self::walk_pte_at_view(view, c)]
1046            reader.cursor.vaddr <= c && c + core::mem::size_of::<C::E>() <= reader.cursor.vaddr
1047                + reader.remain_spec() && (c - reader.cursor.vaddr) % core::mem::size_of::<
1048                C::E,
1049            >() as int == 0 ==> {
1050                let pte = Self::walk_pte_at_view(view, c);
1051                pte.is_present() && pte.is_last(self.level) ==> C::raw_item_well_formed(
1052                    pte.paddr(),
1053                    self.level,
1054                    pte.prop(),
1055                )
1056            }
1057    }
1058
1059    /// Caller-side uniqueness obligation: distinct cursor positions with
1060    /// present non-last PTEs (in `view`) map to distinct paddrs.
1061    pub open spec fn walk_uniqueness_from_view(
1062        self,
1063        reader: crate::mm::VmReader<'_, crate::mm::Infallible>,
1064        view: crate::specs::mm::virt_mem::MemView,
1065    ) -> bool {
1066        forall|c1: usize, c2: usize|
1067            #![trigger Self::walk_pte_at_view(view, c1), Self::walk_pte_at_view(view, c2)]
1068            reader.cursor.vaddr <= c1 && c1 + core::mem::size_of::<C::E>() <= reader.cursor.vaddr
1069                + reader.remain_spec() && (c1 - reader.cursor.vaddr) % core::mem::size_of::<
1070                C::E,
1071            >() as int == 0 && reader.cursor.vaddr <= c2 && c2 + core::mem::size_of::<C::E>()
1072                <= reader.cursor.vaddr + reader.remain_spec() && (c2 - reader.cursor.vaddr)
1073                % core::mem::size_of::<C::E>() as int == 0 && c1 != c2 ==> {
1074                let pte1 = Self::walk_pte_at_view(view, c1);
1075                let pte2 = Self::walk_pte_at_view(view, c2);
1076                pte1.is_present() && !pte1.is_last(self.level) && pte2.is_present()
1077                    && !pte2.is_last(self.level) ==> pte1.paddr() != pte2.paddr()
1078            }
1079    }
1080
1081    /// Caller-side shape obligation: every paddr in `child_perms.dom()`
1082    /// has a slot perm matching the shape `from_raw` + `VerifiedDrop::drop`
1083    /// expect (init, alignment, refcount within bounds, last-reference
1084    /// shape when refcount == 1).
1085    pub open spec fn child_perms_embedding(
1086        regions: crate::specs::mm::frame::meta_region_owners::MetaRegionOwners,
1087        excluded: vstd::set::Set<int>,
1088    ) -> bool {
1089        forall|paddr: crate::mm::Paddr|
1090            #![trigger regions.slot_owners[frame_to_index(paddr)]]
1091            regions.slots.dom().contains(frame_to_index(paddr)) && !excluded.contains(
1092                frame_to_index(paddr),
1093            ) ==> {
1094                let idx = frame_to_index(paddr);
1095                let so = regions.slot_owners[idx];
1096                &&& <Frame<Self>>::from_raw_requires_safety(
1097                    regions,
1098                    paddr,
1099                )
1100                // Borrow-protocol transition: `raw_count` is dormant.
1101                &&& so.inner_perms.ref_count.value() > 0
1102                &&& so.inner_perms.ref_count.value() != REF_COUNT_UNUSED
1103                &&& so.inner_perms.ref_count.value() <= REF_COUNT_MAX
1104                &&& so.inner_perms.ref_count.value() == 1 ==> {
1105                    &&& so.inner_perms.storage.is_init()
1106                    &&& so.inner_perms.in_list.value() == 0
1107                    &&& so.paths_in_pt.is_empty()
1108                }
1109                // Borrow-protocol redesign: in steady state between
1110                // `into_pte`'s consume and `on_drop`'s `from_raw`-mint,
1111                // the per-child `frame_obligations` count is 0.
1112
1113            }
1114    }
1115}
1116
1117} // verus!