Skip to main content

ostd/specs/mm/page_table/node/
entry.rs

1use vstd::prelude::*;
2use vstd_extra::{ghost_tree::*, ownership::*};
3
4use crate::specs::{
5    arch::NR_ENTRIES,
6    mm::{
7        frame::{mapping::frame_to_index, meta_region_owners::MetaRegionOwners},
8        page_table::*,
9    },
10};
11
12use crate::mm::{frame::meta::REF_COUNT_UNUSED, page_table::*};
13
14verus! {
15
16impl<'a, 'rcu, C: PageTableConfig> Entry<'a, 'rcu, C> {
17    pub open spec fn invariants(self, owner: EntryOwner<C>, regions: MetaRegionOwners) -> bool {
18        &&& owner.inv()
19        &&& regions.inv()
20        &&& self.wf(owner)
21        &&& owner.metaregion_sound(regions)
22    }
23
24    pub open spec fn node_matching(
25        self,
26        owner: EntryOwner<C>,
27        parent_owner: NodeOwner<C>,
28        guard: PageTableGuard<'rcu, C>,
29    ) -> bool {
30        &&& parent_owner.level() == owner.parent_level
31        &&& parent_owner.inv()
32        &&& parent_owner.relate_guard(guard)
33        &&& owner.match_pte(parent_owner.children_perm.value()[self.idx as int], owner.parent_level)
34    }
35
36    pub open spec fn metaregion_sound_preserved(
37        regions0: MetaRegionOwners,
38        regions1: MetaRegionOwners,
39    ) -> bool {
40        OwnerSubtree::implies(
41            |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| entry.metaregion_sound(regions0),
42            |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| entry.metaregion_sound(regions1),
43        )
44    }
45
46    pub open spec fn replace_nonpanic_condition(
47        parent_owner: NodeOwner<C>,
48        new_owner: EntryOwner<C>,
49    ) -> bool {
50        if new_owner.is_node() {
51            parent_owner.level() - 1 == new_owner.node().level()
52        } else if new_owner.is_frame() {
53            parent_owner.level() == new_owner.parent_level
54        } else {
55            true
56        }
57    }
58
59    pub open spec fn metaregion_sound_neq_preserved(
60        old_entry_owner: EntryOwner<C>,
61        new_entry_owner: EntryOwner<C>,
62        regions0: MetaRegionOwners,
63        regions1: MetaRegionOwners,
64    ) -> bool {
65        OwnerSubtree::implies(
66            |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
67                entry.meta_slot_paddr_neq(old_entry_owner) && entry.meta_slot_paddr_neq(
68                    new_entry_owner,
69                ) && entry.metaregion_sound(regions0),
70            |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| entry.metaregion_sound(regions1),
71        )
72    }
73
74    /// When the new child is NOT a node, `into_pte` doesn't modify `raw_count`.
75    /// Only `paths_in_pt` changes at `new_idx`, which `metaregion_sound` doesn't inspect.
76    /// So entries with `paddr_neq(old_child)` preserve `metaregion_sound` — no
77    /// `paddr_neq(new_child)` needed.
78    pub open spec fn metaregion_sound_neq_old_preserved(
79        old_entry_owner: EntryOwner<C>,
80        regions0: MetaRegionOwners,
81        regions1: MetaRegionOwners,
82    ) -> bool {
83        OwnerSubtree::implies(
84            |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
85                entry.meta_slot_paddr_neq(old_entry_owner) && entry.metaregion_sound(regions0),
86            |entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>| entry.metaregion_sound(regions1),
87        )
88    }
89
90    /// When `alloc_if_none` allocates a new node from an absent slot, all existing entries'
91    /// `metaregion_sound` is preserved in the new regions.
92    ///
93    /// Justification: `metaregion_sound_neq_preserved` gives preservation for entries whose paddr
94    /// differs from both old_child and new_child. Old_child is absent (paddr_neq trivially true).
95    pub proof fn alloc_if_none_metaregion_sound_preserved(
96        old_child: EntryOwner<C>,
97        new_child: EntryOwner<C>,
98        regions0: MetaRegionOwners,
99        regions1: MetaRegionOwners,
100    )
101        requires
102            old_child.is_absent(),
103            old_child.inv(),
104            new_child.is_node(),
105            regions0.inv(),
106            regions0.contains(frame_to_index(new_child.meta_slot_paddr()->0)),
107            regions0.slot_owner(new_child.meta_slot_paddr()->0).ref_count() == REF_COUNT_UNUSED,
108            // Allocator-pool / MMIO disjointness: the freshly-allocated node's
109            // paddr is non-MMIO. Rules out an MMIO-frame entry sitting at the
110            // same idx as the new node (delivered by `PageTableNode::alloc`).
111            !crate::specs::mm::frame::meta_owners::is_mmio_paddr(new_child.meta_slot_paddr()->0),
112            Self::metaregion_sound_neq_preserved(old_child, new_child, regions0, regions1),
113        ensures
114            Self::metaregion_sound_preserved(regions0, regions1),
115    {
116        broadcast use crate::specs::mm::frame::meta_owners::axiom_mmio_usage_iff_mmio_paddr;
117
118        let new_idx = frame_to_index(new_child.meta_slot_paddr().unwrap());
119        let f = PageTableOwner::<C>::metaregion_sound_pred(regions0);
120        let g = PageTableOwner::<C>::metaregion_sound_pred(regions1);
121
122        assert(old_child.meta_slot_paddr() is None);
123
124        assert forall|entry: EntryOwner<C>, path: TreePath<NR_ENTRIES>|
125            entry.inv() && f(entry, path) implies #[trigger] g(entry, path) by {
126            if entry.meta_slot_paddr() is Some && entry.is_node() {
127                EntryOwner::<C>::lemma_active_entry_not_in_free_pool(entry, regions0, new_idx);
128            }
129            // Frame entries colliding at new_idx are ruled out: either the
130            // slot's `usage != MMIO` (then `metaregion_sound` requires
131            // `rc != UNUSED`, contradicting the precondition), or the slot's
132            // `usage == MMIO` (then by axiom the paddr is MMIO, contradicting
133            // the allocator's non-MMIO guarantee).
134
135        };
136    }
137
138    pub open spec fn path_tracked_pred_preserved(
139        regions0: MetaRegionOwners,
140        regions1: MetaRegionOwners,
141    ) -> bool {
142        OwnerSubtree::implies(
143            PageTableOwner::<C>::path_tracked_pred(regions0),
144            PageTableOwner::<C>::path_tracked_pred(regions1),
145        )
146    }
147
148    pub open spec fn new_owner_compatible(
149        self,
150        new_child: Child<C>,
151        old_owner: EntryOwner<C>,
152        new_owner: EntryOwner<C>,
153        regions: MetaRegionOwners,
154    ) -> bool {
155        &&& old_owner.path == new_owner.path
156        &&& old_owner.parent_level == new_owner.parent_level
157        &&& new_owner.is_node() ==> {
158            &&& regions.contains(frame_to_index(new_owner.meta_slot_paddr()->0))
159            &&& regions.slot_owner(new_owner.meta_slot_paddr()->0).ref_count() != REF_COUNT_UNUSED
160        }
161    }
162
163    pub open spec fn parent_perms_preserved(
164        self,
165        parent_owner0: NodeOwner<C>,
166        parent_owner1: NodeOwner<C>,
167    ) -> bool {
168        &&& forall|i: int|
169            0 <= i < NR_ENTRIES ==> i != self.idx ==> parent_owner0.children_perm.value()[i]
170                == parent_owner1.children_perm.value()[i]
171        &&& parent_owner1.slot_index == parent_owner0.slot_index
172        &&& parent_owner1.level() == parent_owner0.level()
173        &&& parent_owner1.tree_level == parent_owner0.tree_level
174        &&& parent_owner1.meta_own.nr_children.id() == parent_owner0.meta_own.nr_children.id()
175        &&& parent_owner1.meta_own.stray == parent_owner0.meta_own.stray
176    }
177}
178
179} // verus!