#![allow(unused_imports)]
use super::super::testing::*;
use super::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 seg_max_dropped_no_publish() {
use virtio_bindings::bindings::virtio_ring::VRING_DESC_F_NEXT;
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), 256);
dev.set_mem(mem.clone());
wire_device_to_mock(&mut dev, &mock);
let header_addr = GuestAddress(0x10000);
let status_addr = GuestAddress(0x20000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_IN, 0);
let total_descs: u16 = 132;
let data_descs: u16 = total_descs - 2;
let mut descs = Vec::new();
descs.push(RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
VRING_DESC_F_NEXT as u16,
1,
)));
for i in 0..data_descs {
descs.push(RawDescriptor::from(SplitDescriptor::new(
0x40000 + i as u64 * 8,
8,
VRING_DESC_F_WRITE as u16 | VRING_DESC_F_NEXT as u16,
i + 2,
)));
}
descs.push(RawDescriptor::from(SplitDescriptor::new(
status_addr.0,
1,
VRING_DESC_F_WRITE as u16,
0,
)));
mem.write_slice(&[0xEEu8], status_addr).unwrap();
mock.add_desc_chains(&descs, 0).expect("add chain");
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, 0, "SEG_MAX drop must NOT advance used.idx");
let c = dev.counters();
assert!(c.io_errors.load(Ordering::Relaxed) >= 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.throttled_count.load(Ordering::Relaxed), 0);
let mut s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(
s[0], 0xEE,
"SEG_MAX drop must leave status descriptor untouched",
);
}
#[test]
fn header_read_obj_failure_returns_ioerr() {
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(0x200000);
let status_addr = GuestAddress(0x4000);
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(
s[0], VIRTIO_BLK_S_IOERR as u8,
"header read failure must surface as 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!(c.io_errors.load(Ordering::Relaxed) >= 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn size_max_oversized_data_desc_rejected() {
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(0x8000);
let status_addr = GuestAddress(0x9000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_IN, 0);
let oversize: u32 = (1u32 << 20) + 1;
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
oversize,
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR 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.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(c.throttled_count.load(Ordering::Relaxed), 0);
}
#[test]
fn zero_data_t_in_returns_ioerr() {
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 status_addr = GuestAddress(0x5000);
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,
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR as u8);
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.throttled_count.load(Ordering::Relaxed), 0);
}
#[test]
fn sub_sector_data_len_returns_ioerr() {
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,
513,
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR as u8);
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.throttled_count.load(Ordering::Relaxed), 0);
}
#[test]
fn direction_violation_t_in_with_ro_data_returns_ioerr() {
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,
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR as u8);
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.throttled_count.load(Ordering::Relaxed),
0,
"direction violation must NOT touch throttle bucket",
);
}
#[test]
fn direction_violation_t_out_with_writable_data_returns_ioerr() {
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);
let sentinel = vec![0xCDu8; 512];
mem.write_slice(&sentinel, data_addr).unwrap();
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(
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR as u8);
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(
c.writes_completed.load(Ordering::Relaxed),
0,
"direction-violating T_OUT must NOT count as a completed write",
);
assert_eq!(
c.bytes_written.load(Ordering::Relaxed),
0,
"direction-violating T_OUT must NOT touch the backing file",
);
assert_eq!(
c.throttled_count.load(Ordering::Relaxed),
0,
"direction violation must NOT touch throttle bucket",
);
let mut data_check = vec![0u8; 512];
mem.read_slice(&mut data_check, data_addr).unwrap();
assert!(
data_check.iter().all(|&b| b == 0xCDu8),
"data segment sentinel must be intact — device must not run the read or write path",
);
}
#[test]
fn status_write_slice_failure_no_add_used() {
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 status_addr = GuestAddress(0x300000);
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 used_idx: u16 = mem
.read_obj(mock.used_addr().checked_add(2).unwrap())
.expect("read used.idx");
assert_eq!(
used_idx, 0,
"status write failure must skip add_used (status-write-success gate); used.idx stays at 0",
);
let c = dev.counters();
assert!(
c.io_errors.load(Ordering::Relaxed) >= 1,
"status write failure bumps io_errors",
);
}
#[test]
fn add_used_err_path_baseline_io_errors_zero() {
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 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),
0,
"successful add_used must NOT bump io_errors",
);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 1);
}
#[test]
fn ro_flush_full_chain_returns_ok_increments_counter() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let mut dev = VirtioBlk::with_options(f, cap, DiskThrottle::default(), true);
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_OK as u8);
let c = dev.counters();
assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
assert_eq!(c.throttled_count.load(Ordering::Relaxed), 0);
}
#[test]
fn multi_byte_status_writes_to_last_byte() {
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);
mem.write_slice(&[0xAA, 0xBB, 0xCC, 0xDD], status_addr)
.unwrap();
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,
4, 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 buf = [0u8; 4];
mem.read_slice(&mut buf, status_addr).unwrap();
assert_eq!(
buf[0], 0xAA,
"first byte of multi-byte status must be untouched"
);
assert_eq!(buf[1], 0xBB);
assert_eq!(buf[2], 0xCC);
assert_eq!(
buf[3], VIRTIO_BLK_S_OK as u8,
"status byte must be at the LAST byte (offset len-1)",
);
}
#[test]
fn zero_capacity_read_returns_ioerr() {
let cap = 0u64;
let f = tempfile().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 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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR as u8);
let c = dev.counters();
assert!(c.io_errors.load(Ordering::Relaxed) >= 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn partial_data_read_zero_pads_remainder() {
let cap = 4096u64;
let mut f = tempfile().unwrap();
f.set_len(100).unwrap();
f.write_all(&[0xA5; 100]).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 header_addr = GuestAddress(0x4000);
let data_addr = GuestAddress(0x5000);
let status_addr = GuestAddress(0x6000);
let pre = vec![0xFFu8; 512];
mem.write_slice(&pre, data_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(
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_OK as u8);
let mut buf = [0u8; 512];
mem.read_slice(&mut buf, data_addr).unwrap();
assert!(
buf[..100].iter().all(|&b| b == 0xA5),
"first 100 bytes must match backing file pattern",
);
assert!(
buf[100..].iter().all(|&b| b == 0),
"bytes 100..512 must be zero-padded",
);
}
#[test]
fn write_sector_overflow_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 data_addr = GuestAddress(0x5000);
let status_addr = GuestAddress(0x6000);
write_blk_header(&mem, header_addr, VIRTIO_BLK_T_OUT, u64::MAX);
let payload = vec![0xCDu8; 512];
mem.write_slice(&payload, data_addr).unwrap();
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 s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_IOERR as u8);
let c = dev.counters();
assert!(c.io_errors.load(Ordering::Relaxed) >= 1);
assert_eq!(c.writes_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn flush_sync_data_baseline_ok_path() {
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 c = dev.counters();
assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.io_errors.load(Ordering::Relaxed), 0);
}
#[test]
fn validation_gates_do_not_consume_throttle_tokens() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0xAB);
let throttle = DiskThrottle {
iops: std::num::NonZeroU64::new(1),
bytes_per_sec: None,
iops_burst_capacity: None,
bytes_burst_capacity: None,
};
let mut dev = VirtioBlk::new(f, cap, throttle);
dev.worker
.state_mut()
.ops_bucket
.set_last_refill_for_test(std::time::Instant::now());
assert!(dev.worker.state_mut().ops_bucket.consume(1));
dev.worker
.state_mut()
.ops_bucket
.set_last_refill_for_test(std::time::Instant::now());
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,
513, 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 c = dev.counters();
assert_eq!(
c.io_errors.load(Ordering::Relaxed),
1,
"sub-sector gate must bump io_errors",
);
assert_eq!(
c.throttled_count.load(Ordering::Relaxed),
0,
"validation gate must NOT consume throttle tokens; \
throttled_count must stay at 0 even with bucket drained",
);
}
#[test]
fn ro_flush_and_normal_flush_both_increment_counter() {
{
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);
assert_eq!(
dev.counters().flushes_completed.load(Ordering::Relaxed),
1,
"normal flush must increment flushes_completed",
);
}
{
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let mut dev = VirtioBlk::with_options(f, cap, DiskThrottle::default(), true);
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);
assert_eq!(
dev.counters().flushes_completed.load(Ordering::Relaxed),
1,
"RO flush must increment flushes_completed (counter symmetry)",
);
}
}