vmm: generic vhost-user: add support

Add VMM support for generic vhost-user devices.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
This commit is contained in:
Demi Marie Obenour
2026-02-04 09:52:42 -05:00
committed by Rob Bradford
parent 8c618ff5e0
commit 085a7a49fa
8 changed files with 515 additions and 8 deletions

View File

@@ -998,6 +998,7 @@ mod unit_tests {
},
balloon: None,
fs: None,
generic_vhost_user: None,
pmem: None,
serial: ConsoleConfig {
file: None,

View File

@@ -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<u64>);
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}")]

View File

@@ -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<Option<Vec<u8>>, VmError>;
fn vm_add_generic_vhost_user(
&mut self,
fs_cfg: GenericVhostUserConfig,
) -> Result<Option<Vec<u8>>, VmError>;
fn vm_add_pmem(&mut self, pmem_cfg: PmemConfig) -> Result<Option<Vec<u8>>, VmError>;
fn vm_add_net(&mut self, net_cfg: NetConfig) -> Result<Option<Vec<u8>>, VmError>;
@@ -539,6 +548,43 @@ impl ApiAction for VmAddFs {
}
}
pub struct VmAddGenericVhostUser;
impl ApiAction for VmAddGenericVhostUser {
type RequestBody = GenericVhostUserConfig;
type ResponseBody = Option<Body>;
fn request(
&self,
config: Self::RequestBody,
response_sender: Sender<ApiResponse>,
) -> 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<ApiRequest>,
data: Self::RequestBody,
) -> ApiResult<Self::ResponseBody> {
get_response_body(self, api_evt, api_sender, data)
}
}
pub struct VmAddPmem;
impl ApiAction for VmAddPmem {

View File

@@ -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<Vec<&'a str>>,
pub generic_vhost_user: Option<Vec<&'a str>>,
pub pmem: Option<Vec<&'a str>>,
pub serial: &'a str,
pub console: &'a str,
@@ -455,6 +477,9 @@ impl<'a> VmParams<'a> {
let fs: Option<Vec<&str>> = args
.get_many::<String>("fs")
.map(|x| x.map(|y| y as &str).collect());
let generic_vhost_user: Option<Vec<&str>> = args
.get_many::<String>("generic-vhost-user")
.map(|x| x.map(|y| y as &str).collect());
let pmem: Option<Vec<&str>> = args
.get_many::<String>("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=<ID number for virtio device type (FS, block, net, etc)>,\
socket=<socket_path>,\
queue_sizes=<list of queue sizes>,\
id=<device_id>,pci_segment=<segment_id>\"";
pub fn parse(vhost_user: &str) -> Result<Self> {
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<u16> = 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=<tag_name>,socket=<socket_path>,num_queues=<number_of_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<Vec<GenericVhostUserConfig>> = 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<Vec<PmemConfig>> = 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,

View File

@@ -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<MetaVirtioDevice> {
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<Mutex<dyn Migratable>>);
self.device_tree.lock().unwrap().insert(id.clone(), node);
Ok(MetaVirtioDevice {
virtio_device: Arc::clone(&generic_vhost_user_device)
as Arc<Mutex<dyn virtio_devices::VirtioDevice>>,
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<PciDeviceInfo> {
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<PciDeviceInfo> {
self.validate_identifier(&pmem_cfg.id)?;

View File

@@ -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<Option<Vec<u8>>, 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<Option<Vec<u8>>, 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();

View File

@@ -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<PciDeviceInfo> {
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<PciDeviceInfo> {
let pci_device_info = self
.device_manager

View File

@@ -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<u16>,
#[serde(default)]
pub id: Option<String>,
#[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<BalloonConfig>,
pub generic_vhost_user: Option<Vec<GenericVhostUserConfig>>,
pub fs: Option<Vec<FsConfig>>,
pub pmem: Option<Vec<PmemConfig>>,
#[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)?;