Skip to main content

msb_krun_vmm/
resources.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//#![deny(warnings)]
5
6#[cfg(feature = "tee")]
7use std::fs::File;
8#[cfg(feature = "tee")]
9use std::io::BufReader;
10#[cfg(not(target_os = "windows"))]
11use std::os::fd::RawFd;
12use std::path::PathBuf;
13use std::time::Duration;
14
15#[cfg(feature = "tee")]
16use serde::{Deserialize, Serialize};
17
18#[cfg(feature = "blk")]
19use crate::vmm_config::block::{BlockBuilder, BlockConfigError, BlockDeviceConfig};
20use crate::vmm_config::external_kernel::ExternalKernel;
21use crate::vmm_config::firmware::FirmwareConfig;
22#[cfg(not(feature = "tee"))]
23use crate::vmm_config::fs::*;
24use crate::vmm_config::kernel_bundle::InitrdBundle;
25use crate::vmm_config::kernel_bundle::{KernelBundle, KernelBundleError};
26#[cfg(feature = "tee")]
27use crate::vmm_config::kernel_bundle::{QbootBundle, QbootBundleError};
28use crate::vmm_config::kernel_cmdline::{KernelCmdlineConfig, KernelCmdlineConfigError};
29use crate::vmm_config::machine_config::HostCpuId;
30use crate::vmm_config::machine_config::{VmConfig, VmConfigError};
31#[cfg(feature = "net")]
32use crate::vmm_config::net::{NetBuilder, NetworkInterfaceConfig, NetworkInterfaceError};
33use crate::vmm_config::vsock::*;
34use crate::vstate::VcpuConfig;
35#[cfg(feature = "gpu")]
36use devices::virtio::display::DisplayInfo;
37#[cfg(feature = "tee")]
38use kbs_types::Tee;
39#[cfg(feature = "gpu")]
40use krun_display::DisplayBackend;
41use utils::metrics::MetricsWriter;
42
43type Result<E> = std::result::Result<(), E>;
44
45#[cfg(target_os = "windows")]
46pub use crate::vmm_config::vsock::TsiFlags;
47#[cfg(not(target_os = "windows"))]
48pub use devices::virtio::TsiFlags;
49
50/// Errors encountered when configuring microVM resources.
51#[derive(Debug)]
52pub enum Error {
53    /// JSON is invalid.
54    InvalidJson,
55    /// Boot source configuration error.
56    KernelCmdline(KernelCmdlineConfigError),
57    /// Error opening TEE config file.
58    #[cfg(feature = "tee")]
59    OpenTeeConfig(std::io::Error),
60    /// Error parsing TEE config file.
61    #[cfg(feature = "tee")]
62    ParseTeeConfig(serde_json::Error),
63    /// microVM vCpus or memory configuration error.
64    VmConfig(VmConfigError),
65    /// Vsock device configuration error.
66    VsockDevice(VsockConfigError),
67}
68
69#[cfg(feature = "tee")]
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct TeeConfig {
72    pub workload_id: String,
73    pub cpus: u8,
74    pub ram_mib: usize,
75    pub tee: Tee,
76    pub tee_data: String,
77    pub attestation_url: String,
78}
79
80#[cfg(feature = "tee")]
81impl Default for TeeConfig {
82    fn default() -> Self {
83        Self {
84            workload_id: "".to_string(),
85            cpus: 0,
86            ram_mib: 0,
87            tee: Tee::Sev,
88            tee_data: "".to_string(),
89            attestation_url: "".to_string(),
90        }
91    }
92}
93
94#[cfg(not(target_os = "windows"))]
95pub struct SerialConsoleConfig {
96    pub input_fd: RawFd,
97    pub output_fd: RawFd,
98}
99
100#[cfg(not(target_os = "windows"))]
101pub struct DefaultVirtioConsoleConfig {
102    pub input_fd: RawFd,
103    pub output_fd: RawFd,
104    pub err_fd: RawFd,
105}
106
107#[cfg(not(target_os = "windows"))]
108pub enum VirtioConsoleConfigMode {
109    Autoconfigure(DefaultVirtioConsoleConfig),
110    Explicit(Vec<PortConfig>),
111}
112
113#[cfg(target_os = "windows")]
114pub enum VirtioConsoleConfigMode {
115    Explicit(Vec<PortConfig>),
116}
117
118#[cfg(not(target_os = "windows"))]
119pub enum PortConfig {
120    Tty {
121        name: String,
122        tty_fd: RawFd,
123    },
124    InOut {
125        name: String,
126        input_fd: RawFd,
127        output_fd: RawFd,
128    },
129    Custom {
130        name: String,
131        input: Box<dyn devices::virtio::port_io::PortInput + Send>,
132        output: Box<dyn devices::virtio::port_io::PortOutput + Send>,
133    },
134}
135
136#[cfg(target_os = "windows")]
137pub enum PortConfig {
138    ConsoleOutputFile { path: PathBuf },
139    NamedPipe { name: String, pipe_name: String },
140}
141
142/// Configuration for the vsock device
143#[derive(Debug, Default, Clone, Eq, PartialEq)]
144pub enum VsockConfig {
145    /// Default behavior - vsock created implicitly with heuristics-based TSI
146    #[default]
147    Implicit,
148    /// Explicit configuration with specified TSI features
149    Explicit { tsi_flags: TsiFlags },
150    /// Vsock device disabled
151    Disabled,
152}
153
154/// A fully resolved guest NUMA description supplied by the embedding runtime.
155///
156/// The VMM intentionally knows nothing about higher-level policies such as `auto` or
157/// `prefer_single`; it only validates and realizes this concrete topology.
158#[derive(Clone, Debug, PartialEq, Eq)]
159pub struct NumaTopology {
160    /// Dense guest proximity domains in guest-node order.
161    pub nodes: Vec<NumaNodeConfig>,
162    /// Complete square distance matrix expressed with dense guest node IDs.
163    pub distances: Vec<NumaDistance>,
164}
165
166/// One dense guest proximity domain and the host policy for its backing memory.
167#[derive(Clone, Debug, PartialEq, Eq)]
168pub struct NumaNodeConfig {
169    /// Dense zero-based guest proximity-domain identifier.
170    pub guest_node_id: u16,
171    /// Possible vCPU indices associated with this guest node.
172    pub vcpu_indices: Vec<u8>,
173    /// Memory available to the guest at boot, in MiB.
174    pub memory_mib: usize,
175    /// Maximum memory promised to this node after live growth, in MiB.
176    pub max_memory_mib: usize,
177    /// Host policy used for boot RAM and reserved hotplug capacity.
178    pub host_memory: HostMemoryPolicy,
179}
180
181/// One entry in the dense guest NUMA distance matrix.
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub struct NumaDistance {
184    /// Source guest proximity domain.
185    pub from: u16,
186    /// Destination guest proximity domain.
187    pub to: u16,
188    /// Relative distance, with `10` required for local entries.
189    pub value: u8,
190}
191
192/// Host backing policy for guest RAM belonging to a resolved node.
193#[derive(Clone, Debug, PartialEq, Eq)]
194pub enum HostMemoryPolicy {
195    /// Preserve the operating system's ordinary allocation policy.
196    Inherit,
197    /// Restrict future Linux page faults to the selected host nodes.
198    Bind {
199        /// Absolute Linux host NUMA node IDs included in the binding mask.
200        host_nodes: Vec<u32>,
201    },
202    /// Prefer the selected Linux host nodes while allowing page faults to spill elsewhere under
203    /// pressure. If the kernel lacks soft multi-node preference, ordinary host policy is retained.
204    PreferredMany {
205        /// Absolute Linux host NUMA node IDs included in the preference mask.
206        host_nodes: Vec<u32>,
207    },
208    /// Prefer a Windows NUMA node while allowing the host to fall back.
209    Preferred {
210        /// Absolute Windows host NUMA node ID preferred for future page faults.
211        host_node: u32,
212    },
213}
214
215/// The host placement that was actually established before guest execution begins.
216#[derive(Clone, Debug, PartialEq, Eq)]
217pub struct PlacementReport {
218    /// One result for every possible vCPU, in guest-vCPU order.
219    pub vcpus: Vec<VcpuPlacementResult>,
220    /// Result of applying the requested host-memory policy.
221    pub memory: MemoryPlacementResult,
222}
223
224/// The effective host placement of one guest vCPU.
225#[derive(Clone, Debug, PartialEq, Eq)]
226pub enum VcpuPlacementResult {
227    /// The vCPU thread was successfully pinned to the requested host processor.
228    Pinned {
229        /// Guest vCPU index.
230        vcpu_index: u8,
231        /// Host processor selected by the caller.
232        host_cpu: HostCpuId,
233    },
234    /// The vCPU remains under the host scheduler's inherited policy.
235    Inherited {
236        /// Guest vCPU index.
237        vcpu_index: u8,
238        /// Requested host processor, when affinity was attempted.
239        requested_host_cpu: Option<HostCpuId>,
240        /// Why the requested affinity was not established.
241        reason: Option<String>,
242    },
243}
244
245/// The effective host-memory placement of ordinary guest RAM and hotplug capacity.
246#[derive(Clone, Debug, PartialEq, Eq)]
247pub enum MemoryPlacementResult {
248    /// No managed memory policy was requested.
249    Inherited,
250    /// The requested managed policy was established.
251    Applied,
252    /// A best-effort policy could not be retained and memory uses inherited placement.
253    Fallback {
254        /// Why the managed policy was abandoned.
255        reason: String,
256    },
257    /// Only part of the requested memory policy remains effective after best-effort fallback.
258    Partial {
259        /// Why the host could not establish one uniform policy.
260        reason: String,
261    },
262}
263
264/// A data structure that encapsulates the device configurations
265/// held in the Vmm.
266pub struct VmResources {
267    /// The vCpu and memory configuration for this microVM.
268    vm_config: VmConfig,
269    /// Resolved host logical processor for every possible vCPU thread.
270    #[cfg(any(target_os = "linux", target_os = "windows"))]
271    pub vcpu_affinity: Option<Vec<HostCpuId>>,
272    /// Whether failure to apply the resolved vCPU affinity must abort VM startup.
273    #[cfg(any(target_os = "linux", target_os = "windows"))]
274    pub vcpu_affinity_required: bool,
275    /// Resolved guest and host memory topology. `None` retains the legacy memory path exactly.
276    pub numa_topology: Option<NumaTopology>,
277    /// The firmware to be loaded into the microVM.
278    pub firmware_config: Option<FirmwareConfig>,
279    /// The kernel command line for this microVM.
280    pub kernel_cmdline: KernelCmdlineConfig,
281    /// The parameters for the kernel bundle to be loaded in this microVM.
282    pub kernel_bundle: Option<KernelBundle>,
283    /// The path to an external kernel, as an alternative to KernelBundle.
284    pub external_kernel: Option<ExternalKernel>,
285    /// The parameters for the qboot bundle to be loaded in this microVM.
286    #[cfg(feature = "tee")]
287    pub qboot_bundle: Option<QbootBundle>,
288    /// The parameters for the initrd bundle to be loaded in this microVM.
289    pub initrd_bundle: Option<InitrdBundle>,
290    /// The fs device.
291    #[cfg(not(feature = "tee"))]
292    pub fs: Vec<FsDeviceConfig>,
293    /// Custom filesystem devices.
294    #[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
295    pub custom_fs: Vec<CustomFsDeviceConfig>,
296    /// The vsock device.
297    pub vsock: VsockBuilder,
298    /// The virtio-blk device.
299    #[cfg(feature = "blk")]
300    pub block: BlockBuilder,
301    /// The network devices builder.
302    #[cfg(feature = "net")]
303    pub net: NetBuilder,
304    /// TEE configuration
305    #[cfg(feature = "tee")]
306    pub tee_config: TeeConfig,
307    /// Flags for the virtio-gpu device.
308    pub gpu_virgl_flags: Option<u32>,
309    pub gpu_shm_size: Option<usize>,
310    #[cfg(feature = "gpu")]
311    pub display_backend: Option<DisplayBackend<'static>>,
312    #[cfg(feature = "gpu")]
313    pub displays: Vec<DisplayInfo>,
314    #[cfg(feature = "input")]
315    pub input_backends: Vec<(
316        krun_input::InputConfigBackend<'static>,
317        krun_input::InputEventProviderBackend<'static>,
318    )>,
319    #[cfg(feature = "snd")]
320    /// Enable the virtio-snd device.
321    pub snd_device: bool,
322    /// File to send console output.
323    pub console_output: Option<PathBuf>,
324    /// SMBIOS OEM Strings
325    pub smbios_oem_strings: Option<Vec<String>>,
326    /// Whether to enable nested virtualization.
327    pub nested_enabled: bool,
328    /// Whether to enable split irqchip
329    pub split_irqchip: bool,
330    /// Shared metrics state for VMM and device counters.
331    pub metrics: MetricsWriter,
332    /// Whether to attach the virtio-balloon device.
333    pub enable_balloon: bool,
334    /// The virtio-mem device backing live memory resize, created by the API
335    /// layer when max memory exceeds boot memory. The builder places the
336    /// hotplug region and attaches the device; the API layer keeps a clone as
337    /// the runtime control handle.
338    #[cfg(not(feature = "tee"))]
339    pub mem_device: Option<std::sync::Arc<std::sync::Mutex<devices::virtio::Mem>>>,
340    /// The CPU capacity device backing live CPU resize, created by the API
341    /// layer when max vCPUs exceed the boot count. Also the source of the
342    /// enforcement state every vCPU run loop consults.
343    #[cfg(not(feature = "tee"))]
344    pub cpu_device: Option<std::sync::Arc<std::sync::Mutex<devices::virtio::Cpu>>>,
345    /// Guest memory stats polling interval for the virtio-balloon device.
346    pub balloon_stats_interval: Option<Duration>,
347    /// Whether to attach the virtio-rng device.
348    pub enable_rng: bool,
349    /// Whether to attach the private microsandbox metrics device.
350    pub enable_msb_metrics: bool,
351    /// Do not create an implicit console device in the guest
352    pub disable_implicit_console: bool,
353    /// The console id to use for console= in the kernel cmdline
354    pub kernel_console: Option<String>,
355    /// Serial consoles to attach to the guest
356    #[cfg(not(target_os = "windows"))]
357    pub serial_consoles: Vec<SerialConsoleConfig>,
358    /// Virtio consoles to attach to the guest
359    pub virtio_consoles: Vec<VirtioConsoleConfigMode>,
360}
361
362impl Default for VmResources {
363    fn default() -> Self {
364        Self {
365            vm_config: VmConfig::default(),
366            #[cfg(any(target_os = "linux", target_os = "windows"))]
367            vcpu_affinity: None,
368            #[cfg(any(target_os = "linux", target_os = "windows"))]
369            vcpu_affinity_required: true,
370            numa_topology: None,
371            firmware_config: None,
372            kernel_cmdline: KernelCmdlineConfig::default(),
373            kernel_bundle: None,
374            external_kernel: None,
375            #[cfg(feature = "tee")]
376            qboot_bundle: None,
377            initrd_bundle: None,
378            #[cfg(not(feature = "tee"))]
379            fs: Vec::new(),
380            #[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
381            custom_fs: Vec::new(),
382            vsock: VsockBuilder::default(),
383            #[cfg(feature = "blk")]
384            block: BlockBuilder::default(),
385            #[cfg(feature = "net")]
386            net: NetBuilder::default(),
387            #[cfg(feature = "tee")]
388            tee_config: TeeConfig::default(),
389            gpu_virgl_flags: None,
390            gpu_shm_size: None,
391            #[cfg(feature = "gpu")]
392            display_backend: None,
393            #[cfg(feature = "gpu")]
394            displays: Vec::new(),
395            #[cfg(feature = "input")]
396            input_backends: Vec::new(),
397            #[cfg(feature = "snd")]
398            snd_device: false,
399            console_output: None,
400            smbios_oem_strings: None,
401            nested_enabled: false,
402            split_irqchip: false,
403            metrics: MetricsWriter::default(),
404            enable_balloon: true,
405            #[cfg(not(feature = "tee"))]
406            mem_device: None,
407            #[cfg(not(feature = "tee"))]
408            cpu_device: None,
409            balloon_stats_interval: Some(Duration::from_secs(1)),
410            enable_rng: true,
411            enable_msb_metrics: true,
412            disable_implicit_console: false,
413            kernel_console: None,
414            #[cfg(not(target_os = "windows"))]
415            serial_consoles: Vec::new(),
416            virtio_consoles: Vec::new(),
417        }
418    }
419}
420
421impl VmResources {
422    /// Returns a VcpuConfig based on the vm config.
423    pub fn vcpu_config(&self) -> VcpuConfig {
424        // The unwraps are ok to use because the values are initialized using defaults if not
425        // supplied by the user.
426        let vcpu_count = self.vm_config().vcpu_count.unwrap();
427        VcpuConfig {
428            vcpu_count,
429            max_vcpu_count: self.vm_config().max_vcpu_count.unwrap_or(vcpu_count),
430            ht_enabled: self.vm_config().ht_enabled.unwrap(),
431            cpu_template: self.vm_config().cpu_template,
432        }
433    }
434
435    /// Returns the VmConfig.
436    pub fn vm_config(&self) -> &VmConfig {
437        &self.vm_config
438    }
439
440    /// Set the machine configuration of the microVM.
441    pub fn set_vm_config(&mut self, machine_config: &VmConfig) -> Result<VmConfigError> {
442        if machine_config.vcpu_count == Some(0) {
443            return Err(VmConfigError::InvalidVcpuCount);
444        }
445
446        if machine_config.mem_size_mib == Some(0) {
447            return Err(VmConfigError::InvalidMemorySize);
448        }
449
450        let ht_enabled = machine_config
451            .ht_enabled
452            .unwrap_or_else(|| self.vm_config.ht_enabled.unwrap());
453
454        let vcpu_count_value = machine_config
455            .vcpu_count
456            .unwrap_or_else(|| self.vm_config.vcpu_count.unwrap());
457
458        // If hyperthreading is enabled or is to be enabled in this call
459        // only allow vcpu count to be 1 or even.
460        if ht_enabled && vcpu_count_value > 1 && vcpu_count_value % 2 == 1 {
461            return Err(VmConfigError::InvalidVcpuCount);
462        }
463
464        if let Some(max_vcpu_count) = machine_config.max_vcpu_count {
465            if max_vcpu_count < vcpu_count_value
466                || max_vcpu_count > crate::vmm_config::machine_config::MAX_SUPPORTED_VCPUS
467            {
468                return Err(VmConfigError::InvalidMaxVcpuCount);
469            }
470            if ht_enabled && max_vcpu_count > 1 && max_vcpu_count % 2 == 1 {
471                return Err(VmConfigError::InvalidMaxVcpuCount);
472            }
473            // Booting a wider possible topology than the online count relies on parked
474            // vCPUs waiting for wake-ups (INIT/SIPI on x86, PSCI on aarch64) and on
475            // non-TEE boot topology tables. On x86 Windows the AP startup router
476            // provides exactly that and on aarch64 Windows WHP's in-hypervisor PSCI
477            // does, so only the still-unwired platforms reject.
478            #[cfg(any(target_arch = "riscv64", feature = "tee"))]
479            if max_vcpu_count > vcpu_count_value {
480                return Err(VmConfigError::MaxCapacityUnsupported);
481            }
482        }
483
484        if let Some(max_mem_size_mib) = machine_config.max_mem_size_mib {
485            let mem_size_mib = machine_config
486                .mem_size_mib
487                .unwrap_or_else(|| self.vm_config.mem_size_mib.unwrap());
488            if max_mem_size_mib < mem_size_mib {
489                return Err(VmConfigError::InvalidMaxMemorySize);
490            }
491        }
492
493        // Update all the fields that have a new value.
494        self.vm_config.vcpu_count = Some(vcpu_count_value);
495        self.vm_config.ht_enabled = Some(ht_enabled);
496        self.vm_config.max_vcpu_count = machine_config.max_vcpu_count;
497        self.vm_config.max_mem_size_mib = machine_config.max_mem_size_mib;
498
499        if machine_config.mem_size_mib.is_some() {
500            self.vm_config.mem_size_mib = machine_config.mem_size_mib;
501        }
502        let memory_total_bytes = self
503            .vm_config
504            .mem_size_mib
505            .unwrap_or(128)
506            .saturating_mul(1024)
507            .saturating_mul(1024) as u64;
508        self.metrics.set_memory_total_bytes(memory_total_bytes);
509
510        if machine_config.cpu_template.is_some() {
511            self.vm_config.cpu_template = machine_config.cpu_template;
512        }
513
514        Ok(())
515    }
516
517    /// Set the guest kernel cmdline configuration.
518    pub fn set_kernel_cmdline(
519        &mut self,
520        kernel_cmdline_cfg: KernelCmdlineConfig,
521    ) -> Result<KernelCmdlineConfigError> {
522        self.kernel_cmdline = kernel_cmdline_cfg;
523        Ok(())
524    }
525
526    pub fn kernel_bundle(&self) -> Option<&KernelBundle> {
527        self.kernel_bundle.as_ref()
528    }
529
530    pub fn set_kernel_bundle(&mut self, kernel_bundle: KernelBundle) -> Result<KernelBundleError> {
531        // Safe because this call just returns the page size and doesn't have any side effects.
532        let page_size = utils::page_size();
533
534        if kernel_bundle.host_addr == 0 || (kernel_bundle.host_addr as usize) & (page_size - 1) != 0
535        {
536            return Err(KernelBundleError::InvalidHostAddress);
537        }
538
539        if (kernel_bundle.guest_addr as usize) & (page_size - 1) != 0 {
540            return Err(KernelBundleError::InvalidGuestAddress);
541        }
542
543        self.kernel_bundle = Some(kernel_bundle);
544        Ok(())
545    }
546
547    pub fn external_kernel(&self) -> Option<&ExternalKernel> {
548        self.external_kernel.as_ref()
549    }
550
551    pub fn set_external_kernel(&mut self, external_kernel: ExternalKernel) {
552        self.external_kernel = Some(external_kernel);
553    }
554
555    pub fn set_firmware_config(&mut self, firmware_config: FirmwareConfig) {
556        self.firmware_config = Some(firmware_config);
557    }
558
559    #[cfg(feature = "tee")]
560    pub fn qboot_bundle(&self) -> Option<&QbootBundle> {
561        self.qboot_bundle.as_ref()
562    }
563
564    #[cfg(feature = "tee")]
565    pub fn set_qboot_bundle(&mut self, qboot_bundle: QbootBundle) -> Result<QbootBundleError> {
566        if qboot_bundle.size != 0x10000 {
567            return Err(QbootBundleError::InvalidSize);
568        }
569
570        self.qboot_bundle = Some(qboot_bundle);
571        Ok(())
572    }
573
574    pub fn initrd_bundle(&self) -> Option<&InitrdBundle> {
575        self.initrd_bundle.as_ref()
576    }
577
578    pub fn set_initrd_bundle(&mut self, initrd_bundle: InitrdBundle) -> Result<KernelBundleError> {
579        self.initrd_bundle = Some(initrd_bundle);
580        Ok(())
581    }
582
583    #[cfg(not(feature = "tee"))]
584    pub fn add_fs_device(&mut self, config: FsDeviceConfig) {
585        self.fs.push(config)
586    }
587
588    #[cfg(feature = "blk")]
589    pub fn add_block_device(&mut self, config: BlockDeviceConfig) -> Result<BlockConfigError> {
590        self.block.insert(config, self.metrics.clone())
591    }
592
593    /// Adds a block device with an optional per-device hard dirty-data budget.
594    #[cfg(feature = "blk")]
595    pub fn add_block_device_with_writeback_limit(
596        &mut self,
597        config: BlockDeviceConfig,
598        writeback_limit_bytes: Option<u64>,
599    ) -> Result<BlockConfigError> {
600        self.block
601            .insert_with_writeback_limit(config, writeback_limit_bytes, self.metrics.clone())
602    }
603
604    /// Adds a block device with an optional live dirty-data budget.
605    #[cfg(feature = "blk")]
606    pub fn add_block_device_with_writeback_limit_handle(
607        &mut self,
608        config: BlockDeviceConfig,
609        writeback_limit: Option<devices::virtio::block::WritebackLimit>,
610    ) -> Result<BlockConfigError> {
611        self.block
612            .insert_with_writeback_limit_handle(config, writeback_limit, self.metrics.clone())
613    }
614
615    /// Sets a vsock device to be attached when the VM starts.
616    pub fn set_vsock_device(&mut self, config: VsockDeviceConfig) -> Result<VsockConfigError> {
617        self.vsock.insert(config)
618    }
619
620    pub fn set_gpu_virgl_flags(&mut self, virgl_flags: u32) {
621        self.gpu_virgl_flags = Some(virgl_flags);
622    }
623
624    pub fn set_gpu_shm_size(&mut self, shm_size: usize) {
625        self.gpu_shm_size = Some(shm_size);
626    }
627
628    #[cfg(feature = "snd")]
629    pub fn set_snd_device(&mut self, enabled: bool) {
630        self.snd_device = enabled;
631    }
632
633    pub fn set_console_output(&mut self, console_output: PathBuf) {
634        self.console_output = Some(console_output);
635    }
636
637    /// Sets a network device to be attached when the VM starts.
638    #[cfg(feature = "net")]
639    pub fn add_network_interface(
640        &mut self,
641        config: NetworkInterfaceConfig,
642    ) -> Result<NetworkInterfaceError> {
643        self.net.insert(config)
644    }
645
646    #[cfg(feature = "tee")]
647    pub fn tee_config(&self) -> &TeeConfig {
648        &self.tee_config
649    }
650
651    #[cfg(feature = "tee")]
652    pub fn set_tee_config(&mut self, filepath: PathBuf) -> Result<Error> {
653        let file = File::open(filepath.as_path()).map_err(Error::OpenTeeConfig)?;
654        let reader = BufReader::new(file);
655        let tee_config: TeeConfig =
656            serde_json::from_reader(reader).map_err(Error::ParseTeeConfig)?;
657
658        // Override VmConfig with TeeConfig values
659        self.set_vm_config(&VmConfig {
660            vcpu_count: Some(tee_config.cpus),
661            mem_size_mib: Some(tee_config.ram_mib),
662            max_vcpu_count: None,
663            max_mem_size_mib: None,
664            ht_enabled: Some(false),
665            cpu_template: None,
666        })
667        .map_err(Error::VmConfig)?;
668
669        self.tee_config = tee_config;
670
671        Ok(())
672    }
673}
674
675#[cfg(all(test, not(target_os = "windows")))]
676mod tests {
677    #[cfg(feature = "gpu")]
678    use crate::resources::DisplayBackendConfig;
679    use crate::resources::VmResources;
680    use crate::vmm_config::machine_config::{CpuFeaturesTemplate, VmConfig, VmConfigError};
681    use crate::vmm_config::vsock::tests::{default_config, TempSockFile};
682    use crate::vstate::VcpuConfig;
683    use utils::tempfile::TempFile;
684
685    fn default_vm_resources() -> VmResources {
686        VmResources::default()
687    }
688
689    #[test]
690    fn test_vcpu_config() {
691        let vm_resources = default_vm_resources();
692        let expected_vcpu_config = VcpuConfig {
693            vcpu_count: vm_resources.vm_config().vcpu_count.unwrap(),
694            max_vcpu_count: vm_resources.vm_config().vcpu_count.unwrap(),
695            ht_enabled: vm_resources.vm_config().ht_enabled.unwrap(),
696            cpu_template: vm_resources.vm_config().cpu_template,
697        };
698
699        let vcpu_config = vm_resources.vcpu_config();
700        assert_eq!(vcpu_config, expected_vcpu_config);
701    }
702
703    #[test]
704    fn test_vm_config() {
705        let vm_resources = default_vm_resources();
706        let expected_vm_cfg = VmConfig::default();
707
708        assert_eq!(vm_resources.vm_config(), &expected_vm_cfg);
709    }
710
711    #[test]
712    fn test_set_vm_config() {
713        let mut vm_resources = default_vm_resources();
714        let mut aux_vm_config = VmConfig {
715            vcpu_count: Some(32),
716            mem_size_mib: Some(512),
717            max_vcpu_count: None,
718            max_mem_size_mib: None,
719            ht_enabled: Some(true),
720            cpu_template: Some(CpuFeaturesTemplate::T2),
721        };
722
723        assert_ne!(vm_resources.vm_config, aux_vm_config);
724        vm_resources.set_vm_config(&aux_vm_config).unwrap();
725        assert_eq!(vm_resources.vm_config, aux_vm_config);
726
727        // Invalid vcpu count.
728        aux_vm_config.vcpu_count = Some(0);
729        assert_eq!(
730            vm_resources.set_vm_config(&aux_vm_config),
731            Err(VmConfigError::InvalidVcpuCount)
732        );
733        aux_vm_config.vcpu_count = Some(33);
734        assert_eq!(
735            vm_resources.set_vm_config(&aux_vm_config),
736            Err(VmConfigError::InvalidVcpuCount)
737        );
738        aux_vm_config.vcpu_count = Some(32);
739
740        // Invalid mem_size_mib.
741        aux_vm_config.mem_size_mib = Some(0);
742        assert_eq!(
743            vm_resources.set_vm_config(&aux_vm_config),
744            Err(VmConfigError::InvalidMemorySize)
745        );
746    }
747
748    #[test]
749    fn test_set_vm_config_max_capacity() {
750        let mut vm_resources = default_vm_resources();
751        let mut vm_config = VmConfig {
752            vcpu_count: Some(2),
753            mem_size_mib: Some(1024),
754            max_vcpu_count: Some(8),
755            max_mem_size_mib: Some(8192),
756            ht_enabled: Some(false),
757            cpu_template: None,
758        };
759
760        vm_resources.set_vm_config(&vm_config).unwrap();
761        let vcpu_config = vm_resources.vcpu_config();
762        assert_eq!(vcpu_config.vcpu_count, 2);
763        assert_eq!(vcpu_config.max_vcpu_count, 8);
764
765        // Without explicit capacity, max tracks the effective count.
766        vm_config.max_vcpu_count = None;
767        vm_config.max_mem_size_mib = None;
768        vm_resources.set_vm_config(&vm_config).unwrap();
769        assert_eq!(vm_resources.vcpu_config().max_vcpu_count, 2);
770
771        // Max vcpus below the effective count.
772        vm_config.max_vcpu_count = Some(1);
773        assert_eq!(
774            vm_resources.set_vm_config(&vm_config),
775            Err(VmConfigError::InvalidMaxVcpuCount)
776        );
777
778        // Max vcpus above the supported limit.
779        vm_config.max_vcpu_count = Some(65);
780        assert_eq!(
781            vm_resources.set_vm_config(&vm_config),
782            Err(VmConfigError::InvalidMaxVcpuCount)
783        );
784
785        // Odd max vcpus with hyperthreading enabled.
786        vm_config.max_vcpu_count = Some(3);
787        vm_config.ht_enabled = Some(true);
788        assert_eq!(
789            vm_resources.set_vm_config(&vm_config),
790            Err(VmConfigError::InvalidMaxVcpuCount)
791        );
792        vm_config.ht_enabled = Some(false);
793
794        // Max memory below the boot memory size.
795        vm_config.max_vcpu_count = Some(8);
796        vm_config.max_mem_size_mib = Some(512);
797        assert_eq!(
798            vm_resources.set_vm_config(&vm_config),
799            Err(VmConfigError::InvalidMaxMemorySize)
800        );
801    }
802
803    #[test]
804    fn test_set_vsock_device() {
805        let mut vm_resources = default_vm_resources();
806        let tmp_sock_file = TempSockFile::new(TempFile::new().unwrap());
807        let new_vsock_cfg = default_config(&tmp_sock_file);
808        assert!(vm_resources.vsock.get().is_none());
809        vm_resources
810            .set_vsock_device(new_vsock_cfg.clone())
811            .unwrap();
812        let actual_vsock_cfg = vm_resources.vsock.get().unwrap();
813        assert_eq!(
814            actual_vsock_cfg.lock().unwrap().id(),
815            &new_vsock_cfg.vsock_id
816        );
817    }
818}