diff --git a/src/crosvm/cmdline.rs b/src/crosvm/cmdline.rs index fac33608ba..53bdd64220 100644 --- a/src/crosvm/cmdline.rs +++ b/src/crosvm/cmdline.rs @@ -13,6 +13,8 @@ use super::platform::GpuRenderServerParameters; use super::sys::config::parse_coiommu_params; #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))] use super::sys::config::parse_gpu_render_server_options; +#[cfg(feature = "gpu")] +use super::sys::config::{parse_gpu_display_options, parse_gpu_options}; #[cfg(any(feature = "video-decoder", feature = "video-encoder"))] use super::config::parse_video_options; diff --git a/src/crosvm/config.rs b/src/crosvm/config.rs index ad5b8bc296..940caf7705 100644 --- a/src/crosvm/config.rs +++ b/src/crosvm/config.rs @@ -1324,350 +1324,6 @@ pub fn parse_stub_pci_parameters(s: &str) -> Result { Ok(params) } -#[cfg(feature = "gpu")] -#[derive(Default)] -struct GpuDisplayParametersBuilder { - width: Option, - height: Option, - args: Vec, -} - -#[cfg(feature = "gpu")] -impl GpuDisplayParametersBuilder { - fn parse(&mut self, arg: &str) -> argument::Result<()> { - let mut kv = arg.split('='); - let k = kv.next().unwrap_or(""); - let v = kv.next().unwrap_or(""); - match k { - "width" => { - let width = v - .parse::() - .map_err(|_| argument::Error::InvalidValue { - value: v.to_string(), - expected: String::from("gpu parameter 'width' must be a valid integer"), - })?; - self.width = Some(width); - } - "height" => { - let height = v - .parse::() - .map_err(|_| argument::Error::InvalidValue { - value: v.to_string(), - expected: String::from("gpu parameter 'height' must be a valid integer"), - })?; - self.height = Some(height); - } - _ => { - return Err(argument::Error::UnknownArgument(format!( - "gpu-display parameter {}", - k - ))) - } - } - self.args.push(arg.to_string()); - Ok(()) - } - - fn build(&self) -> Result, String> { - match (self.width, self.height) { - (None, None) => Ok(None), - (None, Some(_)) | (Some(_), None) => { - let mut value = self - .args - .clone() - .into_iter() - .fold(String::new(), |args_so_far, arg| args_so_far + &arg + ","); - value.pop(); - return Err(invalid_value_err( - value, - "gpu must include both 'width' and 'height' if either is supplied", - )); - } - (Some(width), Some(height)) => Ok(Some(GpuDisplayParameters { width, height })), - } - } -} - -#[cfg(feature = "gpu")] -pub fn parse_gpu_options(s: &str) -> Result { - use devices::virtio::GpuMode; - use rutabaga_gfx::RutabagaWsi; - - use crate::crosvm::sys::config::is_gpu_backend_deprecated; - - #[cfg(feature = "gfxstream")] - let mut vulkan_specified = false; - #[cfg(feature = "gfxstream")] - let mut syncfd_specified = false; - #[cfg(feature = "gfxstream")] - let mut angle_specified = false; - - let mut display_param_builder: GpuDisplayParametersBuilder = Default::default(); - let mut gpu_params = GpuParameters::default(); - - for frag in s.split(',') { - let mut rest: Option<&str> = None; - let mut kv = frag.split('='); - let k = kv.next().unwrap_or(""); - let v = kv.next().unwrap_or(""); - match k { - // Deprecated: Specifying --gpu= Not great as the mode can be set multiple - // times if the user specifies several modes (--gpu=2d,virglrenderer,gfxstream) - "2d" | "2D" => { - gpu_params.mode = GpuMode::Mode2D; - } - "3d" | "3D" | "virglrenderer" => { - gpu_params.mode = GpuMode::ModeVirglRenderer; - } - #[cfg(feature = "gfxstream")] - "gfxstream" => { - gpu_params.mode = GpuMode::ModeGfxstream; - } - // Preferred: Specifying --gpu,backend= - "backend" => match v { - "2d" | "2D" => { - if is_gpu_backend_deprecated(v) { - return Err(invalid_value_err( - v, - "this backend type is deprecated, please use gfxstream.", - )); - } else { - gpu_params.mode = GpuMode::Mode2D; - } - } - "3d" | "3D" | "virglrenderer" => { - if is_gpu_backend_deprecated(v) { - return Err(invalid_value_err( - v, - "this backend type is deprecated, please use gfxstream.", - )); - } else { - gpu_params.mode = GpuMode::ModeVirglRenderer; - } - } - #[cfg(feature = "gfxstream")] - "gfxstream" => { - gpu_params.mode = GpuMode::ModeGfxstream; - } - _ => { - return Err(invalid_value_err( - v, - #[cfg(feature = "gfxstream")] - "gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)", - #[cfg(not(feature = "gfxstream"))] - "gpu parameter 'backend' should be one of (2d|3d)", - )); - } - }, - "egl" => match v { - "true" | "" => { - gpu_params.renderer_use_egl = true; - } - "false" => { - gpu_params.renderer_use_egl = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'egl' should be a boolean", - )); - } - }, - "gles" => match v { - "true" | "" => { - gpu_params.renderer_use_gles = true; - } - "false" => { - gpu_params.renderer_use_gles = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'gles' should be a boolean", - )); - } - }, - "glx" => match v { - "true" | "" => { - gpu_params.renderer_use_glx = true; - } - "false" => { - gpu_params.renderer_use_glx = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'glx' should be a boolean", - )); - } - }, - "surfaceless" => match v { - "true" | "" => { - gpu_params.renderer_use_surfaceless = true; - } - "false" => { - gpu_params.renderer_use_surfaceless = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'surfaceless' should be a boolean", - )); - } - }, - #[cfg(feature = "gfxstream")] - "syncfd" => { - syncfd_specified = true; - match v { - "true" | "" => { - gpu_params.gfxstream_use_syncfd = true; - } - "false" => { - gpu_params.gfxstream_use_syncfd = false; - } - _ => { - return Err(argument::Error::InvalidValue { - value: v.to_string(), - expected: String::from("gpu parameter 'syncfd' should be a boolean"), - }); - } - } - } - #[cfg(feature = "gfxstream")] - "angle" => { - angle_specified = true; - match v { - "true" | "" => { - gpu_params.gfxstream_use_guest_angle = true; - } - "false" => { - gpu_params.gfxstream_use_guest_angle = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'angle' should be a boolean", - )); - } - } - } - "vulkan" => { - #[cfg(feature = "gfxstream")] - { - vulkan_specified = true; - } - match v { - "true" | "" => { - gpu_params.use_vulkan = true; - } - "false" => { - gpu_params.use_vulkan = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'vulkan' should be a boolean", - )); - } - } - } - "wsi" => match v { - "vk" => { - gpu_params.wsi = Some(RutabagaWsi::Vulkan); - } - _ => { - return Err(invalid_value_err(v, "gpu parameter 'wsi' should be vk")); - } - }, - "cache-path" => gpu_params.cache_path = Some(v.to_string()), - "cache-size" => gpu_params.cache_size = Some(v.to_string()), - "pci-bar-size" => { - let size = parse_hex_or_decimal(v).map_err(|_| { - "gpu parameter `pci-bar-size` must be a valid hex or decimal value" - })?; - gpu_params.pci_bar_size = size; - } - "udmabuf" => match v { - "true" | "" => { - gpu_params.udmabuf = true; - } - "false" => { - gpu_params.udmabuf = false; - } - _ => { - return Err(invalid_value_err( - v, - "gpu parameter 'udmabuf' should be a boolean", - )); - } - }, - "context-types" => { - let context_types: Vec = v.split(':').map(|s| s.to_string()).collect(); - gpu_params.context_mask = rutabaga_gfx::calculate_context_mask(context_types); - } - "" => {} - _ => { - rest = Some(frag); - } - } - if let Some(arg) = rest.take() { - match display_param_builder.parse(arg) { - Ok(()) => {} - Err(argument::Error::UnknownArgument(_)) => { - rest = Some(arg); - } - Err(err) => return Err(err.to_string()), - } - } - if let Some(arg) = rest.take() { - return Err(format!("unknown gpu parameter {}", arg)); - } - } - - if let Some(display_param) = display_param_builder.build()?.take() { - gpu_params.displays.push(display_param); - } - - #[cfg(feature = "gfxstream")] - { - if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream { - gpu_params.use_vulkan = sys::use_vulkan(); - } - - if syncfd_specified || angle_specified { - match gpu_params.mode { - GpuMode::ModeGfxstream => {} - _ => { - return Err( - "gpu parameter syncfd and angle are only supported for gfxstream backend" - .to_string(), - ); - } - } - } - } - - Ok(gpu_params) -} - -#[cfg(feature = "gpu")] -pub fn parse_gpu_display_options(s: &str) -> Result { - let mut display_param_builder: GpuDisplayParametersBuilder = Default::default(); - - for arg in s.split(',') { - display_param_builder - .parse(arg) - .map_err(|e| e.to_string())?; - } - - let display_param = display_param_builder.build()?; - let display_param = display_param.ok_or_else(|| { - invalid_value_err(s, "gpu-display must include both 'width' and 'height'") - })?; - - Ok(display_param) -} - /// Aggregate of all configurable options for a running VM. #[remain::sorted] pub struct Config { @@ -2276,20 +1932,6 @@ mod tests { .expect_err("parse should have failed"); } - #[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"); - } - - #[cfg(feature = "audio")] - #[test] - fn parse_ac97_vios_valid() { - parse_ac97_options("backend=vios,server=/path/to/server") - .expect("parse should have succeded"); - } - #[test] fn parse_serial_vaild() { parse_serial_options("type=syslog,num=1,console=true,stdin=true") @@ -2362,30 +2004,6 @@ mod tests { .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 parse_plugin_mount_invalid() { "".parse::().expect_err("parse should fail"); @@ -2404,6 +2022,7 @@ mod tests { .expect_err("parse should fail because flag is not boolean"); } + #[cfg(feature = "plugin")] #[test] fn parse_plugin_gid_map_valid() { let opt: GidMap = "1:2:3".parse().expect("parse should succeed"); @@ -2412,6 +2031,7 @@ mod tests { assert_eq!(opt.count, 3); } + #[cfg(feature = "plugin")] #[test] fn parse_plugin_gid_map_valid_shorthand() { let opt: GidMap = "1".parse().expect("parse should succeed"); @@ -2430,6 +2050,7 @@ mod tests { assert_eq!(opt.count, 3); } + #[cfg(feature = "plugin")] #[test] fn parse_plugin_gid_map_invalid() { "".parse::().expect_err("parse should fail"); @@ -2447,243 +2068,6 @@ mod tests { .expect_err("parse should fail because count is not a number"); } - #[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") - ); - } - - #[cfg(feature = "gpu")] - #[test] - fn parse_gpu_options_default_vulkan_support() { - { - let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer").unwrap(); - assert!(!gpu_params.use_vulkan); - } - - #[cfg(feature = "gfxstream")] - { - let gpu_params: GpuParameters = parse_gpu_options("backend=gfxstream").unwrap(); - assert!(gpu_params.use_vulkan); - } - } - - #[cfg(feature = "gpu")] - #[test] - fn parse_gpu_options_with_vulkan_specified() { - { - let gpu_params: GpuParameters = parse_gpu_options("vulkan=true").unwrap(); - assert!(gpu_params.use_vulkan); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("backend=virglrenderer,vulkan=true").unwrap(); - assert!(gpu_params.use_vulkan); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("vulkan=true,backend=virglrenderer").unwrap(); - assert!(gpu_params.use_vulkan); - } - { - let gpu_params: GpuParameters = parse_gpu_options("vulkan=false").unwrap(); - assert!(!gpu_params.use_vulkan); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("backend=virglrenderer,vulkan=false").unwrap(); - assert!(!gpu_params.use_vulkan); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("vulkan=false,backend=virglrenderer").unwrap(); - assert!(!gpu_params.use_vulkan); - } - { - assert!(parse_gpu_options("backend=virglrenderer,vulkan=invalid_value").is_err()); - } - { - assert!(parse_gpu_options("vulkan=invalid_value,backend=virglrenderer").is_err()); - } - } - - #[cfg(all(feature = "gpu", feature = "gfxstream"))] - #[test] - fn parse_gpu_options_gfxstream_with_syncfd_specified() { - { - let gpu_params: GpuParameters = - parse_gpu_options("backend=gfxstream,syncfd=true").unwrap(); - - assert!(gpu_params.gfxstream_use_syncfd); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("syncfd=true,backend=gfxstream").unwrap(); - assert!(gpu_params.gfxstream_use_syncfd); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("backend=gfxstream,syncfd=false").unwrap(); - - assert!(!gpu_params.gfxstream_use_syncfd); - } - { - let gpu_params: GpuParameters = - parse_gpu_options("syncfd=false,backend=gfxstream").unwrap(); - assert!(!gpu_params.gfxstream_use_syncfd); - } - { - assert!(parse_gpu_options("backend=gfxstream,syncfd=invalid_value").is_err()); - } - { - assert!(parse_gpu_options("syncfd=invalid_value,backend=gfxstream").is_err()); - } - } - - #[cfg(all(feature = "gpu", feature = "gfxstream"))] - #[test] - fn parse_gpu_options_not_gfxstream_with_syncfd_specified() { - { - assert!(parse_gpu_options("backend=virglrenderer,syncfd=true").is_err()); - } - { - assert!(parse_gpu_options("syncfd=true,backend=virglrenderer").is_err()); - } - } - - #[cfg(feature = "gpu")] - #[test] - fn parse_gpu_options_gfxstream_with_wsi_specified() { - use rutabaga_gfx::RutabagaWsi; - - let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer,wsi=vk").unwrap(); - assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan))); - - let gpu_params: GpuParameters = parse_gpu_options("wsi=vk,backend=virglrenderer").unwrap(); - assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan))); - - assert!(parse_gpu_options("backend=virglrenderer,wsi=invalid_value").is_err()); - - assert!(parse_gpu_options("wsi=invalid_value,backend=virglrenderer").is_err()); - } - #[test] fn parse_battery_vaild() { parse_battery_options("type=goldfish").expect("parse should have succeded"); @@ -2846,81 +2230,4 @@ mod tests { assert!(parse_userspace_msr_options("0x10").is_err()); assert!(parse_userspace_msr_options("hoge").is_err()); } - - #[cfg(feature = "gpu")] - #[test] - fn parse_gpu_display_options_valid() { - { - let gpu_params: GpuDisplayParameters = - parse_gpu_display_options("width=500,height=600").unwrap(); - assert_eq!(gpu_params.width, 500); - assert_eq!(gpu_params.height, 600); - } - } - - #[cfg(feature = "gpu")] - #[test] - fn parse_gpu_display_options_invalid() { - { - assert!(parse_gpu_display_options("width=500").is_err()); - } - { - assert!(parse_gpu_display_options("height=500").is_err()); - } - { - assert!(parse_gpu_display_options("width").is_err()); - } - { - assert!(parse_gpu_display_options("blah").is_err()); - } - } - - #[cfg(feature = "gpu")] - #[test] - fn parse_gpu_options_and_gpu_display_options_valid() { - { - let config: Config = crate::crosvm::cmdline::RunCommand::from_args( - &[], - &[ - "--gpu", - "2D,width=500,height=600", - "--gpu-display", - "width=700,height=800", - "/dev/null", - ], - ) - .unwrap() - .try_into() - .unwrap(); - - let gpu_params = config.gpu_parameters.unwrap(); - - assert_eq!(gpu_params.displays.len(), 2); - assert_eq!(gpu_params.displays[0].width, 500); - assert_eq!(gpu_params.displays[0].height, 600); - assert_eq!(gpu_params.displays[1].width, 700); - assert_eq!(gpu_params.displays[1].height, 800); - } - { - let config: Config = crate::crosvm::cmdline::RunCommand::from_args( - &[], - &[ - "--gpu", - "2D", - "--gpu-display", - "width=700,height=800", - "/dev/null", - ], - ) - .unwrap() - .try_into() - .unwrap(); - - let gpu_params = config.gpu_parameters.unwrap(); - - assert_eq!(gpu_params.displays.len(), 1); - assert_eq!(gpu_params.displays[0].width, 700); - assert_eq!(gpu_params.displays[0].height, 800); - } - } } diff --git a/src/crosvm/sys/unix/config.rs b/src/crosvm/sys/unix/config.rs index c9730380db..cece270944 100644 --- a/src/crosvm/sys/unix/config.rs +++ b/src/crosvm/sys/unix/config.rs @@ -6,7 +6,12 @@ use std::time::Duration; use devices::SerialParameters; +#[cfg(feature = "gpu")] +use devices::virtio::{GpuDisplayParameters, GpuParameters}; + use crate::crosvm::config::Config; +#[cfg(feature = "gpu")] +use crate::crosvm::{argument, argument::parse_hex_or_decimal, config::invalid_value_err}; #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))] pub fn parse_gpu_render_server_options( @@ -182,3 +187,712 @@ pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> { Ok(()) } + +#[cfg(feature = "gpu")] +#[derive(Default)] +struct GpuDisplayParametersBuilder { + width: Option, + height: Option, + args: Vec, +} + +#[cfg(feature = "gpu")] +impl GpuDisplayParametersBuilder { + fn parse(&mut self, arg: &str) -> argument::Result<()> { + let mut kv = arg.split('='); + let k = kv.next().unwrap_or(""); + let v = kv.next().unwrap_or(""); + match k { + "width" => { + let width = v + .parse::() + .map_err(|_| argument::Error::InvalidValue { + value: v.to_string(), + expected: String::from("gpu parameter 'width' must be a valid integer"), + })?; + self.width = Some(width); + } + "height" => { + let height = v + .parse::() + .map_err(|_| argument::Error::InvalidValue { + value: v.to_string(), + expected: String::from("gpu parameter 'height' must be a valid integer"), + })?; + self.height = Some(height); + } + _ => { + return Err(argument::Error::UnknownArgument(format!( + "gpu-display parameter {}", + k + ))) + } + } + self.args.push(arg.to_string()); + Ok(()) + } + + fn build(&self) -> Result, String> { + match (self.width, self.height) { + (None, None) => Ok(None), + (None, Some(_)) | (Some(_), None) => { + let mut value = self + .args + .clone() + .into_iter() + .fold(String::new(), |args_so_far, arg| args_so_far + &arg + ","); + value.pop(); + return Err(invalid_value_err( + value, + "gpu must include both 'width' and 'height' if either is supplied", + )); + } + (Some(width), Some(height)) => Ok(Some(GpuDisplayParameters { width, height })), + } + } +} + +#[cfg(feature = "gpu")] +pub fn parse_gpu_options(s: &str) -> Result { + use devices::virtio::GpuMode; + use rutabaga_gfx::RutabagaWsi; + + use crate::crosvm::sys::config::is_gpu_backend_deprecated; + + #[cfg(feature = "gfxstream")] + let mut vulkan_specified = false; + #[cfg(feature = "gfxstream")] + let mut syncfd_specified = false; + #[cfg(feature = "gfxstream")] + let mut angle_specified = false; + + let mut display_param_builder: GpuDisplayParametersBuilder = Default::default(); + let mut gpu_params = GpuParameters::default(); + + for frag in s.split(',') { + let mut rest: Option<&str> = None; + let mut kv = frag.split('='); + let k = kv.next().unwrap_or(""); + let v = kv.next().unwrap_or(""); + match k { + // Deprecated: Specifying --gpu= Not great as the mode can be set multiple + // times if the user specifies several modes (--gpu=2d,virglrenderer,gfxstream) + "2d" | "2D" => { + gpu_params.mode = GpuMode::Mode2D; + } + "3d" | "3D" | "virglrenderer" => { + gpu_params.mode = GpuMode::ModeVirglRenderer; + } + #[cfg(feature = "gfxstream")] + "gfxstream" => { + gpu_params.mode = GpuMode::ModeGfxstream; + } + // Preferred: Specifying --gpu,backend= + "backend" => match v { + "2d" | "2D" => { + if is_gpu_backend_deprecated(v) { + return Err(invalid_value_err( + v, + "this backend type is deprecated, please use gfxstream.", + )); + } else { + gpu_params.mode = GpuMode::Mode2D; + } + } + "3d" | "3D" | "virglrenderer" => { + if is_gpu_backend_deprecated(v) { + return Err(invalid_value_err( + v, + "this backend type is deprecated, please use gfxstream.", + )); + } else { + gpu_params.mode = GpuMode::ModeVirglRenderer; + } + } + #[cfg(feature = "gfxstream")] + "gfxstream" => { + gpu_params.mode = GpuMode::ModeGfxstream; + } + _ => { + return Err(invalid_value_err( + v, + #[cfg(feature = "gfxstream")] + "gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)", + #[cfg(not(feature = "gfxstream"))] + "gpu parameter 'backend' should be one of (2d|3d)", + )); + } + }, + "egl" => match v { + "true" | "" => { + gpu_params.renderer_use_egl = true; + } + "false" => { + gpu_params.renderer_use_egl = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'egl' should be a boolean", + )); + } + }, + "gles" => match v { + "true" | "" => { + gpu_params.renderer_use_gles = true; + } + "false" => { + gpu_params.renderer_use_gles = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'gles' should be a boolean", + )); + } + }, + "glx" => match v { + "true" | "" => { + gpu_params.renderer_use_glx = true; + } + "false" => { + gpu_params.renderer_use_glx = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'glx' should be a boolean", + )); + } + }, + "surfaceless" => match v { + "true" | "" => { + gpu_params.renderer_use_surfaceless = true; + } + "false" => { + gpu_params.renderer_use_surfaceless = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'surfaceless' should be a boolean", + )); + } + }, + #[cfg(feature = "gfxstream")] + "syncfd" => { + syncfd_specified = true; + match v { + "true" | "" => { + gpu_params.gfxstream_use_syncfd = true; + } + "false" => { + gpu_params.gfxstream_use_syncfd = false; + } + _ => { + return Err(argument::Error::InvalidValue { + value: v.to_string(), + expected: String::from("gpu parameter 'syncfd' should be a boolean"), + }); + } + } + } + #[cfg(feature = "gfxstream")] + "angle" => { + angle_specified = true; + match v { + "true" | "" => { + gpu_params.gfxstream_use_guest_angle = true; + } + "false" => { + gpu_params.gfxstream_use_guest_angle = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'angle' should be a boolean", + )); + } + } + } + "vulkan" => { + #[cfg(feature = "gfxstream")] + { + vulkan_specified = true; + } + match v { + "true" | "" => { + gpu_params.use_vulkan = true; + } + "false" => { + gpu_params.use_vulkan = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'vulkan' should be a boolean", + )); + } + } + } + "wsi" => match v { + "vk" => { + gpu_params.wsi = Some(RutabagaWsi::Vulkan); + } + _ => { + return Err(invalid_value_err(v, "gpu parameter 'wsi' should be vk")); + } + }, + "cache-path" => gpu_params.cache_path = Some(v.to_string()), + "cache-size" => gpu_params.cache_size = Some(v.to_string()), + "pci-bar-size" => { + let size = parse_hex_or_decimal(v).map_err(|_| { + "gpu parameter `pci-bar-size` must be a valid hex or decimal value" + })?; + gpu_params.pci_bar_size = size; + } + "udmabuf" => match v { + "true" | "" => { + gpu_params.udmabuf = true; + } + "false" => { + gpu_params.udmabuf = false; + } + _ => { + return Err(invalid_value_err( + v, + "gpu parameter 'udmabuf' should be a boolean", + )); + } + }, + "context-types" => { + let context_types: Vec = v.split(':').map(|s| s.to_string()).collect(); + gpu_params.context_mask = rutabaga_gfx::calculate_context_mask(context_types); + } + "" => {} + _ => { + rest = Some(frag); + } + } + if let Some(arg) = rest.take() { + match display_param_builder.parse(arg) { + Ok(()) => {} + Err(argument::Error::UnknownArgument(_)) => { + rest = Some(arg); + } + Err(err) => return Err(err.to_string()), + } + } + if let Some(arg) = rest.take() { + return Err(format!("unknown gpu parameter {}", arg)); + } + } + + if let Some(display_param) = display_param_builder.build()?.take() { + gpu_params.displays.push(display_param); + } + + #[cfg(feature = "gfxstream")] + { + if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream { + gpu_params.use_vulkan = sys::use_vulkan(); + } + + if syncfd_specified || angle_specified { + match gpu_params.mode { + GpuMode::ModeGfxstream => {} + _ => { + return Err( + "gpu parameter syncfd and angle are only supported for gfxstream backend" + .to_string(), + ); + } + } + } + } + + Ok(gpu_params) +} + +#[cfg(feature = "gpu")] +pub fn parse_gpu_display_options(s: &str) -> Result { + let mut display_param_builder: GpuDisplayParametersBuilder = Default::default(); + + for arg in s.split(',') { + display_param_builder + .parse(arg) + .map_err(|e| e.to_string())?; + } + + let display_param = display_param_builder.build()?; + let display_param = display_param.ok_or_else(|| { + invalid_value_err(s, "gpu-display must include both 'width' and 'height'") + })?; + + Ok(display_param) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crosvm::config::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH}; + + use argh::FromArgs; + use std::path::PathBuf; + + #[cfg(feature = "audio")] + use crate::crosvm::config::parse_ac97_options; + use crate::crosvm::config::BindMount; + + #[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"); + } + + #[cfg(feature = "gpu")] + #[test] + fn parse_gpu_options_default_vulkan_support() { + { + let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer").unwrap(); + assert!(!gpu_params.use_vulkan); + } + + #[cfg(feature = "gfxstream")] + { + let gpu_params: GpuParameters = parse_gpu_options("backend=gfxstream").unwrap(); + assert!(gpu_params.use_vulkan); + } + } + + #[cfg(feature = "gpu")] + #[test] + fn parse_gpu_options_with_vulkan_specified() { + { + let gpu_params: GpuParameters = parse_gpu_options("vulkan=true").unwrap(); + assert!(gpu_params.use_vulkan); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("backend=virglrenderer,vulkan=true").unwrap(); + assert!(gpu_params.use_vulkan); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("vulkan=true,backend=virglrenderer").unwrap(); + assert!(gpu_params.use_vulkan); + } + { + let gpu_params: GpuParameters = parse_gpu_options("vulkan=false").unwrap(); + assert!(!gpu_params.use_vulkan); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("backend=virglrenderer,vulkan=false").unwrap(); + assert!(!gpu_params.use_vulkan); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("vulkan=false,backend=virglrenderer").unwrap(); + assert!(!gpu_params.use_vulkan); + } + { + assert!(parse_gpu_options("backend=virglrenderer,vulkan=invalid_value").is_err()); + } + { + assert!(parse_gpu_options("vulkan=invalid_value,backend=virglrenderer").is_err()); + } + } + + #[cfg(all(feature = "gpu", feature = "gfxstream"))] + #[test] + fn parse_gpu_options_gfxstream_with_syncfd_specified() { + { + let gpu_params: GpuParameters = + parse_gpu_options("backend=gfxstream,syncfd=true").unwrap(); + + assert!(gpu_params.gfxstream_use_syncfd); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("syncfd=true,backend=gfxstream").unwrap(); + assert!(gpu_params.gfxstream_use_syncfd); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("backend=gfxstream,syncfd=false").unwrap(); + + assert!(!gpu_params.gfxstream_use_syncfd); + } + { + let gpu_params: GpuParameters = + parse_gpu_options("syncfd=false,backend=gfxstream").unwrap(); + assert!(!gpu_params.gfxstream_use_syncfd); + } + { + assert!(parse_gpu_options("backend=gfxstream,syncfd=invalid_value").is_err()); + } + { + assert!(parse_gpu_options("syncfd=invalid_value,backend=gfxstream").is_err()); + } + } + + #[cfg(all(feature = "gpu", feature = "gfxstream"))] + #[test] + fn parse_gpu_options_not_gfxstream_with_syncfd_specified() { + { + assert!(parse_gpu_options("backend=virglrenderer,syncfd=true").is_err()); + } + { + assert!(parse_gpu_options("syncfd=true,backend=virglrenderer").is_err()); + } + } + + #[cfg(feature = "gpu")] + #[test] + fn parse_gpu_options_gfxstream_with_wsi_specified() { + use rutabaga_gfx::RutabagaWsi; + + let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer,wsi=vk").unwrap(); + assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan))); + + let gpu_params: GpuParameters = parse_gpu_options("wsi=vk,backend=virglrenderer").unwrap(); + assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan))); + + assert!(parse_gpu_options("backend=virglrenderer,wsi=invalid_value").is_err()); + + assert!(parse_gpu_options("wsi=invalid_value,backend=virglrenderer").is_err()); + } + + #[cfg(feature = "gpu")] + #[test] + fn parse_gpu_display_options_valid() { + { + let gpu_params: GpuDisplayParameters = + parse_gpu_display_options("width=500,height=600").unwrap(); + assert_eq!(gpu_params.width, 500); + assert_eq!(gpu_params.height, 600); + } + } + + #[cfg(feature = "gpu")] + #[test] + fn parse_gpu_display_options_invalid() { + { + assert!(parse_gpu_display_options("width=500").is_err()); + } + { + assert!(parse_gpu_display_options("height=500").is_err()); + } + { + assert!(parse_gpu_display_options("width").is_err()); + } + { + assert!(parse_gpu_display_options("blah").is_err()); + } + } + + #[cfg(feature = "gpu")] + #[test] + fn parse_gpu_options_and_gpu_display_options_valid() { + { + let config: Config = crate::crosvm::cmdline::RunCommand::from_args( + &[], + &[ + "--gpu", + "2D,width=500,height=600", + "--gpu-display", + "width=700,height=800", + "/dev/null", + ], + ) + .unwrap() + .try_into() + .unwrap(); + + let gpu_params = config.gpu_parameters.unwrap(); + + assert_eq!(gpu_params.displays.len(), 2); + assert_eq!(gpu_params.displays[0].width, 500); + assert_eq!(gpu_params.displays[0].height, 600); + assert_eq!(gpu_params.displays[1].width, 700); + assert_eq!(gpu_params.displays[1].height, 800); + } + { + let config: Config = crate::crosvm::cmdline::RunCommand::from_args( + &[], + &[ + "--gpu", + "2D", + "--gpu-display", + "width=700,height=800", + "/dev/null", + ], + ) + .unwrap() + .try_into() + .unwrap(); + + let gpu_params = config.gpu_parameters.unwrap(); + + assert_eq!(gpu_params.displays.len(), 1); + assert_eq!(gpu_params.displays[0].width, 700); + assert_eq!(gpu_params.displays[0].height, 800); + } + } + + #[cfg(feature = "audio")] + #[test] + fn parse_ac97_vios_valid() { + parse_ac97_options("backend=vios,server=/path/to/server") + .expect("parse should have succeded"); + } + + #[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") + ); + } +}