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