qemu_command_builder/args/
accel.rs1use 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)]
27pub 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)]
64pub 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)]
91pub 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#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
134pub struct Accel {
135 accel_type: AccelType,
136
137 igd_passthru: Option<OnOffDefaultOff>,
141
142 kernel_irqchip: Option<OnOffSplit>,
148
149 kvm_shadow_mem: Option<usize>,
151
152 one_insn_per_tb: Option<OnOff>,
157
158 split_wx: Option<OnOff>,
163
164 tb_size: Option<usize>,
166
167 dirty_ring_size: Option<usize>,
176
177 eager_split_size: Option<usize>,
190
191 notify_vmexit: Option<NotifyVMExit>,
200
201 thread: Option<TCGThreadType>,
203
204 device: Option<ShellPath>,
208
209 hyperv: Option<OnOffAuto>,
212}
213
214impl Accel {
215 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}