Skip to main content

axvirtio_common/queue/
mod.rs

1mod available;
2mod descriptor;
3mod used;
4
5use alloc::{sync::Arc, vec::Vec};
6use core::sync::atomic::{AtomicBool, Ordering};
7
8pub use available::{AvailableRing, VirtQueueAvail};
9use axaddrspace::GuestMemoryAccessor;
10use axvm_types::GuestPhysAddr;
11pub use descriptor::{DescriptorChain, DescriptorTable, VirtQueueDesc};
12use log::{trace, warn};
13pub use used::{UsedRing, VirtQueueUsed, VirtqUsedElem};
14
15use crate::{
16    VirtioDeviceID,
17    constants::{VIRTQ_AVAIL_ALIGN, VIRTQ_DESC_ALIGN, VIRTQ_USED_ALIGN},
18    error::{VirtioError, VirtioResult},
19};
20
21/// VirtIO queue implementation
22#[derive(Debug)]
23pub struct VirtioQueue<T: GuestMemoryAccessor + Clone> {
24    /// Queue index
25    pub index: u16,
26    /// Queue size
27    pub size: u16,
28    /// Descriptor table
29    pub desc_table: Option<DescriptorTable>,
30    /// Available ring
31    avail_ring: Option<AvailableRing<T>>,
32    /// Used ring
33    used_ring: Option<UsedRing<T>>,
34    /// Guest memory accessor
35    accessor: Arc<T>,
36    /// Maximum queue size
37    pub max_size: u16,
38    /// Queue ready flag
39    pub ready: bool,
40    /// Descriptor table address (guest physical)
41    pub desc_table_addr: GuestPhysAddr,
42    /// Available ring address (guest physical)
43    pub avail_ring_addr: GuestPhysAddr,
44    /// Used ring address (guest physical)
45    pub used_ring_addr: GuestPhysAddr,
46    /// Next available index
47    next_avail: u16,
48    /// Next used index
49    next_used: u16,
50    /// Event index enabled.
51    ///
52    /// Currently always `false` and intentionally unused: event-index feature
53    /// negotiation is not implemented yet, and a follow-up will wire this
54    /// flag. Layout validation deliberately does not depend on it: the ring
55    /// regions always include the 2-byte event-index footer through the
56    /// `layout_size` math, so the check stays negotiation-independent.
57    pub event_idx_enabled: bool,
58    /// Set when a runtime ring/descriptor validation failure occurs; the queue
59    /// rejects `pop`/`complete` and the guest-data paths until
60    /// [`reset`](Self::reset) clears it.
61    ///
62    /// A single bool that latches only `false -> true` (no lost-update
63    /// concern): the `Release` store in `latch_fault` publishes the fault to
64    /// any thread that later queries `is_faulted` with an `Acquire` load.
65    /// The queue is expected to be guarded by the owning device's lock, but
66    /// unlike a `Cell` the atomic stays sound even if the queue is aliased
67    /// across threads.
68    faulted: AtomicBool,
69    /// Set once per configuration cycle after the layout-rejection warning has
70    /// been emitted. A guest can re-trigger the `QUEUE_READY` memory probe
71    /// unboundedly, so repeats are logged at `trace!` instead of `warn!`;
72    /// [`reset`](Self::reset) clears the latch for the next configuration.
73    layout_warn_emitted: AtomicBool,
74}
75
76impl<T: GuestMemoryAccessor + Clone> Clone for VirtioQueue<T> {
77    /// Clones the queue configuration, snapshotting the current faulted and
78    /// layout-warning latch states into fresh atomics (the clone does not
79    /// share the original's latches).
80    fn clone(&self) -> Self {
81        Self {
82            index: self.index,
83            size: self.size,
84            desc_table: self.desc_table.clone(),
85            avail_ring: self.avail_ring.clone(),
86            used_ring: self.used_ring.clone(),
87            accessor: self.accessor.clone(),
88            max_size: self.max_size,
89            ready: self.ready,
90            desc_table_addr: self.desc_table_addr,
91            avail_ring_addr: self.avail_ring_addr,
92            used_ring_addr: self.used_ring_addr,
93            next_avail: self.next_avail,
94            next_used: self.next_used,
95            event_idx_enabled: self.event_idx_enabled,
96            faulted: AtomicBool::new(self.faulted.load(Ordering::Acquire)),
97            layout_warn_emitted: AtomicBool::new(self.layout_warn_emitted.load(Ordering::Acquire)),
98        }
99    }
100}
101
102impl<T: GuestMemoryAccessor + Clone> VirtioQueue<T> {
103    /// Create a new VirtIO queue
104    pub fn new(index: u16, size: u16, accessor: Arc<T>) -> Self {
105        Self {
106            index,
107            size,
108            desc_table: None,
109            avail_ring: None,
110            used_ring: None,
111            accessor,
112            max_size: size,
113            ready: false,
114            desc_table_addr: GuestPhysAddr::from(0),
115            avail_ring_addr: GuestPhysAddr::from(0),
116            used_ring_addr: GuestPhysAddr::from(0),
117            next_avail: 0,
118            next_used: 0,
119            event_idx_enabled: false,
120            faulted: AtomicBool::new(false),
121            layout_warn_emitted: AtomicBool::new(false),
122        }
123    }
124
125    /// Set queue size
126    ///
127    /// Rejected once any ring address is programmed or the queue is ready: the
128    /// ring objects snapshot the size when their address is set, so a later
129    /// resize would leave layout validation and runtime ring accesses derived
130    /// from different sizes, letting the queue serve requests outside the
131    /// validated regions.
132    pub fn set_size(&mut self, size: u16) -> VirtioResult<()> {
133        if size == 0 || size > self.max_size || (size & (size - 1)) != 0 {
134            return Err(VirtioError::InvalidQueue);
135        }
136        if self.ready || self.is_configured() {
137            return Err(VirtioError::InvalidQueue);
138        }
139        self.size = size;
140        Ok(())
141    }
142
143    /// Set descriptor table address
144    pub fn set_desc_table_addr(&mut self, addr: GuestPhysAddr) -> VirtioResult<()> {
145        // Overwrite semantics: VirtIO MMIO programs a 64-bit address via separate
146        // LOW/HIGH 32-bit writes, so the setter accepts repeated updates and keeps
147        // the latest combined value rather than rejecting the second write.
148        self.desc_table_addr = addr;
149        if addr.as_usize() != 0 {
150            self.desc_table = Some(DescriptorTable::new(addr, self.size));
151        } else {
152            self.desc_table = None;
153        }
154        Ok(())
155    }
156
157    /// Set available ring address
158    pub fn set_avail_ring_addr(&mut self, addr: GuestPhysAddr) -> VirtioResult<()> {
159        self.avail_ring_addr = addr;
160        if addr.as_usize() != 0 {
161            self.avail_ring = Some(AvailableRing::new(addr, self.size, self.accessor.clone()));
162        } else {
163            self.avail_ring = None;
164        }
165        Ok(())
166    }
167
168    /// Set used ring address
169    pub fn set_used_ring_addr(&mut self, addr: GuestPhysAddr) -> VirtioResult<()> {
170        self.used_ring_addr = addr;
171        if addr.as_usize() != 0 {
172            self.used_ring = Some(UsedRing::new(addr, self.size, self.accessor.clone()));
173        } else {
174            self.used_ring = None;
175        }
176        Ok(())
177    }
178
179    /// Mark queue as ready
180    pub fn set_ready(&mut self, ready: bool) {
181        self.ready = ready;
182    }
183
184    /// Whether the three ring addresses have all been programmed.
185    ///
186    /// Address `0` is the "unconfigured" sentinel: a driver that has not
187    /// finished programming a ring must never be able to make the queue ready.
188    pub fn is_configured(&self) -> bool {
189        self.desc_table_addr.as_usize() != 0
190            && self.avail_ring_addr.as_usize() != 0
191            && self.used_ring_addr.as_usize() != 0
192    }
193
194    /// The guest-memory accessor used by the non-`_with_memory` operations.
195    pub fn accessor(&self) -> &Arc<T> {
196        &self.accessor
197    }
198
199    /// Validate the three ring layouts against the VirtIO split-ring
200    /// requirements. This is a pure query and does not change queue state.
201    ///
202    /// Checks, per VirtIO 1.x ยง2.7:
203    /// - all three ring addresses are non-zero;
204    /// - the descriptor table is 16-byte aligned, the available ring 2-byte and
205    ///   the used ring 4-byte aligned;
206    /// - `addr + size * elem_size` does not overflow the guest address space
207    ///   for any ring;
208    /// - the three regions do not overlap (overlap would let a used-element
209    ///   write corrupt descriptors the device is about to read).
210    ///
211    /// The available and used regions always include their 2-byte event-index
212    /// footer (`used_event` / `avail_event`), even when
213    /// `VIRTIO_F_RING_EVENT_IDX` is not negotiated: a driver that negotiated
214    /// it writes into those bytes, and the ring types' own `total_size`
215    /// counts them. Always covering the footer is the conservative,
216    /// negotiation-independent envelope.
217    ///
218    /// The transport is expected to call this from its single "queue becomes
219    /// usable" enforcement point (MMIO: the `QUEUE_READY` write; PCI: layout
220    /// programmed in the queue config registers) and to refuse to mark the
221    /// queue ready when it fails.
222    pub fn validate_layout(&self) -> VirtioResult<()> {
223        let regions = self.ring_regions();
224        regions
225            .iter()
226            .all(|region| region.base.as_usize() != 0)
227            .then_some(())
228            .ok_or(VirtioError::InvalidRingLayout)?;
229        if !self
230            .desc_table_addr
231            .as_usize()
232            .is_multiple_of(VIRTQ_DESC_ALIGN)
233            || !self
234                .avail_ring_addr
235                .as_usize()
236                .is_multiple_of(VIRTQ_AVAIL_ALIGN)
237            || !self
238                .used_ring_addr
239                .as_usize()
240                .is_multiple_of(VIRTQ_USED_ALIGN)
241        {
242            return Err(VirtioError::RingMisaligned);
243        }
244        for (index, region) in regions.iter().enumerate() {
245            if region.end().is_none() {
246                return Err(VirtioError::InvalidRingLayout);
247            }
248            if regions[index + 1..]
249                .iter()
250                .any(|other| region.overlaps(other))
251            {
252                return Err(VirtioError::RingOverlap);
253            }
254        }
255        Ok(())
256    }
257
258    /// Validates the ring layout like [`validate_layout`](Self::validate_layout)
259    /// and additionally screens the ring regions against `memory`: the first
260    /// byte and the last byte (`end - 1`) of every region must be readable
261    /// through `memory`.
262    ///
263    /// `memory` must be backed by the same accessor the queue uses for its
264    /// runtime accesses; passing a capability over different memory makes the
265    /// check vacuous. Only the two boundary bytes per region are probed on
266    /// purpose: this is a best-effort enable-time screen, and the per-byte
267    /// runtime accesses are what ultimately verify mid-region mapping.
268    ///
269    /// An accessor that cannot translate any guest address (such as
270    /// [`NoGuestMemoryAccessor`](crate::memory::NoGuestMemoryAccessor), whose
271    /// `translate_and_get_limit` always returns `None`) fails every probe and
272    /// therefore cannot satisfy this check; such layouts are rejected (the
273    /// first rejection per configuration cycle is warned, later ones only
274    /// traced), so the MMIO transport requires an accessor backed by real
275    /// guest memory. Memory-screening failures are reported as
276    /// [`VirtioError::InvalidRingLayout`], while pure layout errors keep their
277    /// specific variants ([`RingMisaligned`](VirtioError::RingMisaligned),
278    /// [`RingOverlap`](VirtioError::RingOverlap)); any `Err` means the layout
279    /// was rejected, so the caller only needs to distinguish "layout rejected"
280    /// from "queue ready".
281    pub fn validate_layout_with_memory(
282        &self,
283        memory: &mut dyn crate::GuestMemory,
284    ) -> VirtioResult<()> {
285        self.validate_layout()?;
286        for region in self.ring_regions() {
287            let end = region.end().ok_or(VirtioError::InvalidRingLayout)?;
288            if memory.read(region.base, &mut [0u8; 1]).is_err()
289                || memory
290                    .read(GuestPhysAddr::from(end - 1), &mut [0u8; 1])
291                    .is_err()
292            {
293                // A guest can write QUEUE_READY unboundedly, so warn only once
294                // per configuration cycle; later rejections are traced.
295                if self.layout_warn_emitted.swap(true, Ordering::AcqRel) {
296                    trace!(
297                        "virtqueue {}: ring region 0x{:x}..0x{:x} is still not fully mapped; \
298                         rejecting layout again",
299                        self.index,
300                        region.base.as_usize(),
301                        end,
302                    );
303                } else {
304                    warn!(
305                        "virtqueue {}: ring region 0x{:x}..0x{:x} is not fully mapped in guest \
306                         memory; rejecting layout",
307                        self.index,
308                        region.base.as_usize(),
309                        end,
310                    );
311                }
312                return Err(VirtioError::InvalidRingLayout);
313            }
314        }
315        Ok(())
316    }
317
318    /// The three ring regions derived from the current layout.
319    ///
320    /// The available and used regions always include their 2-byte event-index
321    /// footer, regardless of whether `VIRTIO_F_RING_EVENT_IDX` is negotiated;
322    /// the size math is owned by `DescriptorTable::layout_size`,
323    /// `AvailableRing::layout_size` and `UsedRing::layout_size` so the footer
324    /// cannot be forgotten here.
325    fn ring_regions(&self) -> [RingRegion; 3] {
326        [
327            RingRegion::new(
328                self.desc_table_addr,
329                DescriptorTable::layout_size(self.size),
330            ),
331            RingRegion::new(
332                self.avail_ring_addr,
333                AvailableRing::<T>::layout_size(self.size),
334            ),
335            RingRegion::new(self.used_ring_addr, UsedRing::<T>::layout_size(self.size)),
336        ]
337    }
338
339    /// Whether the queue is in the faulted state and must be reset before
340    /// further `pop`/`complete` calls.
341    ///
342    /// While faulted, the guest-serving data paths (`pop`/`complete`, chain
343    /// walks and data access) reject with [`VirtioError::QueueFaulted`]. The
344    /// configuration setters remain usable so a driver can re-program the
345    /// queue, and [`reset`](Self::reset) is the only operation that clears
346    /// the fault.
347    pub fn is_faulted(&self) -> bool {
348        self.faulted.load(Ordering::Acquire)
349    }
350
351    /// Check if queue is valid and ready
352    pub fn is_valid(&self) -> bool {
353        self.ready
354            && self.desc_table_addr.as_usize() != 0
355            && self.avail_ring_addr.as_usize() != 0
356            && self.used_ring_addr.as_usize() != 0
357            && self.validate_layout().is_ok()
358    }
359
360    /// Latch the queue into the faulted state after a runtime validation
361    /// failure; `pop`/`complete` are rejected until [`reset`](Self::reset).
362    fn latch_fault(&self) {
363        self.faulted.store(true, Ordering::Release);
364        trace!("virtqueue {}: latched faulted state", self.index);
365    }
366
367    /// Reset the queue: clears the ready flag, the faulted state and the
368    /// layout-warning latch, and discards the programmed ring addresses,
369    /// indices and ring objects, so the driver must re-program the queue
370    /// before it can be used again.
371    ///
372    /// While faulted, the guest-serving data paths reject with
373    /// [`VirtioError::QueueFaulted`] but the configuration setters remain
374    /// usable; `reset` is the only operation that clears the fault.
375    pub fn reset(&mut self) {
376        self.ready = false;
377        self.desc_table_addr = GuestPhysAddr::from(0);
378        self.avail_ring_addr = GuestPhysAddr::from(0);
379        self.used_ring_addr = GuestPhysAddr::from(0);
380        self.next_avail = 0;
381        self.next_used = 0;
382        self.desc_table = None;
383        self.avail_ring = None;
384        self.used_ring = None;
385        self.faulted.store(false, Ordering::Release);
386        self.layout_warn_emitted.store(false, Ordering::Release);
387    }
388
389    /// Read available ring index through the queue's own accessor.
390    ///
391    /// Returns [`VirtioError::QueueFaulted`] when the queue is faulted and
392    /// [`VirtioError::QueueNotReady`] when the available ring is not
393    /// configured (not a runtime failure, so the queue is not faulted). A read
394    /// failure of a configured ring is a runtime failure and latches the
395    /// fault, matching the other avail-ring pre-read paths.
396    pub fn read_avail_idx(&self) -> VirtioResult<u16> {
397        let accessor = self.accessor.clone();
398        let mut memory = crate::AddressSpaceMemory::new(&*accessor);
399        self.read_avail_idx_with_memory(&mut memory)
400    }
401
402    /// Reads the available index with a scoped memory capability.
403    ///
404    /// Returns [`VirtioError::QueueFaulted`] when the queue is faulted and
405    /// [`VirtioError::QueueNotReady`] when the available ring is not
406    /// configured (not a runtime failure, so the queue is not faulted). A read
407    /// failure of a configured ring is a runtime failure and latches the
408    /// fault, matching the other avail-ring pre-read paths.
409    pub fn read_avail_idx_with_memory(
410        &self,
411        memory: &mut dyn crate::GuestMemory,
412    ) -> VirtioResult<u16> {
413        if self.faulted.load(Ordering::Acquire) {
414            return Err(VirtioError::QueueFaulted);
415        }
416        let Some(avail_ring) = self.avail_ring.as_ref() else {
417            return Err(VirtioError::QueueNotReady);
418        };
419        let result = avail_ring.read_avail_idx_with_memory(memory);
420        if result.is_err() {
421            self.latch_fault();
422        }
423        result
424    }
425
426    /// Add a used buffer to the used ring.
427    ///
428    /// Returns [`VirtioError::QueueNotReady`] when the queue is not ready or
429    /// the used ring is not configured: a missing used ring is never silently
430    /// accepted as a success (the historical fallback did exactly that).
431    /// Being unconfigured is not a runtime failure, so the queue is not
432    /// faulted; a guest-memory write failure on a configured ring does latch
433    /// the fault.
434    pub fn add_used(&mut self, desc_index: u16, len: u32) -> VirtioResult<()> {
435        if self.faulted.load(Ordering::Acquire) {
436            return Err(VirtioError::QueueFaulted);
437        }
438        if !self.is_valid() {
439            return Err(VirtioError::QueueNotReady);
440        }
441
442        let Some(used_ring) = self.used_ring.as_mut() else {
443            // No used ring is configured: completing into nothing would be
444            // an "error success" against this crate's fault contract, so
445            // report QueueNotReady without latching the fault.
446            return Err(VirtioError::QueueNotReady);
447        };
448        let result = used_ring.add_used(desc_index as u32, len);
449        if result.is_ok() {
450            self.next_used = used_ring.get_used_idx();
451        } else {
452            // A guest-memory write failure on a configured queue is a runtime
453            // failure: latch the fault so no "error success" completion can
454            // follow, mirroring `complete_with_memory`.
455            self.latch_fault();
456        }
457        result
458    }
459
460    /// Consume one available-ring head index, or `None` if the queue is empty.
461    ///
462    /// Advances `last_avail_idx` by one (wrapping at `u16::MAX`). Returns
463    /// [`VirtioError::InvalidQueue`] when the guest's `avail.idx` is ahead by
464    /// more than `size`, which indicates a corrupted available ring.
465    pub fn pop_available_head(&mut self) -> VirtioResult<Option<u16>> {
466        let accessor = self.accessor.clone();
467        let mut memory = crate::AddressSpaceMemory::new(&*accessor);
468        self.pop_available_head_with_memory(&mut memory)
469    }
470
471    /// Consumes one available head with a scoped memory capability.
472    pub fn pop_available_head_with_memory(
473        &mut self,
474        memory: &mut dyn crate::GuestMemory,
475    ) -> VirtioResult<Option<u16>> {
476        if self.faulted.load(Ordering::Acquire) {
477            return Err(VirtioError::QueueFaulted);
478        }
479        if !self.is_valid() {
480            return Err(VirtioError::QueueNotReady);
481        }
482        let avail_idx = self.read_avail_idx_with_memory(memory)?;
483        let last = self.get_last_avail_idx();
484        let pending = avail_idx.wrapping_sub(last);
485        if pending > self.size {
486            // Corrupted available ring: more entries pending than the queue
487            // can hold. Latch the fault so the queue stops serving until reset.
488            self.latch_fault();
489            return Err(VirtioError::InvalidQueue);
490        }
491        if pending == 0 {
492            return Ok(None);
493        }
494        let head = match self
495            .avail_ring
496            .as_ref()
497            .map(|ring| ring.read_avail_ring_entry_with_memory(last % self.size, memory))
498        {
499            Some(Ok(head)) => head,
500            Some(Err(error)) => {
501                // A guest-memory read failure on a configured queue is a
502                // runtime failure; latch the fault like the other runtime
503                // paths do.
504                self.latch_fault();
505                return Err(error);
506            }
507            None => return Err(VirtioError::QueueNotReady),
508        };
509        self.update_last_avail_idx(last.wrapping_add(1));
510        Ok(Some(head))
511    }
512
513    /// Consume one available head and return a validated [`DescriptorChain`].
514    ///
515    /// Returns `Ok(None)` when the queue is empty. The head is consumed *before*
516    /// the chain is validated; on a validation error the head is already
517    /// advanced (so the queue is not stalled) and the caller should complete
518    /// that head with length 0. To recover the head on error, use
519    /// [`pop_available_head`](Self::pop_available_head) plus
520    /// [`descriptor_chain`](Self::descriptor_chain) directly.
521    pub fn pop_available(&mut self) -> VirtioResult<Option<DescriptorChain>> {
522        let head = match self.pop_available_head()? {
523            Some(h) => h,
524            None => return Ok(None),
525        };
526        Ok(Some(self.descriptor_chain(head)?))
527    }
528
529    /// Build a validated [`DescriptorChain`] for an already-consumed head index.
530    pub fn descriptor_chain(&self, head: u16) -> VirtioResult<DescriptorChain> {
531        let mut memory = crate::AddressSpaceMemory::new(&*self.accessor);
532        self.descriptor_chain_with_memory(head, &mut memory)
533    }
534
535    /// Builds a validated descriptor chain using a scoped memory capability.
536    ///
537    /// Returns [`VirtioError::QueueNotReady`] when the descriptor table is not
538    /// configured (not a runtime failure, so the queue is not faulted) and
539    /// [`VirtioError::QueueFaulted`] when the queue is already faulted.
540    pub fn descriptor_chain_with_memory(
541        &self,
542        head: u16,
543        memory: &mut dyn crate::GuestMemory,
544    ) -> VirtioResult<DescriptorChain> {
545        if self.faulted.load(Ordering::Acquire) {
546            return Err(VirtioError::QueueFaulted);
547        }
548        let Some(ref desc_table) = self.desc_table else {
549            return Err(VirtioError::QueueNotReady);
550        };
551        let result = desc_table.descriptor_chain(head, memory);
552        if result.is_err() {
553            // The descriptor table is configured, so a chain failure is a
554            // runtime validation failure: latch the fault.
555            self.latch_fault();
556        }
557        result
558    }
559
560    /// Complete a descriptor chain: append a used element for `head` with the
561    /// given written length, then report whether the driver should be notified.
562    ///
563    /// `written_len` is the number of bytes the device wrote into guest-writable
564    /// buffers (RX bytes, or 0 for TX / discarded / error completions).
565    pub fn complete(&mut self, head: u16, written_len: u32) -> VirtioResult<bool> {
566        self.add_used(head, written_len)?;
567        self.should_notify()
568    }
569
570    /// Completes a chain with a scoped memory capability.
571    ///
572    /// Returns [`VirtioError::QueueNotReady`] when a ring is not configured
573    /// (not a runtime failure, so the queue is not faulted) and
574    /// [`VirtioError::QueueFaulted`] when the queue is already faulted. Any
575    /// failure while writing the used ring or reading the available flags is
576    /// treated as a runtime failure and latches the fault.
577    pub fn complete_with_memory(
578        &mut self,
579        head: u16,
580        written_len: u32,
581        memory: &mut dyn crate::GuestMemory,
582    ) -> VirtioResult<bool> {
583        if self.faulted.load(Ordering::Acquire) {
584            return Err(VirtioError::QueueFaulted);
585        }
586        let result = (|| {
587            let used_ring = self.used_ring.as_mut().ok_or(VirtioError::QueueNotReady)?;
588            used_ring.add_used_with_memory(head as u32, written_len, memory)?;
589            self.next_used = used_ring.get_used_idx();
590            let avail_ring = self.avail_ring.as_ref().ok_or(VirtioError::QueueNotReady)?;
591            Ok(!avail_ring.interrupts_suppressed_with_memory(memory)?)
592        })();
593        if result.is_err() {
594            self.latch_fault();
595        }
596        result
597    }
598
599    /// Get the used ring reference
600    pub fn get_used_ring(&self) -> Option<&UsedRing<T>> {
601        self.used_ring.as_ref()
602    }
603
604    /// Get the used ring mutable reference
605    pub fn get_used_ring_mut(&mut self) -> Option<&mut UsedRing<T>> {
606        self.used_ring.as_mut()
607    }
608
609    /// Get the available ring reference
610    pub fn get_avail_ring(&self) -> Option<&AvailableRing<T>> {
611        self.avail_ring.as_ref()
612    }
613
614    /// Get the descriptor table reference
615    pub fn get_desc_table(&self) -> Option<&DescriptorTable> {
616        self.desc_table.as_ref()
617    }
618
619    /// Read available ring entry through the queue's own accessor.
620    ///
621    /// Returns [`VirtioError::QueueFaulted`] when the queue is faulted and
622    /// [`VirtioError::QueueNotReady`] when the available ring is not
623    /// configured (not a runtime failure, so the queue is not faulted). A read
624    /// failure of a configured ring is a runtime failure and latches the
625    /// fault, matching the other avail-ring pre-read paths.
626    pub fn read_avail_entry(&self, ring_index: u16) -> VirtioResult<u16> {
627        let accessor = self.accessor.clone();
628        let mut memory = crate::AddressSpaceMemory::new(&*accessor);
629        self.read_avail_entry_with_memory(ring_index, &mut memory)
630    }
631
632    /// Reads an available-ring entry with a scoped memory capability.
633    ///
634    /// Returns [`VirtioError::QueueFaulted`] when the queue is faulted and
635    /// [`VirtioError::QueueNotReady`] when the available ring is not
636    /// configured (not a runtime failure, so the queue is not faulted). A read
637    /// failure of a configured ring is a runtime failure and latches the
638    /// fault, matching the other avail-ring pre-read paths.
639    pub fn read_avail_entry_with_memory(
640        &self,
641        ring_index: u16,
642        memory: &mut dyn crate::GuestMemory,
643    ) -> VirtioResult<u16> {
644        if self.faulted.load(Ordering::Acquire) {
645            return Err(VirtioError::QueueFaulted);
646        }
647        let Some(avail_ring) = self.avail_ring.as_ref() else {
648            return Err(VirtioError::QueueNotReady);
649        };
650        let result = avail_ring.read_avail_ring_entry_with_memory(ring_index, memory);
651        if result.is_err() {
652            self.latch_fault();
653        }
654        result
655    }
656
657    /// Update last available index
658    pub fn update_last_avail_idx(&mut self, idx: u16) {
659        if let Some(ref mut avail_ring) = self.avail_ring {
660            avail_ring.update_last_avail_idx(idx);
661        } else {
662            self.next_avail = idx % self.size;
663        }
664    }
665
666    /// Get last available index
667    pub fn get_last_avail_idx(&self) -> u16 {
668        if let Some(avail_ring) = &self.avail_ring {
669            avail_ring.last_avail_idx
670        } else {
671            self.next_avail
672        }
673    }
674
675    /// Validate VirtIO block chain
676    ///
677    /// Returns [`VirtioError::QueueNotReady`] when the descriptor table is not
678    /// configured and [`VirtioError::QueueFaulted`] when the queue is faulted.
679    /// Any validation or guest-memory failure on a configured queue latches
680    /// the fault, matching the other descriptor-chain walk entry points.
681    pub fn validate_virtio_block_chain(
682        &self,
683        head_index: u16,
684        min_length: usize,
685    ) -> VirtioResult<bool> {
686        if self.faulted.load(Ordering::Acquire) {
687            return Err(VirtioError::QueueFaulted);
688        }
689        let Some(ref desc_table) = self.desc_table else {
690            return Err(VirtioError::QueueNotReady);
691        };
692        let mut memory = crate::AddressSpaceMemory::new(&*self.accessor);
693        let result = desc_table
694            .follow_chain(head_index, &mut memory)
695            .map(|descriptors| descriptors.len() >= min_length);
696        if result.is_err() {
697            self.latch_fault();
698        }
699        result
700    }
701
702    /// Get data buffers from descriptor chain
703    ///
704    /// Returns [`VirtioError::QueueNotReady`] when the descriptor table is not
705    /// configured and [`VirtioError::QueueFaulted`] when the queue is faulted.
706    /// Any guest-memory failure on a configured queue latches the fault.
707    pub fn get_data_buffers(
708        &self,
709        head_index: u16,
710        device_type: VirtioDeviceID,
711    ) -> VirtioResult<Vec<(GuestPhysAddr, usize, bool)>> {
712        if self.faulted.load(Ordering::Acquire) {
713            return Err(VirtioError::QueueFaulted);
714        }
715        let Some(ref desc_table) = self.desc_table else {
716            return Err(VirtioError::QueueNotReady);
717        };
718        let mut memory = crate::AddressSpaceMemory::new(&*self.accessor);
719        let result = desc_table.get_data_buffers(head_index, device_type, &mut memory);
720        if result.is_err() {
721            self.latch_fault();
722        }
723        result
724    }
725
726    /// Get status address from descriptor chain
727    ///
728    /// Returns [`VirtioError::QueueNotReady`] when the descriptor table is not
729    /// configured and [`VirtioError::QueueFaulted`] when the queue is faulted.
730    /// Any guest-memory failure on a configured queue latches the fault.
731    pub fn get_status_addr(&self, head_index: u16) -> VirtioResult<GuestPhysAddr> {
732        if self.faulted.load(Ordering::Acquire) {
733            return Err(VirtioError::QueueFaulted);
734        }
735        let Some(ref desc_table) = self.desc_table else {
736            return Err(VirtioError::QueueNotReady);
737        };
738        let mut memory = crate::AddressSpaceMemory::new(&*self.accessor);
739        let result = desc_table.get_status_addr(head_index, &mut memory);
740        if result.is_err() {
741            self.latch_fault();
742        }
743        result
744    }
745
746    /// Whether the device should interrupt the driver after updating the used ring.
747    ///
748    /// Per the VirtIO specification the device honors the *available* ring's
749    /// `VIRTQ_AVAIL_F_NO_INTERRUPT` flag. The used ring's `VIRTQ_USED_F_NO_NOTIFY`
750    /// flag is the opposite direction (the driver reads it to decide whether to
751    /// kick the device), so it must not gate device-to-driver interrupts.
752    ///
753    /// Returns [`VirtioError::QueueFaulted`] when the queue is faulted,
754    /// [`VirtioError::QueueNotReady`] when the available ring is not
755    /// configured (not a runtime failure, so the queue is not faulted), and
756    /// latches the fault on a read failure of a configured ring.
757    pub fn should_notify(&self) -> VirtioResult<bool> {
758        if self.faulted.load(Ordering::Acquire) {
759            return Err(VirtioError::QueueFaulted);
760        }
761        let Some(ref avail_ring) = self.avail_ring else {
762            return Err(VirtioError::QueueNotReady);
763        };
764        let result = avail_ring
765            .interrupts_suppressed()
766            .map(|suppressed| !suppressed);
767        if result.is_err() {
768            self.latch_fault();
769        }
770        result
771    }
772
773    /// Write status byte to the status buffer of a descriptor chain
774    ///
775    /// This method writes the status byte to the last descriptor in the chain,
776    /// which should be a write-only descriptor according to VirtIO specification.
777    ///
778    /// Returns [`VirtioError::QueueNotReady`] when the descriptor table is not
779    /// configured and [`VirtioError::QueueFaulted`] when the queue is faulted;
780    /// a faulted queue never writes guest memory.
781    pub fn write_status_byte(&self, head_index: u16, status: u8) -> VirtioResult<()> {
782        if self.faulted.load(Ordering::Acquire) {
783            return Err(VirtioError::QueueFaulted);
784        }
785        // Get the status descriptor address (last descriptor in chain)
786        let status_addr_guest = self.get_status_addr(head_index)?;
787
788        trace!(
789            "Writing status byte {} to guest address 0x{:x} for descriptor chain {}",
790            status,
791            status_addr_guest.as_usize(),
792            head_index
793        );
794
795        // Write the status byte to guest memory using the new memory access interface
796        self.accessor
797            .write_obj(status_addr_guest, status)
798            .map_err(|_| VirtioError::InvalidAddress)?;
799
800        Ok(())
801    }
802}
803
804/// One half-open guest region `[base, base + size)` used by ring-layout
805/// validation. Deliberately uses `usize` arithmetic like the rest of the queue
806/// layer so the checks match the arithmetic actually performed on ring
807/// accesses.
808#[derive(Clone, Copy)]
809struct RingRegion {
810    base: GuestPhysAddr,
811    size: usize,
812}
813
814impl RingRegion {
815    fn new(base: GuestPhysAddr, size: usize) -> Self {
816        Self { base, size }
817    }
818
819    /// The exclusive end address, or `None` if `base + size` overflows the
820    /// guest address space.
821    fn end(&self) -> Option<usize> {
822        self.base.as_usize().checked_add(self.size)
823    }
824
825    /// Whether the two regions share any byte.
826    ///
827    /// A region whose end overflows the address space is unbounded; treating
828    /// it as non-overlapping would let a wrap-around ring alias the memory of
829    /// a neighbouring ring, so an overflowing region always overlaps.
830    fn overlaps(&self, other: &Self) -> bool {
831        let Some(self_end) = self.end() else {
832            return true;
833        };
834        let Some(other_end) = other.end() else {
835            return true;
836        };
837        self.base.as_usize() < other_end && other.base.as_usize() < self_end
838    }
839}