diff --git a/openvmm/hvlite_core/src/worker/vm_loaders/igvm.rs b/openvmm/hvlite_core/src/worker/vm_loaders/igvm.rs index 00e5c812a4..bf9e58d5a9 100644 --- a/openvmm/hvlite_core/src/worker/vm_loaders/igvm.rs +++ b/openvmm/hvlite_core/src/worker/vm_loaders/igvm.rs @@ -89,6 +89,8 @@ pub enum Error { UnsupportedGuestArch, #[error("igvm file does not support vbs")] NoVbsSupport, + #[error("vp context for lower VTL not supported")] + LowerVtlContext, } fn from_memory_range(range: &MemoryRange) -> IGVM_VHS_MEMORY_RANGE { @@ -995,6 +997,10 @@ fn load_igvm_x86( ref registers, compatibility_mask: _, } => { + if from_igvm_vtl(vtl) != max_vtl { + return Err(Error::LowerVtlContext); + } + let mut cr3: Option = None; let mut cr4: Option = None; @@ -1097,7 +1103,7 @@ fn load_igvm_x86( }; loader - .import_vp_register(from_igvm_vtl(vtl), reloc_reg) + .import_vp_register(reloc_reg) .map_err(Error::Loader)?; } diff --git a/openvmm/hvlite_core/src/worker/vm_loaders/linux.rs b/openvmm/hvlite_core/src/worker/vm_loaders/linux.rs index 20cac8048a..0cbbbd45e6 100644 --- a/openvmm/hvlite_core/src/worker/vm_loaders/linux.rs +++ b/openvmm/hvlite_core/src/worker/vm_loaders/linux.rs @@ -421,7 +421,7 @@ pub fn load_linux_arm64( // Set the registers separately so they won't conflict with the UEFI boot when // `load_kernel_and_initrd_arm64` is used for VTL2 direct kernel boot. - loader::linux::set_direct_boot_registers_arm64(&mut loader, &load_info, hvdef::Vtl::Vtl0) + loader::linux::set_direct_boot_registers_arm64(&mut loader, &load_info) .map_err(Error::Loader)?; Ok(loader.initial_regs()) diff --git a/vm/loader/igvmfilegen/src/file_loader.rs b/vm/loader/igvmfilegen/src/file_loader.rs index 433292917b..0c5f12347d 100644 --- a/vm/loader/igvmfilegen/src/file_loader.rs +++ b/vm/loader/igvmfilegen/src/file_loader.rs @@ -11,10 +11,10 @@ use crate::signed_measurement::generate_snp_measurement; use crate::signed_measurement::generate_tdx_measurement; use crate::signed_measurement::generate_vbs_measurement; use crate::vp_context_builder::snp::InjectionType; -use crate::vp_context_builder::snp::SnpVpContextBuilder; -use crate::vp_context_builder::tdx::TdxVpContextBuilder; +use crate::vp_context_builder::snp::SnpHardwareContext; +use crate::vp_context_builder::tdx::TdxHardwareContext; use crate::vp_context_builder::vbs::VbsRegister; -use crate::vp_context_builder::vbs::VbsVpContextBuilder; +use crate::vp_context_builder::vbs::VbsVpContext; use crate::vp_context_builder::VpContextBuilder; use crate::vp_context_builder::VpContextPageState; use crate::vp_context_builder::VpContextState; @@ -57,7 +57,6 @@ use std::fmt::Display; use zerocopy::AsBytes; use zerocopy::FromBytes; -pub const HV_NUM_VTLS: usize = 3; pub const DEFAULT_COMPATIBILITY_MASK: u32 = 0x1; const TDX_SHARED_GPA_BOUNDARY_BITS: u8 = 47; @@ -101,7 +100,8 @@ pub struct IgvmLoader { initialization_headers: Vec, directives: Vec, page_data_directives: Vec, - vp_context_builder: Option>>, + vp_context: Option>>, + lower_vtl_context: Option>, max_vtl: Vtl, parameter_areas: BTreeMap<(u64, u32), u32>, isolation_type: LoaderIsolationType, @@ -109,6 +109,17 @@ pub struct IgvmLoader { imported_regions_config_page: Option, } +pub struct IgvmVtlLoader<'a, R: VbsRegister + GuestArch> { + loader: &'a mut IgvmLoader, + vtl: Vtl, +} + +impl IgvmVtlLoader<'_, R> { + pub fn loader(&self) -> &IgvmLoader { + self.loader + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LoaderIsolationType { None, @@ -192,15 +203,12 @@ impl IgvmLoaderRegister for X86Register { compatibility_mask: DEFAULT_COMPATIBILITY_MASK, }; - let vp_context_builder = Box::new( - SnpVpContextBuilder::new( - max_vtl, - !with_paravisor, - shared_gpa_boundary, - injection_type, - ) - .expect("must be valid"), - ); + let vp_context_builder = Box::new(SnpHardwareContext::new( + max_vtl, + !with_paravisor, + shared_gpa_boundary, + injection_type, + )); (platform_header, vec![init_header], vp_context_builder) } @@ -224,9 +232,7 @@ impl IgvmLoaderRegister for X86Register { }); } - let vp_context_builder = Box::new( - TdxVpContextBuilder::new(max_vtl, !with_paravisor).expect("must be valid"), - ); + let vp_context_builder = Box::new(TdxHardwareContext::new(!with_paravisor)); (platform_header, init_headers, vp_context_builder) } @@ -473,7 +479,7 @@ impl IgvmLoader { match isolation_type { LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => { - vp_context_builder = Some(Box::new(VbsVpContextBuilder::::new())); + vp_context_builder = Some(Box::new(VbsVpContext::::new(max_vtl.into()))); // Add VBS platform header let info = IGVM_VHS_SUPPORTED_PLATFORM { @@ -496,6 +502,12 @@ impl IgvmLoader { } } + let lower_vtl_context_builder = if with_paravisor { + Some(VbsVpContext::new(Vtl::Vtl0.into())) + } else { + None + }; + IgvmLoader { accepted_ranges: RangeMap::new(), relocatable_regions: RangeMap::new(), @@ -505,7 +517,8 @@ impl IgvmLoader { initialization_headers, directives: Vec::new(), page_data_directives: Vec::new(), - vp_context_builder, + vp_context: vp_context_builder, + lower_vtl_context: lower_vtl_context_builder, max_vtl, parameter_areas: BTreeMap::new(), isolation_type, @@ -555,13 +568,13 @@ impl IgvmLoader { /// Finalize the loader state, returning an IGVM file. pub fn finalize(mut self, guest_svn: u32) -> anyhow::Result { // Finalize any VP state. - for context in self - .vp_context_builder - .take() - .expect("should never be none") - .finalize() - .into_iter() - { + let mut state = Vec::new(); + self.vp_context.take().unwrap().finalize(&mut state); + if let Some(mut context) = self.lower_vtl_context.take() { + context.finalize(&mut state); + } + + for context in state { match context { VpContextState::Page(VpContextPageState { page_base, @@ -751,18 +764,124 @@ impl IgvmLoader { _ => false, } } + + pub fn for_vtl(&mut self, vtl: Vtl) -> IgvmVtlLoader<'_, R> { + assert!(vtl <= self.max_vtl); + assert!(vtl == Vtl::Vtl0 || vtl == Vtl::Vtl2); + IgvmVtlLoader { loader: self, vtl } + } + + fn import_pages( + &mut self, + page_base: u64, + page_count: u64, + debug_tag: &str, + acceptance: BootPageAcceptance, + mut data: &[u8], + ) -> Result<(), anyhow::Error> { + tracing::debug!( + page_base, + ?acceptance, + page_count, + data_size = data.len(), + "Importing page", + ); + + // Pages must not overlap already accepted ranges + self.accept_new_range(page_base, page_count, debug_tag, acceptance)?; + + // Page count must be larger or equal to data. + if page_count * PAGE_SIZE_4K < data.len() as u64 { + anyhow::bail!( + "data len {:x} is larger than page_count {page_count:x}", + data.len() + ); + } + + // VpContext imports are handled differently, as they have a different IGVM header + // type than normal data pages. + if acceptance == BootPageAcceptance::VpContext { + // This is only supported on SNP currently. + match self.isolation_type { + LoaderIsolationType::Snp { .. } => {} + _ => { + anyhow::bail!("vpcontext acceptance only supported on SNP"); + } + } + + // Data size must match SNP VMSA size. + if data.len() != size_of::() { + anyhow::bail!("data len {:x} does not match VMSA size", data.len()); + } + + // Page count must be 1. + if page_count != 1 { + anyhow::bail!("page count {page_count:x} for snp vmsa is not 1"); + } + + self.directives.push(IgvmDirectiveHeader::SnpVpContext { + gpa: page_base * PAGE_SIZE_4K, + compatibility_mask: DEFAULT_COMPATIBILITY_MASK, + vp_index: 0, + vmsa: Box::new(SevVmsa::read_from(data).expect("should be correct size")), + }); + } else { + for page in page_base..page_base + page_count { + let (data_type, flags) = match acceptance { + BootPageAcceptance::Exclusive => { + (IgvmPageDataType::NORMAL, IgvmPageDataFlags::new()) + } + BootPageAcceptance::ExclusiveUnmeasured => ( + IgvmPageDataType::NORMAL, + IgvmPageDataFlags::new().with_unmeasured(true), + ), + BootPageAcceptance::ErrorPage => todo!(), + BootPageAcceptance::SecretsPage => { + (IgvmPageDataType::SECRETS, IgvmPageDataFlags::new()) + } + BootPageAcceptance::CpuidPage => { + (IgvmPageDataType::CPUID_DATA, IgvmPageDataFlags::new()) + } + BootPageAcceptance::CpuidExtendedStatePage => { + (IgvmPageDataType::CPUID_XF, IgvmPageDataFlags::new()) + } + BootPageAcceptance::VpContext => unreachable!(), + BootPageAcceptance::Shared => ( + IgvmPageDataType::NORMAL, + IgvmPageDataFlags::new().with_shared(true), + ), + }; + + // Split data slice into data to be imported for this page and remaining. + let import_data_len = std::cmp::min(PAGE_SIZE_4K as usize, data.len()); + let (import_data, data_remaining) = data.split_at(import_data_len); + data = data_remaining; + + self.page_data_directives + .push(IgvmDirectiveHeader::PageData { + gpa: page * PAGE_SIZE_4K, + compatibility_mask: DEFAULT_COMPATIBILITY_MASK, + flags, + data_type, + data: import_data.to_vec(), + }); + } + } + + Ok(()) + } } -impl ImageLoad for IgvmLoader { +impl ImageLoad for IgvmVtlLoader<'_, R> { fn isolation_config(&self) -> IsolationConfig { - match self.isolation_type { + match self.loader.isolation_type { LoaderIsolationType::None => IsolationConfig { - paravisor_present: self.paravisor_present, + paravisor_present: self.loader.paravisor_present, isolation_type: IsolationType::None, shared_gpa_boundary_bits: None, }, LoaderIsolationType::Vbs { .. } => IsolationConfig { - paravisor_present: self.paravisor_present, + paravisor_present: self.loader.paravisor_present, isolation_type: IsolationType::Vbs, shared_gpa_boundary_bits: None, }, @@ -771,12 +890,12 @@ impl ImageLoad for IgvmLoader policy: _, injection_type: _, } => IsolationConfig { - paravisor_present: self.paravisor_present, + paravisor_present: self.loader.paravisor_present, isolation_type: IsolationType::Snp, shared_gpa_boundary_bits, }, LoaderIsolationType::Tdx { .. } => IsolationConfig { - paravisor_present: self.paravisor_present, + paravisor_present: self.loader.paravisor_present, isolation_type: IsolationType::Tdx, shared_gpa_boundary_bits: Some(TDX_SHARED_GPA_BOUNDARY_BITS), }, @@ -802,7 +921,7 @@ impl ImageLoad for IgvmLoader let area_id = (page_base, page_count); // Allocate a new parameter area, that must not overlap other accepted ranges. - self.accept_new_range( + self.loader.accept_new_range( page_base, page_count as u64, debug_tag, @@ -810,18 +929,21 @@ impl ImageLoad for IgvmLoader )?; let index: u32 = self + .loader .parameter_areas .len() .try_into() .expect("parameter area greater than u32"); - self.parameter_areas.insert(area_id, index); + self.loader.parameter_areas.insert(area_id, index); // Add the newly allocated parameter area index to headers. - self.directives.push(IgvmDirectiveHeader::ParameterArea { - number_of_bytes: page_count as u64 * PAGE_SIZE_4K, - parameter_area_index: index, - initial_data: initial_data.to_vec(), - }); + self.loader + .directives + .push(IgvmDirectiveHeader::ParameterArea { + number_of_bytes: page_count as u64 * PAGE_SIZE_4K, + parameter_area_index: index, + initial_data: initial_data.to_vec(), + }); tracing::debug!( index, @@ -842,7 +964,7 @@ impl ImageLoad for IgvmLoader ) -> anyhow::Result<()> { let index = parameter_area.0; - if index >= self.parameter_areas.len() as u32 { + if index >= self.loader.parameter_areas.len() as u32 { anyhow::bail!("invalid parameter area index: {:x}", index); } @@ -870,7 +992,7 @@ impl ImageLoad for IgvmLoader IgvmParameterType::DeviceTree => IgvmDirectiveHeader::DeviceTree(info), }; - self.directives.push(header); + self.loader.directives.push(header); Ok(()) } @@ -881,113 +1003,28 @@ impl ImageLoad for IgvmLoader page_count: u64, debug_tag: &str, acceptance: BootPageAcceptance, - mut data: &[u8], + data: &[u8], ) -> anyhow::Result<()> { - tracing::debug!( - page_base, - ?acceptance, - page_count, - data_size = data.len(), - "Importing page", - ); - - // Pages must not overlap already accepted ranges - self.accept_new_range(page_base, page_count, debug_tag, acceptance)?; - - // Page count must be larger or equal to data. - if page_count * PAGE_SIZE_4K < data.len() as u64 { - anyhow::bail!( - "data len {:x} is larger than page_count {page_count:x}", - data.len() - ); - } - - // VpContext imports are handled differently, as they have a different IGVM header - // type than normal data pages. - if acceptance == BootPageAcceptance::VpContext { - // This is only supported on SNP currently. - match self.isolation_type { - LoaderIsolationType::Snp { .. } => {} - _ => { - anyhow::bail!("vpcontext acceptance only supported on SNP"); - } - } - - // Data size must match SNP VMSA size. - if data.len() != size_of::() { - anyhow::bail!("data len {:x} does not match VMSA size", data.len()); - } - - // Page count must be 1. - if page_count != 1 { - anyhow::bail!("page count {page_count:x} for snp vmsa is not 1"); - } - - self.directives.push(IgvmDirectiveHeader::SnpVpContext { - gpa: page_base * PAGE_SIZE_4K, - compatibility_mask: DEFAULT_COMPATIBILITY_MASK, - vp_index: 0, - vmsa: Box::new(SevVmsa::read_from(data).expect("should be correct size")), - }); - } else { - for page in page_base..page_base + page_count { - let (data_type, flags) = match acceptance { - BootPageAcceptance::Exclusive => { - (IgvmPageDataType::NORMAL, IgvmPageDataFlags::new()) - } - BootPageAcceptance::ExclusiveUnmeasured => ( - IgvmPageDataType::NORMAL, - IgvmPageDataFlags::new().with_unmeasured(true), - ), - BootPageAcceptance::ErrorPage => todo!(), - BootPageAcceptance::SecretsPage => { - (IgvmPageDataType::SECRETS, IgvmPageDataFlags::new()) - } - BootPageAcceptance::CpuidPage => { - (IgvmPageDataType::CPUID_DATA, IgvmPageDataFlags::new()) - } - BootPageAcceptance::CpuidExtendedStatePage => { - (IgvmPageDataType::CPUID_XF, IgvmPageDataFlags::new()) - } - BootPageAcceptance::VpContext => unreachable!(), - BootPageAcceptance::Shared => ( - IgvmPageDataType::NORMAL, - IgvmPageDataFlags::new().with_shared(true), - ), - }; - - // Split data slice into data to be imported for this page and remaining. - let import_data_len = std::cmp::min(PAGE_SIZE_4K as usize, data.len()); - let (import_data, data_remaining) = data.split_at(import_data_len); - data = data_remaining; - - self.page_data_directives - .push(IgvmDirectiveHeader::PageData { - gpa: page * PAGE_SIZE_4K, - compatibility_mask: DEFAULT_COMPATIBILITY_MASK, - flags, - data_type, - data: import_data.to_vec(), - }); - } - } - - Ok(()) + self.loader + .import_pages(page_base, page_count, debug_tag, acceptance, data) } - fn import_vp_register(&mut self, vtl: Vtl, register: R) -> anyhow::Result<()> { - if vtl > self.max_vtl { - anyhow::bail!( - "vtl specified {vtl:?} is greater than max enabled vtl {:?}", - self.max_vtl - ); + fn import_vp_register(&mut self, register: R) -> anyhow::Result<()> { + if self.vtl == self.loader.max_vtl { + self.loader + .vp_context + .as_mut() + .unwrap() + .import_vp_register(register); + } else { + assert_eq!(self.vtl, Vtl::Vtl0); + self.loader + .lower_vtl_context + .as_mut() + .unwrap() + .import_vp_register(register) } - self.vp_context_builder - .as_mut() - .expect("option should always be some") - .import_vp_register(vtl, register); - Ok(()) } @@ -1017,14 +1054,16 @@ impl ImageLoad for IgvmLoader let vtl2_protectable = memory_type == loader::importer::StartupMemoryType::Vtl2ProtectableRam; - self.directives.push(IgvmDirectiveHeader::RequiredMemory { - gpa, - compatibility_mask, - number_of_bytes, - vtl2_protectable, - }); + self.loader + .directives + .push(IgvmDirectiveHeader::RequiredMemory { + gpa, + compatibility_mask, + number_of_bytes, + vtl2_protectable, + }); - self.required_memory.push(RequiredMemory { + self.loader.required_memory.push(RequiredMemory { range: MemoryRange::new(gpa..gpa + number_of_bytes as u64), vtl2_protectable, }); @@ -1032,39 +1071,26 @@ impl ImageLoad for IgvmLoader Ok(()) } - fn set_vp_context_page( - &mut self, - vtl: Vtl, - page_base: u64, - acceptance: BootPageAcceptance, - ) -> anyhow::Result<()> { - if vtl > self.max_vtl { - anyhow::bail!( - "vtl specified {vtl:?} is greater than max enabled vtl {:?}", - self.max_vtl - ); - } - - self.vp_context_builder + fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> { + self.loader + .vp_context .as_mut() - .expect("option should always be some") - .set_vp_context_memory(vtl, page_base, acceptance); + .unwrap() + .set_vp_context_memory(page_base); Ok(()) } - fn vp_context_page(&self, vtl: Vtl) -> anyhow::Result { - if vtl > self.max_vtl { - anyhow::bail!( - "vtl specified {vtl:?} is greater than max enabled vtl {:?}", - self.max_vtl - ); + fn set_lower_vtl_context_page(&mut self, page_base: u64) -> anyhow::Result<()> { + if self.vtl != Vtl::Vtl0 { + self.loader + .lower_vtl_context + .as_mut() + .unwrap() + .set_vp_context_memory(page_base); } - self.vp_context_builder - .as_ref() - .expect("option should always be some") - .vp_context_page(vtl) + Ok(()) } fn relocation_region( @@ -1074,13 +1100,12 @@ impl ImageLoad for IgvmLoader relocation_alignment: u64, minimum_relocation_gpa: u64, maximum_relocation_gpa: u64, - is_vtl2: bool, apply_rip_offset: bool, apply_gdtr_offset: bool, vp_index: u16, - vtl: Vtl, ) -> anyhow::Result<()> { if let Some(overlap) = self + .loader .relocatable_regions .get_range(gpa..=(gpa + size_bytes - 1)) { @@ -1116,7 +1141,8 @@ impl ImageLoad for IgvmLoader ); } - self.initialization_headers + self.loader + .initialization_headers .push(IgvmInitializationHeader::RelocatableRegion { compatibility_mask: DEFAULT_COMPATIBILITY_MASK, relocation_alignment, @@ -1124,14 +1150,14 @@ impl ImageLoad for IgvmLoader relocation_region_size: size_bytes, minimum_relocation_gpa, maximum_relocation_gpa, - is_vtl2, + is_vtl2: self.vtl == Vtl::Vtl2, apply_rip_offset, apply_gdtr_offset, vp_index, - vtl: to_igvm_vtl(vtl), + vtl: to_igvm_vtl(self.vtl), }); - self.relocatable_regions.insert( + self.loader.relocatable_regions.insert( gpa..=gpa + size_bytes - 1, RelocationType::Normal(IgvmRelocatableRegion { base_gpa: gpa, @@ -1139,11 +1165,11 @@ impl ImageLoad for IgvmLoader minimum_relocation_gpa, maximum_relocation_gpa, relocation_alignment, - is_vtl2, + is_vtl2: self.vtl == Vtl::Vtl2, apply_rip_offset, apply_gdtr_offset, vp_index, - vtl: to_igvm_vtl(vtl), + vtl: to_igvm_vtl(self.vtl), }), ); @@ -1156,10 +1182,9 @@ impl ImageLoad for IgvmLoader size_pages: u64, used_size_pages: u64, vp_index: u16, - vtl: Vtl, ) -> anyhow::Result<()> { // can only be one set - if let Some(region) = &self.page_table_region { + if let Some(region) = &self.loader.page_table_region { anyhow::bail!("page table relocation already set {:?}", region) } @@ -1172,22 +1197,27 @@ impl ImageLoad for IgvmLoader let end_gpa = page_table_gpa + size_pages * PAGE_SIZE_4K - 1; // cannot override other relocatable regions - if let Some(overlap) = self.relocatable_regions.get_range(page_table_gpa..=end_gpa) { + if let Some(overlap) = self + .loader + .relocatable_regions + .get_range(page_table_gpa..=end_gpa) + { anyhow::bail!( "new page table relocation region overlaps existing region {:?}", overlap ); } - self.initialization_headers - .push(IgvmInitializationHeader::PageTableRelocationRegion { + self.loader.initialization_headers.push( + IgvmInitializationHeader::PageTableRelocationRegion { compatibility_mask: DEFAULT_COMPATIBILITY_MASK, gpa: page_table_gpa, size: size_pages * PAGE_SIZE_4K, used_size: used_size_pages * PAGE_SIZE_4K, vp_index, - vtl: to_igvm_vtl(vtl), - }); + vtl: to_igvm_vtl(self.vtl), + }, + ); let region = PageTableRegion { gpa: page_table_gpa, @@ -1195,18 +1225,18 @@ impl ImageLoad for IgvmLoader used_size_pages, }; - self.relocatable_regions.insert( + self.loader.relocatable_regions.insert( page_table_gpa..=end_gpa, RelocationType::PageTable(region.clone()), ); - self.page_table_region = Some(region); + self.loader.page_table_region = Some(region); Ok(()) } fn set_imported_regions_config_page(&mut self, page_base: u64) { - self.imported_regions_config_page = Some(page_base); + self.loader.imported_regions_config_page = Some(page_base); } } @@ -1309,20 +1339,23 @@ mod tests { enable_debug: false, }, ); - - let data = vec![0, 5]; - loader - .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data) - .unwrap(); - loader - .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data) - .unwrap(); - loader - .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data) - .unwrap(); - loader - .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data) - .unwrap(); + { + let mut loader = loader.for_vtl(Vtl::Vtl0); + + let data = vec![0, 5]; + loader + .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data) + .unwrap(); + loader + .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data) + .unwrap(); + loader + .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data) + .unwrap(); + loader + .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data) + .unwrap(); + } let igvm_output = loader.finalize(1).unwrap(); let doc = igvm_output.doc.expect("doc"); diff --git a/vm/loader/igvmfilegen/src/main.rs b/vm/loader/igvmfilegen/src/main.rs index 0adf9c066f..f03957a35b 100644 --- a/vm/loader/igvmfilegen/src/main.rs +++ b/vm/loader/igvmfilegen/src/main.rs @@ -14,6 +14,8 @@ use anyhow::bail; use anyhow::Context; use clap::Parser; use file_loader::IgvmLoaderRegister; +use file_loader::IgvmVtlLoader; +use hvdef::Vtl; use igvm::IgvmFile; use igvm_defs::SnpPolicy; use igvm_defs::TdxPolicy; @@ -211,10 +213,20 @@ fn create_igvm_file( let mut loader = IgvmLoader::::new(with_paravisor, loader_isolation_type); // Load VTL0, then VTL2, if present. - let vtl0_load_info = load_vtl0(&mut loader, &config.vtl0, &resources)?; + let vtl0_load_info = load_image( + &mut loader.for_vtl(Vtl::Vtl0), + &config.vtl0, + &resources, + Vtl0LoadInfo::None, + )?; if config.max_vtl == 2 { - load_vtl2(&mut loader, &config.vtl2, &resources, vtl0_load_info)?; + load_image( + &mut loader.for_vtl(Vtl::Vtl2), + &config.vtl2, + &resources, + vtl0_load_info, + )?; } let igvm_output = loader @@ -551,11 +563,12 @@ impl IgvmfilegenRegister for Aarch64Register { } } -/// Load an image into VTL0. -fn load_vtl0<'a, R: IgvmfilegenRegister + GuestArch + 'static>( - loader: &mut IgvmLoader, +/// Load an image. +fn load_image<'a, R: IgvmfilegenRegister + GuestArch + 'static>( + loader: &mut IgvmVtlLoader<'_, R>, config: &'a VtlConfig, resources: &'a Resources, + vtl0_load_info: Vtl0LoadInfo<'_>, ) -> anyhow::Result> { tracing::debug!(?config, "loading into VTL0"); @@ -616,34 +629,6 @@ fn load_vtl0<'a, R: IgvmfilegenRegister + GuestArch + 'static>( Vtl0LoadInfo::Linux(command_line, load_info) } - VtlConfig::Underhill { .. } => { - bail!("underhill can only be loaded into VTL2") - } - }; - - Ok(load_info) -} - -/// Load an image into VTL2. -fn load_vtl2( - loader: &mut IgvmLoader, - config: &VtlConfig, - resources: &Resources, - vtl0_load_info: Vtl0LoadInfo<'_>, -) -> anyhow::Result<()> { - tracing::debug!( - config = ?config, - load_info = ?&vtl0_load_info, - "Loading into VTL2 with VTL0 load info", - ); - - match config { - VtlConfig::None => { - // Nothing is loaded. - } - VtlConfig::Uefi { .. } => { - bail!("uefi can only be loaded into VTL0") - } VtlConfig::Underhill { command_line, static_command_line, @@ -697,7 +682,7 @@ fn load_vtl2( supports_linux: None, }, Vtl0LoadInfo::Uefi(load_info) => Vtl0Config { - supports_pcat: loader.arch() == GuestArchKind::X86_64, + supports_pcat: loader.loader().arch() == GuestArchKind::X86_64, supports_uefi: Some(load_info), supports_linux: None, }, @@ -711,7 +696,7 @@ fn load_vtl2( }, }; - let command_line = if loader.confidential_debug() { + let command_line = if loader.loader().confidential_debug() { tracing::info!("enabling underhill confidential debug environment flag"); format!( "{command_line} {}=1", @@ -739,11 +724,10 @@ fn load_vtl2( vtl0_load_config, ) .context("underhill kernel loader")?; + + Vtl0LoadInfo::None } - VtlConfig::Linux { .. } => { - bail!("linux can only be loaded into vtl0") - } - } + }; - Ok(()) + Ok(load_info) } diff --git a/vm/loader/igvmfilegen/src/vp_context_builder/mod.rs b/vm/loader/igvmfilegen/src/vp_context_builder/mod.rs index 4312bf7a99..b799946976 100644 --- a/vm/loader/igvmfilegen/src/vp_context_builder/mod.rs +++ b/vm/loader/igvmfilegen/src/vp_context_builder/mod.rs @@ -7,7 +7,6 @@ pub mod snp; pub mod tdx; pub mod vbs; -use hvdef::Vtl; use igvm::IgvmDirectiveHeader; use loader::importer::BootPageAcceptance; @@ -33,15 +32,12 @@ pub trait VpContextBuilder { type Register; /// Import a register to the BSP at the given vtl. - fn import_vp_register(&mut self, vtl: Vtl, register: Self::Register); + fn import_vp_register(&mut self, register: Self::Register); /// Define the base of the GPA range to be used for architecture-specific VP context data. - fn set_vp_context_memory(&mut self, vtl: Vtl, page_base: u64, acceptance: BootPageAcceptance); - - /// Obtain the page base of the GPA range to be used for architecture specific VP context data. - fn vp_context_page(&self, vtl: Vtl) -> anyhow::Result; + fn set_vp_context_memory(&mut self, page_base: u64); /// Finalize all VP context data. Returns architecture specific data that should be either imported /// into guest memory space or added directly to the IGVM file. - fn finalize(self: Box) -> Vec; + fn finalize(&mut self, state: &mut Vec); } diff --git a/vm/loader/igvmfilegen/src/vp_context_builder/snp.rs b/vm/loader/igvmfilegen/src/vp_context_builder/snp.rs index 4d202cf9bf..8e6fa9e187 100644 --- a/vm/loader/igvmfilegen/src/vp_context_builder/snp.rs +++ b/vm/loader/igvmfilegen/src/vp_context_builder/snp.rs @@ -3,8 +3,6 @@ //! SNP VP context builder. -use super::vbs::VbsVpContext; -use crate::file_loader::HV_NUM_VTLS; use crate::vp_context_builder::VpContextBuilder; use crate::vp_context_builder::VpContextPageState; use crate::vp_context_builder::VpContextState; @@ -22,59 +20,6 @@ use x86defs::X64_EFER_SVME; use zerocopy::AsBytes; use zerocopy::FromZeroes; -// The usage of this enum is in an outer box, so it doesn't need to box -// internally itself. -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] -enum SnpVpContext { - None, - Hardware(SnpHardwareContext), - Vbs(VbsVpContext), -} - -impl SnpVpContext { - fn import_vp_register(&mut self, register: X86Register) { - match self { - SnpVpContext::None => { - panic!("importing register to None context") - } - SnpVpContext::Hardware(hardware_context) => hardware_context.import_register(register), - SnpVpContext::Vbs(vbs_context) => vbs_context.import_vp_register(register), - } - } - - fn vp_context_page(&self) -> anyhow::Result { - match self { - SnpVpContext::None => Err(anyhow::anyhow!("no vp context available")), - SnpVpContext::Hardware(hardware_context) => hardware_context.vp_context_page(), - SnpVpContext::Vbs(vbs_context) => vbs_context.vp_context_page(), - } - } - - fn set_vp_context_memory(&mut self, page_base: u64, acceptance: BootPageAcceptance) { - match self { - SnpVpContext::None => panic!("setting vp context memory on None context"), - SnpVpContext::Hardware(hardware_context) => { - hardware_context.set_vp_context_memory(page_base, acceptance) - } - SnpVpContext::Vbs(vbs_context) => { - vbs_context.set_vp_context_memory(page_base, acceptance) - } - } - } - - fn finalize(self) -> Vec { - match self { - SnpVpContext::None => Vec::new(), - SnpVpContext::Hardware(hardware_context) => hardware_context.finalize(), - SnpVpContext::Vbs(vbs_context) => match vbs_context.finalize() { - None => Vec::new(), - Some(state) => vec![state], - }, - } - } -} - /// The interrupt injection type to use for the highest vmpl's VMSA. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum InjectionType { @@ -86,21 +31,30 @@ pub enum InjectionType { /// A hardware SNP VP context, that is imported as a VMSA. #[derive(Debug)] -struct SnpHardwareContext { +pub struct SnpHardwareContext { /// If an assembly stub to accept the lower 1mb should be imported as page /// data. accept_lower_1mb: bool, - /// The acceptance to import this vp context as. This must be - /// [`BootPageAcceptance::VpContext`]. - acceptance: Option, /// The page number to import this vp context at. - page_number: u64, + page_number: Option, /// The VMSA for this VP. vmsa: SevVmsa, } impl SnpHardwareContext { - fn new( + /// Create a new SNP VP context builder. + /// + /// `enlightened_uefi` specifies if UEFI is enlightened. This will result in + /// [`VpContextBuilder::finalize`] generating additional trampoline code for + /// UEFI running without a paravisor, along with setting different fields in + /// the `SEV_FEATURES` register. + /// + /// `injection_type` specifies the injection type for the highest enabled + /// VMPL. + /// + /// Only the highest VTL will have a VMSA generated, with lower VTLs being + /// imported with the VBS format as page data. + pub fn new( vtl: Vtl, enlightened_uefi: bool, shared_gpa_boundary: u64, @@ -148,13 +102,16 @@ impl SnpHardwareContext { SnpHardwareContext { accept_lower_1mb: enlightened_uefi, - acceptance: None, - page_number: 0, + page_number: None, vmsa, } } +} - fn import_register(&mut self, register: X86Register) { +impl VpContextBuilder for SnpHardwareContext { + type Register = X86Register; + + fn import_vp_register(&mut self, register: X86Register) { let create_vmsa_table_register = |reg: TableRegister| -> SevSelector { SevSelector { limit: reg.limit as u32, @@ -223,35 +180,16 @@ impl SnpHardwareContext { } } - fn vp_context_page(&self) -> anyhow::Result { - match self.acceptance { - None => Err(anyhow::anyhow!("no vp context acceptance set")), - Some(_) => Ok(self.page_number), - } + fn set_vp_context_memory(&mut self, page_base: u64) { + assert!(self.page_number.is_none(), "only allowed to set vmsa once"); + self.page_number = Some(page_base); } - fn set_vp_context_memory(&mut self, page_base: u64, acceptance: BootPageAcceptance) { - assert!(self.acceptance.is_none(), "only allowed to set vmsa once"); - assert_eq!( - acceptance, - BootPageAcceptance::VpContext, - "snp vp context memory must be VpContext" - ); - - self.page_number = page_base; - self.acceptance = Some(acceptance); - } - - fn finalize(mut self) -> Vec { - let mut state = Vec::new(); - - let acceptance = match self.acceptance { - None => return state, - Some(acceptance) => acceptance, + fn finalize(&mut self, state: &mut Vec) { + let Some(page_number) = self.page_number else { + return; }; - assert_eq!(acceptance, BootPageAcceptance::VpContext); - // If no paravisor is present, then generate a trampoline page to perform // validation of the low 1 MB of memory. This is expected by UEFI and // normally performed by the HCL, but must be done in a trampoline if no @@ -356,92 +294,10 @@ impl SnpHardwareContext { } state.push(VpContextState::Page(VpContextPageState { - page_base: self.page_number, + page_base: page_number, page_count: 1, - acceptance, + acceptance: BootPageAcceptance::VpContext, data: self.vmsa.as_bytes().to_vec(), })); - - state - } -} - -/// Implementation of [`VpContextBuilder``] for a platform with AMD SEV-SNP -/// isolation. -#[derive(Debug)] -pub struct SnpVpContextBuilder { - contexts: [SnpVpContext; HV_NUM_VTLS], -} - -impl SnpVpContextBuilder { - /// Create a new SNP VP context builder. - /// - /// `enlightened_uefi` specifies if UEFI is enlightened. This will result in - /// [`VpContextBuilder::finalize`] generating additional trampoline code for - /// UEFI running without a paravisor, along with setting different fields in - /// the `SEV_FEATURES` register. - /// - /// `injection_type` specifies the injection type for the highest enabled - /// VMPL. - /// - /// Only the highest VTL will have a VMSA generated, with lower VTLs being - /// imported with the VBS format as page data. - pub fn new( - max_vtl: Vtl, - enlightened_uefi: bool, - shared_gpa_boundary: u64, - injection_type: InjectionType, - ) -> anyhow::Result { - let mut contexts = [SnpVpContext::None, SnpVpContext::None, SnpVpContext::None]; - - match max_vtl { - Vtl::Vtl0 => { - contexts[0] = SnpVpContext::Hardware(SnpHardwareContext::new( - Vtl::Vtl0, - enlightened_uefi, - shared_gpa_boundary, - injection_type, - )) - } - Vtl::Vtl1 => anyhow::bail!("VTL1 import state not supported for SNP"), - Vtl::Vtl2 => { - // Treat VTL0 as the VBS format. - contexts[0] = SnpVpContext::Vbs(VbsVpContext::new(0)); - contexts[2] = SnpVpContext::Hardware(SnpHardwareContext::new( - Vtl::Vtl2, - enlightened_uefi, - shared_gpa_boundary, - injection_type, - )) - } - } - - Ok(Self { contexts }) - } -} - -impl VpContextBuilder for SnpVpContextBuilder { - type Register = X86Register; - - fn import_vp_register(&mut self, vtl: Vtl, register: X86Register) { - self.contexts[vtl as usize].import_vp_register(register); - } - - fn vp_context_page(&self, vtl: Vtl) -> anyhow::Result { - self.contexts[vtl as usize].vp_context_page() - } - - fn set_vp_context_memory(&mut self, vtl: Vtl, page_base: u64, acceptance: BootPageAcceptance) { - self.contexts[vtl as usize].set_vp_context_memory(page_base, acceptance); - } - - fn finalize(self: Box) -> Vec { - let mut state = Vec::new(); - - for context in self.contexts { - state.extend(context.finalize()) - } - - state } } diff --git a/vm/loader/igvmfilegen/src/vp_context_builder/tdx.rs b/vm/loader/igvmfilegen/src/vp_context_builder/tdx.rs index 94462863b4..4aca88a702 100644 --- a/vm/loader/igvmfilegen/src/vp_context_builder/tdx.rs +++ b/vm/loader/igvmfilegen/src/vp_context_builder/tdx.rs @@ -3,12 +3,9 @@ //! TDX VP context builder. -use super::vbs::VbsVpContext; use super::VpContextBuilder; use super::VpContextState; -use crate::file_loader::HV_NUM_VTLS; use crate::vp_context_builder::VpContextPageState; -use hvdef::Vtl; use igvm_defs::PAGE_SIZE_4K; use loader::importer::SegmentRegister; use loader::importer::X86Register; @@ -61,22 +58,26 @@ pub struct TdxTrampolineContext { /// Represents a hardware context for TDX. This contains both the sets of /// initial registers and registers set by the trampoline code. #[derive(Debug)] -struct TdxHardwareContext { +pub struct TdxHardwareContext { trampoline_context: TdxTrampolineContext, accept_lower_1mb: bool, } impl TdxHardwareContext { - fn new(accept_lower_1mb: bool) -> Self { + pub fn new(accept_lower_1mb: bool) -> Self { Self { trampoline_context: TdxTrampolineContext::default(), accept_lower_1mb, } } +} + +impl VpContextBuilder for TdxHardwareContext { + type Register = X86Register; /// Import a register into the hardware context. Only a subset of registers /// are allowed. - fn import_register(&mut self, register: X86Register) { + fn import_vp_register(&mut self, register: X86Register) { let mut set_data_selector = |reg: SegmentRegister| { if self.trampoline_context.data_selector == 0 { self.trampoline_context.data_selector = reg.selector; @@ -156,7 +157,11 @@ impl TdxHardwareContext { } } - fn finalize(mut self) -> VpContextState { + fn set_vp_context_memory(&mut self, _page_base: u64) { + unimplemented!("not supported for TDX"); + } + + fn finalize(&mut self, state: &mut Vec) { // Construct and load an initial temporary GDT to use for the transition // to long mode. A single selector (0008:) is defined as a 64-bit code // segment. @@ -505,116 +510,11 @@ impl TdxHardwareContext { copy_instr(&mut reset_page, byte_offset, relative_offset.as_bytes()); // Add this data to the architectural reset page. - VpContextState::Page(VpContextPageState { + state.push(VpContextState::Page(VpContextPageState { page_base: 0xFFFFF, page_count: 1, acceptance: loader::importer::BootPageAcceptance::Exclusive, data: reset_page, - }) - } -} - -#[derive(Debug)] -enum TdxVpContext { - None, - Hardware(TdxHardwareContext), - Vbs(VbsVpContext), -} - -impl TdxVpContext { - fn import_vp_register(&mut self, register: X86Register) { - match self { - TdxVpContext::None => panic!("importing register to None context"), - TdxVpContext::Hardware(hardware_context) => hardware_context.import_register(register), - TdxVpContext::Vbs(vbs_context) => vbs_context.import_vp_register(register), - } - } - - fn vp_context_page(&self) -> anyhow::Result { - match self { - TdxVpContext::None => Err(anyhow::anyhow!("no vp context available")), - TdxVpContext::Hardware(_) => Ok(0xFFFFF), - TdxVpContext::Vbs(vbs_context) => vbs_context.vp_context_page(), - } - } - - fn set_vp_context_memory( - &mut self, - page_base: u64, - acceptance: loader::importer::BootPageAcceptance, - ) { - match self { - TdxVpContext::None => panic!("setting vp context memory on None context"), - TdxVpContext::Hardware(_) => panic!("tdx hardware context is only the reset page"), - TdxVpContext::Vbs(vbs_context) => { - vbs_context.set_vp_context_memory(page_base, acceptance) - } - } - } - - fn finalize(self) -> Option { - match self { - TdxVpContext::None => None, - TdxVpContext::Hardware(hardware_context) => Some(hardware_context.finalize()), - TdxVpContext::Vbs(vbs_context) => vbs_context.finalize(), - } - } -} - -#[derive(Debug)] -pub struct TdxVpContextBuilder { - contexts: [TdxVpContext; HV_NUM_VTLS], -} - -impl TdxVpContextBuilder { - pub fn new(max_vtl: Vtl, accept_lower_1mb: bool) -> anyhow::Result { - let mut contexts = [TdxVpContext::None, TdxVpContext::None, TdxVpContext::None]; - - match max_vtl { - Vtl::Vtl0 => { - contexts[0] = TdxVpContext::Hardware(TdxHardwareContext::new(accept_lower_1mb)) - } - Vtl::Vtl1 => anyhow::bail!("VTL1 import state not supported for TDX"), - Vtl::Vtl2 => { - // Treat VTL0 as the VBS format, as that's what Underhill expects. - contexts[0] = TdxVpContext::Vbs(VbsVpContext::new(0)); - contexts[2] = TdxVpContext::Hardware(TdxHardwareContext::new(accept_lower_1mb)); - } - } - - Ok(Self { contexts }) - } -} - -impl VpContextBuilder for TdxVpContextBuilder { - type Register = X86Register; - - fn import_vp_register(&mut self, vtl: Vtl, register: X86Register) { - self.contexts[vtl as usize].import_vp_register(register); - } - - fn set_vp_context_memory( - &mut self, - vtl: Vtl, - page_base: u64, - acceptance: loader::importer::BootPageAcceptance, - ) { - self.contexts[vtl as usize].set_vp_context_memory(page_base, acceptance); - } - - fn vp_context_page(&self, vtl: Vtl) -> anyhow::Result { - self.contexts[vtl as usize].vp_context_page() - } - - fn finalize(self: Box) -> Vec { - let mut contexts = Vec::new(); - - for context in self.contexts { - if let Some(v) = context.finalize() { - contexts.push(v); - } - } - - contexts + })); } } diff --git a/vm/loader/igvmfilegen/src/vp_context_builder/vbs.rs b/vm/loader/igvmfilegen/src/vp_context_builder/vbs.rs index 85046802b4..57693f6b7c 100644 --- a/vm/loader/igvmfilegen/src/vp_context_builder/vbs.rs +++ b/vm/loader/igvmfilegen/src/vp_context_builder/vbs.rs @@ -4,7 +4,6 @@ //! VBS VP context builder. use crate::file_loader::DEFAULT_COMPATIBILITY_MASK; -use crate::file_loader::HV_NUM_VTLS; use crate::vp_context_builder::VpContextBuilder; use crate::vp_context_builder::VpContextPageState; use crate::vp_context_builder::VpContextState; @@ -24,15 +23,15 @@ use std::mem::discriminant; pub trait VbsRegister: Sized { /// Convert the list of registers into the corresponding /// [`IgvmDirectiveHeader`] for this architecture. - fn into_igvm_header(vtl: Vtl, list: Vec) -> IgvmDirectiveHeader; + fn into_igvm_header(vtl: Vtl, list: &[Self]) -> IgvmDirectiveHeader; } impl VbsRegister for X86Register { - fn into_igvm_header(vtl: Vtl, list: Vec) -> IgvmDirectiveHeader { + fn into_igvm_header(vtl: Vtl, list: &[Self]) -> IgvmDirectiveHeader { IgvmDirectiveHeader::X64VbsVpContext { registers: list - .into_iter() - .map(|reg| reg.into()) + .iter() + .map(|®| reg.into()) .collect::>(), vtl: (vtl as u8).try_into().expect("vtl should be valid"), compatibility_mask: DEFAULT_COMPATIBILITY_MASK, @@ -41,11 +40,11 @@ impl VbsRegister for X86Register { } impl VbsRegister for Aarch64Register { - fn into_igvm_header(vtl: Vtl, list: Vec) -> IgvmDirectiveHeader { + fn into_igvm_header(vtl: Vtl, list: &[Self]) -> IgvmDirectiveHeader { IgvmDirectiveHeader::AArch64VbsVpContext { registers: list - .into_iter() - .map(|reg| reg.into()) + .iter() + .map(|®| reg.into()) .collect::>(), vtl: (vtl as u8).try_into().expect("vtl should be valid"), compatibility_mask: DEFAULT_COMPATIBILITY_MASK, @@ -55,11 +54,8 @@ impl VbsRegister for Aarch64Register { #[derive(Debug, Clone)] pub struct VbsVpContext { - /// The acceptance to import this vp context as. This tracks if finalize - /// will generate page data or an IGVM VP context header. - acceptance: Option, /// The page number to import this vp context at. - page_number: u64, + page_number: Option, /// The registers set for this VP. registers: Vec, /// The VTL this VP context is for. @@ -69,14 +65,17 @@ pub struct VbsVpContext { impl VbsVpContext { pub fn new(vtl: u8) -> Self { Self { - acceptance: None, - page_number: 0, + page_number: None, registers: Vec::new(), vtl, } } +} + +impl VpContextBuilder for VbsVpContext { + type Register = R; - pub fn import_vp_register(&mut self, register: R) { + fn import_vp_register(&mut self, register: R) { // Check for duplicate register assert!( !self @@ -89,111 +88,48 @@ impl VbsVpContext { self.registers.push(register); } - pub fn vp_context_page(&self) -> anyhow::Result { - match self.acceptance { - None => Err(anyhow::anyhow!("no vp context page set")), - Some(_) => Ok(self.page_number), - } - } - - pub fn set_vp_context_memory(&mut self, page_base: u64, acceptance: BootPageAcceptance) { + fn set_vp_context_memory(&mut self, page_base: u64) { assert!( - self.acceptance.is_none(), + self.page_number.is_none(), "only allowed to set vp context memory once" ); - self.page_number = page_base; - self.acceptance = Some(acceptance); + self.page_number = Some(page_base); } - pub fn finalize(self) -> Option { + fn finalize(&mut self, state: &mut Vec) { if self.registers.is_empty() { - None - } else { - let header = R::into_igvm_header( - self.vtl.try_into().expect("vtl should be valid"), - self.registers, - ); - - match self.acceptance { - None => { - // Serialize as a VP context IGVM header. - Some(VpContextState::Directive(header)) - } - Some(acceptance) => { - // Serialize the same binary format as an IGVM header, but instead to be deposited as page data. - let mut variable_header = Vec::new(); - let mut file_data = FileDataSerializer::new(0); - header - .write_binary_header(&mut variable_header, &mut file_data) - .expect("registers should be valid"); - - let file_data = file_data.take(); - - assert!(file_data.len() <= PAGE_SIZE_4K as usize); - - Some(VpContextState::Page(VpContextPageState { - page_base: self.page_number, - page_count: 1, - acceptance, - data: file_data, - })) - } - } + return; } - } -} - -#[derive(Debug)] -pub struct VbsVpContextBuilder { - contexts: [VbsVpContext; HV_NUM_VTLS], -} - -impl VbsVpContextBuilder { - pub(crate) fn new() -> Self { - Self { - contexts: [ - VbsVpContext::::new(0), - VbsVpContext::::new(1), - VbsVpContext::::new(2), - ], - } - } -} - -impl VpContextBuilder for VbsVpContextBuilder { - type Register = R; - - fn import_vp_register(&mut self, vtl: Vtl, register: R) { - // TODO: Importing VTL1 state not currently supported. - assert!(vtl != Vtl::Vtl1); - - self.contexts[vtl as usize].import_vp_register(register); - } - - fn vp_context_page(&self, vtl: Vtl) -> anyhow::Result { - self.contexts[vtl as usize].vp_context_page() - } - - fn set_vp_context_memory(&mut self, vtl: Vtl, page_base: u64, acceptance: BootPageAcceptance) { - // TODO: Importing VTL1 state not currently supported. - assert!(vtl != Vtl::Vtl1); - - self.contexts[vtl as usize].set_vp_context_memory(page_base, acceptance); - } - - fn finalize(self: Box) -> Vec { - // TODO: Importing VTL1 state not currently supported. - assert!(self.contexts[1].registers.is_empty()); - - let mut state = Vec::new(); + let header = R::into_igvm_header( + self.vtl.try_into().expect("vtl should be valid"), + &self.registers, + ); - for context in self.contexts { - if let Some(v) = context.finalize() { - state.push(v); + match self.page_number { + None => { + // Serialize as a VP context IGVM header. + state.push(VpContextState::Directive(header)); + } + Some(page_number) => { + // Serialize the same binary format as an IGVM header, but instead to be deposited as page data. + let mut variable_header = Vec::new(); + let mut file_data = FileDataSerializer::new(0); + header + .write_binary_header(&mut variable_header, &mut file_data) + .expect("registers should be valid"); + + let file_data = file_data.take(); + + assert!(file_data.len() <= PAGE_SIZE_4K as usize); + + state.push(VpContextState::Page(VpContextPageState { + page_base: page_number, + page_count: 1, + acceptance: BootPageAcceptance::Exclusive, + data: file_data, + })); } } - - state } } diff --git a/vm/loader/src/common.rs b/vm/loader/src/common.rs index 7ad08ce3f6..bd17dae4a6 100644 --- a/vm/loader/src/common.rs +++ b/vm/loader/src/common.rs @@ -8,7 +8,6 @@ use crate::importer::ImageLoad; use crate::importer::SegmentRegister; use crate::importer::TableRegister; use crate::importer::X86Register; -use hvdef::Vtl; use hvdef::HV_PAGE_SIZE; use vm_topology::memory::MemoryLayout; use x86defs::GdtEntry; @@ -63,7 +62,7 @@ pub fn import_default_gdt( )?; // Import GDTR and selectors. - let mut import_reg = |register| importer.import_vp_register(Vtl::Vtl0, register); + let mut import_reg = |register| importer.import_vp_register(register); import_reg(X86Register::Gdtr(TableRegister { base: gdt_page_base * HV_PAGE_SIZE, limit: (size_of::() * DEFAULT_GDT_COUNT - 1) as u16, diff --git a/vm/loader/src/importer.rs b/vm/loader/src/importer.rs index fa16b5537c..2b8e4d9cca 100644 --- a/vm/loader/src/importer.rs +++ b/vm/loader/src/importer.rs @@ -9,8 +9,6 @@ pub use Aarch64Register as Register; #[cfg(guest_arch = "x86_64")] pub use X86Register as Register; -use hvdef::Vtl; - /// The page acceptance used for importing pages into the initial launch context /// of the guest. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -474,8 +472,8 @@ where data: &[u8], ) -> anyhow::Result<()>; - /// Import a register into the BSP at the given VTL. - fn import_vp_register(&mut self, vtl: Vtl, register: R) -> anyhow::Result<()>; + /// Import a register into the BSP. + fn import_vp_register(&mut self, register: R) -> anyhow::Result<()>; /// Verify with the loader that memory is available in guest address space with the given type. fn verify_startup_memory_available( @@ -488,15 +486,10 @@ where /// Notify the loader to deposit architecture specific VP context information at the given page. /// /// TODO: It probably makes sense to use a different acceptance type than the default one? - fn set_vp_context_page( - &mut self, - vtl: Vtl, - page_base: u64, - acceptance: BootPageAcceptance, - ) -> anyhow::Result<()>; + fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()>; - /// Obtain the page base of the GPA range to be used for architecture specific VP context data. - fn vp_context_page(&self, vtl: Vtl) -> anyhow::Result; + /// Notify the loader to deposit lower VTL context information at the given page. + fn set_lower_vtl_context_page(&mut self, page_base: u64) -> anyhow::Result<()>; /// Specify this region as relocatable. fn relocation_region( @@ -506,11 +499,9 @@ where relocation_alignment: u64, minimum_relocation_gpa: u64, maximum_relocation_gpa: u64, - is_vtl2: bool, apply_rip_offset: bool, apply_gdtr_offset: bool, vp_index: u16, - vtl: Vtl, ) -> anyhow::Result<()>; /// Specify a region as relocatable page table memory. @@ -520,7 +511,6 @@ where size_pages: u64, used_pages: u64, vp_index: u16, - vtl: Vtl, ) -> anyhow::Result<()>; /// Lets the loader know what the base page of where the config page diff --git a/vm/loader/src/linux.rs b/vm/loader/src/linux.rs index 5a80db7c3c..679ccbd210 100644 --- a/vm/loader/src/linux.rs +++ b/vm/loader/src/linux.rs @@ -18,7 +18,6 @@ use aarch64defs::TranslationControlEl1; use aarch64defs::TranslationGranule0; use aarch64defs::TranslationGranule1; use bitfield_struct::bitfield; -use hvdef::Vtl; use hvdef::HV_PAGE_SIZE; use loader_defs::linux as defs; use page_table::x64::align_up_to_large_page_size; @@ -392,7 +391,7 @@ pub fn load_config( // Set common X64 registers. Segments already set by default gdt. let mut import_reg = |register| { importer - .import_vp_register(Vtl::Vtl0, register) + .import_vp_register(register) .map_err(Error::Importer) }; @@ -672,13 +671,10 @@ where pub fn set_direct_boot_registers_arm64( importer: &mut impl ImageLoad, load_info: &LoadInfo, - vtl: Vtl, ) -> Result<(), Error> { - assert!(vtl == Vtl::Vtl0); - let mut import_reg = |register| { importer - .import_vp_register(vtl, register) + .import_vp_register(register) .map_err(Error::Importer) }; diff --git a/vm/loader/src/paravisor.rs b/vm/loader/src/paravisor.rs index 2b3bb42e46..b9f5c6e4f1 100644 --- a/vm/loader/src/paravisor.rs +++ b/vm/loader/src/paravisor.rs @@ -407,9 +407,7 @@ where 1 << 48, true, true, - true, 0, // BSP - Vtl::Vtl2, )?; // Tell the loader page table relocation information. @@ -418,7 +416,6 @@ where page_table_region_size / HV_PAGE_SIZE, page_table.len() as u64 / HV_PAGE_SIZE, 0, - Vtl::Vtl2, )?; } @@ -519,7 +516,7 @@ where let mut import_reg = |register| { importer - .import_vp_register(Vtl::Vtl2, register) + .import_vp_register(register) .map_err(Error::Importer) }; @@ -652,7 +649,7 @@ where )?; let vmsa_page_base = config_region_page_base + PARAVISOR_CONFIG_VMSA_PAGE_INDEX; - importer.set_vp_context_page(Vtl::Vtl2, vmsa_page_base, BootPageAcceptance::VpContext)?; + importer.set_vp_context_page(vmsa_page_base)?; } // Load measured config. @@ -694,7 +691,7 @@ where // state? Right now, UEFI is the only thing that actually sets VTL0 vp // context, but if PCAT/Linux did, this wouldn't work. importer - .set_vp_context_page(Vtl::Vtl0, vp_context_page, BootPageAcceptance::Exclusive) + .set_lower_vtl_context_page(vp_context_page) .map_err(Error::Importer)?; } @@ -1057,7 +1054,7 @@ where // state? Right now, UEFI is the only thing that actually sets VTL0 vp // context, but if PCAT/Linux did, this wouldn't work. importer - .set_vp_context_page(Vtl::Vtl0, vp_context_page, BootPageAcceptance::Exclusive) + .set_lower_vtl_context_page(vp_context_page) .map_err(Error::Importer)?; } @@ -1104,10 +1101,8 @@ where PARAVISOR_DEFAULT_MEMORY_BASE_ADDRESS, 1 << 48, true, - true, false, 0, // BSP - Vtl::Vtl2, )?; // Tell the loader page table relocation information. @@ -1116,7 +1111,6 @@ where page_table_region_size / HV_PAGE_SIZE, page_tables.len() as u64 / HV_PAGE_SIZE, 0, - Vtl::Vtl2, )?; } @@ -1132,7 +1126,7 @@ where let mut import_reg = |register| { importer - .import_vp_register(Vtl::Vtl2, register) + .import_vp_register(register) .map_err(Error::Importer) }; diff --git a/vm/loader/src/pcat.rs b/vm/loader/src/pcat.rs index 12806b34cf..8a8594880d 100644 --- a/vm/loader/src/pcat.rs +++ b/vm/loader/src/pcat.rs @@ -7,7 +7,6 @@ use crate::importer::BootPageAcceptance; use crate::importer::ImageLoad; use crate::importer::SegmentRegister; use crate::importer::X86Register; -use hvdef::Vtl; use hvdef::HV_PAGE_SIZE; use thiserror::Error; @@ -97,7 +96,7 @@ pub fn load( // Enable MTRRs, default MTRR is uncached, and set lowest 640KB and highest 128KB as WB let mut import_reg = |register| { importer - .import_vp_register(Vtl::Vtl0, register) + .import_vp_register(register) .map_err(Error::Importer) }; import_reg(X86Register::MtrrDefType(0xc00))?; diff --git a/vm/loader/src/uefi/mod.rs b/vm/loader/src/uefi/mod.rs index 16ea056f1e..22f7c6c899 100644 --- a/vm/loader/src/uefi/mod.rs +++ b/vm/loader/src/uefi/mod.rs @@ -356,7 +356,6 @@ pub mod x86_64 { use crate::importer::X86Register; use crate::uefi::get_sec_entry_point_offset; use crate::uefi::SEC_FIRMWARE_VOLUME_OFFSET; - use hvdef::Vtl; use hvdef::HV_PAGE_SIZE; use page_table::x64::align_up_to_page_size; use page_table::x64::build_page_tables_64; @@ -523,7 +522,7 @@ pub mod x86_64 { let mut import_reg = |register| { importer - .import_vp_register(Vtl::Vtl0, register) + .import_vp_register(register) .map_err(Error::Importer) }; @@ -666,10 +665,7 @@ pub mod x86_64 { // Supply the address of the parameter info block so it can be used // before PEI parses the config information. importer - .import_vp_register( - Vtl::Vtl0, - X86Register::R12(config_area_base_page * HV_PAGE_SIZE), - ) + .import_vp_register(X86Register::R12(config_area_base_page * HV_PAGE_SIZE)) .map_err(Error::Importer)?; // Reserve two pages to hold CPUID information. The first CPUID page @@ -708,19 +704,19 @@ pub mod x86_64 { // config block, since it has different memory protection properties. // The first page following the config block is chosen for the // allocation. + let vp_context_page_number = config_area_base_page + allocator.total() as u64; importer - .set_vp_context_page( - Vtl::Vtl0, - config_area_base_page + allocator.total() as u64, - BootPageAcceptance::VpContext, - ) + .set_vp_context_page(vp_context_page_number) .map_err(Error::Importer)?; - } - // Obtain the page number used for the VP context. - parameter_info.vp_context_page_number = importer - .vp_context_page(Vtl::Vtl0) - .map_err(Error::Importer)?; + parameter_info.vp_context_page_number = vp_context_page_number; + } else { + // If this is not an SNP image, then the VP context page does not + // need to be reported to UEFI. Put in the TDX reset page value for + // consistency with old code; this probably is unnecessary (or the + // UEFI firmware should just be improved to not need this). + parameter_info.vp_context_page_number = 0xfffff; + } // Encode the total amount of pages used by all parameters. parameter_info.parameter_page_count = allocator.total(); @@ -844,7 +840,6 @@ pub mod aarch64 { use crate::importer::BootPageAcceptance; use crate::importer::ImageLoad; use aarch64defs::Cpsr64; - use hvdef::Vtl; use hvdef::HV_PAGE_SIZE; use zerocopy::AsBytes; @@ -928,11 +923,7 @@ pub mod aarch64 { let total_size = blob_offset + blob_size; - let mut import_reg = |reg| { - importer - .import_vp_register(Vtl::Vtl0, reg) - .map_err(Error::Importer) - }; + let mut import_reg = |reg| importer.import_vp_register(reg).map_err(Error::Importer); import_reg(Aarch64Register::Cpsr( Cpsr64::new().with_sp(true).with_el(1).into(), diff --git a/vmm_core/vm_loader/src/lib.rs b/vmm_core/vm_loader/src/lib.rs index a323baa41d..3bae9cd75e 100644 --- a/vmm_core/vm_loader/src/lib.rs +++ b/vmm_core/vm_loader/src/lib.rs @@ -176,16 +176,7 @@ impl ImageLoad for Loader<'_, R> { .context("unable to zero remaining import") } - fn import_vp_register(&mut self, vtl: Vtl, register: R) -> anyhow::Result<()> { - // Only importing to the max VTL for registers is currently allowed, as - // only one set of registers is stored. - if vtl != self.max_vtl { - anyhow::bail!( - "vtl specified {vtl:?} is not the max enabled vtl {:?}", - self.max_vtl - ); - } - + fn import_vp_register(&mut self, register: R) -> anyhow::Result<()> { let entry = self.regs.entry(std::mem::discriminant(®ister)); match entry { std::collections::hash_map::Entry::Occupied(_) => { @@ -270,16 +261,11 @@ impl ImageLoad for Loader<'_, R> { } } - fn set_vp_context_page( - &mut self, - _vtl: Vtl, - _page_base: u64, - _acceptance: BootPageAcceptance, - ) -> anyhow::Result<()> { + fn set_vp_context_page(&mut self, _page_base: u64) -> anyhow::Result<()> { unimplemented!() } - fn vp_context_page(&self, _vtl: Vtl) -> anyhow::Result { + fn set_lower_vtl_context_page(&mut self, _page_base: u64) -> anyhow::Result<()> { unimplemented!() } @@ -318,11 +304,9 @@ impl ImageLoad for Loader<'_, R> { _relocation_alignment: u64, _minimum_relocation_gpa: u64, _maximum_relocation_gpa: u64, - _is_vtl2: bool, _apply_rip_offset: bool, _apply_gdtr_offset: bool, _vp_index: u16, - _vtl: Vtl, ) -> anyhow::Result<()> { unimplemented!() } @@ -333,7 +317,6 @@ impl ImageLoad for Loader<'_, R> { _size_pages: u64, _used_pages: u64, _vp_index: u16, - _vtl: Vtl, ) -> anyhow::Result<()> { unimplemented!() }