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