underhill_core/loader/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Functionality to prepare VTL0 to run.
5
6use self::vtl2_config::RuntimeParameters;
7use crate::loader::vtl0_config::LinuxInfo;
8use crate::worker::FirmwareType;
9use cvm_tracing::CVM_ALLOWED;
10use guest_emulation_transport::api::platform_settings::DevicePlatformSettings;
11use guest_emulation_transport::api::platform_settings::General;
12use guestmem::GuestMemory;
13use hvdef::HV_PAGE_SIZE;
14use igvm_defs::MemoryMapEntryType;
15use loader::importer::Register;
16use loader::uefi::IMAGE_SIZE;
17use loader::uefi::config;
18use loader_defs::paravisor::PageRegionDescriptor;
19use memory_range::MemoryRange;
20#[cfg(guest_arch = "x86_64")]
21use serial_16550_resources::ComPort;
22use std::ffi::CString;
23use thiserror::Error;
24use vm_topology::memory::MemoryLayout;
25use vm_topology::memory::MemoryRangeWithNode;
26use vm_topology::processor::ProcessorTopology;
27use vmm_core::acpi_builder::AcpiTablesBuilder;
28use zerocopy::FromBytes;
29use zerocopy::IntoBytes;
30
31pub mod vtl0_config;
32pub mod vtl2_config;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum LoadKind {
36    None,
37    Uefi,
38    Pcat,
39    Linux,
40}
41
42impl From<LoadKind> for FirmwareType {
43    fn from(value: LoadKind) -> Self {
44        match value {
45            LoadKind::None | LoadKind::Linux => FirmwareType::None,
46            LoadKind::Uefi => FirmwareType::Uefi,
47            LoadKind::Pcat => FirmwareType::Pcat,
48        }
49    }
50}
51
52#[derive(Debug, Clone)]
53pub enum VpContext {
54    Vbs(Vec<Register>),
55    // TODO SNP: add SNP with VMSA
56}
57
58#[derive(Debug, Error)]
59pub enum Error {
60    #[error("accessing guest memory failed")]
61    GuestMemoryAccess(#[source] guestmem::GuestMemoryError),
62    #[cfg(guest_arch = "x86_64")]
63    #[error("linux loader error")]
64    LinuxLoader(#[source] loader::linux::Error),
65    #[cfg(guest_arch = "x86_64")]
66    #[error("pcat loader error")]
67    PcatLoader(#[source] loader::pcat::Error),
68    #[error("pcat not supported")]
69    PcatSupport,
70    #[error("uefi not supported")]
71    UefiSupport,
72    #[error("linux not supported")]
73    LinuxSupport,
74    #[error("finalizing boot")]
75    Finalize(#[source] vtl0_config::Error),
76    #[error("invalid acpi table: too short")]
77    InvalidAcpiTableLength,
78    #[error("invalid acpi table: unknown header signature {0:?}")]
79    InvalidAcpiTableSignature([u8; 4]),
80    #[cfg(guest_arch = "x86_64")]
81    #[error("acpi tables require at least two mmio ranges")]
82    UnsupportedMmio,
83}
84
85pub const PV_CONFIG_BASE_PAGE: u64 = if cfg!(guest_arch = "x86_64") {
86    loader_defs::paravisor::PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_X64
87} else if cfg!(guest_arch = "aarch64") {
88    loader_defs::paravisor::PARAVISOR_VTL0_MEASURED_CONFIG_BASE_PAGE_AARCH64
89} else {
90    panic!("unsupported guest architecture");
91};
92
93/// Additional loader config specified at runtime via underhill launch arguments.
94pub struct Config {
95    /// A string to append to the current VTL0 command line. Currently only used
96    /// when booting linux directly.
97    pub cmdline_append: CString,
98    /// Disable the UEFI frontpage or not, if loading UEFI.
99    pub disable_uefi_frontpage: bool,
100}
101
102/// Load VTL0 based on measured config. Returns any VP state that should be set.
103pub fn load(
104    gm: &GuestMemory,
105    mem_layout: &MemoryLayout,
106    processor_topology: &ProcessorTopology,
107    vtl0_memory_map: &[(MemoryRangeWithNode, MemoryMapEntryType)],
108    runtime_params: &RuntimeParameters,
109    load_kind: LoadKind,
110    vtl0_info: vtl0_config::MeasuredVtl0Info,
111    platform_config: &DevicePlatformSettings,
112    config: Config,
113    caps: &virt::PartitionCapabilities,
114    isolated: bool,
115) -> Result<VpContext, Error> {
116    let context = match load_kind {
117        LoadKind::None => {
118            tracing::info!(CVM_ALLOWED, "loading nothing into VTL0");
119            VpContext::Vbs(Vec::new())
120        }
121        LoadKind::Uefi => {
122            tracing::info!(CVM_ALLOWED, "loading UEFI into VTL0");
123            // UEFI image is already loaded into guest memory, so only the
124            // dynamic config needs to be written.
125            let uefi_info = vtl0_info.supports_uefi.as_ref().ok_or(Error::UefiSupport)?;
126
127            write_uefi_config(
128                gm,
129                mem_layout,
130                processor_topology,
131                vtl0_memory_map,
132                runtime_params,
133                platform_config,
134                caps,
135                isolated,
136                config.disable_uefi_frontpage,
137            )?;
138            uefi_info.vp_context.clone()
139        }
140        #[cfg(not(guest_arch = "x86_64"))]
141        LoadKind::Linux => {
142            let _ = config.cmdline_append;
143            let LinuxInfo {
144                kernel_range: _kernel_range,
145                kernel_entrypoint: _kernel_entrypoint,
146                initrd: _initrd,
147                command_line: _command_line,
148            } = vtl0_info
149                .supports_linux
150                .as_ref()
151                .ok_or(Error::LinuxSupport)?;
152            todo!();
153        }
154        #[cfg(guest_arch = "x86_64")]
155        LoadKind::Linux => {
156            tracing::info!(CVM_ALLOWED, "loading Linux into VTL0");
157
158            let LinuxInfo {
159                kernel_range,
160                kernel_entrypoint,
161                initrd,
162                command_line,
163            } = vtl0_info
164                .supports_linux
165                .as_ref()
166                .ok_or(Error::LinuxSupport)?;
167
168            // Convert the read cstring to a vec to allow appending.
169            let mut command_line = command_line.clone().unwrap_or_default().into_bytes();
170
171            // Add a trailing space to the base string so that the appended
172            // string won't corrupt the last argument.
173            if !command_line.is_empty() && command_line.last() != Some(&b' ') {
174                command_line.push(b' ');
175            }
176
177            // Copy from the append string.
178            command_line.extend_from_slice(config.cmdline_append.to_bytes());
179
180            let command_line = CString::new(command_line).expect("constructed from valid CStrings");
181
182            load_linux(LoadLinuxParams {
183                gm,
184                mem_layout,
185                processor_topology,
186                platform_config,
187                kernel_range: *kernel_range,
188                kernel_entrypoint: *kernel_entrypoint,
189                initrd: *initrd,
190                command_line,
191            })?
192        }
193        LoadKind::Pcat => {
194            tracing::info!(CVM_ALLOWED, "loading pcat into VTL0");
195
196            if !vtl0_info.supports_pcat {
197                return Err(Error::PcatSupport);
198            }
199
200            #[cfg(not(guest_arch = "x86_64"))]
201            panic!("Not supported");
202
203            #[cfg(guest_arch = "x86_64")]
204            load_pcat(gm, mem_layout)?
205        }
206    };
207
208    vtl0_info
209        .finalize_load(gm, load_kind)
210        .map_err(Error::Finalize)?;
211
212    Ok(context)
213}
214
215/// Load PCAT into VTL0.
216#[cfg(guest_arch = "x86_64")]
217fn load_pcat(gm: &GuestMemory, mem_layout: &MemoryLayout) -> Result<VpContext, Error> {
218    let mut loader = vm_loader::Loader::new(gm.clone(), mem_layout, hvdef::Vtl::Vtl0);
219
220    // PCAT image is already loaded into guest memory, so only register state
221    // needs to get set
222    loader::pcat::load(&mut loader, None, mem_layout.max_ram_below_4gb())
223        .map_err(Error::PcatLoader)?;
224
225    Ok(VpContext::Vbs(loader.initial_regs()))
226}
227
228#[cfg(guest_arch = "x86_64")]
229struct LoadLinuxParams<'a> {
230    gm: &'a GuestMemory,
231    mem_layout: &'a MemoryLayout,
232    processor_topology: &'a ProcessorTopology,
233    platform_config: &'a DevicePlatformSettings,
234    /// The region of memory used by the kernel.
235    kernel_range: MemoryRange,
236    /// The entrypoint of the kernel.
237    kernel_entrypoint: u64,
238    /// The (base address, size in bytes) of the initrd.
239    initrd: Option<(u64, u64)>,
240    /// The command line to pass to the kernel.
241    command_line: CString,
242}
243
244/// Load Linux into VTL0.
245#[cfg(guest_arch = "x86_64")]
246fn load_linux(params: LoadLinuxParams<'_>) -> Result<VpContext, Error> {
247    const GDT_BASE: u64 = 0x1000;
248    const CR3_BASE: u64 = 0x4000;
249    const ZERO_PAGE_BASE: u64 = 0x2000;
250    const CMDLINE_BASE: u64 = 0x3000;
251    const ACPI_BASE: u64 = 0xe0000;
252
253    let LoadLinuxParams {
254        gm,
255        mem_layout,
256        processor_topology,
257        platform_config,
258        kernel_range,
259        kernel_entrypoint,
260        initrd,
261        command_line,
262    } = params;
263
264    let cmdline_config = loader::linux::CommandLineConfig {
265        address: CMDLINE_BASE,
266        cmdline: &command_line,
267    };
268
269    let acpi_builder = AcpiTablesBuilder {
270        processor_topology,
271        mem_layout,
272        cache_topology: None,
273        with_ioapic: true, // underhill always runs with ioapic
274        with_pic: false,
275        with_pit: false,
276        with_psp: platform_config.general.psp_enabled,
277        pm_base: crate::worker::PM_BASE,
278        acpi_irq: crate::worker::SYSTEM_IRQ_ACPI,
279    };
280
281    if mem_layout.mmio().len() < 2 {
282        return Err(Error::UnsupportedMmio);
283    }
284
285    let acpi_tables = acpi_builder.build_acpi_tables(ACPI_BASE, |mem_layout, dsdt| {
286        dsdt.add_apic();
287
288        // Add serial ports if enabled.
289        if platform_config.general.com1_enabled {
290            dsdt.add_uart(
291                b"\\_SB.UAR1",
292                b"COM1",
293                1,
294                ComPort::Com1.io_port(),
295                ComPort::Com1.irq().into(),
296            );
297        }
298
299        if platform_config.general.com2_enabled {
300            dsdt.add_uart(
301                b"\\_SB.UAR2",
302                b"COM2",
303                2,
304                ComPort::Com2.io_port(),
305                ComPort::Com2.irq().into(),
306            );
307        }
308
309        dsdt.add_mmio_module(mem_layout.mmio()[0], mem_layout.mmio()[1]);
310        // TODO: change this once PCI is running in underhill
311        dsdt.add_vmbus(false);
312        dsdt.add_rtc();
313    });
314    let acpi_len = acpi_tables.tables.len() + 0x1000;
315
316    let acpi_config = loader::linux::AcpiConfig {
317        rdsp_address: ACPI_BASE,
318        rdsp: &acpi_tables.rdsp,
319        tables_address: ACPI_BASE + 0x1000,
320        tables: &acpi_tables.tables,
321    };
322
323    let register_config = loader::linux::RegisterConfig {
324        gdt_address: GDT_BASE,
325        page_table_address: CR3_BASE,
326    };
327
328    let mut loader = vm_loader::Loader::new(gm.clone(), mem_layout, hvdef::Vtl::Vtl0);
329
330    let initrd_info = if let Some((initrd_base, initrd_size)) = initrd {
331        let size_pages = (initrd_size + HV_PAGE_SIZE - 1) & !(HV_PAGE_SIZE - 1);
332
333        // Accept the initrd range to detect overlaps.
334        loader
335            .accept_new_range(
336                initrd_base / HV_PAGE_SIZE,
337                size_pages,
338                "linux-initrd",
339                loader::importer::BootPageAcceptance::Exclusive,
340            )
341            .expect("should be valid range");
342
343        Some(loader::linux::InitrdInfo {
344            gpa: initrd_base,
345            size: initrd_size,
346        })
347    } else {
348        None
349    };
350
351    let zero_page_config = loader::linux::ZeroPageConfig {
352        address: ZERO_PAGE_BASE,
353        mem_layout,
354        acpi_base_address: ACPI_BASE,
355        acpi_len,
356    };
357
358    tracing::trace!(?initrd_info);
359
360    // Accept the kernel range to detect overlaps.
361    loader
362        .accept_new_range(
363            kernel_range.start() / HV_PAGE_SIZE,
364            kernel_range.len() / HV_PAGE_SIZE,
365            "linux-kernel",
366            loader::importer::BootPageAcceptance::Exclusive,
367        )
368        .expect("should be valid range");
369
370    let load_info = loader::linux::LoadInfo {
371        kernel: loader::linux::KernelInfo {
372            gpa: kernel_range.start(),
373            size: kernel_range.len(),
374            entrypoint: kernel_entrypoint,
375        },
376        initrd: initrd_info,
377        dtb: None,
378    };
379
380    loader::linux::load_config(
381        &mut loader,
382        &load_info,
383        cmdline_config,
384        zero_page_config,
385        acpi_config,
386        register_config,
387    )
388    .map_err(Error::LinuxLoader)?;
389
390    Ok(VpContext::Vbs(loader.initial_regs()))
391}
392
393fn convert_range_type_flag(entry_type: MemoryMapEntryType) -> u32 {
394    match entry_type {
395        MemoryMapEntryType::MEMORY | MemoryMapEntryType::VTL2_PROTECTABLE => 0,
396        MemoryMapEntryType::PLATFORM_RESERVED => config::VM_MEMORY_RANGE_FLAG_PLATFORM_RESERVED,
397        // Note: this is needed when support for persistent memory is added.
398        // IGVM_VHF_MEMORY_MAP_ENTRY_TYPE_PERSISTENT => VM_MEMORY_RANGE_FLAG_PERSISTENT,
399        MemoryMapEntryType::PERSISTENT => {
400            unimplemented!("underhill does not support persistent memory type")
401        }
402        MemoryMapEntryType::SPECIFIC_PURPOSE => config::VM_MEMORY_RANGE_FLAG_SPECIFIC_PURPOSE,
403        _ => panic!("bad memory range type {:?}", entry_type),
404    }
405}
406
407/// Write the UEFI config blob into guest memory.
408pub fn write_uefi_config(
409    gm: &GuestMemory,
410    mem_layout: &MemoryLayout,
411    processor_topology: &ProcessorTopology,
412    vtl0_memory_map: &[(MemoryRangeWithNode, MemoryMapEntryType)],
413    igvm_parameters: &RuntimeParameters,
414    platform_config: &DevicePlatformSettings,
415    caps: &virt::PartitionCapabilities,
416    isolated: bool,
417    disable_frontpage: bool,
418) -> Result<(), Error> {
419    use guest_emulation_transport::api::platform_settings::UefiConsoleMode;
420
421    // The bios config consists of information that comes from a few different sources...
422    let mut cfg = config::Blob::new();
423
424    // - Data that we generate ourselves
425    cfg.add(&config::Entropy({
426        let mut entropy = [0; 64];
427        getrandom::fill(&mut entropy).expect("rng failure");
428        entropy
429    }));
430
431    // We will generate these tables unless trusted tables are passed via DevicePlatformSettings
432    let mut build_madt = true;
433    let mut build_srat = true;
434
435    // ACPI tables that come from the DevicePlatformSettings
436    // We can only trust these tables from the host if this is not an isolated VM
437    if !isolated {
438        for table in &platform_config.acpi_tables {
439            let header = acpi_spec::Header::ref_from_prefix(table)
440                .map_err(|_| Error::InvalidAcpiTableLength)? // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
441                .0;
442            match &header.signature {
443                b"APIC" => {
444                    build_madt = false;
445                    cfg.add_raw(config::BlobStructureType::Madt, table)
446                }
447                b"HMAT" => cfg.add_raw(config::BlobStructureType::Hmat, table),
448                b"IORT" => cfg.add_raw(config::BlobStructureType::Iort, table),
449                b"MCFG" => cfg.add_raw(config::BlobStructureType::Mcfg, table),
450                b"SRAT" => {
451                    build_srat = false;
452                    cfg.add_raw(config::BlobStructureType::Srat, table)
453                }
454                b"SSDT" => cfg.add_raw(config::BlobStructureType::Ssdt, table),
455                _ => return Err(Error::InvalidAcpiTableSignature(header.signature)),
456            };
457        }
458    }
459
460    // - Data that comes from the IGVM parameters
461
462    if build_madt || build_srat {
463        let acpi_builder = AcpiTablesBuilder {
464            processor_topology,
465            mem_layout,
466            cache_topology: None,
467            with_ioapic: cfg!(guest_arch = "x86_64"), // OpenHCL always runs with ioapic on x64
468            with_pic: false,                          // uefi never runs with pic or pit
469            with_pit: false,
470            with_psp: platform_config.general.psp_enabled,
471            pm_base: crate::worker::PM_BASE,
472            acpi_irq: crate::worker::SYSTEM_IRQ_ACPI,
473        };
474
475        // Build the ACPI tables as specified.
476        if build_madt {
477            let madt = acpi_builder.build_madt();
478            cfg.add_raw(config::BlobStructureType::Madt, &madt);
479        }
480
481        if build_srat {
482            let srat = acpi_builder.build_srat();
483            cfg.add_raw(config::BlobStructureType::Srat, &srat);
484        }
485    }
486
487    {
488        cfg.add_raw(
489            config::BlobStructureType::MemoryMap,
490            vtl0_memory_map
491                .iter()
492                .map(|(range, typ)| config::MemoryRangeV5 {
493                    base_address: range.range.start(),
494                    length: range.range.len(),
495                    flags: convert_range_type_flag(*typ),
496                    reserved: 0,
497                })
498                .collect::<Vec<_>>()
499                .as_bytes(),
500        )
501        .add_raw(
502            config::BlobStructureType::MmioRanges,
503            mem_layout
504                .mmio()
505                .iter()
506                .map(|range| config::Mmio {
507                    mmio_page_number_start: range.start() / HV_PAGE_SIZE,
508                    mmio_size_in_pages: range.len() / HV_PAGE_SIZE,
509                })
510                .collect::<Vec<_>>()
511                .as_bytes(),
512        )
513        .add(&config::ProcessorInformation {
514            max_processor_count: processor_topology.vp_count(),
515            processor_count: processor_topology.vp_count(),
516            processors_per_virtual_socket: processor_topology.reserved_vps_per_socket(),
517            threads_per_processor: if processor_topology.smt_enabled() {
518                2
519            } else {
520                1
521            },
522        });
523
524        if let Some(slit) = igvm_parameters.slit() {
525            cfg.add_raw(config::BlobStructureType::Slit, slit);
526        }
527
528        // TODO: reconstruct this instead of getting it from the host.
529        if let Some(pptt) = igvm_parameters.pptt() {
530            cfg.add_raw(config::BlobStructureType::Pptt, pptt);
531        }
532    }
533
534    cfg.add(&config::BiosInformation {
535        bios_size_pages: (IMAGE_SIZE / HV_PAGE_SIZE) as u32,
536        flags: platform_config.general.legacy_memory_map as u32,
537    })
538    .add(&config::BiosGuid(platform_config.general.bios_guid))
539    .add_cstring(
540        config::BlobStructureType::SmbiosSystemSerialNumber,
541        &platform_config.smbios.serial_number,
542    )
543    .add_cstring(
544        config::BlobStructureType::SmbiosBaseSerialNumber,
545        &platform_config.smbios.base_board_serial_number,
546    )
547    .add_cstring(
548        config::BlobStructureType::SmbiosChassisSerialNumber,
549        &platform_config.smbios.chassis_serial_number,
550    )
551    .add_cstring(
552        config::BlobStructureType::SmbiosChassisAssetTag,
553        &platform_config.smbios.chassis_asset_tag,
554    );
555
556    cfg.add(&config::NvdimmCount {
557        count: platform_config.general.nvdimm_count,
558        padding: [0; 3],
559    });
560
561    if let Some(instance_guid) = platform_config.general.vpci_instance_filter {
562        cfg.add(&config::VpciInstanceFilter { instance_guid });
563    }
564
565    cfg.add_cstring(
566        config::BlobStructureType::SmbiosSystemManufacturer,
567        &platform_config.smbios.system_manufacturer,
568    )
569    .add_cstring(
570        config::BlobStructureType::SmbiosSystemProductName,
571        &platform_config.smbios.system_product_name,
572    )
573    .add_cstring(
574        config::BlobStructureType::SmbiosSystemVersion,
575        &platform_config.smbios.system_version,
576    )
577    .add_cstring(
578        config::BlobStructureType::SmbiosSystemSkuNumber,
579        &platform_config.smbios.system_sku_number,
580    )
581    .add_cstring(
582        config::BlobStructureType::SmbiosSystemFamily,
583        &platform_config.smbios.system_family,
584    )
585    .add_cstring(
586        config::BlobStructureType::SmbiosBiosLockString,
587        &platform_config.smbios.bios_lock_string,
588    )
589    .add_cstring(
590        config::BlobStructureType::SmbiosMemoryDeviceSerialNumber,
591        &platform_config.smbios.memory_device_serial_number,
592    )
593    .add_cstring(
594        config::BlobStructureType::SmbiosProcessorManufacturer,
595        &platform_config.smbios.processor_manufacturer,
596    )
597    .add_cstring(
598        config::BlobStructureType::SmbiosProcessorVersion,
599        &platform_config.smbios.processor_version,
600    )
601    .add(&config::Smbios31ProcessorInformation {
602        processor_id: platform_config.smbios.processor_id,
603        external_clock: platform_config.smbios.external_clock,
604        max_speed: platform_config.smbios.max_speed,
605        current_speed: platform_config.smbios.current_speed,
606        processor_characteristics: platform_config.smbios.processor_characteristics,
607        processor_family2: platform_config.smbios.processor_family2,
608        processor_type: platform_config.smbios.processor_type,
609        voltage: platform_config.smbios.voltage,
610        status: platform_config.smbios.status,
611        processor_upgrade: platform_config.smbios.processor_upgrade,
612        reserved: 0,
613    });
614
615    // Flags is a special bit of config, as it uses information scattered across
616    // many settings
617    cfg.add(&{
618        let mut flags = config::Flags::new();
619
620        #[cfg(guest_arch = "x86_64")]
621        flags.set_sgx_memory_enabled(caps.sgx);
622        #[cfg(not(guest_arch = "x86_64"))]
623        let _ = caps;
624
625        // Frontpage is disabled if either the host requests it, or the openhcl
626        // cmdline specifies it.
627        flags.set_disable_frontpage(disable_frontpage || platform_config.general.disable_frontpage);
628
629        flags.set_console(match platform_config.general.console_mode {
630            UefiConsoleMode::Default => config::ConsolePort::Default,
631            UefiConsoleMode::COM1 => config::ConsolePort::Com1,
632            UefiConsoleMode::COM2 => config::ConsolePort::Com2,
633            UefiConsoleMode::None => config::ConsolePort::None,
634        });
635        flags.set_tpm_enabled(platform_config.general.tpm_enabled);
636        flags.set_virtual_battery_enabled(platform_config.general.battery_enabled);
637        flags.set_proc_idle_enabled(platform_config.general.processor_idle_enabled);
638        flags.set_serial_controllers_enabled(
639            platform_config.general.com1_enabled || platform_config.general.com2_enabled,
640        );
641        flags.set_hibernate_enabled(platform_config.general.hibernation_enabled);
642        flags.set_debugger_enabled(platform_config.general.firmware_debugging_enabled);
643
644        flags.set_pause_after_boot_failure(platform_config.general.pause_after_boot_failure);
645        flags.set_pxe_ip_v6(platform_config.general.pxe_ip_v6);
646        flags.set_media_present_enabled_by_default(
647            platform_config.general.media_present_enabled_by_default,
648        );
649        flags.set_vpci_boot_enabled(platform_config.general.vpci_boot_enabled);
650        flags.set_watchdog_enabled(platform_config.general.watchdog_enabled);
651
652        flags.set_memory_protection(determine_memory_protection_mode(
653            &platform_config.general,
654            isolated,
655        ));
656
657        if isolated {
658            // This flag is only used inside isolated guests
659            flags.set_enable_imc_when_isolated(platform_config.general.imc_enabled);
660        }
661
662        flags.set_cxl_memory_enabled(platform_config.general.cxl_memory_enabled);
663        flags.set_default_boot_always_attempt(platform_config.general.default_boot_always_attempt);
664
665        // Some settings do not depend on host config
666
667        // All OpenHCL vTPMs must opt-in to these settings
668        flags.set_measure_additional_pcrs(true);
669        flags.set_tpm_locality_regs_enabled(true);
670        // OpenHCL pre-sets the MTRRs; tell the firmware
671        flags.set_mtrrs_initialized_at_load(true);
672
673        flags
674    });
675
676    #[cfg(guest_arch = "aarch64")]
677    {
678        cfg.add(&config::Gic {
679            gic_distributor_base: processor_topology.gic_distributor_base(),
680            gic_redistributors_base: processor_topology.gic_redistributors_base(),
681        });
682    }
683
684    // Finally, with the bios config constructed, we can inject it into guest memory
685    gm.write_at(loader::uefi::CONFIG_BLOB_GPA_BASE, &cfg.complete())
686        .map_err(Error::GuestMemoryAccess)
687}
688
689/// Converts a [`PageRegionDescriptor`] to a [`MemoryRange`] if non-empty
690fn memory_range_from_page_region(region: &PageRegionDescriptor) -> Option<MemoryRange> {
691    region.pages().map(|(base_page, page_count)| {
692        MemoryRange::from_4k_gpn_range(base_page..(base_page + page_count))
693    })
694}
695
696fn determine_memory_protection_mode(general: &General, isolated: bool) -> config::MemoryProtection {
697    use guest_emulation_transport::api::platform_settings::MemoryProtectionMode;
698    use guest_emulation_transport::api::platform_settings::SecureBootTemplateType;
699
700    let is_windows_secure_boot = general.secure_boot_enabled
701        && matches!(
702            general.secure_boot_template,
703            SecureBootTemplateType::MicrosoftWindows
704        );
705
706    let mut requested_mode = general.memory_protection_mode;
707
708    // CVM NOTE: While secure boot enabled is attested to, the memory protection mode is not.
709    // Since we can't trust it, ensure it's always at least Default.
710    if isolated
711        && matches!(
712            requested_mode,
713            MemoryProtectionMode::Disabled | MemoryProtectionMode::Relaxed
714        )
715    {
716        requested_mode = MemoryProtectionMode::Default;
717    }
718
719    // TODO: For now, we use secure boot template type to override what kind of memory protection mode to enable.
720    //       This allows linux VMs to boot correctly as strict memory protection triggers with older versions of
721    //       grub. We should revisit this in the future.
722    if is_windows_secure_boot {
723        match requested_mode {
724            MemoryProtectionMode::Disabled => config::MemoryProtection::Disabled,
725            MemoryProtectionMode::Default => config::MemoryProtection::Default,
726            MemoryProtectionMode::Strict => config::MemoryProtection::Strict,
727            MemoryProtectionMode::Relaxed => config::MemoryProtection::Relaxed,
728        }
729    } else {
730        match requested_mode {
731            MemoryProtectionMode::Disabled => config::MemoryProtection::Disabled,
732            MemoryProtectionMode::Default
733            | MemoryProtectionMode::Strict
734            | MemoryProtectionMode::Relaxed => {
735                // TODO: For now, Linux only ever boots with relaxed.
736                config::MemoryProtection::Relaxed
737            }
738        }
739    }
740}