Skip to main content

qemu_command_builder/args/
acpitable.rs

1use crate::parsers::ARG_ACPITABLE;
2use std::fmt::{Display, Formatter};
3use std::str::FromStr;
4
5use crate::parsers::{DELIM_COLON, DELIM_COMMA};
6use crate::qao;
7use crate::shell_path::ShellPath;
8use crate::shell_string::ShellString;
9use crate::to_command::ToCommand;
10use bon::Builder;
11use proptest_derive::Arbitrary;
12
13const KEY_SIG: &str = "sig=";
14const KEY_REV: &str = "rev=";
15const KEY_OEM_ID: &str = "oem_id=";
16const KEY_OEM_TABLE_ID: &str = "oem_table_id=";
17const KEY_OEM_REV: &str = "oem_rev=";
18const KEY_ASL_COMPILER_ID: &str = "asl_compiler_id=";
19const KEY_ASL_COMPILER_REV: &str = "asl_compiler_rev=";
20const KEY_FILE: &str = "file=";
21const KEY_DATA: &str = "data=";
22
23/// ACPI table payload source for `-acpitable`.
24///
25/// `file=` passes one or more complete ACPI table files including headers.
26/// `data=` passes one or more payload files while the table header fields are
27/// supplied on the command line.
28#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Arbitrary)]
29pub enum AcpiTableData {
30    /// Emit `file=file1[:file2]...`.
31    File(Vec<ShellPath>),
32    /// Emit `data=file1[:file2]...`.
33    Data(Vec<ShellPath>),
34}
35
36impl AcpiTableData {
37    fn key(&self) -> &'static str {
38        match self {
39            AcpiTableData::File(_) => KEY_FILE,
40            AcpiTableData::Data(_) => KEY_DATA,
41        }
42    }
43
44    fn files(&self) -> &[ShellPath] {
45        match self {
46            AcpiTableData::File(files) | AcpiTableData::Data(files) => files,
47        }
48    }
49}
50
51impl Display for AcpiTableData {
52    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53        let files = self.files().iter().map(|p| p.as_ref()).collect::<Vec<_>>().join(DELIM_COLON);
54        write!(f, "{}{}", self.key(), files)
55    }
56}
57
58/// Add ACPI table with specified header fields and context from
59/// specified files. For file=, take whole ACPI table from the specified
60/// files, including all ACPI headers (possible overridden by other
61/// options). For data=, only data portion of the table is used, all
62/// header information is specified in the command line. If a SLIC table
63/// is supplied to QEMU, then the SLIC's oem\_id and oem\_table\_id
64/// fields will override the same in the RSDT and the FADT (a.k.a.
65/// FACP), in order to ensure the field matches required by the
66/// Microsoft SLIC spec and the ACPI spec.
67#[derive(Debug, Clone, Hash, Ord, PartialOrd, Eq, PartialEq, Default, Builder, Arbitrary)]
68pub struct AcpiTable {
69    /// ACPI signature override.
70    sig: Option<ShellString>,
71    /// ACPI table revision.
72    rev: Option<usize>,
73    /// ACPI OEM ID override.
74    oem_id: Option<ShellString>,
75    /// ACPI OEM table ID override.
76    oem_table_id: Option<ShellString>,
77    /// ACPI OEM revision override.
78    oem_rev: Option<usize>,
79    /// ASL compiler ID override.
80    asl_compiler_id: Option<ShellString>,
81    /// ASL compiler revision override.
82    asl_compiler_rev: Option<usize>,
83    #[proptest(filter = "acpi_table_data_is_nonempty")]
84    /// Table content source rendered as either `file=` or `data=`.
85    data: Option<AcpiTableData>,
86}
87
88fn acpi_table_data_is_nonempty(segment: &Option<AcpiTableData>) -> bool {
89    match segment {
90        None => false,
91        Some(f) => !f.files().is_empty(),
92    }
93}
94
95impl AcpiTable {
96    /// Creates an empty `-acpitable` argument builder for incremental setup.
97    pub fn new() -> Self {
98        Self::default()
99    }
100}
101
102impl ToCommand for AcpiTable {
103    fn has_args(&self) -> bool {
104        self.sig.is_some()
105            || self.rev.is_some()
106            || self.oem_id.is_some()
107            || self.oem_table_id.is_some()
108            || self.oem_rev.is_some()
109            || self.asl_compiler_id.is_some()
110            || self.asl_compiler_rev.is_some()
111            || self.data.is_some()
112    }
113    fn command(&self) -> String {
114        ARG_ACPITABLE.to_string()
115    }
116    fn to_args(&self) -> Vec<String> {
117        let mut args = vec![];
118        if let Some(sig) = &self.sig {
119            args.push(format!("{}{}", KEY_SIG, sig.as_ref()));
120        }
121        qao!(&self.rev, args, KEY_REV);
122        if let Some(oem_id) = &self.oem_id {
123            args.push(format!("{}{}", KEY_OEM_ID, oem_id.as_ref()));
124        }
125        if let Some(oem_table_id) = &self.oem_table_id {
126            args.push(format!("{}{}", KEY_OEM_TABLE_ID, oem_table_id.as_ref()));
127        }
128        qao!(&self.oem_rev, args, KEY_OEM_REV);
129        if let Some(asl_compiler_id) = &self.asl_compiler_id {
130            args.push(format!("{}{}", KEY_ASL_COMPILER_ID, asl_compiler_id.as_ref()));
131        }
132        qao!(&self.asl_compiler_rev, args, KEY_ASL_COMPILER_REV);
133
134        if let Some(data) = &self.data {
135            args.push(data.to_string());
136        }
137
138        vec![args.join(DELIM_COMMA)]
139    }
140}
141
142impl FromStr for AcpiTable {
143    type Err = String;
144
145    fn from_str(s: &str) -> Result<Self, Self::Err> {
146        let mut table = AcpiTable::default();
147
148        for part in s.split(DELIM_COMMA) {
149            let (key, value) = part.split_once('=').ok_or_else(|| format!("invalid acpitable option: {part}"))?;
150            match key {
151                "sig" => table.sig = Some(ShellString::from_str(value)?),
152                "rev" => table.rev = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
153                "oem_id" => table.oem_id = Some(ShellString::from_str(value)?),
154                "oem_table_id" => table.oem_table_id = Some(ShellString::from_str(value)?),
155                "oem_rev" => table.oem_rev = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
156                "asl_compiler_id" => table.asl_compiler_id = Some(ShellString::from_str(value)?),
157                "asl_compiler_rev" => table.asl_compiler_rev = Some(value.parse::<usize>().map_err(|e| e.to_string())?),
158                "file" => table.data = Some(parse_data_list(value, false)),
159                "data" => table.data = Some(parse_data_list(value, true)),
160                other => return Err(format!("unsupported acpitable option: {other}")),
161            }
162        }
163
164        Ok(table)
165    }
166}
167
168fn parse_data_list(value: &str, is_data: bool) -> AcpiTableData {
169    let files = value.split(DELIM_COLON).map(ShellPath::from).collect::<Vec<_>>();
170    if is_data { AcpiTableData::Data(files) } else { AcpiTableData::File(files) }
171}