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