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