Skip to main content

qemu_command_builder/args/
accel.rs

1use crate::parsers::ARG_ACCEL;
2use bon::Builder;
3use proptest_derive::Arbitrary;
4use std::fmt::{Display, Formatter};
5use std::str::FromStr;
6
7use crate::common::*;
8use crate::parsers::DELIM_COMMA;
9use crate::qao;
10use crate::shell_path::ShellPath;
11use crate::to_command::{ToArg, ToCommand};
12
13const KEY_IGD_PASSTHRU: &str = "igd-passthru=";
14const KEY_KERNEL_IRQCHIP: &str = "kernel-irqchip=";
15const KEY_KVM_SHADOW_MEM: &str = "kvm-shadow-mem=";
16const KEY_ONE_INSN_PER_TB: &str = "one-insn-per-tb=";
17const KEY_SPLIT_WX: &str = "split-wx=";
18const KEY_TB_SIZE: &str = "tb-size=";
19const KEY_DIRTY_RING_SIZE: &str = "dirty-ring-size=";
20const KEY_EAGER_SPLIT_SIZE: &str = "eager-split-size=";
21const KEY_NOTIFY_VMEXIT: &str = "notify-vmexit=";
22const KEY_THREAD: &str = "thread=";
23const KEY_DEVICE: &str = "device=";
24const KEY_HYPERV: &str = "hyperv=";
25
26#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Arbitrary)]
27/// QEMU `kernel-irqchip=` values for `-accel`.
28pub enum OnOffSplit {
29    #[default]
30    On,
31    Off,
32    Split,
33}
34
35impl Display for OnOffSplit {
36    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37        write!(f, "{}", self.to_arg())
38    }
39}
40
41impl FromStr for OnOffSplit {
42    type Err = ();
43
44    fn from_str(s: &str) -> Result<Self, Self::Err> {
45        match s {
46            "on" => Ok(OnOffSplit::On),
47            "off" => Ok(OnOffSplit::Off),
48            "split" => Ok(OnOffSplit::Split),
49            _ => Err(()),
50        }
51    }
52}
53impl ToArg for OnOffSplit {
54    fn to_arg(&self) -> &str {
55        match self {
56            OnOffSplit::On => "on",
57            OnOffSplit::Off => "off",
58            OnOffSplit::Split => "split",
59        }
60    }
61}
62
63#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
64/// QEMU `thread=` values for TCG acceleration.
65pub enum TCGThreadType {
66    Single,
67    Multi,
68}
69
70impl FromStr for TCGThreadType {
71    type Err = ();
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        match s {
75            "single" => Ok(TCGThreadType::Single),
76            "multi" => Ok(TCGThreadType::Multi),
77            _ => Err(()),
78        }
79    }
80}
81impl Display for TCGThreadType {
82    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83        match self {
84            TCGThreadType::Single => write!(f, "single"),
85            TCGThreadType::Multi => write!(f, "multi"),
86        }
87    }
88}
89
90#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
91/// QEMU `notify-vmexit=` values.
92///
93/// The `run` mode may also carry the companion `notify-window=` property.
94pub enum NotifyVMExit {
95    Run(Option<usize>),
96    InternalError,
97    Disable,
98}
99
100impl FromStr for NotifyVMExit {
101    type Err = String;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        match s {
105            "run" => Ok(NotifyVMExit::Run(None)),
106            "internal-error" => Ok(NotifyVMExit::InternalError),
107            "disable" => Ok(NotifyVMExit::Disable),
108            _ => Err(format!("invalid notify-vmexit value: {s}")),
109        }
110    }
111}
112impl Display for NotifyVMExit {
113    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114        match self {
115            NotifyVMExit::Run(n) => {
116                if let Some(n) = n {
117                    write!(f, "run,notify-window={}", n)
118                } else {
119                    write!(f, "run")
120                }
121            }
122            NotifyVMExit::InternalError => write!(f, "internal-error"),
123            NotifyVMExit::Disable => write!(f, "disable"),
124        }
125    }
126}
127
128/// This is used to enable an accelerator. Depending on the target
129/// architecture, kvm, xen, hvf, nvmm, whpx or tcg can be available. By
130/// default, tcg is used. If there is more than one accelerator
131/// specified, the next one is used if the previous one fails to
132/// initialize.
133#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
134pub struct Accel {
135    accel_type: AccelType,
136
137    /// When Xen is in use, this option controls whether Intel
138    /// integrated graphics devices can be passed through to the guest
139    /// (default=off)
140    igd_passthru: Option<OnOffDefaultOff>,
141
142    /// Controls KVM in-kernel irqchip support. The default is full
143    /// acceleration of the interrupt controllers. On x86, split irqchip
144    /// reduces the kernel attack surface, at a performance cost for
145    /// non-MSI interrupts. Disabling the in-kernel irqchip completely
146    /// is not recommended except for debugging purposes.
147    kernel_irqchip: Option<OnOffSplit>,
148
149    /// Defines the size of the KVM shadow MMU.
150    kvm_shadow_mem: Option<usize>,
151
152    /// Makes the TCG accelerator put only one guest instruction into
153    /// each translation block. This slows down emulation a lot, but
154    /// can be useful in some situations, such as when trying to analyse
155    /// the logs produced by the ``-d`` option.
156    one_insn_per_tb: Option<OnOff>,
157
158    /// Controls the use of split w^x mapping for the TCG code generation
159    /// buffer. Some operating systems require this to be enabled, and in
160    /// such a case this will default on. On other operating systems, this
161    /// will default off, but one may enable this for testing or debugging.
162    split_wx: Option<OnOff>,
163
164    /// Controls the size (in MiB) of the TCG translation block cache.
165    tb_size: Option<usize>,
166
167    /// When the KVM accelerator is used, it controls the size of the per-vCPU
168    /// dirty page ring buffer (number of entries for each vCPU). It should
169    /// be a value that is power of two, and it should be 1024 or bigger (but
170    /// still less than the maximum value that the kernel supports).  4096
171    /// could be a good initial value if you have no idea which is the best.
172    /// Set this value to 0 to disable the feature.  By default, this feature
173    /// is disabled (dirty-ring-size=0).  When enabled, KVM will instead
174    /// record dirty pages in a bitmap.
175    dirty_ring_size: Option<usize>,
176
177    /// KVM implements dirty page logging at the PAGE_SIZE granularity and
178    /// enabling dirty-logging on a huge-page requires breaking it into
179    /// PAGE_SIZE pages in the first place. KVM on ARM does this splitting
180    /// lazily by default. There are performance benefits in doing huge-page
181    /// split eagerly, especially in situations where TLBI costs associated
182    /// with break-before-make sequences are considerable and also if guest
183    /// workloads are read intensive. The size here specifies how many pages
184    /// to break at a time and needs to be a valid block size which is
185    /// 1GB/2MB/4KB, 32MB/16KB and 512MB/64KB for 4KB/16KB/64KB PAGE_SIZE
186    /// respectively. Be wary of specifying a higher size as it will have an
187    /// impact on the memory. By default, this feature is disabled
188    /// (eager-split-size=0).
189    eager_split_size: Option<usize>,
190
191    /// Enables or disables notify VM exit support on x86 host and specify
192    /// the corresponding notify window to trigger the VM exit if enabled.
193    /// ``run`` option enables the feature. It does nothing and continue
194    /// if the exit happens. ``internal-error`` option enables the feature.
195    /// It raises a internal error. ``disable`` option doesn't enable the feature.
196    /// This feature can mitigate the CPU stuck issue due to event windows don't
197    /// open up for a specified of time (i.e. notify-window).
198    /// Default: notify-vmexit=run,notify-window=0.
199    notify_vmexit: Option<NotifyVMExit>,
200
201    /// Enable single or multi-threaded TCG
202    thread: Option<TCGThreadType>,
203
204    // Sets the path to the KVM device node. Defaults to ``/dev/kvm``. This
205    // option can be used to pass the KVM device to use via a file descriptor
206    // by setting the value to ``/dev/fdset/NN``.
207    device: Option<ShellPath>,
208
209    /// For the WHPX backend, determines whether to enable Hyper-V
210    /// enlightenments.
211    hyperv: Option<OnOffAuto>,
212}
213
214impl Accel {
215    /// Creates an accelerator configuration for the given backend.
216    pub fn new(accel_type: AccelType) -> Self {
217        Self {
218            accel_type,
219            igd_passthru: None,
220            kernel_irqchip: None,
221            kvm_shadow_mem: None,
222            one_insn_per_tb: None,
223            split_wx: None,
224            tb_size: None,
225            dirty_ring_size: None,
226            eager_split_size: None,
227            notify_vmexit: None,
228            thread: None,
229            device: None,
230            hyperv: None,
231        }
232    }
233}
234
235impl ToCommand for Accel {
236    fn command(&self) -> String {
237        ARG_ACCEL.to_string()
238    }
239
240    fn to_args(&self) -> Vec<String> {
241        let mut args = vec![self.accel_type.to_arg().to_string()];
242
243        qao!(&self.igd_passthru, args, KEY_IGD_PASSTHRU);
244        qao!(&self.kernel_irqchip, args, KEY_KERNEL_IRQCHIP);
245        qao!(&self.kvm_shadow_mem, args, KEY_KVM_SHADOW_MEM);
246        qao!(&self.one_insn_per_tb, args, KEY_ONE_INSN_PER_TB);
247        qao!(&self.split_wx, args, KEY_SPLIT_WX);
248        qao!(&self.tb_size, args, KEY_TB_SIZE);
249        qao!(&self.dirty_ring_size, args, KEY_DIRTY_RING_SIZE);
250        qao!(&self.eager_split_size, args, KEY_EAGER_SPLIT_SIZE);
251        qao!(&self.notify_vmexit, args, KEY_NOTIFY_VMEXIT);
252        qao!(&self.thread, args, KEY_THREAD);
253        if let Some(device) = &self.device {
254            args.push(format!("{}{}", KEY_DEVICE, device.as_ref()));
255        }
256        qao!(&self.hyperv, args, KEY_HYPERV);
257
258        vec![args.join(DELIM_COMMA)]
259    }
260}
261
262impl FromStr for Accel {
263    type Err = String;
264
265    fn from_str(s: &str) -> Result<Self, Self::Err> {
266        let mut parts = s.split(DELIM_COMMA);
267        let accel_token = parts.next().ok_or_else(|| "empty accel argument".to_string())?;
268        let accel_name = accel_token.strip_prefix("accel=").unwrap_or(accel_token);
269        let accel_type = accel_name.parse::<AccelType>().map_err(|_| format!("invalid accel type: {accel_name}"))?;
270
271        let mut accel = Accel::new(accel_type);
272        let mut pending_notify_window = None;
273
274        for part in parts {
275            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid accel option: {part}"))?;
276            match key {
277                "igd-passthru" => {
278                    accel.igd_passthru = Some(value.parse::<OnOffDefaultOff>().map_err(|_| format!("invalid igd-passthru value: {value}"))?);
279                }
280                "kernel-irqchip" => {
281                    accel.kernel_irqchip = Some(value.parse::<OnOffSplit>().map_err(|_| format!("invalid kernel-irqchip value: {value}"))?);
282                }
283                "kvm-shadow-mem" => {
284                    accel.kvm_shadow_mem = Some(value.parse::<usize>().map_err(|e| e.to_string())?);
285                }
286                "one-insn-per-tb" => {
287                    accel.one_insn_per_tb = Some(value.parse::<OnOff>().map_err(|_| format!("invalid one-insn-per-tb value: {value}"))?);
288                }
289                "split-wx" => {
290                    accel.split_wx = Some(value.parse::<OnOff>().map_err(|_| format!("invalid split-wx value: {value}"))?);
291                }
292                "tb-size" => {
293                    accel.tb_size = Some(value.parse::<usize>().map_err(|e| e.to_string())?);
294                }
295                "dirty-ring-size" => {
296                    accel.dirty_ring_size = Some(value.parse::<usize>().map_err(|e| e.to_string())?);
297                }
298                "eager-split-size" => {
299                    accel.eager_split_size = Some(value.parse::<usize>().map_err(|e| e.to_string())?);
300                }
301                "notify-vmexit" => {
302                    accel.notify_vmexit = Some(value.parse::<NotifyVMExit>()?);
303                }
304                "notify-window" => {
305                    pending_notify_window = Some(value.parse::<usize>().map_err(|e| e.to_string())?);
306                }
307                "thread" => {
308                    accel.thread = Some(value.parse::<TCGThreadType>().map_err(|_| format!("invalid thread value: {value}"))?);
309                }
310                "device" => {
311                    accel.device = Some(ShellPath::from(value));
312                }
313                "hyperv" => {
314                    accel.hyperv = Some(value.parse::<OnOffAuto>().map_err(|_| format!("invalid hyperv value: {value}"))?);
315                }
316                other => return Err(format!("unsupported accel option: {other}")),
317            }
318        }
319
320        if let Some(window) = pending_notify_window {
321            accel.notify_vmexit = match accel.notify_vmexit.take() {
322                Some(NotifyVMExit::Run(_)) => Some(NotifyVMExit::Run(Some(window))),
323                Some(other) => return Err(format!("notify-window requires notify-vmexit=run, got {other}")),
324                None => return Err("notify-window requires notify-vmexit=run".to_string()),
325            };
326        }
327
328        Ok(accel)
329    }
330}