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 pub acpi_tables: Vec<PathBuf>,
579 #[cfg(feature = "android_display")]
580 pub android_display_service: Option<String>,
581 pub android_fstab: Option<PathBuf>,
582 pub async_executor: Option<ExecutorKind>,
583 #[cfg(feature = "balloon")]
584 pub balloon: bool,
585 #[cfg(feature = "balloon")]
586 pub balloon_bias: i64,
587 #[cfg(feature = "balloon")]
588 pub balloon_control: Option<PathBuf>,
589 #[cfg(feature = "balloon")]
590 pub balloon_page_reporting: bool,
591 #[cfg(feature = "balloon")]
592 pub balloon_ws_num_bins: u8,
593 #[cfg(feature = "balloon")]
594 pub balloon_ws_reporting: bool,
595 pub battery_config: Option<BatteryConfig>,
596 #[cfg(windows)]
597 pub block_control_tube: Vec<Tube>,
598 #[cfg(windows)]
599 pub block_vhost_user_tube: Vec<Tube>,
600 #[cfg(any(target_os = "android", target_os = "linux"))]
601 pub boost_uclamp: bool,
602 pub boot_cpu: usize,
603 #[cfg(target_arch = "x86_64")]
604 pub break_linux_pci_config_io: bool,
605 #[cfg(windows)]
606 pub broker_shutdown_event: Option<Event>,
607 #[cfg(target_arch = "x86_64")]
608 pub bus_lock_ratelimit: u64,
609 #[cfg(any(target_os = "android", target_os = "linux"))]
610 pub coiommu_param: Option<devices::CoIommuParameters>,
611 pub core_scheduling: bool,
612 pub cpu_capacity: BTreeMap<usize, u32>, pub cpu_clusters: Vec<CpuSet>,
614 pub cpu_freq_domains: Vec<CpuSet>,
615 #[cfg(all(
616 target_arch = "aarch64",
617 any(target_os = "android", target_os = "linux")
618 ))]
619 pub cpu_frequencies_khz: BTreeMap<usize, Vec<u32>>, #[cfg(all(
621 target_arch = "aarch64",
622 any(target_os = "android", target_os = "linux")
623 ))]
624 pub cpu_ipc_ratio: BTreeMap<usize, u32>, #[cfg(feature = "crash-report")]
626 pub crash_pipe_name: Option<String>,
627 #[cfg(feature = "crash-report")]
628 pub crash_report_uuid: Option<String>,
629 pub delay_rt: bool,
630 pub dev_pm: Option<DevicePowerManagerConfig>,
631 pub device_tree_overlay: Vec<DtboOption>,
632 pub disable_virtio_intx: bool,
633 pub disks: Vec<DiskOption>,
634 pub display_input_height: Option<u32>,
635 pub display_input_width: Option<u32>,
636 pub display_window_keyboard: bool,
637 pub display_window_mouse: bool,
638 pub dump_device_tree_blob: Option<PathBuf>,
639 pub dynamic_power_coefficient: BTreeMap<usize, u32>,
640 pub enable_fw_cfg: bool,
641 pub enable_hwp: bool,
642 pub executable_path: Option<Executable>,
643 #[cfg(windows)]
644 pub exit_stats: bool,
645 pub fdt_position: Option<FdtPosition>,
646 #[cfg(all(target_os = "android", target_arch = "aarch64"))]
647 pub ffa: Option<FfaConfig>,
648 pub file_backed_mappings_mmio: Vec<FileBackedMappingParameters>,
649 pub file_backed_mappings_ram: Vec<FileBackedMappingParameters>,
650 pub force_calibrated_tsc_leaf: bool,
651 pub force_disable_readonly_mem: bool,
652 pub force_s2idle: bool,
653 pub fw_cfg_parameters: Vec<FwCfgParameters>,
654 #[cfg(feature = "gdb")]
655 pub gdb: Option<u32>,
656 #[cfg(all(windows, feature = "gpu"))]
657 pub gpu_backend_config: Option<GpuBackendConfig>,
658 #[cfg(all(unix, feature = "gpu"))]
659 pub gpu_cgroup_path: Option<PathBuf>,
660 #[cfg(feature = "gpu")]
661 pub gpu_parameters: Option<GpuParameters>,
662 #[cfg(all(unix, feature = "gpu"))]
663 pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
664 #[cfg(all(unix, feature = "gpu"))]
665 pub gpu_server_cgroup_path: Option<PathBuf>,
666 #[cfg(all(windows, feature = "gpu"))]
667 pub gpu_vmm_config: Option<GpuVmmConfig>,
668 pub host_cpu_topology: bool,
669 #[cfg(windows)]
670 pub host_guid: Option<String>,
671 pub hugepages: bool,
672 pub hypervisor: Option<HypervisorKind>,
673 #[cfg(feature = "balloon")]
674 pub init_memory: Option<u64>,
675 pub initrd_path: Option<PathBuf>,
676 #[cfg(all(windows, feature = "gpu"))]
677 pub input_event_split_config: Option<InputEventSplitConfig>,
678 pub irq_chip: Option<IrqChipKind>,
679 pub itmt: bool,
680 pub jail_config: Option<JailConfig>,
681 #[cfg(windows)]
682 pub kernel_log_file: Option<String>,
683 #[cfg(any(target_os = "android", target_os = "linux"))]
684 pub lock_guest_memory: bool,
685 #[cfg(windows)]
686 pub log_file: Option<String>,
687 #[cfg(windows)]
688 pub logs_directory: Option<String>,
689 #[cfg(all(feature = "media", feature = "video-decoder"))]
690 pub media_decoder: Vec<VideoDeviceConfig>,
691 pub memory: Option<u64>,
692 pub memory_file: Option<PathBuf>,
693 pub mmio_address_ranges: Vec<AddressRange>,
694 #[cfg(target_arch = "aarch64")]
695 pub mte: bool,
696 pub name: Option<String>,
697 #[cfg(feature = "net")]
698 pub net: Vec<NetParameters>,
699 #[cfg(windows)]
700 pub net_vhost_user_tube: Option<Tube>,
701 pub no_i8042: bool,
702 pub no_pmu: bool,
703 pub no_rtc: bool,
704 pub no_smt: bool,
705 pub params: Vec<String>,
706 pub pci_config: PciConfig,
707 #[cfg(feature = "pci-hotplug")]
708 pub pci_hotplug_slots: Option<u8>,
709 pub per_vm_core_scheduling: bool,
710 pub pflash_parameters: Option<PflashParameters>,
711 #[cfg(any(target_os = "android", target_os = "linux"))]
712 pub pmem_ext2: Vec<crate::crosvm::sys::config::PmemExt2Option>,
713 pub pmems: Vec<PmemOption>,
714 #[cfg(feature = "process-invariants")]
715 pub process_invariants_data_handle: Option<u64>,
716 #[cfg(feature = "process-invariants")]
717 pub process_invariants_data_size: Option<usize>,
718 #[cfg(windows)]
719 pub product_channel: Option<String>,
720 #[cfg(windows)]
721 pub product_name: Option<String>,
722 #[cfg(windows)]
723 pub product_version: Option<String>,
724 pub protection_type: ProtectionType,
725 pub pstore: Option<Pstore>,
726 #[cfg(feature = "pvclock")]
727 pub pvclock: bool,
728 pub pvm_fw: Option<PathBuf>,
730 pub restore_path: Option<PathBuf>,
731 pub rng: bool,
732 pub rt_cpus: CpuSet,
733 pub scsis: Vec<ScsiOption>,
734 #[serde(with = "serde_serial_params")]
735 pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
736 #[cfg(windows)]
737 pub service_pipe_name: Option<String>,
738 #[cfg(any(target_os = "android", target_os = "linux"))]
739 #[serde(skip)]
740 pub shared_dirs: Vec<SharedDir>,
741 #[cfg(feature = "media")]
742 pub simple_media_device: bool,
743 #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
744 pub slirp_capture_file: Option<String>,
745 #[cfg(target_arch = "x86_64")]
746 pub smbios: SmbiosOptions,
747 pub smccc_trng: bool,
748 #[cfg(all(windows, feature = "audio"))]
749 pub snd_split_configs: Vec<SndSplitConfig>,
750 pub socket_path: Option<PathBuf>,
751 #[cfg(feature = "audio")]
752 pub sound: Option<PathBuf>,
753 pub stub_pci_devices: Vec<StubPciParameters>,
754 pub suspended: bool,
755 #[cfg(target_arch = "aarch64")]
756 pub sve: Option<SveConfig>,
757 pub swap_dir: Option<PathBuf>,
758 pub swiotlb: Option<u64>,
759 #[cfg(target_os = "android")]
760 pub task_profiles: Vec<String>,
761 #[cfg(any(target_os = "android", target_os = "linux"))]
762 pub unmap_guest_memory_on_fork: bool,
763 pub usb: bool,
764 #[cfg(any(target_os = "android", target_os = "linux"))]
765 #[cfg(feature = "media")]
766 pub v4l2_proxy: Vec<PathBuf>,
767 pub vcpu_affinity: Option<VcpuAffinity>,
768 pub vcpu_cgroup_path: Option<PathBuf>,
769 pub vcpu_count: Option<usize>,
770 #[cfg(target_arch = "x86_64")]
771 pub vcpu_hybrid_type: BTreeMap<usize, CpuHybridType>, #[cfg(any(target_os = "android", target_os = "linux"))]
773 pub vfio: Vec<super::sys::config::VfioOption>,
774 #[cfg(any(target_os = "android", target_os = "linux"))]
775 pub vfio_isolate_hotplug: bool,
776 #[cfg(any(target_os = "android", target_os = "linux"))]
777 pub vfio_platform_pm: bool,
778 #[cfg(any(target_os = "android", target_os = "linux"))]
779 #[cfg(target_arch = "aarch64")]
780 pub vhost_scmi: bool,
781 #[cfg(any(target_os = "android", target_os = "linux"))]
782 #[cfg(target_arch = "aarch64")]
783 pub vhost_scmi_device: PathBuf,
784 pub vhost_user: Vec<VhostUserFrontendOption>,
785 pub vhost_user_connect_timeout_ms: Option<u64>,
786 #[cfg(feature = "video-decoder")]
787 pub video_dec: Vec<VideoDeviceConfig>,
788 #[cfg(feature = "video-encoder")]
789 pub video_enc: Vec<VideoDeviceConfig>,
790 #[cfg(all(
791 target_arch = "aarch64",
792 any(target_os = "android", target_os = "linux")
793 ))]
794 pub virt_cpufreq: bool,
795 pub virt_cpufreq_v2: bool,
796 pub virtio_input: Vec<InputDeviceOption>,
797 #[cfg(feature = "audio")]
798 #[serde(skip)]
799 pub virtio_snds: Vec<SndParameters>,
800 pub vsock: Option<VsockConfig>,
801 #[cfg(feature = "vtpm")]
802 pub vtpm_proxy: bool,
803 pub wayland_socket_paths: BTreeMap<String, PathBuf>,
804 #[cfg(all(windows, feature = "gpu"))]
805 pub window_procedure_thread_split_config: Option<WindowProcedureThreadSplitConfig>,
806 pub x_display: Option<String>,
807}
808
809impl Default for Config {
810 fn default() -> Config {
811 Config {
812 acpi_tables: Vec::new(),
813 #[cfg(feature = "android_display")]
814 android_display_service: None,
815 android_fstab: None,
816 async_executor: None,
817 #[cfg(feature = "balloon")]
818 balloon: true,
819 #[cfg(feature = "balloon")]
820 balloon_bias: 0,
821 #[cfg(feature = "balloon")]
822 balloon_control: None,
823 #[cfg(feature = "balloon")]
824 balloon_page_reporting: false,
825 #[cfg(feature = "balloon")]
826 balloon_ws_num_bins: VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS,
827 #[cfg(feature = "balloon")]
828 balloon_ws_reporting: false,
829 battery_config: None,
830 boot_cpu: 0,
831 #[cfg(windows)]
832 block_control_tube: Vec::new(),
833 #[cfg(windows)]
834 block_vhost_user_tube: Vec::new(),
835 #[cfg(target_arch = "x86_64")]
836 break_linux_pci_config_io: false,
837 #[cfg(windows)]
838 broker_shutdown_event: None,
839 #[cfg(target_arch = "x86_64")]
840 bus_lock_ratelimit: 0,
841 #[cfg(any(target_os = "android", target_os = "linux"))]
842 coiommu_param: None,
843 core_scheduling: true,
844 #[cfg(feature = "crash-report")]
845 crash_pipe_name: None,
846 #[cfg(feature = "crash-report")]
847 crash_report_uuid: None,
848 cpu_capacity: BTreeMap::new(),
849 cpu_clusters: Vec::new(),
850 #[cfg(all(
851 target_arch = "aarch64",
852 any(target_os = "android", target_os = "linux")
853 ))]
854 cpu_frequencies_khz: BTreeMap::new(),
855 cpu_freq_domains: Vec::new(),
856 #[cfg(all(
857 target_arch = "aarch64",
858 any(target_os = "android", target_os = "linux")
859 ))]
860 cpu_ipc_ratio: BTreeMap::new(),
861 delay_rt: false,
862 device_tree_overlay: Vec::new(),
863 dev_pm: None,
864 disks: Vec::new(),
865 disable_virtio_intx: false,
866 display_input_height: None,
867 display_input_width: None,
868 display_window_keyboard: false,
869 display_window_mouse: false,
870 dump_device_tree_blob: None,
871 dynamic_power_coefficient: BTreeMap::new(),
872 enable_fw_cfg: false,
873 enable_hwp: false,
874 executable_path: None,
875 #[cfg(windows)]
876 exit_stats: false,
877 fdt_position: None,
878 #[cfg(all(target_os = "android", target_arch = "aarch64"))]
879 ffa: None,
880 file_backed_mappings_mmio: Vec::new(),
881 file_backed_mappings_ram: Vec::new(),
882 force_calibrated_tsc_leaf: false,
883 force_disable_readonly_mem: false,
884 force_s2idle: false,
885 fw_cfg_parameters: Vec::new(),
886 #[cfg(feature = "gdb")]
887 gdb: None,
888 #[cfg(all(windows, feature = "gpu"))]
889 gpu_backend_config: None,
890 #[cfg(feature = "gpu")]
891 gpu_parameters: None,
892 #[cfg(all(unix, feature = "gpu"))]
893 gpu_render_server_parameters: None,
894 #[cfg(all(unix, feature = "gpu"))]
895 gpu_cgroup_path: None,
896 #[cfg(all(unix, feature = "gpu"))]
897 gpu_server_cgroup_path: None,
898 #[cfg(all(windows, feature = "gpu"))]
899 gpu_vmm_config: None,
900 host_cpu_topology: false,
901 #[cfg(windows)]
902 host_guid: None,
903 #[cfg(windows)]
904 product_version: None,
905 #[cfg(windows)]
906 product_channel: None,
907 hugepages: false,
908 hypervisor: None,
909 #[cfg(feature = "balloon")]
910 init_memory: None,
911 initrd_path: None,
912 #[cfg(all(windows, feature = "gpu"))]
913 input_event_split_config: None,
914 irq_chip: None,
915 itmt: false,
916 jail_config: if !cfg!(feature = "default-no-sandbox") {
917 Some(Default::default())
918 } else {
919 None
920 },
921 #[cfg(windows)]
922 kernel_log_file: None,
923 #[cfg(any(target_os = "android", target_os = "linux"))]
924 lock_guest_memory: false,
925 #[cfg(windows)]
926 log_file: None,
927 #[cfg(windows)]
928 logs_directory: None,
929 #[cfg(any(target_os = "android", target_os = "linux"))]
930 boost_uclamp: false,
931 #[cfg(all(feature = "media", feature = "video-decoder"))]
932 media_decoder: Default::default(),
933 memory: None,
934 memory_file: None,
935 mmio_address_ranges: Vec::new(),
936 #[cfg(target_arch = "aarch64")]
937 mte: false,
938 name: None,
939 #[cfg(feature = "net")]
940 net: Vec::new(),
941 #[cfg(windows)]
942 net_vhost_user_tube: None,
943 no_i8042: false,
944 no_pmu: false,
945 no_rtc: false,
946 no_smt: false,
947 params: Vec::new(),
948 pci_config: Default::default(),
949 #[cfg(feature = "pci-hotplug")]
950 pci_hotplug_slots: None,
951 per_vm_core_scheduling: false,
952 pflash_parameters: None,
953 #[cfg(any(target_os = "android", target_os = "linux"))]
954 pmem_ext2: Vec::new(),
955 pmems: Vec::new(),
956 #[cfg(feature = "process-invariants")]
957 process_invariants_data_handle: None,
958 #[cfg(feature = "process-invariants")]
959 process_invariants_data_size: None,
960 #[cfg(windows)]
961 product_name: None,
962 protection_type: ProtectionType::Unprotected,
963 pstore: None,
964 #[cfg(feature = "pvclock")]
965 pvclock: false,
966 pvm_fw: None,
967 restore_path: None,
968 rng: true,
969 rt_cpus: Default::default(),
970 serial_parameters: BTreeMap::new(),
971 scsis: Vec::new(),
972 #[cfg(windows)]
973 service_pipe_name: None,
974 #[cfg(any(target_os = "android", target_os = "linux"))]
975 shared_dirs: Vec::new(),
976 #[cfg(feature = "media")]
977 simple_media_device: Default::default(),
978 #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
979 slirp_capture_file: None,
980 #[cfg(target_arch = "x86_64")]
981 smbios: SmbiosOptions::default(),
982 smccc_trng: false,
983 #[cfg(all(windows, feature = "audio"))]
984 snd_split_configs: Vec::new(),
985 socket_path: None,
986 #[cfg(feature = "audio")]
987 sound: None,
988 stub_pci_devices: Vec::new(),
989 suspended: false,
990 #[cfg(target_arch = "aarch64")]
991 sve: None,
992 swap_dir: None,
993 swiotlb: None,
994 #[cfg(target_os = "android")]
995 task_profiles: Vec::new(),
996 #[cfg(any(target_os = "android", target_os = "linux"))]
997 unmap_guest_memory_on_fork: false,
998 usb: true,
999 vcpu_affinity: None,
1000 vcpu_cgroup_path: None,
1001 vcpu_count: None,
1002 #[cfg(target_arch = "x86_64")]
1003 vcpu_hybrid_type: BTreeMap::new(),
1004 #[cfg(any(target_os = "android", target_os = "linux"))]
1005 vfio: Vec::new(),
1006 #[cfg(any(target_os = "android", target_os = "linux"))]
1007 vfio_isolate_hotplug: false,
1008 #[cfg(any(target_os = "android", target_os = "linux"))]
1009 vfio_platform_pm: false,
1010 #[cfg(any(target_os = "android", target_os = "linux"))]
1011 #[cfg(target_arch = "aarch64")]
1012 vhost_scmi: false,
1013 #[cfg(any(target_os = "android", target_os = "linux"))]
1014 #[cfg(target_arch = "aarch64")]
1015 vhost_scmi_device: PathBuf::from(VHOST_SCMI_PATH),
1016 vhost_user: Vec::new(),
1017 vhost_user_connect_timeout_ms: None,
1018 vsock: None,
1019 #[cfg(feature = "video-decoder")]
1020 video_dec: Vec::new(),
1021 #[cfg(feature = "video-encoder")]
1022 video_enc: Vec::new(),
1023 #[cfg(all(
1024 target_arch = "aarch64",
1025 any(target_os = "android", target_os = "linux")
1026 ))]
1027 virt_cpufreq: false,
1028 virt_cpufreq_v2: false,
1029 virtio_input: Vec::new(),
1030 #[cfg(feature = "audio")]
1031 virtio_snds: Vec::new(),
1032 #[cfg(any(target_os = "android", target_os = "linux"))]
1033 #[cfg(feature = "media")]
1034 v4l2_proxy: Vec::new(),
1035 #[cfg(feature = "vtpm")]
1036 vtpm_proxy: false,
1037 wayland_socket_paths: BTreeMap::new(),
1038 #[cfg(windows)]
1039 window_procedure_thread_split_config: None,
1040 x_display: None,
1041 }
1042 }
1043}
1044
1045pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
1046 if cfg.executable_path.is_none() {
1047 return Err("Executable is not specified".to_string());
1048 }
1049
1050 #[cfg(feature = "gpu")]
1051 {
1052 crate::crosvm::gpu_config::validate_gpu_config(cfg)?;
1053 }
1054 #[cfg(feature = "gdb")]
1055 if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
1056 return Err("`gdb` requires the number of vCPU to be 1".to_string());
1057 }
1058 if cfg.host_cpu_topology {
1059 if cfg.no_smt {
1060 return Err(
1061 "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
1062 the smt of the Guest is the same as that of the Host when \
1063 `host-cpu-topology` is set."
1064 .to_string(),
1065 );
1066 }
1067
1068 let pcpu_count =
1069 base::number_of_online_cores().expect("Could not read number of online cores");
1070 if let Some(vcpu_count) = cfg.vcpu_count {
1071 if pcpu_count != vcpu_count {
1072 return Err(format!(
1073 "`host-cpu-topology` requires the count of vCPUs({vcpu_count}) to equal the \
1074 count of online CPUs({pcpu_count}) on host."
1075 ));
1076 }
1077 } else {
1078 cfg.vcpu_count = Some(pcpu_count);
1079 }
1080
1081 match &cfg.vcpu_affinity {
1082 None => {
1083 let vcpu_count = cfg.vcpu_count.unwrap();
1084 let max_cores = base::number_of_logical_cores()
1085 .expect("Could not read number of logical cores");
1086 let affinity_map =
1087 default_vcpu_affinity_map(vcpu_count, max_cores, base::is_cpu_online);
1088 cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
1089 }
1090 _ => {
1091 return Err(
1092 "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
1093 .to_string(),
1094 );
1095 }
1096 }
1097
1098 if !cfg.cpu_capacity.is_empty() {
1099 return Err(
1100 "`host-cpu-topology` requires not to set `cpu-capacity` at the same time"
1101 .to_string(),
1102 );
1103 }
1104
1105 if !cfg.cpu_clusters.is_empty() {
1106 return Err(
1107 "`host-cpu-topology` requires not to set `cpu clusters` at the same time"
1108 .to_string(),
1109 );
1110 }
1111 }
1112
1113 if cfg.boot_cpu >= cfg.vcpu_count.unwrap_or(1) {
1114 log::warn!("boot_cpu selection cannot be higher than vCPUs available, defaulting to 0");
1115 cfg.boot_cpu = 0;
1116 }
1117
1118 #[cfg(all(
1119 target_arch = "aarch64",
1120 any(target_os = "android", target_os = "linux")
1121 ))]
1122 if !cfg.cpu_frequencies_khz.is_empty() {
1123 if !cfg.virt_cpufreq_v2 {
1124 return Err("`cpu-frequencies` requires `virt-cpufreq-upstream`".to_string());
1125 }
1126
1127 if cfg.host_cpu_topology {
1128 return Err(
1129 "`host-cpu-topology` cannot be used with 'cpu-frequencies` at the same time"
1130 .to_string(),
1131 );
1132 }
1133 }
1134
1135 #[cfg(all(
1136 target_arch = "aarch64",
1137 any(target_os = "android", target_os = "linux")
1138 ))]
1139 if cfg.virt_cpufreq {
1140 if !cfg.host_cpu_topology && (cfg.vcpu_affinity.is_none() || cfg.cpu_capacity.is_empty()) {
1141 return Err("`virt-cpufreq` requires 'host-cpu-topology' enabled or \
1142 have vcpu_affinity and cpu_capacity configured"
1143 .to_string());
1144 }
1145 }
1146 #[cfg(target_arch = "x86_64")]
1147 if !cfg.vcpu_hybrid_type.is_empty() {
1148 if cfg.host_cpu_topology {
1149 return Err("`core-types` cannot be set with `host-cpu-topology`.".to_string());
1150 }
1151 check_host_hybrid_support(&CpuIdCall::new(__cpuid_count, __cpuid))
1152 .map_err(|e| format!("the cpu doesn't support `core-types`: {e}"))?;
1153 if cfg.vcpu_hybrid_type.len() != cfg.vcpu_count.unwrap_or(1) {
1154 return Err("`core-types` must be set for all virtual CPUs".to_string());
1155 }
1156 for cpu_id in 0..cfg.vcpu_count.unwrap_or(1) {
1157 if !cfg.vcpu_hybrid_type.contains_key(&cpu_id) {
1158 return Err("`core-types` must be set for all virtual CPUs".to_string());
1159 }
1160 }
1161 }
1162 #[cfg(target_arch = "x86_64")]
1163 if cfg.enable_hwp && !cfg.host_cpu_topology {
1164 return Err("setting `enable-hwp` requires `host-cpu-topology` is set.".to_string());
1165 }
1166 #[cfg(target_arch = "x86_64")]
1167 if cfg.itmt {
1168 use std::collections::BTreeSet;
1169 if !cfg.host_cpu_topology {
1173 if let Some(VcpuAffinity::PerVcpu(v)) = &cfg.vcpu_affinity {
1176 if v.len() != cfg.vcpu_count.unwrap_or(1) {
1178 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1179 }
1180
1181 let mut pcpu_set = BTreeSet::new();
1182 for cpus in v.values() {
1183 if cpus.len() != 1 {
1184 return Err(
1185 "`itmt` requires affinity to be set 1 pCPU for 1 vCPU.".to_owned()
1186 );
1187 }
1188 if !pcpu_set.insert(cpus[0]) {
1191 return Err(
1192 "`cpu_host` requires affinity to be set different pVPU for each vCPU."
1193 .to_owned(),
1194 );
1195 }
1196 }
1197 } else {
1198 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1199 }
1200 }
1201 if !cfg.enable_hwp {
1202 return Err("setting `itmt` requires `enable-hwp` is set.".to_string());
1203 }
1204 }
1205
1206 #[cfg(feature = "balloon")]
1207 {
1208 if !cfg.balloon && cfg.balloon_control.is_some() {
1209 return Err("'balloon-control' requires enabled balloon".to_string());
1210 }
1211
1212 if !cfg.balloon && cfg.balloon_page_reporting {
1213 return Err("'balloon_page_reporting' requires enabled balloon".to_string());
1214 }
1215 }
1216
1217 #[cfg(feature = "swap")]
1220 if cfg.swap_dir.is_some() && cfg.jail_config.is_none() {
1221 return Err("'swap' and 'disable-sandbox' are mutually exclusive".to_string());
1222 }
1223
1224 set_default_serial_parameters(
1225 &mut cfg.serial_parameters,
1226 cfg.vhost_user
1227 .iter()
1228 .any(|opt| opt.type_ == DeviceType::Console),
1229 );
1230
1231 for mapping in cfg
1232 .file_backed_mappings_mmio
1233 .iter_mut()
1234 .chain(cfg.file_backed_mappings_ram.iter_mut())
1235 {
1236 validate_file_backed_mapping(mapping)?;
1237 }
1238
1239 for pmem in cfg.pmems.iter() {
1240 validate_pmem(pmem)?;
1241 }
1242
1243 super::sys::config::validate_config(cfg)
1245}
1246
1247fn default_vcpu_affinity_map(
1248 vcpu_count: usize,
1249 max_cores: usize,
1250 is_cpu_online: impl Fn(usize) -> bool,
1251) -> BTreeMap<usize, CpuSet> {
1252 let mut affinity_map = BTreeMap::new();
1253 let mut vcpu_id = 0;
1254 for cpu_id in 0..max_cores {
1255 if is_cpu_online(cpu_id) {
1256 affinity_map.insert(vcpu_id, CpuSet::new([cpu_id]));
1257 vcpu_id += 1;
1258 }
1259 if vcpu_id >= vcpu_count {
1260 break;
1262 }
1263 }
1264 affinity_map
1265}
1266
1267fn validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String> {
1268 let pagesize_mask = pagesize() as u64 - 1;
1269 let aligned_address = mapping.address & !pagesize_mask;
1270 let aligned_size =
1271 ((mapping.address + mapping.size + pagesize_mask) & !pagesize_mask) - aligned_address;
1272
1273 if mapping.align {
1274 mapping.address = aligned_address;
1275 mapping.size = aligned_size;
1276 } else if aligned_address != mapping.address || aligned_size != mapping.size {
1277 return Err(
1278 "--file-backed-mapping addr and size parameters must be page size aligned".to_string(),
1279 );
1280 }
1281
1282 Ok(())
1283}
1284
1285fn validate_pmem(pmem: &PmemOption) -> Result<(), String> {
1286 if (pmem.swap_interval.is_some() && pmem.vma_size.is_none())
1287 || (pmem.swap_interval.is_none() && pmem.vma_size.is_some())
1288 {
1289 return Err(
1290 "--pmem vma-size and swap-interval parameters must be specified together".to_string(),
1291 );
1292 }
1293
1294 if pmem.ro && pmem.swap_interval.is_some() {
1295 return Err(
1296 "--pmem swap-interval parameter can only be set for writable pmem device".to_string(),
1297 );
1298 }
1299
1300 Ok(())
1301}
1302
1303#[cfg(test)]
1304#[allow(clippy::needless_update)]
1305mod tests {
1306 use argh::FromArgs;
1307 use devices::PciClassCode;
1308 use devices::StubPciParameters;
1309 #[cfg(target_arch = "x86_64")]
1310 use uuid::uuid;
1311
1312 use super::*;
1313
1314 fn config_from_args(args: &[&str]) -> Config {
1315 crate::crosvm::cmdline::RunCommand::from_args(&[], args)
1316 .unwrap()
1317 .try_into()
1318 .unwrap()
1319 }
1320
1321 #[test]
1322 fn parse_cpu_opts() {
1323 let res: CpuOptions = from_key_values("").unwrap();
1324 assert_eq!(res, CpuOptions::default());
1325
1326 let res: CpuOptions = from_key_values("12").unwrap();
1328 assert_eq!(
1329 res,
1330 CpuOptions {
1331 num_cores: Some(12),
1332 ..Default::default()
1333 }
1334 );
1335
1336 let res: CpuOptions = from_key_values("num-cores=16").unwrap();
1337 assert_eq!(
1338 res,
1339 CpuOptions {
1340 num_cores: Some(16),
1341 ..Default::default()
1342 }
1343 );
1344
1345 let res: CpuOptions = from_key_values("clusters=[[0],[1],[2],[3]]").unwrap();
1347 assert_eq!(
1348 res,
1349 CpuOptions {
1350 clusters: vec![
1351 CpuSet::new([0]),
1352 CpuSet::new([1]),
1353 CpuSet::new([2]),
1354 CpuSet::new([3])
1355 ],
1356 ..Default::default()
1357 }
1358 );
1359
1360 let res: CpuOptions = from_key_values("clusters=[[0-3]]").unwrap();
1361 assert_eq!(
1362 res,
1363 CpuOptions {
1364 clusters: vec![CpuSet::new([0, 1, 2, 3])],
1365 ..Default::default()
1366 }
1367 );
1368
1369 let res: CpuOptions = from_key_values("clusters=[[0,2],[1,3],[4-7,12]]").unwrap();
1370 assert_eq!(
1371 res,
1372 CpuOptions {
1373 clusters: vec![
1374 CpuSet::new([0, 2]),
1375 CpuSet::new([1, 3]),
1376 CpuSet::new([4, 5, 6, 7, 12])
1377 ],
1378 ..Default::default()
1379 }
1380 );
1381
1382 #[cfg(target_arch = "x86_64")]
1383 {
1384 let res: CpuOptions = from_key_values("core-types=[atom=[1,3-7],core=[0,2]]").unwrap();
1385 assert_eq!(
1386 res,
1387 CpuOptions {
1388 core_types: Some(CpuCoreType {
1389 atom: CpuSet::new([1, 3, 4, 5, 6, 7]),
1390 core: CpuSet::new([0, 2])
1391 }),
1392 ..Default::default()
1393 }
1394 );
1395 }
1396
1397 let res: CpuOptions = from_key_values("16,clusters=[[0],[4-6],[7]]").unwrap();
1399 assert_eq!(
1400 res,
1401 CpuOptions {
1402 num_cores: Some(16),
1403 clusters: vec![CpuSet::new([0]), CpuSet::new([4, 5, 6]), CpuSet::new([7])],
1404 ..Default::default()
1405 }
1406 );
1407
1408 let res: CpuOptions = from_key_values("clusters=[[0-7],[30-31]],num-cores=32").unwrap();
1409 assert_eq!(
1410 res,
1411 CpuOptions {
1412 num_cores: Some(32),
1413 clusters: vec![CpuSet::new([0, 1, 2, 3, 4, 5, 6, 7]), CpuSet::new([30, 31])],
1414 ..Default::default()
1415 }
1416 );
1417 }
1418
1419 #[test]
1420 fn parse_cpu_set_single() {
1421 assert_eq!(
1422 CpuSet::from_str("123").expect("parse failed"),
1423 CpuSet::new([123])
1424 );
1425 }
1426
1427 #[test]
1428 fn parse_cpu_set_list() {
1429 assert_eq!(
1430 CpuSet::from_str("0,1,2,3").expect("parse failed"),
1431 CpuSet::new([0, 1, 2, 3])
1432 );
1433 }
1434
1435 #[test]
1436 fn parse_cpu_set_range() {
1437 assert_eq!(
1438 CpuSet::from_str("0-3").expect("parse failed"),
1439 CpuSet::new([0, 1, 2, 3])
1440 );
1441 }
1442
1443 #[test]
1444 fn parse_cpu_set_list_of_ranges() {
1445 assert_eq!(
1446 CpuSet::from_str("3-4,7-9,18").expect("parse failed"),
1447 CpuSet::new([3, 4, 7, 8, 9, 18])
1448 );
1449 }
1450
1451 #[test]
1452 fn parse_cpu_set_repeated() {
1453 assert_eq!(
1456 CpuSet::from_str("1,1,1").expect("parse failed"),
1457 CpuSet::new([1, 1, 1])
1458 );
1459 }
1460
1461 #[test]
1462 fn parse_cpu_set_negative() {
1463 CpuSet::from_str("-3").expect_err("parse should have failed");
1465 }
1466
1467 #[test]
1468 fn parse_cpu_set_reverse_range() {
1469 CpuSet::from_str("5-2").expect_err("parse should have failed");
1471 }
1472
1473 #[test]
1474 fn parse_cpu_set_open_range() {
1475 CpuSet::from_str("3-").expect_err("parse should have failed");
1476 }
1477
1478 #[test]
1479 fn parse_cpu_set_extra_comma() {
1480 CpuSet::from_str("0,1,2,").expect_err("parse should have failed");
1481 }
1482
1483 #[test]
1484 fn parse_cpu_affinity_global() {
1485 assert_eq!(
1486 parse_cpu_affinity("0,5-7,9").expect("parse failed"),
1487 VcpuAffinity::Global(CpuSet::new([0, 5, 6, 7, 9])),
1488 );
1489 }
1490
1491 #[test]
1492 fn parse_cpu_affinity_per_vcpu_one_to_one() {
1493 let mut expected_map = BTreeMap::new();
1494 expected_map.insert(0, CpuSet::new([0]));
1495 expected_map.insert(1, CpuSet::new([1]));
1496 expected_map.insert(2, CpuSet::new([2]));
1497 expected_map.insert(3, CpuSet::new([3]));
1498 assert_eq!(
1499 parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
1500 VcpuAffinity::PerVcpu(expected_map),
1501 );
1502 }
1503
1504 #[test]
1505 fn parse_cpu_affinity_per_vcpu_sets() {
1506 let mut expected_map = BTreeMap::new();
1507 expected_map.insert(0, CpuSet::new([0, 1, 2]));
1508 expected_map.insert(1, CpuSet::new([3, 4, 5]));
1509 expected_map.insert(2, CpuSet::new([6, 7, 8]));
1510 assert_eq!(
1511 parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
1512 VcpuAffinity::PerVcpu(expected_map),
1513 );
1514 }
1515
1516 #[test]
1517 fn parse_mem_opts() {
1518 let res: MemOptions = from_key_values("").unwrap();
1519 assert_eq!(res.size, None);
1520
1521 let res: MemOptions = from_key_values("1024").unwrap();
1522 assert_eq!(res.size, Some(1024));
1523
1524 let res: MemOptions = from_key_values("size=0x4000").unwrap();
1525 assert_eq!(res.size, Some(16384));
1526 }
1527
1528 #[test]
1529 fn parse_serial_vaild() {
1530 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1531 .expect("parse should have succeded");
1532 }
1533
1534 #[test]
1535 fn parse_serial_virtio_console_vaild() {
1536 parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
1537 .expect("parse should have succeded");
1538 }
1539
1540 #[test]
1541 fn parse_serial_valid_no_num() {
1542 parse_serial_options("type=syslog").expect("parse should have succeded");
1543 }
1544
1545 #[test]
1546 fn parse_serial_equals_in_value() {
1547 let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
1548 .expect("parse should have succeded");
1549 assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
1550 }
1551
1552 #[test]
1553 fn parse_serial_invalid_type() {
1554 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1555 }
1556
1557 #[test]
1558 fn parse_serial_invalid_num_upper() {
1559 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1560 }
1561
1562 #[test]
1563 fn parse_serial_invalid_num_lower() {
1564 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1565 }
1566
1567 #[test]
1568 fn parse_serial_virtio_console_invalid_num_lower() {
1569 parse_serial_options("type=syslog,hardware=virtio-console,num=0")
1570 .expect_err("parse should have failed");
1571 }
1572
1573 #[test]
1574 fn parse_serial_invalid_num_string() {
1575 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1576 }
1577
1578 #[test]
1579 fn parse_serial_invalid_option() {
1580 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1581 }
1582
1583 #[test]
1584 fn parse_serial_invalid_two_stdin() {
1585 assert!(TryInto::<Config>::try_into(
1586 crate::crosvm::cmdline::RunCommand::from_args(
1587 &[],
1588 &[
1589 "--serial",
1590 "num=1,type=stdout,stdin=true",
1591 "--serial",
1592 "num=2,type=stdout,stdin=true"
1593 ]
1594 )
1595 .unwrap()
1596 )
1597 .is_err())
1598 }
1599
1600 #[test]
1601 fn parse_serial_pci_address_valid_for_virtio() {
1602 let parsed =
1603 parse_serial_options("type=syslog,hardware=virtio-console,pci-address=00:0e.0")
1604 .expect("parse should have succeded");
1605 assert_eq!(
1606 parsed.pci_address,
1607 Some(PciAddress {
1608 bus: 0,
1609 dev: 14,
1610 func: 0
1611 })
1612 );
1613 }
1614
1615 #[test]
1616 fn parse_serial_pci_address_valid_for_legacy_virtio() {
1617 let parsed =
1618 parse_serial_options("type=syslog,hardware=legacy-virtio-console,pci-address=00:0e.0")
1619 .expect("parse should have succeded");
1620 assert_eq!(
1621 parsed.pci_address,
1622 Some(PciAddress {
1623 bus: 0,
1624 dev: 14,
1625 func: 0
1626 })
1627 );
1628 }
1629
1630 #[test]
1631 fn parse_serial_pci_address_failed_for_serial() {
1632 parse_serial_options("type=syslog,hardware=serial,pci-address=00:0e.0")
1633 .expect_err("expected pci-address error for serial hardware");
1634 }
1635
1636 #[test]
1637 fn parse_serial_pci_address_failed_for_debugcon() {
1638 parse_serial_options("type=syslog,hardware=debugcon,pci-address=00:0e.0")
1639 .expect_err("expected pci-address error for debugcon hardware");
1640 }
1641
1642 #[test]
1643 fn parse_battery_valid() {
1644 let bat_config: BatteryConfig = from_key_values("type=goldfish").unwrap();
1645 assert_eq!(bat_config.type_, BatteryType::Goldfish);
1646 }
1647
1648 #[test]
1649 fn parse_battery_valid_no_type() {
1650 let bat_config: BatteryConfig = from_key_values("").unwrap();
1651 assert_eq!(bat_config.type_, BatteryType::Goldfish);
1652 }
1653
1654 #[test]
1655 fn parse_battery_invalid_parameter() {
1656 from_key_values::<BatteryConfig>("tyep=goldfish").expect_err("parse should have failed");
1657 }
1658
1659 #[test]
1660 fn parse_battery_invalid_type_value() {
1661 from_key_values::<BatteryConfig>("type=xxx").expect_err("parse should have failed");
1662 }
1663
1664 #[test]
1665 fn parse_irqchip_kernel() {
1666 let cfg = TryInto::<Config>::try_into(
1667 crate::crosvm::cmdline::RunCommand::from_args(
1668 &[],
1669 &["--irqchip", "kernel", "/dev/null"],
1670 )
1671 .unwrap(),
1672 )
1673 .unwrap();
1674
1675 assert_eq!(
1676 cfg.irq_chip,
1677 Some(IrqChipKind::Kernel {
1678 #[cfg(target_arch = "aarch64")]
1679 allow_vgic_its: false
1680 })
1681 );
1682 }
1683
1684 #[test]
1685 #[cfg(target_arch = "aarch64")]
1686 fn parse_irqchip_kernel_with_its() {
1687 let cfg = TryInto::<Config>::try_into(
1688 crate::crosvm::cmdline::RunCommand::from_args(
1689 &[],
1690 &["--irqchip", "kernel[allow-vgic-its]", "/dev/null"],
1691 )
1692 .unwrap(),
1693 )
1694 .unwrap();
1695
1696 assert_eq!(
1697 cfg.irq_chip,
1698 Some(IrqChipKind::Kernel {
1699 allow_vgic_its: true
1700 })
1701 );
1702 }
1703
1704 #[test]
1705 fn parse_irqchip_split() {
1706 let cfg = TryInto::<Config>::try_into(
1707 crate::crosvm::cmdline::RunCommand::from_args(
1708 &[],
1709 &["--irqchip", "split", "/dev/null"],
1710 )
1711 .unwrap(),
1712 )
1713 .unwrap();
1714
1715 assert_eq!(cfg.irq_chip, Some(IrqChipKind::Split));
1716 }
1717
1718 #[test]
1719 fn parse_irqchip_userspace() {
1720 let cfg = TryInto::<Config>::try_into(
1721 crate::crosvm::cmdline::RunCommand::from_args(
1722 &[],
1723 &["--irqchip", "userspace", "/dev/null"],
1724 )
1725 .unwrap(),
1726 )
1727 .unwrap();
1728
1729 assert_eq!(cfg.irq_chip, Some(IrqChipKind::Userspace));
1730 }
1731
1732 #[test]
1733 fn parse_stub_pci() {
1734 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();
1735 assert_eq!(params.address.bus, 1);
1736 assert_eq!(params.address.dev, 2);
1737 assert_eq!(params.address.func, 3);
1738 assert_eq!(params.vendor, 0xfffe);
1739 assert_eq!(params.device, 0xfffd);
1740 assert_eq!(params.class.class as u8, PciClassCode::Other as u8);
1741 assert_eq!(params.class.subclass, 0xc1);
1742 assert_eq!(params.class.programming_interface, 0xc2);
1743 assert_eq!(params.subsystem_vendor, 0xfffc);
1744 assert_eq!(params.subsystem_device, 0xfffb);
1745 assert_eq!(params.revision, 0xa);
1746 }
1747
1748 #[test]
1749 fn parse_file_backed_mapping_valid() {
1750 let params = from_key_values::<FileBackedMappingParameters>(
1751 "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,rw,sync",
1752 )
1753 .unwrap();
1754 assert_eq!(params.address, 0x1000);
1755 assert_eq!(params.size, 0x2000);
1756 assert_eq!(params.path, PathBuf::from("/dev/mem"));
1757 assert_eq!(params.offset, 0x3000);
1758 assert!(params.writable);
1759 assert!(params.sync);
1760 }
1761
1762 #[test]
1763 fn parse_file_backed_mapping_incomplete() {
1764 assert!(
1765 from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2000")
1766 .unwrap_err()
1767 .contains("missing field `path`")
1768 );
1769 assert!(
1770 from_key_values::<FileBackedMappingParameters>("size=0x2000,path=/dev/mem")
1771 .unwrap_err()
1772 .contains("missing field `addr`")
1773 );
1774 assert!(
1775 from_key_values::<FileBackedMappingParameters>("addr=0x1000,path=/dev/mem")
1776 .unwrap_err()
1777 .contains("missing field `size`")
1778 );
1779 }
1780
1781 #[test]
1782 fn parse_file_backed_mapping_unaligned_addr() {
1783 let mut params =
1784 from_key_values::<FileBackedMappingParameters>("addr=0x1001,size=0x2000,path=/dev/mem")
1785 .unwrap();
1786 assert!(validate_file_backed_mapping(&mut params)
1787 .unwrap_err()
1788 .contains("aligned"));
1789 }
1790 #[test]
1791 fn parse_file_backed_mapping_unaligned_size() {
1792 let mut params =
1793 from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2001,path=/dev/mem")
1794 .unwrap();
1795 assert!(validate_file_backed_mapping(&mut params)
1796 .unwrap_err()
1797 .contains("aligned"));
1798 }
1799
1800 #[test]
1801 fn parse_file_backed_mapping_align() {
1802 let addr = pagesize() as u64 * 3 + 42;
1803 let size = pagesize() as u64 - 0xf;
1804 let mut params = from_key_values::<FileBackedMappingParameters>(&format!(
1805 "addr={addr},size={size},path=/dev/mem,align",
1806 ))
1807 .unwrap();
1808 assert_eq!(params.address, addr);
1809 assert_eq!(params.size, size);
1810 validate_file_backed_mapping(&mut params).unwrap();
1811 assert_eq!(params.address, pagesize() as u64 * 3);
1812 assert_eq!(params.size, pagesize() as u64 * 2);
1813 }
1814
1815 #[test]
1816 fn parse_fw_cfg_valid_path() {
1817 let cfg = TryInto::<Config>::try_into(
1818 crate::crosvm::cmdline::RunCommand::from_args(
1819 &[],
1820 &["--fw-cfg", "name=bar,path=data.bin", "/dev/null"],
1821 )
1822 .unwrap(),
1823 )
1824 .unwrap();
1825
1826 assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1827 assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1828 assert_eq!(cfg.fw_cfg_parameters[0].string, None);
1829 assert_eq!(cfg.fw_cfg_parameters[0].path, Some("data.bin".into()));
1830 }
1831
1832 #[test]
1833 fn parse_fw_cfg_valid_string() {
1834 let cfg = TryInto::<Config>::try_into(
1835 crate::crosvm::cmdline::RunCommand::from_args(
1836 &[],
1837 &["--fw-cfg", "name=bar,string=foo", "/dev/null"],
1838 )
1839 .unwrap(),
1840 )
1841 .unwrap();
1842
1843 assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1844 assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1845 assert_eq!(cfg.fw_cfg_parameters[0].string, Some("foo".to_string()));
1846 assert_eq!(cfg.fw_cfg_parameters[0].path, None);
1847 }
1848
1849 #[test]
1850 fn parse_dtbo() {
1851 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1852 &[],
1853 &[
1854 "--device-tree-overlay",
1855 "/path/to/dtbo1",
1856 "--device-tree-overlay",
1857 "/path/to/dtbo2",
1858 "/dev/null",
1859 ],
1860 )
1861 .unwrap()
1862 .try_into()
1863 .unwrap();
1864
1865 assert_eq!(cfg.device_tree_overlay.len(), 2);
1866 for (opt, p) in cfg
1867 .device_tree_overlay
1868 .into_iter()
1869 .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1870 {
1871 assert_eq!(opt.path, PathBuf::from(p));
1872 assert!(opt.select_symbols.is_none());
1873 }
1874 }
1875
1876 #[test]
1877 #[cfg(any(target_os = "android", target_os = "linux"))]
1878 fn parse_dtbo_filtered() {
1879 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1880 &[],
1881 &[
1882 "--vfio",
1883 "/path/to/dev",
1884 "--device-tree-overlay",
1885 "/path/to/dtbo1,select-symbols=[mydev]",
1886 "--device-tree-overlay",
1887 "/path/to/dtbo2,select-symbols=[mydev]",
1888 "/dev/null",
1889 ],
1890 )
1891 .unwrap()
1892 .try_into()
1893 .unwrap();
1894
1895 assert_eq!(cfg.device_tree_overlay.len(), 2);
1896 for (opt, p) in cfg
1897 .device_tree_overlay
1898 .into_iter()
1899 .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1900 {
1901 assert_eq!(opt.path, PathBuf::from(p));
1902 assert_eq!(opt.select_symbols, Some(vec!["mydev".to_string()]));
1903 }
1904 }
1905
1906 #[test]
1907 #[cfg(any(target_os = "android", target_os = "linux"))]
1908 fn parse_dtbo_legacy_filter() {
1909 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1910 &[],
1911 &[
1912 "--vfio",
1913 "/path/to/dev,dt-symbol=mydev",
1914 "--device-tree-overlay",
1915 "/path/to/dtbo1,filter",
1916 "/dev/null",
1917 ],
1918 )
1919 .unwrap()
1920 .try_into()
1921 .unwrap();
1922
1923 assert_eq!(cfg.device_tree_overlay.len(), 1);
1924 let opt = cfg.device_tree_overlay.first().unwrap();
1925 assert_eq!(opt.path, PathBuf::from("/path/to/dtbo1"));
1926 assert_eq!(opt.select_symbols, Some(vec!["mydev".to_string()]));
1927 }
1928
1929 #[test]
1930 #[cfg(any(target_os = "android", target_os = "linux"))]
1931 fn parse_dtbo_filter_and_select_symbols() {
1932 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1933 &[],
1934 &[
1935 "--vfio",
1936 "/path/to/dev,dt-symbol=mydev",
1937 "--device-tree-overlay",
1938 "/path/to/dtbo1,filter,select-symbols=[otherdev]",
1939 "/dev/null",
1940 ],
1941 )
1942 .unwrap()
1943 .try_into()
1944 .unwrap();
1945
1946 assert_eq!(cfg.device_tree_overlay.len(), 1);
1947 let opt = cfg.device_tree_overlay.first().unwrap();
1948 assert_eq!(opt.path, PathBuf::from("/path/to/dtbo1"));
1949 assert_eq!(
1950 opt.select_symbols,
1951 Some(vec!["otherdev".to_string(), "mydev".to_string()])
1952 );
1953 }
1954
1955 #[test]
1956 fn parse_fw_cfg_invalid_no_name() {
1957 assert!(
1958 crate::crosvm::cmdline::RunCommand::from_args(&[], &["--fw-cfg", "string=foo",])
1959 .is_err()
1960 );
1961 }
1962
1963 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
1964 #[test]
1965 fn parse_video() {
1966 use devices::virtio::device_constants::video::VideoBackendType;
1967
1968 #[cfg(feature = "libvda")]
1969 {
1970 let params: VideoDeviceConfig = from_key_values("libvda").unwrap();
1971 assert_eq!(params.backend, VideoBackendType::Libvda);
1972
1973 let params: VideoDeviceConfig = from_key_values("libvda-vd").unwrap();
1974 assert_eq!(params.backend, VideoBackendType::LibvdaVd);
1975 }
1976
1977 #[cfg(feature = "ffmpeg")]
1978 {
1979 let params: VideoDeviceConfig = from_key_values("ffmpeg").unwrap();
1980 assert_eq!(params.backend, VideoBackendType::Ffmpeg);
1981 }
1982
1983 #[cfg(feature = "vaapi")]
1984 {
1985 let params: VideoDeviceConfig = from_key_values("vaapi").unwrap();
1986 assert_eq!(params.backend, VideoBackendType::Vaapi);
1987 }
1988 }
1989
1990 #[test]
1991 fn parse_vhost_user_option_all_device_types() {
1992 fn test_device_type(type_string: &str, type_: DeviceType) {
1993 let vhost_user_arg = format!("{type_string},socket=sock");
1994
1995 let cfg = TryInto::<Config>::try_into(
1996 crate::crosvm::cmdline::RunCommand::from_args(
1997 &[],
1998 &["--vhost-user", &vhost_user_arg, "/dev/null"],
1999 )
2000 .unwrap(),
2001 )
2002 .unwrap();
2003
2004 assert_eq!(cfg.vhost_user.len(), 1);
2005 let vu = &cfg.vhost_user[0];
2006 assert_eq!(vu.type_, type_);
2007 }
2008
2009 test_device_type("net", DeviceType::Net);
2010 test_device_type("block", DeviceType::Block);
2011 test_device_type("console", DeviceType::Console);
2012 test_device_type("rng", DeviceType::Rng);
2013 test_device_type("balloon", DeviceType::Balloon);
2014 test_device_type("scsi", DeviceType::Scsi);
2015 test_device_type("9p", DeviceType::P9);
2016 test_device_type("gpu", DeviceType::Gpu);
2017 test_device_type("input", DeviceType::Input);
2018 test_device_type("vsock", DeviceType::Vsock);
2019 test_device_type("iommu", DeviceType::Iommu);
2020 test_device_type("sound", DeviceType::Sound);
2021 test_device_type("fs", DeviceType::Fs);
2022 test_device_type("pmem", DeviceType::Pmem);
2023 test_device_type("mac80211-hwsim", DeviceType::Mac80211HwSim);
2024 test_device_type("video-encoder", DeviceType::VideoEncoder);
2025 test_device_type("video-decoder", DeviceType::VideoDecoder);
2026 test_device_type("scmi", DeviceType::Scmi);
2027 test_device_type("wl", DeviceType::Wl);
2028 test_device_type("tpm", DeviceType::Tpm);
2029 test_device_type("pvclock", DeviceType::Pvclock);
2030 }
2031
2032 #[cfg(target_arch = "x86_64")]
2033 #[test]
2034 fn parse_smbios_uuid() {
2035 let opt: SmbiosOptions =
2036 from_key_values("uuid=12e474af-2cc1-49d1-b0e5-d03a3e03ca03").unwrap();
2037 assert_eq!(
2038 opt.uuid,
2039 Some(uuid!("12e474af-2cc1-49d1-b0e5-d03a3e03ca03"))
2040 );
2041
2042 from_key_values::<SmbiosOptions>("uuid=zzzz").expect_err("expected error parsing uuid");
2043 }
2044
2045 #[test]
2046 fn parse_touch_legacy() {
2047 let cfg = TryInto::<Config>::try_into(
2048 crate::crosvm::cmdline::RunCommand::from_args(
2049 &[],
2050 &["--multi-touch", "my_socket:867:5309", "bzImage"],
2051 )
2052 .unwrap(),
2053 )
2054 .unwrap();
2055
2056 assert_eq!(cfg.virtio_input.len(), 1);
2057 let multi_touch = cfg
2058 .virtio_input
2059 .iter()
2060 .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2061 .unwrap();
2062 assert_eq!(
2063 *multi_touch,
2064 InputDeviceOption::MultiTouch {
2065 path: PathBuf::from("my_socket"),
2066 width: Some(867),
2067 height: Some(5309),
2068 name: None
2069 }
2070 );
2071 }
2072
2073 #[test]
2074 fn parse_touch() {
2075 let cfg = TryInto::<Config>::try_into(
2076 crate::crosvm::cmdline::RunCommand::from_args(
2077 &[],
2078 &["--multi-touch", r"C:\path,width=867,height=5309", "bzImage"],
2079 )
2080 .unwrap(),
2081 )
2082 .unwrap();
2083
2084 assert_eq!(cfg.virtio_input.len(), 1);
2085 let multi_touch = cfg
2086 .virtio_input
2087 .iter()
2088 .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2089 .unwrap();
2090 assert_eq!(
2091 *multi_touch,
2092 InputDeviceOption::MultiTouch {
2093 path: PathBuf::from(r"C:\path"),
2094 width: Some(867),
2095 height: Some(5309),
2096 name: None
2097 }
2098 );
2099 }
2100
2101 #[test]
2102 fn single_touch_spec_and_track_pad_spec_default_size() {
2103 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2104 &[],
2105 &[
2106 "--single-touch",
2107 "/dev/single-touch-test",
2108 "--trackpad",
2109 "/dev/single-touch-test",
2110 "/dev/null",
2111 ],
2112 )
2113 .unwrap()
2114 .try_into()
2115 .unwrap();
2116
2117 let single_touch = config
2118 .virtio_input
2119 .iter()
2120 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2121 .unwrap();
2122 let trackpad = config
2123 .virtio_input
2124 .iter()
2125 .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2126 .unwrap();
2127
2128 assert_eq!(
2129 *single_touch,
2130 InputDeviceOption::SingleTouch {
2131 path: PathBuf::from("/dev/single-touch-test"),
2132 width: None,
2133 height: None,
2134 name: None
2135 }
2136 );
2137 assert_eq!(
2138 *trackpad,
2139 InputDeviceOption::Trackpad {
2140 path: PathBuf::from("/dev/single-touch-test"),
2141 width: None,
2142 height: None,
2143 name: None
2144 }
2145 );
2146 }
2147
2148 #[cfg(feature = "gpu")]
2149 #[test]
2150 fn single_touch_spec_default_size_from_gpu() {
2151 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2152 &[],
2153 &[
2154 "--single-touch",
2155 "/dev/single-touch-test",
2156 "--gpu",
2157 "width=1024,height=768",
2158 "/dev/null",
2159 ],
2160 )
2161 .unwrap()
2162 .try_into()
2163 .unwrap();
2164
2165 let single_touch = config
2166 .virtio_input
2167 .iter()
2168 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2169 .unwrap();
2170 assert_eq!(
2171 *single_touch,
2172 InputDeviceOption::SingleTouch {
2173 path: PathBuf::from("/dev/single-touch-test"),
2174 width: None,
2175 height: None,
2176 name: None
2177 }
2178 );
2179
2180 assert_eq!(config.display_input_width, Some(1024));
2181 assert_eq!(config.display_input_height, Some(768));
2182 }
2183
2184 #[test]
2185 fn single_touch_spec_and_track_pad_spec_with_size() {
2186 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2187 &[],
2188 &[
2189 "--single-touch",
2190 "/dev/single-touch-test:12345:54321",
2191 "--trackpad",
2192 "/dev/single-touch-test:5678:9876",
2193 "/dev/null",
2194 ],
2195 )
2196 .unwrap()
2197 .try_into()
2198 .unwrap();
2199
2200 let single_touch = config
2201 .virtio_input
2202 .iter()
2203 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2204 .unwrap();
2205 let trackpad = config
2206 .virtio_input
2207 .iter()
2208 .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2209 .unwrap();
2210
2211 assert_eq!(
2212 *single_touch,
2213 InputDeviceOption::SingleTouch {
2214 path: PathBuf::from("/dev/single-touch-test"),
2215 width: Some(12345),
2216 height: Some(54321),
2217 name: None
2218 }
2219 );
2220 assert_eq!(
2221 *trackpad,
2222 InputDeviceOption::Trackpad {
2223 path: PathBuf::from("/dev/single-touch-test"),
2224 width: Some(5678),
2225 height: Some(9876),
2226 name: None
2227 }
2228 );
2229 }
2230
2231 #[cfg(feature = "gpu")]
2232 #[test]
2233 fn single_touch_spec_with_size_independent_from_gpu() {
2234 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2235 &[],
2236 &[
2237 "--single-touch",
2238 "/dev/single-touch-test:12345:54321",
2239 "--gpu",
2240 "width=1024,height=768",
2241 "/dev/null",
2242 ],
2243 )
2244 .unwrap()
2245 .try_into()
2246 .unwrap();
2247
2248 let single_touch = config
2249 .virtio_input
2250 .iter()
2251 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2252 .unwrap();
2253
2254 assert_eq!(
2255 *single_touch,
2256 InputDeviceOption::SingleTouch {
2257 path: PathBuf::from("/dev/single-touch-test"),
2258 width: Some(12345),
2259 height: Some(54321),
2260 name: None
2261 }
2262 );
2263
2264 assert_eq!(config.display_input_width, Some(1024));
2265 assert_eq!(config.display_input_height, Some(768));
2266 }
2267
2268 #[test]
2269 fn virtio_switches() {
2270 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2271 &[],
2272 &["--switches", "/dev/switches-test", "/dev/null"],
2273 )
2274 .unwrap()
2275 .try_into()
2276 .unwrap();
2277
2278 let switches = config
2279 .virtio_input
2280 .iter()
2281 .find(|input| matches!(input, InputDeviceOption::Switches { .. }))
2282 .unwrap();
2283
2284 assert_eq!(
2285 *switches,
2286 InputDeviceOption::Switches {
2287 path: PathBuf::from("/dev/switches-test")
2288 }
2289 );
2290 }
2291
2292 #[test]
2293 fn virtio_rotary() {
2294 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2295 &[],
2296 &["--rotary", "/dev/rotary-test", "/dev/null"],
2297 )
2298 .unwrap()
2299 .try_into()
2300 .unwrap();
2301
2302 let rotary = config
2303 .virtio_input
2304 .iter()
2305 .find(|input| matches!(input, InputDeviceOption::Rotary { .. }))
2306 .unwrap();
2307
2308 assert_eq!(
2309 *rotary,
2310 InputDeviceOption::Rotary {
2311 path: PathBuf::from("/dev/rotary-test")
2312 }
2313 );
2314 }
2315
2316 #[cfg(target_arch = "aarch64")]
2317 #[test]
2318 fn parse_pci_cam() {
2319 assert_eq!(
2320 config_from_args(&["--pci", "cam=[start=0x123]", "/dev/null"]).pci_config,
2321 PciConfig {
2322 cam: Some(arch::MemoryRegionConfig {
2323 start: 0x123,
2324 size: None,
2325 }),
2326 ..PciConfig::default()
2327 }
2328 );
2329 assert_eq!(
2330 config_from_args(&["--pci", "cam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2331 PciConfig {
2332 cam: Some(arch::MemoryRegionConfig {
2333 start: 0x123,
2334 size: Some(0x456),
2335 }),
2336 ..PciConfig::default()
2337 },
2338 );
2339 }
2340
2341 #[cfg(target_arch = "x86_64")]
2342 #[test]
2343 fn parse_pci_ecam() {
2344 assert_eq!(
2345 config_from_args(&["--pci", "ecam=[start=0x123]", "/dev/null"]).pci_config,
2346 PciConfig {
2347 ecam: Some(arch::MemoryRegionConfig {
2348 start: 0x123,
2349 size: None,
2350 }),
2351 ..PciConfig::default()
2352 }
2353 );
2354 assert_eq!(
2355 config_from_args(&["--pci", "ecam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2356 PciConfig {
2357 ecam: Some(arch::MemoryRegionConfig {
2358 start: 0x123,
2359 size: Some(0x456),
2360 }),
2361 ..PciConfig::default()
2362 },
2363 );
2364 }
2365
2366 #[test]
2367 fn parse_pci_mem() {
2368 assert_eq!(
2369 config_from_args(&["--pci", "mem=[start=0x123]", "/dev/null"]).pci_config,
2370 PciConfig {
2371 mem: Some(arch::MemoryRegionConfig {
2372 start: 0x123,
2373 size: None,
2374 }),
2375 ..PciConfig::default()
2376 }
2377 );
2378 assert_eq!(
2379 config_from_args(&["--pci", "mem=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2380 PciConfig {
2381 mem: Some(arch::MemoryRegionConfig {
2382 start: 0x123,
2383 size: Some(0x456),
2384 }),
2385 ..PciConfig::default()
2386 },
2387 );
2388 }
2389
2390 #[test]
2391 fn parse_pmem_options_missing_path() {
2392 assert!(from_key_values::<PmemOption>("")
2393 .unwrap_err()
2394 .contains("missing field `path`"));
2395 }
2396
2397 #[test]
2398 fn parse_pmem_options_default_values() {
2399 let pmem = from_key_values::<PmemOption>("/path/to/disk.img").unwrap();
2400 assert_eq!(
2401 pmem,
2402 PmemOption {
2403 path: "/path/to/disk.img".into(),
2404 ro: false,
2405 root: false,
2406 vma_size: None,
2407 swap_interval: None,
2408 }
2409 );
2410 }
2411
2412 #[test]
2413 fn parse_pmem_options_virtual_swap() {
2414 let pmem =
2415 from_key_values::<PmemOption>("virtual_path,vma-size=12345,swap-interval-ms=1000")
2416 .unwrap();
2417 assert_eq!(
2418 pmem,
2419 PmemOption {
2420 path: "virtual_path".into(),
2421 ro: false,
2422 root: false,
2423 vma_size: Some(12345),
2424 swap_interval: Some(Duration::new(1, 0)),
2425 }
2426 );
2427 }
2428
2429 #[test]
2430 fn validate_pmem_missing_virtual_swap_param() {
2431 let pmem = from_key_values::<PmemOption>("virtual_path,swap-interval-ms=1000").unwrap();
2432 assert!(validate_pmem(&pmem)
2433 .unwrap_err()
2434 .contains("vma-size and swap-interval parameters must be specified together"));
2435 }
2436
2437 #[test]
2438 fn validate_pmem_read_only_virtual_swap() {
2439 let pmem = from_key_values::<PmemOption>(
2440 "virtual_path,ro=true,vma-size=12345,swap-interval-ms=1000",
2441 )
2442 .unwrap();
2443 assert!(validate_pmem(&pmem)
2444 .unwrap_err()
2445 .contains("swap-interval parameter can only be set for writable pmem device"));
2446 }
2447
2448 #[test]
2449 fn test_default_vcpu_affinity_map() {
2450 let affinity = default_vcpu_affinity_map(4, 4, |_| true);
2452 assert_eq!(affinity.len(), 4);
2453 assert_eq!(affinity.get(&0), Some(&CpuSet::new([0])));
2454 assert_eq!(affinity.get(&1), Some(&CpuSet::new([1])));
2455 assert_eq!(affinity.get(&2), Some(&CpuSet::new([2])));
2456 assert_eq!(affinity.get(&3), Some(&CpuSet::new([3])));
2457
2458 let affinity = default_vcpu_affinity_map(3, 4, |id| id != 1);
2460 assert_eq!(affinity.len(), 3);
2461 assert_eq!(affinity.get(&0), Some(&CpuSet::new([0])));
2462 assert_eq!(affinity.get(&1), Some(&CpuSet::new([2])));
2463 assert_eq!(affinity.get(&2), Some(&CpuSet::new([3])));
2464 }
2465}