@@ -565,3 +565,139 @@ fn verify_add_used() {
565565 kani:: cover!( ) ;
566566 }
567567}
568+
569+ /// This proof checks that after setting the `next_used` field of the queue
570+ /// using `set_next_used(x)`, reading back the value of `next_used` returns the
571+ /// same value `x`.
572+ #[ kani:: proof]
573+ #[ kani:: unwind( 0 ) ]
574+ fn verify_used_ring_avail_event ( ) {
575+ let ProofContext {
576+ mut queue,
577+ memory : _,
578+ } = kani:: any ( ) ;
579+ let x = kani:: any ( ) ;
580+ queue. set_next_used ( x) ;
581+ assert_eq ! ( x, queue. next_used. 0 ) ;
582+ }
583+
584+ /// # Specification (VirtIO 1.3, Section 2.7.6.1: "Driver Requirements: The Virtqueue Available Ring")
585+ ///
586+ /// This proof checks that:
587+ /// - If there are pending entries in the avail ring (avail_idx != next_avail),
588+ /// `enable_notification` returns true.
589+ /// - If there are no pending entries (avail_idx == next_avail), it returns false.
590+ /// This matches the monotonicity property of the avail ring in VirtIO 1.3 Section 2.7.6.1.
591+ #[ kani:: proof]
592+ #[ kani:: unwind( 0 ) ]
593+ fn verify_enable_driver_to_device_notification ( ) {
594+ let ProofContext { mut queue, memory } = kani:: any ( ) ;
595+
596+ // The enable_notification method sets notification to true and returns
597+ // - true, if there are pending entries in the `idx` field of the
598+ // avail ring
599+ // - false, if there are no pending entries in the `idx` field of the
600+ // avail ring The check for pending entries is done by comparing the
601+ // current `avail_idx` with the `next_avail` field of the queue. If they
602+ // are different, there are pending entries, otherwise there are no
603+ // pending entries. The equality check is a consequence of the
604+ // monotonicity property of `idx` (2.7.6.1) that it cannot be decreased
605+ // by the driver.
606+ if queue. enable_notification ( & memory) . unwrap ( ) {
607+ assert_ne ! (
608+ queue. avail_idx( & memory, Ordering :: Relaxed ) . unwrap( ) ,
609+ queue. next_avail
610+ ) ;
611+ } else {
612+ assert_eq ! (
613+ queue. avail_idx( & memory, Ordering :: Relaxed ) . unwrap( ) ,
614+ queue. next_avail
615+ ) ;
616+ }
617+ }
618+
619+ // Helper method that reads `val` from the `avail_event` field of the used ring, using
620+ // the provided ordering. Takes used_ring address and queue size directly.
621+ fn get_avail_event < M : GuestMemory > (
622+ used_ring_addr : GuestAddress ,
623+ queue_size : u16 ,
624+ mem : & M ,
625+ order : Ordering ,
626+ ) -> Result < u16 , Error > {
627+ // This can not overflow an u64 since it is working with relatively small numbers compar
628+ // to u64::MAX.
629+ let avail_event_offset =
630+ VIRTQ_USED_RING_HEADER_SIZE + VIRTQ_USED_ELEMENT_SIZE * u64:: from ( queue_size) ;
631+ let addr = used_ring_addr
632+ . checked_add ( avail_event_offset)
633+ . ok_or ( Error :: AddressOverflow ) ?;
634+
635+ mem. load :: < u16 > ( addr, order) . map_err ( Error :: GuestMemory )
636+ }
637+
638+ // Get the value of the `flags` field of the used ring, applying the specified ordering.
639+ fn get_used_flags < M : GuestMemory > ( queue : & Queue , mem : & M , order : Ordering ) -> Result < u16 , Error > {
640+ mem. load :: < u16 > ( queue. used_ring , order)
641+ . map ( u16:: from_le)
642+ . map_err ( Error :: GuestMemory )
643+ }
644+ /// # Specification (VirtIO 1.3, Section 2.7.7: "Used Buffer Notification Suppression")
645+ ///
646+ /// This proof checks:
647+ /// - If event_idx is not enabled, `set_notification(false)` sets used.flags to 1 (NO_NOTIFY).
648+ /// - If event_idx is enabled, the call is a no-op.
649+ #[ kani:: proof]
650+ #[ kani:: unwind( 0 ) ]
651+ fn verify_set_notification_true ( ) {
652+ let ProofContext { mut queue, memory } = kani:: any ( ) ;
653+ if queue
654+ . set_notification ( & memory, true /* enable notification */ )
655+ . is_ok ( )
656+ {
657+ if queue. event_idx_enabled {
658+ // Since VIRTIO_F_EVENT_IDX is negotiated, we make sure that set_notification
659+ // has updated the used.avail_event field with the tail position of
660+ // the avail ring.
661+ let used_ring_addr = queue. used_ring ;
662+ let queue_size = queue. size ( ) ;
663+ kani:: cover!( ) ;
664+ assert_eq ! (
665+ get_avail_event( used_ring_addr, queue_size, & memory, Ordering :: Relaxed ) . unwrap( ) ,
666+ queue. next_avail. 0
667+ ) ;
668+ } else {
669+ // If VIRTIO_F_EVENT_IDX is not negotiated, we make sure that the
670+ // used.flags field is set to 0, meaning that the driver should not
671+ // send notifications to the device.
672+ kani:: cover!( ) ;
673+ assert_eq ! (
674+ get_used_flags( & queue, & memory, Ordering :: Relaxed ) . unwrap( ) ,
675+ 0
676+ ) ;
677+ }
678+ }
679+ }
680+
681+ /// # Specification (VirtIO 1.3, Section 2.7.7: "Used Buffer Notification Suppression")
682+ ///
683+ /// This proof checks:
684+ /// - If event_idx is not enabled, `set_notification(false)` sets used.flags to 1 (NO_NOTIFY).
685+ /// - If event_idx is enabled, the call is a no-op.
686+ #[ kani:: proof]
687+ #[ kani:: unwind( 0 ) ]
688+ fn verify_set_notification_false ( ) {
689+ let ProofContext { mut queue, memory } = kani:: any ( ) ;
690+ let result = queue. set_notification ( & memory, false /* disable notification */ ) ;
691+ if !queue. event_idx_enabled {
692+ // Check for Sec 2.7.10
693+ assert_eq ! (
694+ get_used_flags( & queue, & memory, Ordering :: Relaxed ) . unwrap( ) ,
695+ 1
696+ ) ;
697+ // don't check Ok() result since that is a property of the
698+ // underlying mem system and out of scope. E.g., it is stubbed for
699+ // this proof and we always expect it to succeed.
700+ } else {
701+ assert ! ( result. is_ok( ) ) ;
702+ }
703+ }
0 commit comments