blob: c8c4e6a1de59f3f33df72031cb9944058069b467 [file] [log] [blame]
// 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::collections::BTreeMap;
use std::path::PathBuf;
use std::str::FromStr;
#[cfg(feature = "gpu")]
use devices::virtio::GpuDisplayMode;
#[cfg(feature = "gpu")]
use devices::virtio::GpuDisplayParameters;
#[cfg(feature = "gfxstream")]
use devices::virtio::GpuMode;
#[cfg(feature = "gpu")]
use devices::virtio::GpuParameters;
use devices::IommuDevType;
use devices::PciAddress;
use devices::SerialParameters;
use serde::Deserialize;
use serde::Serialize;
#[cfg(feature = "gpu")]
use crate::crosvm::cmdline::FixedGpuParameters;
use crate::crosvm::config::invalid_value_err;
use crate::crosvm::config::Config;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum HypervisorKind {
Kvm,
}
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),
_ => 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)),
}
}
#[cfg(feature = "gfxstream")]
pub fn use_vulkan() -> bool {
true
}
// 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(())
}
#[cfg(feature = "gpu")]
pub fn fixup_gpu_options(mut gpu_params: GpuParameters) -> Result<FixedGpuParameters, String> {
if let (Some(width), Some(height)) = (
gpu_params.__width_compat.take(),
gpu_params.__height_compat.take(),
) {
let display_param =
GpuDisplayParameters::default_with_mode(GpuDisplayMode::Windowed(width, height));
gpu_params.display_params.push(display_param);
}
#[cfg(feature = "gfxstream")]
{
if gpu_params.use_vulkan.is_none() && gpu_params.mode == GpuMode::ModeGfxstream {
gpu_params.use_vulkan = Some(use_vulkan());
}
if gpu_params.gfxstream_use_guest_angle.is_some() {
match gpu_params.mode {
GpuMode::ModeGfxstream => {}
_ => {
return Err(
"gpu parameter angle is only supported for gfxstream backend".to_string(),
);
}
}
}
}
Ok(FixedGpuParameters(gpu_params))
}
#[cfg(feature = "gpu")]
pub(crate) fn validate_gpu_config(cfg: &mut Config) -> Result<(), String> {
if let Some(gpu_parameters) = cfg.gpu_parameters.as_mut() {
if !gpu_parameters.pci_bar_size.is_power_of_two() {
return Err(format!(
"gpu parameter `pci-bar-size` must be a power of two but is {}",
gpu_parameters.pci_bar_size
));
}
if gpu_parameters.display_params.is_empty() {
gpu_parameters.display_params.push(Default::default());
}
let (width, height) = gpu_parameters.display_params[0].get_virtual_display_size();
if let Some(virtio_multi_touch) = cfg.virtio_multi_touch.first_mut() {
virtio_multi_touch.set_default_size(width, height);
}
if let Some(virtio_single_touch) = cfg.virtio_single_touch.first_mut() {
virtio_single_touch.set_default_size(width, height);
}
}
Ok(())
}
/// Vfio device type, recognized based on command line option.
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub enum VfioType {
Pci,
Platform,
}
impl FromStr for VfioType {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use VfioType::*;
match s {
"vfio" => Ok(Pci),
"vfio-platform" => Ok(Platform),
_ => Err("invalid vfio device type, must be 'vfio|vfio-platform'"),
}
}
}
#[derive(Serialize, Deserialize)]
/// VFIO device structure for creating a new instance based on command line options.
pub struct VfioCommand {
pub vfio_path: PathBuf,
pub dev_type: VfioType,
pub params: BTreeMap<String, String>,
}
pub fn parse_vfio(s: &str) -> Result<VfioCommand, String> {
VfioCommand::new(VfioType::Pci, s)
}
pub fn parse_vfio_platform(s: &str) -> Result<VfioCommand, String> {
VfioCommand::new(VfioType::Platform, s)
}
impl VfioCommand {
pub fn new(dev_type: VfioType, path: &str) -> Result<VfioCommand, String> {
let mut param = path.split(',');
let vfio_path = PathBuf::from(
param
.next()
.ok_or_else(|| invalid_value_err(path, "missing vfio path"))?,
);
if !vfio_path.exists() {
return Err(invalid_value_err(path, "the vfio path does not exist"));
}
if !vfio_path.is_dir() {
return Err(invalid_value_err(path, "the vfio path should be directory"));
}
let mut params = BTreeMap::new();
for p in param {
let mut kv = p.splitn(2, '=');
if let (Some(kind), Some(value)) = (kv.next(), kv.next()) {
Self::validate_params(kind, value)?;
params.insert(kind.to_owned(), value.to_owned());
};
}
Ok(VfioCommand {
vfio_path,
params,
dev_type,
})
}
fn validate_params(kind: &str, value: &str) -> Result<(), String> {
match kind {
"guest-address" => {
if value.eq_ignore_ascii_case("auto") || PciAddress::from_str(value).is_ok() {
Ok(())
} else {
Err(invalid_value_err(
format!("{}={}", kind, value),
"option must be `guest-address=auto|<BUS:DEVICE.FUNCTION>`",
))
}
}
"iommu" => {
if IommuDevType::from_str(value).is_ok() {
Ok(())
} else {
Err(invalid_value_err(
format!("{}={}", kind, value),
"option must be `iommu=viommu|coiommu|off`",
))
}
}
#[cfg(feature = "direct")]
"intel-lpss" => {
if value.parse::<bool>().is_ok() {
Ok(())
} else {
Err(invalid_value_err(
format!("{}={}", kind, value),
"option must be `intel-lpss=true|false`",
))
}
}
_ => Err(invalid_value_err(
format!("{}={}", kind, value),
"option must be `guest-address=<val>` and/or `iommu=<val>`",
)),
}
}
pub fn get_type(&self) -> VfioType {
self.dev_type
}
pub fn guest_address(&self) -> Option<PciAddress> {
self.params
.get("guest-address")
.and_then(|addr| PciAddress::from_str(addr).ok())
}
pub fn iommu_dev_type(&self) -> IommuDevType {
if let Some(iommu) = self.params.get("iommu") {
if let Ok(v) = IommuDevType::from_str(iommu) {
return v;
}
}
IommuDevType::NoIommu
}
#[cfg(feature = "direct")]
pub fn is_intel_lpss(&self) -> bool {
if let Some(lpss) = self.params.get("intel-lpss") {
return lpss.parse::<bool>().unwrap_or(false);
}
false
}
}
#[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");
}
/// Parses and fix up a `GpuParameters` from a command-line option string.
#[cfg(feature = "gpu")]
fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
from_key_values::<FixedGpuParameters>(s).map(|p| p.0)
}
#[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());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_mode() {
use devices::virtio::gpu::GpuMode;
let gpu_params = parse_gpu_options("backend=2d").unwrap();
assert_eq!(gpu_params.mode, GpuMode::Mode2D);
let gpu_params = parse_gpu_options("backend=2D").unwrap();
assert_eq!(gpu_params.mode, GpuMode::Mode2D);
#[cfg(feature = "virgl_renderer")]
{
let gpu_params = parse_gpu_options("backend=3d").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
let gpu_params = parse_gpu_options("backend=3D").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
let gpu_params = parse_gpu_options("backend=virglrenderer").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeVirglRenderer);
}
#[cfg(feature = "gfxstream")]
{
let gpu_params = parse_gpu_options("backend=gfxstream").unwrap();
assert_eq!(gpu_params.mode, GpuMode::ModeGfxstream);
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_flags() {
macro_rules! assert_default {
($p:ident.$a:ident) => {
assert_eq!($p.$a, GpuParameters::default().$a)
};
}
let gpu_params = parse_gpu_options("").unwrap();
assert_default!(gpu_params.renderer_use_egl);
assert_default!(gpu_params.renderer_use_gles);
assert_default!(gpu_params.renderer_use_glx);
assert_default!(gpu_params.renderer_use_surfaceless);
assert_default!(gpu_params.use_vulkan);
assert_default!(gpu_params.udmabuf);
let gpu_params = parse_gpu_options("egl=false,gles=false").unwrap();
assert_eq!(gpu_params.renderer_use_egl, false);
assert_eq!(gpu_params.renderer_use_gles, false);
assert_default!(gpu_params.renderer_use_glx);
assert_default!(gpu_params.renderer_use_surfaceless);
assert_default!(gpu_params.use_vulkan);
assert_default!(gpu_params.udmabuf);
let gpu_params = parse_gpu_options("surfaceless=false,glx").unwrap();
assert_default!(gpu_params.renderer_use_egl);
assert_default!(gpu_params.renderer_use_gles);
assert_eq!(gpu_params.renderer_use_surfaceless, false);
assert_eq!(gpu_params.renderer_use_glx, true);
assert_default!(gpu_params.use_vulkan);
assert_default!(gpu_params.udmabuf);
let gpu_params = parse_gpu_options("vulkan,udmabuf").unwrap();
assert_default!(gpu_params.renderer_use_egl);
assert_default!(gpu_params.renderer_use_gles);
assert_default!(gpu_params.renderer_use_glx);
assert_default!(gpu_params.renderer_use_surfaceless);
assert_eq!(gpu_params.use_vulkan, Some(true));
assert_eq!(gpu_params.udmabuf, true);
assert!(parse_gpu_options("egl=false,gles=true,foomatic").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_default_vulkan_support() {
#[cfg(feature = "virgl_renderer")]
{
let gpu_params = parse_gpu_options("backend=virglrenderer").unwrap();
assert_eq!(gpu_params.use_vulkan, None);
}
#[cfg(feature = "gfxstream")]
{
let gpu_params = parse_gpu_options("backend=gfxstream").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(true));
}
}
#[cfg(all(feature = "gpu", feature = "virglrenderer"))]
#[test]
fn parse_gpu_options_with_vulkan_specified() {
{
let gpu_params = parse_gpu_options("vulkan=true").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(true));
}
{
let gpu_params = parse_gpu_options("backend=virglrenderer,vulkan=true").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(true));
}
{
let gpu_params = parse_gpu_options("vulkan=true,backend=virglrenderer").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(true));
}
{
let gpu_params = parse_gpu_options("vulkan=false").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(false));
}
{
let gpu_params = parse_gpu_options("backend=virglrenderer,vulkan=false").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(false));
}
{
let gpu_params = parse_gpu_options("vulkan=false,backend=virglrenderer").unwrap();
assert_eq!(gpu_params.use_vulkan, Some(false));
}
{
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 = "virgl_renderer"))]
#[test]
fn parse_gpu_options_gfxstream_with_wsi_specified() {
use rutabaga_gfx::RutabagaWsi;
let gpu_params = parse_gpu_options("backend=virglrenderer,wsi=vk").unwrap();
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
let gpu_params = parse_gpu_options("backend=virglrenderer,wsi=vulkan").unwrap();
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
let gpu_params = 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_options_context_types() {
use rutabaga_gfx::RUTABAGA_CAPSET_CROSS_DOMAIN;
use rutabaga_gfx::RUTABAGA_CAPSET_VIRGL;
let gpu_params = parse_gpu_options("context-types=virgl:cross-domain").unwrap();
assert_eq!(
gpu_params.context_mask,
(1 << RUTABAGA_CAPSET_VIRGL) | (1 << RUTABAGA_CAPSET_CROSS_DOMAIN)
);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_cache() {
let gpu_params = parse_gpu_options("cache-path=/path/to/cache,cache-size=16384").unwrap();
assert_eq!(gpu_params.cache_path, Some("/path/to/cache".into()));
assert_eq!(gpu_params.cache_size, Some("16384".into()));
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_pci_bar() {
let gpu_params = parse_gpu_options("pci-bar-size=0x100000").unwrap();
assert_eq!(gpu_params.pci_bar_size, 0x100000);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_valid() {
// Default values.
let gpu_params: GpuDisplayParameters = from_key_values("").unwrap();
assert_eq!(gpu_params, GpuDisplayParameters::default());
let gpu_params: GpuDisplayParameters = from_key_values("mode=windowed[800,600]").unwrap();
assert_eq!(
gpu_params,
GpuDisplayParameters {
mode: GpuDisplayMode::Windowed(800, 600),
..Default::default()
}
);
assert!(from_key_values::<GpuDisplayParameters>("mode=invalid").is_err());
let gpu_params: GpuDisplayParameters = from_key_values("hidden,refresh-rate=100").unwrap();
assert_eq!(
gpu_params,
GpuDisplayParameters {
hidden: true,
refresh_rate: 100,
..Default::default()
}
);
let gpu_params: GpuDisplayParameters =
from_key_values("horizontal-dpi=320,vertical-dpi=25").unwrap();
assert_eq!(
gpu_params,
GpuDisplayParameters {
horizontal_dpi: 320,
vertical_dpi: 25,
..Default::default()
}
);
}
#[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",
"mode=windowed[700,800]",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.display_params.len(), 2);
assert_eq!(
gpu_params.display_params[0].get_virtual_display_size(),
(500, 600),
);
assert_eq!(
gpu_params.display_params[1].get_virtual_display_size(),
(700, 800),
);
}
{
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"2D",
"--gpu-display",
"mode=windowed[700,800]",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.display_params.len(), 1);
assert_eq!(
gpu_params.display_params[0].get_virtual_display_size(),
(700, 800),
);
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_cache_size() {
{
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"vulkan=false,cache-path=/some/path,cache-size=50M",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.cache_path, Some("/some/path".into()));
assert_eq!(gpu_params.cache_size, Some("50M".into()));
}
}
#[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")
);
}
}