#![cfg(test)]
#![allow(dead_code)]
use std::fs::File;
use std::io::{Seek, 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, Bytes, GuestAddress, GuestMemoryMmap};
use super::{
DiskThrottle, QUEUE_MAX_SIZE, S_ACK, S_DRV, S_FEAT, S_OK, VIRTIO_BLK_OUTHDR_SIZE,
VIRTIO_BLK_T_IN, VIRTIO_F_VERSION_1, VIRTIO_MMIO_DRIVER_FEATURES,
VIRTIO_MMIO_DRIVER_FEATURES_SEL, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, VIRTIO_MMIO_QUEUE_AVAIL_LOW,
VIRTIO_MMIO_QUEUE_DESC_HIGH, VIRTIO_MMIO_QUEUE_DESC_LOW, VIRTIO_MMIO_QUEUE_NUM,
VIRTIO_MMIO_QUEUE_READY, VIRTIO_MMIO_QUEUE_SEL, VIRTIO_MMIO_QUEUE_USED_HIGH,
VIRTIO_MMIO_QUEUE_USED_LOW, VIRTIO_MMIO_STATUS, VIRTIO_RING_F_EVENT_IDX, VirtioBlk,
VirtioBlkOutHdr,
};
pub(super) fn make_device(capacity_bytes: u64, throttle: DiskThrottle) -> VirtioBlk {
let mut f = tempfile().expect("create tempfile for virtio-blk test backing");
f.set_len(capacity_bytes)
.expect("set tempfile length to capacity_bytes — usually fails when TMPDIR is full");
f.rewind().expect("rewind tempfile after set_len");
VirtioBlk::new(f, capacity_bytes, throttle)
}
pub(super) fn read_reg(dev: &VirtioBlk, offset: u32) -> u32 {
let mut buf = [0u8; 4];
dev.mmio_read(offset as u64, &mut buf);
u32::from_le_bytes(buf)
}
pub(super) fn write_reg(dev: &mut VirtioBlk, offset: u32, val: u32) {
dev.mmio_write(offset as u64, &val.to_le_bytes());
}
pub(super) fn init_device(dev: &mut VirtioBlk) {
write_reg(dev, VIRTIO_MMIO_STATUS, S_ACK);
write_reg(dev, VIRTIO_MMIO_STATUS, S_DRV);
write_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 1);
write_reg(
dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1 << (VIRTIO_F_VERSION_1 - 32),
);
write_reg(dev, VIRTIO_MMIO_STATUS, S_FEAT);
write_reg(dev, VIRTIO_MMIO_STATUS, S_OK);
}
pub(super) fn make_guest_mem(bytes: usize) -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), bytes)]).expect("create test guest mem")
}
pub(super) fn make_backed_file_with_pattern(capacity: u64, pattern: u8) -> File {
let mut f = tempfile().unwrap();
f.set_len(capacity).unwrap();
f.rewind().unwrap();
let buf = vec![pattern; capacity as usize];
f.write_all(&buf).unwrap();
f.rewind().unwrap();
f
}
pub(super) fn write_blk_header(
mem: &GuestMemoryMmap,
header_addr: GuestAddress,
req_type: u32,
sector: u64,
) {
let hdr = VirtioBlkOutHdr {
type_: req_type,
_ioprio: 0,
sector,
};
mem.write_obj(hdr, header_addr).expect("plant header");
}
pub(super) fn wire_device_to_mock(dev: &mut VirtioBlk, mock: &MockSplitQueue<GuestMemoryMmap>) {
write_reg(dev, VIRTIO_MMIO_STATUS, S_ACK);
write_reg(dev, VIRTIO_MMIO_STATUS, S_DRV);
write_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 1);
write_reg(
dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1 << (VIRTIO_F_VERSION_1 - 32),
);
write_reg(dev, VIRTIO_MMIO_STATUS, S_FEAT);
write_reg(dev, VIRTIO_MMIO_QUEUE_SEL, 0);
write_reg(dev, VIRTIO_MMIO_QUEUE_NUM, QUEUE_MAX_SIZE as u32);
let desc = mock.desc_table_addr().0;
let avail = mock.avail_addr().0;
let used = mock.used_addr().0;
write_reg(dev, VIRTIO_MMIO_QUEUE_DESC_LOW, desc as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH, (desc >> 32) as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_LOW, avail as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, (avail >> 32) as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_USED_LOW, used as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_USED_HIGH, (used >> 32) as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_READY, 1);
write_reg(dev, VIRTIO_MMIO_STATUS, S_OK);
let status_now = dev.device_status.load(Ordering::Acquire);
assert_eq!(
status_now, S_OK,
"wire_device_to_mock: FSM did not reach DRIVER_OK \
(got {:#x}) — feature negotiation likely regressed",
status_now,
);
}
pub(super) fn wire_device_to_mock_with_event_idx(
dev: &mut VirtioBlk,
mock: &MockSplitQueue<GuestMemoryMmap>,
queue_size: u16,
used_override_addr: GuestAddress,
) {
write_reg(dev, VIRTIO_MMIO_STATUS, S_ACK);
write_reg(dev, VIRTIO_MMIO_STATUS, S_DRV);
write_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 0);
write_reg(
dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1u32 << VIRTIO_RING_F_EVENT_IDX,
);
write_reg(dev, VIRTIO_MMIO_DRIVER_FEATURES_SEL, 1);
write_reg(
dev,
VIRTIO_MMIO_DRIVER_FEATURES,
1 << (VIRTIO_F_VERSION_1 - 32),
);
write_reg(dev, VIRTIO_MMIO_STATUS, S_FEAT);
write_reg(dev, VIRTIO_MMIO_QUEUE_SEL, 0);
write_reg(dev, VIRTIO_MMIO_QUEUE_NUM, queue_size as u32);
let desc = mock.desc_table_addr().0;
let avail = mock.avail_addr().0;
let used = used_override_addr.0;
write_reg(dev, VIRTIO_MMIO_QUEUE_DESC_LOW, desc as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_DESC_HIGH, (desc >> 32) as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_LOW, avail as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, (avail >> 32) as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_USED_LOW, used as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_USED_HIGH, (used >> 32) as u32);
write_reg(dev, VIRTIO_MMIO_QUEUE_READY, 1);
write_reg(dev, VIRTIO_MMIO_STATUS, S_OK);
let status_now = dev.device_status.load(Ordering::Acquire);
assert_eq!(
status_now, S_OK,
"wire_device_to_mock_with_event_idx: FSM did not reach \
DRIVER_OK (got {:#x})",
status_now,
);
assert_ne!(
dev.driver_features & (1u64 << VIRTIO_RING_F_EVENT_IDX),
0,
"VIRTIO_RING_F_EVENT_IDX missing from driver_features after \
wire_device_to_mock_with_event_idx",
);
}
pub(super) fn used_event_addr(avail_addr: GuestAddress, queue_size: u16) -> GuestAddress {
avail_addr
.checked_add(4 + queue_size as u64 * 2)
.expect("used_event_addr overflow")
}
pub(super) fn make_chain_test_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 1 << 20)])
.expect("create chain test guest mem")
}
pub(super) fn setup_blk<'a>(
mem: &'a GuestMemoryMmap,
read_only: bool,
throttle: DiskThrottle,
) -> (VirtioBlk, MockSplitQueue<'a, GuestMemoryMmap>) {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0xAB);
let dev = VirtioBlk::with_options(f, cap, throttle, read_only);
let mock = MockSplitQueue::create(mem, GuestAddress(0), 16);
(dev, mock)
}
pub(super) fn setup_iops1_drained_chain(mem: &GuestMemoryMmap) -> VirtioBlk {
let throttle = DiskThrottle {
iops: std::num::NonZeroU64::new(1),
bytes_per_sec: None,
iops_burst_capacity: None,
bytes_burst_capacity: None,
};
let (mut dev, mock) = setup_blk(mem, false, 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),
"drain the 1-token bucket on the freshly-built device",
);
dev.worker
.state_mut()
.ops_bucket
.set_last_refill_for_test(std::time::Instant::now());
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);
dev.worker
.state_mut()
.ops_bucket
.set_last_refill_for_test(std::time::Instant::now());
dev
}
pub(super) fn setup_bytes_only_drained_chain(
mem: &GuestMemoryMmap,
iops_rate: u64,
bytes_rate: u64,
) -> VirtioBlk {
let throttle = DiskThrottle {
iops: std::num::NonZeroU64::new(iops_rate),
bytes_per_sec: std::num::NonZeroU64::new(bytes_rate),
iops_burst_capacity: None,
bytes_burst_capacity: None,
};
let (mut dev, mock) = setup_blk(mem, false, throttle);
let now0 = std::time::Instant::now();
dev.worker
.state_mut()
.ops_bucket
.set_last_refill_for_test(now0);
dev.worker
.state_mut()
.bytes_bucket
.set_last_refill_for_test(now0);
assert!(
dev.worker.state_mut().bytes_bucket.consume(bytes_rate),
"drain the bytes bucket on the freshly-built device",
);
dev.worker
.state_mut()
.bytes_bucket
.set_last_refill_for_test(now0);
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 data_len_u32 =
u32::try_from(bytes_rate).expect("bytes_rate fits in a single descriptor for tests");
let descs = [
RawDescriptor::from(SplitDescriptor::new(
header_addr.0,
VIRTIO_BLK_OUTHDR_SIZE as u32,
0,
0,
)),
RawDescriptor::from(SplitDescriptor::new(
data_addr.0,
data_len_u32,
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);
let now1 = std::time::Instant::now();
dev.worker
.state_mut()
.ops_bucket
.set_last_refill_for_test(now1);
dev.worker
.state_mut()
.bytes_bucket
.set_last_refill_for_test(now1);
dev
}