diff --git a/cloud-hypervisor/src/main.rs b/cloud-hypervisor/src/main.rs index 1d78b400b..351dd73b7 100644 --- a/cloud-hypervisor/src/main.rs +++ b/cloud-hypervisor/src/main.rs @@ -998,6 +998,7 @@ mod unit_tests { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, diff --git a/option_parser/src/lib.rs b/option_parser/src/lib.rs index 1722da39f..699a26252 100644 --- a/option_parser/src/lib.rs +++ b/option_parser/src/lib.rs @@ -4,6 +4,7 @@ // use std::collections::HashMap; +use std::fmt::{Display, Write}; use std::num::ParseIntError; use std::str::FromStr; @@ -240,6 +241,21 @@ impl FromStr for ByteSized { pub struct IntegerList(pub Vec); +impl Display for IntegerList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_char('[')?; + let mut iter = self.0.iter(); + if let Some(first) = iter.next() { + first.fmt(f)?; + for i in iter { + f.write_char(',')?; + i.fmt(f)?; + } + } + f.write_char(']') + } +} + #[derive(Error, Debug)] pub enum IntegerListParseError { #[error("invalid value: {0}")] diff --git a/vmm/src/api/mod.rs b/vmm/src/api/mod.rs index 12ca6b987..17ba0011b 100644 --- a/vmm/src/api/mod.rs +++ b/vmm/src/api/mod.rs @@ -51,8 +51,8 @@ use crate::config::RestoreConfig; use crate::device_tree::DeviceTree; use crate::vm::{Error as VmError, VmState}; use crate::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; /// API errors are sent back from the VMM API server through the ApiResponse. @@ -170,6 +170,10 @@ pub enum ApiError { #[error("The fs could not be added to the VM")] VmAddFs(#[source] VmError), + /// The generic vhost-user device could not be added to the VM. + #[error("The generic vhost-user device could not be added to the VM")] + VmAddGenericVhostUser(#[source] VmError), + /// The pmem device could not be added to the VM. #[error("The pmem device could not be added to the VM")] VmAddPmem(#[source] VmError), @@ -340,6 +344,11 @@ pub trait RequestHandler { fn vm_add_fs(&mut self, fs_cfg: FsConfig) -> Result>, VmError>; + fn vm_add_generic_vhost_user( + &mut self, + fs_cfg: GenericVhostUserConfig, + ) -> Result>, VmError>; + fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result>, VmError>; fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result>, VmError>; @@ -539,6 +548,43 @@ impl ApiAction for VmAddFs { } } +pub struct VmAddGenericVhostUser; + +impl ApiAction for VmAddGenericVhostUser { + type RequestBody = GenericVhostUserConfig; + type ResponseBody = Option; + + fn request( + &self, + config: Self::RequestBody, + response_sender: Sender, + ) -> ApiRequest { + Box::new(move |vmm| { + info!("API request event: VmAddGenericVhostUser {config:?}"); + + let response = vmm + .vm_add_generic_vhost_user(config) + .map_err(ApiError::VmAddGenericVhostUser) + .map(ApiResponsePayload::VmAction); + + response_sender + .send(response) + .map_err(VmmError::ApiResponseSend)?; + + Ok(false) + }) + } + + fn send( + &self, + api_evt: EventFd, + api_sender: Sender, + data: Self::RequestBody, + ) -> ApiResult { + get_response_body(self, api_evt, api_sender, data) + } +} + pub struct VmAddPmem; impl ApiAction for VmAddPmem { diff --git a/vmm/src/config.rs b/vmm/src/config.rs index a4339c27b..25c0d96ae 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -46,6 +46,24 @@ pub enum Error { /// Filesystem socket is missing #[error("Error parsing --fs: socket missing")] ParseFsSockMissing, + /// Generic vhost-user socket is missing + #[error("Error parsing --generic-vhost-user: socket missing")] + ParseGenericVhostUserSockMissing, + /// Generic vhost-user number of queues is missing + #[error("Error parsing --generic-vhost-user: number of queues missing")] + ParseGenericVhostUserNumResponseQueuesMissing, + /// Generic vhost-user virtio ID is missing + #[error("Error parsing --generic-vhost-user: virtio ID missing")] + ParseGenericVhostUserVirtioIdMissing, + /// Generic vhost-user available features is missing + #[error("Error parsing --generic-vhost-user: available features missing")] + ParseGenericVhostUserAvailFeaturesMissing, + /// Generic vhost-user queue size is too large + #[error("Error parsing --generic-vhost-user: queue size {0} is {1}, but limit is 65535")] + ParseGenericVhostUserQueueSizeTooLarge(usize, u64), + /// Generic vhost-user queue size missing + #[error("Error parsing --generic-vhost-user: queue size missing")] + ParseGenericVhostUserQueueSizeMissing, /// Missing persistent memory file parameter. #[error("Error parsing --pmem: file missing")] ParsePmemFileMissing, @@ -94,6 +112,9 @@ pub enum Error { /// Error parsing persistent memory parameters #[error("Error parsing --pmem")] ParsePersistentMemory(#[source] OptionParserError), + /// Error parsing generic vhost-user parameters + #[error("Error parsing --generic-vhost-user")] + ParseGenericVhostUser(#[source] OptionParserError), /// Failed parsing console #[error("Error parsing --console")] ParseConsole(#[source] OptionParserError), @@ -394,6 +415,7 @@ pub struct VmParams<'a> { pub rng: &'a str, pub balloon: Option<&'a str>, pub fs: Option>, + pub generic_vhost_user: Option>, pub pmem: Option>, pub serial: &'a str, pub console: &'a str, @@ -455,6 +477,9 @@ impl<'a> VmParams<'a> { let fs: Option> = args .get_many::("fs") .map(|x| x.map(|y| y as &str).collect()); + let generic_vhost_user: Option> = args + .get_many::("generic-vhost-user") + .map(|x| x.map(|y| y as &str).collect()); let pmem: Option> = args .get_many::("pmem") .map(|x| x.map(|y| y as &str).collect()); @@ -509,6 +534,7 @@ impl<'a> VmParams<'a> { rng, balloon, fs, + generic_vhost_user, pmem, serial, console, @@ -1642,6 +1668,82 @@ impl BalloonConfig { } } +impl GenericVhostUserConfig { + pub const SYNTAX: &'static str = "generic vhost-user parameters \ + \"virtio_id=,\ + socket=,\ + queue_sizes=,\ + id=,pci_segment=\""; + + pub fn parse(vhost_user: &str) -> Result { + let mut parser = OptionParser::new(); + parser + .add("virtio_id") + .add("queue_sizes") + .add("socket") + .add("id") + .add("pci_segment"); + parser + .parse(vhost_user) + .map_err(Error::ParseGenericVhostUser)?; + + let socket = parser + .get("socket") + .ok_or(Error::ParseGenericVhostUserSockMissing)?; + + let IntegerList(queue_sizes) = parser + .convert("queue_sizes") + .map_err(Error::ParseGenericVhostUser)? + .ok_or(Error::ParseGenericVhostUserQueueSizeMissing)?; + let device_type = parser + .convert("virtio_id") + .map_err(Error::ParseGenericVhostUser)? + .ok_or(Error::ParseGenericVhostUserVirtioIdMissing)?; + let id = parser.get("id"); + let pci_segment = parser + .convert("pci_segment") + .map_err(Error::ParseGenericVhostUser)? + .unwrap_or_default(); + let mut converted_queue_sizes: Vec = Vec::new(); + for (offset, &queue_size) in queue_sizes.iter().enumerate() { + match queue_size.try_into() { + Err(_) => { + return Err(Error::ParseGenericVhostUserQueueSizeTooLarge( + offset, queue_size, + )); + } + Ok(queue_size) => converted_queue_sizes.push(queue_size), + } + } + + Ok(GenericVhostUserConfig { + socket: socket.into(), + device_type, + id, + pci_segment, + queue_sizes: converted_queue_sizes, + }) + } + + pub fn validate(&self, vm_config: &VmConfig) -> ValidationResult<()> { + if let Some(platform_config) = vm_config.platform.as_ref() { + if self.pci_segment >= platform_config.num_pci_segments { + return Err(ValidationError::InvalidPciSegment(self.pci_segment)); + } + + if let Some(iommu_segments) = platform_config.iommu_segments.as_ref() + && iommu_segments.contains(&self.pci_segment) + { + return Err(ValidationError::IommuNotSupportedOnSegment( + self.pci_segment, + )); + } + } + + Ok(()) + } +} + impl FsConfig { pub const SYNTAX: &'static str = "virtio-fs parameters \ \"tag=,socket=,num_queues=,\ @@ -2738,6 +2840,17 @@ impl VmConfig { } } + if let Some(generic_vhost_user_devices) = &self.generic_vhost_user { + if !generic_vhost_user_devices.is_empty() && !self.backed_by_shared_memory() { + return Err(ValidationError::VhostUserRequiresSharedMemory); + } + for generic_vhost_user_device in generic_vhost_user_devices { + generic_vhost_user_device.validate(self)?; + + Self::validate_identifier(&mut id_list, &generic_vhost_user_device.id)?; + } + } + if let Some(pmems) = &self.pmem { for pmem in pmems { pmem.validate(self)?; @@ -2991,6 +3104,15 @@ impl VmConfig { fs = Some(fs_config_list); } + let mut generic_vhost_user: Option> = None; + if let Some(generic_vhost_user_list) = &vm_params.generic_vhost_user { + let mut generic_vhost_user_config_list = Vec::new(); + for item in generic_vhost_user_list.iter() { + generic_vhost_user_config_list.push(GenericVhostUserConfig::parse(item)?); + } + generic_vhost_user = Some(generic_vhost_user_config_list); + } + let mut pmem: Option> = None; if let Some(pmem_list) = &vm_params.pmem { let mut pmem_config_list = Vec::new(); @@ -3126,6 +3248,7 @@ impl VmConfig { net, rng, balloon, + generic_vhost_user, fs, pmem, serial, @@ -3188,6 +3311,13 @@ impl VmConfig { removed |= fs.len() != len; } + // Remove if generic vhost-user device + if let Some(generic_vhost_user) = self.generic_vhost_user.as_mut() { + let len = generic_vhost_user.len(); + generic_vhost_user.retain(|dev| dev.id.as_ref().map(|id| id.as_ref()) != Some(id)); + removed |= generic_vhost_user.len() != len; + } + // Remove if net device if let Some(net) = self.net.as_mut() { let len = net.len(); @@ -3260,6 +3390,7 @@ impl Clone for VmConfig { #[cfg(feature = "pvmemcontrol")] pvmemcontrol: self.pvmemcontrol.clone(), fs: self.fs.clone(), + generic_vhost_user: self.generic_vhost_user.clone(), pmem: self.pmem.clone(), serial: self.serial.clone(), console: self.console.clone(), @@ -3784,6 +3915,90 @@ mod unit_tests { Ok(()) } + #[track_caller] + #[allow(clippy::too_many_arguments)] + fn make_vhost_user_config( + socket: &str, + virtio_id: u64, + id: &str, + pci_segment: u64, + queue_sizes: &IntegerList, + ) { + assert!(!socket.contains(",[]\n\r\0\"")); + assert!(!id.contains(",[]\n\r\0\"")); + let config = GenericVhostUserConfig::parse(&format!( + "virtio_id={virtio_id},socket=\"{socket}\",\ +id=\"{id}\",pci_segment={pci_segment},queue_sizes={queue_sizes}" + )); + if pci_segment <= u16::MAX.into() + && virtio_id <= u32::MAX.into() + && queue_sizes.0.iter().all(|&f| f <= u16::MAX.into()) + { + assert_eq!( + config.unwrap(), + GenericVhostUserConfig { + socket: socket.into(), + id: Some(id.to_owned()), + device_type: u32::try_from(virtio_id).unwrap(), + pci_segment: u16::try_from(pci_segment).unwrap(), + queue_sizes: queue_sizes + .0 + .iter() + .map(|&f| u16::try_from(f).unwrap()) + .collect(), + } + ); + } else { + config.unwrap_err(); + } + } + + #[test] + fn test_parse_vhost_user() -> Result<()> { + // all parameters must be supplied, except pci_segment + GenericVhostUserConfig::parse("").unwrap_err(); + GenericVhostUserConfig::parse("virtio_id=1").unwrap_err(); + GenericVhostUserConfig::parse("queue_size=1").unwrap_err(); + GenericVhostUserConfig::parse("socket=/tmp/sock").unwrap_err(); + GenericVhostUserConfig::parse("id=1").unwrap_err(); + make_vhost_user_config( + "/dev/null/doesnotexist", + 100, + "Something", + 10, + &IntegerList(vec![u16::MAX.into(), 20u16.into()]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + 100, + "Something", + 10, + &IntegerList(vec![u16::MAX.into()]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + u64::from(u32::MAX) + 1, + "Something", + 10, + &IntegerList(vec![20u64]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + u64::from(u32::MAX) + 1, + "Something", + 10, + &IntegerList(vec![20u64]), + ); + make_vhost_user_config( + "/dev/null/doesnotexist", + u64::from(u32::MAX) + 1, + "Something", + 10, + &IntegerList(vec![20u64]), + ); + Ok(()) + } + fn pmem_fixture() -> PmemConfig { PmemConfig { file: PathBuf::from("/tmp/pmem"), @@ -4168,6 +4383,7 @@ mod unit_tests { rate_limit_groups: None, disks: None, rng: RngConfig::default(), + generic_vhost_user: None, balloon: None, fs: None, pmem: None, @@ -4373,6 +4589,7 @@ mod unit_tests { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index a7d3254c3..f22696c7f 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -127,8 +127,8 @@ use crate::serial_manager::{Error as SerialManagerError, SerialManager}; use crate::vm_config::IvshmemConfig; use crate::vm_config::{ ConsoleOutputMode, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS, DEFAULT_PCI_SEGMENT_APERTURE_WEIGHT, - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VhostMode, VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VhostMode, VmConfig, VsockConfig, }; use crate::{DEVICE_MANAGER_SNAPSHOT_ID, GuestRegionMmap, PciDeviceInfo, device_node}; @@ -158,6 +158,7 @@ const IVSHMEM_DEVICE_NAME: &str = "__ivshmem"; const DISK_DEVICE_NAME_PREFIX: &str = "_disk"; const FS_DEVICE_NAME_PREFIX: &str = "_fs"; const NET_DEVICE_NAME_PREFIX: &str = "_net"; +const GENERIC_VHOST_USER_DEVICE_NAME_PREFIX: &str = "_generic_vhost_user"; const PMEM_DEVICE_NAME_PREFIX: &str = "_pmem"; const VDPA_DEVICE_NAME_PREFIX: &str = "_vdpa"; const VSOCK_DEVICE_NAME_PREFIX: &str = "_vsock"; @@ -197,6 +198,10 @@ pub enum DeviceManagerError { #[error("Cannot create virtio-rng device")] CreateVirtioRng(#[source] io::Error), + /// Cannot create generic vhost-user device + #[error("Cannot create generic vhost-user device")] + CreateGenericVhostUser(#[source] virtio_devices::vhost_user::Error), + /// Cannot create virtio-fs device #[error("Cannot create virtio-fs device")] CreateVirtioFs(#[source] virtio_devices::vhost_user::Error), @@ -205,6 +210,10 @@ pub enum DeviceManagerError { #[error("Virtio-fs device was created without a socket")] NoVirtioFsSock, + /// Generic vhost-user device was created without a socket. + #[error("Generic vhost-user device was created without a socket")] + NoGenericVhostUserSock, + /// Cannot create vhost-user-blk device #[error("Cannot create vhost-user-blk device")] CreateVhostUserBlk(#[source] virtio_devices::vhost_user::Error), @@ -2554,6 +2563,9 @@ impl DeviceManager { self.make_virtio_net_devices()?; self.make_virtio_rng_devices()?; + // Add generic vhost-user if required + self.make_generic_vhost_user_devices()?; + // Add virtio-fs if required self.make_virtio_fs_devices()?; @@ -3122,6 +3134,72 @@ impl DeviceManager { Ok(()) } + fn make_generic_vhost_user_device( + &mut self, + generic_vhost_user_cfg: &mut GenericVhostUserConfig, + ) -> DeviceManagerResult { + let id = if let Some(id) = &generic_vhost_user_cfg.id { + id.clone() + } else { + let id = self.next_device_name(GENERIC_VHOST_USER_DEVICE_NAME_PREFIX)?; + generic_vhost_user_cfg.id = Some(id.clone()); + id + }; + + info!("Creating generic vhost-user device: {generic_vhost_user_cfg:?}"); + + let mut node = device_node!(id); + + if let Some(generic_vhost_user_socket) = generic_vhost_user_cfg.socket.to_str() { + let generic_vhost_user_device = Arc::new(Mutex::new( + virtio_devices::vhost_user::GenericVhostUser::new( + id.clone(), + generic_vhost_user_socket, + generic_vhost_user_cfg.queue_sizes.clone(), + generic_vhost_user_cfg.device_type, + None, + self.seccomp_action.clone(), + self.exit_evt + .try_clone() + .map_err(DeviceManagerError::EventFd)?, + self.force_iommu, + state_from_id(self.snapshot.as_ref(), id.as_str()) + .map_err(DeviceManagerError::RestoreGetState)?, + ) + .map_err(DeviceManagerError::CreateGenericVhostUser)?, + )); + + // Update the device tree with the migratable device. + node.migratable = + Some(Arc::clone(&generic_vhost_user_device) as Arc>); + self.device_tree.lock().unwrap().insert(id.clone(), node); + + Ok(MetaVirtioDevice { + virtio_device: Arc::clone(&generic_vhost_user_device) + as Arc>, + iommu: false, + id, + pci_segment: generic_vhost_user_cfg.pci_segment, + dma_handler: None, + }) + } else { + Err(DeviceManagerError::NoGenericVhostUserSock) + } + } + + fn make_generic_vhost_user_devices(&mut self) -> DeviceManagerResult<()> { + let mut generic_vhost_user_devices = self.config.lock().unwrap().generic_vhost_user.clone(); + if let Some(generic_vhost_user_list_cfg) = &mut generic_vhost_user_devices { + for generic_vhost_user_cfg in generic_vhost_user_list_cfg.iter_mut() { + let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?; + self.virtio_devices.push(device); + } + } + self.config.lock().unwrap().generic_vhost_user = generic_vhost_user_devices; + + Ok(()) + } + fn make_virtio_fs_device( &mut self, fs_cfg: &mut FsConfig, @@ -4918,6 +4996,16 @@ impl DeviceManager { self.hotplug_virtio_pci_device(device) } + pub fn add_generic_vhost_user( + &mut self, + generic_vhost_user_cfg: &mut GenericVhostUserConfig, + ) -> DeviceManagerResult { + self.validate_identifier(&generic_vhost_user_cfg.id)?; + + let device = self.make_generic_vhost_user_device(generic_vhost_user_cfg)?; + self.hotplug_virtio_pci_device(device) + } + pub fn add_pmem(&mut self, pmem_cfg: &mut PmemConfig) -> DeviceManagerResult { self.validate_identifier(&pmem_cfg.id)?; diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index dcf6614b2..627b13d5d 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -59,8 +59,8 @@ use crate::migration::{recv_vm_config, recv_vm_state}; use crate::seccomp_filters::{Thread, get_seccomp_filter}; use crate::vm::{Error as VmError, Vm, VmState}; use crate::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig, VdpaConfig, - VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, NetConfig, PmemConfig, + UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; mod acpi; @@ -2125,6 +2125,39 @@ impl RequestHandler for Vmm { } } + fn vm_add_generic_vhost_user( + &mut self, + generic_vhost_user_cfg: GenericVhostUserConfig, + ) -> result::Result>, VmError> { + self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; + + { + // Validate the configuration change in a cloned configuration + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap().clone(); + add_to_config( + &mut config.generic_vhost_user, + generic_vhost_user_cfg.clone(), + ); + config.validate().map_err(VmError::ConfigValidation)?; + } + + if let Some(ref mut vm) = self.vm { + let info = vm + .add_generic_vhost_user(generic_vhost_user_cfg) + .inspect_err(|e| { + error!("Error when adding new generic vhost-user device to the VM: {e:?}"); + })?; + serde_json::to_vec(&info) + .map(Some) + .map_err(VmError::SerializeJson) + } else { + // Update VmConfig by adding the new device. + let mut config = self.vm_config.as_ref().unwrap().lock().unwrap(); + add_to_config(&mut config.generic_vhost_user, generic_vhost_user_cfg); + Ok(None) + } + } + fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> result::Result>, VmError> { self.vm_config.as_ref().ok_or(VmError::VmNotCreated)?; @@ -2443,6 +2476,7 @@ mod unit_tests { }, balloon: None, fs: None, + generic_vhost_user: None, pmem: None, serial: ConsoleConfig { file: None, @@ -2679,6 +2713,59 @@ mod unit_tests { ); } + #[test] + fn test_vmm_vm_cold_add_generic_vhost_user() { + let mut vmm = create_dummy_vmm(); + let generic_vhost_user_config = + GenericVhostUserConfig::parse("virtio_id=26,socket=/tmp/sock,queue_sizes=[1024]") + .unwrap(); + + assert!(matches!( + vmm.vm_add_generic_vhost_user(generic_vhost_user_config.clone()), + Err(VmError::VmNotCreated) + )); + + let _ = vmm.vm_create(create_dummy_vm_config()); + assert!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .generic_vhost_user + .is_none() + ); + + assert!( + vmm.vm_add_generic_vhost_user(generic_vhost_user_config.clone()) + .unwrap() + .is_none() + ); + assert_eq!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .generic_vhost_user + .clone() + .unwrap() + .len(), + 1 + ); + assert_eq!( + vmm.vm_config + .as_ref() + .unwrap() + .lock() + .unwrap() + .generic_vhost_user + .clone() + .unwrap()[0], + generic_vhost_user_config + ); + } + #[test] fn test_vmm_vm_cold_add_pmem() { let mut vmm = create_dummy_vmm(); diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index db29072be..9c37457c1 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -100,8 +100,8 @@ use crate::migration::{SNAPSHOT_CONFIG_FILE, SNAPSHOT_STATE_FILE, url_to_path}; #[cfg(feature = "fw_cfg")] use crate::vm_config::FwCfgConfig; use crate::vm_config::{ - DeviceConfig, DiskConfig, FsConfig, HotplugMethod, NetConfig, NumaConfig, PayloadConfig, - PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, + DeviceConfig, DiskConfig, FsConfig, GenericVhostUserConfig, HotplugMethod, NetConfig, + NumaConfig, PayloadConfig, PmemConfig, UserDeviceConfig, VdpaConfig, VmConfig, VsockConfig, }; use crate::{ CPU_MANAGER_SNAPSHOT_ID, DEVICE_MANAGER_SNAPSHOT_ID, GuestMemoryMmap, @@ -2136,6 +2136,33 @@ impl Vm { Ok(pci_device_info) } + pub fn add_generic_vhost_user( + &mut self, + mut generic_vhost_user_cfg: GenericVhostUserConfig, + ) -> Result { + let pci_device_info = self + .device_manager + .lock() + .unwrap() + .add_generic_vhost_user(&mut generic_vhost_user_cfg) + .map_err(Error::DeviceManager)?; + + // Update VmConfig by adding the new device. This is important to + // ensure the device would be created in case of a reboot. + { + let mut config = self.config.lock().unwrap(); + add_to_config(&mut config.generic_vhost_user, generic_vhost_user_cfg); + } + + self.device_manager + .lock() + .unwrap() + .notify_hotplug(AcpiNotificationFlags::PCI_DEVICES_CHANGED) + .map_err(Error::DeviceManager)?; + + Ok(pci_device_info) + } + pub fn add_pmem(&mut self, mut pmem_cfg: PmemConfig) -> Result { let pci_device_info = self .device_manager diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 5de8c3145..b9e67f7bb 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -472,6 +472,24 @@ impl ApplyLandlock for FsConfig { } } +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct GenericVhostUserConfig { + pub socket: PathBuf, + pub queue_sizes: Vec, + #[serde(default)] + pub id: Option, + #[serde(default)] + pub pci_segment: u16, + pub device_type: u32, +} + +impl ApplyLandlock for GenericVhostUserConfig { + fn apply_landlock(&self, landlock: &mut Landlock) -> LandlockResult<()> { + landlock.add_rule_with_access(&self.socket, "rw")?; + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct PmemConfig { pub file: PathBuf, @@ -924,6 +942,7 @@ pub struct VmConfig { #[serde(default)] pub rng: RngConfig, pub balloon: Option, + pub generic_vhost_user: Option>, pub fs: Option>, pub pmem: Option>, #[serde(default = "default_serial")] @@ -1000,6 +1019,12 @@ impl VmConfig { } } + if let Some(generic_vhost_user_configs) = &self.generic_vhost_user { + for generic_vhost_user_config in generic_vhost_user_configs.iter() { + generic_vhost_user_config.apply_landlock(&mut landlock)?; + } + } + if let Some(pmem_configs) = &self.pmem { for pmem_config in pmem_configs.iter() { pmem_config.apply_landlock(&mut landlock)?;