pub(crate) use std::fs::File;
#[cfg(not(test))]
pub(crate) use std::os::unix::io::AsRawFd;
pub(crate) use std::sync::Arc;
pub(crate) use std::sync::OnceLock;
pub(crate) use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
#[cfg(not(test))]
use std::thread;
pub(crate) use std::time::Duration;
pub(crate) use virtio_bindings::virtio_blk::{
VIRTIO_BLK_F_BLK_SIZE, VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_RO, VIRTIO_BLK_F_SEG_MAX,
VIRTIO_BLK_F_SIZE_MAX, VIRTIO_BLK_ID_BYTES, VIRTIO_BLK_S_IOERR, VIRTIO_BLK_S_OK,
VIRTIO_BLK_S_UNSUPP, VIRTIO_BLK_T_FLUSH, VIRTIO_BLK_T_GET_ID, VIRTIO_BLK_T_IN,
VIRTIO_BLK_T_OUT,
};
pub(crate) use virtio_bindings::virtio_config::{
VIRTIO_CONFIG_S_ACKNOWLEDGE, VIRTIO_CONFIG_S_DRIVER, VIRTIO_CONFIG_S_DRIVER_OK,
VIRTIO_CONFIG_S_FAILED, VIRTIO_CONFIG_S_FEATURES_OK, VIRTIO_CONFIG_S_NEEDS_RESET,
VIRTIO_F_VERSION_1,
};
pub(crate) use virtio_bindings::virtio_ids::VIRTIO_ID_BLOCK;
#[allow(unused_imports)]
pub(crate) use virtio_bindings::virtio_mmio::{
VIRTIO_MMIO_CONFIG_GENERATION, VIRTIO_MMIO_DEVICE_FEATURES, VIRTIO_MMIO_DEVICE_FEATURES_SEL,
VIRTIO_MMIO_DEVICE_ID, VIRTIO_MMIO_DRIVER_FEATURES, VIRTIO_MMIO_DRIVER_FEATURES_SEL,
VIRTIO_MMIO_INT_CONFIG, VIRTIO_MMIO_INT_VRING, VIRTIO_MMIO_INTERRUPT_ACK,
VIRTIO_MMIO_INTERRUPT_STATUS, VIRTIO_MMIO_MAGIC_VALUE, VIRTIO_MMIO_QUEUE_AVAIL_HIGH,
VIRTIO_MMIO_QUEUE_AVAIL_LOW, VIRTIO_MMIO_QUEUE_DESC_HIGH, VIRTIO_MMIO_QUEUE_DESC_LOW,
VIRTIO_MMIO_QUEUE_NOTIFY, VIRTIO_MMIO_QUEUE_NUM, VIRTIO_MMIO_QUEUE_NUM_MAX,
VIRTIO_MMIO_QUEUE_READY, VIRTIO_MMIO_QUEUE_SEL, VIRTIO_MMIO_QUEUE_USED_HIGH,
VIRTIO_MMIO_QUEUE_USED_LOW, VIRTIO_MMIO_STATUS, VIRTIO_MMIO_VENDOR_ID, VIRTIO_MMIO_VERSION,
};
pub(crate) use virtio_bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
#[allow(unused_imports)]
pub(crate) use virtio_queue::Error as VirtioQueueError;
#[cfg(test)]
use virtio_queue::Queue;
#[allow(unused_imports)]
pub(crate) use virtio_queue::QueueOwnedT;
#[cfg(not(test))]
use virtio_queue::QueueSync;
pub(crate) use virtio_queue::QueueT;
pub(crate) use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
use super::VirtioBlkCounters;
use super::{Backing, advance_iovecs};
#[allow(unused_imports)]
pub(crate) use vmm_sys_util::epoll::{EpollEvent, EventSet};
pub(crate) use vmm_sys_util::eventfd::EventFd;
pub(crate) use super::super::disk_config::DiskThrottle;
pub(crate) const MMIO_MAGIC: u32 = 0x7472_6976; pub(crate) const MMIO_VERSION: u32 = 2; pub(crate) const VENDOR_ID: u32 = 0;
pub const VIRTIO_MMIO_SIZE: u64 = 0x1000;
pub(crate) const NUM_QUEUES: usize = 1;
pub(crate) const QUEUE_MAX_SIZE: u16 = 256;
pub(crate) const REQ_QUEUE: usize = 0;
#[cfg(not(test))]
pub(crate) type BlkQueue = QueueSync;
#[cfg(test)]
pub(crate) type BlkQueue = Queue;
pub const VIRTIO_BLK_SECTOR_SIZE: u32 = 512;
#[allow(dead_code)]
pub const VIRTIO_BLK_DEFAULT_CAPACITY_BYTES: u64 = 256 * 1024 * 1024;
pub(crate) const VIRTIO_BLK_SEG_MAX: u32 = 128;
pub(crate) const VIRTIO_BLK_SIZE_MAX: u32 = 1 << 20;
const IOV_MAX: usize = 1024;
const MAX_EINTR_RETRIES: u32 = 16;
pub(crate) const VIRTIO_BLK_SERIAL: [u8; VIRTIO_BLK_ID_BYTES as usize] =
*b"ktstr-virtio-blk\0\0\0\0";
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct VirtioBlkOutHdr {
pub(crate) type_: u32,
pub(crate) _ioprio: u32,
pub(crate) sector: u64,
}
unsafe impl vm_memory::ByteValued for VirtioBlkOutHdr {}
pub(crate) const VIRTIO_BLK_OUTHDR_SIZE: usize = std::mem::size_of::<VirtioBlkOutHdr>();
#[repr(C, packed)]
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct VirtioBlkGeometry {
pub(crate) cylinders: u16,
pub(crate) heads: u8,
pub(crate) sectors: u8,
}
#[repr(C, packed)]
#[derive(Copy, Clone, Default, Debug)]
pub(crate) struct VirtioBlkConfig {
pub(crate) capacity: u64,
pub(crate) size_max: u32,
pub(crate) seg_max: u32,
pub(crate) geometry: VirtioBlkGeometry,
pub(crate) blk_size: u32,
}
unsafe impl vm_memory::ByteValued for VirtioBlkConfig {}
unsafe impl vm_memory::ByteValued for VirtioBlkGeometry {}
pub(crate) const VIRTIO_BLK_CONFIG_SIZE: usize = std::mem::size_of::<VirtioBlkConfig>();
const _: () = assert!(VIRTIO_BLK_CONFIG_SIZE == 24);
const _: () = assert!(std::mem::offset_of!(VirtioBlkConfig, capacity) == 0x00);
const _: () = assert!(std::mem::offset_of!(VirtioBlkConfig, size_max) == 0x08);
const _: () = assert!(std::mem::offset_of!(VirtioBlkConfig, seg_max) == 0x0C);
const _: () = assert!(std::mem::offset_of!(VirtioBlkConfig, geometry) == 0x10);
const _: () = assert!(std::mem::offset_of!(VirtioBlkConfig, blk_size) == 0x14);
#[derive(Clone, Copy, Debug)]
pub(crate) struct ChainDescriptor {
pub(crate) addr: GuestAddress,
pub(crate) len: u32,
pub(crate) is_write_only: bool,
}
pub(crate) const S_ACK: u32 = VIRTIO_CONFIG_S_ACKNOWLEDGE;
pub(crate) const S_DRV: u32 = S_ACK | VIRTIO_CONFIG_S_DRIVER;
pub(crate) const S_FEAT: u32 = S_DRV | VIRTIO_CONFIG_S_FEATURES_OK;
#[cfg(test)]
pub(crate) const S_OK: u32 = S_FEAT | VIRTIO_CONFIG_S_DRIVER_OK;
use super::throttle::*;
#[allow(clippy::too_many_arguments)]
pub(crate) fn publish_completion<Q: QueueT>(
mem: &GuestMemoryMmap,
q: &mut Q,
counters: &VirtioBlkCounters,
head: u16,
status_addr: GuestAddress,
status_byte: u8,
used_len: u32,
label: &'static str,
) -> bool {
if mem.write_slice(&[status_byte], status_addr).is_err() {
counters.record_io_error();
return false;
}
match q.add_used(mem, head, used_len) {
Ok(()) => true,
Err(e) => {
tracing::warn!(head, %e, label, "virtio-blk add_used failed");
counters.record_io_error();
false
}
}
}
pub(crate) struct BlkWorkerState {
pub(crate) backing: Box<dyn Backing>,
pub(crate) ops_bucket: TokenBucket,
pub(crate) bytes_bucket: TokenBucket,
pub(crate) all_descs_scratch: Vec<ChainDescriptor>,
pub(crate) io_buf_scratch: Vec<u8>,
pub(crate) capacity_bytes: u64,
pub(crate) read_only: bool,
pub(crate) counters: Arc<VirtioBlkCounters>,
pub(crate) currently_stalled: bool,
pub(crate) queue_poisoned: bool,
}
pub(crate) struct BlkWorker {
pub(crate) queues: [BlkQueue; NUM_QUEUES],
pub(crate) read_only: bool,
pub(crate) counters: Arc<VirtioBlkCounters>,
pub(crate) engine: WorkerEngine,
}
pub(crate) enum WorkerEngine {
#[cfg(test)]
Inline(InlineEngine),
#[cfg(not(test))]
Spawned(SpawnedEngine),
}
#[cfg(test)]
pub(crate) struct InlineEngine {
pub(crate) state: BlkWorkerState,
}
#[cfg(test)]
impl BlkWorker {
pub(crate) fn state(&self) -> &BlkWorkerState {
let WorkerEngine::Inline(engine) = &self.engine;
&engine.state
}
pub(crate) fn state_mut(&mut self) -> &mut BlkWorkerState {
let WorkerEngine::Inline(engine) = &mut self.engine;
&mut engine.state
}
}
#[cfg(not(test))]
pub(crate) struct SpawnedEngine {
pub(crate) kick_fd: EventFd,
pub(crate) stop_fd: EventFd,
pub(crate) handle: Option<thread::JoinHandle<BlkWorkerState>>,
pub(crate) respawn_pending: Option<BlkWorkerState>,
}
pub(crate) static VIRTIO_BLK_INSTANCE_COUNTER: AtomicU64 = AtomicU64::new(0);
pub struct VirtioBlk {
pub(crate) queue_select: u32,
pub(crate) device_features_sel: u32,
pub(crate) driver_features_sel: u32,
pub(crate) driver_features: u64,
pub(crate) device_status: Arc<AtomicU32>,
pub(crate) interrupt_status: Arc<AtomicU32>,
pub(crate) config_generation: AtomicU32,
pub(crate) irq_evt: Arc<EventFd>,
pub(crate) mem: Arc<OnceLock<GuestMemoryMmap>>,
pub(crate) capacity_sectors: u64,
pub(crate) worker: BlkWorker,
pub(crate) mem_unset_warned: Arc<AtomicBool>,
pub(crate) throttle: DiskThrottle,
pub(crate) instance_id: u64,
pub(crate) pause_evt: Arc<EventFd>,
pub(crate) paused: Arc<AtomicBool>,
pub(crate) parked_evt: Arc<std::sync::Mutex<Option<Arc<EventFd>>>>,
pub(crate) worker_placement: WorkerPlacement,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct WorkerPlacement {
pub service_cpu: Option<usize>,
pub no_perf_cpus: Option<Vec<usize>>,
}
impl VirtioBlk {
#[allow(dead_code)]
pub fn new(backing: File, capacity_bytes: u64, throttle: DiskThrottle) -> Self {
Self::with_options(backing, capacity_bytes, throttle, false)
}
pub fn with_options(
backing: File,
capacity_bytes: u64,
throttle: DiskThrottle,
read_only: bool,
) -> Self {
let irq_evt = Arc::new(
EventFd::new(libc::EFD_NONBLOCK).expect("failed to create virtio-blk irq eventfd"),
);
if capacity_bytes < VIRTIO_BLK_SECTOR_SIZE as u64 && capacity_bytes != 0 {
tracing::warn!(
capacity_bytes,
sector_size = VIRTIO_BLK_SECTOR_SIZE,
"virtio-blk capacity_bytes smaller than one sector; clamping \
capacity_sectors to 0 (every IO will be rejected)"
);
}
let capacity_sectors = capacity_bytes / VIRTIO_BLK_SECTOR_SIZE as u64;
let capacity_bytes = capacity_sectors * VIRTIO_BLK_SECTOR_SIZE as u64;
let (ops_bucket, bytes_bucket) = buckets_from_throttle(throttle);
let counters = Arc::new(VirtioBlkCounters::default());
let state = BlkWorkerState {
backing: Box::new(backing),
ops_bucket,
bytes_bucket,
all_descs_scratch: Vec::with_capacity(VIRTIO_BLK_SEG_MAX as usize + 2),
io_buf_scratch: Vec::new(),
capacity_bytes,
read_only,
counters: Arc::clone(&counters),
currently_stalled: false,
queue_poisoned: false,
};
let interrupt_status = Arc::new(AtomicU32::new(0));
let device_status = Arc::new(AtomicU32::new(0));
let mem = Arc::new(OnceLock::new());
let mem_unset_warned = Arc::new(AtomicBool::new(false));
let pause_evt = Arc::new(
EventFd::new(libc::EFD_NONBLOCK).expect("failed to create virtio-blk pause eventfd"),
);
let paused = Arc::new(AtomicBool::new(true));
let queues = [BlkQueue::new(QUEUE_MAX_SIZE).expect("valid queue size")];
#[cfg(test)]
let engine = WorkerEngine::Inline(InlineEngine { state });
#[cfg(not(test))]
let engine = {
let kick_fd =
EventFd::new(libc::EFD_NONBLOCK).expect("failed to create virtio-blk kick eventfd");
let stop_fd =
EventFd::new(libc::EFD_NONBLOCK).expect("failed to create virtio-blk stop eventfd");
WorkerEngine::Spawned(SpawnedEngine {
kick_fd,
stop_fd,
handle: None,
respawn_pending: Some(state),
})
};
let worker = BlkWorker {
queues,
read_only,
counters,
engine,
};
VirtioBlk {
queue_select: 0,
device_features_sel: 0,
driver_features_sel: 0,
driver_features: 0,
device_status,
interrupt_status,
config_generation: AtomicU32::new(0),
irq_evt,
mem,
capacity_sectors,
worker,
mem_unset_warned,
throttle,
instance_id: VIRTIO_BLK_INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed),
pause_evt,
paused,
parked_evt: Arc::new(std::sync::Mutex::new(None)),
worker_placement: WorkerPlacement::default(),
}
}
pub fn set_parked_evt(&self, evt: Arc<EventFd>) {
if let Ok(mut guard) = self.parked_evt.lock() {
*guard = Some(evt);
}
}
pub fn set_worker_placement(&mut self, placement: WorkerPlacement) {
self.worker_placement = placement;
}
pub fn irq_evt(&self) -> &EventFd {
&self.irq_evt
}
pub fn set_mem(&mut self, mem: GuestMemoryMmap) {
if self.mem.set(mem).is_err() {
tracing::warn!(
"virtio-blk: set_mem called on already-initialised \
device; guest memory binding unchanged (mem is set \
once at boot and preserved across reset())"
);
}
}
#[allow(dead_code)]
pub fn capacity_sectors(&self) -> u64 {
self.capacity_sectors
}
pub fn counters(&self) -> Arc<VirtioBlkCounters> {
Arc::clone(&self.worker.counters)
}
pub fn paused_handle(&self) -> Arc<AtomicBool> {
Arc::clone(&self.paused)
}
pub(crate) fn device_features(&self) -> u64 {
let mut feats = (1u64 << VIRTIO_F_VERSION_1)
| (1u64 << VIRTIO_BLK_F_BLK_SIZE)
| (1u64 << VIRTIO_BLK_F_SEG_MAX)
| (1u64 << VIRTIO_BLK_F_SIZE_MAX)
| (1u64 << VIRTIO_BLK_F_FLUSH)
| (1u64 << VIRTIO_RING_F_EVENT_IDX);
if self.worker.read_only {
feats |= 1u64 << VIRTIO_BLK_F_RO;
}
feats
}
pub(crate) fn selected_queue(&self) -> Option<usize> {
let idx = self.queue_select as usize;
if idx < NUM_QUEUES { Some(idx) } else { None }
}
pub(crate) fn queue_config_allowed(&self) -> bool {
let status = self.device_status.load(Ordering::Acquire);
status & S_FEAT == S_FEAT && status & VIRTIO_CONFIG_S_DRIVER_OK == 0
}
pub(crate) fn features_write_allowed(&self) -> bool {
let status = self.device_status.load(Ordering::Acquire);
status & S_DRV == S_DRV && status & VIRTIO_CONFIG_S_FEATURES_OK == 0
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn handle_read_vectored_impl(
backing: &dyn Backing,
capacity_bytes: u64,
counters: &VirtioBlkCounters,
mem: &GuestMemoryMmap,
sector: u64,
data_segments: &[ChainDescriptor],
data_len: u64,
) -> (u8, u32) {
let Some(base_offset) = sector.checked_mul(VIRTIO_BLK_SECTOR_SIZE as u64) else {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
};
if base_offset
.checked_add(data_len)
.is_none_or(|end| end > capacity_bytes)
{
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
let mut iovecs: Vec<libc::iovec> = Vec::with_capacity(VIRTIO_BLK_SEG_MAX as usize + 2);
let mut _guards: Vec<vm_memory::volatile_memory::PtrGuardMut> =
Vec::with_capacity(VIRTIO_BLK_SEG_MAX as usize + 2);
for seg in data_segments {
if !seg.is_write_only {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
let len = seg.len as usize;
if len == 0 {
continue;
}
for slice_result in mem.get_slices(seg.addr, len) {
let slice = match slice_result {
Ok(s) => s,
Err(_) => {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
};
let guard = slice.ptr_guard_mut();
iovecs.push(libc::iovec {
iov_base: guard.as_ptr() as *mut libc::c_void,
iov_len: slice.len(),
});
_guards.push(guard);
}
}
debug_assert!(
iovecs.len() <= IOV_MAX,
"blk preadv iovcnt {} exceeds IOV_MAX {} — structural bound broke (see SAFETY comment)",
iovecs.len(),
IOV_MAX,
);
let bytes_from_backing: u64 = if iovecs.is_empty() {
0
} else {
let mut eintr_retries: u32 = 0;
loop {
match unsafe { backing.preadv(&iovecs, base_offset) } {
Ok(n) => break n as u64,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
eintr_retries += 1;
if eintr_retries > MAX_EINTR_RETRIES {
tracing::warn!(
sector,
"virtio-blk preadv: EINTR retry cap hit (pending fatal signal?); failing"
);
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
continue;
}
Err(e) => {
tracing::warn!(sector, %e, "virtio-blk preadv error");
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
}
}
};
if bytes_from_backing < data_len {
let mut filled = bytes_from_backing;
let mut to_zero = data_len - bytes_from_backing;
const ZERO_BUF_LEN: usize = 65536;
let zeros = [0u8; ZERO_BUF_LEN];
for seg in data_segments {
if to_zero == 0 {
break;
}
let seg_len = seg.len as u64;
if filled >= seg_len {
filled -= seg_len;
continue;
}
let seg_offset = filled as u32;
let seg_remaining = (seg_len - filled).min(to_zero) as u32;
let Some(zero_addr_u64) = seg.addr.0.checked_add(seg_offset as u64) else {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
};
let mut zero_addr = GuestAddress(zero_addr_u64);
let mut remaining = seg_remaining;
while remaining > 0 {
let chunk = (remaining as usize).min(ZERO_BUF_LEN);
if mem.write_slice(&zeros[..chunk], zero_addr).is_err() {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
let Some(next) = zero_addr.0.checked_add(chunk as u64) else {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
};
zero_addr = GuestAddress(next);
remaining -= chunk as u32;
}
to_zero -= seg_remaining as u64;
filled = 0;
}
}
counters.record_read(bytes_from_backing);
let bytes_to_guest = data_len as u32;
(VIRTIO_BLK_S_OK as u8, bytes_to_guest + 1)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn handle_write_vectored_impl(
backing: &dyn Backing,
capacity_bytes: u64,
counters: &VirtioBlkCounters,
mem: &GuestMemoryMmap,
sector: u64,
data_segments: &[ChainDescriptor],
data_len: u64,
) -> (u8, u32) {
let Some(base_offset) = sector.checked_mul(VIRTIO_BLK_SECTOR_SIZE as u64) else {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
};
if base_offset
.checked_add(data_len)
.is_none_or(|end| end > capacity_bytes)
{
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
let mut iovecs: Vec<libc::iovec> = Vec::with_capacity(VIRTIO_BLK_SEG_MAX as usize + 2);
let mut _guards: Vec<vm_memory::volatile_memory::PtrGuard> =
Vec::with_capacity(VIRTIO_BLK_SEG_MAX as usize + 2);
for seg in data_segments {
if seg.is_write_only {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
let len = seg.len as usize;
if len == 0 {
continue;
}
for slice_result in mem.get_slices(seg.addr, len) {
let slice = match slice_result {
Ok(s) => s,
Err(_) => {
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
};
let guard = slice.ptr_guard();
iovecs.push(libc::iovec {
iov_base: guard.as_ptr() as *mut libc::c_void,
iov_len: slice.len(),
});
_guards.push(guard);
}
}
debug_assert!(
iovecs.len() <= IOV_MAX,
"blk pwritev iovcnt {} exceeds IOV_MAX {} — structural bound broke (see SAFETY comment)",
iovecs.len(),
IOV_MAX,
);
if iovecs.is_empty() {
counters.record_write(0);
return (VIRTIO_BLK_S_OK as u8, 1);
}
let mut remaining: &mut [libc::iovec] = &mut iovecs;
let mut off = base_offset;
let mut total_written: u64 = 0;
let mut eintr_retries: u32 = 0;
loop {
let n = match unsafe { backing.pwritev(remaining, off) } {
Ok(0) => {
tracing::warn!(
sector,
total_written,
data_len,
"virtio-blk pwritev made zero forward progress; failing"
);
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
Ok(n) => n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
eintr_retries += 1;
if eintr_retries > MAX_EINTR_RETRIES {
tracing::warn!(
sector,
"virtio-blk pwritev: EINTR retry cap hit (pending fatal signal?); failing"
);
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
continue;
}
Err(e) => {
tracing::warn!(sector, %e, "virtio-blk pwritev error");
counters.record_io_error();
return (VIRTIO_BLK_S_IOERR as u8, 1);
}
};
total_written += n as u64;
off += n as u64;
if total_written == data_len {
break;
}
remaining = advance_iovecs(remaining, n);
debug_assert!(
!remaining.is_empty(),
"virtio-blk pwritev: iovecs exhausted at {total_written} of {data_len} bytes"
);
}
counters.record_write(total_written);
(VIRTIO_BLK_S_OK as u8, 1)
}
pub(crate) fn classify_pre_throttle(
req_type: u32,
read_only: bool,
counters: &VirtioBlkCounters,
) -> Option<(u8, u32)> {
match req_type {
VIRTIO_BLK_T_OUT if read_only => {
counters.record_io_error();
Some((VIRTIO_BLK_S_IOERR as u8, 1))
}
VIRTIO_BLK_T_FLUSH if read_only => {
counters.record_flush();
Some((VIRTIO_BLK_S_OK as u8, 1))
}
VIRTIO_BLK_T_IN | VIRTIO_BLK_T_OUT | VIRTIO_BLK_T_FLUSH | VIRTIO_BLK_T_GET_ID => None,
_ => Some((VIRTIO_BLK_S_UNSUPP as u8, 1)),
}
}
pub(crate) fn process_requests(&mut self) {
#[cfg(test)]
{
self.drain_inline();
}
#[cfg(not(test))]
{
let WorkerEngine::Spawned(eng) = &self.worker.engine;
let _ = eng.kick_fd.write(1);
}
}
#[cfg(test)]
pub(crate) fn drain_inline(&mut self) {
let Some(mem) = self.mem.get() else {
if !self.mem_unset_warned.swap(true, Ordering::Relaxed) {
tracing::warn!(
"virtio-blk: queue notify before set_mem; \
dropping requests until guest memory is wired"
);
}
return;
};
let WorkerEngine::Inline(engine) = &mut self.worker.engine;
let _ = super::drain_bracket_impl(
&mut engine.state,
&mut self.worker.queues,
mem,
&self.irq_evt,
&self.interrupt_status,
&self.device_status,
);
}
}