#![cfg(test)]
#![allow(unused_imports)]
use super::testing::*;
use super::*;
use std::io::Write;
use std::sync::atomic::Ordering;
use tempfile::tempfile;
use virtio_bindings::bindings::virtio_ring::VRING_DESC_F_WRITE;
use virtio_queue::desc::{RawDescriptor, split::Descriptor as SplitDescriptor};
use virtio_queue::mock::MockSplitQueue;
use vm_memory::Address;
#[test]
fn hostile_avail_idx_poisons_queue_until_reset() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0xAB);
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let header_addr = GuestAddress(0x4000);
let data_addr = GuestAddress(0x5000);
let status_addr = GuestAddress(0x6000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_IN, 0);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
virtio_bindings::bindings::virtio_ring::VRING_DESC_F_NEXT as u16,
1,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
512,
VRING_DESC_F_WRITE as u16
| virtio_bindings::bindings::virtio_ring::VRING_DESC_F_NEXT as u16,
2,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs)
.expect("build chain (consumed by hostile-idx test)");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
assert_eq!(
dev.counters().invalid_avail_idx_count(),
0,
"fresh device must have zero InvalidAvailRingIndex events",
);
let avail_idx_addr = mock.avail_addr().checked_add(2).unwrap();
mem.write_obj(1000u16, avail_idx_addr).unwrap();
let pre_reads = dev.counters().reads_completed();
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
assert_eq!(
dev.counters().invalid_avail_idx_count(),
1,
"first hostile-idx kick must bump invalid_avail_idx_count exactly once",
);
assert_eq!(
dev.counters().reads_completed(),
pre_reads,
"no reads must be serviced — the poisoned queue is structurally broken",
);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
assert_eq!(
dev.counters().invalid_avail_idx_count(),
1,
"subsequent kicks against a poisoned queue MUST NOT \
re-bump the counter — the per-event semantics rely on \
the queue_poisoned flag short-circuiting before the \
iter() call",
);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, 0);
let used_idx_addr = mock.used_addr().checked_add(2).unwrap();
mem.write_obj(0u16, avail_idx_addr).unwrap();
mem.write_obj(0u16, used_idx_addr).unwrap();
mem.write_slice(&[0xEEu8], status_addr).unwrap();
mock.build_desc_chain(&descs)
.expect("build chain post-reset");
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(
s[0], VIRTIO_BLK_S_OK as u8,
"post-reset chain must complete S_OK — the queue_poisoned \
flag must have cleared in reset_engine_inline",
);
assert_eq!(
dev.counters().reads_completed(),
pre_reads + 1,
"post-reset chain must bump reads_completed",
);
assert_eq!(
dev.counters().invalid_avail_idx_count(),
1,
"invalid_avail_idx_count is cumulative across reset; only \
the per-worker poison flag clears",
);
}
#[test]
fn poison_signals_needs_reset_int_config_and_irqfd() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0xCD);
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let header_addr = GuestAddress(0x4000);
let data_addr = GuestAddress(0x5000);
let status_addr = GuestAddress(0x6000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_IN, 0);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
virtio_bindings::bindings::virtio_ring::VRING_DESC_F_NEXT as u16,
1,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
512,
VRING_DESC_F_WRITE as u16
| virtio_bindings::bindings::virtio_ring::VRING_DESC_F_NEXT as u16,
2,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs)
.expect("build chain (consumed by signal-on-poison test)");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
assert_eq!(
dev.device_status.load(Ordering::Acquire) & VIRTIO_CONFIG_S_NEEDS_RESET,
0,
"fresh device must not have NEEDS_RESET set",
);
assert_eq!(
dev.interrupt_status.load(Ordering::Acquire) & VIRTIO_MMIO_INT_CONFIG,
0,
"fresh device must not have INT_CONFIG set",
);
let avail_idx_addr = mock.avail_addr().checked_add(2).unwrap();
mem.write_obj(1000u16, avail_idx_addr).unwrap();
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
assert_ne!(
dev.device_status.load(Ordering::Acquire) & VIRTIO_CONFIG_S_NEEDS_RESET,
0,
"queue-poison path must set VIRTIO_CONFIG_S_NEEDS_RESET \
so a STATUS read surfaces the wedged state",
);
assert_ne!(
dev.interrupt_status.load(Ordering::Acquire) & VIRTIO_MMIO_INT_CONFIG,
0,
"queue-poison path must set VIRTIO_MMIO_INT_CONFIG so \
the guest's vm_interrupt dispatches the config-change \
callback on the next IRQ delivery",
);
assert!(
dev.irq_evt().read().is_ok(),
"queue-poison path must signal irq_evt; a missed write \
would prevent the guest's vm_interrupt from running",
);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
assert!(
dev.irq_evt().read().is_err(),
"re-kick of a poisoned queue must NOT re-fire the irqfd \
— the queue_poisoned gate short-circuits before the \
InvalidAvailRingIndex arm runs again",
);
assert_ne!(
dev.device_status.load(Ordering::Acquire) & VIRTIO_CONFIG_S_NEEDS_RESET,
0,
"NEEDS_RESET stays set across re-kicks until reset",
);
assert_ne!(
dev.interrupt_status.load(Ordering::Acquire) & VIRTIO_MMIO_INT_CONFIG,
0,
"INT_CONFIG stays set until the guest acknowledges via \
INTERRUPT_ACK or a STATUS=0 reset clears it",
);
write_reg(&mut dev, VIRTIO_MMIO_STATUS, 0);
assert_eq!(
dev.device_status.load(Ordering::Acquire) & VIRTIO_CONFIG_S_NEEDS_RESET,
0,
"STATUS=0 reset must clear NEEDS_RESET — the guest's \
re-bind FSM walk needs a clean slate",
);
assert_eq!(
dev.interrupt_status.load(Ordering::Acquire) & VIRTIO_MMIO_INT_CONFIG,
0,
"STATUS=0 reset must clear INT_CONFIG too; otherwise a \
post-reset spurious IRQ would re-deliver the bit",
);
}