Skip to main content

virtio/
in_order.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! In-order completion discipline layered on top of a [`VirtioQueue`].
5//!
6//! Some devices (notably virtio-net) require that the used ring be published
7//! strictly in the order descriptors were consumed from the available ring,
8//! even though work may complete out of order (e.g. a network backend that
9//! finishes packets in a different order, or descriptors that are dropped
10//! early). This makes the outstanding set exactly the contiguous
11//! `[used_index, avail_index)` range, which enables a simple cursor-based
12//! save/restore.
13//!
14//! This is deliberately a *side* helper whose methods borrow a
15//! [`VirtioQueue`], rather than being baked into the queue itself. Devices that
16//! do not need in-order completion (virtio-blk, virtio-vsock, ...) never
17//! instantiate it and pay nothing — the queue's hot path is untouched. It uses
18//! only the queue's public API, mirroring QEMU's
19//! `virtqueue_ordered_fill`/`virtqueue_ordered_flush` (`used_elems`): consumed
20//! descriptors are recorded in consumption order, completions fill their slot
21//! (possibly out of order), and only the contiguous filled prefix is published
22//! to the used ring, in order.
23
24use crate::VirtioQueue;
25use crate::VirtioQueueCallbackWork;
26use crate::queue::QueueCompletion;
27use inspect::Inspect;
28use std::collections::VecDeque;
29use std::io::Error;
30
31/// Enforces in-order used-ring publication for a single [`VirtioQueue`].
32///
33/// Consume descriptors via [`try_next`](Self::try_next) and complete them via
34/// [`complete`](Self::complete) instead of calling the queue's methods
35/// directly. Completions may be issued in any order; the used ring is always
36/// published in consumption (available) order.
37#[derive(Inspect)]
38pub struct InOrderCompletion {
39    /// Descriptor indices of outstanding descriptors, in consumption order.
40    /// The front is the next descriptor eligible to be published.
41    #[inspect(with = "VecDeque::len")]
42    order: VecDeque<u16>,
43    /// Completed-but-not-yet-published work, indexed by descriptor index.
44    /// Bounded by the queue size, so a descriptor index (always
45    /// `< queue_size`) is a unique key for the outstanding set: a repeated
46    /// index while still outstanding is a guest protocol violation that the
47    /// device detects and treats as fatal.
48    #[inspect(skip)]
49    completed: Vec<Option<Completed>>,
50}
51
52struct Completed {
53    completion: QueueCompletion,
54    bytes_written: u32,
55}
56
57impl InOrderCompletion {
58    /// Creates an in-order completion tracker for a queue of the given size.
59    pub fn new(queue_size: u16) -> Self {
60        Self {
61            order: VecDeque::with_capacity(queue_size as usize),
62            completed: (0..queue_size).map(|_| None).collect(),
63        }
64    }
65
66    /// Consumes the next available descriptor from `queue`, recording it for
67    /// in-order completion. Returns `Ok(None)` if no work is available.
68    ///
69    /// Use this in place of [`VirtioQueue::try_next`].
70    pub fn try_next(
71        &mut self,
72        queue: &mut VirtioQueue,
73    ) -> Result<Option<VirtioQueueCallbackWork>, Error> {
74        let work = queue.try_next()?;
75        if let Some(work) = &work {
76            let idx = work.descriptor_index() as usize;
77            // The queue validates the descriptor index against the ring size
78            // when it reads the descriptor, so an out-of-range index fails
79            // above rather than reaching here. Assert the invariant so a
80            // violation fails fast at entry instead of corrupting later state.
81            assert!(
82                idx < self.completed.len(),
83                "descriptor index {idx} exceeds queue size {}",
84                self.completed.len()
85            );
86            self.order.push_back(work.descriptor_index());
87        }
88        Ok(work)
89    }
90
91    /// Records a completion for the descriptor identified by `completion` and
92    /// publishes the contiguous run of completed descriptors at the front of
93    /// the consumption order to the used ring, in order.
94    ///
95    /// `completion` is the lightweight token obtained from
96    /// [`VirtioQueueCallbackWork::into_completion`], so callers that buffer
97    /// outstanding descriptors can retain only the token rather than the full
98    /// work (and its payload).
99    ///
100    /// Use this in place of [`VirtioQueue::complete_prepared`].
101    ///
102    /// Each `completion` must be for a descriptor still outstanding from
103    /// [`try_next`](Self::try_next), completed once. Some violations panic (an
104    /// out-of-range index, or a collision with a still-buffered slot); a
105    /// completion for a no-longer-outstanding descriptor is not caught and
106    /// would strand an entry in `completed`, eventually stalling publication.
107    pub fn complete(
108        &mut self,
109        queue: &mut VirtioQueue,
110        completion: QueueCompletion,
111        bytes_written: u32,
112    ) {
113        let idx = completion.descriptor_index();
114
115        // Fast path: the completing descriptor is the one at the front of the
116        // consumption order — the overwhelmingly common case, since an ordered
117        // backend completes in the order buffers were posted. Publish it
118        // directly, with no `completed` slot write/read. (When `idx` is at the
119        // front, its slot is guaranteed empty: any completed front descriptor is
120        // published and popped immediately, so the front is always outstanding
121        // on entry.)
122        if self.order.front() == Some(&idx) {
123            self.order.pop_front();
124            queue.complete_prepared(completion, bytes_written);
125            if !self.order.is_empty() {
126                self.drain_completed_prefix(queue);
127            }
128            return;
129        }
130
131        // Slow path: an out-of-order completion. Buffer it until the
132        // descriptors ahead of it have been published. Because `idx` is not at
133        // the front, nothing new can be published yet.
134        let slot = self
135            .completed
136            .get_mut(idx as usize)
137            .expect("completed descriptor index must be within the queue size");
138        assert!(
139            slot.is_none(),
140            "descriptor {idx} completed more than once while outstanding"
141        );
142        *slot = Some(Completed {
143            completion,
144            bytes_written,
145        });
146    }
147
148    /// Publishes the longest run of already-completed descriptors at the front
149    /// of the consumption order, in order.
150    fn drain_completed_prefix(&mut self, queue: &mut VirtioQueue) {
151        while let Some(&front) = self.order.front() {
152            let Some(Completed {
153                completion,
154                bytes_written,
155            }) = self.completed[front as usize].take()
156            else {
157                break;
158            };
159            self.order.pop_front();
160            queue.complete_prepared(completion, bytes_written);
161        }
162    }
163
164    /// Returns the number of descriptors consumed but not yet published.
165    pub fn outstanding(&self) -> usize {
166        self.order.len()
167    }
168}