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: true,
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        // Workaround for a host side issue: disable NVMe keepalive by default.
145        self.disable_nvme_keep_alive = true;
146
147        let mut override_vtl2_gpa_pool: Option<Vtl2GpaPoolConfig> = None;
148        for arg in cmdline.split_whitespace() {
149            if arg.starts_with(OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME) {
150                let arg = arg.split_once('=').map(|(_, arg)| arg);
151                if arg.is_some_and(|a| a != "0") {
152                    self.confidential_debug = true;
153                }
154            } else if arg.starts_with(IGVM_VTL2_GPA_POOL_CONFIG) {
155                if let Some((_, arg)) = arg.split_once('=') {
156                    self.enable_vtl2_gpa_pool = Vtl2GpaPoolConfig::from(arg);
157                } else {
158                    log::warn!("Missing value for IGVM_VTL2_GPA_POOL_CONFIG argument");
159                }
160            } else if arg.starts_with(ENABLE_VTL2_GPA_POOL) {
161                if let Some((_, arg)) = arg.split_once('=') {
162                    override_vtl2_gpa_pool = Some(Vtl2GpaPoolConfig::from(arg));
163                } else {
164                    log::warn!("Missing value for ENABLE_VTL2_GPA_POOL argument");
165                }
166            } else if arg.starts_with(SIDECAR) {
167                if let Some((_, arg)) = arg.split_once('=') {
168                    for arg in arg.split(',') {
169                        match arg {
170                            "off" => self.sidecar = SidecarOptions::DisabledCommandLine,
171                            "on" => {
172                                self.sidecar = SidecarOptions::Enabled {
173                                    enable_logging: false,
174                                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
175                                }
176                            }
177                            "log" => {
178                                self.sidecar = SidecarOptions::Enabled {
179                                    enable_logging: true,
180                                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
181                                }
182                            }
183                            _ => {}
184                        }
185                    }
186                }
187            } else if arg.starts_with(DISABLE_NVME_KEEP_ALIVE) {
188                let arg = arg.split_once('=').map(|(_, arg)| arg);
189                if arg.is_some_and(|a| a == "0") {
190                    self.disable_nvme_keep_alive = false;
191                }
192            } else if arg.starts_with(VTL2_GPA_POOL_NUMA) {
193                if let Some((_, arg)) = arg.split_once('=') {
194                    match arg {
195                        "split" => self.vtl2_gpa_pool_numa_split = true,
196                        _ => log::warn!("Unknown value for OPENHCL_VTL2_GPA_POOL_NUMA: {arg}"),
197                    }
198                }
199            }
200        }
201
202        if let Some(override_config) = override_vtl2_gpa_pool {
203            self.enable_vtl2_gpa_pool = override_config;
204            log::info!(
205                "Overriding VTL2 GPA pool config to {:?} from command line",
206                override_config
207            );
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    fn parse_boot_command_line(cmdline: &str) -> BootCommandLineOptions {
217        let mut options = BootCommandLineOptions::new();
218        options.parse(cmdline);
219        options
220    }
221
222    #[test]
223    fn test_vtl2_gpa_pool_parsing() {
224        for (cmdline, expected) in [
225            (
226                // default
227                "",
228                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
229            ),
230            (
231                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=1",
232                Vtl2GpaPoolConfig::Pages(1),
233            ),
234            (
235                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=0",
236                Vtl2GpaPoolConfig::Off,
237            ),
238            (
239                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=asdf",
240                Vtl2GpaPoolConfig::Off,
241            ),
242            (
243                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=512",
244                Vtl2GpaPoolConfig::Pages(512),
245            ),
246            (
247                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=off",
248                Vtl2GpaPoolConfig::Off,
249            ),
250            (
251                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=debug",
252                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
253            ),
254            (
255                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=release",
256                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Release),
257            ),
258            (
259                // OPENHCL_ENABLE_VTL2_GPA_POOL= takes precedence over OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=
260                "OPENHCL_IGVM_VTL2_GPA_POOL_CONFIG=release OPENHCL_ENABLE_VTL2_GPA_POOL=debug",
261                Vtl2GpaPoolConfig::Heuristics(Vtl2GpaPoolLookupTable::Debug),
262            ),
263        ] {
264            assert_eq!(
265                parse_boot_command_line(cmdline).enable_vtl2_gpa_pool,
266                expected,
267                "Failed parsing VTL2 GPA pool config from command line: {}",
268                cmdline
269            );
270        }
271    }
272
273    #[test]
274    fn test_sidecar_parsing() {
275        assert_eq!(
276            parse_boot_command_line("OPENHCL_SIDECAR=on"),
277            BootCommandLineOptions {
278                sidecar: SidecarOptions::Enabled {
279                    enable_logging: false,
280                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
281                },
282                ..BootCommandLineOptions::new()
283            }
284        );
285        assert_eq!(
286            parse_boot_command_line("OPENHCL_SIDECAR=off"),
287            BootCommandLineOptions {
288                sidecar: SidecarOptions::DisabledCommandLine,
289                ..BootCommandLineOptions::new()
290            }
291        );
292        assert_eq!(
293            parse_boot_command_line("OPENHCL_SIDECAR=on,off"),
294            BootCommandLineOptions {
295                sidecar: SidecarOptions::DisabledCommandLine,
296                ..BootCommandLineOptions::new()
297            }
298        );
299        assert_eq!(
300            parse_boot_command_line("OPENHCL_SIDECAR=on,log"),
301            BootCommandLineOptions {
302                sidecar: SidecarOptions::Enabled {
303                    enable_logging: true,
304                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
305                },
306                ..BootCommandLineOptions::new()
307            }
308        );
309        assert_eq!(
310            parse_boot_command_line("OPENHCL_SIDECAR=log"),
311            BootCommandLineOptions {
312                sidecar: SidecarOptions::Enabled {
313                    enable_logging: true,
314                    cpu_threshold: SidecarOptions::DEFAULT_CPU_THRESHOLD,
315                },
316                ..BootCommandLineOptions::new()
317            }
318        );
319    }
320}