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