crosvm: move gpu arg parsing related code into sys/unix

windows parses gpu code quite differently. The cl also move some unix
only(because they expect the file /dev/kvm to be around) tests.

BUG=b:213146388
TEST=presubmit

Change-Id: I1f9b11485e09808f4748c8b49d0bc8b29e8ca525
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3732374
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Commit-Queue: Vikram Auradkar <auradkar@google.com>
This commit is contained in:
Vikram Auradkar 2022-06-28 19:55:13 +00:00 committed by Chromeos LUCI
parent ed071b6edd
commit 88b3f563f8
3 changed files with 719 additions and 696 deletions

View file

@ -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;

View file

@ -1324,350 +1324,6 @@ pub fn parse_stub_pci_parameters(s: &str) -> Result<StubPciParameters, String> {
Ok(params)
}
#[cfg(feature = "gpu")]
#[derive(Default)]
struct GpuDisplayParametersBuilder {
width: Option<u32>,
height: Option<u32>,
args: Vec<String>,
}
#[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::<u32>()
.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::<u32>()
.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<Option<GpuDisplayParameters>, 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<GpuParameters, String> {
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=<mode> 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=<mode>
"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<String> = 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<GpuDisplayParameters, String> {
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::<BindMount>().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::<GidMap>().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);
}
}
}

View file

@ -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<u32>,
height: Option<u32>,
args: Vec<String>,
}
#[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::<u32>()
.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::<u32>()
.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<Option<GpuDisplayParameters>, 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<GpuParameters, String> {
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=<mode> 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=<mode>
"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<String> = 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<GpuDisplayParameters, String> {
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")
);
}
}