mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
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:
committed by
Rob Bradford
parent
8c618ff5e0
commit
085a7a49fa
@@ -998,6 +998,7 @@ mod unit_tests {
|
||||
},
|
||||
balloon: None,
|
||||
fs: None,
|
||||
generic_vhost_user: None,
|
||||
pmem: None,
|
||||
serial: ConsoleConfig {
|
||||
file: None,
|
||||
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
Reference in New Issue
Block a user