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}