vmm: ivshmem device support

Signed-off-by: Yi Wang <foxywang@tencent.com>
Signed-off-by: Songqian Li <sionli@tencent.com>
This commit is contained in:
Songqian Li
2024-08-29 15:58:50 +08:00
committed by Bo Chen
parent c72414552b
commit 2c282a5a54
7 changed files with 184 additions and 0 deletions

View File

@@ -68,6 +68,7 @@ fw_cfg = ["vmm/fw_cfg"]
guest_debug = ["vmm/guest_debug"]
igvm = ["mshv", "vmm/igvm"]
io_uring = ["vmm/io_uring"]
ivshmem = ["vmm/ivshmem"]
kvm = ["vmm/kvm"]
mshv = ["vmm/mshv"]
pvmemcontrol = ["vmm/pvmemcontrol"]

View File

@@ -29,6 +29,8 @@ use vmm::landlock::{Landlock, LandlockError};
use vmm::vm_config;
#[cfg(feature = "fw_cfg")]
use vmm::vm_config::FwCfgConfig;
#[cfg(feature = "ivshmem")]
use vmm::vm_config::IvshmemConfig;
#[cfg(target_arch = "x86_64")]
use vmm::vm_config::SgxEpcConfig;
use vmm::vm_config::{
@@ -300,6 +302,12 @@ fn get_cli_options_sorted(
.help("Path to initramfs image")
.num_args(1)
.group("vm-config"),
#[cfg(feature = "ivshmem")]
Arg::new("ivshmem")
.long("ivshmem")
.help(IvshmemConfig::SYNTAX)
.num_args(1)
.group("vm-config"),
Arg::new("kernel")
.long("kernel")
.help(
@@ -1034,6 +1042,8 @@ mod unit_tests {
preserved_fds: None,
landlock_enable: false,
landlock_rules: None,
#[cfg(feature = "ivshmem")]
ivshmem: None,
};
assert_eq!(expected_vm_config, result_vm_config);

View File

@@ -12,6 +12,7 @@ fw_cfg = ["devices/fw_cfg"]
guest_debug = ["gdbstub", "gdbstub_arch", "kvm"]
igvm = ["dep:igvm", "hex", "igvm_defs", "mshv-bindings", "range_map_vec"]
io_uring = ["block/io_uring"]
ivshmem = ["devices/ivshmem"]
kvm = [
"arch/kvm",
"hypervisor/kvm",

View File

@@ -4,6 +4,8 @@
//
use std::collections::{BTreeSet, HashMap};
#[cfg(feature = "ivshmem")]
use std::fs;
use std::path::PathBuf;
use std::result;
use std::str::FromStr;
@@ -154,9 +156,17 @@ pub enum Error {
/// Failed parsing TPM device
#[error("Error parsing --tpm")]
ParseTpm(#[source] OptionParserError),
#[cfg(feature = "ivshmem")]
/// Failed parsing ivsmem device
#[error("Error parsing --ivshmem")]
ParseIvshmem(#[source] OptionParserError),
/// Missing path for TPM device
#[error("Error parsing --tpm: path missing")]
ParseTpmPathMissing,
#[cfg(feature = "ivshmem")]
/// Missing path for ivsmem device
#[error("Error parsing --ivshmem: path missing")]
ParseIvshmemPathMissing,
/// Error parsing Landlock rules
#[error("Error parsing --landlock-rules")]
ParseLandlockRules(#[source] OptionParserError),
@@ -334,6 +344,18 @@ pub enum ValidationError {
/// FwCfg missing initramfs
#[error("Error --fw-cfg-config: missing --initramfs")]
FwCfgMissingInitramfs,
#[cfg(feature = "ivshmem")]
/// Invalid Ivshmem input size
#[error("Invalid ivshmem input size")]
InvalidIvshmemInputSize(u64),
#[cfg(feature = "ivshmem")]
/// Invalid Ivshmem backend file size
#[error("Invalid ivshmem backend file size")]
InvalidIvshmemSize(u64),
#[cfg(feature = "ivshmem")]
/// Invalid Ivshmem backend file path
#[error("Invalid ivshmem backend file path")]
InvalidIvshmemPath,
}
type ValidationResult<T> = std::result::Result<T, ValidationError>;
@@ -391,6 +413,8 @@ pub struct VmParams<'a> {
pub landlock_rules: Option<Vec<&'a str>>,
#[cfg(feature = "fw_cfg")]
pub fw_cfg_config: Option<&'a str>,
#[cfg(feature = "ivshmem")]
pub ivshmem: Option<&'a str>,
}
impl<'a> VmParams<'a> {
@@ -465,6 +489,8 @@ impl<'a> VmParams<'a> {
#[cfg(feature = "fw_cfg")]
let fw_cfg_config: Option<&str> =
args.get_one::<String>("fw-cfg-config").map(|x| x as &str);
#[cfg(feature = "ivshmem")]
let ivshmem: Option<&str> = args.get_one::<String>("ivshmem").map(|x| x as &str);
VmParams {
cpus,
memory,
@@ -508,6 +534,8 @@ impl<'a> VmParams<'a> {
landlock_rules,
#[cfg(feature = "fw_cfg")]
fw_cfg_config,
#[cfg(feature = "ivshmem")]
ivshmem,
}
}
}
@@ -2397,6 +2425,47 @@ impl LandlockConfig {
}
}
#[cfg(feature = "ivshmem")]
impl IvshmemConfig {
pub const SYNTAX: &'static str = "Ivshmem device. Specify the backend file path and size \
for the shared memory: \"path=</path/to/a/file>, size=<file_size>\" \
\nThe <file_size> must be a power of 2 (e.g., 2M, 4M, etc.), as it represents the size \
of the memory region mapped to the guest. Default size is 128M.";
pub fn parse(ivshmem: &str) -> Result<Self> {
let mut parser = OptionParser::new();
parser.add("path").add("size");
parser.parse(ivshmem).map_err(Error::ParseIvshmem)?;
let path = parser
.get("path")
.map(PathBuf::from)
.ok_or(Error::ParseIvshmemPathMissing)?;
let size = parser
.convert::<ByteSized>("size")
.map_err(Error::ParseIvshmem)?
.unwrap_or(ByteSized((DEFAULT_IVSHMEM_SIZE << 20) as u64))
.0;
Ok(IvshmemConfig {
path,
size: size as usize,
})
}
pub fn validate(&self) -> ValidationResult<()> {
let size = self.size as u64;
let path = &self.path;
// size must = 2^n
if !size.is_power_of_two() {
return Err(ValidationError::InvalidIvshmemInputSize(size));
}
let metadata = fs::metadata(path.to_str().unwrap())
.map_err(|_| ValidationError::InvalidIvshmemPath)?;
if metadata.len() < size {
return Err(ValidationError::InvalidIvshmemSize(metadata.len()));
}
Ok(())
}
}
impl VmConfig {
fn validate_identifier(
id_list: &mut BTreeSet<String>,
@@ -2754,6 +2823,10 @@ impl VmConfig {
landlock_rule.validate()?;
}
}
#[cfg(feature = "ivshmem")]
if let Some(ivshmem_config) = &self.ivshmem {
ivshmem_config.validate()?;
}
Ok(id_list)
}
@@ -2951,6 +3024,14 @@ impl VmConfig {
);
}
#[cfg(feature = "ivshmem")]
let mut ivshmem: Option<IvshmemConfig> = None;
#[cfg(feature = "ivshmem")]
if let Some(iv) = vm_params.ivshmem {
let ivshmem_conf = IvshmemConfig::parse(iv)?;
ivshmem = Some(ivshmem_conf);
}
let mut config = VmConfig {
cpus: CpusConfig::parse(vm_params.cpus)?,
memory: MemoryConfig::parse(vm_params.memory, vm_params.memory_zones)?,
@@ -2986,6 +3067,8 @@ impl VmConfig {
preserved_fds: None,
landlock_enable: vm_params.landlock_enable,
landlock_rules,
#[cfg(feature = "ivshmem")]
ivshmem,
};
config.validate().map_err(Error::Validation)?;
Ok(config)
@@ -3115,6 +3198,8 @@ impl Clone for VmConfig {
// SAFETY: FFI call with valid FDs
.map(|fds| fds.iter().map(|fd| unsafe { libc::dup(*fd) }).collect()),
landlock_rules: self.landlock_rules.clone(),
#[cfg(feature = "ivshmem")]
ivshmem: self.ivshmem.clone(),
..*self
}
}
@@ -3919,6 +4004,8 @@ mod tests {
]),
landlock_enable: false,
landlock_rules: None,
#[cfg(feature = "ivshmem")]
ivshmem: None,
};
let valid_config = RestoreConfig {
@@ -4114,6 +4201,8 @@ mod tests {
preserved_fds: None,
landlock_enable: false,
landlock_rules: None,
#[cfg(feature = "ivshmem")]
ivshmem: None,
};
valid_config.validate().unwrap();

View File

@@ -115,6 +115,8 @@ use crate::interrupt::{LegacyUserspaceInterruptManager, MsiInterruptManager};
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager, MEMORY_MANAGER_ACPI_SIZE};
use crate::pci_segment::PciSegment;
use crate::serial_manager::{Error as SerialManagerError, SerialManager};
#[cfg(feature = "ivshmem")]
use crate::vm_config::IvshmemConfig;
use crate::vm_config::{
ConsoleOutputMode, DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, UserDeviceConfig,
VdpaConfig, VhostMode, VmConfig, VsockConfig, DEFAULT_IOMMU_ADDRESS_WIDTH_BITS,
@@ -140,6 +142,8 @@ const PVMEMCONTROL_DEVICE_NAME: &str = "__pvmemcontrol";
const BALLOON_DEVICE_NAME: &str = "__balloon";
const CONSOLE_DEVICE_NAME: &str = "__console";
const PVPANIC_DEVICE_NAME: &str = "__pvpanic";
#[cfg(feature = "ivshmem")]
const IVSHMEM_DEVICE_NAME: &str = "__ivshmem";
// Devices that the user may name and for which we generate
// identifiers if the user doesn't give one
@@ -632,6 +636,11 @@ pub enum DeviceManagerError {
#[error("Cannot create a PvPanic device")]
PvPanicCreate(#[source] devices::pvpanic::PvPanicError),
#[cfg(feature = "ivshmem")]
/// Cannot create a ivshmem device
#[error("Cannot create a ivshmem device: {0}")]
IvshmemCreate(devices::ivshmem::IvshmemError),
/// Cannot create a RateLimiterGroup
#[error("Cannot create a RateLimiterGroup")]
RateLimiterGroupCreate(#[source] rate_limiter::group::Error),
@@ -1085,6 +1094,10 @@ pub struct DeviceManager {
#[cfg(feature = "fw_cfg")]
fw_cfg: Option<Arc<Mutex<FwCfg>>>,
#[cfg(feature = "ivshmem")]
// ivshmem device
ivshmem_device: Option<Arc<Mutex<devices::IvshmemDevice>>>,
}
fn create_mmio_allocators(
@@ -1351,6 +1364,8 @@ impl DeviceManager {
mmio_regions: Arc::new(Mutex::new(Vec::new())),
#[cfg(feature = "fw_cfg")]
fw_cfg: None,
#[cfg(feature = "ivshmem")]
ivshmem_device: None,
};
let device_manager = Arc::new(Mutex::new(device_manager));
@@ -1474,6 +1489,11 @@ impl DeviceManager {
self.pvpanic_device = self.add_pvpanic_device()?;
}
#[cfg(feature = "ivshmem")]
if let Some(ivshmem) = self.config.clone().lock().unwrap().ivshmem.as_ref() {
self.ivshmem_device = self.add_ivshmem_device(ivshmem)?;
}
Ok(())
}
@@ -4199,6 +4219,43 @@ impl DeviceManager {
Ok(Some(pvpanic_device))
}
#[cfg(feature = "ivshmem")]
fn add_ivshmem_device(
&mut self,
ivshmem_cfg: &IvshmemConfig,
) -> DeviceManagerResult<Option<Arc<Mutex<devices::IvshmemDevice>>>> {
let id = String::from(IVSHMEM_DEVICE_NAME);
let pci_segment_id = 0x0_u16;
info!("Creating ivshmem device {}", id);
let (pci_segment_id, pci_device_bdf, resources) =
self.pci_resources(&id, pci_segment_id)?;
let snapshot = snapshot_from_id(self.snapshot.as_ref(), id.as_str());
let ivshmem_device = Arc::new(Mutex::new(
devices::IvshmemDevice::new(
id.clone(),
ivshmem_cfg.size as u64,
snapshot,
)
.map_err(DeviceManagerError::IvshmemCreate)?,
));
let new_resources = self.add_pci_device(
ivshmem_device.clone(),
ivshmem_device.clone(),
pci_segment_id,
pci_device_bdf,
resources,
)?;
let mut node = device_node!(id, ivshmem_device);
node.resources = new_resources;
node.pci_bdf = Some(pci_device_bdf);
node.pci_device_handle = None;
self.device_tree.lock().unwrap().insert(id, node);
Ok(Some(ivshmem_device))
}
fn pci_resources(
&self,
id: &str,

View File

@@ -423,6 +423,8 @@ pub fn feature_list() -> Vec<String> {
"tdx".to_string(),
#[cfg(feature = "tracing")]
"tracing".to_string(),
#[cfg(feature = "ivshmem")]
"ivshmem".to_string(),
]
}
@@ -2438,6 +2440,8 @@ mod unit_tests {
preserved_fds: None,
landlock_enable: false,
landlock_rules: None,
#[cfg(feature = "ivshmem")]
ivshmem: None,
})
}

View File

@@ -650,6 +650,26 @@ impl ApplyLandlock for VsockConfig {
}
}
#[cfg(feature = "ivshmem")]
pub const DEFAULT_IVSHMEM_SIZE: usize = 128;
#[cfg(feature = "ivshmem")]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct IvshmemConfig {
pub path: PathBuf,
pub size: usize,
}
#[cfg(feature = "ivshmem")]
impl Default for IvshmemConfig {
fn default() -> Self {
Self {
path: PathBuf::new(),
size: DEFAULT_IVSHMEM_SIZE << 20,
}
}
}
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct SgxEpcConfig {
@@ -896,6 +916,8 @@ pub struct VmConfig {
#[serde(default)]
pub landlock_enable: bool,
pub landlock_rules: Option<Vec<LandlockConfig>>,
#[cfg(feature = "ivshmem")]
pub ivshmem: Option<IvshmemConfig>,
}
impl VmConfig {