From b2b17d4b64a39f356edfb76cb2eb98704d9b5549 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Wed, 3 Jun 2026 18:05:54 +0000 Subject: [PATCH] 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 Assisted-by: OpenAI Codex --- .../hypervisor/src/qemu/cmdline_generator.rs | 62 +++++++++++++++++-- .../crates/hypervisor/src/qemu/inner.rs | 5 ++ .../crates/hypervisor/src/qemu/qmp.rs | 13 ++-- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs index cf73034933..5352a85a45 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs @@ -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, } @@ -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")); + } +} diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs index 2661cb0233..9c6a52a51f 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs @@ -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(), diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs b/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs index eda604ac3b..0792d92332 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/qmp.rs @@ -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, 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, Option)> { // `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());