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    /// subject to platform support and servicing per-CPU state capacity.
96    Enabled {
97        enable_logging: bool,
98        cpu_threshold: Option<u32>,
99    },
100    /// Sidecar is disabled because the per-CPU overrides required during
101    /// servicing restore exceed the per-CPU state array capacity.
102    DisabledPerCpuStateCapacity,
103    /// Sidecar is explicitly disabled via command line.
104    DisabledCommandLine,
105}
106
107impl SidecarOptions {
108    pub const DEFAULT_CPU_THRESHOLD: Option<u32> = Some(100);
109    pub const fn default() -> Self {
110        SidecarOptions::Enabled {
111            enable_logging: false,
112            cpu_threshold: Self::DEFAULT_CPU_THRESHOLD,
113        }
114    }
115}
116
117#[derive(Debug, PartialEq)]
118pub struct BootCommandLineOptions {
119    pub confidential_debug: bool,
120    pub enable_vtl2_gpa_pool: Vtl2GpaPoolConfig,
121    pub sidecar: SidecarOptions,
122    pub disable_nvme_keep_alive: bool,
123    /// When true, force the VTL2 GPA pool to be split evenly across NUMA
124    /// nodes instead of trying to allocate entirely on node 0 first.
125    pub vtl2_gpa_pool_numa_split: bool,
126}
127
128impl BootCommandLineOptions {
129    pub const fn new() -> Self {
130        BootCommandLineOptions {
131            confidential_debug: false,
132            enable_vtl2_gpa_pool: Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release), // use the release config by default
133            sidecar: SidecarOptions::default(),
134            disable_nvme_keep_alive: false,
135            vtl2_gpa_pool_numa_split: false,
136        }
137    }
138}
139
140impl BootCommandLineOptions {
141    /// Parse arguments from a command line.
142    pub fn parse(&mut self, cmdline: &str) {
143        let mut override_vtl2_gpa_pool: Option<Vtl2GpaPoolConfig> = None;
144        for arg in cmdline.split_whitespace() {
145            if arg.starts_with(OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME) {
146                let arg = arg.split_once('=').map(|(_, arg)| arg);
147                if arg.is_some_and(|a| a != "0") {
148                    self.confidential_debug = true;
149                }
150            } else if arg.starts_with(IGVM_VTL2_GPA_POOL_CONFIG) {
151                if let Some((_, arg)) = arg.split_once('=') {
152                    self.enable_vtl2_gpa_pool = Vtl2GpaPoolConfig::from(arg);
153                } else {
154                    log::warn!("Missing value for IGVM_VTL2_GPA_POOL_CONFIG argument");
155                }
156            } else if arg.starts_with(ENABLE_VTL2_GPA_POOL) {
157                if let Some((_, arg)) = arg.split_once('=') {
158                    override_vtl2_gpa_pool = Some(Vtl2GpaPoolConfig::from(arg));
159                } else {
160                    log::warn!("Missing value for ENABLE_VTL2_GPA_POOL argument");
161                }
162            } else if arg.starts_with(SIDECAR) {
163                if let Some((_, arg)) = arg.split_once('=') {
164                    for arg in arg.split(',') {
165                        match arg {
166                            "off" => self.sidecar = SidecarOptions::DisabledCommandLine,
167                            "on" => {
168                                self.sidecar = SidecarOptions::Enabled {
169                                    enable_logging: false,
170                                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
171                                }
172                            }
173                            "log" => {
174                                self.sidecar = SidecarOptions::Enabled {
175                                    enable_logging: true,
176                                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
177                                }
178                            }
179                            _ => {}
180                        }
181                    }
182                }
183            } else if arg.starts_with(DISABLE_NVME_KEEP_ALIVE) {
184                let arg = arg.split_once('=').map(|(_, arg)| arg);
185                if arg.is_some_and(|a| a != "0") {
186                    self.disable_nvme_keep_alive = true;
187                }
188            } else if arg.starts_with(VTL2_GPA_POOL_NUMA) {
189                if let Some((_, arg)) = arg.split_once('=') {
190                    match arg {
191                        "split" => self.vtl2_gpa_pool_numa_split = true,
192                        _ => log::warn!("Unknown value for OPENHCL_VTL2_GPA_POOL_NUMA: {arg}"),
193                    }
194                }
195            }
196        }
197
198        if let Some(override_config) = override_vtl2_gpa_pool {
199            self.enable_vtl2_gpa_pool = override_config;
200            log::info!(
201                "Overriding VTL2 GPA pool config to {:?} from command line",
202                override_config
203            );
204        }
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    fn parse_boot_command_line(cmdline: &str) -> BootCommandLineOptions {
213        let mut options = BootCommandLineOptions::new();
214        options.parse(cmdline);
215        options
216    }
217
218    #[test]
219    fn test_vtl2_gpa_pool_parsing() {
220        for (cmdline, expected) in [
221            (
222                // default
223                "",
224                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
225            ),
226            (
227                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=1",
228                Vtl2GpaPoolConfig::Pages(1),
229            ),
230            (
231                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=0",
232                Vtl2GpaPoolConfig::Off,
233            ),
234            (
235                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=asdf",
236                Vtl2GpaPoolConfig::Off,
237            ),
238            (
239                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=512",
240                Vtl2GpaPoolConfig::Pages(512),
241            ),
242            (
243                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=off",
244                Vtl2GpaPoolConfig::Off,
245            ),
246            (
247                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=debug",
248                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
249            ),
250            (
251                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=release",
252                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
253            ),
254            (
255                // OPENHCL_ENABLE_VTL2_GPA_POOL= takes precedence over OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=
256                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=release OPENHCL_ENABLE_VTL2_GPA_POOL=debug",
257                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
258            ),
259        ] {
260            assert_eq!(
261                parse_boot_command_line(cmdline).enable_vtl2_gpa_pool,
262                expected,
263                "Failed parsing VTL2 GPA pool config from command line: {}",
264                cmdline
265            );
266        }
267    }
268
269    #[test]
270    fn test_sidecar_parsing() {
271        assert_eq!(
272            parse_boot_command_line("OPENHCL_SIDECAR=on"),
273            BootCommandLineOptions {
274                sidecar: SidecarOptions::Enabled {
275                    enable_logging: false,
276                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
277                },
278                ..BootCommandLineOptions::new()
279            }
280        );
281        assert_eq!(
282            parse_boot_command_line("OPENHCL_SIDECAR=off"),
283            BootCommandLineOptions {
284                sidecar: SidecarOptions::DisabledCommandLine,
285                ..BootCommandLineOptions::new()
286            }
287        );
288        assert_eq!(
289            parse_boot_command_line("OPENHCL_SIDECAR=on,off"),
290            BootCommandLineOptions {
291                sidecar: SidecarOptions::DisabledCommandLine,
292                ..BootCommandLineOptions::new()
293            }
294        );
295        assert_eq!(
296            parse_boot_command_line("OPENHCL_SIDECAR=on,log"),
297            BootCommandLineOptions {
298                sidecar: SidecarOptions::Enabled {
299                    enable_logging: true,
300                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
301                },
302                ..BootCommandLineOptions::new()
303            }
304        );
305        assert_eq!(
306            parse_boot_command_line("OPENHCL_SIDECAR=log"),
307            BootCommandLineOptions {
308                sidecar: SidecarOptions::Enabled {
309                    enable_logging: true,
310                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
311                },
312                ..BootCommandLineOptions::new()
313            }
314        );
315    }
316}