openhcl_boot/
cmdline.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Command line arguments and parsing for openhcl_boot.
5
6use crate::boot_logger::log;
7use underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME;
8
9/// Enable the private VTL2 GPA pool for page allocations.
10///
11/// Possible values:
12/// * `release`: Use the release version of the lookup table (default), or device tree.
13/// * `debug`: Use the debug version of the lookup table, or device tree.
14/// * `off`: Disable the VTL2 GPA pool.
15/// * `<num_pages>`: Explicitly specify the size of the VTL2 GPA pool.
16///
17/// See `Vtl2GpaPoolConfig` for more details.
18const IGVM_VTL2_GPA_POOL_CONFIG: &str = "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=";
19
20/// Test-legacy/test-compat override for `OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG`.
21/// (otherwise, tests cannot modify the VTL2 GPA pool config different from what
22/// may be in the manifest).
23const ENABLE_VTL2_GPA_POOL: &str = "OPENHCL_ENABLE_VTL2_GPA_POOL=";
24
25/// Options controlling sidecar.
26///
27/// * `off`: Disable sidecar support.
28/// * `on`: Enable sidecar support. Sidecar will still only be started if
29///   sidecar is present in the binary and supported on the platform. This
30///   is the default.
31/// * `log`: Enable sidecar logging.
32const SIDECAR: &str = "OPENHCL_SIDECAR=";
33
34/// Disable NVME keep alive regardless if the host supports it.
35const DISABLE_NVME_KEEP_ALIVE: &str = "OPENHCL_DISABLE_NVME_KEEP_ALIVE=";
36
37/// Lookup table to use for VTL2 GPA pool size heuristics.
38#[derive(Debug, PartialEq, Clone, Copy)]
39pub enum Vtl2GpaPoolLookupTable {
40    Release,
41    Debug,
42}
43
44#[derive(Debug, PartialEq, Clone, Copy)]
45pub enum Vtl2GpaPoolConfig {
46    /// Use heuristics to determine the VTL2 GPA pool size.
47    /// Reserve a default size based on the amount of VTL2 ram and
48    /// number of vCPUs. The point of this method is to account for cases where
49    /// we retrofit the private pool into existing deployments that do not
50    /// specify it explicitly.
51    ///
52    /// If the host specifies a size via the device tree, that size will be used
53    /// instead.
54    ///
55    /// The lookup table specifies whether to use the debug or release
56    /// heuristics (as the dev manifests provide different amounts of VTL2 RAM).
57    Heuristics(Vtl2GpaPoolLookupTable),
58
59    /// Explicitly disable the VTL2 private pool.
60    Off,
61
62    /// Explicitly specify the size of the VTL2 GPA pool in pages.
63    Pages(u64),
64}
65
66impl<S: AsRef<str>> From<S> for Vtl2GpaPoolConfig {
67    fn from(arg: S) -> Self {
68        match arg.as_ref() {
69            "debug" => Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
70            "release" => Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
71            "off" => Vtl2GpaPoolConfig::Off,
72            _ => {
73                let num = arg.as_ref().parse::<u64>().unwrap_or(0);
74                // A size of 0 or failure to parse is treated as disabling
75                // the pool.
76                if num == 0 {
77                    Vtl2GpaPoolConfig::Off
78                } else {
79                    Vtl2GpaPoolConfig::Pages(num)
80                }
81            }
82        }
83    }
84}
85
86#[derive(Debug, PartialEq)]
87pub enum SidecarOptions {
88    /// Sidecar is enabled (either via command line or by default),
89    /// but should be ignored if this is a restore and the host has
90    /// devices and the number of VPs below the threshold.
91    Enabled {
92        enable_logging: bool,
93        cpu_threshold: Option<u32>,
94    },
95    /// Sidecar is disabled because this is a restore from save state (during servicing),
96    /// and sidecar will not benefit this specific scenario.
97    DisabledServicing,
98    /// Sidecar is explicitly disabled via command line.
99    DisabledCommandLine,
100}
101
102impl SidecarOptions {
103    pub const DEFAULT_CPU_THRESHOLD: Option<u32> = Some(100);
104    pub const fn default() -> Self {
105        SidecarOptions::Enabled {
106            enable_logging: false,
107            cpu_threshold: Self::DEFAULT_CPU_THRESHOLD,
108        }
109    }
110}
111
112#[derive(Debug, PartialEq)]
113pub struct BootCommandLineOptions {
114    pub confidential_debug: bool,
115    pub enable_vtl2_gpa_pool: Vtl2GpaPoolConfig,
116    pub sidecar: SidecarOptions,
117    pub disable_nvme_keep_alive: bool,
118}
119
120impl BootCommandLineOptions {
121    pub const fn new() -> Self {
122        BootCommandLineOptions {
123            confidential_debug: false,
124            enable_vtl2_gpa_pool: Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release), // use the release config by default
125            sidecar: SidecarOptions::default(),
126            disable_nvme_keep_alive: false,
127        }
128    }
129}
130
131impl BootCommandLineOptions {
132    /// Parse arguments from a command line.
133    pub fn parse(&mut self, cmdline: &str) {
134        let mut override_vtl2_gpa_pool: Option<Vtl2GpaPoolConfig> = None;
135        for arg in cmdline.split_whitespace() {
136            if arg.starts_with(OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME) {
137                let arg = arg.split_once('=').map(|(_, arg)| arg);
138                if arg.is_some_and(|a| a != "0") {
139                    self.confidential_debug = true;
140                }
141            } else if arg.starts_with(IGVM_VTL2_GPA_POOL_CONFIG) {
142                if let Some((_, arg)) = arg.split_once('=') {
143                    self.enable_vtl2_gpa_pool = Vtl2GpaPoolConfig::from(arg);
144                } else {
145                    log!("WARNING: Missing value for IGVM_VTL2_GPA_POOL_CONFIG argument");
146                }
147            } else if arg.starts_with(ENABLE_VTL2_GPA_POOL) {
148                if let Some((_, arg)) = arg.split_once('=') {
149                    override_vtl2_gpa_pool = Some(Vtl2GpaPoolConfig::from(arg));
150                } else {
151                    log!("WARNING: Missing value for ENABLE_VTL2_GPA_POOL argument");
152                }
153            } else if arg.starts_with(SIDECAR) {
154                if let Some((_, arg)) = arg.split_once('=') {
155                    for arg in arg.split(',') {
156                        match arg {
157                            "off" => self.sidecar = SidecarOptions::DisabledCommandLine,
158                            "on" => {
159                                self.sidecar = SidecarOptions::Enabled {
160                                    enable_logging: false,
161                                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
162                                }
163                            }
164                            "log" => {
165                                self.sidecar = SidecarOptions::Enabled {
166                                    enable_logging: true,
167                                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
168                                }
169                            }
170                            _ => {}
171                        }
172                    }
173                }
174            } else if arg.starts_with(DISABLE_NVME_KEEP_ALIVE) {
175                let arg = arg.split_once('=').map(|(_, arg)| arg);
176                if arg.is_some_and(|a| a != "0") {
177                    self.disable_nvme_keep_alive = true;
178                }
179            }
180        }
181
182        if let Some(override_config) = override_vtl2_gpa_pool {
183            self.enable_vtl2_gpa_pool = override_config;
184            log!(
185                "INFO: Overriding VTL2 GPA pool config to {:?} from command line",
186                override_config
187            );
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    fn parse_boot_command_line(cmdline: &str) -> BootCommandLineOptions {
197        let mut options = BootCommandLineOptions::new();
198        options.parse(cmdline);
199        options
200    }
201
202    #[test]
203    fn test_vtl2_gpa_pool_parsing() {
204        for (cmdline, expected) in [
205            (
206                // default
207                "",
208                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
209            ),
210            (
211                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=1",
212                Vtl2GpaPoolConfig::Pages(1),
213            ),
214            (
215                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=0",
216                Vtl2GpaPoolConfig::Off,
217            ),
218            (
219                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=asdf",
220                Vtl2GpaPoolConfig::Off,
221            ),
222            (
223                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=512",
224                Vtl2GpaPoolConfig::Pages(512),
225            ),
226            (
227                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=off",
228                Vtl2GpaPoolConfig::Off,
229            ),
230            (
231                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=debug",
232                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
233            ),
234            (
235                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=release",
236                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
237            ),
238            (
239                // OPENHCL_ENABLE_VTL2_GPA_POOL= takes precedence over OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=
240                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=release OPENHCL_ENABLE_VTL2_GPA_POOL=debug",
241                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
242            ),
243        ] {
244            assert_eq!(
245                parse_boot_command_line(cmdline).enable_vtl2_gpa_pool,
246                expected,
247                "Failed parsing VTL2 GPA pool config from command line: {}",
248                cmdline
249            );
250        }
251    }
252
253    #[test]
254    fn test_sidecar_parsing() {
255        assert_eq!(
256            parse_boot_command_line("OPENHCL_SIDECAR=on"),
257            BootCommandLineOptions {
258                sidecar: SidecarOptions::Enabled {
259                    enable_logging: false,
260                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
261                },
262                ..BootCommandLineOptions::new()
263            }
264        );
265        assert_eq!(
266            parse_boot_command_line("OPENHCL_SIDECAR=off"),
267            BootCommandLineOptions {
268                sidecar: SidecarOptions::DisabledCommandLine,
269                ..BootCommandLineOptions::new()
270            }
271        );
272        assert_eq!(
273            parse_boot_command_line("OPENHCL_SIDECAR=on,off"),
274            BootCommandLineOptions {
275                sidecar: SidecarOptions::DisabledCommandLine,
276                ..BootCommandLineOptions::new()
277            }
278        );
279        assert_eq!(
280            parse_boot_command_line("OPENHCL_SIDECAR=on,log"),
281            BootCommandLineOptions {
282                sidecar: SidecarOptions::Enabled {
283                    enable_logging: true,
284                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
285                },
286                ..BootCommandLineOptions::new()
287            }
288        );
289        assert_eq!(
290            parse_boot_command_line("OPENHCL_SIDECAR=log"),
291            BootCommandLineOptions {
292                sidecar: SidecarOptions::Enabled {
293                    enable_logging: true,
294                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
295                },
296                ..BootCommandLineOptions::new()
297            }
298        );
299    }
300}