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}