Skip to main content

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

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