extern crate ax_runtime as _;
use std::sync::Arc;
use ax_memory_addr::PhysAddr;
use axaddrspace::GuestMemoryAccessor;
use axvirtio_common::{VirtioError, VirtioQueue, constants::*};
use axvm_types::GuestPhysAddr;
#[derive(Clone)]
struct MockMem {
buf: std::vec::Vec<u8>,
}
impl MockMem {
fn new(len: usize) -> Self {
Self {
buf: vec![0u8; len],
}
}
fn put(&self, off: usize, bytes: &[u8]) {
self.write_buffer(GuestPhysAddr::from(off), bytes).unwrap();
}
}
impl GuestMemoryAccessor for MockMem {
fn translate_and_get_limit(&self, guest_addr: GuestPhysAddr) -> Option<(PhysAddr, usize)> {
let off = guest_addr.as_usize();
if off < self.buf.len() {
let host = self.buf.as_ptr() as usize + off;
Some((PhysAddr::from(host), self.buf.len() - off))
} else {
None
}
}
}
struct PublishAfterAvailEventWrite {
mem: Arc<MockMem>,
avail_event_addr: GuestPhysAddr,
avail_idx_addr: GuestPhysAddr,
published_idx: u16,
}
impl axvirtio_common::GuestMemory for PublishAfterAvailEventWrite {
fn read(
&mut self,
guest_addr: GuestPhysAddr,
data: &mut [u8],
) -> axvirtio_common::VirtioResult<()> {
self.mem
.read_buffer(guest_addr, data)
.map_err(|_| VirtioError::InvalidAddress)
}
fn write(
&mut self,
guest_addr: GuestPhysAddr,
data: &[u8],
) -> axvirtio_common::VirtioResult<()> {
self.mem
.write_buffer(guest_addr, data)
.map_err(|_| VirtioError::InvalidAddress)?;
if guest_addr == self.avail_event_addr {
self.mem.put(
self.avail_idx_addr.as_usize(),
&self.published_idx.to_le_bytes(),
);
}
Ok(())
}
}
struct Fixture {
mem: Arc<MockMem>,
queue: VirtioQueue<MockMem>,
size: u16,
desc_base: usize,
avail_base: usize,
used_base: usize,
}
fn layout(size: u16) -> (usize, usize, usize, usize) {
let desc = 0x1000usize;
let desc_size = size as usize * 16;
let avail = round_up(desc + desc_size, 16);
let avail_size = 4 + size as usize * 2 + 2;
let used = round_up(avail + avail_size, 16);
let used_size = 4 + size as usize * 8 + 2;
let total = round_up(used + used_size, 16);
(desc, avail, used, total)
}
fn round_up(v: usize, a: usize) -> usize {
v.div_ceil(a) * a
}
impl Fixture {
fn new(size: u16) -> Self {
let (desc_base, avail_base, used_base, total) = layout(size);
let mem = Arc::new(MockMem::new(total));
let mut queue = VirtioQueue::new(0, size, mem.clone());
queue
.set_desc_table_addr(GuestPhysAddr::from(desc_base))
.unwrap();
queue
.set_avail_ring_addr(GuestPhysAddr::from(avail_base))
.unwrap();
queue
.set_used_ring_addr(GuestPhysAddr::from(used_base))
.unwrap();
queue.set_ready(true);
Self {
mem,
queue,
size,
desc_base,
avail_base,
used_base,
}
}
fn set_desc(&self, index: u16, addr: usize, len: u32, flags: u16, next: u16) {
let off = self.desc_base + index as usize * 16;
let mut b = [0u8; 16];
b[0..8].copy_from_slice(&(addr as u64).to_le_bytes());
b[8..12].copy_from_slice(&len.to_le_bytes());
b[12..14].copy_from_slice(&flags.to_le_bytes());
b[14..16].copy_from_slice(&next.to_le_bytes());
self.mem.put(off, &b);
}
fn set_avail_idx(&self, idx: u16) {
self.mem.put(self.avail_base + 2, &idx.to_le_bytes());
}
fn set_avail_entry(&self, pos: u16, head: u16) {
let off = self.avail_base + 4 + pos as usize * 2;
self.mem.put(off, &head.to_le_bytes());
}
fn set_avail_flags(&self, flags: u16) {
self.mem.put(self.avail_base, &flags.to_le_bytes());
}
fn set_used_event(&self, event: u16) {
let off = self.avail_base + 4 + self.size as usize * 2;
self.mem.put(off, &event.to_le_bytes());
}
fn used_idx(&self) -> u16 {
let off = self.used_base + 2;
u16::from_le_bytes([self.mem.buf[off], self.mem.buf[off + 1]])
}
fn avail_event(&self) -> u16 {
let off = self.used_base + 4 + self.size as usize * 8;
u16::from_le_bytes([self.mem.buf[off], self.mem.buf[off + 1]])
}
fn used_elem(&self, pos: u16) -> (u32, u32) {
let off = self.used_base + 4 + pos as usize * 8;
let id = u32::from_le_bytes(self.mem.buf[off..off + 4].try_into().unwrap());
let len = u32::from_le_bytes(self.mem.buf[off + 4..off + 8].try_into().unwrap());
(id, len)
}
}
#[test]
fn set_size_rejects_zero_non_pow2_and_too_large() {
let mem = Arc::new(MockMem::new(4096));
let mut q = VirtioQueue::new(0, 8, mem);
assert_eq!(q.set_size(0).unwrap_err(), VirtioError::InvalidQueue);
assert_eq!(q.set_size(3).unwrap_err(), VirtioError::InvalidQueue); assert_eq!(q.set_size(16).unwrap_err(), VirtioError::InvalidQueue); q.set_size(8).unwrap(); }
#[test]
fn address_setters_overwrite_so_low_high_combine() {
let mem = Arc::new(MockMem::new(4096));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x0000_00ff))
.unwrap();
q.set_desc_table_addr(GuestPhysAddr::from(0x1234_0000_0000_00ff))
.unwrap();
assert_eq!(
q.desc_table_addr.as_usize(),
0x1234_0000_0000_00ff,
"second write must overwrite, not be rejected"
);
q.set_avail_ring_addr(GuestPhysAddr::from(0x1234_0000_0000_0000))
.unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x0000_00ab))
.unwrap();
assert_eq!(q.avail_ring_addr.as_usize(), 0x0000_00ab);
}
#[test]
fn setter_combined_layout_fails_alignment_validation() {
let mem = Arc::new(MockMem::new(4096));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x0000_00ff))
.unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
assert_eq!(
q.validate_layout().unwrap_err(),
VirtioError::RingMisaligned,
"the desc table address 0x..ff is not 16-byte aligned"
);
}
#[test]
fn set_size_after_ring_addresses_is_rejected() {
let mem = Arc::new(MockMem::new(4096));
let mut q = VirtioQueue::new(0, 8, mem.clone());
q.set_size(4).unwrap();
q.set_desc_table_addr(GuestPhysAddr::from(0x1000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
assert_eq!(
q.set_size(8).unwrap_err(),
VirtioError::InvalidQueue,
"resizing after ring addresses are programmed must be rejected"
);
assert_eq!(q.size, 4, "the size must not change on a rejected write");
let mut f = Fixture::new(4);
f.queue.set_ready(true);
assert_eq!(f.queue.set_size(4).unwrap_err(), VirtioError::InvalidQueue);
assert!(f.queue.is_valid(), "the ready queue must stay usable");
}
#[test]
fn reset_clears_addresses_ready_and_indexes() {
let mut f = Fixture::new(4);
f.set_avail_idx(2);
f.queue.pop_available_head().unwrap();
f.queue.reset();
assert!(!f.queue.ready);
assert_eq!(f.queue.desc_table_addr.as_usize(), 0);
assert_eq!(f.queue.avail_ring_addr.as_usize(), 0);
assert_eq!(f.queue.used_ring_addr.as_usize(), 0);
assert_eq!(f.queue.get_last_avail_idx(), 0);
}
#[test]
fn pop_available_head_empty_returns_none() {
let mut f = Fixture::new(4);
f.set_avail_idx(0); assert!(f.queue.pop_available_head().unwrap().is_none());
}
#[test]
fn pop_available_head_consumes_in_order() {
let mut f = Fixture::new(4);
f.set_desc(0, 0x1000, 16, 0, 0);
f.set_desc(1, 0x2000, 16, 0, 0);
f.set_avail_entry(0, 0);
f.set_avail_entry(1, 1);
f.set_avail_idx(2);
assert_eq!(f.queue.pop_available_head().unwrap(), Some(0));
assert_eq!(f.queue.pop_available_head().unwrap(), Some(1));
assert!(f.queue.pop_available_head().unwrap().is_none());
}
#[test]
fn pop_available_head_wraps_u16_index() {
let mut f = Fixture::new(4);
f.set_desc(0, 0x1000, 16, 0, 0);
f.set_avail_entry(0, 0);
f.queue.update_last_avail_idx(u16::MAX);
f.set_avail_idx(0);
assert_eq!(f.queue.pop_available_head().unwrap(), Some(0));
assert_eq!(f.queue.get_last_avail_idx(), 0);
}
#[test]
fn event_idx_rearm_publishes_the_next_expected_available_index() {
let mut f = Fixture::new(4);
f.queue.event_idx_enabled = true;
f.queue.update_last_avail_idx(2);
f.set_avail_idx(2);
assert!(!f.queue.rearm_available_event().unwrap());
assert_eq!(f.avail_event(), 2);
}
#[test]
fn event_idx_rearm_detects_buffers_published_before_the_recheck() {
let mut f = Fixture::new(4);
f.queue.event_idx_enabled = true;
f.queue.update_last_avail_idx(2);
f.set_avail_idx(2);
let mut memory = PublishAfterAvailEventWrite {
mem: Arc::clone(&f.mem),
avail_event_addr: GuestPhysAddr::from(f.used_base + 4 + f.size as usize * 8),
avail_idx_addr: GuestPhysAddr::from(f.avail_base + 2),
published_idx: 3,
};
assert!(
f.queue
.rearm_available_event_with_memory(&mut memory)
.unwrap()
);
assert_eq!(f.avail_event(), 2);
}
#[test]
fn event_idx_rearm_detects_available_index_wraparound() {
let mut f = Fixture::new(4);
f.queue.event_idx_enabled = true;
f.queue.update_last_avail_idx(u16::MAX);
f.set_avail_idx(0);
assert!(f.queue.rearm_available_event().unwrap());
assert_eq!(f.avail_event(), u16::MAX);
}
#[test]
fn pop_available_head_detects_ring_corruption() {
let mut f = Fixture::new(4);
f.set_avail_idx(f.size + 5);
assert_eq!(
f.queue.pop_available_head().unwrap_err(),
VirtioError::InvalidQueue
);
}
#[test]
fn descriptor_chain_single_and_multi() {
let f = Fixture::new(4);
f.set_desc(0, 0x1000, 8, VIRTQ_DESC_F_NEXT, 1);
f.set_desc(1, 0x2000, 12, VIRTQ_DESC_F_WRITE, 0);
let chain = f.queue.descriptor_chain(0).unwrap();
assert_eq!(chain.head(), 0);
assert_eq!(chain.len(), 2);
assert_eq!(chain.readable_len().unwrap(), 8); assert_eq!(chain.writable_len().unwrap(), 12); }
#[test]
fn descriptor_chain_rejects_indirect() {
let f = Fixture::new(4);
f.set_desc(
0,
0x1000,
16,
axvirtio_common::constants::VIRTQ_DESC_F_INDIRECT,
0,
);
assert_eq!(
f.queue.descriptor_chain(0).unwrap_err(),
VirtioError::NotSupported
);
}
#[test]
fn descriptor_chain_rejects_cycle() {
let f = Fixture::new(4);
f.set_desc(0, 0x1000, 8, VIRTQ_DESC_F_NEXT, 1);
f.set_desc(1, 0x2000, 8, VIRTQ_DESC_F_NEXT, 0);
assert!(matches!(
f.queue.descriptor_chain(0),
Err(VirtioError::InvalidDescriptor)
));
}
#[test]
fn descriptor_chain_rejects_next_out_of_bounds() {
let f = Fixture::new(4);
f.set_desc(0, 0x1000, 8, VIRTQ_DESC_F_NEXT, 99);
assert_eq!(
f.queue.descriptor_chain(0).unwrap_err(),
VirtioError::InvalidDescriptor
);
}
#[test]
fn descriptor_chain_accepts_full_size_chain() {
let f = Fixture::new(4);
for i in 0..4u16 {
let next = if i == 3 { 0 } else { i + 1 };
let flags = if i == 3 { 0 } else { VIRTQ_DESC_F_NEXT };
f.set_desc(i, 0x1000 + i as usize * 16, 8, flags, next);
}
let chain = f.queue.descriptor_chain(0).unwrap();
assert_eq!(
chain.len(),
4,
"a full-size chain (len == size) must be accepted"
);
f.set_desc(3, 0x1000 + 3 * 16, 8, VIRTQ_DESC_F_NEXT, 0); assert!(matches!(
f.queue.descriptor_chain(0),
Err(VirtioError::InvalidDescriptor)
));
}
#[test]
fn validate_layout_rejects_zero_addresses() {
let mem = Arc::new(MockMem::new(4096));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
assert_eq!(
q.validate_layout().unwrap_err(),
VirtioError::InvalidRingLayout
);
q.set_ready(true);
assert!(!q.is_valid());
}
#[test]
fn validate_layout_rejects_misaligned_rings() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x1000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3003)).unwrap();
assert_eq!(
q.validate_layout().unwrap_err(),
VirtioError::RingMisaligned
);
}
#[test]
fn validate_layout_rejects_overlapping_rings() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x1000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x1008)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
assert_eq!(q.validate_layout().unwrap_err(), VirtioError::RingOverlap);
}
#[test]
fn validate_layout_rejects_used_ring_aliasing_avail_footer() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x1000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x200c)).unwrap();
assert_eq!(q.validate_layout().unwrap_err(), VirtioError::RingOverlap);
}
#[test]
fn validate_layout_rejects_avail_ring_aliasing_used_footer() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x1000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x3024)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
assert_eq!(q.validate_layout().unwrap_err(), VirtioError::RingOverlap);
}
#[test]
fn validate_layout_rejects_overflowing_ring() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(usize::MAX - 15))
.unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
assert_eq!(
q.validate_layout().unwrap_err(),
VirtioError::InvalidRingLayout
);
}
#[test]
fn validate_layout_accepts_proper_layout() {
let f = Fixture::new(4);
assert!(f.queue.validate_layout().is_ok());
}
#[test]
fn validate_layout_rejects_overflowing_region_adjacent_to_valid_ring() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(usize::MAX - 15))
.unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
assert_eq!(
q.validate_layout().unwrap_err(),
VirtioError::InvalidRingLayout
);
}
#[test]
fn descriptor_chain_failure_faults_queue_and_blocks_pop_complete() {
let mut f = Fixture::new(4);
f.set_desc(0, 0x1000, 8, VIRTQ_DESC_F_NEXT, 1);
f.set_desc(1, 0x2000, 8, VIRTQ_DESC_F_NEXT, 0);
assert!(matches!(
f.queue.descriptor_chain(0),
Err(VirtioError::InvalidDescriptor)
));
assert!(
f.queue.is_faulted(),
"validation failure must fault the queue"
);
assert_eq!(
f.queue.pop_available_head().unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.complete(0, 0).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.used_idx(),
0,
"no used element may be written while faulted"
);
f.queue.reset();
assert!(!f.queue.is_faulted());
f.queue
.set_desc_table_addr(GuestPhysAddr::from(f.desc_base))
.unwrap();
f.queue
.set_avail_ring_addr(GuestPhysAddr::from(f.avail_base))
.unwrap();
f.queue
.set_used_ring_addr(GuestPhysAddr::from(f.used_base))
.unwrap();
f.queue.set_ready(true);
f.set_desc(0, 0x1000, 8, 0, 0);
f.set_avail_idx(1);
f.set_avail_entry(0, 0);
assert!(f.queue.pop_available().is_ok());
assert_eq!(f.used_idx(), 0);
assert!(f.queue.complete(3, 42).is_ok());
assert_eq!(f.used_idx(), 1, "used ring must advance after reset");
assert_eq!(f.used_elem(0), (3, 42));
}
#[test]
fn unconfigured_queue_failures_do_not_fault() {
let mem = Arc::new(MockMem::new(4096));
let mut q = VirtioQueue::new(0, 4, mem);
assert_eq!(
q.descriptor_chain(0).unwrap_err(),
VirtioError::QueueNotReady
);
assert_eq!(
q.get_status_addr(0).unwrap_err(),
VirtioError::QueueNotReady
);
assert_eq!(q.should_notify().unwrap_err(), VirtioError::QueueNotReady);
assert!(!q.is_faulted(), "unconfigured must not latch a fault");
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
assert_eq!(q.should_notify().unwrap_err(), VirtioError::InvalidAddress);
assert!(q.is_faulted());
assert_eq!(
q.descriptor_chain(0).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(q.should_notify().unwrap_err(), VirtioError::QueueFaulted);
}
#[test]
fn descriptor_chain_memory_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x100));
let mut q = VirtioQueue::new(0, 4, mem.clone());
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap(); q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
let mut memory = axvirtio_common::AddressSpaceMemory::new(&*mem);
assert_eq!(
q.descriptor_chain_with_memory(0, &mut memory).unwrap_err(),
VirtioError::InvalidAddress
);
assert!(q.is_faulted(), "memory read failure must fault the queue");
assert_eq!(
q.complete_with_memory(0, 0, &mut memory).unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn pop_available_head_read_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x3005));
let mut q = VirtioQueue::new(0, 4, mem.clone());
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap(); q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
let mut memory = axvirtio_common::AddressSpaceMemory::new(&*mem);
assert!(
q.pop_available_head_with_memory(&mut memory)
.unwrap()
.is_none()
);
assert!(!q.is_faulted());
mem.put(0x3002, &1u16.to_le_bytes());
assert_eq!(
q.pop_available_head_with_memory(&mut memory).unwrap_err(),
VirtioError::InvalidAddress
);
assert!(q.is_faulted(), "head read failure must fault the queue");
}
#[test]
fn read_avail_idx_pre_read_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x3002));
let mut q = VirtioQueue::new(0, 4, mem.clone());
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
let mut memory = axvirtio_common::AddressSpaceMemory::new(&*mem);
assert_eq!(
q.read_avail_idx_with_memory(&mut memory).unwrap_err(),
VirtioError::InvalidAddress
);
assert!(
q.is_faulted(),
"avail-index pre-read failure on a configured queue must fault it"
);
assert_eq!(
q.read_avail_idx_with_memory(&mut memory).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
q.pop_available_head_with_memory(&mut memory).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
q.read_avail_entry_with_memory(0, &mut memory).unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn read_avail_entry_pre_read_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x3004));
let mut q = VirtioQueue::new(0, 4, mem.clone());
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
let mut memory = axvirtio_common::AddressSpaceMemory::new(&*mem);
assert_eq!(
q.read_avail_entry_with_memory(0, &mut memory).unwrap_err(),
VirtioError::InvalidAddress
);
assert!(
q.is_faulted(),
"avail-entry pre-read failure on a configured queue must fault it"
);
assert_eq!(
q.read_avail_entry_with_memory(0, &mut memory).unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn read_avail_idx_non_memory_read_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x3002));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
assert_eq!(q.read_avail_idx().unwrap_err(), VirtioError::InvalidAddress);
assert!(
q.is_faulted(),
"avail-index read failure through the non-memory API must fault the queue"
);
assert_eq!(q.read_avail_idx().unwrap_err(), VirtioError::QueueFaulted);
}
#[test]
fn read_avail_entry_non_memory_read_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x3004));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
assert_eq!(
q.read_avail_entry(0).unwrap_err(),
VirtioError::InvalidAddress
);
assert!(
q.is_faulted(),
"avail-entry read failure through the non-memory API must fault the queue"
);
assert_eq!(
q.read_avail_entry(0).unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn add_used_write_failure_faults_queue() {
let mem = Arc::new(MockMem::new(0x100));
let mut q = VirtioQueue::new(0, 4, mem.clone());
q.set_desc_table_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x3000)).unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(0x4000)).unwrap();
q.set_ready(true);
assert_eq!(q.add_used(0, 0).unwrap_err(), VirtioError::InvalidAddress);
assert!(
q.is_faulted(),
"used-ring write failure must fault the queue"
);
assert_eq!(q.add_used(0, 0).unwrap_err(), VirtioError::QueueFaulted);
assert_eq!(q.complete(0, 0).unwrap_err(), VirtioError::QueueFaulted);
}
#[test]
fn add_used_without_used_ring_reports_not_ready_without_faulting() {
let mem = Arc::new(MockMem::new(0x1_0000));
let mut q = VirtioQueue::new(0, 4, mem);
q.set_desc_table_addr(GuestPhysAddr::from(0x1000)).unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(0x2000)).unwrap();
q.used_ring_addr = GuestPhysAddr::from(0x3000);
q.set_ready(true);
assert!(q.is_valid());
assert_eq!(q.add_used(0, 0).unwrap_err(), VirtioError::QueueNotReady);
assert!(
!q.is_faulted(),
"an unconfigured used ring is not a runtime failure"
);
assert!(q.get_used_ring().is_none());
}
#[test]
fn faulted_queue_rejects_guest_data_paths() {
let mut f = Fixture::new(4);
f.set_desc(0, 0x1000, 8, VIRTQ_DESC_F_NEXT, 1);
f.set_desc(1, 0x2000, 8, VIRTQ_DESC_F_NEXT, 0);
assert!(f.queue.descriptor_chain(0).is_err());
assert!(f.queue.is_faulted());
assert_eq!(
f.queue.get_status_addr(0).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.write_status_byte(0, 0).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue
.get_data_buffers(0, axvirtio_common::VirtioDeviceID::Block)
.unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.validate_virtio_block_chain(0, 1).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.should_notify().unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.pop_available_head().unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.write_status_byte(0, 0xab).unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn write_status_byte_writes_guest_status_and_completes() {
let (desc_base, avail_base, used_base, _) = layout(4);
let mem = Arc::new(MockMem::new(0x3000));
let mut q = VirtioQueue::new(0, 4, mem.clone());
q.set_desc_table_addr(GuestPhysAddr::from(desc_base))
.unwrap();
q.set_avail_ring_addr(GuestPhysAddr::from(avail_base))
.unwrap();
q.set_used_ring_addr(GuestPhysAddr::from(used_base))
.unwrap();
q.set_ready(true);
let mut d0 = [0u8; 16];
d0[0..8].copy_from_slice(&(0x1000u64).to_le_bytes());
d0[8..12].copy_from_slice(&8u32.to_le_bytes());
d0[12..14].copy_from_slice(&VIRTQ_DESC_F_NEXT.to_le_bytes());
d0[14..16].copy_from_slice(&1u16.to_le_bytes());
mem.put(desc_base, &d0);
let mut d1 = [0u8; 16];
d1[0..8].copy_from_slice(&(0x2000u64).to_le_bytes());
d1[8..12].copy_from_slice(&8u32.to_le_bytes());
d1[12..14].copy_from_slice(&VIRTQ_DESC_F_WRITE.to_le_bytes());
mem.put(desc_base + 16, &d1);
assert_eq!(q.get_status_addr(0).unwrap().as_usize(), 0x2000);
q.write_status_byte(0, 0xab).unwrap();
assert_eq!(
mem.buf[0x2000], 0xab,
"a healthy queue must write the status byte"
);
}
#[test]
fn faulted_check_precedes_ready_check_on_complete() {
let mut f = Fixture::new(4);
f.set_desc(0, 0x1000, 8, VIRTQ_DESC_F_NEXT, 1);
f.set_desc(1, 0x2000, 8, VIRTQ_DESC_F_NEXT, 0);
assert!(f.queue.descriptor_chain(0).is_err());
assert!(f.queue.is_faulted());
f.queue.set_ready(false); assert_eq!(
f.queue.complete(0, 0).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.add_used(0, 0).unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.pop_available_head().unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn pop_available_faults_queue_on_ring_corruption() {
let mut f = Fixture::new(4);
f.set_avail_idx(f.size + 5);
assert_eq!(
f.queue.pop_available_head().unwrap_err(),
VirtioError::InvalidQueue
);
assert!(f.queue.is_faulted());
assert_eq!(
f.queue.pop_available_head().unwrap_err(),
VirtioError::QueueFaulted
);
assert_eq!(
f.queue.complete(0, 0).unwrap_err(),
VirtioError::QueueFaulted
);
}
#[test]
fn complete_writes_used_element_and_advances_idx() {
let mut f = Fixture::new(4);
let notify = f.queue.complete(7, 128).unwrap();
assert!(notify, "without NO_INTERRUPT the driver must be notified");
assert_eq!(f.used_idx(), 1);
let (id, len) = f.used_elem(0);
assert_eq!(id, 7);
assert_eq!(len, 128);
}
#[test]
fn no_interrupt_flag_suppresses_notification() {
let mut f = Fixture::new(4);
f.set_avail_flags(VIRTQ_AVAIL_F_NO_INTERRUPT);
let notify = f.queue.complete(3, 64).unwrap();
assert_eq!(f.used_idx(), 1);
assert!(!notify);
}
#[test]
fn event_idx_notifies_only_at_the_requested_used_index() {
let mut f = Fixture::new(4);
f.queue.event_idx_enabled = true;
f.set_avail_flags(VIRTQ_AVAIL_F_NO_INTERRUPT);
f.set_used_event(0);
assert!(
f.queue.complete(3, 64).unwrap(),
"event_idx must ignore NO_INTERRUPT and notify for used_event 0"
);
assert!(
!f.queue.complete(2, 32).unwrap(),
"the next completion must stay suppressed until used_event advances"
);
}
#[test]
fn event_idx_batch_notification_uses_the_previous_check_index() {
let mut f = Fixture::new(4);
f.queue.event_idx_enabled = true;
f.set_used_event(0);
f.queue.add_used(1, 8).unwrap();
f.queue.add_used(2, 8).unwrap();
assert!(f.queue.should_notify().unwrap());
}
#[test]
fn complete_wraps_used_idx_at_queue_size() {
let mut f = Fixture::new(2);
f.queue.complete(1, 1).unwrap();
f.queue.complete(2, 2).unwrap();
assert_eq!(f.used_idx(), 2);
f.queue.complete(3, 3).unwrap();
assert_eq!(f.used_idx(), 3);
let (id, _) = f.used_elem(0);
assert_eq!(id, 3, "slot 0 reused after wrap");
}