Skip to main content

virtio/queue/
packed.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Virtio packed queue implementation.
5
6use crate::queue::QueueDescriptor;
7use crate::queue::QueueError;
8use crate::queue::QueueParams;
9use crate::queue::descriptor_offset;
10use crate::spec::VirtioDeviceFeatures;
11use crate::spec::queue as spec;
12use crate::spec::queue::DescriptorFlags;
13use guestmem::GuestMemory;
14use inspect::Inspect;
15use spec::EventSuppressionFlags;
16use spec::PackedDescriptor;
17use spec::PackedEventSuppression;
18use std::sync::atomic;
19
20pub struct PackedQueueCompletionContext {
21    buffer_id: u16,
22    descriptor_count: u16,
23}
24
25impl PackedQueueCompletionContext {
26    pub(super) fn new(last_descriptor: &QueueDescriptor, descriptor_count: u16) -> Self {
27        Self {
28            buffer_id: last_descriptor
29                .buffer_id
30                .expect("packed descriptors have buffer id"),
31            descriptor_count,
32        }
33    }
34
35    pub(super) fn descriptor_count(&self) -> u16 {
36        self.descriptor_count
37    }
38}
39
40#[derive(Debug, Inspect)]
41#[inspect(extra = "Self::inspect_extra")]
42pub(crate) struct PackedQueueGetWork {
43    #[inspect(skip)]
44    queue_desc: GuestMemory,
45    #[inspect(skip)]
46    device_event: GuestMemory,
47    queue_size: u16,
48    next_avail_index: u16,
49    wrapped_bit: bool,
50    next_is_available: bool,
51}
52
53impl PackedQueueGetWork {
54    fn inspect_extra(&self, resp: &mut inspect::Response<'_>) {
55        if let Ok(event) = self.device_event.read_plain::<PackedEventSuppression>(0) {
56            resp.field("device_event_flags", event.flags());
57            resp.field("device_event_offset", event.offset());
58            resp.field("device_event_wrap", event.wrap());
59        }
60    }
61
62    pub fn new(
63        _features: VirtioDeviceFeatures,
64        mem: GuestMemory,
65        params: QueueParams,
66        initial_index: u16,
67        initial_wrap: bool,
68    ) -> Result<Self, QueueError> {
69        let queue_desc = mem
70            .subrange(params.desc_addr, descriptor_offset(params.size), true)
71            .map_err(QueueError::Memory)?;
72        let device_event = mem
73            .subrange(
74                params.used_addr,
75                size_of::<PackedEventSuppression>() as u64,
76                true,
77            )
78            .map_err(QueueError::Memory)?;
79        Ok(Self {
80            queue_desc,
81            device_event,
82            queue_size: params.size,
83            next_avail_index: initial_index,
84            wrapped_bit: initial_wrap,
85            next_is_available: false,
86        })
87    }
88
89    /// Return the packed avail state: `index | (wrap_counter << 15)`.
90    pub fn avail_state(&self) -> u16 {
91        self.next_avail_index | (u16::from(self.wrapped_bit) << 15)
92    }
93
94    /// Checks whether a descriptor is available, returning its index.
95    ///
96    /// This is a lightweight check that does not arm kick notification. When
97    /// `None` is returned, the caller must call [`arm_kick`](Self::arm_kick)
98    /// before sleeping to ensure the guest will send a kick when new work
99    /// arrives.
100    pub fn is_available(&mut self) -> Result<Option<u16>, QueueError> {
101        if !self.next_is_available {
102            let flags: DescriptorFlags = self
103                .queue_desc
104                .read_plain(
105                    descriptor_offset(self.next_avail_index)
106                        + std::mem::offset_of!(PackedDescriptor, flags_raw) as u64,
107                )
108                .map_err(QueueError::Memory)?;
109            if flags.available() != self.wrapped_bit || flags.used() == self.wrapped_bit {
110                return Ok(None);
111            }
112            // Ensure subsequent descriptor-field reads cannot be reordered
113            // before the flags read on weakly ordered architectures.
114            atomic::fence(atomic::Ordering::Acquire);
115            self.next_is_available = true;
116        }
117        Ok(Some(self.next_avail_index))
118    }
119
120    /// Arms kick notification so the guest will send a doorbell when new work
121    /// is available. Returns `true` if armed successfully (caller should
122    /// sleep), or `false` if new data arrived during arming (caller should
123    /// retry).
124    pub fn arm_kick(&mut self) -> Result<bool, QueueError> {
125        let enable_event = PackedEventSuppression::new().with_flags(EventSuppressionFlags::Enabled);
126        self.device_event
127            .write_plain(0, &enable_event)
128            .map_err(QueueError::Memory)?;
129        // Ensure the event enable is visible before checking the descriptor.
130        atomic::fence(atomic::Ordering::SeqCst);
131        if self.is_available()?.is_some() {
132            // New data arrived during arming — suppress kicks and report.
133            self.suppress_kicks()?;
134            return Ok(false);
135        }
136        Ok(true)
137    }
138
139    /// Suppress kick notifications from the guest. Call this after finding
140    /// work to avoid unnecessary kicks while processing.
141    pub fn suppress_kicks(&self) -> Result<(), QueueError> {
142        let disable_event =
143            PackedEventSuppression::new().with_flags(EventSuppressionFlags::Disabled);
144        self.device_event
145            .write_plain(0, &disable_event)
146            .map_err(QueueError::Memory)?;
147        Ok(())
148    }
149
150    /// Advances `next_avail_index` by `count` descriptors.
151    pub fn advance(&mut self, count: u16) {
152        // A chain is never longer than the ring, so the cursor wraps at most
153        // once; compare-and-subtract avoids a modulo.
154        let raw = self.next_avail_index + count;
155        self.next_avail_index = if raw >= self.queue_size {
156            self.wrapped_bit = !self.wrapped_bit;
157            raw - self.queue_size
158        } else {
159            raw
160        };
161        self.next_is_available = false;
162    }
163}
164
165#[derive(Debug, Inspect)]
166#[inspect(extra = "Self::inspect_extra")]
167pub(crate) struct PackedQueueCompleteWork {
168    #[inspect(skip)]
169    queue_desc: GuestMemory,
170    #[inspect(skip)]
171    driver_event: GuestMemory,
172    queue_size: u16,
173    next_index: u16,
174    wrapped_bit: bool,
175    use_event_index: bool,
176}
177
178impl PackedQueueCompleteWork {
179    fn inspect_extra(&self, resp: &mut inspect::Response<'_>) {
180        if let Ok(event) = self.driver_event.read_plain::<PackedEventSuppression>(0) {
181            resp.field("driver_event_flags", event.flags());
182            resp.field("driver_event_offset", event.offset());
183            resp.field("driver_event_wrap", event.wrap());
184        }
185    }
186
187    pub fn new(
188        features: VirtioDeviceFeatures,
189        mem: GuestMemory,
190        params: QueueParams,
191        initial_index: u16,
192        initial_wrap: bool,
193    ) -> Result<Self, QueueError> {
194        let queue_desc = mem
195            .subrange(params.desc_addr, descriptor_offset(params.size), true)
196            .map_err(QueueError::Memory)?;
197        let driver_event = mem
198            .subrange(
199                params.avail_addr,
200                size_of::<PackedEventSuppression>() as u64,
201                true,
202            )
203            .map_err(QueueError::Memory)?;
204        Ok(Self {
205            queue_desc,
206            driver_event,
207            queue_size: params.size,
208            next_index: initial_index,
209            wrapped_bit: initial_wrap,
210            use_event_index: features.ring_event_idx(),
211        })
212    }
213
214    /// Return the packed used state: `index | (wrap_counter << 15)`.
215    pub fn used_state(&self) -> u16 {
216        self.next_index | (u16::from(self.wrapped_bit) << 15)
217    }
218
219    pub fn complete_descriptor(
220        &mut self,
221        context: &PackedQueueCompletionContext,
222        bytes_written: u32,
223    ) -> Result<bool, QueueError> {
224        let descriptor = PackedDescriptor::new()
225            .with_buffer_id(context.buffer_id)
226            .with_length(bytes_written)
227            .with_flags(
228                DescriptorFlags::new()
229                    .with_available(self.wrapped_bit)
230                    .with_used(self.wrapped_bit),
231            );
232        // Ensure any prior writes to guest buffers (e.g. device data) are
233        // visible before the used descriptor becomes visible to the guest.
234        atomic::fence(atomic::Ordering::Release);
235        self.queue_desc
236            .write_plain(descriptor_offset(self.next_index), &descriptor)
237            .map_err(QueueError::Memory)?;
238        // Ensure the descriptor update is visible before checking if the guest requires notification.
239        atomic::fence(atomic::Ordering::SeqCst);
240        let driver_event: PackedEventSuppression = self
241            .driver_event
242            .read_plain(0)
243            .map_err(QueueError::Memory)?;
244        let send_signal = match driver_event.flags() {
245            EventSuppressionFlags::Disabled => false,
246            EventSuppressionFlags::DescriptorIndex if self.use_event_index => {
247                driver_event.offset() == self.next_index && driver_event.wrap() == self.wrapped_bit
248            }
249            _ => true,
250        };
251        // Wraps at most once (see `advance`); compare-and-subtract avoids a modulo.
252        let raw = self.next_index + context.descriptor_count;
253        self.next_index = if raw >= self.queue_size {
254            self.wrapped_bit = !self.wrapped_bit;
255            raw - self.queue_size
256        } else {
257            raw
258        };
259        Ok(send_signal)
260    }
261}