Skip to main content

axvirtio_common/queue/
used.rs

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