Skip to main content

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

1use vstd::prelude::*;
2
3use vstd::{
4    cell::{self, pcell_maybe_uninit},
5    simple_pptr::*,
6};
7use vstd_extra::{array_ptr, ownership::*};
8
9use crate::specs::{
10    arch::{MAX_PADDR, NR_ENTRIES, NR_LEVELS},
11    mm::{
12        frame::{
13            mapping::{index_to_meta, max_meta_slots, meta_to_index},
14            meta_owners::*,
15            meta_region_owners::MetaRegionOwners,
16        },
17        page_table::owners::INC_LEVELS,
18    },
19};
20
21use crate::arch::mm::PagingConsts;
22use crate::mm::{
23    Paddr, PagingConstsTrait, PagingLevel, Vaddr,
24    frame::meta::{META_SLOT_SIZE, MetaSlot, mapping::meta_to_frame},
25    kspace::{FRAME_METADATA_RANGE, LINEAR_MAPPING_BASE_VADDR, VMALLOC_BASE_VADDR},
26    paddr_to_vaddr,
27    page_table::{PageTableGuard, *},
28};
29
30verus! {
31
32// ─── Present-PTE counting ──────────────────────────────────────────────────────
33// The intended meaning of `nr_children`: the number of *present* PTEs in the
34// node's `children_perm` array. `metaregion_sound_node` ties `nr_children` to
35// `count_present(children_perm.value())` (a settled-node invariant), which lets
36// the `nr_children_*_slot_bound` boundary facts be proven rather than axiomatized.
37/// Number of present PTEs among the first `n` entries of `s`.
38pub open spec fn count_present_upto<E: PageTableEntryTrait>(s: Seq<E>, n: int) -> int
39    decreases n,
40{
41    if n <= 0 {
42        0
43    } else {
44        count_present_upto(s, n - 1) + if s[n - 1].is_present() {
45            1int
46        } else {
47            0int
48        }
49    }
50}
51
52/// Number of present PTEs in `s`.
53pub open spec fn count_present<E: PageTableEntryTrait>(s: Seq<E>) -> int {
54    count_present_upto(s, s.len() as int)
55}
56
57/// `count_present_upto` is between `0` and `n`.
58pub proof fn lemma_count_present_upto_bound<E: PageTableEntryTrait>(s: Seq<E>, n: int)
59    requires
60        0 <= n,
61    ensures
62        0 <= count_present_upto(s, n) <= n,
63    decreases n,
64{
65    if n > 0 {
66        lemma_count_present_upto_bound(s, n - 1);
67    }
68}
69
70/// An absent slot below `n` makes the count strictly less than `n`.
71pub proof fn lemma_count_present_upto_absent<E: PageTableEntryTrait>(s: Seq<E>, n: int, idx: int)
72    requires
73        0 <= idx < n,
74        !s[idx].is_present(),
75    ensures
76        count_present_upto(s, n) < n,
77    decreases n,
78{
79    lemma_count_present_upto_bound(s, n - 1);
80    if idx < n - 1 {
81        lemma_count_present_upto_absent(s, n - 1, idx);
82    }
83}
84
85/// A present slot below `n` makes the count at least `1`.
86pub proof fn lemma_count_present_upto_present<E: PageTableEntryTrait>(s: Seq<E>, n: int, idx: int)
87    requires
88        0 <= idx < n,
89        s[idx].is_present(),
90    ensures
91        count_present_upto(s, n) >= 1,
92    decreases n,
93{
94    lemma_count_present_upto_bound(s, n - 1);
95    if idx < n - 1 {
96        lemma_count_present_upto_present(s, n - 1, idx);
97    }
98}
99
100/// Updating slot `idx` (with `idx < n`) shifts the count by the change in that
101/// slot's present-indicator.
102pub proof fn lemma_count_present_upto_update<E: PageTableEntryTrait>(
103    s: Seq<E>,
104    n: int,
105    idx: int,
106    pte: E,
107)
108    requires
109        0 <= idx < n <= s.len(),
110    ensures
111        count_present_upto(s.update(idx, pte), n) == count_present_upto(s, n) - (
112        if s[idx].is_present() {
113            1int
114        } else {
115            0int
116        }) + (if pte.is_present() {
117            1int
118        } else {
119            0int
120        }),
121    decreases n,
122{
123    let s2 = s.update(idx, pte);
124    if n - 1 == idx {
125        // The first `idx` entries are unchanged.
126        assert(count_present_upto(s2, n - 1) == count_present_upto(s, n - 1)) by {
127            lemma_count_present_upto_unchanged(s, s2, n - 1, idx);
128        }
129    } else {
130        lemma_count_present_upto_update(s, n - 1, idx, pte);
131    }
132}
133
134/// A zero present-count up to `n` means every slot below `n` is absent.
135pub proof fn lemma_count_present_upto_zero_all_absent<E: PageTableEntryTrait>(s: Seq<E>, n: int)
136    requires
137        count_present_upto(s, n) == 0,
138        0 <= n,
139    ensures
140        forall|k: int| 0 <= k < n ==> !#[trigger] s[k].is_present(),
141    decreases n,
142{
143    if n > 0 {
144        lemma_count_present_upto_bound(s, n - 1);
145        lemma_count_present_upto_zero_all_absent(s, n - 1);
146    }
147}
148
149/// If two sequences agree on `[0, n)`, their counts up to `n` are equal.
150pub proof fn lemma_count_present_upto_unchanged<E: PageTableEntryTrait>(
151    s: Seq<E>,
152    s2: Seq<E>,
153    n: int,
154    idx: int,
155)
156    requires
157        0 <= n <= idx,
158        forall|k: int| 0 <= k < n ==> s[k] == s2[k],
159    ensures
160        count_present_upto(s2, n) == count_present_upto(s, n),
161    decreases n,
162{
163    if n > 0 {
164        lemma_count_present_upto_unchanged(s, s2, n - 1, idx);
165    }
166}
167
168pub tracked struct PageMetaOwner {
169    pub nr_children: pcell_maybe_uninit::PointsTo<u16>,
170    pub stray: pcell_maybe_uninit::PointsTo<bool>,
171}
172
173impl Inv for PageMetaOwner {
174    open spec fn inv(self) -> bool {
175        &&& self.nr_children.is_init()
176        &&& 0 <= self.nr_children.value() <= NR_ENTRIES
177        &&& self.stray.is_init()
178    }
179}
180
181pub ghost struct PageMetaModel {
182    pub nr_children: u16,
183    pub stray: bool,
184}
185
186impl Inv for PageMetaModel {
187    open spec fn inv(self) -> bool {
188        true
189    }
190}
191
192impl View for PageMetaOwner {
193    type V = PageMetaModel;
194
195    open spec fn view(&self) -> <Self as View>::V {
196        PageMetaModel { nr_children: self.nr_children.value(), stray: self.stray.value() }
197    }
198}
199
200impl InvView for PageMetaOwner {
201    proof fn view_preserves_inv(self) {
202    }
203}
204
205impl<C: PageTableConfig> OwnerOf for PageTablePageMeta<C> {
206    type Owner = PageMetaOwner;
207
208    open spec fn wf(self, owner: Self::Owner) -> bool {
209        &&& self.nr_children.id() == owner.nr_children.id()
210        &&& self.stray.id() == owner.stray.id()
211        &&& 0 <= owner.nr_children.value() <= NR_ENTRIES
212    }
213}
214
215/// # Verification Design
216/// The owner type for a page table node. It contains:
217/// - `meta_own`, a `PageMetaOwner`, which holds the permissions for node-specific
218///   metadata fields, `nr_children` and `stray`
219/// - `children_perm` is an array permission for the underlying frame in which the node
220///   is allocated, interpreted as an array of `NR_ENTRIES` page table entries
221/// - `slot_index` identifies the underlying frame's index in the metadata region
222/// - Each node is a page table with a level between 1 and 4 (on x86); `level` tracks
223///   the level of this node.
224/// - `tree_level` is the level field of the `ghost_tree::TreeNode` that carries this object.
225///   Carried here for convenience, though it can be computed from `level`.
226pub tracked struct NodeOwner<C: PageTableConfig> {
227    pub meta_own: PageMetaOwner,
228    pub children_perm: array_ptr::PointsTo<C::E, NR_ENTRIES>,
229    pub ghost level: PagingLevel,
230    pub ghost tree_level: int,
231    pub ghost slot_index: int,
232}
233
234impl<C: PageTableConfig> Inv for NodeOwner<C> {
235    open spec fn inv(self) -> bool {
236        &&& self.meta_own.inv()
237        &&& 0 <= self.meta_own.nr_children.value() <= NR_ENTRIES
238        &&& 1 <= self.level <= NR_LEVELS
239        &&& self.children_perm.wf()
240        &&& self.children_perm.is_init_all()
241        &&& self.children_perm.addr() == paddr_to_vaddr(
242            meta_to_frame(index_to_meta(self.slot_index)),
243        )
244        &&& self.tree_level == INC_LEVELS - self.level - 1
245        &&& 0 <= self.slot_index < max_meta_slots()
246        &&& FRAME_METADATA_RANGE.start <= index_to_meta(self.slot_index) < FRAME_METADATA_RANGE.end
247        &&& index_to_meta(self.slot_index) % META_SLOT_SIZE == 0
248        &&& meta_to_frame(index_to_meta(self.slot_index)) < VMALLOC_BASE_VADDR
249            - LINEAR_MAPPING_BASE_VADDR
250        &&& meta_to_frame(index_to_meta(self.slot_index)) < MAX_PADDR
251        &&& meta_to_frame(index_to_meta(self.slot_index)) == self.children_perm.addr()
252        &&& self.slot_index == meta_to_index(index_to_meta(self.slot_index))
253    }
254}
255
256impl<C: PageTableConfig> NodeOwner<C> {
257    /// The meta address of this node's slot, computed from `slot_index`.
258    pub open spec fn meta_vaddr(self) -> Vaddr {
259        index_to_meta(self.slot_index)
260    }
261
262    pub open spec fn meta_wf(self, regions: MetaRegionOwners) -> bool {
263        typed_meta_wf::<PageTablePageMeta<C>>(
264            *regions.slots[self.slot_index],
265            regions.slot_owners[self.slot_index].metadata_perm,
266            (),
267        )
268    }
269
270    pub open spec fn meta_value(self, regions: MetaRegionOwners) -> PageTablePageMeta<C> {
271        typed_meta_value::<PageTablePageMeta<C>>(
272            regions.slot_owners[self.slot_index].metadata_perm,
273            (),
274        )
275    }
276
277    /// Regions-tied invariants that used to live in `NodeOwner::inv()` via
278    /// the now-removed `meta_perm` field. Establishes the bridge between
279    /// the NodeOwner and the slot perm parked in regions.
280    pub open spec fn metaregion_sound_node(self, regions: MetaRegionOwners) -> bool {
281        let idx = self.slot_index;
282        &&& regions.contains(idx)
283        &&& self.meta_wf(regions)
284        &&& self.meta_value(regions).wf(self.meta_own)
285        &&& self.level == self.meta_value(regions).level
286        &&& self.meta_own.nr_children.id() == self.meta_value(
287            regions,
288        ).nr_children.id()
289        // A page-table node's slot is tracked with `PageTable` usage (set at
290        // allocation via `get_node_from_unused_spec`). This discriminates node
291        // slots from data-frame slots (`Frame`/MMIO) by `usage` alone, so a
292        // freshly-allocated node (whose slot was `UNUSED`) can't collide with an
293        // existing live node — giving `alloc_if_none`/`split` the parent≠child
294        // slot distinctness without a PointsTo-linearity axiom.
295        &&& regions.slot_owners[self.slot_index].usage is PageTable
296        // `nr_children` counts the present PTEs in `children_perm`. A settled-node
297        // invariant (it is momentarily broken mid-`replace`/`alloc_if_none`, between
298        // the PTE write and the counter update, which is why it lives here rather
299        // than in `inv()`). Lets `nr_children_*_slot_bound` be proven, not axiomatized.
300        &&& self.count_consistent()
301    }
302
303    /// `nr_children` equals the number of present PTEs in `children_perm`.
304    /// Held by a settled node (see `metaregion_sound_node`'s use site).
305    pub open spec fn count_consistent(self) -> bool {
306        self.meta_own.nr_children.value() == count_present(self.children_perm.value())
307    }
308}
309
310impl<C: PageTableConfig> NodeOwner<C> {
311    /// If a slot in `children_perm` holds a non-present PTE, then
312    /// `nr_children < NR_ENTRIES`. Proven (no longer axiomatized) from the
313    /// `count_consistent` invariant: `nr_children` counts present PTEs, and an
314    /// absent slot means not all `NR_ENTRIES` slots are present.
315    pub proof fn nr_children_absent_slot_bound(self, idx: usize)
316        requires
317            self.inv(),
318            self.count_consistent(),
319            self.children_perm.value().len() == NR_ENTRIES,
320            idx < NR_ENTRIES,
321            !self.children_perm.value()[idx as int].is_present(),
322        ensures
323            self.meta_own.nr_children.value() < NR_ENTRIES,
324    {
325        lemma_count_present_upto_absent(self.children_perm.value(), NR_ENTRIES as int, idx as int);
326    }
327
328    /// If a slot in `children_perm` holds a present PTE, then `nr_children > 0`.
329    /// Dual of [`Self::nr_children_absent_slot_bound`]; proven from
330    /// `count_consistent`.
331    pub proof fn nr_children_present_slot_bound(self, idx: usize)
332        requires
333            self.inv(),
334            self.count_consistent(),
335            self.children_perm.value().len() == NR_ENTRIES,
336            idx < NR_ENTRIES,
337            self.children_perm.value()[idx as int].is_present(),
338        ensures
339            self.meta_own.nr_children.value() > 0,
340    {
341        lemma_count_present_upto_present(self.children_perm.value(), NR_ENTRIES as int, idx as int);
342    }
343}
344
345impl<'rcu, C: PageTableConfig> NodeOwner<C> {
346    pub open spec fn relate_guard(self, guard: PageTableGuard<'rcu, C>) -> bool {
347        &&& guard.inner.inner@.ptr.addr() == self.meta_vaddr()
348        &&& guard.inner.inner@.wf(self)
349    }
350}
351
352pub ghost struct NodeModel<C: PageTableConfig> {
353    pub level: PagingLevel,
354    pub _phantom: core::marker::PhantomData<C>,
355}
356
357impl<C: PageTableConfig> Inv for NodeModel<C> {
358    open spec fn inv(self) -> bool {
359        true
360    }
361}
362
363impl<C: PageTableConfig> View for NodeOwner<C> {
364    type V = NodeModel<C>;
365
366    open spec fn view(&self) -> <Self as View>::V {
367        NodeModel { level: self.level, _phantom: core::marker::PhantomData }
368    }
369}
370
371impl<C: PageTableConfig> InvView for NodeOwner<C> {
372    proof fn view_preserves_inv(self) {
373    }
374}
375
376impl<C: PageTableConfig> OwnerOf for PageTableNode<C> {
377    type Owner = NodeOwner<C>;
378
379    open spec fn wf(self, owner: Self::Owner) -> bool {
380        &&& self.ptr.addr() == owner.meta_vaddr()
381    }
382}
383
384impl<C: PageTableConfig> PageTableNode<C> {
385    pub open spec fn invariants(self, owner: NodeOwner<C>) -> bool {
386        &&& owner.inv()
387        &&& self.wf(
388            owner,
389        )
390        //        &&& owner.meta_perm.wf(owner.meta_perm.storage_perm())
391        //        &&& owner.meta_perm.addr() == self.ptr.addr()
392        //        &&& owner.meta_perm.addr() == self.ptr.addr()
393
394    }
395}
396
397} // verus!