Skip to main content

axvirtio_common/queue/
used.rs

1use alloc::sync::Arc;
2
3use axaddrspace::GuestMemoryAccessor;
4use axvm_types::GuestPhysAddr;
5use mbarrier::mb;
6
7use crate::{
8    constants::*,
9    error::{VirtioError, VirtioResult},
10    memory::GuestMemory,
11};
12
13/// VirtIO used ring element structure.
14///
15/// This structure represents the memory layout of a single element in the
16/// used ring array according to the VirtIO specification. Each element
17/// records information about a completed descriptor chain.
18///
19/// This structure is used by `UsedRing` to read/write individual used
20/// elements in guest memory through the guest memory accessor.
21#[repr(C)]
22#[derive(Debug, Clone, Copy)]
23pub struct VirtqUsedElem {
24    /// Index of start of used descriptor chain
25    pub id: u32,
26    /// Total length of the descriptor chain which was used
27    pub len: u32,
28}
29
30impl VirtqUsedElem {
31    /// Create a new used element
32    pub fn new(id: u32, len: u32) -> Self {
33        Self { id, len }
34    }
35}
36
37/// VirtIO used ring header structure.
38///
39/// This structure represents the memory layout of the used ring header
40/// in guest memory according to the VirtIO specification. It is a simple
41/// C-compatible data structure that directly maps to guest memory.
42///
43/// The complete used ring in guest memory consists of:
44/// 1. This header structure (VirtQueueUsed)
45/// 2. An array of used elements (ring[\queue_size], each VirtqUsedElem)
46/// 3. An optional avail_event field (if VIRTIO_F_EVENT_IDX is negotiated)
47///
48/// This structure is used by `UsedRing` to read/write the header portion
49/// of the used ring through guest memory accessor.
50#[repr(C)]
51#[derive(Debug, Clone, Copy, Default)]
52pub struct VirtQueueUsed {
53    /// Flags
54    pub flags: u16,
55    /// Index of the next used element
56    pub idx: u16,
57    // Ring of used elements (variable length)
58}
59
60impl VirtQueueUsed {
61    /// Create a new used ring header
62    pub fn new() -> Self {
63        Self { flags: 0, idx: 0 }
64    }
65
66    /// Check if notifications are disabled
67    pub fn no_notify(&self) -> bool {
68        (self.flags & VIRTQ_USED_F_NO_NOTIFY) != 0
69    }
70
71    /// Set the no notify flag
72    pub fn set_no_notify(&mut self, no_notify: bool) {
73        if no_notify {
74            self.flags |= VIRTQ_USED_F_NO_NOTIFY;
75        } else {
76            self.flags &= !VIRTQ_USED_F_NO_NOTIFY;
77        }
78    }
79}
80
81/// Used ring management structure.
82///
83/// This structure provides a high-level interface for managing the VirtIO
84/// used ring in guest memory. It wraps the guest memory accessor and
85/// provides methods to read/write various parts of the used ring:
86/// - The header (VirtQueueUsed structure)
87/// - The ring array of used elements (VirtqUsedElem structures)
88/// - The avail_event field (if VIRTIO_F_EVENT_IDX is negotiated)
89///
90/// Relationship with VirtQueueUsed and VirtqUsedElem:
91/// - VirtQueueUsed defines the memory layout of the used ring header
92/// - VirtqUsedElem defines the memory layout of each ring element
93/// - UsedRing uses both structures to access the complete used ring in guest memory
94/// - UsedRing manages the entire used ring structure and provides high-level operations
95///
96/// Memory Layout:
97/// ```text
98/// base_addr -> +-------------------+
99///              | VirtQueueUsed     |  (flags + idx)
100///              +-------------------+
101///              | ring[0]           |  (VirtqUsedElem: id + len)
102///              | ring[1]           |  (VirtqUsedElem: id + len)
103///              | ...               |
104///              | ring[queue_size-1]|  (VirtqUsedElem: id + len)
105///              +-------------------+
106///              | avail_event       |  (optional, if event_idx enabled)
107///              +-------------------+
108/// ```
109#[derive(Debug, Clone)]
110pub struct UsedRing<T: GuestMemoryAccessor + Clone> {
111    /// Base address of the used ring
112    pub base_addr: GuestPhysAddr,
113    /// Queue size
114    pub size: u16,
115    /// Current used index
116    pub used_idx: u16,
117    /// Guest memory accessor
118    accessor: Arc<T>,
119}
120
121impl<T: GuestMemoryAccessor + Clone> UsedRing<T> {
122    /// Create a new used ring
123    pub fn new(base_addr: GuestPhysAddr, size: u16, accessor: Arc<T>) -> Self {
124        Self {
125            base_addr,
126            size,
127            used_idx: 0,
128            accessor,
129        }
130    }
131
132    /// Get the address of the used ring header
133    pub fn header_addr(&self) -> GuestPhysAddr {
134        self.base_addr
135    }
136
137    /// Get the address of the ring array
138    pub fn ring_addr(&self) -> GuestPhysAddr {
139        self.base_addr + core::mem::size_of::<VirtQueueUsed>()
140    }
141
142    /// Get the address of a specific ring entry
143    pub fn ring_entry_addr(&self, index: u16) -> Option<GuestPhysAddr> {
144        if index >= self.size {
145            return None;
146        }
147
148        let offset = core::mem::size_of::<VirtQueueUsed>()
149            + (index as usize * core::mem::size_of::<VirtqUsedElem>());
150        Some(self.base_addr + offset)
151    }
152
153    /// Get the address of the available event field (if event_idx is enabled)
154    pub fn avail_event_addr(&self) -> GuestPhysAddr {
155        // Header + ring array fill the region up to 2 bytes before its end;
156        // the `avail_event` footer is always part of the region (see
157        // `layout_size`).
158        self.base_addr + Self::layout_size(self.size) - 2
159    }
160
161    /// Size in bytes of the complete used ring for a queue of `size` entries,
162    /// including the trailing 2-byte `avail_event` field.
163    ///
164    /// The footer is always counted: a driver that negotiated
165    /// `VIRTIO_F_RING_EVENT_IDX` reads `avail_event` from there, and layout
166    /// validation must not depend on negotiation state.
167    pub(crate) const fn layout_size(size: u16) -> usize {
168        core::mem::size_of::<VirtQueueUsed>()
169            + (size as usize) * core::mem::size_of::<VirtqUsedElem>()
170            + 2
171    }
172
173    /// The size in bytes this ring occupies in guest memory, always including
174    /// the trailing 2-byte event-index footer (see
175    /// [`layout_size`](Self::layout_size)).
176    pub fn total_size(&self) -> usize {
177        Self::layout_size(self.size)
178    }
179
180    /// Check if the used ring is valid
181    pub fn is_valid(&self) -> bool {
182        self.base_addr.as_usize() != 0 && self.size > 0
183    }
184
185    /// Add a used element to the ring
186    pub fn add_used(&mut self, id: u32, len: u32) -> VirtioResult<()> {
187        let accessor = self.accessor.clone();
188        let mut memory = crate::AddressSpaceMemory::new(&*accessor);
189        self.add_used_with_memory(id, len, &mut memory)
190    }
191
192    /// Adds a used element with a scoped memory capability.
193    pub fn add_used_with_memory(
194        &mut self,
195        id: u32,
196        len: u32,
197        memory: &mut dyn GuestMemory,
198    ) -> VirtioResult<()> {
199        self.add_used_with_memory_and_barrier(id, len, memory, mb)
200    }
201
202    fn add_used_with_memory_and_barrier(
203        &mut self,
204        id: u32,
205        len: u32,
206        memory: &mut dyn GuestMemory,
207        barrier: impl FnOnce(),
208    ) -> VirtioResult<()> {
209        if !self.is_valid() {
210            return Err(VirtioError::QueueNotReady);
211        }
212
213        // Calculate the address of the used element to write
214        let ring_index = self.used_idx % self.size;
215        let elem_addr = self
216            .ring_entry_addr(ring_index)
217            .ok_or(VirtioError::InvalidQueue)?;
218
219        // Create the used element
220        let used_elem = VirtqUsedElem::new(id, len);
221
222        // Write the used element to guest memory using injected memory accessor
223        let mut bytes = [0u8; 8];
224        bytes[0..4].copy_from_slice(&used_elem.id.to_le_bytes());
225        bytes[4..8].copy_from_slice(&used_elem.len.to_le_bytes());
226        memory.write(elem_addr, &bytes)?;
227
228        // Update the used index
229        self.used_idx = self.used_idx.wrapping_add(1);
230
231        // Publish the used element before publishing used_idx to the driver.
232        barrier();
233
234        // Update the used ring header index
235        self.write_used_idx_with_memory(memory)?;
236
237        Ok(())
238    }
239
240    /// Write the used index to the used ring header
241    pub fn write_used_idx(&self) -> VirtioResult<()> {
242        let mut memory = crate::AddressSpaceMemory::new(&*self.accessor);
243        self.write_used_idx_with_memory(&mut memory)
244    }
245
246    /// Writes the used index with a scoped memory capability.
247    pub fn write_used_idx_with_memory(&self, memory: &mut dyn GuestMemory) -> VirtioResult<()> {
248        if !self.is_valid() {
249            return Err(VirtioError::QueueNotReady);
250        }
251
252        // Write the used index to the header (offset 2 bytes for flags)
253        let idx_addr = self.base_addr + 2;
254        memory.write(idx_addr, &self.used_idx.to_le_bytes())?;
255
256        Ok(())
257    }
258
259    /// Read the used ring header
260    pub fn read_used_header(&self) -> VirtioResult<VirtQueueUsed> {
261        if !self.is_valid() {
262            return Err(VirtioError::QueueNotReady);
263        }
264
265        self.accessor
266            .read_obj(self.base_addr)
267            .map_err(|_| VirtioError::InvalidAddress)
268    }
269
270    /// Write the used ring header
271    pub fn write_used_header(&self, header: &VirtQueueUsed) -> VirtioResult<()> {
272        if !self.is_valid() {
273            return Err(VirtioError::QueueNotReady);
274        }
275
276        self.accessor
277            .write_obj(self.base_addr, *header)
278            .map_err(|_| VirtioError::InvalidAddress)
279    }
280
281    /// Get the current used index
282    pub fn get_used_idx(&self) -> u16 {
283        self.used_idx
284    }
285
286    /// Set the used index
287    pub fn set_used_idx(&mut self, idx: u16) {
288        self.used_idx = idx;
289    }
290
291    /// Check if notifications should be suppressed
292    pub fn should_notify(&self) -> VirtioResult<bool> {
293        if !self.is_valid() {
294            return Err(VirtioError::QueueNotReady);
295        }
296
297        let header = self.read_used_header()?;
298        Ok(!header.no_notify())
299    }
300
301    /// Set notification suppression
302    pub fn set_notification(&self, suppress: bool) -> VirtioResult<()> {
303        if !self.is_valid() {
304            return Err(VirtioError::QueueNotReady);
305        }
306
307        let mut header = self.read_used_header()?;
308        header.set_no_notify(suppress);
309        self.write_used_header(&header)?;
310
311        Ok(())
312    }
313
314    /// Sets notification suppression with a scoped memory capability.
315    pub(crate) fn set_notification_with_memory(
316        &self,
317        suppress: bool,
318        memory: &mut dyn GuestMemory,
319    ) -> VirtioResult<()> {
320        if !self.is_valid() {
321            return Err(VirtioError::QueueNotReady);
322        }
323        let flags = if suppress { VIRTQ_USED_F_NO_NOTIFY } else { 0 };
324        memory.write(self.base_addr, &flags.to_le_bytes())
325    }
326
327    /// Writes the available event field with a scoped memory capability.
328    pub(crate) fn write_avail_event_with_memory(
329        &self,
330        event: u16,
331        memory: &mut dyn GuestMemory,
332    ) -> VirtioResult<()> {
333        if !self.is_valid() {
334            return Err(VirtioError::QueueNotReady);
335        }
336        memory.write(self.avail_event_addr(), &event.to_le_bytes())
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use alloc::{rc::Rc, vec::Vec};
343    use core::cell::RefCell;
344
345    use axvm_types::GuestPhysAddr;
346
347    use super::*;
348    use crate::{GuestMemory, NoGuestMemoryAccessor};
349
350    struct RecordingMemory {
351        events: Rc<RefCell<Vec<&'static str>>>,
352    }
353
354    impl GuestMemory for RecordingMemory {
355        fn read(&mut self, _: GuestPhysAddr, _: &mut [u8]) -> VirtioResult<()> {
356            Err(VirtioError::InvalidAddress)
357        }
358
359        fn write(&mut self, address: GuestPhysAddr, _: &[u8]) -> VirtioResult<()> {
360            self.events
361                .borrow_mut()
362                .push(if address.as_usize() == 0x1002 {
363                    "used_idx"
364                } else {
365                    "used_elem"
366                });
367            Ok(())
368        }
369    }
370
371    #[test]
372    fn publishes_used_element_before_used_index() {
373        let events = Rc::new(RefCell::new(Vec::new()));
374        let mut memory = RecordingMemory {
375            events: events.clone(),
376        };
377        let mut ring = UsedRing::new(
378            GuestPhysAddr::from(0x1000),
379            1,
380            alloc::sync::Arc::new(NoGuestMemoryAccessor),
381        );
382
383        ring.add_used_with_memory_and_barrier(7, 11, &mut memory, || {
384            events.borrow_mut().push("barrier");
385        })
386        .unwrap();
387
388        assert_eq!(&*events.borrow(), &["used_elem", "barrier", "used_idx"]);
389    }
390
391    #[test]
392    fn layout_size_counts_header_elements_and_footer() {
393        // header (4) + 4 elements * 8 bytes + avail_event footer (2) = 38.
394        assert_eq!(UsedRing::<NoGuestMemoryAccessor>::layout_size(4), 38);
395        // Boundary: the largest queue size still counts the footer.
396        assert_eq!(
397            UsedRing::<NoGuestMemoryAccessor>::layout_size(256),
398            4 + 256 * 8 + 2
399        );
400    }
401}