diff --git a/Cargo.toml b/Cargo.toml index 89f994aaf..c621c256d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/main.rs b/src/main.rs index 4ba766e05..6daac338f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); diff --git a/vmm/Cargo.toml b/vmm/Cargo.toml index 6b33e6344..6ebabc01a 100644 --- a/vmm/Cargo.toml +++ b/vmm/Cargo.toml @@ -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", diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 28d8cc6d8..1977c8728 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -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 = std::result::Result; @@ -391,6 +413,8 @@ pub struct VmParams<'a> { pub landlock_rules: Option>, #[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::("fw-cfg-config").map(|x| x as &str); + #[cfg(feature = "ivshmem")] + let ivshmem: Option<&str> = args.get_one::("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=, size=\" \ + \nThe 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 { + 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::("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, @@ -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 = 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(); diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index ac1c0a406..9f32da8ba 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -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>>, + + #[cfg(feature = "ivshmem")] + // ivshmem device + ivshmem_device: Option>>, } 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>>> { + 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, diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index f17c4b79d..dddfe9bd3 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -423,6 +423,8 @@ pub fn feature_list() -> Vec { "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, }) } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 2d829a678..50841eeed 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -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>, + #[cfg(feature = "ivshmem")] + pub ivshmem: Option, } impl VmConfig {