#![cfg(test)]
#![allow(unused_imports)]
use super::testing::*;
use super::*;
use std::io::{Seek, Write};
use std::num::NonZeroU64;
use std::os::unix::fs::FileExt;
use std::sync::atomic::Ordering;
use std::time::Instant;
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 process_requests_full_read_chain() {
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,
0, 0,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
512,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut data_buf = [0u8; 512];
mem.read_slice(&mut data_buf, data_addr).unwrap();
assert!(
data_buf.iter().all(|&b| b == 0xAB),
"data segment must contain backing file's 0xAB pattern after read",
);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_OK as u8,
"status byte must be S_OK after successful read",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1, "exactly one used-ring entry expected");
let c = dev.counters();
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
assert_eq!(c.bytes_read.load(Ordering::Relaxed), 512);
}
#[test]
fn process_requests_full_write_chain() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let f_for_verify = f.try_clone().expect("clone backing for verify");
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_OUT, 1);
let payload = vec![0xCDu8; 512];
mem.write_slice(&payload, data_addr).expect("plant payload");
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
512,
0, 0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut readback = [0u8; 512];
FileExt::read_at(&f_for_verify, &mut readback, 512).expect("read backing");
assert!(
readback.iter().all(|&b| b == 0xCD),
"backing file at sector 1 must hold the 0xCD payload after write",
);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(status_buf[0], VIRTIO_BLK_S_OK as u8);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1);
let c = dev.counters();
assert_eq!(c.writes_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.bytes_written.load(Ordering::Relaxed), 512);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
}
struct FaultBacking {
inner: File,
write_partials: std::cell::Cell<u32>,
write_zero: bool,
read_eintrs: std::cell::Cell<u32>,
eintr_forever: bool,
}
impl FaultBacking {
fn write_partial_then_complete(inner: File, partials: u32) -> Self {
Self {
inner,
write_partials: std::cell::Cell::new(partials),
write_zero: false,
read_eintrs: std::cell::Cell::new(0),
eintr_forever: false,
}
}
fn write_zero_progress(inner: File) -> Self {
Self {
inner,
write_partials: std::cell::Cell::new(0),
write_zero: true,
read_eintrs: std::cell::Cell::new(0),
eintr_forever: false,
}
}
fn read_eintr_then_complete(inner: File, eintrs: u32) -> Self {
Self {
inner,
write_partials: std::cell::Cell::new(0),
write_zero: false,
read_eintrs: std::cell::Cell::new(eintrs),
eintr_forever: false,
}
}
fn eintr_forever(inner: File) -> Self {
Self {
inner,
write_partials: std::cell::Cell::new(0),
write_zero: false,
read_eintrs: std::cell::Cell::new(0),
eintr_forever: true,
}
}
}
impl Backing for FaultBacking {
fn read_at(&self, buf: &mut [u8], offset: u64) -> std::io::Result<usize> {
Backing::read_at(&self.inner, buf, offset)
}
fn write_at(&self, buf: &[u8], offset: u64) -> std::io::Result<usize> {
Backing::write_at(&self.inner, buf, offset)
}
fn sync_data(&self) -> std::io::Result<()> {
Backing::sync_data(&self.inner)
}
unsafe fn preadv(&self, iovs: &[libc::iovec], offset: u64) -> std::io::Result<usize> {
if self.eintr_forever {
return Err(std::io::Error::from_raw_os_error(libc::EINTR));
}
if self.read_eintrs.get() > 0 {
self.read_eintrs.set(self.read_eintrs.get() - 1);
return Err(std::io::Error::from_raw_os_error(libc::EINTR));
}
unsafe { Backing::preadv(&self.inner, iovs, offset) }
}
unsafe fn pwritev(&self, iovs: &[libc::iovec], offset: u64) -> std::io::Result<usize> {
if self.eintr_forever {
return Err(std::io::Error::from_raw_os_error(libc::EINTR));
}
if self.write_zero {
return Ok(0);
}
if self.write_partials.get() > 0 && !iovs.is_empty() && iovs[0].iov_len > 1 {
self.write_partials.set(self.write_partials.get() - 1);
let first = iovs[0];
let partial = libc::iovec {
iov_base: first.iov_base,
iov_len: first.iov_len / 2,
};
return unsafe {
Backing::pwritev(&self.inner, std::slice::from_ref(&partial), offset)
};
}
unsafe { Backing::pwritev(&self.inner, iovs, offset) }
}
}
#[test]
fn process_requests_write_chain_short_write_retries_to_completion() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let f_for_verify = f.try_clone().expect("clone backing for verify");
let fault_inner = f.try_clone().expect("clone backing for fault mock");
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
dev.worker.state_mut().backing =
Box::new(FaultBacking::write_partial_then_complete(fault_inner, 1));
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_OUT, 1);
let payload = vec![0xCDu8; 512];
mem.write_slice(&payload, data_addr).expect("plant payload");
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(data_addr.0, 512, 0, 0)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut readback = [0u8; 512];
FileExt::read_at(&f_for_verify, &mut readback, 512).expect("read backing");
assert!(
readback.iter().all(|&b| b == 0xCD),
"full payload must land after a short-write retry, not a half-write",
);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_OK as u8,
"short-then-complete write is S_OK",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1);
let c = dev.counters();
assert_eq!(c.writes_completed.load(Ordering::Relaxed), 1);
assert_eq!(
c.bytes_written.load(Ordering::Relaxed),
512,
"bytes_written counts the full payload, not the partial first write",
);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_write_chain_zero_progress_is_ioerr() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let fault_inner = f.try_clone().expect("clone backing for fault mock");
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
dev.worker.state_mut().backing = Box::new(FaultBacking::write_zero_progress(fault_inner));
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_OUT, 1);
let payload = vec![0xCDu8; 512];
mem.write_slice(&payload, data_addr).expect("plant payload");
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(data_addr.0, 512, 0, 0)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_IOERR as u8,
"zero-progress write is S_IOERR",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1);
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(
c.writes_completed.load(Ordering::Relaxed),
0,
"a failed write is not a completion",
);
assert_eq!(c.bytes_written.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_read_chain_eintr_retries_to_completion() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0xAB);
let fault_inner = f.try_clone().expect("clone backing for fault mock");
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
dev.worker.state_mut().backing =
Box::new(FaultBacking::read_eintr_then_complete(fault_inner, 2));
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,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
512,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut data_buf = [0u8; 512];
mem.read_slice(&mut data_buf, data_addr).unwrap();
assert!(
data_buf.iter().all(|&b| b == 0xAB),
"data delivered after the EINTR retries",
);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(status_buf[0], VIRTIO_BLK_S_OK as u8);
let c = dev.counters();
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.bytes_read.load(Ordering::Relaxed), 512);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_read_chain_eintr_forever_hits_retry_cap() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0xAB);
let fault_inner = f.try_clone().expect("clone backing for fault mock");
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
dev.worker.state_mut().backing = Box::new(FaultBacking::eintr_forever(fault_inner));
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,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
512,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_IOERR as u8,
"unbounded EINTR fails with S_IOERR after the retry cap",
);
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_write_chain_all_zero_len_segments_is_zero_byte_ok() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
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 data0_addr = GuestAddress(0x5000);
let data1_addr = GuestAddress(0x5100);
let status_addr = GuestAddress(0x6000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_OUT, 1);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(data0_addr.0, 0, 0, 0)),
RawDescriptor::from(SplitDescriptor::new(data1_addr.0, 0, 0, 0)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_OK as u8,
"all-zero-len T_OUT must complete S_OK, not be rejected",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1, "chain marked used exactly once");
let c = dev.counters();
assert_eq!(
c.writes_completed.load(Ordering::Relaxed),
1,
"a 0-byte write counts as one completed write",
);
assert_eq!(
c.bytes_written.load(Ordering::Relaxed),
0,
"zero bytes written",
);
assert_eq!(
c.io_errors.load(Ordering::Relaxed),
0,
"all-zero-len chain is NOT an IO error",
);
}
#[test]
fn process_requests_read_chain_all_zero_len_segments_is_zero_byte_ok() {
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 data0_addr = GuestAddress(0x5000);
let data1_addr = GuestAddress(0x5100);
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,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data0_addr.0,
0,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data1_addr.0,
0,
VRING_DESC_F_WRITE as u16,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_OK as u8,
"all-zero-len T_IN must complete S_OK, not be rejected",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1, "chain marked used exactly once");
let c = dev.counters();
assert_eq!(
c.reads_completed.load(Ordering::Relaxed),
1,
"a 0-byte read counts as one completed read",
);
assert_eq!(c.bytes_read.load(Ordering::Relaxed), 0, "zero bytes read",);
assert_eq!(
c.io_errors.load(Ordering::Relaxed),
0,
"all-zero-len chain is NOT an IO error",
);
}
#[test]
fn process_requests_unknown_type_returns_unsupp() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
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 status_addr = GuestAddress(0x5000);
write_blk_header(&mem, header_addr, 0xBEEF, 0);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_UNSUPP as u8,
"unknown req_type must produce S_UNSUPP, not S_IOERR or S_OK",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1, "UNSUPP completions still update used.idx");
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.writes_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_flush_chain() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
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 status_addr = GuestAddress(0x5000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_FLUSH, 0);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(status_buf[0], VIRTIO_BLK_S_OK as u8);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1);
let c = dev.counters();
assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.writes_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_short_header_returns_ioerr() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
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 status_addr = GuestAddress(0x5000);
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
8, 0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], VIRTIO_BLK_S_IOERR as u8,
"short header must be rejected with S_IOERR",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 1);
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.writes_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn process_requests_status_not_writable_drops_chain() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
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 status_addr = GuestAddress(0x5000);
mem.write_slice(&[0xEEu8], status_addr).unwrap();
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,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
0, 0,
)),
];
mock.build_desc_chain(&descs).expect("build chain");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let mut status_buf = [0u8; 1];
mem.read_slice(&mut status_buf, status_addr).unwrap();
assert_eq!(
status_buf[0], 0xEE,
"no status descriptor → device must not write a status byte; \
sentinel 0xEE survives",
);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(
used_idx, 0,
"no-status chain must NOT advance used.idx; advancing would \
let the guest's stale in_hdr.status surface as \
BLK_STS_OK (silent data corruption)",
);
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(
dev.interrupt_status.load(Ordering::Acquire) & VIRTIO_MMIO_INT_VRING,
0
);
}
#[test]
fn process_requests_multiple_chains_drained_in_one_notify() {
use virtio_bindings::bindings::virtio_ring::VRING_DESC_F_NEXT;
let cap = 4096u64;
let mut f = tempfile().unwrap();
f.set_len(cap).unwrap();
f.write_all(&[0x11; 512]).unwrap(); f.write_all(&[0x22; 512]).unwrap(); f.write_all(&[0x33; 512]).unwrap(); f.rewind().unwrap();
let mut dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_chain_test_mem();
let mock = MockSplitQueue::create(&mem, GuestAddress(0), 16);
let chains = [
(
GuestAddress(0x4000),
GuestAddress(0x4400),
GuestAddress(0x4800),
0u64,
),
(
GuestAddress(0x5000),
GuestAddress(0x5400),
GuestAddress(0x5800),
1u64,
),
(
GuestAddress(0x6000),
GuestAddress(0x6400),
GuestAddress(0x6800),
2u64,
),
];
for &(hdr, _, _, sector) in &chains {
write_blk_header(&mem, hdr, VIRTIO_BLK_T_IN, sector);
}
let mut descs = Vec::new();
for (chain_i, &(hdr, data, status, _)) in chains.iter().enumerate() {
let base = (chain_i as u16) * 3;
descs.push(RawDescriptor::from(SplitDescriptor::new(
hdr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
VRING_DESC_F_NEXT as u16,
base + 1,
)));
descs.push(RawDescriptor::from(SplitDescriptor::new(
data.0,
512,
VRING_DESC_F_WRITE as u16 | VRING_DESC_F_NEXT as u16,
base + 2,
)));
descs.push(RawDescriptor::from(SplitDescriptor::new(
status.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)));
}
mock.add_desc_chains(&descs, 0).expect("add 3 chains");
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
write_reg(&mut dev, VIRTIO_MMIO_QUEUE_NOTIFY, REQ_QUEUE as u32);
let used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(used_idx, 3, "single notify must drain 3 chains");
let c = dev.counters();
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 3);
assert_eq!(c.bytes_read.load(Ordering::Relaxed), 3 * 512);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
for (i, &(_, data, _, _)) in chains.iter().enumerate() {
let mut buf = [0u8; 512];
mem.read_slice(&mut buf, data).unwrap();
let expected = (i as u8 + 1) * 0x11;
assert!(
buf.iter().all(|&b| b == expected),
"chain {i}'s data must hold sector {i}'s pattern (0x{expected:02X})",
);
}
}