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