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