diff --git a/src/main.rs b/src/main.rs index ba4999777..e3d09054d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -301,6 +301,14 @@ fn create_app<'a>( .min_values(1) .group("vm-config"), ) + .arg( + Arg::new("vdpa") + .long("vdpa") + .help(config::VdpaConfig::SYNTAX) + .takes_value(true) + .min_values(1) + .group("vm-config"), + ) .arg( Arg::new("vsock") .long("vsock") @@ -712,6 +720,7 @@ mod unit_tests { }, devices: None, user_devices: None, + vdpa: None, vsock: None, iommu: false, #[cfg(target_arch = "x86_64")] @@ -1669,6 +1678,51 @@ mod unit_tests { }); } + #[test] + fn test_valid_vm_config_vdpa() { + vec![ + ( + vec![ + "cloud-hypervisor", + "--kernel", + "/path/to/kernel", + "--vdpa", + "path=/path/to/device/1", + "path=/path/to/device/2,num_queues=2", + ], + r#"{ + "kernel": {"path": "/path/to/kernel"}, + "vdpa": [ + {"path": "/path/to/device/1", "num_queues": 1}, + {"path": "/path/to/device/2", "num_queues": 2} + ] + }"#, + true, + ), + ( + vec![ + "cloud-hypervisor", + "--kernel", + "/path/to/kernel", + "--vdpa", + "path=/path/to/device/1", + "path=/path/to/device/2", + ], + r#"{ + "kernel": {"path": "/path/to/kernel"}, + "vdpa": [ + {"path": "/path/to/device/1"} + ] + }"#, + false, + ), + ] + .iter() + .for_each(|(cli, openapi, equal)| { + compare_vm_config_cli_vs_json(cli, openapi, *equal); + }); + } + #[test] fn test_valid_vm_config_vsock() { vec![ diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index c9ecd399e..8531bdcf3 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -505,6 +505,10 @@ components: type: array items: $ref: '#/components/schemas/DeviceConfig' + vdpa: + type: array + items: + $ref: '#/components/schemas/VdpaConfig' vsock: $ref: '#/components/schemas/VsockConfig' sgx_epc: @@ -921,6 +925,23 @@ components: id: type: string + VdpaConfig: + required: + - path + - num_queues + type: object + properties: + path: + type: string + num_queues: + type: integer + default: 1 + pci_segment: + type: integer + format: int16 + id: + type: string + VsockConfig: required: - cid diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 59c553d01..8d27410d2 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -106,6 +106,10 @@ pub enum Error { ParseUserDeviceSocketMissing, /// Failed parsing platform parameters ParsePlatform(OptionParserError), + /// Failed parsing vDPA device + ParseVdpa(OptionParserError), + /// Missing path for vDPA device + ParseVdpaPathMissing, } #[derive(Debug)] @@ -285,6 +289,8 @@ impl fmt::Display for Error { #[cfg(feature = "tdx")] FirmwarePathMissing => write!(f, "TDX firmware missing"), ParsePlatform(o) => write!(f, "Error parsing --platform: {}", o), + ParseVdpa(o) => write!(f, "Error parsing --vdpa: {}", o), + ParseVdpaPathMissing => write!(f, "Error parsing --vdpa: path missing"), } } } @@ -316,6 +322,7 @@ pub struct VmParams<'a> { pub console: &'a str, pub devices: Option>, pub user_devices: Option>, + pub vdpa: Option>, pub vsock: Option<&'a str>, #[cfg(target_arch = "x86_64")] pub sgx_epc: Option>, @@ -349,6 +356,7 @@ impl<'a> VmParams<'a> { let pmem: Option> = args.values_of("pmem").map(|x| x.collect()); let devices: Option> = args.values_of("device").map(|x| x.collect()); let user_devices: Option> = args.values_of("user-device").map(|x| x.collect()); + let vdpa: Option> = args.values_of("vdpa").map(|x| x.collect()); let vsock: Option<&str> = args.value_of("vsock"); #[cfg(target_arch = "x86_64")] let sgx_epc: Option> = args.values_of("sgx-epc").map(|x| x.collect()); @@ -376,6 +384,7 @@ impl<'a> VmParams<'a> { console, devices, user_devices, + vdpa, vsock, #[cfg(target_arch = "x86_64")] sgx_epc, @@ -1851,6 +1860,68 @@ impl UserDeviceConfig { Ok(()) } } + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] +pub struct VdpaConfig { + pub path: PathBuf, + #[serde(default = "default_vdpaconfig_num_queues")] + pub num_queues: usize, + #[serde(default)] + pub id: Option, + #[serde(default)] + pub pci_segment: u16, +} + +fn default_vdpaconfig_num_queues() -> usize { + 1 +} + +impl VdpaConfig { + pub const SYNTAX: &'static str = "vDPA device \ + \"path=,num_queues=,iommu=on|off,\ + id=,pci_segment=\""; + pub fn parse(vdpa: &str) -> Result { + let mut parser = OptionParser::new(); + parser + .add("path") + .add("num_queues") + .add("id") + .add("pci_segment"); + parser.parse(vdpa).map_err(Error::ParseVdpa)?; + + let path = parser + .get("path") + .map(PathBuf::from) + .ok_or(Error::ParseVdpaPathMissing)?; + let num_queues = parser + .convert("num_queues") + .map_err(Error::ParseVdpa)? + .unwrap_or_else(default_vdpaconfig_num_queues); + let id = parser.get("id"); + let pci_segment = parser + .convert("pci_segment") + .map_err(Error::ParseVdpa)? + .unwrap_or_default(); + + Ok(VdpaConfig { + path, + num_queues, + id, + pci_segment, + }) + } + + 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)); + } + } + + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Default)] pub struct VsockConfig { pub cid: u64, @@ -2105,6 +2176,7 @@ pub struct VmConfig { pub console: ConsoleConfig, pub devices: Option>, pub user_devices: Option>, + pub vdpa: Option>, pub vsock: Option, #[serde(default)] pub iommu: bool, @@ -2374,6 +2446,16 @@ impl VmConfig { user_devices = Some(user_device_config_list); } + let mut vdpa: Option> = None; + if let Some(vdpa_list) = &vm_params.vdpa { + let mut vdpa_config_list = Vec::new(); + for item in vdpa_list.iter() { + let vdpa_config = VdpaConfig::parse(item)?; + vdpa_config_list.push(vdpa_config); + } + vdpa = Some(vdpa_config_list); + } + let mut vsock: Option = None; if let Some(vs) = &vm_params.vsock { let vsock_config = VsockConfig::parse(vs)?; @@ -2450,6 +2532,7 @@ impl VmConfig { console, devices, user_devices, + vdpa, vsock, iommu, #[cfg(target_arch = "x86_64")] @@ -2993,6 +3076,31 @@ mod tests { Ok(()) } + #[test] + fn test_vdpa_parsing() -> Result<()> { + // path is required + assert!(VdpaConfig::parse("").is_err()); + assert_eq!( + VdpaConfig::parse("path=/dev/vhost-vdpa")?, + VdpaConfig { + path: PathBuf::from("/dev/vhost-vdpa"), + num_queues: 1, + id: None, + ..Default::default() + } + ); + assert_eq!( + VdpaConfig::parse("path=/dev/vhost-vdpa,num_queues=2,id=my_vdpa")?, + VdpaConfig { + path: PathBuf::from("/dev/vhost-vdpa"), + num_queues: 2, + id: Some("my_vdpa".to_owned()), + ..Default::default() + } + ); + Ok(()) + } + #[test] fn test_vsock_parsing() -> Result<()> { // socket and cid is required @@ -3068,6 +3176,7 @@ mod tests { }, devices: None, user_devices: None, + vdpa: None, vsock: None, iommu: false, #[cfg(target_arch = "x86_64")] diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index ad438bb08..a783c069c 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1887,6 +1887,7 @@ mod unit_tests { }, devices: None, user_devices: None, + vdpa: None, vsock: None, iommu: false, #[cfg(target_arch = "x86_64")]