1#[cfg(target_arch = "x86_64")]
6use std::arch::x86_64::__cpuid;
7#[cfg(target_arch = "x86_64")]
8use std::arch::x86_64::__cpuid_count;
9use std::collections::BTreeMap;
10use std::path::PathBuf;
11use std::str::FromStr;
12use std::time::Duration;
13
14use arch::set_default_serial_parameters;
15use arch::CpuSet;
16use arch::FdtPosition;
17#[cfg(all(target_os = "android", target_arch = "aarch64"))]
18use arch::FfaConfig;
19use arch::PciConfig;
20use arch::Pstore;
21#[cfg(target_arch = "x86_64")]
22use arch::SmbiosOptions;
23#[cfg(target_arch = "aarch64")]
24use arch::SveConfig;
25use arch::VcpuAffinity;
26use base::debug;
27use base::pagesize;
28use cros_async::ExecutorKind;
29use devices::serial_device::SerialHardware;
30use devices::serial_device::SerialParameters;
31use devices::virtio::block::DiskOption;
32#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
33use devices::virtio::device_constants::video::VideoDeviceConfig;
34#[cfg(feature = "gpu")]
35use devices::virtio::gpu::GpuParameters;
36use devices::virtio::scsi::ScsiOption;
37#[cfg(feature = "audio")]
38use devices::virtio::snd::parameters::Parameters as SndParameters;
39#[cfg(all(windows, feature = "gpu"))]
40use devices::virtio::vhost_user_backend::gpu::sys::windows::GpuBackendConfig;
41#[cfg(all(windows, feature = "gpu"))]
42use devices::virtio::vhost_user_backend::gpu::sys::windows::GpuVmmConfig;
43#[cfg(all(windows, feature = "gpu"))]
44use devices::virtio::vhost_user_backend::gpu::sys::windows::InputEventSplitConfig;
45#[cfg(all(windows, feature = "gpu"))]
46use devices::virtio::vhost_user_backend::gpu::sys::windows::WindowProcedureThreadSplitConfig;
47#[cfg(all(windows, feature = "audio"))]
48use devices::virtio::vhost_user_backend::snd::sys::windows::SndSplitConfig;
49use devices::virtio::vsock::VsockConfig;
50use devices::virtio::DeviceType;
51#[cfg(feature = "net")]
52use devices::virtio::NetParameters;
53use devices::FwCfgParameters;
54use devices::PciAddress;
55use devices::PflashParameters;
56use devices::StubPciParameters;
57#[cfg(target_arch = "x86_64")]
58use hypervisor::CpuHybridType;
59use hypervisor::ProtectionType;
60use jail::JailConfig;
61use resources::AddressRange;
62use serde::Deserialize;
63use serde::Deserializer;
64use serde::Serialize;
65use serde_keyvalue::FromKeyValues;
66use vm_control::BatteryType;
67use vm_memory::FileBackedMappingParameters;
68#[cfg(target_arch = "x86_64")]
69use x86_64::check_host_hybrid_support;
70#[cfg(target_arch = "x86_64")]
71use x86_64::CpuIdCall;
72
73pub(crate) use super::sys::HypervisorKind;
74#[cfg(any(target_os = "android", target_os = "linux"))]
75use crate::crosvm::sys::config::SharedDir;
76
77cfg_if::cfg_if! {
78 if #[cfg(any(target_os = "android", target_os = "linux"))] {
79 #[cfg(feature = "gpu")]
80 use crate::crosvm::sys::GpuRenderServerParameters;
81
82 #[cfg(target_arch = "aarch64")]
83 static VHOST_SCMI_PATH: &str = "/dev/vhost-scmi";
84 } else if #[cfg(windows)] {
85 use base::{Event, Tube};
86 }
87}
88
89#[cfg(feature = "balloon")]
91const VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS: u8 = 4;
92
93#[allow(dead_code)]
95#[derive(Debug, Serialize, Deserialize)]
96pub enum Executable {
97 Bios(PathBuf),
99 Kernel(PathBuf),
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
104#[serde(deny_unknown_fields, rename_all = "kebab-case")]
105pub enum IrqChipKind {
106 #[serde(rename_all = "kebab-case")]
108 Kernel {
109 #[cfg(target_arch = "aarch64")]
112 #[serde(default)]
113 allow_vgic_its: bool,
114 },
115 Split,
117 Userspace,
119}
120
121impl Default for IrqChipKind {
122 fn default() -> Self {
123 IrqChipKind::Kernel {
124 #[cfg(target_arch = "aarch64")]
125 allow_vgic_its: false,
126 }
127 }
128}
129
130#[cfg(target_arch = "x86_64")]
132#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
133#[serde(deny_unknown_fields, rename_all = "kebab-case")]
134pub struct CpuCoreType {
135 pub atom: CpuSet,
137 pub core: CpuSet,
139}
140
141#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize, FromKeyValues)]
142#[serde(deny_unknown_fields, rename_all = "kebab-case")]
143pub struct CpuOptions {
144 #[serde(default)]
146 pub num_cores: Option<usize>,
147 #[serde(default)]
149 pub clusters: Vec<CpuSet>,
150 #[cfg(target_arch = "x86_64")]
152 pub core_types: Option<CpuCoreType>,
153 #[serde(default)]
155 pub boot_cpu: Option<usize>,
156 #[serde(default)]
158 pub freq_domains: Vec<CpuSet>,
159 #[cfg(target_arch = "aarch64")]
161 pub sve: Option<SveConfig>,
162}
163
164#[derive(Debug, Default, Serialize, Deserialize, FromKeyValues)]
166#[serde(deny_unknown_fields, rename_all = "kebab-case")]
167pub struct DtboOption {
168 pub path: PathBuf,
170 #[serde(rename = "filter", default)]
172 pub filter_devs: bool,
173}
174
175#[derive(Debug, Default, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
176#[serde(deny_unknown_fields, rename_all = "kebab-case")]
177pub struct MemOptions {
178 #[serde(default)]
180 pub size: Option<u64>,
181}
182
183fn deserialize_swap_interval<'de, D: Deserializer<'de>>(
184 deserializer: D,
185) -> Result<Option<Duration>, D::Error> {
186 let ms = Option::<u64>::deserialize(deserializer)?;
187 match ms {
188 None => Ok(None),
189 Some(ms) => Ok(Some(Duration::from_millis(ms))),
190 }
191}
192
193#[derive(
194 Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues,
195)]
196#[serde(deny_unknown_fields, rename_all = "kebab-case")]
197pub struct PmemOption {
198 pub path: PathBuf,
200 #[serde(default)]
202 pub ro: bool,
203 #[serde(default)]
205 pub root: bool,
206 #[serde(default)]
209 pub vma_size: Option<u64>,
210 #[serde(
212 default,
213 deserialize_with = "deserialize_swap_interval",
214 rename = "swap-interval-ms"
215 )]
216 pub swap_interval: Option<Duration>,
217}
218
219#[derive(Serialize, Deserialize, FromKeyValues)]
220#[serde(deny_unknown_fields, rename_all = "kebab-case")]
221pub struct VhostUserFrontendOption {
222 #[serde(rename = "type")]
224 pub type_: devices::virtio::DeviceType,
225
226 pub socket: PathBuf,
228
229 pub max_queue_size: Option<u16>,
231
232 pub pci_address: Option<PciAddress>,
234}
235
236pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1024;
237pub const DEFAULT_TOUCH_DEVICE_WIDTH: u32 = 1280;
238
239#[derive(Serialize, Deserialize, Debug, FromKeyValues)]
240#[serde(deny_unknown_fields, rename_all = "kebab-case")]
241pub struct TouchDeviceOption {
242 pub path: PathBuf,
243 pub width: Option<u32>,
244 pub height: Option<u32>,
245 pub name: Option<String>,
246}
247
248fn parse_touch_device_option_legacy(s: &str) -> Option<TouchDeviceOption> {
252 let mut it = s.split(':');
253 let path = PathBuf::from(it.next()?.to_owned());
254 let width = if let Some(width) = it.next() {
255 Some(width.trim().parse().ok()?)
256 } else {
257 None
258 };
259 let height = if let Some(height) = it.next() {
260 Some(height.trim().parse().ok()?)
261 } else {
262 None
263 };
264 let name = it.next().map(|name| name.trim().to_string());
265 if it.next().is_some() {
266 return None;
267 }
268
269 Some(TouchDeviceOption {
270 path,
271 width,
272 height,
273 name,
274 })
275}
276
277pub fn parse_touch_device_option(s: &str) -> Result<TouchDeviceOption, String> {
283 if s.contains(':') {
284 if let Some(touch_spec) = parse_touch_device_option_legacy(s) {
285 log::warn!(
286 "colon-separated touch device options are deprecated; \
287 please use --input instead"
288 );
289 return Ok(touch_spec);
290 }
291 }
292
293 from_key_values::<TouchDeviceOption>(s)
294}
295
296#[derive(Serialize, Deserialize, Debug, FromKeyValues, Eq, PartialEq)]
298#[serde(deny_unknown_fields, rename_all = "kebab-case")]
299pub enum InputDeviceOption {
300 Evdev {
301 path: PathBuf,
302 },
303 Keyboard {
304 path: PathBuf,
305 },
306 Mouse {
307 path: PathBuf,
308 },
309 MultiTouch {
310 path: PathBuf,
311 width: Option<u32>,
312 height: Option<u32>,
313 name: Option<String>,
314 },
315 Rotary {
316 path: PathBuf,
317 },
318 SingleTouch {
319 path: PathBuf,
320 width: Option<u32>,
321 height: Option<u32>,
322 name: Option<String>,
323 },
324 Switches {
325 path: PathBuf,
326 },
327 Trackpad {
328 path: PathBuf,
329 width: Option<u32>,
330 height: Option<u32>,
331 name: Option<String>,
332 },
333 MultiTouchTrackpad {
334 path: PathBuf,
335 width: Option<u32>,
336 height: Option<u32>,
337 name: Option<String>,
338 },
339 #[serde(rename_all = "kebab-case")]
340 Custom {
341 path: PathBuf,
342 config_path: PathBuf,
343 },
344}
345
346fn parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64, String> {
347 if let Some(hex_string) = maybe_hex_string.strip_prefix("0x") {
349 u64::from_str_radix(hex_string, 16)
350 } else if let Some(hex_string) = maybe_hex_string.strip_prefix("0X") {
351 u64::from_str_radix(hex_string, 16)
352 } else {
353 u64::from_str(maybe_hex_string)
354 }
355 .map_err(|e| format!("invalid numeric value {maybe_hex_string}: {e}"))
356}
357
358pub fn parse_mmio_address_range(s: &str) -> Result<Vec<AddressRange>, String> {
359 s.split(',')
360 .map(|s| {
361 let r: Vec<&str> = s.split('-').collect();
362 if r.len() != 2 {
363 return Err(invalid_value_err(s, "invalid range"));
364 }
365 let parse = |s: &str| -> Result<u64, String> {
366 match parse_hex_or_decimal(s) {
367 Ok(v) => Ok(v),
368 Err(_) => Err(invalid_value_err(s, "expected u64 value")),
369 }
370 };
371 Ok(AddressRange {
372 start: parse(r[0])?,
373 end: parse(r[1])?,
374 })
375 })
376 .collect()
377}
378
379pub fn validate_serial_parameters(params: &SerialParameters) -> Result<(), String> {
380 if params.stdin && params.input.is_some() {
381 return Err("Cannot specify both stdin and input options".to_string());
382 }
383 if params.num < 1 {
384 return Err(invalid_value_err(
385 params.num.to_string(),
386 "Serial port num must be at least 1",
387 ));
388 }
389
390 if params.hardware == SerialHardware::Serial && params.num > 4 {
391 return Err(invalid_value_err(
392 format!("{}", params.num),
393 "Serial port num must be 4 or less",
394 ));
395 }
396
397 if params.pci_address.is_some() && params.hardware != SerialHardware::VirtioConsole {
398 return Err(invalid_value_err(
399 params.pci_address.unwrap().to_string(),
400 "Providing serial PCI address is only supported for virtio-console hardware type",
401 ));
402 }
403
404 Ok(())
405}
406
407pub fn parse_serial_options(s: &str) -> Result<SerialParameters, String> {
408 let params: SerialParameters = from_key_values(s)?;
409
410 validate_serial_parameters(¶ms)?;
411
412 Ok(params)
413}
414
415pub fn parse_bus_id_addr(v: &str) -> Result<(u8, u8, u16, u16), String> {
416 debug!("parse_bus_id_addr: {}", v);
417 let mut ids = v.split(':');
418 let errorre = move |item| move |e| format!("{item}: {e}");
419 match (ids.next(), ids.next(), ids.next(), ids.next()) {
420 (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
421 let bus_id = bus_id.parse::<u8>().map_err(errorre("bus_id"))?;
422 let addr = addr.parse::<u8>().map_err(errorre("addr"))?;
423 let vid = u16::from_str_radix(vid, 16).map_err(errorre("vid"))?;
424 let pid = u16::from_str_radix(pid, 16).map_err(errorre("pid"))?;
425 Ok((bus_id, addr, vid, pid))
426 }
427 _ => Err(String::from("BUS_ID:ADDR:BUS_NUM:DEV_NUM")),
428 }
429}
430
431pub fn invalid_value_err<T: AsRef<str>, S: ToString>(value: T, expected: S) -> String {
432 format!("invalid value {}: {}", value.as_ref(), expected.to_string())
433}
434
435#[derive(Debug, Serialize, Deserialize, FromKeyValues)]
436#[serde(deny_unknown_fields, rename_all = "kebab-case")]
437pub struct BatteryConfig {
438 #[serde(rename = "type", default)]
439 pub type_: BatteryType,
440}
441
442pub fn parse_cpu_btreemap_u32(s: &str) -> Result<BTreeMap<usize, u32>, String> {
443 let mut parsed_btreemap: BTreeMap<usize, u32> = BTreeMap::default();
444 for cpu_pair in s.split(',') {
445 let assignment: Vec<&str> = cpu_pair.split('=').collect();
446 if assignment.len() != 2 {
447 return Err(invalid_value_err(
448 cpu_pair,
449 "Invalid CPU pair syntax, missing '='",
450 ));
451 }
452 let cpu = assignment[0].parse().map_err(|_| {
453 invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
454 })?;
455 let val = assignment[1].parse().map_err(|_| {
456 invalid_value_err(assignment[1], "CPU property must be a non-negative integer")
457 })?;
458 if parsed_btreemap.insert(cpu, val).is_some() {
459 return Err(invalid_value_err(cpu_pair, "CPU index must be unique"));
460 }
461 }
462 Ok(parsed_btreemap)
463}
464
465#[cfg(all(
466 target_arch = "aarch64",
467 any(target_os = "android", target_os = "linux")
468))]
469pub fn parse_cpu_frequencies(s: &str) -> Result<BTreeMap<usize, Vec<u32>>, String> {
470 let mut cpu_frequencies: BTreeMap<usize, Vec<u32>> = BTreeMap::default();
471 for cpufreq_assigns in s.split(';') {
472 let assignment: Vec<&str> = cpufreq_assigns.split('=').collect();
473 if assignment.len() != 2 {
474 return Err(invalid_value_err(
475 cpufreq_assigns,
476 "invalid CPU freq syntax",
477 ));
478 }
479 let cpu = assignment[0].parse().map_err(|_| {
480 invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
481 })?;
482 let freqs = assignment[1]
483 .split(',')
484 .map(|x| x.parse::<u32>().unwrap())
485 .collect::<Vec<_>>();
486 if cpu_frequencies.insert(cpu, freqs).is_some() {
487 return Err(invalid_value_err(
488 cpufreq_assigns,
489 "CPU index must be unique",
490 ));
491 }
492 }
493 Ok(cpu_frequencies)
494}
495
496pub fn from_key_values<'a, T: Deserialize<'a>>(value: &'a str) -> Result<T, String> {
497 serde_keyvalue::from_key_values(value).map_err(|e| e.to_string())
498}
499
500pub fn parse_cpu_affinity(s: &str) -> Result<VcpuAffinity, String> {
507 if s.contains('=') {
508 let mut affinity_map = BTreeMap::new();
509 for cpu_pair in s.split(':') {
510 let assignment: Vec<&str> = cpu_pair.split('=').collect();
511 if assignment.len() != 2 {
512 return Err(invalid_value_err(
513 cpu_pair,
514 "invalid VCPU assignment syntax",
515 ));
516 }
517 let guest_cpu = assignment[0].parse().map_err(|_| {
518 invalid_value_err(assignment[0], "CPU index must be a non-negative integer")
519 })?;
520 let host_cpu_set = CpuSet::from_str(assignment[1])?;
521 if affinity_map.insert(guest_cpu, host_cpu_set).is_some() {
522 return Err(invalid_value_err(cpu_pair, "VCPU index must be unique"));
523 }
524 }
525 Ok(VcpuAffinity::PerVcpu(affinity_map))
526 } else {
527 Ok(VcpuAffinity::Global(CpuSet::from_str(s)?))
528 }
529}
530
531pub fn parse_pflash_parameters(s: &str) -> Result<PflashParameters, String> {
532 let pflash_parameters: PflashParameters = from_key_values(s)?;
533
534 Ok(pflash_parameters)
535}
536
537mod serde_serial_params {
540 use std::iter::FromIterator;
541
542 use serde::Deserializer;
543 use serde::Serializer;
544
545 use super::*;
546
547 pub fn serialize<S>(
548 params: &BTreeMap<(SerialHardware, u8), SerialParameters>,
549 ser: S,
550 ) -> Result<S::Ok, S::Error>
551 where
552 S: Serializer,
553 {
554 let v: Vec<(&(SerialHardware, u8), &SerialParameters)> = params.iter().collect();
555 serde::Serialize::serialize(&v, ser)
556 }
557
558 pub fn deserialize<'a, D>(
559 de: D,
560 ) -> Result<BTreeMap<(SerialHardware, u8), SerialParameters>, D::Error>
561 where
562 D: Deserializer<'a>,
563 {
564 let params: Vec<((SerialHardware, u8), SerialParameters)> =
565 serde::Deserialize::deserialize(de)?;
566 Ok(BTreeMap::from_iter(params))
567 }
568}
569
570#[derive(Serialize, Deserialize)]
572#[remain::sorted]
573pub struct Config {
574 #[cfg(all(target_arch = "x86_64", unix))]
575 pub ac_adapter: bool,
576 pub acpi_tables: Vec<PathBuf>,
577 #[cfg(feature = "android_display")]
578 pub android_display_service: Option<String>,
579 pub android_fstab: Option<PathBuf>,
580 pub async_executor: Option<ExecutorKind>,
581 #[cfg(feature = "balloon")]
582 pub balloon: bool,
583 #[cfg(feature = "balloon")]
584 pub balloon_bias: i64,
585 #[cfg(feature = "balloon")]
586 pub balloon_control: Option<PathBuf>,
587 #[cfg(feature = "balloon")]
588 pub balloon_page_reporting: bool,
589 #[cfg(feature = "balloon")]
590 pub balloon_ws_num_bins: u8,
591 #[cfg(feature = "balloon")]
592 pub balloon_ws_reporting: bool,
593 pub battery_config: Option<BatteryConfig>,
594 #[cfg(windows)]
595 pub block_control_tube: Vec<Tube>,
596 #[cfg(windows)]
597 pub block_vhost_user_tube: Vec<Tube>,
598 #[cfg(any(target_os = "android", target_os = "linux"))]
599 pub boost_uclamp: bool,
600 pub boot_cpu: usize,
601 #[cfg(target_arch = "x86_64")]
602 pub break_linux_pci_config_io: bool,
603 #[cfg(windows)]
604 pub broker_shutdown_event: Option<Event>,
605 #[cfg(target_arch = "x86_64")]
606 pub bus_lock_ratelimit: u64,
607 #[cfg(any(target_os = "android", target_os = "linux"))]
608 pub coiommu_param: Option<devices::CoIommuParameters>,
609 pub core_scheduling: bool,
610 pub cpu_capacity: BTreeMap<usize, u32>, pub cpu_clusters: Vec<CpuSet>,
612 pub cpu_freq_domains: Vec<CpuSet>,
613 #[cfg(all(
614 target_arch = "aarch64",
615 any(target_os = "android", target_os = "linux")
616 ))]
617 pub cpu_frequencies_khz: BTreeMap<usize, Vec<u32>>, #[cfg(all(
619 target_arch = "aarch64",
620 any(target_os = "android", target_os = "linux")
621 ))]
622 pub cpu_ipc_ratio: BTreeMap<usize, u32>, #[cfg(feature = "crash-report")]
624 pub crash_pipe_name: Option<String>,
625 #[cfg(feature = "crash-report")]
626 pub crash_report_uuid: Option<String>,
627 pub delay_rt: bool,
628 pub device_tree_overlay: Vec<DtboOption>,
629 pub disable_virtio_intx: bool,
630 pub disks: Vec<DiskOption>,
631 pub display_input_height: Option<u32>,
632 pub display_input_width: Option<u32>,
633 pub display_window_keyboard: bool,
634 pub display_window_mouse: bool,
635 pub dump_device_tree_blob: Option<PathBuf>,
636 pub dynamic_power_coefficient: BTreeMap<usize, u32>,
637 pub enable_fw_cfg: bool,
638 pub enable_hwp: bool,
639 pub executable_path: Option<Executable>,
640 #[cfg(windows)]
641 pub exit_stats: bool,
642 pub fdt_position: Option<FdtPosition>,
643 #[cfg(all(target_os = "android", target_arch = "aarch64"))]
644 pub ffa: Option<FfaConfig>,
645 pub file_backed_mappings_mmio: Vec<FileBackedMappingParameters>,
646 pub file_backed_mappings_ram: Vec<FileBackedMappingParameters>,
647 pub force_calibrated_tsc_leaf: bool,
648 pub force_disable_readonly_mem: bool,
649 pub force_s2idle: bool,
650 pub fw_cfg_parameters: Vec<FwCfgParameters>,
651 #[cfg(feature = "gdb")]
652 pub gdb: Option<u32>,
653 #[cfg(all(windows, feature = "gpu"))]
654 pub gpu_backend_config: Option<GpuBackendConfig>,
655 #[cfg(all(unix, feature = "gpu"))]
656 pub gpu_cgroup_path: Option<PathBuf>,
657 #[cfg(feature = "gpu")]
658 pub gpu_parameters: Option<GpuParameters>,
659 #[cfg(all(unix, feature = "gpu"))]
660 pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
661 #[cfg(all(unix, feature = "gpu"))]
662 pub gpu_server_cgroup_path: Option<PathBuf>,
663 #[cfg(all(windows, feature = "gpu"))]
664 pub gpu_vmm_config: Option<GpuVmmConfig>,
665 pub host_cpu_topology: bool,
666 #[cfg(windows)]
667 pub host_guid: Option<String>,
668 pub hugepages: bool,
669 pub hypervisor: Option<HypervisorKind>,
670 #[cfg(feature = "balloon")]
671 pub init_memory: Option<u64>,
672 pub initrd_path: Option<PathBuf>,
673 #[cfg(all(windows, feature = "gpu"))]
674 pub input_event_split_config: Option<InputEventSplitConfig>,
675 pub irq_chip: Option<IrqChipKind>,
676 pub itmt: bool,
677 pub jail_config: Option<JailConfig>,
678 #[cfg(windows)]
679 pub kernel_log_file: Option<String>,
680 #[cfg(any(target_os = "android", target_os = "linux"))]
681 pub lock_guest_memory: bool,
682 #[cfg(windows)]
683 pub log_file: Option<String>,
684 #[cfg(windows)]
685 pub logs_directory: Option<String>,
686 #[cfg(all(feature = "media", feature = "video-decoder"))]
687 pub media_decoder: Vec<VideoDeviceConfig>,
688 pub memory: Option<u64>,
689 pub memory_file: Option<PathBuf>,
690 pub mmio_address_ranges: Vec<AddressRange>,
691 #[cfg(target_arch = "aarch64")]
692 pub mte: bool,
693 pub name: Option<String>,
694 #[cfg(feature = "net")]
695 pub net: Vec<NetParameters>,
696 #[cfg(windows)]
697 pub net_vhost_user_tube: Option<Tube>,
698 pub no_i8042: bool,
699 pub no_pmu: bool,
700 pub no_rtc: bool,
701 pub no_smt: bool,
702 pub params: Vec<String>,
703 pub pci_config: PciConfig,
704 #[cfg(feature = "pci-hotplug")]
705 pub pci_hotplug_slots: Option<u8>,
706 pub per_vm_core_scheduling: bool,
707 pub pflash_parameters: Option<PflashParameters>,
708 #[cfg(any(target_os = "android", target_os = "linux"))]
709 pub pmem_ext2: Vec<crate::crosvm::sys::config::PmemExt2Option>,
710 pub pmems: Vec<PmemOption>,
711 #[cfg(feature = "process-invariants")]
712 pub process_invariants_data_handle: Option<u64>,
713 #[cfg(feature = "process-invariants")]
714 pub process_invariants_data_size: Option<usize>,
715 #[cfg(windows)]
716 pub product_channel: Option<String>,
717 #[cfg(windows)]
718 pub product_name: Option<String>,
719 #[cfg(windows)]
720 pub product_version: Option<String>,
721 pub protection_type: ProtectionType,
722 pub pstore: Option<Pstore>,
723 #[cfg(feature = "pvclock")]
724 pub pvclock: bool,
725 pub pvm_fw: Option<PathBuf>,
727 pub restore_path: Option<PathBuf>,
728 pub rng: bool,
729 pub rt_cpus: CpuSet,
730 pub scsis: Vec<ScsiOption>,
731 #[serde(with = "serde_serial_params")]
732 pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
733 #[cfg(windows)]
734 pub service_pipe_name: Option<String>,
735 #[cfg(any(target_os = "android", target_os = "linux"))]
736 #[serde(skip)]
737 pub shared_dirs: Vec<SharedDir>,
738 #[cfg(feature = "media")]
739 pub simple_media_device: bool,
740 #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
741 pub slirp_capture_file: Option<String>,
742 #[cfg(target_arch = "x86_64")]
743 pub smbios: SmbiosOptions,
744 #[cfg(all(windows, feature = "audio"))]
745 pub snd_split_configs: Vec<SndSplitConfig>,
746 pub socket_path: Option<PathBuf>,
747 #[cfg(feature = "audio")]
748 pub sound: Option<PathBuf>,
749 pub stub_pci_devices: Vec<StubPciParameters>,
750 pub suspended: bool,
751 #[cfg(target_arch = "aarch64")]
752 pub sve: Option<SveConfig>,
753 pub swap_dir: Option<PathBuf>,
754 pub swiotlb: Option<u64>,
755 #[cfg(target_os = "android")]
756 pub task_profiles: Vec<String>,
757 #[cfg(any(target_os = "android", target_os = "linux"))]
758 pub unmap_guest_memory_on_fork: bool,
759 pub usb: bool,
760 #[cfg(any(target_os = "android", target_os = "linux"))]
761 #[cfg(feature = "media")]
762 pub v4l2_proxy: Vec<PathBuf>,
763 pub vcpu_affinity: Option<VcpuAffinity>,
764 pub vcpu_cgroup_path: Option<PathBuf>,
765 pub vcpu_count: Option<usize>,
766 #[cfg(target_arch = "x86_64")]
767 pub vcpu_hybrid_type: BTreeMap<usize, CpuHybridType>, #[cfg(any(target_os = "android", target_os = "linux"))]
769 pub vfio: Vec<super::sys::config::VfioOption>,
770 #[cfg(any(target_os = "android", target_os = "linux"))]
771 pub vfio_isolate_hotplug: bool,
772 #[cfg(any(target_os = "android", target_os = "linux"))]
773 #[cfg(target_arch = "aarch64")]
774 pub vhost_scmi: bool,
775 #[cfg(any(target_os = "android", target_os = "linux"))]
776 #[cfg(target_arch = "aarch64")]
777 pub vhost_scmi_device: PathBuf,
778 pub vhost_user: Vec<VhostUserFrontendOption>,
779 pub vhost_user_connect_timeout_ms: Option<u64>,
780 #[cfg(feature = "video-decoder")]
781 pub video_dec: Vec<VideoDeviceConfig>,
782 #[cfg(feature = "video-encoder")]
783 pub video_enc: Vec<VideoDeviceConfig>,
784 #[cfg(all(
785 target_arch = "aarch64",
786 any(target_os = "android", target_os = "linux")
787 ))]
788 pub virt_cpufreq: bool,
789 pub virt_cpufreq_v2: bool,
790 pub virtio_input: Vec<InputDeviceOption>,
791 #[cfg(feature = "audio")]
792 #[serde(skip)]
793 pub virtio_snds: Vec<SndParameters>,
794 pub vsock: Option<VsockConfig>,
795 #[cfg(feature = "vtpm")]
796 pub vtpm_proxy: bool,
797 pub wayland_socket_paths: BTreeMap<String, PathBuf>,
798 #[cfg(all(windows, feature = "gpu"))]
799 pub window_procedure_thread_split_config: Option<WindowProcedureThreadSplitConfig>,
800 pub x_display: Option<String>,
801}
802
803impl Default for Config {
804 fn default() -> Config {
805 Config {
806 #[cfg(all(target_arch = "x86_64", unix))]
807 ac_adapter: false,
808 acpi_tables: Vec::new(),
809 #[cfg(feature = "android_display")]
810 android_display_service: None,
811 android_fstab: None,
812 async_executor: None,
813 #[cfg(feature = "balloon")]
814 balloon: true,
815 #[cfg(feature = "balloon")]
816 balloon_bias: 0,
817 #[cfg(feature = "balloon")]
818 balloon_control: None,
819 #[cfg(feature = "balloon")]
820 balloon_page_reporting: false,
821 #[cfg(feature = "balloon")]
822 balloon_ws_num_bins: VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS,
823 #[cfg(feature = "balloon")]
824 balloon_ws_reporting: false,
825 battery_config: None,
826 boot_cpu: 0,
827 #[cfg(windows)]
828 block_control_tube: Vec::new(),
829 #[cfg(windows)]
830 block_vhost_user_tube: Vec::new(),
831 #[cfg(target_arch = "x86_64")]
832 break_linux_pci_config_io: false,
833 #[cfg(windows)]
834 broker_shutdown_event: None,
835 #[cfg(target_arch = "x86_64")]
836 bus_lock_ratelimit: 0,
837 #[cfg(any(target_os = "android", target_os = "linux"))]
838 coiommu_param: None,
839 core_scheduling: true,
840 #[cfg(feature = "crash-report")]
841 crash_pipe_name: None,
842 #[cfg(feature = "crash-report")]
843 crash_report_uuid: None,
844 cpu_capacity: BTreeMap::new(),
845 cpu_clusters: Vec::new(),
846 #[cfg(all(
847 target_arch = "aarch64",
848 any(target_os = "android", target_os = "linux")
849 ))]
850 cpu_frequencies_khz: BTreeMap::new(),
851 cpu_freq_domains: Vec::new(),
852 #[cfg(all(
853 target_arch = "aarch64",
854 any(target_os = "android", target_os = "linux")
855 ))]
856 cpu_ipc_ratio: BTreeMap::new(),
857 delay_rt: false,
858 device_tree_overlay: Vec::new(),
859 disks: Vec::new(),
860 disable_virtio_intx: false,
861 display_input_height: None,
862 display_input_width: None,
863 display_window_keyboard: false,
864 display_window_mouse: false,
865 dump_device_tree_blob: None,
866 dynamic_power_coefficient: BTreeMap::new(),
867 enable_fw_cfg: false,
868 enable_hwp: false,
869 executable_path: None,
870 #[cfg(windows)]
871 exit_stats: false,
872 fdt_position: None,
873 #[cfg(all(target_os = "android", target_arch = "aarch64"))]
874 ffa: None,
875 file_backed_mappings_mmio: Vec::new(),
876 file_backed_mappings_ram: Vec::new(),
877 force_calibrated_tsc_leaf: false,
878 force_disable_readonly_mem: false,
879 force_s2idle: false,
880 fw_cfg_parameters: Vec::new(),
881 #[cfg(feature = "gdb")]
882 gdb: None,
883 #[cfg(all(windows, feature = "gpu"))]
884 gpu_backend_config: None,
885 #[cfg(feature = "gpu")]
886 gpu_parameters: None,
887 #[cfg(all(unix, feature = "gpu"))]
888 gpu_render_server_parameters: None,
889 #[cfg(all(unix, feature = "gpu"))]
890 gpu_cgroup_path: None,
891 #[cfg(all(unix, feature = "gpu"))]
892 gpu_server_cgroup_path: None,
893 #[cfg(all(windows, feature = "gpu"))]
894 gpu_vmm_config: None,
895 host_cpu_topology: false,
896 #[cfg(windows)]
897 host_guid: None,
898 #[cfg(windows)]
899 product_version: None,
900 #[cfg(windows)]
901 product_channel: None,
902 hugepages: false,
903 hypervisor: None,
904 #[cfg(feature = "balloon")]
905 init_memory: None,
906 initrd_path: None,
907 #[cfg(all(windows, feature = "gpu"))]
908 input_event_split_config: None,
909 irq_chip: None,
910 itmt: false,
911 jail_config: if !cfg!(feature = "default-no-sandbox") {
912 Some(Default::default())
913 } else {
914 None
915 },
916 #[cfg(windows)]
917 kernel_log_file: None,
918 #[cfg(any(target_os = "android", target_os = "linux"))]
919 lock_guest_memory: false,
920 #[cfg(windows)]
921 log_file: None,
922 #[cfg(windows)]
923 logs_directory: None,
924 #[cfg(any(target_os = "android", target_os = "linux"))]
925 boost_uclamp: false,
926 #[cfg(all(feature = "media", feature = "video-decoder"))]
927 media_decoder: Default::default(),
928 memory: None,
929 memory_file: None,
930 mmio_address_ranges: Vec::new(),
931 #[cfg(target_arch = "aarch64")]
932 mte: false,
933 name: None,
934 #[cfg(feature = "net")]
935 net: Vec::new(),
936 #[cfg(windows)]
937 net_vhost_user_tube: None,
938 no_i8042: false,
939 no_pmu: false,
940 no_rtc: false,
941 no_smt: false,
942 params: Vec::new(),
943 pci_config: Default::default(),
944 #[cfg(feature = "pci-hotplug")]
945 pci_hotplug_slots: None,
946 per_vm_core_scheduling: false,
947 pflash_parameters: None,
948 #[cfg(any(target_os = "android", target_os = "linux"))]
949 pmem_ext2: Vec::new(),
950 pmems: Vec::new(),
951 #[cfg(feature = "process-invariants")]
952 process_invariants_data_handle: None,
953 #[cfg(feature = "process-invariants")]
954 process_invariants_data_size: None,
955 #[cfg(windows)]
956 product_name: None,
957 protection_type: ProtectionType::Unprotected,
958 pstore: None,
959 #[cfg(feature = "pvclock")]
960 pvclock: false,
961 pvm_fw: None,
962 restore_path: None,
963 rng: true,
964 rt_cpus: Default::default(),
965 serial_parameters: BTreeMap::new(),
966 scsis: Vec::new(),
967 #[cfg(windows)]
968 service_pipe_name: None,
969 #[cfg(any(target_os = "android", target_os = "linux"))]
970 shared_dirs: Vec::new(),
971 #[cfg(feature = "media")]
972 simple_media_device: Default::default(),
973 #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
974 slirp_capture_file: None,
975 #[cfg(target_arch = "x86_64")]
976 smbios: SmbiosOptions::default(),
977 #[cfg(all(windows, feature = "audio"))]
978 snd_split_configs: Vec::new(),
979 socket_path: None,
980 #[cfg(feature = "audio")]
981 sound: None,
982 stub_pci_devices: Vec::new(),
983 suspended: false,
984 #[cfg(target_arch = "aarch64")]
985 sve: None,
986 swap_dir: None,
987 swiotlb: None,
988 #[cfg(target_os = "android")]
989 task_profiles: Vec::new(),
990 #[cfg(any(target_os = "android", target_os = "linux"))]
991 unmap_guest_memory_on_fork: false,
992 usb: true,
993 vcpu_affinity: None,
994 vcpu_cgroup_path: None,
995 vcpu_count: None,
996 #[cfg(target_arch = "x86_64")]
997 vcpu_hybrid_type: BTreeMap::new(),
998 #[cfg(any(target_os = "android", target_os = "linux"))]
999 vfio: Vec::new(),
1000 #[cfg(any(target_os = "android", target_os = "linux"))]
1001 vfio_isolate_hotplug: false,
1002 #[cfg(any(target_os = "android", target_os = "linux"))]
1003 #[cfg(target_arch = "aarch64")]
1004 vhost_scmi: false,
1005 #[cfg(any(target_os = "android", target_os = "linux"))]
1006 #[cfg(target_arch = "aarch64")]
1007 vhost_scmi_device: PathBuf::from(VHOST_SCMI_PATH),
1008 vhost_user: Vec::new(),
1009 vhost_user_connect_timeout_ms: None,
1010 vsock: None,
1011 #[cfg(feature = "video-decoder")]
1012 video_dec: Vec::new(),
1013 #[cfg(feature = "video-encoder")]
1014 video_enc: Vec::new(),
1015 #[cfg(all(
1016 target_arch = "aarch64",
1017 any(target_os = "android", target_os = "linux")
1018 ))]
1019 virt_cpufreq: false,
1020 virt_cpufreq_v2: false,
1021 virtio_input: Vec::new(),
1022 #[cfg(feature = "audio")]
1023 virtio_snds: Vec::new(),
1024 #[cfg(any(target_os = "android", target_os = "linux"))]
1025 #[cfg(feature = "media")]
1026 v4l2_proxy: Vec::new(),
1027 #[cfg(feature = "vtpm")]
1028 vtpm_proxy: false,
1029 wayland_socket_paths: BTreeMap::new(),
1030 #[cfg(windows)]
1031 window_procedure_thread_split_config: None,
1032 x_display: None,
1033 }
1034 }
1035}
1036
1037pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
1038 if cfg.executable_path.is_none() {
1039 return Err("Executable is not specified".to_string());
1040 }
1041
1042 #[cfg(feature = "gpu")]
1043 {
1044 crate::crosvm::gpu_config::validate_gpu_config(cfg)?;
1045 }
1046 #[cfg(feature = "gdb")]
1047 if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
1048 return Err("`gdb` requires the number of vCPU to be 1".to_string());
1049 }
1050 if cfg.host_cpu_topology {
1051 if cfg.no_smt {
1052 return Err(
1053 "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
1054 the smt of the Guest is the same as that of the Host when \
1055 `host-cpu-topology` is set."
1056 .to_string(),
1057 );
1058 }
1059
1060 let pcpu_count =
1061 base::number_of_logical_cores().expect("Could not read number of logical cores");
1062 if let Some(vcpu_count) = cfg.vcpu_count {
1063 if pcpu_count != vcpu_count {
1064 return Err(format!(
1065 "`host-cpu-topology` requires the count of vCPUs({vcpu_count}) to equal the \
1066 count of CPUs({pcpu_count}) on host."
1067 ));
1068 }
1069 } else {
1070 cfg.vcpu_count = Some(pcpu_count);
1071 }
1072
1073 match &cfg.vcpu_affinity {
1074 None => {
1075 let mut affinity_map = BTreeMap::new();
1076 for cpu_id in 0..cfg.vcpu_count.unwrap() {
1077 affinity_map.insert(cpu_id, CpuSet::new([cpu_id]));
1078 }
1079 cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
1080 }
1081 _ => {
1082 return Err(
1083 "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
1084 .to_string(),
1085 );
1086 }
1087 }
1088
1089 if !cfg.cpu_capacity.is_empty() {
1090 return Err(
1091 "`host-cpu-topology` requires not to set `cpu-capacity` at the same time"
1092 .to_string(),
1093 );
1094 }
1095
1096 if !cfg.cpu_clusters.is_empty() {
1097 return Err(
1098 "`host-cpu-topology` requires not to set `cpu clusters` at the same time"
1099 .to_string(),
1100 );
1101 }
1102 }
1103
1104 if cfg.boot_cpu >= cfg.vcpu_count.unwrap_or(1) {
1105 log::warn!("boot_cpu selection cannot be higher than vCPUs available, defaulting to 0");
1106 cfg.boot_cpu = 0;
1107 }
1108
1109 #[cfg(all(
1110 target_arch = "aarch64",
1111 any(target_os = "android", target_os = "linux")
1112 ))]
1113 if !cfg.cpu_frequencies_khz.is_empty() {
1114 if !cfg.virt_cpufreq_v2 {
1115 return Err("`cpu-frequencies` requires `virt-cpufreq-upstream`".to_string());
1116 }
1117
1118 if cfg.host_cpu_topology {
1119 return Err(
1120 "`host-cpu-topology` cannot be used with 'cpu-frequencies` at the same time"
1121 .to_string(),
1122 );
1123 }
1124 }
1125
1126 #[cfg(all(
1127 target_arch = "aarch64",
1128 any(target_os = "android", target_os = "linux")
1129 ))]
1130 if cfg.virt_cpufreq {
1131 if !cfg.host_cpu_topology && (cfg.vcpu_affinity.is_none() || cfg.cpu_capacity.is_empty()) {
1132 return Err("`virt-cpufreq` requires 'host-cpu-topology' enabled or \
1133 have vcpu_affinity and cpu_capacity configured"
1134 .to_string());
1135 }
1136 }
1137 #[cfg(target_arch = "x86_64")]
1138 if !cfg.vcpu_hybrid_type.is_empty() {
1139 if cfg.host_cpu_topology {
1140 return Err("`core-types` cannot be set with `host-cpu-topology`.".to_string());
1141 }
1142 check_host_hybrid_support(&CpuIdCall::new(__cpuid_count, __cpuid))
1143 .map_err(|e| format!("the cpu doesn't support `core-types`: {e}"))?;
1144 if cfg.vcpu_hybrid_type.len() != cfg.vcpu_count.unwrap_or(1) {
1145 return Err("`core-types` must be set for all virtual CPUs".to_string());
1146 }
1147 for cpu_id in 0..cfg.vcpu_count.unwrap_or(1) {
1148 if !cfg.vcpu_hybrid_type.contains_key(&cpu_id) {
1149 return Err("`core-types` must be set for all virtual CPUs".to_string());
1150 }
1151 }
1152 }
1153 #[cfg(target_arch = "x86_64")]
1154 if cfg.enable_hwp && !cfg.host_cpu_topology {
1155 return Err("setting `enable-hwp` requires `host-cpu-topology` is set.".to_string());
1156 }
1157 #[cfg(target_arch = "x86_64")]
1158 if cfg.itmt {
1159 use std::collections::BTreeSet;
1160 if !cfg.host_cpu_topology {
1164 if let Some(VcpuAffinity::PerVcpu(v)) = &cfg.vcpu_affinity {
1167 if v.len() != cfg.vcpu_count.unwrap_or(1) {
1169 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1170 }
1171
1172 let mut pcpu_set = BTreeSet::new();
1173 for cpus in v.values() {
1174 if cpus.len() != 1 {
1175 return Err(
1176 "`itmt` requires affinity to be set 1 pCPU for 1 vCPU.".to_owned()
1177 );
1178 }
1179 if !pcpu_set.insert(cpus[0]) {
1182 return Err(
1183 "`cpu_host` requires affinity to be set different pVPU for each vCPU."
1184 .to_owned(),
1185 );
1186 }
1187 }
1188 } else {
1189 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1190 }
1191 }
1192 if !cfg.enable_hwp {
1193 return Err("setting `itmt` requires `enable-hwp` is set.".to_string());
1194 }
1195 }
1196
1197 #[cfg(feature = "balloon")]
1198 {
1199 if !cfg.balloon && cfg.balloon_control.is_some() {
1200 return Err("'balloon-control' requires enabled balloon".to_string());
1201 }
1202
1203 if !cfg.balloon && cfg.balloon_page_reporting {
1204 return Err("'balloon_page_reporting' requires enabled balloon".to_string());
1205 }
1206 }
1207
1208 #[cfg(feature = "swap")]
1211 if cfg.swap_dir.is_some() && cfg.jail_config.is_none() {
1212 return Err("'swap' and 'disable-sandbox' are mutually exclusive".to_string());
1213 }
1214
1215 set_default_serial_parameters(
1216 &mut cfg.serial_parameters,
1217 cfg.vhost_user
1218 .iter()
1219 .any(|opt| opt.type_ == DeviceType::Console),
1220 );
1221
1222 for mapping in cfg
1223 .file_backed_mappings_mmio
1224 .iter_mut()
1225 .chain(cfg.file_backed_mappings_ram.iter_mut())
1226 {
1227 validate_file_backed_mapping(mapping)?;
1228 }
1229
1230 for pmem in cfg.pmems.iter() {
1231 validate_pmem(pmem)?;
1232 }
1233
1234 super::sys::config::validate_config(cfg)
1236}
1237
1238fn validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String> {
1239 let pagesize_mask = pagesize() as u64 - 1;
1240 let aligned_address = mapping.address & !pagesize_mask;
1241 let aligned_size =
1242 ((mapping.address + mapping.size + pagesize_mask) & !pagesize_mask) - aligned_address;
1243
1244 if mapping.align {
1245 mapping.address = aligned_address;
1246 mapping.size = aligned_size;
1247 } else if aligned_address != mapping.address || aligned_size != mapping.size {
1248 return Err(
1249 "--file-backed-mapping addr and size parameters must be page size aligned".to_string(),
1250 );
1251 }
1252
1253 Ok(())
1254}
1255
1256fn validate_pmem(pmem: &PmemOption) -> Result<(), String> {
1257 if (pmem.swap_interval.is_some() && pmem.vma_size.is_none())
1258 || (pmem.swap_interval.is_none() && pmem.vma_size.is_some())
1259 {
1260 return Err(
1261 "--pmem vma-size and swap-interval parameters must be specified together".to_string(),
1262 );
1263 }
1264
1265 if pmem.ro && pmem.swap_interval.is_some() {
1266 return Err(
1267 "--pmem swap-interval parameter can only be set for writable pmem device".to_string(),
1268 );
1269 }
1270
1271 Ok(())
1272}
1273
1274#[cfg(test)]
1275#[allow(clippy::needless_update)]
1276mod tests {
1277 use argh::FromArgs;
1278 use devices::PciClassCode;
1279 use devices::StubPciParameters;
1280 #[cfg(target_arch = "x86_64")]
1281 use uuid::uuid;
1282
1283 use super::*;
1284
1285 fn config_from_args(args: &[&str]) -> Config {
1286 crate::crosvm::cmdline::RunCommand::from_args(&[], args)
1287 .unwrap()
1288 .try_into()
1289 .unwrap()
1290 }
1291
1292 #[test]
1293 fn parse_cpu_opts() {
1294 let res: CpuOptions = from_key_values("").unwrap();
1295 assert_eq!(res, CpuOptions::default());
1296
1297 let res: CpuOptions = from_key_values("12").unwrap();
1299 assert_eq!(
1300 res,
1301 CpuOptions {
1302 num_cores: Some(12),
1303 ..Default::default()
1304 }
1305 );
1306
1307 let res: CpuOptions = from_key_values("num-cores=16").unwrap();
1308 assert_eq!(
1309 res,
1310 CpuOptions {
1311 num_cores: Some(16),
1312 ..Default::default()
1313 }
1314 );
1315
1316 let res: CpuOptions = from_key_values("clusters=[[0],[1],[2],[3]]").unwrap();
1318 assert_eq!(
1319 res,
1320 CpuOptions {
1321 clusters: vec![
1322 CpuSet::new([0]),
1323 CpuSet::new([1]),
1324 CpuSet::new([2]),
1325 CpuSet::new([3])
1326 ],
1327 ..Default::default()
1328 }
1329 );
1330
1331 let res: CpuOptions = from_key_values("clusters=[[0-3]]").unwrap();
1332 assert_eq!(
1333 res,
1334 CpuOptions {
1335 clusters: vec![CpuSet::new([0, 1, 2, 3])],
1336 ..Default::default()
1337 }
1338 );
1339
1340 let res: CpuOptions = from_key_values("clusters=[[0,2],[1,3],[4-7,12]]").unwrap();
1341 assert_eq!(
1342 res,
1343 CpuOptions {
1344 clusters: vec![
1345 CpuSet::new([0, 2]),
1346 CpuSet::new([1, 3]),
1347 CpuSet::new([4, 5, 6, 7, 12])
1348 ],
1349 ..Default::default()
1350 }
1351 );
1352
1353 #[cfg(target_arch = "x86_64")]
1354 {
1355 let res: CpuOptions = from_key_values("core-types=[atom=[1,3-7],core=[0,2]]").unwrap();
1356 assert_eq!(
1357 res,
1358 CpuOptions {
1359 core_types: Some(CpuCoreType {
1360 atom: CpuSet::new([1, 3, 4, 5, 6, 7]),
1361 core: CpuSet::new([0, 2])
1362 }),
1363 ..Default::default()
1364 }
1365 );
1366 }
1367
1368 let res: CpuOptions = from_key_values("16,clusters=[[0],[4-6],[7]]").unwrap();
1370 assert_eq!(
1371 res,
1372 CpuOptions {
1373 num_cores: Some(16),
1374 clusters: vec![CpuSet::new([0]), CpuSet::new([4, 5, 6]), CpuSet::new([7])],
1375 ..Default::default()
1376 }
1377 );
1378
1379 let res: CpuOptions = from_key_values("clusters=[[0-7],[30-31]],num-cores=32").unwrap();
1380 assert_eq!(
1381 res,
1382 CpuOptions {
1383 num_cores: Some(32),
1384 clusters: vec![CpuSet::new([0, 1, 2, 3, 4, 5, 6, 7]), CpuSet::new([30, 31])],
1385 ..Default::default()
1386 }
1387 );
1388 }
1389
1390 #[test]
1391 fn parse_cpu_set_single() {
1392 assert_eq!(
1393 CpuSet::from_str("123").expect("parse failed"),
1394 CpuSet::new([123])
1395 );
1396 }
1397
1398 #[test]
1399 fn parse_cpu_set_list() {
1400 assert_eq!(
1401 CpuSet::from_str("0,1,2,3").expect("parse failed"),
1402 CpuSet::new([0, 1, 2, 3])
1403 );
1404 }
1405
1406 #[test]
1407 fn parse_cpu_set_range() {
1408 assert_eq!(
1409 CpuSet::from_str("0-3").expect("parse failed"),
1410 CpuSet::new([0, 1, 2, 3])
1411 );
1412 }
1413
1414 #[test]
1415 fn parse_cpu_set_list_of_ranges() {
1416 assert_eq!(
1417 CpuSet::from_str("3-4,7-9,18").expect("parse failed"),
1418 CpuSet::new([3, 4, 7, 8, 9, 18])
1419 );
1420 }
1421
1422 #[test]
1423 fn parse_cpu_set_repeated() {
1424 assert_eq!(
1427 CpuSet::from_str("1,1,1").expect("parse failed"),
1428 CpuSet::new([1, 1, 1])
1429 );
1430 }
1431
1432 #[test]
1433 fn parse_cpu_set_negative() {
1434 CpuSet::from_str("-3").expect_err("parse should have failed");
1436 }
1437
1438 #[test]
1439 fn parse_cpu_set_reverse_range() {
1440 CpuSet::from_str("5-2").expect_err("parse should have failed");
1442 }
1443
1444 #[test]
1445 fn parse_cpu_set_open_range() {
1446 CpuSet::from_str("3-").expect_err("parse should have failed");
1447 }
1448
1449 #[test]
1450 fn parse_cpu_set_extra_comma() {
1451 CpuSet::from_str("0,1,2,").expect_err("parse should have failed");
1452 }
1453
1454 #[test]
1455 fn parse_cpu_affinity_global() {
1456 assert_eq!(
1457 parse_cpu_affinity("0,5-7,9").expect("parse failed"),
1458 VcpuAffinity::Global(CpuSet::new([0, 5, 6, 7, 9])),
1459 );
1460 }
1461
1462 #[test]
1463 fn parse_cpu_affinity_per_vcpu_one_to_one() {
1464 let mut expected_map = BTreeMap::new();
1465 expected_map.insert(0, CpuSet::new([0]));
1466 expected_map.insert(1, CpuSet::new([1]));
1467 expected_map.insert(2, CpuSet::new([2]));
1468 expected_map.insert(3, CpuSet::new([3]));
1469 assert_eq!(
1470 parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
1471 VcpuAffinity::PerVcpu(expected_map),
1472 );
1473 }
1474
1475 #[test]
1476 fn parse_cpu_affinity_per_vcpu_sets() {
1477 let mut expected_map = BTreeMap::new();
1478 expected_map.insert(0, CpuSet::new([0, 1, 2]));
1479 expected_map.insert(1, CpuSet::new([3, 4, 5]));
1480 expected_map.insert(2, CpuSet::new([6, 7, 8]));
1481 assert_eq!(
1482 parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
1483 VcpuAffinity::PerVcpu(expected_map),
1484 );
1485 }
1486
1487 #[test]
1488 fn parse_mem_opts() {
1489 let res: MemOptions = from_key_values("").unwrap();
1490 assert_eq!(res.size, None);
1491
1492 let res: MemOptions = from_key_values("1024").unwrap();
1493 assert_eq!(res.size, Some(1024));
1494
1495 let res: MemOptions = from_key_values("size=0x4000").unwrap();
1496 assert_eq!(res.size, Some(16384));
1497 }
1498
1499 #[test]
1500 fn parse_serial_vaild() {
1501 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1502 .expect("parse should have succeded");
1503 }
1504
1505 #[test]
1506 fn parse_serial_virtio_console_vaild() {
1507 parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
1508 .expect("parse should have succeded");
1509 }
1510
1511 #[test]
1512 fn parse_serial_valid_no_num() {
1513 parse_serial_options("type=syslog").expect("parse should have succeded");
1514 }
1515
1516 #[test]
1517 fn parse_serial_equals_in_value() {
1518 let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
1519 .expect("parse should have succeded");
1520 assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
1521 }
1522
1523 #[test]
1524 fn parse_serial_invalid_type() {
1525 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1526 }
1527
1528 #[test]
1529 fn parse_serial_invalid_num_upper() {
1530 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1531 }
1532
1533 #[test]
1534 fn parse_serial_invalid_num_lower() {
1535 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1536 }
1537
1538 #[test]
1539 fn parse_serial_virtio_console_invalid_num_lower() {
1540 parse_serial_options("type=syslog,hardware=virtio-console,num=0")
1541 .expect_err("parse should have failed");
1542 }
1543
1544 #[test]
1545 fn parse_serial_invalid_num_string() {
1546 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1547 }
1548
1549 #[test]
1550 fn parse_serial_invalid_option() {
1551 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1552 }
1553
1554 #[test]
1555 fn parse_serial_invalid_two_stdin() {
1556 assert!(TryInto::<Config>::try_into(
1557 crate::crosvm::cmdline::RunCommand::from_args(
1558 &[],
1559 &[
1560 "--serial",
1561 "num=1,type=stdout,stdin=true",
1562 "--serial",
1563 "num=2,type=stdout,stdin=true"
1564 ]
1565 )
1566 .unwrap()
1567 )
1568 .is_err())
1569 }
1570
1571 #[test]
1572 fn parse_serial_pci_address_valid_for_virtio() {
1573 let parsed =
1574 parse_serial_options("type=syslog,hardware=virtio-console,pci-address=00:0e.0")
1575 .expect("parse should have succeded");
1576 assert_eq!(
1577 parsed.pci_address,
1578 Some(PciAddress {
1579 bus: 0,
1580 dev: 14,
1581 func: 0
1582 })
1583 );
1584 }
1585
1586 #[test]
1587 fn parse_serial_pci_address_valid_for_legacy_virtio() {
1588 let parsed =
1589 parse_serial_options("type=syslog,hardware=legacy-virtio-console,pci-address=00:0e.0")
1590 .expect("parse should have succeded");
1591 assert_eq!(
1592 parsed.pci_address,
1593 Some(PciAddress {
1594 bus: 0,
1595 dev: 14,
1596 func: 0
1597 })
1598 );
1599 }
1600
1601 #[test]
1602 fn parse_serial_pci_address_failed_for_serial() {
1603 parse_serial_options("type=syslog,hardware=serial,pci-address=00:0e.0")
1604 .expect_err("expected pci-address error for serial hardware");
1605 }
1606
1607 #[test]
1608 fn parse_serial_pci_address_failed_for_debugcon() {
1609 parse_serial_options("type=syslog,hardware=debugcon,pci-address=00:0e.0")
1610 .expect_err("expected pci-address error for debugcon hardware");
1611 }
1612
1613 #[test]
1614 fn parse_battery_valid() {
1615 let bat_config: BatteryConfig = from_key_values("type=goldfish").unwrap();
1616 assert_eq!(bat_config.type_, BatteryType::Goldfish);
1617 }
1618
1619 #[test]
1620 fn parse_battery_valid_no_type() {
1621 let bat_config: BatteryConfig = from_key_values("").unwrap();
1622 assert_eq!(bat_config.type_, BatteryType::Goldfish);
1623 }
1624
1625 #[test]
1626 fn parse_battery_invalid_parameter() {
1627 from_key_values::<BatteryConfig>("tyep=goldfish").expect_err("parse should have failed");
1628 }
1629
1630 #[test]
1631 fn parse_battery_invalid_type_value() {
1632 from_key_values::<BatteryConfig>("type=xxx").expect_err("parse should have failed");
1633 }
1634
1635 #[test]
1636 fn parse_irqchip_kernel() {
1637 let cfg = TryInto::<Config>::try_into(
1638 crate::crosvm::cmdline::RunCommand::from_args(
1639 &[],
1640 &["--irqchip", "kernel", "/dev/null"],
1641 )
1642 .unwrap(),
1643 )
1644 .unwrap();
1645
1646 assert_eq!(
1647 cfg.irq_chip,
1648 Some(IrqChipKind::Kernel {
1649 #[cfg(target_arch = "aarch64")]
1650 allow_vgic_its: false
1651 })
1652 );
1653 }
1654
1655 #[test]
1656 #[cfg(target_arch = "aarch64")]
1657 fn parse_irqchip_kernel_with_its() {
1658 let cfg = TryInto::<Config>::try_into(
1659 crate::crosvm::cmdline::RunCommand::from_args(
1660 &[],
1661 &["--irqchip", "kernel[allow-vgic-its]", "/dev/null"],
1662 )
1663 .unwrap(),
1664 )
1665 .unwrap();
1666
1667 assert_eq!(
1668 cfg.irq_chip,
1669 Some(IrqChipKind::Kernel {
1670 allow_vgic_its: true
1671 })
1672 );
1673 }
1674
1675 #[test]
1676 fn parse_irqchip_split() {
1677 let cfg = TryInto::<Config>::try_into(
1678 crate::crosvm::cmdline::RunCommand::from_args(
1679 &[],
1680 &["--irqchip", "split", "/dev/null"],
1681 )
1682 .unwrap(),
1683 )
1684 .unwrap();
1685
1686 assert_eq!(cfg.irq_chip, Some(IrqChipKind::Split));
1687 }
1688
1689 #[test]
1690 fn parse_irqchip_userspace() {
1691 let cfg = TryInto::<Config>::try_into(
1692 crate::crosvm::cmdline::RunCommand::from_args(
1693 &[],
1694 &["--irqchip", "userspace", "/dev/null"],
1695 )
1696 .unwrap(),
1697 )
1698 .unwrap();
1699
1700 assert_eq!(cfg.irq_chip, Some(IrqChipKind::Userspace));
1701 }
1702
1703 #[test]
1704 fn parse_stub_pci() {
1705 let params = from_key_values::<StubPciParameters>("0000:01:02.3,vendor=0xfffe,device=0xfffd,class=0xffc1c2,subsystem_vendor=0xfffc,subsystem_device=0xfffb,revision=0xa").unwrap();
1706 assert_eq!(params.address.bus, 1);
1707 assert_eq!(params.address.dev, 2);
1708 assert_eq!(params.address.func, 3);
1709 assert_eq!(params.vendor, 0xfffe);
1710 assert_eq!(params.device, 0xfffd);
1711 assert_eq!(params.class.class as u8, PciClassCode::Other as u8);
1712 assert_eq!(params.class.subclass, 0xc1);
1713 assert_eq!(params.class.programming_interface, 0xc2);
1714 assert_eq!(params.subsystem_vendor, 0xfffc);
1715 assert_eq!(params.subsystem_device, 0xfffb);
1716 assert_eq!(params.revision, 0xa);
1717 }
1718
1719 #[test]
1720 fn parse_file_backed_mapping_valid() {
1721 let params = from_key_values::<FileBackedMappingParameters>(
1722 "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,rw,sync",
1723 )
1724 .unwrap();
1725 assert_eq!(params.address, 0x1000);
1726 assert_eq!(params.size, 0x2000);
1727 assert_eq!(params.path, PathBuf::from("/dev/mem"));
1728 assert_eq!(params.offset, 0x3000);
1729 assert!(params.writable);
1730 assert!(params.sync);
1731 }
1732
1733 #[test]
1734 fn parse_file_backed_mapping_incomplete() {
1735 assert!(
1736 from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2000")
1737 .unwrap_err()
1738 .contains("missing field `path`")
1739 );
1740 assert!(
1741 from_key_values::<FileBackedMappingParameters>("size=0x2000,path=/dev/mem")
1742 .unwrap_err()
1743 .contains("missing field `addr`")
1744 );
1745 assert!(
1746 from_key_values::<FileBackedMappingParameters>("addr=0x1000,path=/dev/mem")
1747 .unwrap_err()
1748 .contains("missing field `size`")
1749 );
1750 }
1751
1752 #[test]
1753 fn parse_file_backed_mapping_unaligned_addr() {
1754 let mut params =
1755 from_key_values::<FileBackedMappingParameters>("addr=0x1001,size=0x2000,path=/dev/mem")
1756 .unwrap();
1757 assert!(validate_file_backed_mapping(&mut params)
1758 .unwrap_err()
1759 .contains("aligned"));
1760 }
1761 #[test]
1762 fn parse_file_backed_mapping_unaligned_size() {
1763 let mut params =
1764 from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2001,path=/dev/mem")
1765 .unwrap();
1766 assert!(validate_file_backed_mapping(&mut params)
1767 .unwrap_err()
1768 .contains("aligned"));
1769 }
1770
1771 #[test]
1772 fn parse_file_backed_mapping_align() {
1773 let addr = pagesize() as u64 * 3 + 42;
1774 let size = pagesize() as u64 - 0xf;
1775 let mut params = from_key_values::<FileBackedMappingParameters>(&format!(
1776 "addr={addr},size={size},path=/dev/mem,align",
1777 ))
1778 .unwrap();
1779 assert_eq!(params.address, addr);
1780 assert_eq!(params.size, size);
1781 validate_file_backed_mapping(&mut params).unwrap();
1782 assert_eq!(params.address, pagesize() as u64 * 3);
1783 assert_eq!(params.size, pagesize() as u64 * 2);
1784 }
1785
1786 #[test]
1787 fn parse_fw_cfg_valid_path() {
1788 let cfg = TryInto::<Config>::try_into(
1789 crate::crosvm::cmdline::RunCommand::from_args(
1790 &[],
1791 &["--fw-cfg", "name=bar,path=data.bin", "/dev/null"],
1792 )
1793 .unwrap(),
1794 )
1795 .unwrap();
1796
1797 assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1798 assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1799 assert_eq!(cfg.fw_cfg_parameters[0].string, None);
1800 assert_eq!(cfg.fw_cfg_parameters[0].path, Some("data.bin".into()));
1801 }
1802
1803 #[test]
1804 fn parse_fw_cfg_valid_string() {
1805 let cfg = TryInto::<Config>::try_into(
1806 crate::crosvm::cmdline::RunCommand::from_args(
1807 &[],
1808 &["--fw-cfg", "name=bar,string=foo", "/dev/null"],
1809 )
1810 .unwrap(),
1811 )
1812 .unwrap();
1813
1814 assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1815 assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1816 assert_eq!(cfg.fw_cfg_parameters[0].string, Some("foo".to_string()));
1817 assert_eq!(cfg.fw_cfg_parameters[0].path, None);
1818 }
1819
1820 #[test]
1821 fn parse_dtbo() {
1822 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1823 &[],
1824 &[
1825 "--device-tree-overlay",
1826 "/path/to/dtbo1",
1827 "--device-tree-overlay",
1828 "/path/to/dtbo2",
1829 "/dev/null",
1830 ],
1831 )
1832 .unwrap()
1833 .try_into()
1834 .unwrap();
1835
1836 assert_eq!(cfg.device_tree_overlay.len(), 2);
1837 for (opt, p) in cfg
1838 .device_tree_overlay
1839 .into_iter()
1840 .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1841 {
1842 assert_eq!(opt.path, PathBuf::from(p));
1843 assert!(!opt.filter_devs);
1844 }
1845 }
1846
1847 #[test]
1848 #[cfg(any(target_os = "android", target_os = "linux"))]
1849 fn parse_dtbo_filtered() {
1850 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1851 &[],
1852 &[
1853 "--vfio",
1854 "/path/to/dev,dt-symbol=mydev",
1855 "--device-tree-overlay",
1856 "/path/to/dtbo1,filter",
1857 "--device-tree-overlay",
1858 "/path/to/dtbo2,filter",
1859 "/dev/null",
1860 ],
1861 )
1862 .unwrap()
1863 .try_into()
1864 .unwrap();
1865
1866 assert_eq!(cfg.device_tree_overlay.len(), 2);
1867 for (opt, p) in cfg
1868 .device_tree_overlay
1869 .into_iter()
1870 .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1871 {
1872 assert_eq!(opt.path, PathBuf::from(p));
1873 assert!(opt.filter_devs);
1874 }
1875
1876 assert!(TryInto::<Config>::try_into(
1877 crate::crosvm::cmdline::RunCommand::from_args(
1878 &[],
1879 &["--device-tree-overlay", "/path/to/dtbo,filter", "/dev/null"],
1880 )
1881 .unwrap(),
1882 )
1883 .is_err());
1884 }
1885
1886 #[test]
1887 fn parse_fw_cfg_invalid_no_name() {
1888 assert!(
1889 crate::crosvm::cmdline::RunCommand::from_args(&[], &["--fw-cfg", "string=foo",])
1890 .is_err()
1891 );
1892 }
1893
1894 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
1895 #[test]
1896 fn parse_video() {
1897 use devices::virtio::device_constants::video::VideoBackendType;
1898
1899 #[cfg(feature = "libvda")]
1900 {
1901 let params: VideoDeviceConfig = from_key_values("libvda").unwrap();
1902 assert_eq!(params.backend, VideoBackendType::Libvda);
1903
1904 let params: VideoDeviceConfig = from_key_values("libvda-vd").unwrap();
1905 assert_eq!(params.backend, VideoBackendType::LibvdaVd);
1906 }
1907
1908 #[cfg(feature = "ffmpeg")]
1909 {
1910 let params: VideoDeviceConfig = from_key_values("ffmpeg").unwrap();
1911 assert_eq!(params.backend, VideoBackendType::Ffmpeg);
1912 }
1913
1914 #[cfg(feature = "vaapi")]
1915 {
1916 let params: VideoDeviceConfig = from_key_values("vaapi").unwrap();
1917 assert_eq!(params.backend, VideoBackendType::Vaapi);
1918 }
1919 }
1920
1921 #[test]
1922 fn parse_vhost_user_option_all_device_types() {
1923 fn test_device_type(type_string: &str, type_: DeviceType) {
1924 let vhost_user_arg = format!("{type_string},socket=sock");
1925
1926 let cfg = TryInto::<Config>::try_into(
1927 crate::crosvm::cmdline::RunCommand::from_args(
1928 &[],
1929 &["--vhost-user", &vhost_user_arg, "/dev/null"],
1930 )
1931 .unwrap(),
1932 )
1933 .unwrap();
1934
1935 assert_eq!(cfg.vhost_user.len(), 1);
1936 let vu = &cfg.vhost_user[0];
1937 assert_eq!(vu.type_, type_);
1938 }
1939
1940 test_device_type("net", DeviceType::Net);
1941 test_device_type("block", DeviceType::Block);
1942 test_device_type("console", DeviceType::Console);
1943 test_device_type("rng", DeviceType::Rng);
1944 test_device_type("balloon", DeviceType::Balloon);
1945 test_device_type("scsi", DeviceType::Scsi);
1946 test_device_type("9p", DeviceType::P9);
1947 test_device_type("gpu", DeviceType::Gpu);
1948 test_device_type("input", DeviceType::Input);
1949 test_device_type("vsock", DeviceType::Vsock);
1950 test_device_type("iommu", DeviceType::Iommu);
1951 test_device_type("sound", DeviceType::Sound);
1952 test_device_type("fs", DeviceType::Fs);
1953 test_device_type("pmem", DeviceType::Pmem);
1954 test_device_type("mac80211-hwsim", DeviceType::Mac80211HwSim);
1955 test_device_type("video-encoder", DeviceType::VideoEncoder);
1956 test_device_type("video-decoder", DeviceType::VideoDecoder);
1957 test_device_type("scmi", DeviceType::Scmi);
1958 test_device_type("wl", DeviceType::Wl);
1959 test_device_type("tpm", DeviceType::Tpm);
1960 test_device_type("pvclock", DeviceType::Pvclock);
1961 }
1962
1963 #[cfg(target_arch = "x86_64")]
1964 #[test]
1965 fn parse_smbios_uuid() {
1966 let opt: SmbiosOptions =
1967 from_key_values("uuid=12e474af-2cc1-49d1-b0e5-d03a3e03ca03").unwrap();
1968 assert_eq!(
1969 opt.uuid,
1970 Some(uuid!("12e474af-2cc1-49d1-b0e5-d03a3e03ca03"))
1971 );
1972
1973 from_key_values::<SmbiosOptions>("uuid=zzzz").expect_err("expected error parsing uuid");
1974 }
1975
1976 #[test]
1977 fn parse_touch_legacy() {
1978 let cfg = TryInto::<Config>::try_into(
1979 crate::crosvm::cmdline::RunCommand::from_args(
1980 &[],
1981 &["--multi-touch", "my_socket:867:5309", "bzImage"],
1982 )
1983 .unwrap(),
1984 )
1985 .unwrap();
1986
1987 assert_eq!(cfg.virtio_input.len(), 1);
1988 let multi_touch = cfg
1989 .virtio_input
1990 .iter()
1991 .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
1992 .unwrap();
1993 assert_eq!(
1994 *multi_touch,
1995 InputDeviceOption::MultiTouch {
1996 path: PathBuf::from("my_socket"),
1997 width: Some(867),
1998 height: Some(5309),
1999 name: None
2000 }
2001 );
2002 }
2003
2004 #[test]
2005 fn parse_touch() {
2006 let cfg = TryInto::<Config>::try_into(
2007 crate::crosvm::cmdline::RunCommand::from_args(
2008 &[],
2009 &["--multi-touch", r"C:\path,width=867,height=5309", "bzImage"],
2010 )
2011 .unwrap(),
2012 )
2013 .unwrap();
2014
2015 assert_eq!(cfg.virtio_input.len(), 1);
2016 let multi_touch = cfg
2017 .virtio_input
2018 .iter()
2019 .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2020 .unwrap();
2021 assert_eq!(
2022 *multi_touch,
2023 InputDeviceOption::MultiTouch {
2024 path: PathBuf::from(r"C:\path"),
2025 width: Some(867),
2026 height: Some(5309),
2027 name: None
2028 }
2029 );
2030 }
2031
2032 #[test]
2033 fn single_touch_spec_and_track_pad_spec_default_size() {
2034 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2035 &[],
2036 &[
2037 "--single-touch",
2038 "/dev/single-touch-test",
2039 "--trackpad",
2040 "/dev/single-touch-test",
2041 "/dev/null",
2042 ],
2043 )
2044 .unwrap()
2045 .try_into()
2046 .unwrap();
2047
2048 let single_touch = config
2049 .virtio_input
2050 .iter()
2051 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2052 .unwrap();
2053 let trackpad = config
2054 .virtio_input
2055 .iter()
2056 .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2057 .unwrap();
2058
2059 assert_eq!(
2060 *single_touch,
2061 InputDeviceOption::SingleTouch {
2062 path: PathBuf::from("/dev/single-touch-test"),
2063 width: None,
2064 height: None,
2065 name: None
2066 }
2067 );
2068 assert_eq!(
2069 *trackpad,
2070 InputDeviceOption::Trackpad {
2071 path: PathBuf::from("/dev/single-touch-test"),
2072 width: None,
2073 height: None,
2074 name: None
2075 }
2076 );
2077 }
2078
2079 #[cfg(feature = "gpu")]
2080 #[test]
2081 fn single_touch_spec_default_size_from_gpu() {
2082 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2083 &[],
2084 &[
2085 "--single-touch",
2086 "/dev/single-touch-test",
2087 "--gpu",
2088 "width=1024,height=768",
2089 "/dev/null",
2090 ],
2091 )
2092 .unwrap()
2093 .try_into()
2094 .unwrap();
2095
2096 let single_touch = config
2097 .virtio_input
2098 .iter()
2099 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2100 .unwrap();
2101 assert_eq!(
2102 *single_touch,
2103 InputDeviceOption::SingleTouch {
2104 path: PathBuf::from("/dev/single-touch-test"),
2105 width: None,
2106 height: None,
2107 name: None
2108 }
2109 );
2110
2111 assert_eq!(config.display_input_width, Some(1024));
2112 assert_eq!(config.display_input_height, Some(768));
2113 }
2114
2115 #[test]
2116 fn single_touch_spec_and_track_pad_spec_with_size() {
2117 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2118 &[],
2119 &[
2120 "--single-touch",
2121 "/dev/single-touch-test:12345:54321",
2122 "--trackpad",
2123 "/dev/single-touch-test:5678:9876",
2124 "/dev/null",
2125 ],
2126 )
2127 .unwrap()
2128 .try_into()
2129 .unwrap();
2130
2131 let single_touch = config
2132 .virtio_input
2133 .iter()
2134 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2135 .unwrap();
2136 let trackpad = config
2137 .virtio_input
2138 .iter()
2139 .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2140 .unwrap();
2141
2142 assert_eq!(
2143 *single_touch,
2144 InputDeviceOption::SingleTouch {
2145 path: PathBuf::from("/dev/single-touch-test"),
2146 width: Some(12345),
2147 height: Some(54321),
2148 name: None
2149 }
2150 );
2151 assert_eq!(
2152 *trackpad,
2153 InputDeviceOption::Trackpad {
2154 path: PathBuf::from("/dev/single-touch-test"),
2155 width: Some(5678),
2156 height: Some(9876),
2157 name: None
2158 }
2159 );
2160 }
2161
2162 #[cfg(feature = "gpu")]
2163 #[test]
2164 fn single_touch_spec_with_size_independent_from_gpu() {
2165 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2166 &[],
2167 &[
2168 "--single-touch",
2169 "/dev/single-touch-test:12345:54321",
2170 "--gpu",
2171 "width=1024,height=768",
2172 "/dev/null",
2173 ],
2174 )
2175 .unwrap()
2176 .try_into()
2177 .unwrap();
2178
2179 let single_touch = config
2180 .virtio_input
2181 .iter()
2182 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2183 .unwrap();
2184
2185 assert_eq!(
2186 *single_touch,
2187 InputDeviceOption::SingleTouch {
2188 path: PathBuf::from("/dev/single-touch-test"),
2189 width: Some(12345),
2190 height: Some(54321),
2191 name: None
2192 }
2193 );
2194
2195 assert_eq!(config.display_input_width, Some(1024));
2196 assert_eq!(config.display_input_height, Some(768));
2197 }
2198
2199 #[test]
2200 fn virtio_switches() {
2201 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2202 &[],
2203 &["--switches", "/dev/switches-test", "/dev/null"],
2204 )
2205 .unwrap()
2206 .try_into()
2207 .unwrap();
2208
2209 let switches = config
2210 .virtio_input
2211 .iter()
2212 .find(|input| matches!(input, InputDeviceOption::Switches { .. }))
2213 .unwrap();
2214
2215 assert_eq!(
2216 *switches,
2217 InputDeviceOption::Switches {
2218 path: PathBuf::from("/dev/switches-test")
2219 }
2220 );
2221 }
2222
2223 #[test]
2224 fn virtio_rotary() {
2225 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2226 &[],
2227 &["--rotary", "/dev/rotary-test", "/dev/null"],
2228 )
2229 .unwrap()
2230 .try_into()
2231 .unwrap();
2232
2233 let rotary = config
2234 .virtio_input
2235 .iter()
2236 .find(|input| matches!(input, InputDeviceOption::Rotary { .. }))
2237 .unwrap();
2238
2239 assert_eq!(
2240 *rotary,
2241 InputDeviceOption::Rotary {
2242 path: PathBuf::from("/dev/rotary-test")
2243 }
2244 );
2245 }
2246
2247 #[cfg(target_arch = "aarch64")]
2248 #[test]
2249 fn parse_pci_cam() {
2250 assert_eq!(
2251 config_from_args(&["--pci", "cam=[start=0x123]", "/dev/null"]).pci_config,
2252 PciConfig {
2253 cam: Some(arch::MemoryRegionConfig {
2254 start: 0x123,
2255 size: None,
2256 }),
2257 ..PciConfig::default()
2258 }
2259 );
2260 assert_eq!(
2261 config_from_args(&["--pci", "cam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2262 PciConfig {
2263 cam: Some(arch::MemoryRegionConfig {
2264 start: 0x123,
2265 size: Some(0x456),
2266 }),
2267 ..PciConfig::default()
2268 },
2269 );
2270 }
2271
2272 #[cfg(target_arch = "x86_64")]
2273 #[test]
2274 fn parse_pci_ecam() {
2275 assert_eq!(
2276 config_from_args(&["--pci", "ecam=[start=0x123]", "/dev/null"]).pci_config,
2277 PciConfig {
2278 ecam: Some(arch::MemoryRegionConfig {
2279 start: 0x123,
2280 size: None,
2281 }),
2282 ..PciConfig::default()
2283 }
2284 );
2285 assert_eq!(
2286 config_from_args(&["--pci", "ecam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2287 PciConfig {
2288 ecam: Some(arch::MemoryRegionConfig {
2289 start: 0x123,
2290 size: Some(0x456),
2291 }),
2292 ..PciConfig::default()
2293 },
2294 );
2295 }
2296
2297 #[test]
2298 fn parse_pci_mem() {
2299 assert_eq!(
2300 config_from_args(&["--pci", "mem=[start=0x123]", "/dev/null"]).pci_config,
2301 PciConfig {
2302 mem: Some(arch::MemoryRegionConfig {
2303 start: 0x123,
2304 size: None,
2305 }),
2306 ..PciConfig::default()
2307 }
2308 );
2309 assert_eq!(
2310 config_from_args(&["--pci", "mem=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2311 PciConfig {
2312 mem: Some(arch::MemoryRegionConfig {
2313 start: 0x123,
2314 size: Some(0x456),
2315 }),
2316 ..PciConfig::default()
2317 },
2318 );
2319 }
2320
2321 #[test]
2322 fn parse_pmem_options_missing_path() {
2323 assert!(from_key_values::<PmemOption>("")
2324 .unwrap_err()
2325 .contains("missing field `path`"));
2326 }
2327
2328 #[test]
2329 fn parse_pmem_options_default_values() {
2330 let pmem = from_key_values::<PmemOption>("/path/to/disk.img").unwrap();
2331 assert_eq!(
2332 pmem,
2333 PmemOption {
2334 path: "/path/to/disk.img".into(),
2335 ro: false,
2336 root: false,
2337 vma_size: None,
2338 swap_interval: None,
2339 }
2340 );
2341 }
2342
2343 #[test]
2344 fn parse_pmem_options_virtual_swap() {
2345 let pmem =
2346 from_key_values::<PmemOption>("virtual_path,vma-size=12345,swap-interval-ms=1000")
2347 .unwrap();
2348 assert_eq!(
2349 pmem,
2350 PmemOption {
2351 path: "virtual_path".into(),
2352 ro: false,
2353 root: false,
2354 vma_size: Some(12345),
2355 swap_interval: Some(Duration::new(1, 0)),
2356 }
2357 );
2358 }
2359
2360 #[test]
2361 fn validate_pmem_missing_virtual_swap_param() {
2362 let pmem = from_key_values::<PmemOption>("virtual_path,swap-interval-ms=1000").unwrap();
2363 assert!(validate_pmem(&pmem)
2364 .unwrap_err()
2365 .contains("vma-size and swap-interval parameters must be specified together"));
2366 }
2367
2368 #[test]
2369 fn validate_pmem_read_only_virtual_swap() {
2370 let pmem = from_key_values::<PmemOption>(
2371 "virtual_path,ro=true,vma-size=12345,swap-interval-ms=1000",
2372 )
2373 .unwrap();
2374 assert!(validate_pmem(&pmem)
2375 .unwrap_err()
2376 .contains("swap-interval parameter can only be set for writable pmem device"));
2377 }
2378}