runtime-rs: plumb block discard unmap

Pass block-device discard support through the runtime-rs QEMU stack.

Block device configuration now reaches both QEMU startup and hotplug
paths. The backend receives discard=unmap, and virtio-blk frontend
devices advertise discard support when requested. The SCSI hotplug path
keeps its existing frontend arguments.

Signed-off-by: Manuel Huber <manuelh@nvidia.com>
Assisted-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Manuel Huber
2026-06-03 18:05:54 +00:00
parent c9352ffffe
commit b2b17d4b64
3 changed files with 72 additions and 8 deletions

View File

@@ -943,6 +943,7 @@ struct BlockBackend {
cache_direct: bool,
cache_no_flush: bool,
read_only: bool,
discard_unmap: bool,
}
impl BlockBackend {
@@ -955,6 +956,7 @@ impl BlockBackend {
cache_direct,
cache_no_flush: false,
read_only: true,
discard_unmap: false,
}
}
@@ -987,6 +989,12 @@ impl BlockBackend {
self.read_only = read_only;
self
}
#[allow(dead_code)]
fn set_discard_unmap(&mut self, discard_unmap: bool) -> &mut Self {
self.discard_unmap = discard_unmap;
self
}
}
#[async_trait]
@@ -1012,6 +1020,9 @@ impl ToQemuParams for BlockBackend {
} else {
params.push("auto-read-only=off".to_owned());
}
if self.discard_unmap {
params.push("discard=unmap".to_owned());
}
Ok(vec!["-blockdev".to_owned(), params.join(",")])
}
}
@@ -1070,6 +1081,7 @@ struct DeviceVirtioBlk {
id: String,
config_wce: bool,
share_rw: bool,
discard: bool,
devno: Option<String>,
}
@@ -1080,6 +1092,7 @@ impl DeviceVirtioBlk {
id: id.to_owned(),
config_wce: false,
share_rw: true,
discard: false,
devno,
}
}
@@ -1095,6 +1108,12 @@ impl DeviceVirtioBlk {
self.share_rw = share_rw;
self
}
#[allow(dead_code)]
fn set_discard(&mut self, discard: bool) -> &mut Self {
self.discard = discard;
self
}
}
#[async_trait]
@@ -1113,6 +1132,9 @@ impl ToQemuParams for DeviceVirtioBlk {
} else {
params.push("share-rw=off".to_owned());
}
if self.discard {
params.push("discard=on".to_owned());
}
params.push(format!("serial=image-{}", self.id));
if let Some(devno) = &self.devno {
params.push(format!("devno={devno}"));
@@ -2870,16 +2892,19 @@ impl<'a> QemuCmdLine<'a> {
path: &str,
is_direct: bool,
is_scsi: bool,
discard_unmap: bool,
) -> Result<()> {
self.devices
.push(Box::new(BlockBackend::new(device_id, path, is_direct)));
let mut backend = BlockBackend::new(device_id, path, is_direct);
backend.set_discard_unmap(discard_unmap);
self.devices.push(Box::new(backend));
let devno = get_devno_ccw(&mut self.ccw_subchannel, device_id);
if is_scsi {
self.devices
.push(Box::new(DeviceScsiHd::new(device_id, "scsi0.0", devno)));
} else {
self.devices
.push(Box::new(DeviceVirtioBlk::new(device_id, bus_type(), devno)));
let mut device = DeviceVirtioBlk::new(device_id, bus_type(), devno);
device.set_discard(discard_unmap);
self.devices.push(Box::new(device));
}
Ok(())
@@ -3468,3 +3493,32 @@ impl ToQemuParams for SeccompSandbox {
Ok(vec!["-sandbox".to_owned(), self.param.clone()])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn contains_param(params: &[String], expected: &str) -> bool {
params
.iter()
.flat_map(|param| param.split(','))
.any(|param| param == expected)
}
#[actix_rt::test]
async fn test_qemu_block_discard_unmap_params() {
let mut backend = BlockBackend::new("blk0", "/tmp/disk.img", true);
backend.set_discard_unmap(true);
let backend_params = backend.qemu_params().await.unwrap();
assert!(contains_param(&backend_params, "discard=unmap"));
let mut virtio_blk = DeviceVirtioBlk::new("blk0", VirtioBusType::Pci, None);
virtio_blk.set_discard(true);
let virtio_blk_params = virtio_blk.qemu_params().await.unwrap();
assert!(contains_param(&virtio_blk_params, "discard=on"));
let scsi_hd = DeviceScsiHd::new("blk0", "scsi0.0", None);
let scsi_hd_params = scsi_hd.qemu_params().await.unwrap();
assert!(!contains_param(&scsi_hd_params, "discard=on"));
}
}

View File

@@ -150,6 +150,7 @@ impl QemuInner {
.is_direct
.unwrap_or(self.config.blockdev_info.block_device_cache_direct),
block_dev.config.driver_option.as_str() == KATA_SCSI_DEV_TYPE,
block_dev.config.discard_unmap,
)?,
unsupported => {
info!(sl!(), "unsupported block device driver: {}", unsupported)
@@ -1027,6 +1028,7 @@ impl QemuInner {
),
block_device.config.is_readonly,
block_device.config.no_drop,
block_device.config.discard_unmap,
block_device.config.logical_sector_size,
block_device.config.physical_sector_size,
&block_device.config.format,
@@ -1079,6 +1081,7 @@ impl QemuInner {
is_direct,
is_readonly,
no_drop,
discard_unmap,
driver,
logical_sector_size,
physical_sector_size,
@@ -1094,6 +1097,7 @@ impl QemuInner {
),
cfg.is_readonly,
cfg.no_drop,
cfg.discard_unmap,
self.config.blockdev_info.block_device_driver.clone(),
cfg.logical_sector_size,
cfg.physical_sector_size,
@@ -1110,6 +1114,7 @@ impl QemuInner {
is_direct,
is_readonly,
no_drop,
discard_unmap,
logical_sector_size,
physical_sector_size,
&BlockDeviceFormat::default(),

View File

@@ -14,7 +14,7 @@ use kata_types::config::hypervisor::{VIRTIO_BLK_CCW, VIRTIO_SCSI};
use kata_types::rootless::is_rootless;
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
use qapi_qmp::{
self as qmp, BlockdevAioOptions, BlockdevOptions, BlockdevOptionsBase,
self as qmp, BlockdevAioOptions, BlockdevDiscardOptions, BlockdevOptions, BlockdevOptionsBase,
BlockdevOptionsGenericCOWFormat, BlockdevOptionsGenericFormat, BlockdevOptionsRaw, BlockdevRef,
MigrationInfo, PciDeviceInfo,
};
@@ -775,6 +775,7 @@ impl Qmp {
is_direct: Option<bool>,
is_readonly: bool,
no_drop: bool,
discard_unmap: bool,
logical_block_size: u32,
physical_block_size: u32,
format: &BlockDeviceFormat,
@@ -782,6 +783,7 @@ impl Qmp {
) -> Result<(Option<PciPath>, Option<String>)> {
// `blockdev-add`
let node_name = block_node_name(index);
let discard_option = || discard_unmap.then_some(BlockdevDiscardOptions::unmap);
let create_base_options = || qapi_qmp::BlockdevOptionsBase {
auto_read_only: None,
@@ -794,7 +796,7 @@ impl Qmp {
})
},
detect_zeroes: None,
discard: None,
discard: discard_option(),
force_share: None,
node_name: None,
read_only: Some(is_readonly),
@@ -832,7 +834,7 @@ impl Qmp {
base: BlockdevOptionsBase {
detect_zeroes: None,
cache: None,
discard: None,
discard: discard_option(),
force_share: if is_readonly { Some(true) } else { None },
auto_read_only: None,
node_name: Some(node_name.clone()),
@@ -857,7 +859,7 @@ impl Qmp {
base: BlockdevOptionsBase {
detect_zeroes: None,
cache: None,
discard: None,
discard: discard_option(),
force_share: Some(true),
auto_read_only: None,
node_name: Some(node_name.clone()),
@@ -882,6 +884,9 @@ impl Qmp {
// `device_add`
let mut blkdev_add_args = Dictionary::new();
blkdev_add_args.insert("drive".to_owned(), node_name.clone().into());
if discard_unmap && block_driver != VIRTIO_SCSI {
blkdev_add_args.insert("discard".to_owned(), true.into());
}
if logical_block_size > 0 {
blkdev_add_args.insert("logical_block_size".to_owned(), logical_block_size.into());