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=";
24
25#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Arbitrary)]
26pub 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)]
63pub 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)]
90pub 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#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
133pub struct Accel {
134 accel_type: AccelType,
135
136 igd_passthru: Option<OnOffDefaultOff>,
140
141 kernel_irqchip: Option<OnOffSplit>,
147
148 kvm_shadow_mem: Option<usize>,
150
151 one_insn_per_tb: Option<OnOff>,
156
157 split_wx: Option<OnOff>,
162
163 tb_size: Option<usize>,
165
166 dirty_ring_size: Option<usize>,
175
176 eager_split_size: Option<usize>,
189
190 notify_vmexit: Option<NotifyVMExit>,
199
200 thread: Option<TCGThreadType>,
202
203 device: Option<ShellPath>,
207}
208
209impl Accel {
210 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}