Skip to main content

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