Skip to main content

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