block: Add virtio-blk DISCARD and WRITE_ZEROES support

Add VIRTIO_BLK_T_DISCARD and VIRTIO_BLK_T_WRITE_ZEROES request types.
Parse discard/write_zeroes descriptors (sector, num_sectors, flags),
convert to byte offsets, and call punch_hole/write_zeroes on the disk
backend. Mark as unsupported in sync mode.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski 2026-02-04 15:54:48 +01:00 committed by Rob Bradford
parent e913d0d9cb
commit 81c075b317

View file

@ -50,7 +50,7 @@ use virtio_bindings::virtio_blk::*;
use virtio_queue::DescriptorChain;
use vm_memory::bitmap::Bitmap;
use vm_memory::{
ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryLoadGuard,
};
use vm_virtio::{AccessPlatform, Translatable};
use vmm_sys_util::eventfd::EventFd;
@ -156,6 +156,10 @@ pub enum ExecuteError {
AsyncWrite(#[source] AsyncIoError),
#[error("failed to async flush")]
AsyncFlush(#[source] AsyncIoError),
#[error("Failed to async punch hole")]
AsyncPunchHole(#[source] AsyncIoError),
#[error("Failed to async write zeroes")]
AsyncWriteZeroes(#[source] AsyncIoError),
#[error("Failed allocating a temporary buffer")]
TemporaryBufferAllocation(#[source] io::Error),
}
@ -177,6 +181,8 @@ impl ExecuteError {
ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncPunchHole(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::AsyncWriteZeroes(_) => VIRTIO_BLK_S_IOERR,
ExecuteError::TemporaryBufferAllocation(_) => VIRTIO_BLK_S_IOERR,
};
status as u8
@ -189,6 +195,8 @@ pub enum RequestType {
Out,
Flush,
GetDeviceId,
Discard,
WriteZeroes,
Unsupported(u32),
}
@ -202,6 +210,8 @@ pub fn request_type<B: Bitmap + 'static>(
VIRTIO_BLK_T_OUT => Ok(RequestType::Out),
VIRTIO_BLK_T_FLUSH => Ok(RequestType::Flush),
VIRTIO_BLK_T_GET_ID => Ok(RequestType::GetDeviceId),
VIRTIO_BLK_T_DISCARD => Ok(RequestType::Discard),
VIRTIO_BLK_T_WRITE_ZEROES => Ok(RequestType::WriteZeroes),
t => Ok(RequestType::Unsupported(t)),
}
}
@ -299,6 +309,12 @@ impl Request {
if desc.is_write_only() && req.request_type == RequestType::Out {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
if desc.is_write_only() && req.request_type == RequestType::Discard {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
if desc.is_write_only() && req.request_type == RequestType::WriteZeroes {
return Err(Error::UnexpectedWriteOnlyDescriptor);
}
if !desc.is_write_only() && req.request_type == RequestType::In {
return Err(Error::UnexpectedReadOnlyDescriptor);
}
@ -395,6 +411,12 @@ impl Request {
mem.write_slice(serial, *data_addr)
.map_err(ExecuteError::Write)?;
}
RequestType::Discard => {
return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_DISCARD));
}
RequestType::WriteZeroes => {
return Err(ExecuteError::Unsupported(VIRTIO_BLK_T_WRITE_ZEROES));
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
}
}
@ -544,6 +566,62 @@ impl Request {
ret.async_complete = false;
return Ok(ret);
}
RequestType::Discard => {
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
(self.data_descriptors[0].0, self.data_descriptors[0].1)
} else {
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
};
if data_len < 16 {
return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall));
}
let mut discard_sector = [0u8; 8];
let mut discard_num_sectors = [0u8; 4];
mem.read_slice(&mut discard_sector, data_addr)
.map_err(ExecuteError::Read)?;
mem.read_slice(&mut discard_num_sectors, data_addr.checked_add(8).unwrap())
.map_err(ExecuteError::Read)?;
let discard_sector = u64::from_le_bytes(discard_sector);
let discard_num_sectors = u32::from_le_bytes(discard_num_sectors);
let discard_offset = discard_sector * SECTOR_SIZE;
let discard_length = (discard_num_sectors as u64) * SECTOR_SIZE;
disk_image
.punch_hole(discard_offset, discard_length, user_data)
.map_err(ExecuteError::AsyncPunchHole)?;
}
RequestType::WriteZeroes => {
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
(self.data_descriptors[0].0, self.data_descriptors[0].1)
} else {
return Err(ExecuteError::BadRequest(Error::TooManyDescriptors));
};
if data_len < 16 {
return Err(ExecuteError::BadRequest(Error::DescriptorLengthTooSmall));
}
let mut wz_sector = [0u8; 8];
let mut wz_num_sectors = [0u8; 4];
mem.read_slice(&mut wz_sector, data_addr)
.map_err(ExecuteError::Read)?;
mem.read_slice(&mut wz_num_sectors, data_addr.checked_add(8).unwrap())
.map_err(ExecuteError::Read)?;
let wz_sector = u64::from_le_bytes(wz_sector);
let wz_num_sectors = u32::from_le_bytes(wz_num_sectors);
let wz_offset = wz_sector * SECTOR_SIZE;
let wz_length = (wz_num_sectors as u64) * SECTOR_SIZE;
disk_image
.write_zeroes(wz_offset, wz_length, user_data)
.map_err(ExecuteError::AsyncWriteZeroes)?;
}
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
}