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