runtime-rs: support block device driver virtio-scsi within qemu-rs

It is important that we continue to support VirtIO-SCSI. While
VirtIO-BLK is a common choice, virtio-scsi offers significant
performance advantages in specific scenarios, particularly when
utilizing iothreads and with NVMe Fabrics.

Maintaining Flexibility and Choice by supporting both virtio-blk and
virtio-scsi, we provide greater flexibility for users to choose the
optimal storage(virtio-blk, virtio-scsi) interface based on their
specific workload requirements and hardware configurations.

As virtio-scsi controller has been created when qemu vm starts with
block device driver is set to `virtio-scsi`. This commit is for blockdev_add
the backend block device and device_add frondend virtio-scsi device via qmp.

Fixes #11516

Signed-off-by: alex.lyn <alex.lyn@antgroup.com>
This commit is contained in:
alex.lyn
2025-07-23 18:55:37 +08:00
parent e683a7fd37
commit b40d65bc1b
2 changed files with 106 additions and 46 deletions

View File

@@ -632,7 +632,7 @@ impl QemuInner {
qmp.hotplug_network_device(&netdev, &virtio_net_device)? qmp.hotplug_network_device(&netdev, &virtio_net_device)?
} }
DeviceType::Block(mut block_device) => { DeviceType::Block(mut block_device) => {
block_device.config.pci_path = qmp let (pci_path, scsi_addr) = qmp
.hotplug_block_device( .hotplug_block_device(
&self.config.blockdev_info.block_device_driver, &self.config.blockdev_info.block_device_driver,
block_device.config.index, block_device.config.index,
@@ -644,6 +644,13 @@ impl QemuInner {
) )
.context("hotplug block device")?; .context("hotplug block device")?;
if pci_path.is_some() {
block_device.config.pci_path = pci_path;
}
if scsi_addr.is_some() {
block_device.config.scsi_addr = scsi_addr;
}
return Ok(DeviceType::Block(block_device)); return Ok(DeviceType::Block(block_device));
} }
DeviceType::Vfio(mut vfiodev) => { DeviceType::Vfio(mut vfiodev) => {

View File

@@ -7,6 +7,7 @@ use crate::device::pci_path::PciPath;
use crate::qemu::cmdline_generator::{DeviceVirtioNet, Netdev}; use crate::qemu::cmdline_generator::{DeviceVirtioNet, Netdev};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use kata_types::config::hypervisor::VIRTIO_SCSI;
use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags}; use nix::sys::socket::{sendmsg, ControlMessage, MsgFlags};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{Debug, Error, Formatter}; use std::fmt::{Debug, Error, Formatter};
@@ -16,8 +17,10 @@ use std::os::unix::net::UnixStream;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use qapi::qmp; use qapi_qmp::{
use qapi_qmp::{self, BlockdevAioOptions, PciDeviceInfo}; self as qmp, BlockdevAioOptions, BlockdevOptions, BlockdevOptionsBase,
BlockdevOptionsGenericFormat, BlockdevOptionsRaw, BlockdevRef, PciDeviceInfo,
};
use qapi_spec::Dictionary; use qapi_spec::Dictionary;
/// default qmp connection read timeout /// default qmp connection read timeout
@@ -494,7 +497,7 @@ impl Qmp {
Err(anyhow!("no target device found")) Err(anyhow!("no target device found"))
} }
/// hotplug block device: /// Hotplug block device:
/// { /// {
/// "execute": "blockdev-add", /// "execute": "blockdev-add",
/// "arguments": { /// "arguments": {
@@ -515,21 +518,29 @@ impl Qmp {
/// "bus": "pcie.1" /// "bus": "pcie.1"
/// } /// }
/// } /// }
/// Hotplug SCSI block device
/// # virtio-scsi0
/// {"execute":"device_add","arguments":{"driver":"virtio-scsi-pci","id":"virtio-scsi0","bus":"bus1"}}
/// {"return": {}}
///
/// {"execute":"blockdev_add", "arguments": {"file":"/path/to/block.image","format":"qcow2","id":"virtio-scsi0"}}
/// {"return": {}}
/// {"execute":"device_add","arguments":{"driver":"scsi-hd","drive":"virtio-scsi0","id":"scsi_device_0","bus":"virtio-scsi1.0"}}
/// {"return": {}}
///
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn hotplug_block_device( pub fn hotplug_block_device(
&mut self, &mut self,
block_driver: &str, block_driver: &str,
device_id: u64, index: u64,
path_on_host: &str, path_on_host: &str,
blkdev_aio: &str, blkdev_aio: &str,
is_direct: Option<bool>, is_direct: Option<bool>,
is_readonly: bool, is_readonly: bool,
no_drop: bool, no_drop: bool,
) -> Result<Option<PciPath>> { ) -> Result<(Option<PciPath>, Option<String>)> {
let (bus, slot) = self.find_free_slot()?;
// `blockdev-add` // `blockdev-add`
let node_name = format!("drive-{}", device_id); let node_name = format!("drive-{index}");
let create_base_options = || qapi_qmp::BlockdevOptionsBase { let create_base_options = || qapi_qmp::BlockdevOptionsBase {
auto_read_only: None, auto_read_only: None,
@@ -575,51 +586,93 @@ impl Qmp {
} }
}; };
let blockdev_options_raw = BlockdevOptions::raw {
base: BlockdevOptionsBase {
detect_zeroes: None,
cache: None,
discard: None,
force_share: None,
auto_read_only: None,
node_name: Some(node_name.clone()),
read_only: None,
},
raw: BlockdevOptionsRaw {
base: BlockdevOptionsGenericFormat {
file: BlockdevRef::definition(Box::new(blockdev_file)),
},
offset: None,
size: None,
},
};
self.qmp self.qmp
.execute(&qapi_qmp::blockdev_add(qmp::BlockdevOptions::raw { .execute(&qapi_qmp::blockdev_add(blockdev_options_raw))
base: qmp::BlockdevOptionsBase { .map_err(|e| anyhow!("blockdev-add backend {:?}", e))
detect_zeroes: None,
cache: None,
discard: None,
force_share: None,
auto_read_only: None,
node_name: Some(node_name.clone()),
read_only: None,
},
raw: qmp::BlockdevOptionsRaw {
base: qmp::BlockdevOptionsGenericFormat {
file: qmp::BlockdevRef::definition(Box::new(blockdev_file)),
},
offset: None,
size: None,
},
}))
.map_err(|e| anyhow!("blockdev_add backend {:?}", e))
.map(|_| ())?; .map(|_| ())?;
// block device
// `device_add` // `device_add`
let mut blkdev_add_args = Dictionary::new(); let mut blkdev_add_args = Dictionary::new();
blkdev_add_args.insert("addr".to_owned(), format!("{:02}", slot).into());
blkdev_add_args.insert("drive".to_owned(), node_name.clone().into()); blkdev_add_args.insert("drive".to_owned(), node_name.clone().into());
self.qmp
.execute(&qmp::device_add {
bus: Some(bus),
id: Some(node_name.clone()),
driver: block_driver.to_string(),
arguments: blkdev_add_args,
})
.map_err(|e| anyhow!("device_add {:?}", e))
.map(|_| ())?;
let pci_path = self if block_driver == VIRTIO_SCSI {
.get_device_by_qdev_id(&node_name) // Helper closure to decode a flattened u16 SCSI index into an (ID, LUN) pair.
.context("get device by qdev_id failed")?; let get_scsi_id_lun = |index_u16: u16| -> Result<(u8, u8)> {
info!( // Uses bitwise operations for efficient and clear conversion.
sl!(), let scsi_id = (index_u16 >> 8) as u8; // Equivalent to index_u16 / 256
"hotplug_block_device return pci path: {:?}", &pci_path let lun = (index_u16 & 0xFF) as u8; // Equivalent to index_u16 % 256
);
Ok(Some(pci_path)) Ok((scsi_id, lun))
};
// Safely convert the u64 index to u16, ensuring it does not exceed `u16::MAX` (65535).
let (scsi_id, lun) = get_scsi_id_lun(u16::try_from(index)?)?;
let scsi_addr = format!("{}:{}", scsi_id, lun);
// add SCSI frontend device
blkdev_add_args.insert("scsi-id".to_string(), scsi_id.into());
blkdev_add_args.insert("lun".to_string(), lun.into());
self.qmp
.execute(&qmp::device_add {
bus: Some("scsi0.0".to_string()),
id: Some(node_name.clone()),
driver: "scsi-hd".to_string(),
arguments: blkdev_add_args,
})
.map_err(|e| anyhow!("device_add {:?}", e))
.map(|_| ())?;
info!(
sl!(),
"hotplug scsi block device return scsi address: {:?}", &scsi_addr
);
Ok((None, Some(scsi_addr)))
} else {
let (bus, slot) = self.find_free_slot()?;
blkdev_add_args.insert("addr".to_owned(), format!("{:02}", slot).into());
self.qmp
.execute(&qmp::device_add {
bus: Some(bus),
id: Some(node_name.clone()),
driver: block_driver.to_string(),
arguments: blkdev_add_args,
})
.map_err(|e| anyhow!("device_add {:?}", e))
.map(|_| ())?;
let pci_path = self
.get_device_by_qdev_id(&node_name)
.context("get device by qdev_id failed")?;
info!(
sl!(),
"hotplug block device return pci path: {:?}", &pci_path
);
Ok((Some(pci_path), None))
}
} }
pub fn hotplug_vfio_device( pub fn hotplug_vfio_device(