| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::path::PathBuf; |
| use std::str::FromStr; |
| |
| use devices::IommuDevType; |
| use devices::PciAddress; |
| use devices::SerialParameters; |
| use serde::Deserialize; |
| use serde::Serialize; |
| use serde_keyvalue::FromKeyValues; |
| |
| use crate::crosvm::config::Config; |
| |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| pub enum HypervisorKind { |
| Kvm, |
| #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] |
| #[cfg(feature = "geniezone")] |
| Geniezone, |
| #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))] |
| Gunyah, |
| } |
| |
| impl FromStr for HypervisorKind { |
| type Err = &'static str; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| match s.to_lowercase().as_str() { |
| "kvm" => Ok(HypervisorKind::Kvm), |
| #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] |
| #[cfg(feature = "geniezone")] |
| "geniezone" => Ok(HypervisorKind::Geniezone), |
| #[cfg(all(any(target_arch = "arm", target_arch = "aarch64"), feature = "gunyah"))] |
| "gunyah" => Ok(HypervisorKind::Gunyah), |
| _ => Err("invalid hypervisor backend"), |
| } |
| } |
| } |
| |
| #[cfg(feature = "audio")] |
| pub fn parse_ac97_options( |
| #[allow(unused_variables)] ac97_params: &mut devices::Ac97Parameters, |
| key: &str, |
| #[allow(unused_variables)] value: &str, |
| ) -> Result<(), String> { |
| match key { |
| #[cfg(feature = "audio_cras")] |
| "client_type" => ac97_params |
| .set_client_type(value) |
| .map_err(|e| crate::crosvm::config::invalid_value_err(value, e)), |
| #[cfg(feature = "audio_cras")] |
| "socket_type" => ac97_params |
| .set_socket_type(value) |
| .map_err(|e| crate::crosvm::config::invalid_value_err(value, e)), |
| _ => Err(format!("unknown ac97 parameter {}", key)), |
| } |
| } |
| |
| // Doesn't do anything on unix. |
| pub fn check_serial_params(_serial_params: &SerialParameters) -> Result<(), String> { |
| Ok(()) |
| } |
| |
| pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> { |
| if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() { |
| if cfg.host_ip.is_none() { |
| return Err("`host-ip` missing from network config".to_string()); |
| } |
| if cfg.netmask.is_none() { |
| return Err("`netmask` missing from network config".to_string()); |
| } |
| if cfg.mac_address.is_none() { |
| return Err("`mac` missing from network config".to_string()); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// VFIO device structure for creating a new instance based on command line options. |
| #[derive(Serialize, Deserialize, FromKeyValues)] |
| #[serde(deny_unknown_fields, rename_all = "kebab-case")] |
| pub struct VfioOption { |
| /// Path to the VFIO device. |
| pub path: PathBuf, |
| |
| /// IOMMU type to use for this VFIO device. |
| #[serde(default)] |
| pub iommu: IommuDevType, |
| |
| /// PCI address to use for the VFIO device in the guest. |
| /// If not specified, defaults to mirroring the host PCI address. |
| pub guest_address: Option<PciAddress>, |
| |
| /// Apply special handling for Intel LPSS devices. |
| #[cfg(feature = "direct")] |
| #[serde(default)] |
| pub intel_lpss: bool, |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::path::PathBuf; |
| |
| use argh::FromArgs; |
| |
| use super::*; |
| use crate::crosvm::config::from_key_values; |
| #[cfg(feature = "audio_cras")] |
| use crate::crosvm::config::parse_ac97_options; |
| use crate::crosvm::config::BindMount; |
| use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_HEIGHT; |
| use crate::crosvm::config::DEFAULT_TOUCH_DEVICE_WIDTH; |
| |
| #[cfg(feature = "audio_cras")] |
| #[test] |
| fn parse_ac97_socket_type() { |
| parse_ac97_options("socket_type=unified").expect("parse should have succeded"); |
| parse_ac97_options("socket_type=legacy").expect("parse should have succeded"); |
| } |
| |
| #[test] |
| fn parse_coiommu_options() { |
| use std::time::Duration; |
| |
| use devices::CoIommuParameters; |
| use devices::CoIommuUnpinPolicy; |
| |
| // unpin_policy |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=off").unwrap(); |
| assert_eq!( |
| coiommu_params, |
| CoIommuParameters { |
| unpin_policy: CoIommuUnpinPolicy::Off, |
| ..Default::default() |
| } |
| ); |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=lru").unwrap(); |
| assert_eq!( |
| coiommu_params, |
| CoIommuParameters { |
| unpin_policy: CoIommuUnpinPolicy::Lru, |
| ..Default::default() |
| } |
| ); |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_policy=foo"); |
| assert!(coiommu_params.is_err()); |
| |
| // unpin_interval |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=42").unwrap(); |
| assert_eq!( |
| coiommu_params, |
| CoIommuParameters { |
| unpin_interval: Duration::from_secs(42), |
| ..Default::default() |
| } |
| ); |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_interval=foo"); |
| assert!(coiommu_params.is_err()); |
| |
| // unpin_limit |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=256").unwrap(); |
| assert_eq!( |
| coiommu_params, |
| CoIommuParameters { |
| unpin_limit: Some(256), |
| ..Default::default() |
| } |
| ); |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=0"); |
| assert!(coiommu_params.is_err()); |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_limit=foo"); |
| assert!(coiommu_params.is_err()); |
| |
| // unpin_gen_threshold |
| let coiommu_params = |
| from_key_values::<CoIommuParameters>("unpin_gen_threshold=32").unwrap(); |
| assert_eq!( |
| coiommu_params, |
| CoIommuParameters { |
| unpin_gen_threshold: 32, |
| ..Default::default() |
| } |
| ); |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_gen_threshold=foo"); |
| assert!(coiommu_params.is_err()); |
| |
| // All together |
| let coiommu_params = from_key_values::<CoIommuParameters>( |
| "unpin_policy=lru,unpin_interval=90,unpin_limit=8,unpin_gen_threshold=64", |
| ) |
| .unwrap(); |
| assert_eq!( |
| coiommu_params, |
| CoIommuParameters { |
| unpin_policy: CoIommuUnpinPolicy::Lru, |
| unpin_interval: Duration::from_secs(90), |
| unpin_limit: Some(8), |
| unpin_gen_threshold: 64, |
| } |
| ); |
| |
| // invalid parameter |
| let coiommu_params = from_key_values::<CoIommuParameters>("unpin_invalid_param=0"); |
| assert!(coiommu_params.is_err()); |
| } |
| |
| #[test] |
| fn parse_plugin_mount_valid() { |
| let opt: BindMount = "/dev/null:/dev/zero:true".parse().unwrap(); |
| |
| assert_eq!(opt.src, PathBuf::from("/dev/null")); |
| assert_eq!(opt.dst, PathBuf::from("/dev/zero")); |
| assert!(opt.writable); |
| } |
| |
| #[test] |
| fn parse_plugin_mount_valid_shorthand() { |
| let opt: BindMount = "/dev/null".parse().unwrap(); |
| assert_eq!(opt.dst, PathBuf::from("/dev/null")); |
| assert!(!opt.writable); |
| |
| let opt: BindMount = "/dev/null:/dev/zero".parse().unwrap(); |
| assert_eq!(opt.dst, PathBuf::from("/dev/zero")); |
| assert!(!opt.writable); |
| |
| let opt: BindMount = "/dev/null::true".parse().unwrap(); |
| assert_eq!(opt.dst, PathBuf::from("/dev/null")); |
| assert!(opt.writable); |
| } |
| |
| #[test] |
| fn single_touch_spec_and_track_pad_spec_default_size() { |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &[ |
| "--single-touch", |
| "/dev/single-touch-test", |
| "--trackpad", |
| "/dev/single-touch-test", |
| "/dev/null", |
| ], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| assert_eq!( |
| config.virtio_single_touch.first().unwrap().get_size(), |
| (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT) |
| ); |
| assert_eq!( |
| config.virtio_trackpad.first().unwrap().get_size(), |
| (DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT) |
| ); |
| } |
| |
| #[cfg(feature = "gpu")] |
| #[test] |
| fn single_touch_spec_default_size_from_gpu() { |
| let width = 12345u32; |
| let height = 54321u32; |
| |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &[ |
| "--single-touch", |
| "/dev/single-touch-test", |
| "--gpu", |
| &format!("width={},height={}", width, height), |
| "/dev/null", |
| ], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| assert_eq!( |
| config.virtio_single_touch.first().unwrap().get_size(), |
| (width, height) |
| ); |
| } |
| |
| #[test] |
| fn single_touch_spec_and_track_pad_spec_with_size() { |
| let width = 12345u32; |
| let height = 54321u32; |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &[ |
| "--single-touch", |
| &format!("/dev/single-touch-test:{}:{}", width, height), |
| "--trackpad", |
| &format!("/dev/single-touch-test:{}:{}", width, height), |
| "/dev/null", |
| ], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| assert_eq!( |
| config.virtio_single_touch.first().unwrap().get_size(), |
| (width, height) |
| ); |
| assert_eq!( |
| config.virtio_trackpad.first().unwrap().get_size(), |
| (width, height) |
| ); |
| } |
| |
| #[cfg(feature = "gpu")] |
| #[test] |
| fn single_touch_spec_with_size_independent_from_gpu() { |
| let touch_width = 12345u32; |
| let touch_height = 54321u32; |
| let display_width = 1234u32; |
| let display_height = 5432u32; |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &[ |
| "--single-touch", |
| &format!("/dev/single-touch-test:{}:{}", touch_width, touch_height), |
| "--gpu", |
| &format!("width={},height={}", display_width, display_height), |
| "/dev/null", |
| ], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| assert_eq!( |
| config.virtio_single_touch.first().unwrap().get_size(), |
| (touch_width, touch_height) |
| ); |
| } |
| |
| #[test] |
| fn virtio_switches() { |
| let mut config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &["--switches", "/dev/switches-test", "/dev/null"], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| assert_eq!( |
| config.virtio_switches.pop().unwrap(), |
| PathBuf::from("/dev/switches-test") |
| ); |
| } |
| |
| #[test] |
| fn vfio_pci_path() { |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &["--vfio", "/path/to/dev", "/dev/null"], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| let vfio = config.vfio.first().unwrap(); |
| |
| assert_eq!(vfio.path, PathBuf::from("/path/to/dev")); |
| assert_eq!(vfio.iommu, IommuDevType::NoIommu); |
| assert_eq!(vfio.guest_address, None); |
| } |
| |
| #[test] |
| fn vfio_pci_path_coiommu() { |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &["--vfio", "/path/to/dev,iommu=coiommu", "/dev/null"], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| let vfio = config.vfio.first().unwrap(); |
| |
| assert_eq!(vfio.path, PathBuf::from("/path/to/dev")); |
| assert_eq!(vfio.iommu, IommuDevType::CoIommu); |
| assert_eq!(vfio.guest_address, None); |
| } |
| |
| #[test] |
| fn vfio_pci_path_viommu_guest_address() { |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &[ |
| "--vfio", |
| "/path/to/dev,iommu=viommu,guest-address=42:15.4", |
| "/dev/null", |
| ], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| let vfio = config.vfio.first().unwrap(); |
| |
| assert_eq!(vfio.path, PathBuf::from("/path/to/dev")); |
| assert_eq!(vfio.iommu, IommuDevType::VirtioIommu); |
| assert_eq!( |
| vfio.guest_address, |
| Some(PciAddress::new(0, 0x42, 0x15, 4).unwrap()) |
| ); |
| } |
| |
| #[test] |
| #[cfg(feature = "direct")] |
| fn vfio_pci_intel_lpss() { |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &["--vfio", "/path/to/dev,intel-lpss=true", "/dev/null"], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| let vfio = config.vfio.first().unwrap(); |
| |
| assert_eq!(vfio.intel_lpss, true); |
| } |
| |
| #[test] |
| fn vfio_platform() { |
| let config: Config = crate::crosvm::cmdline::RunCommand::from_args( |
| &[], |
| &["--vfio-platform", "/path/to/dev", "/dev/null"], |
| ) |
| .unwrap() |
| .try_into() |
| .unwrap(); |
| |
| let vfio = config.vfio.first().unwrap(); |
| |
| assert_eq!(vfio.path, PathBuf::from("/path/to/dev")); |
| } |
| } |