Skip to main content

flowey_lib_hvlite/
build_openhcl_igvm_from_recipe.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Build an OpenHCL IGVM file using a particular known-good "recipe", which
5//! encodes the precise features / build parameters used by each constituent
6//! component.
7//!
8//! By having a clearly enumerated list of recipes, it is possible for multiple
9//! pipelines / flows to depend on _precisely_ the same IGVM file, without
10//! having to duplicate the non-trivial OpenHCL IGVM build chain.
11
12use crate::build_openhcl_boot::OpenhclBootOutput;
13use crate::build_openhcl_initrd::OpenhclInitrdExtraParams;
14use crate::build_openvmm_hcl::MaxTraceLevel;
15use crate::build_openvmm_hcl::OpenvmmHclBuildProfile;
16use crate::build_openvmm_hcl::OpenvmmHclFeature;
17use crate::build_openvmm_hcl::OpenvmmHclOutput;
18use crate::build_sidecar::SidecarOutput;
19use crate::common::CommonArch;
20use crate::common::CommonPlatform;
21use crate::common::CommonTriple;
22use crate::resolve_openhcl_kernel_package::OpenhclKernelPackageKind;
23use crate::run_cargo_build::BuildProfile;
24use crate::run_igvmfilegen::IgvmOutput;
25use flowey::node::prelude::*;
26use igvmfilegen_config::ResourceType;
27use std::collections::BTreeMap;
28use std::collections::BTreeSet;
29
30/// OpenHCL IGVM output
31#[derive(Serialize, Deserialize)]
32#[serde(untagged)]
33pub enum OpenhclIgvmOutput {
34    LocalOnlyCustom {
35        #[serde(rename = "openhcl-custom.bin")]
36        igvm_bin: PathBuf,
37        #[serde(flatten)]
38        endorsements: Option<OpenhclIgvmEndorsements>,
39    },
40    X64 {
41        #[serde(rename = "openhcl-x64.bin")]
42        igvm_bin: PathBuf,
43    },
44    X64Devkern {
45        #[serde(rename = "openhcl-x64-devkern.bin")]
46        igvm_bin: PathBuf,
47    },
48    X64TestLinuxDirect {
49        #[serde(rename = "openhcl-x64-test-linux-direct.bin")]
50        igvm_bin: PathBuf,
51    },
52    X64TestLinuxDirectDevkern {
53        #[serde(rename = "openhcl-x64-test-linux-direct-devkern.bin")]
54        igvm_bin: PathBuf,
55    },
56    X64Cvm {
57        #[serde(rename = "openhcl-x64-cvm.bin")]
58        igvm_bin: PathBuf,
59        #[serde(flatten)]
60        endorsements: Option<OpenhclIgvmEndorsements>,
61    },
62    X64CvmDevkern {
63        #[serde(rename = "openhcl-x64-cvm-devkern.bin")]
64        igvm_bin: PathBuf,
65        #[serde(flatten)]
66        endorsements: Option<OpenhclIgvmEndorsements>,
67    },
68    Aarch64 {
69        #[serde(rename = "openhcl-aarch64.bin")]
70        igvm_bin: PathBuf,
71    },
72    Aarch64Devkern {
73        #[serde(rename = "openhcl-aarch64-devkern.bin")]
74        igvm_bin: PathBuf,
75    },
76}
77
78#[derive(Serialize, Deserialize, Clone)]
79#[serde(untagged)]
80pub enum OpenhclIgvmEndorsements {
81    X64 {
82        #[serde(rename = "openhcl-tdx.json")]
83        #[serde(skip_serializing_if = "Option::is_none")]
84        igvm_tdx_json: Option<PathBuf>,
85        #[serde(rename = "openhcl-snp.json")]
86        #[serde(skip_serializing_if = "Option::is_none")]
87        igvm_snp_json: Option<PathBuf>,
88        #[serde(rename = "openhcl-vbs.json")]
89        #[serde(skip_serializing_if = "Option::is_none")]
90        igvm_vbs_json: Option<PathBuf>,
91        /// The unsigned SNP ID block signing payload emitted by `manifest`.
92        /// This travels with the endorsements (rather than the debug-symbol
93        /// extras) because it is a release/signing input consumed alongside the
94        /// `.json` identity documents. SNP-only, so absent for other platforms.
95        #[serde(rename = "openhcl-snp.idblock")]
96        #[serde(skip_serializing_if = "Option::is_none")]
97        igvm_snp_idblock: Option<PathBuf>,
98        #[serde(rename = "openhcl-tdx.cbor")]
99        #[serde(skip_serializing_if = "Option::is_none")]
100        igvm_tdx_corim: Option<PathBuf>,
101        #[serde(rename = "openhcl-snp.cbor")]
102        #[serde(skip_serializing_if = "Option::is_none")]
103        igvm_snp_corim: Option<PathBuf>,
104        #[serde(rename = "openhcl-vbs.cbor")]
105        #[serde(skip_serializing_if = "Option::is_none")]
106        igvm_vbs_corim: Option<PathBuf>,
107    },
108}
109
110impl OpenhclIgvmEndorsements {
111    fn is_complete(&self) -> bool {
112        match self {
113            OpenhclIgvmEndorsements::X64 {
114                igvm_tdx_json,
115                igvm_snp_json,
116                igvm_vbs_json,
117                igvm_tdx_corim,
118                igvm_snp_corim,
119                igvm_vbs_corim,
120                // Not part of completeness: the SNP ID block signing payload is
121                // an optional, SNP-only signing input, not a per-platform
122                // release endorsement.
123                igvm_snp_idblock: _,
124            } => {
125                igvm_tdx_json.is_some()
126                    && igvm_snp_json.is_some()
127                    && igvm_vbs_json.is_some()
128                    && igvm_tdx_corim.is_some()
129                    && igvm_snp_corim.is_some()
130                    && igvm_vbs_corim.is_some()
131            }
132        }
133    }
134}
135
136impl Artifact for OpenhclIgvmOutput {}
137
138impl OpenhclIgvmOutput {
139    pub fn igvm_bin(&self) -> &Path {
140        match self {
141            OpenhclIgvmOutput::LocalOnlyCustom { igvm_bin, .. }
142            | OpenhclIgvmOutput::X64 { igvm_bin }
143            | OpenhclIgvmOutput::X64Devkern { igvm_bin }
144            | OpenhclIgvmOutput::X64TestLinuxDirect { igvm_bin }
145            | OpenhclIgvmOutput::X64TestLinuxDirectDevkern { igvm_bin }
146            | OpenhclIgvmOutput::X64Cvm { igvm_bin, .. }
147            | OpenhclIgvmOutput::X64CvmDevkern { igvm_bin, .. }
148            | OpenhclIgvmOutput::Aarch64 { igvm_bin }
149            | OpenhclIgvmOutput::Aarch64Devkern { igvm_bin } => igvm_bin,
150        }
151    }
152
153    pub fn endorsements(&self) -> Option<&OpenhclIgvmEndorsements> {
154        match self {
155            OpenhclIgvmOutput::LocalOnlyCustom { endorsements, .. }
156            | OpenhclIgvmOutput::X64Cvm { endorsements, .. }
157            | OpenhclIgvmOutput::X64CvmDevkern { endorsements, .. } => endorsements.as_ref(),
158            _ => None,
159        }
160    }
161
162    pub fn recipe(&self) -> Option<OpenhclIgvmRecipe> {
163        match self {
164            OpenhclIgvmOutput::LocalOnlyCustom { .. } => None,
165            OpenhclIgvmOutput::X64 { .. } => Some(OpenhclIgvmRecipe::X64),
166            OpenhclIgvmOutput::X64Devkern { .. } => Some(OpenhclIgvmRecipe::X64Devkern),
167            OpenhclIgvmOutput::X64TestLinuxDirect { .. } => {
168                Some(OpenhclIgvmRecipe::X64TestLinuxDirect)
169            }
170            OpenhclIgvmOutput::X64TestLinuxDirectDevkern { .. } => {
171                Some(OpenhclIgvmRecipe::X64TestLinuxDirectDevkern)
172            }
173            OpenhclIgvmOutput::X64Cvm { .. } => Some(OpenhclIgvmRecipe::X64Cvm),
174            OpenhclIgvmOutput::X64CvmDevkern { .. } => Some(OpenhclIgvmRecipe::X64CvmDevkern),
175            OpenhclIgvmOutput::Aarch64 { .. } => Some(OpenhclIgvmRecipe::Aarch64),
176            OpenhclIgvmOutput::Aarch64Devkern { .. } => Some(OpenhclIgvmRecipe::Aarch64Devkern),
177        }
178    }
179
180    pub fn new(recipe: Option<OpenhclIgvmRecipe>, igvm: IgvmOutput) -> Self {
181        let IgvmOutput {
182            igvm_bin,
183            igvm_map: _,
184            igvm_tdx_json,
185            igvm_snp_json,
186            igvm_vbs_json,
187            igvm_snp_idblock,
188            igvm_tdx_corim,
189            igvm_snp_corim,
190            igvm_vbs_corim,
191        } = igvm;
192        let mut endorsements = if igvm_tdx_json.is_some()
193            || igvm_snp_json.is_some()
194            || igvm_vbs_json.is_some()
195            || igvm_snp_idblock.is_some()
196        {
197            Some(OpenhclIgvmEndorsements::X64 {
198                igvm_tdx_json,
199                igvm_snp_json,
200                igvm_vbs_json,
201                igvm_snp_idblock,
202                igvm_tdx_corim,
203                igvm_snp_corim,
204                igvm_vbs_corim,
205            })
206        } else {
207            None
208        };
209        match recipe {
210            None => OpenhclIgvmOutput::LocalOnlyCustom {
211                igvm_bin,
212                endorsements,
213            },
214            Some(recipe) => {
215                let output = match recipe {
216                    OpenhclIgvmRecipe::X64 => OpenhclIgvmOutput::X64 { igvm_bin },
217                    OpenhclIgvmRecipe::X64Devkern => OpenhclIgvmOutput::X64Devkern { igvm_bin },
218                    OpenhclIgvmRecipe::X64TestLinuxDirect => {
219                        OpenhclIgvmOutput::X64TestLinuxDirect { igvm_bin }
220                    }
221                    OpenhclIgvmRecipe::X64TestLinuxDirectDevkern => {
222                        OpenhclIgvmOutput::X64TestLinuxDirectDevkern { igvm_bin }
223                    }
224                    OpenhclIgvmRecipe::X64Cvm => OpenhclIgvmOutput::X64Cvm {
225                        igvm_bin,
226                        endorsements: Some(
227                            endorsements
228                                .take()
229                                .filter(OpenhclIgvmEndorsements::is_complete)
230                                .expect("missing endorsements"),
231                        ),
232                    },
233                    OpenhclIgvmRecipe::X64CvmDevkern => OpenhclIgvmOutput::X64CvmDevkern {
234                        igvm_bin,
235                        endorsements: Some(
236                            endorsements
237                                .take()
238                                .filter(OpenhclIgvmEndorsements::is_complete)
239                                .expect("missing endorsements"),
240                        ),
241                    },
242                    OpenhclIgvmRecipe::Aarch64 => OpenhclIgvmOutput::Aarch64 { igvm_bin },
243                    OpenhclIgvmRecipe::Aarch64Devkern => {
244                        OpenhclIgvmOutput::Aarch64Devkern { igvm_bin }
245                    }
246                };
247                if endorsements.is_some() {
248                    panic!("unexpected endorsements");
249                }
250                output
251            }
252        }
253    }
254}
255
256/// OpenHCL IGVM extras output
257#[derive(Serialize, Deserialize)]
258pub struct OpenhclIgvmExtrasOutput {
259    #[serde(flatten)]
260    pub openhcl_boot: OpenhclBootOutput,
261    #[serde(flatten)]
262    pub openvmm_hcl: OpenvmmHclOutput,
263    #[serde(flatten)]
264    pub sidecar: Option<SidecarOutput>,
265    #[serde(rename = "openhcl.bin.map")]
266    pub igvm_map: Option<PathBuf>,
267}
268
269impl Artifact for OpenhclIgvmExtrasOutput {}
270
271#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
272pub enum OpenhclKernelPackage {
273    /// Kernel from the hcl-main branch
274    Main,
275    /// CVM kernel from the hcl-main branch
276    Cvm,
277    /// Kernel from the hcl-dev branch
278    Dev,
279    /// CVM kernel from the hcl-dev brnach
280    CvmDev,
281}
282
283/// Vtl0 kernel type
284#[derive(Serialize, Deserialize, Debug, Clone)]
285pub enum Vtl0KernelType {
286    Example,
287    LocalOnlyCustom(PathBuf),
288}
289
290#[derive(Serialize, Deserialize, Clone, Debug)]
291pub enum IgvmManifestPath {
292    /// Name of an in-tree manifest (located under `vm/loader/manifests`)
293    InTree(String),
294    /// An absolute path to a custom manifest (for local use only)
295    LocalOnlyCustom(PathBuf),
296}
297
298#[derive(Serialize, Deserialize, Clone, Debug)]
299pub struct OpenhclIgvmRecipeDetails {
300    pub local_only: Option<OpenhclIgvmRecipeDetailsLocalOnly>,
301
302    pub igvm_manifest: IgvmManifestPath,
303    pub openhcl_kernel_package: OpenhclKernelPackage,
304    pub openvmm_hcl_features: BTreeSet<OpenvmmHclFeature>,
305    pub target: CommonTriple,
306    pub vtl0_kernel_type: Option<Vtl0KernelType>,
307    pub with_uefi: bool,
308    pub with_interactive: bool,
309    pub with_sidecar: bool,
310    pub max_trace_level: MaxTraceLevel,
311}
312
313#[derive(Serialize, Deserialize, Clone, Debug)]
314pub struct OpenhclIgvmRecipeDetailsLocalOnly {
315    pub openvmm_hcl_no_strip: bool,
316    pub openhcl_initrd_extra_params: Option<OpenhclInitrdExtraParams>,
317    pub custom_openvmm_hcl: Option<PathBuf>,
318    pub custom_openhcl_boot: Option<PathBuf>,
319    pub custom_kernel: Option<PathBuf>,
320    pub custom_sidecar: Option<PathBuf>,
321    pub custom_extra_rootfs: Vec<PathBuf>,
322}
323
324#[expect(clippy::large_enum_variant)]
325#[derive(Debug, Serialize, Deserialize, Clone)]
326pub enum OpenhclIgvmRecipeType {
327    LocalOnlyCustom(OpenhclIgvmRecipeDetails),
328    WellKnown(OpenhclIgvmRecipe),
329}
330
331#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
332pub enum OpenhclIgvmRecipe {
333    X64,
334    X64Devkern,
335    X64TestLinuxDirect,
336    X64TestLinuxDirectDevkern,
337    X64Cvm,
338    X64CvmDevkern,
339    Aarch64,
340    Aarch64Devkern,
341}
342
343impl ArtifactType for OpenhclIgvmRecipe {
344    fn name(&self, prefix: Option<&str>, suffix: Option<&str>) -> String {
345        [
346            Some(self.arch()),
347            prefix,
348            Some("openhcl"),
349            Some("igvm"),
350            self.flavor(),
351            suffix,
352        ]
353        .into_iter()
354        .flatten()
355        .collect::<Vec<_>>()
356        .join("-")
357    }
358}
359
360impl OpenhclIgvmRecipe {
361    pub fn common_arch(&self) -> CommonArch {
362        match self {
363            OpenhclIgvmRecipe::X64
364            | OpenhclIgvmRecipe::X64Devkern
365            | OpenhclIgvmRecipe::X64TestLinuxDirect
366            | OpenhclIgvmRecipe::X64TestLinuxDirectDevkern
367            | OpenhclIgvmRecipe::X64Cvm
368            | OpenhclIgvmRecipe::X64CvmDevkern => CommonArch::X86_64,
369            OpenhclIgvmRecipe::Aarch64 | OpenhclIgvmRecipe::Aarch64Devkern => CommonArch::Aarch64,
370        }
371    }
372
373    pub fn uses_dev_kernel(&self) -> bool {
374        match self {
375            Self::X64 | Self::X64TestLinuxDirect | Self::X64Cvm | Self::Aarch64 => false,
376            Self::X64Devkern
377            | Self::X64TestLinuxDirectDevkern
378            | Self::X64CvmDevkern
379            | Self::Aarch64Devkern => true,
380        }
381    }
382
383    pub fn non_production_tag(&self) -> String {
384        let mut tag = self.arch().to_string();
385        if let Some(flavor) = self.flavor() {
386            tag.push('-');
387            tag.push_str(flavor);
388        }
389        tag
390    }
391
392    pub fn non_production_name(&self) -> String {
393        format!("openhcl-{}", self.non_production_tag())
394    }
395
396    fn flavor(&self) -> Option<&'static str> {
397        match self {
398            OpenhclIgvmRecipe::X64 | OpenhclIgvmRecipe::Aarch64 => None,
399            OpenhclIgvmRecipe::X64Devkern | OpenhclIgvmRecipe::Aarch64Devkern => Some("devkern"),
400            OpenhclIgvmRecipe::X64TestLinuxDirect => Some("test-linux-direct"),
401            OpenhclIgvmRecipe::X64TestLinuxDirectDevkern => Some("test-linux-direct-devkern"),
402            OpenhclIgvmRecipe::X64Cvm => Some("cvm"),
403            OpenhclIgvmRecipe::X64CvmDevkern => Some("cvm-devkern"),
404        }
405    }
406
407    fn arch(&self) -> &'static str {
408        match self.common_arch() {
409            CommonArch::X86_64 => "x64",
410            CommonArch::Aarch64 => "aarch64",
411        }
412    }
413}
414
415impl OpenhclIgvmRecipeType {
416    pub fn recipe_details(&self, release_cfg: bool) -> OpenhclIgvmRecipeDetails {
417        match self {
418            Self::LocalOnlyCustom(details) => details.clone(),
419            Self::WellKnown(recipe) => recipe.recipe_details(release_cfg),
420        }
421    }
422
423    pub fn recipe(&self) -> Option<OpenhclIgvmRecipe> {
424        match self {
425            Self::LocalOnlyCustom(_) => None,
426            Self::WellKnown(recipe) => Some(recipe.clone()),
427        }
428    }
429}
430
431impl OpenhclIgvmRecipe {
432    pub fn recipe_details(&self, release_cfg: bool) -> OpenhclIgvmRecipeDetails {
433        let base_openvmm_hcl_features = || {
434            let mut m = BTreeSet::new();
435
436            m.insert(OpenvmmHclFeature::Tpm);
437
438            if !release_cfg {
439                m.insert(OpenvmmHclFeature::Gdb);
440            }
441
442            m
443        };
444
445        let in_repo_template = |debug_manifest: &'static str, release_manifest: &'static str| {
446            IgvmManifestPath::InTree(if release_cfg {
447                release_manifest.into()
448            } else {
449                debug_manifest.into()
450            })
451        };
452
453        // Debug configurations include --interactive by default, for busybox, gdbserver, and perf.
454        let with_interactive = !release_cfg;
455
456        // Save memory and cycles in hot paths by limiting the trace level in
457        // release builds.
458        let max_trace_level = if release_cfg {
459            MaxTraceLevel::Debug
460        } else {
461            MaxTraceLevel::Trace
462        };
463
464        match self {
465            Self::X64 => OpenhclIgvmRecipeDetails {
466                local_only: None,
467                igvm_manifest: in_repo_template("openhcl-x64-dev.json", "openhcl-x64-release.json"),
468                openhcl_kernel_package: OpenhclKernelPackage::Main,
469                openvmm_hcl_features: base_openvmm_hcl_features(),
470                target: CommonTriple::X86_64_LINUX_MUSL,
471                vtl0_kernel_type: None,
472                with_uefi: true,
473                with_interactive,
474                with_sidecar: true,
475                max_trace_level,
476            },
477            Self::X64Devkern => OpenhclIgvmRecipeDetails {
478                local_only: None,
479                igvm_manifest: in_repo_template("openhcl-x64-dev.json", "openhcl-x64-release.json"),
480                openhcl_kernel_package: OpenhclKernelPackage::Dev,
481                openvmm_hcl_features: base_openvmm_hcl_features(),
482                target: CommonTriple::X86_64_LINUX_MUSL,
483                vtl0_kernel_type: None,
484                with_uefi: true,
485                with_interactive,
486                with_sidecar: true,
487                max_trace_level,
488            },
489            Self::X64CvmDevkern => OpenhclIgvmRecipeDetails {
490                local_only: None,
491                igvm_manifest: in_repo_template(
492                    "openhcl-x64-cvm-dev.json",
493                    "openhcl-x64-cvm-release.json",
494                ),
495                openhcl_kernel_package: OpenhclKernelPackage::CvmDev,
496                openvmm_hcl_features: base_openvmm_hcl_features(),
497                target: CommonTriple::X86_64_LINUX_MUSL,
498                vtl0_kernel_type: None,
499                with_uefi: true,
500                with_interactive,
501                with_sidecar: false,
502                max_trace_level,
503            },
504            Self::X64TestLinuxDirect => OpenhclIgvmRecipeDetails {
505                local_only: None,
506                igvm_manifest: in_repo_template(
507                    "openhcl-x64-direct-dev.json",
508                    "openhcl-x64-direct-release.json",
509                ),
510                openhcl_kernel_package: OpenhclKernelPackage::Main,
511                openvmm_hcl_features: base_openvmm_hcl_features(),
512                target: CommonTriple::X86_64_LINUX_MUSL,
513                vtl0_kernel_type: Some(Vtl0KernelType::Example),
514                with_uefi: false,
515                with_interactive,
516                with_sidecar: true,
517                max_trace_level,
518            },
519            Self::X64TestLinuxDirectDevkern => OpenhclIgvmRecipeDetails {
520                local_only: None,
521                igvm_manifest: in_repo_template(
522                    "openhcl-x64-direct-dev.json",
523                    "openhcl-x64-direct-release.json",
524                ),
525                openhcl_kernel_package: OpenhclKernelPackage::Dev,
526                openvmm_hcl_features: base_openvmm_hcl_features(),
527                target: CommonTriple::X86_64_LINUX_MUSL,
528                vtl0_kernel_type: Some(Vtl0KernelType::Example),
529                with_uefi: false,
530                with_interactive,
531                with_sidecar: true,
532                max_trace_level,
533            },
534            Self::X64Cvm => OpenhclIgvmRecipeDetails {
535                local_only: None,
536                igvm_manifest: in_repo_template(
537                    "openhcl-x64-cvm-dev.json",
538                    "openhcl-x64-cvm-release.json",
539                ),
540                openhcl_kernel_package: OpenhclKernelPackage::Cvm,
541                openvmm_hcl_features: base_openvmm_hcl_features(),
542                target: CommonTriple::X86_64_LINUX_MUSL,
543                vtl0_kernel_type: None,
544                with_uefi: true,
545                with_interactive,
546                with_sidecar: false,
547                max_trace_level,
548            },
549            Self::Aarch64 => OpenhclIgvmRecipeDetails {
550                local_only: None,
551                igvm_manifest: in_repo_template(
552                    "openhcl-aarch64-dev.json",
553                    "openhcl-aarch64-release.json",
554                ),
555                openhcl_kernel_package: OpenhclKernelPackage::Main,
556                openvmm_hcl_features: base_openvmm_hcl_features(),
557                target: CommonTriple::AARCH64_LINUX_MUSL,
558                vtl0_kernel_type: None,
559                with_uefi: true,
560                with_interactive: false, // #1234
561                with_sidecar: false,
562                max_trace_level,
563            },
564            Self::Aarch64Devkern => OpenhclIgvmRecipeDetails {
565                local_only: None,
566                igvm_manifest: in_repo_template(
567                    "openhcl-aarch64-dev.json",
568                    "openhcl-aarch64-release.json",
569                ),
570                openhcl_kernel_package: OpenhclKernelPackage::Dev,
571                openvmm_hcl_features: base_openvmm_hcl_features(),
572                target: CommonTriple::AARCH64_LINUX_MUSL,
573                vtl0_kernel_type: None,
574                with_uefi: true,
575                with_interactive: false, // #1234
576                with_sidecar: false,
577                max_trace_level,
578            },
579        }
580    }
581}
582
583flowey_request! {
584    pub struct Request {
585        pub build_profile: OpenvmmHclBuildProfile,
586        pub release_cfg: bool,
587        pub recipe: OpenhclIgvmRecipeType,
588        pub custom_target: Option<CommonTriple>,
589        /// Additional features to enable on top of the recipe's defaults.
590        pub extra_features: BTreeSet<OpenvmmHclFeature>,
591        pub disable_secure_avic: bool,
592        pub uefi_firmware_flavor: Option<crate::download_uefi_mu_msvm::FirmwareFlavor>,
593        /// Add the confidential debug flag to the measured OpenHCL command
594        /// line, enabling confidential diagnostics on CVM builds.
595        pub confidential_debug: bool,
596
597        pub openhcl_igvm: WriteVar<OpenhclIgvmOutput>,
598        pub openhcl_igvm_extras: WriteVar<OpenhclIgvmExtrasOutput>,
599    }
600}
601
602new_simple_flow_node!(struct Node);
603
604impl SimpleFlowNode for Node {
605    type Request = Request;
606
607    fn imports(ctx: &mut ImportCtx<'_>) {
608        ctx.import::<crate::build_igvmfilegen::Node>();
609        ctx.import::<crate::build_openhcl_boot::Node>();
610        ctx.import::<crate::build_openhcl_initrd::Node>();
611        ctx.import::<crate::build_openvmm_hcl::Node>();
612        ctx.import::<crate::build_sidecar::Node>();
613        ctx.import::<crate::resolve_openhcl_kernel_package::Node>();
614        ctx.import::<crate::resolve_openvmm_deps::Node>();
615        ctx.import::<crate::resolve_openvmm_test_initrd::Node>();
616        ctx.import::<crate::resolve_openvmm_test_linux_kernel::Node>();
617        ctx.import::<crate::download_uefi_mu_msvm::Node>();
618        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
619        ctx.import::<crate::run_igvmfilegen::Node>();
620        ctx.import::<crate::run_split_debug_info::Node>();
621    }
622
623    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
624        let Request {
625            build_profile,
626            release_cfg,
627            recipe,
628            custom_target,
629            extra_features,
630            disable_secure_avic,
631            uefi_firmware_flavor,
632            confidential_debug,
633            openhcl_igvm,
634            openhcl_igvm_extras,
635        } = request;
636
637        let OpenhclIgvmRecipeDetails {
638            local_only,
639            igvm_manifest,
640            openhcl_kernel_package,
641            mut openvmm_hcl_features,
642            target,
643            vtl0_kernel_type,
644            with_uefi,
645            with_interactive,
646            with_sidecar,
647            max_trace_level,
648        } = recipe.recipe_details(release_cfg);
649
650        openvmm_hcl_features.extend(extra_features);
651
652        if disable_secure_avic {
653            openvmm_hcl_features.insert(OpenvmmHclFeature::LocalOnlyCustom(
654                "disable_secure_avic".into(),
655            ));
656        }
657
658        let OpenhclIgvmRecipeDetailsLocalOnly {
659            openvmm_hcl_no_strip,
660            openhcl_initrd_extra_params,
661            custom_openvmm_hcl,
662            custom_openhcl_boot,
663            custom_kernel,
664            custom_sidecar,
665            custom_extra_rootfs,
666        } = local_only.unwrap_or(OpenhclIgvmRecipeDetailsLocalOnly {
667            openvmm_hcl_no_strip: false,
668            openhcl_initrd_extra_params: None,
669            custom_openvmm_hcl: None,
670            custom_openhcl_boot: None,
671            custom_kernel: None,
672            custom_sidecar: None,
673            custom_extra_rootfs: Vec::new(),
674        });
675
676        let target = custom_target.unwrap_or(target);
677        let arch = CommonArch::from_triple(&target.as_triple())
678            .with_context(|| format!("cannot build openHCL from recipe on {target}"))?;
679
680        let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
681
682        let kernel_kind = match openhcl_kernel_package {
683            OpenhclKernelPackage::Main => OpenhclKernelPackageKind::Main,
684            OpenhclKernelPackage::Cvm => OpenhclKernelPackageKind::Cvm,
685            OpenhclKernelPackage::Dev => OpenhclKernelPackageKind::Dev,
686            OpenhclKernelPackage::CvmDev => OpenhclKernelPackageKind::CvmDev,
687        };
688
689        // Get the kernel package root for initrd building (needs metadata)
690        let vtl2_kernel_package_root =
691            ctx.reqv(
692                |v| crate::resolve_openhcl_kernel_package::Request::GetPackageRoot {
693                    kind: kernel_kind,
694                    arch,
695                    pkg: v,
696                },
697            );
698
699        // Get the modules path from the resolve node
700        let vtl2_kernel_modules =
701            ctx.reqv(
702                |v| crate::resolve_openhcl_kernel_package::Request::GetModules {
703                    kind: kernel_kind,
704                    arch,
705                    modules: v,
706                },
707            );
708
709        // Get the kernel metadata path from the resolve node
710        let vtl2_kernel_metadata =
711            ctx.reqv(
712                |v| crate::resolve_openhcl_kernel_package::Request::GetMetadata {
713                    kind: kernel_kind,
714                    arch,
715                    metadata: v,
716                },
717            );
718
719        let uefi_resource: Option<UefiResource> = with_uefi.then(|| UefiResource {
720            msvm_fd: ctx.reqv(|msvm_fd| crate::download_uefi_mu_msvm::Request::GetMsvmFd {
721                arch,
722                flavor: uefi_firmware_flavor,
723                msvm_fd,
724            }),
725        });
726
727        let vtl0_kernel_resource = vtl0_kernel_type.map(|typ| {
728            let kernel = if let Vtl0KernelType::LocalOnlyCustom(path) = typ {
729                ReadVar::from_static(path)
730            } else {
731                match typ {
732                    Vtl0KernelType::Example => ctx.reqv(|v| {
733                        crate::resolve_openvmm_test_linux_kernel::Request::Get(
734                            crate::resolve_openvmm_test_linux_kernel::OpenvmmTestKernelFile::Kernel,
735                            arch,
736                            crate::resolve_openvmm_test_linux_kernel::DEFAULT_LINUX_TEST_KERNEL_VERSION,
737                            v,
738                        )
739                    }),
740                    Vtl0KernelType::LocalOnlyCustom(_) => unreachable!("special cased above"),
741                }
742            };
743
744            let initrd = ctx.reqv(|v| {
745                crate::resolve_openvmm_test_initrd::Request::Get(arch, v)
746            });
747
748            Vtl0KernelResource { kernel, initrd }
749        });
750
751        // build sidecar
752        let sidecar = if with_sidecar {
753            let sidecar_bin = if let Some(path) = custom_sidecar {
754                ctx.emit_rust_stepv("set custom_sidecar", |_ctx| {
755                    |_rt| {
756                        let fake_dbg_path = std::env::current_dir()?
757                            .join("fake_sidecar.dbg")
758                            .absolute()?;
759                        fs_err::write(&fake_dbg_path, "")?;
760
761                        Ok(SidecarOutput {
762                            bin: path,
763                            dbg: fake_dbg_path,
764                        })
765                    }
766                })
767            } else {
768                ctx.reqv(|v| crate::build_sidecar::Request {
769                    build_params: crate::build_sidecar::SidecarBuildParams {
770                        arch,
771                        profile: match build_profile {
772                            OpenvmmHclBuildProfile::Debug => {
773                                crate::build_sidecar::SidecarBuildProfile::Debug
774                            }
775                            OpenvmmHclBuildProfile::Release
776                            | OpenvmmHclBuildProfile::OpenvmmHclShip => {
777                                crate::build_sidecar::SidecarBuildProfile::Release
778                            }
779                        },
780                    },
781                    sidecar: v,
782                })
783            };
784            Some(sidecar_bin)
785        } else {
786            None
787        };
788
789        // build openvmm_hcl bin
790        let openvmm_hcl = if let Some(ref path) = custom_openvmm_hcl {
791            let path = path.clone();
792            ctx.emit_rust_stepv("set custom_openvmm_hcl", |_ctx| {
793                |_rt| {
794                    Ok(OpenvmmHclOutput {
795                        bin: path,
796                        dbg: None,
797                    })
798                }
799            })
800        } else {
801            ctx.reqv(|v| {
802                crate::build_openvmm_hcl::Request {
803                    build_params: crate::build_openvmm_hcl::OpenvmmHclBuildParams {
804                        target: target.clone(),
805                        profile: build_profile,
806                        features: openvmm_hcl_features,
807                        // manually strip later, depending on provided igvm flags
808                        no_split_dbg_info: true,
809                        max_trace_level,
810                    },
811                    openvmm_hcl_output: v,
812                }
813            })
814        };
815
816        // build igvmfilegen (always built for host arch)
817        let igvmfilegen_arch: CommonArch = ctx.arch().try_into()?;
818
819        let igvmfilegen = ctx.reqv(|v| crate::build_igvmfilegen::Request {
820            build_params: crate::build_igvmfilegen::IgvmfilegenBuildParams {
821                target: CommonTriple::Common {
822                    arch: igvmfilegen_arch,
823                    platform: CommonPlatform::LinuxGnu,
824                },
825                profile: BuildProfile::Light,
826            },
827            igvmfilegen: v,
828        });
829
830        // build openhcl_boot
831        let openhcl_boot = if let Some(path) = custom_openhcl_boot {
832            ctx.emit_rust_stepv("set custom_openhcl_boot", |_ctx| {
833                |_rt| {
834                    let fake_dbg_path = std::env::current_dir()?.join("fake.dbg").absolute()?;
835                    fs_err::write(&fake_dbg_path, "")?;
836
837                    Ok(OpenhclBootOutput {
838                        bin: path,
839                        dbg: fake_dbg_path,
840                    })
841                }
842            })
843        } else {
844            ctx.reqv(|v| crate::build_openhcl_boot::Request {
845                build_params: crate::build_openhcl_boot::OpenhclBootBuildParams {
846                    arch,
847                    profile: match build_profile {
848                        OpenvmmHclBuildProfile::Debug => {
849                            crate::build_openhcl_boot::OpenhclBootBuildProfile::Debug
850                        }
851                        OpenvmmHclBuildProfile::Release
852                        | OpenvmmHclBuildProfile::OpenvmmHclShip => {
853                            crate::build_openhcl_boot::OpenhclBootBuildProfile::Release
854                        }
855                    },
856                },
857                openhcl_boot: v,
858            })
859        };
860
861        let use_stripped_openvmm_hcl = {
862            if custom_openvmm_hcl.is_some() {
863                // trust the user knows what they are doing if they specified a
864                // custom bin
865                false
866            } else {
867                !openvmm_hcl_no_strip
868            }
869        };
870
871        // use the stripped or unstripped openvmm_hcl as requested
872        let openvmm_hcl = if use_stripped_openvmm_hcl {
873            let (read, write) = ctx.new_var();
874            let (read_dbg, write_dbg) = ctx.new_var();
875
876            let in_bin = openvmm_hcl.map(ctx, |o| o.bin);
877            ctx.req(crate::run_split_debug_info::Request {
878                arch,
879                in_bin,
880                out_bin: write,
881                out_dbg_info: write_dbg,
882                reproducible_without_debuglink: matches!(
883                    ctx.platform(),
884                    FlowPlatform::Linux(FlowPlatformLinuxDistro::Nix)
885                ),
886            });
887
888            read.zip(ctx, read_dbg)
889                .map(ctx, |(bin, dbg)| OpenvmmHclOutput {
890                    bin,
891                    dbg: Some(dbg),
892                })
893        } else {
894            openvmm_hcl
895        };
896
897        let initrd = {
898            let rootfs_config = [openvmm_repo_path.map(ctx, |p| p.join("openhcl/rootfs.config"))]
899                .into_iter()
900                .chain(
901                    custom_extra_rootfs
902                        .into_iter()
903                        .map(|p| ReadVar::from_static(p)),
904                )
905                .collect();
906            let openvmm_hcl_bin = openvmm_hcl.map(ctx, |o| o.bin);
907
908            ctx.reqv(|v| crate::build_openhcl_initrd::Request {
909                interactive: with_interactive,
910                arch,
911                extra_params: openhcl_initrd_extra_params,
912                rootfs_config,
913                extra_env: None,
914                kernel_package_root: vtl2_kernel_package_root.clone(),
915                kernel_modules: vtl2_kernel_modules,
916                kernel_metadata: vtl2_kernel_metadata,
917                bin_openhcl: openvmm_hcl_bin,
918                initrd: v,
919            })
920        };
921
922        let kernel = if let Some(path) = custom_kernel {
923            ReadVar::from_static(path)
924        } else {
925            ctx.reqv(
926                |v| crate::resolve_openhcl_kernel_package::Request::GetKernel {
927                    kind: kernel_kind,
928                    arch,
929                    kernel: v,
930                },
931            )
932        };
933
934        let sidecar_bin = sidecar.clone().map(|x| x.map(ctx, |y| y.bin));
935        let openhcl_boot_bin = openhcl_boot.map(ctx, |x| x.bin);
936        let resources = ctx.emit_minor_rust_stepv("enumerate igvm resources", |ctx| {
937            claim_vars!(
938                ctx,
939                (
940                    initrd,
941                    kernel,
942                    openhcl_boot_bin,
943                    sidecar_bin,
944                    uefi_resource,
945                    vtl0_kernel_resource
946                )
947            );
948            |rt| {
949                let mut resources = BTreeMap::<ResourceType, PathBuf>::new();
950                resources.insert(ResourceType::UnderhillKernel, rt.read(kernel));
951                resources.insert(ResourceType::UnderhillInitrd, rt.read(initrd).initrd);
952                resources.insert(ResourceType::OpenhclBoot, rt.read(openhcl_boot_bin));
953                if let Some(sidecar_bin) = sidecar_bin {
954                    resources.insert(ResourceType::UnderhillSidecar, rt.read(sidecar_bin));
955                }
956                if let Some(uefi_resource) = uefi_resource {
957                    uefi_resource.add_to_resources(&mut resources, rt);
958                }
959                if let Some(vtl0_kernel_resource) = vtl0_kernel_resource {
960                    vtl0_kernel_resource.add_to_resources(&mut resources, rt);
961                }
962                resources
963            }
964        });
965
966        let igvmfilegen = igvmfilegen.map(ctx, |o| match o {
967            crate::build_igvmfilegen::IgvmfilegenOutput::LinuxBin { bin, dbg: _ } => bin,
968            crate::build_igvmfilegen::IgvmfilegenOutput::WindowsBin { exe, pdb: _ } => exe,
969        });
970
971        let manifest = match igvm_manifest {
972            IgvmManifestPath::InTree(path) => {
973                openvmm_repo_path.map(ctx, |p| p.join("vm/loader/manifests").join(path))
974            }
975            IgvmManifestPath::LocalOnlyCustom(p) => ReadVar::from_static(p),
976        };
977
978        let igvm = ctx.reqv(|v| crate::run_igvmfilegen::Request {
979            igvmfilegen,
980            manifest,
981            resources,
982            disable_secure_avic,
983            confidential_debug,
984            // Open-source builds embed a temporary-key SNP ID block so the file
985            // launches with `id_block_en = 1`; production pipelines add a
986            // real-key ID block out-of-band instead.
987            add_temp_snp_id_block: true,
988            igvm: v,
989        });
990
991        let igvm_map = igvm.map(ctx, |x| x.igvm_map);
992        ctx.emit_minor_rust_step("construct openhcl extras", move |ctx| {
993            claim_vars!(
994                ctx,
995                (
996                    openhcl_boot,
997                    openvmm_hcl,
998                    sidecar,
999                    igvm_map,
1000                    openhcl_igvm_extras
1001                )
1002            );
1003
1004            |rt| {
1005                read_vars!(rt, (openhcl_boot, openvmm_hcl, sidecar, igvm_map));
1006
1007                rt.write(
1008                    openhcl_igvm_extras,
1009                    &OpenhclIgvmExtrasOutput {
1010                        openhcl_boot,
1011                        openvmm_hcl,
1012                        sidecar,
1013                        igvm_map,
1014                    },
1015                );
1016            }
1017        });
1018
1019        igvm.write_into_with(ctx, openhcl_igvm, move |igvm| {
1020            OpenhclIgvmOutput::new(recipe.recipe(), igvm)
1021        });
1022
1023        Ok(())
1024    }
1025}
1026
1027#[derive(Debug)]
1028pub struct UefiResource<C = VarNotClaimed> {
1029    pub msvm_fd: ReadVar<PathBuf, C>,
1030}
1031
1032impl ClaimVar for UefiResource {
1033    type Claimed = UefiResource<VarClaimed>;
1034
1035    fn claim(self, ctx: &mut StepCtx<'_>) -> UefiResource<VarClaimed> {
1036        UefiResource {
1037            msvm_fd: self.msvm_fd.claim(ctx),
1038        }
1039    }
1040}
1041
1042impl UefiResource<VarClaimed> {
1043    pub fn add_to_resources(
1044        self,
1045        resources: &mut BTreeMap<ResourceType, PathBuf>,
1046        rt: &mut RustRuntimeServices<'_>,
1047    ) {
1048        let path = rt.read(self.msvm_fd);
1049        resources.insert(ResourceType::Uefi, path);
1050    }
1051}
1052
1053pub struct Vtl0KernelResource<C = VarNotClaimed> {
1054    pub kernel: ReadVar<PathBuf, C>,
1055    pub initrd: ReadVar<PathBuf, C>,
1056}
1057
1058impl ClaimVar for Vtl0KernelResource {
1059    type Claimed = Vtl0KernelResource<VarClaimed>;
1060
1061    fn claim(self, ctx: &mut StepCtx<'_>) -> Vtl0KernelResource<VarClaimed> {
1062        Vtl0KernelResource {
1063            kernel: self.kernel.claim(ctx),
1064            initrd: self.initrd.claim(ctx),
1065        }
1066    }
1067}
1068
1069impl Vtl0KernelResource<VarClaimed> {
1070    pub fn add_to_resources(
1071        self,
1072        resources: &mut BTreeMap<ResourceType, PathBuf>,
1073        rt: &mut RustRuntimeServices<'_>,
1074    ) {
1075        let kernel = rt.read(self.kernel);
1076        let initrd = rt.read(self.initrd);
1077        resources.insert(ResourceType::LinuxKernel, kernel);
1078        resources.insert(ResourceType::LinuxInitrd, initrd);
1079    }
1080}