diff --git a/core/patina_internal_cpu/src/gdt.rs b/core/patina_internal_cpu/src/gdt.rs index 905b157e1f..ee49060dd3 100644 --- a/core/patina_internal_cpu/src/gdt.rs +++ b/core/patina_internal_cpu/src/gdt.rs @@ -6,10 +6,9 @@ //! //! SPDX-License-Identifier: Apache-2.0 //! -#![cfg_attr(test, allow(dead_code))] +#![cfg_attr(any(test, not(target_os = "uefi")), allow(dead_code))] #![cfg_attr(test, allow(unused_imports))] use core::ptr::{addr_of, addr_of_mut}; -use patina::SIZE_4GB; // The AP GDT only exists for the `mp` module, which needs `alloc`. #[cfg(any(feature = "alloc", test))] @@ -235,45 +234,6 @@ pub(crate) fn descriptor() -> DescriptorTablePointer { } } -#[cfg_attr(coverage, coverage(off))] -/// Initializes the GDT from a fixed descriptor set and loads it. -pub fn init() { - let gdt_ptr = GDT.as_ptr() as usize; - assert!(gdt_ptr < SIZE_4GB, "GDT above 4GB, MP services will fail"); - - let gdtr = descriptor(); - - // SAFETY: We are constructing a well known GDT that maps all segments in a flat map - unsafe { - core::arch::asm!("lgdt [{}]", in(reg) &raw const gdtr, options(nostack, preserves_flags)); - - // Reload CS via a far return - core::arch::asm!( - "push {sel}", - "lea {tmp}, [rip + 2f]", - "push {tmp}", - "retfq", - "2:", - sel = in(reg) u64::from(CODE_SELECTOR), - tmp = lateout(reg) _, - options(preserves_flags), - ); - - // These segments need to be valid, but can be all the same. Program them to the same GDT entry, - // following what the C codebase does, as these are unused in long mode. - core::arch::asm!("mov ss, {0:x}", in(reg) DATA_SELECTOR, options(nostack, preserves_flags)); - core::arch::asm!("mov ds, {0:x}", in(reg) DATA_SELECTOR, options(nostack, preserves_flags)); - core::arch::asm!("mov es, {0:x}", in(reg) DATA_SELECTOR, options(nostack, preserves_flags)); - core::arch::asm!("mov fs, {0:x}", in(reg) DATA_SELECTOR, options(nostack, preserves_flags)); - core::arch::asm!("mov gs, {0:x}", in(reg) DATA_SELECTOR, options(nostack, preserves_flags)); - - // Load TSS - core::arch::asm!("ltr {0:x}", in(reg) TSS_SELECTOR, options(nostack, preserves_flags)); - } - - log::info!("Loaded GDT @ {:p}", GDT.as_ptr()); -} - #[cfg(test)] #[cfg_attr(coverage, coverage(off))] mod tests { diff --git a/core/patina_internal_cpu/src/interrupts/aarch64/interrupt_manager.rs b/core/patina_internal_cpu/src/interrupts/aarch64/interrupt_manager.rs index 36d9448d24..3a8d1cc564 100644 --- a/core/patina_internal_cpu/src/interrupts/aarch64/interrupt_manager.rs +++ b/core/patina_internal_cpu/src/interrupts/aarch64/interrupt_manager.rs @@ -14,7 +14,8 @@ use patina::{ use patina_paging::PageTable; use crate::interrupts::{ - EfiExceptionInfoDump, EfiSystemContext, HandlerType, InterruptManager, aarch64::ExceptionContextAArch64, + EfiExceptionInfoDump, EfiSystemContext, ExceptionType, HandlerType, InterruptHandler, InterruptManager, + aarch64::ExceptionContextAArch64, }; cfg_if::cfg_if! { @@ -48,13 +49,20 @@ impl InterruptsAarch64 { /// for interrupts and exceptions to be taken. This routine may install some /// architecture specific default handlers for exceptions. /// - pub fn initialize(&mut self) -> Result<(), EfiError> { - // Initialize exception entrypoint - initialize_exception()?; - + /// Default and platform handlers are registered before activating the exception vector table. + pub fn initialize( + &mut self, + exception_handlers: &[(ExceptionType, &'static dyn InterruptHandler)], + ) -> Result<(), EfiError> { self.register_exception_handler(0, HandlerType::UefiRoutine(synchronous_exception_handler)) .expect("Failed to install default exception handler!"); + for &(exception_type, handler) in exception_handlers { + self.register_exception_handler(exception_type, HandlerType::Handler(handler))?; + } + + initialize_exception()?; + Ok(()) } } diff --git a/core/patina_internal_cpu/src/interrupts/stub.rs b/core/patina_internal_cpu/src/interrupts/stub.rs index 524afaca3f..466bb8f75a 100644 --- a/core/patina_internal_cpu/src/interrupts/stub.rs +++ b/core/patina_internal_cpu/src/interrupts/stub.rs @@ -9,7 +9,7 @@ use patina::{error::EfiError, pi::protocol::cpu_arch::EfiSystemContext}; -use crate::interrupts::InterruptManager; +use crate::interrupts::{ExceptionType, HandlerType, InterruptHandler, InterruptManager}; /// Null implementation of the `EfiSystemContextFactory` and `EfiExceptionInfoDump` traits. #[derive(Debug)] @@ -38,8 +38,14 @@ impl InterruptsStub { Self {} } - /// A do-nothing initialization function for the null implementation. - pub fn initialize(&mut self) -> Result<(), EfiError> { + /// Registers supplied platform handlers without initializing hardware. + pub fn initialize( + &mut self, + exception_handlers: &[(ExceptionType, &'static dyn InterruptHandler)], + ) -> Result<(), EfiError> { + for &(exception_type, handler) in exception_handlers { + self.register_exception_handler(exception_type, HandlerType::Handler(handler))?; + } Ok(()) } } diff --git a/core/patina_internal_cpu/src/interrupts/x64.rs b/core/patina_internal_cpu/src/interrupts/x64.rs index 6789485cb9..84f7fd13a5 100644 --- a/core/patina_internal_cpu/src/interrupts/x64.rs +++ b/core/patina_internal_cpu/src/interrupts/x64.rs @@ -11,6 +11,9 @@ use crate::{interrupts::ExceptionContextX64, log_registers}; use patina::pi::protocol::cpu_arch::EfiSystemContext; use patina_stacktrace::{StackFrame, StackTrace}; +#[cfg_attr(coverage, coverage(off))] +#[cfg(all(target_arch = "x86_64", target_os = "uefi"))] +mod apic; #[cfg(target_arch = "x86_64")] pub(crate) mod idt; mod interrupt_manager; diff --git a/core/patina_internal_cpu/src/interrupts/x64/apic.rs b/core/patina_internal_cpu/src/interrupts/x64/apic.rs new file mode 100644 index 0000000000..7355ba3fc5 --- /dev/null +++ b/core/patina_internal_cpu/src/interrupts/x64/apic.rs @@ -0,0 +1,98 @@ +//! X64 APIC initialization. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! SPDX-License-Identifier: Apache-2.0 +//! + +use patina::{ + arch::x64::{read_msr, write_msr}, + mmio::{UniqueMmioPointer, fields::ReadPureWrite}, +}; +use patina::{bit, error::EfiError}; + +const APIC_BASE_MASK: u64 = 0x000F_FFFF_FFFF_F000; +const APIC_ENABLE: u64 = bit!(11); +const X2APIC_ENABLE: u64 = bit!(10); +const SVR_OFFSET: usize = 0xF0; +const LINT0_OFFSET: usize = 0x350; +const LINT1_OFFSET: usize = 0x360; +const SPURIOUS_VECTOR_MASK: u32 = 0xFF; +const SOFTWARE_ENABLE: u32 = bit!(8); +const DELIVERY_MODE_MASK: u32 = bit!(8) | bit!(9) | bit!(10); +const ACTIVE_LOW: u32 = bit!(13); +const LEVEL_TRIGGERED: u32 = bit!(15); +const MASKED: u32 = bit!(16); +const EXTINT: u32 = bit!(8) | bit!(9) | bit!(10); +const NMI: u32 = bit!(10); + +const APIC_BASE_MSR: u32 = 0x1B; +const X2APIC_MSR_BASE: u32 = 0x800; + +#[derive(Debug, PartialEq)] +enum ApicMode { + XApic(usize), + X2Apic, +} + +impl ApicMode { + fn from_base(apic_base: u64) -> Result { + if apic_base & APIC_ENABLE == 0 { + return Err(EfiError::Unsupported); + } + if apic_base & X2APIC_ENABLE != 0 { + Ok(Self::X2Apic) + } else { + let base = (apic_base & APIC_BASE_MASK) as usize; + if base == 0 { + return Err(EfiError::Unsupported); + } + Ok(Self::XApic(base)) + } + } +} + +fn program_virtual_wire_mode(mut update: impl FnMut(usize, u32, u32)) { + update(SVR_OFFSET, SPURIOUS_VECTOR_MASK, SOFTWARE_ENABLE | 0x0F); + let lint_mask = DELIVERY_MODE_MASK | ACTIVE_LOW | LEVEL_TRIGGERED | MASKED; + update(LINT0_OFFSET, lint_mask, EXTINT); + update(LINT1_OFFSET, lint_mask, NMI); +} + +fn apic_base_msr_supported(features: u32) -> bool { + let required_features = bit!(5) | bit!(9); + features & required_features == required_features +} + +pub(super) fn initialize_virtual_wire_mode() -> Result<(), EfiError> { + #[allow(unused_unsafe)] + // SAFETY: It is safe to read CPUID leaf 1 + let cpu_features = unsafe { core::arch::x86_64::__cpuid(1) }; + if !apic_base_msr_supported(cpu_features.edx) { + return Err(EfiError::Unsupported); + } + + // SAFETY: It is safe to read this MSR once we have confirmed support for it + let mode = ApicMode::from_base(unsafe { read_msr(APIC_BASE_MSR) })?; + program_virtual_wire_mode(|offset, clear, set| match mode { + ApicMode::X2Apic => { + let msr = X2APIC_MSR_BASE + (offset >> 4) as u32; + // SAFETY: The enabled x2APIC exposes SVR and LINT0/1 at these architectural MSRs. + let value = unsafe { read_msr(msr) } as u32; + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + // SAFETY: Only the documented SVR and LINT configuration fields are changed. + unsafe { write_msr(msr, u64::from((value & !clear) | set)) }; + } + ApicMode::XApic(base) => { + let address = core::ptr::NonNull::new((base + offset) as *mut ReadPureWrite) + .expect("Validated nonzero APIC base"); + // SAFETY: The enabled xAPIC's base MSR identifies its mapped register page. CPU + // initialization exclusively accesses these aligned SVR and LINT registers. + let mut register = unsafe { UniqueMmioPointer::new(address) }; + let value = register.read(); + register.write((value & !clear) | set); + } + }); + Ok(()) +} diff --git a/core/patina_internal_cpu/src/interrupts/x64/idt.rs b/core/patina_internal_cpu/src/interrupts/x64/idt.rs index 8d6fd297b5..abbef46c6b 100644 --- a/core/patina_internal_cpu/src/interrupts/x64/idt.rs +++ b/core/patina_internal_cpu/src/interrupts/x64/idt.rs @@ -104,7 +104,7 @@ fn get_vector_address(index: usize) -> u64 { static IDT: StaticIdt = StaticIdt(UnsafeCell::new(Idt::filled(IdtEntry::empty()))); #[cfg(target_os = "uefi")] -pub fn initialize_idt() { +pub(crate) fn prepare_idt() -> crate::gdt::DescriptorTablePointer { let cs = crate::gdt::CODE_SELECTOR; // SAFETY: There is only path to access the IDT and it is not possible to have concurrent access. let idt = unsafe { &mut *IDT.0.get() }; @@ -118,8 +118,5 @@ pub fn initialize_idt() { } assert!((IDT.0.get() as usize) < SIZE_4GB, "IDT above 4GB, MP services will fail"); - let idtr = idt.descriptor(); - // SAFETY: Loading our fully initialized IDT. - unsafe { core::arch::asm!("lidt [{}]", in(reg) core::ptr::addr_of!(idtr), options(nostack)) }; - log::info!("Loaded IDT"); + idt.descriptor() } diff --git a/core/patina_internal_cpu/src/interrupts/x64/interrupt_manager.rs b/core/patina_internal_cpu/src/interrupts/x64/interrupt_manager.rs index da6d44d03d..d6ad3580c8 100644 --- a/core/patina_internal_cpu/src/interrupts/x64/interrupt_manager.rs +++ b/core/patina_internal_cpu/src/interrupts/x64/interrupt_manager.rs @@ -18,7 +18,9 @@ use patina_mtrr::{Mtrr, error::MtrrError}; use patina_paging::PageTable; use patina_stacktrace::{StackFrame, StackTrace}; -use crate::interrupts::{EfiExceptionInfoDump, HandlerType, InterruptManager, x64::ExceptionContextX64}; +use crate::interrupts::{ + EfiExceptionInfoDump, ExceptionType, HandlerType, InterruptHandler, InterruptManager, x64::ExceptionContextX64, +}; /// X64 Implementation of the `InterruptManager`. /// @@ -40,11 +42,12 @@ impl InterruptsX64 { /// for interrupts and exceptions to be taken. This routine may install some /// architecture specific default handlers for exceptions. /// - pub fn initialize(&mut self) -> Result<(), EfiError> { - // Initialize the IDT. - #[cfg(target_os = "uefi")] - crate::interrupts::x64::idt::initialize_idt(); - + /// Default and platform handlers are registered before activating the GDT and IDT. + /// Virtual-wire mode is then programmed on non-CC guests. + pub fn initialize( + &mut self, + exception_handlers: &[(ExceptionType, &'static dyn InterruptHandler)], + ) -> Result<(), EfiError> { // Register some default handlers. self.register_exception_handler(8, HandlerType::UefiRoutine(double_fault_handler)) .expect("Failed to install default exception handler!"); @@ -53,12 +56,75 @@ impl InterruptsX64 { self.register_exception_handler(14, HandlerType::UefiRoutine(page_fault_handler)) .expect("Failed to install default exception handler!"); + for &(exception_type, handler) in exception_handlers { + self.register_exception_handler(exception_type, HandlerType::Handler(handler))?; + } + #[cfg(target_os = "uefi")] + { + patina::arch::disable_interrupts(); + descriptor_table_install(); + patina::arch::enable_interrupts(); + self.initialize_virtual_wire_mode()?; + } + Ok(()) } + + /// Programs virtual-wire mode on non-CC guests after platform exception handlers are installed. CC guests do not + /// require this and may fail if it is attempted. If this function is called from a CC guest it will simply return + /// `Ok()`. + #[cfg(target_os = "uefi")] + fn initialize_virtual_wire_mode(&mut self) -> Result<(), EfiError> { + if patina::arch::is_cc_guest() { + return Ok(()); + } + super::apic::initialize_virtual_wire_mode() + } } impl InterruptManager for InterruptsX64 {} +#[cfg_attr(coverage, coverage(off))] +#[cfg(target_os = "uefi")] +/// Prepares both descriptor tables, loads the GDT, reloads segments and the TSS, then loads the IDT. +fn descriptor_table_install() { + let gdtr = crate::gdt::descriptor(); + let gdt_ptr = gdtr.base as *const u64; + assert!((gdt_ptr as usize) < patina::SIZE_4GB, "GDT above 4GB, MP services will fail"); + + let idtr = super::idt::prepare_idt(); + + // SAFETY: Both descriptors reference fully initialized static tables. The GDT defines + // the code, data, and TSS selectors used below and by the IDT. Initialization is exclusive. + unsafe { + core::arch::asm!( + "lgdt [{gdtr}]", + "push {sel}", + "lea {tmp}, [rip + 2f]", + "push {tmp}", + "retfq", + "2:", + "mov ss, {data:x}", + "mov ds, {data:x}", + "mov es, {data:x}", + "mov fs, {data:x}", + "mov gs, {data:x}", + "ltr {tss:x}", + "lidt [{idtr}]", + gdtr = in(reg) &raw const gdtr, + idtr = in(reg) &raw const idtr, + sel = in(reg) u64::from(crate::gdt::CODE_SELECTOR), + data = in(reg) crate::gdt::DATA_SELECTOR, + tss = in(reg) crate::gdt::TSS_SELECTOR, + tmp = out(reg) _, + options(preserves_flags), + ); + } + + log::info!("Loaded GDT @ {gdt_ptr:p}"); + log::info!("Loaded IDT"); +} + #[cfg_attr(coverage, coverage(off))] /// Default handler for double faults. extern "efiapi" fn double_fault_handler(_exception_type: isize, context: EfiSystemContext) { @@ -262,8 +328,32 @@ mod test { #[serial(exception_handlers)] fn test_interrupts_x64() { let mut interrupts = InterruptsX64::new(); - assert!(interrupts.initialize().is_ok()); + assert!(interrupts.initialize(&[]).is_ok()); + assert!(interrupts.unregister_exception_handler(8).is_ok()); assert!(interrupts.unregister_exception_handler(13).is_ok()); assert!(interrupts.unregister_exception_handler(14).is_ok()); } + + #[test] + #[serial(exception_handlers)] + fn test_interrupts_x64_platform_handlers() { + struct PlatformHandler; + + impl InterruptHandler for PlatformHandler { + fn handle_interrupt( + &'static self, + _exception_type: ExceptionType, + _context: &mut crate::interrupts::ExceptionContext, + ) { + } + } + + static HANDLER: PlatformHandler = PlatformHandler; + let mut interrupts = InterruptsX64::new(); + assert!(interrupts.initialize(&[(20, &HANDLER)]).is_ok()); + assert!(interrupts.unregister_exception_handler(20).is_ok()); + for exception_type in [8, 13, 14] { + assert!(interrupts.unregister_exception_handler(exception_type).is_ok()); + } + } } diff --git a/docs/src/integrate/confidential_compute.md b/docs/src/integrate/confidential_compute.md index fd29a7713e..d50b6c72bc 100644 --- a/docs/src/integrate/confidential_compute.md +++ b/docs/src/integrate/confidential_compute.md @@ -6,18 +6,46 @@ guest. For the latest updates, view [the tracking item](https://github.com/OpenD ## Platform Enablement -To enable the features that are specifically for a confidential compute guest in Patina, build patina_dxe_core with -the `confidential_compute` flag set: +Patina will attempt to detect whether it is running in a confidential compute guest before executing operations that +these environments are sensitive to. However, this relies on the platform following the guidance below. -```text -# Cargo.toml -patina_dxe_core = { version = "x.x.x", features = ["confidential_compute"] } +### Virtualization Exceptions + +Patina, like EDK II, does not have generic virtualization exception handling because it is platform/hypervisor +specific. Platforms must provide virtualization exception handlers during Patina initialization that Patina will +install. + +```rust +# extern crate patina_dxe_core; +use patina_dxe_core::{CpuInfo, ExceptionContext, ExceptionType, InterruptHandler}; + +const EXCEPTION_VECTOR_VE: ExceptionType = 20; + +struct MyPlatform; + +impl InterruptHandler for MyPlatform { + fn handle_interrupt(&'static self, exception_type: ExceptionType, context: &mut ExceptionContext) { + // Handle... + } +} + +impl CpuInfo for MyPlatform { + fn exception_handlers() -> &'static [(ExceptionType, &'static dyn InterruptHandler)] { + static EXCEPTION_HANDLERS: [(ExceptionType, &'static dyn InterruptHandler); 1] = + [(EXCEPTION_VECTOR_VE, &MyPlatform)]; + + &EXCEPTION_HANDLERS + } +} ``` +Patina will install these to the new IDT/VBAR immediately upon creation to ensure the exceptions can be serviced. + ### Shared Memory Regions -In order to map shared regions with the VMM, a C driver can call the `AliasedMemoryMappingProtocol` which has a C -binding in patina-edk2. This protocol allows arbitrary VA to PA mappings. +In order to map shared regions with the VMM, a C driver can call the `AliasedMemoryMappingProtocol` which has a +[C binding](https://github.com/OpenDevicePartnership/patina-edk2/blob/main/PatinaPkg/Include/Protocol/AliasedMemoryMapping.h) +in patina-edk2. This protocol allows arbitrary VA to PA mappings. For example, in a TDX VM where a page is being shared, the driver would call: @@ -71,35 +99,3 @@ prior phase page tables and requires platforms to be deterministic about mapping For x64 virtualization technologies, MTRR access may or may not be allowed. Patina will check if MTRRs are supported and if not will continue to gracefully map pages. - -### Virtualization Exceptions - -Patina, like EDK II, does not have generic virtualization exception handling because this is platform/hypervisor -specific. Platforms must provide virtualization exception handlers during Patina initialization that Patina will -install. - -```rust -# extern crate patina_dxe_core; -use patina_dxe_core::{CpuInfo, ExceptionContext, ExceptionType, InterruptHandler}; - -const EXCEPTION_VECTOR_VE: ExceptionType = 20; - -struct MyPlatform; - -impl InterruptHandler for MyPlatform { - fn handle_interrupt(&'static self, exception_type: ExceptionType, context: &mut ExceptionContext) { - // Handle... - } -} - -impl CpuInfo for MyPlatform { - fn exception_handlers() -> &'static [(ExceptionType, &'static dyn InterruptHandler)] { - static EXCEPTION_HANDLERS: [(ExceptionType, &'static dyn InterruptHandler); 1] = - [(EXCEPTION_VECTOR_VE, &MyPlatform)]; - - &EXCEPTION_HANDLERS - } -} -``` - -Patina will install these to the new IDT/VBAR immediately upon creation to ensure the exceptions can be serviced. diff --git a/patina_dxe_core/Cargo.toml b/patina_dxe_core/Cargo.toml index c0a4bcb816..173815a791 100644 --- a/patina_dxe_core/Cargo.toml +++ b/patina_dxe_core/Cargo.toml @@ -58,7 +58,6 @@ corosensei = { workspace = true } [features] compatibility_mode_allowed = [] -confidential_compute = [] debugger_reload = [] default = [] std = ["patina/std"] @@ -66,4 +65,4 @@ std = ["patina/std"] unstable-device-path = ["patina/unstable-device-path"] v1_resource_descriptor_support = [] # All non-std features to test in CI -ci_features = ["compatibility_mode_allowed", "confidential_compute", "debugger_reload", "unstable-device-path", "v1_resource_descriptor_support"] +ci_features = ["compatibility_mode_allowed", "debugger_reload", "unstable-device-path", "v1_resource_descriptor_support"] diff --git a/patina_dxe_core/src/cpu.rs b/patina_dxe_core/src/cpu.rs index 3c05d2f429..1f1fc5ab20 100644 --- a/patina_dxe_core/src/cpu.rs +++ b/patina_dxe_core/src/cpu.rs @@ -8,7 +8,6 @@ //! //! SPDX-License-Identifier: Apache-2.0 //! -#[cfg(any(test, feature = "confidential_compute"))] mod aliased_memory_map_protocol; mod cpu_arch_protocol; mod efi_cpu; @@ -28,8 +27,8 @@ pub(crate) use perf_timer::PerfTimer; pub(crate) struct MpServicesEnabled(pub(crate) bool); use efi_cpu::EfiCpu; +use patina_internal_cpu::interrupts::Interrupts; pub use patina_internal_cpu::interrupts::{ExceptionContext, ExceptionContextX64, ExceptionType, InterruptHandler}; -use patina_internal_cpu::interrupts::{HandlerType, InterruptManager, Interrupts}; /// A configuration struct containing the GIC bases (`gic_d`, `gic_r`) for AARCH64 systems. /// @@ -144,29 +143,16 @@ pub fn initialize_cpu_subsystem( exception_handlers: &[(ExceptionType, &'static dyn InterruptHandler)], ) -> crate::error::Result { let mut cpu = EfiCpu::default(); - let log_level = log::max_level(); - - // Confidential VMs cannot log between GDT install and IDT install because this creates a #VE/#VC exception - // which cannot be handled until the IDT is installed. Disable logging globally through this transition. - #[cfg(feature = "confidential_compute")] - log::set_max_level(log::LevelFilter::Off); cpu.initialize().inspect_err(|err| { log::error!("Failed to initialize CPU subsystem: {err}"); })?; let mut interrupt_manager = Interrupts::new(); - interrupt_manager.initialize().inspect_err(|err| { + interrupt_manager.initialize(exception_handlers).inspect_err(|err| { log::error!("Failed to initialize Interrupt Manager: {err}"); })?; - for &(exception_type, handler) in exception_handlers { - interrupt_manager.register_exception_handler(exception_type, HandlerType::Handler(handler))?; - } - - // Restore logging - log::set_max_level(log_level); - Ok(interrupt_manager) } diff --git a/patina_dxe_core/src/cpu/cpu_arch_protocol.rs b/patina_dxe_core/src/cpu/cpu_arch_protocol.rs index 47c5b33680..28df95c1ae 100644 --- a/patina_dxe_core/src/cpu/cpu_arch_protocol.rs +++ b/patina_dxe_core/src/cpu/cpu_arch_protocol.rs @@ -247,7 +247,7 @@ impl EfiCpuArchProtocolImpl { } } -/// This component installs the cpu arch protocol and optionally the aliased memory mapping protocol. +/// Installs the CPU arch protocol and, on TDX guests, the aliased memory mapping protocol. #[derive(Default)] pub(crate) struct CpuArchProtocolInstaller; @@ -266,8 +266,9 @@ impl CpuArchProtocolInstaller { .inspect_err(|_| log::error!("Failed to install EFI_CPU_ARCH_PROTOCOL"))?; log::info!("installed EFI_CPU_ARCH_PROTOCOL_GUID"); - #[cfg(any(test, feature = "confidential_compute"))] - super::aliased_memory_map_protocol::install(&protocols, handle)?; + if patina::arch::is_cc_guest() { + super::aliased_memory_map_protocol::install(&protocols, handle)?; + } Ok(()) } diff --git a/patina_dxe_core/src/cpu/efi_cpu/x64/cpu.rs b/patina_dxe_core/src/cpu/efi_cpu/x64/cpu.rs index 14f5964674..476cc93f72 100644 --- a/patina_dxe_core/src/cpu/efi_cpu/x64/cpu.rs +++ b/patina_dxe_core/src/cpu/efi_cpu/x64/cpu.rs @@ -8,7 +8,6 @@ //! #[cfg(target_os = "uefi")] use core::arch::asm; -use patina::arch as interrupts; use patina::error::EfiError; /// Struct to implement X64 Cpu Init. @@ -24,22 +23,9 @@ impl EfiCpuX64 { // Initialize floating point units self.initialize_fpu(); - // disable interrupts - interrupts::disable_interrupts(); - - // Initialize GDT - self.initialize_gdt(); - - interrupts::enable_interrupts(); - Ok(()) } - fn initialize_gdt(&self) { - #[cfg(target_os = "uefi")] - patina_internal_cpu::gdt::init(); - } - #[cfg_attr(coverage, coverage(off))] fn initialize_fpu(&self) { #[cfg(target_os = "uefi")] diff --git a/patina_dxe_core/src/dxe_services.rs b/patina_dxe_core/src/dxe_services.rs index 7dc7ef13a0..fac800fdb3 100644 --- a/patina_dxe_core/src/dxe_services.rs +++ b/patina_dxe_core/src/dxe_services.rs @@ -166,7 +166,6 @@ pub fn core_set_memory_space_attributes( } #[cfg_attr(coverage, coverage(off))] -#[cfg(any(test, feature = "confidential_compute"))] pub fn core_map_aliased_memory_region( virtual_address: efi::VirtualAddress, physical_address: efi::PhysicalAddress, @@ -177,7 +176,6 @@ pub fn core_map_aliased_memory_region( } #[cfg_attr(coverage, coverage(off))] -#[cfg(any(test, feature = "confidential_compute"))] pub fn core_unmap_aliased_memory_region(virtual_address: efi::VirtualAddress, length: u64) -> Result<(), EfiError> { GCD.unmap_aliased_memory_region(virtual_address, length) } diff --git a/patina_dxe_core/src/gcd/spin_locked_gcd.rs b/patina_dxe_core/src/gcd/spin_locked_gcd.rs index 02be22dd7b..12d5e2bf69 100644 --- a/patina_dxe_core/src/gcd/spin_locked_gcd.rs +++ b/patina_dxe_core/src/gcd/spin_locked_gcd.rs @@ -19,7 +19,6 @@ use core::{fmt::Display, ptr}; #[cfg(test)] use io_gcd::IO_BLOCK_SLICE_LEN; use io_gcd::IoGCD; -#[cfg(any(test, feature = "confidential_compute"))] use paging::AliasedMapping; pub(crate) use paging::PagingAllocator; use patina::{DEFAULT_CACHE_ATTR, crc32, error::EfiError, log_debug_assert}; @@ -1386,7 +1385,6 @@ pub struct SpinLockedGcd { io: tpl_mutex::TplMutex, memory_change_callback: Option, page_table: tpl_mutex::TplMutex>>, - #[cfg(any(test, feature = "confidential_compute"))] aliased_mappings: tpl_mutex::TplMutex>, /// Contains the current memory protection policy pub(crate) memory_protection_policy: MemoryProtectionPolicy, @@ -1423,7 +1421,6 @@ impl SpinLockedGcd { ), memory_change_callback, page_table: tpl_mutex::TplMutex::new(efi::TPL_HIGH_LEVEL, None, "GcdPageTableLock"), - #[cfg(any(test, feature = "confidential_compute"))] aliased_mappings: tpl_mutex::TplMutex::new(efi::TPL_HIGH_LEVEL, Vec::new(), "GcdAliasedMappingsLock"), memory_protection_policy: MemoryProtectionPolicy::new(), last_efi_memory_map_key: tpl_mutex::TplMutex::new(efi::TPL_HIGH_LEVEL, None, "LastEfiMemoryMapKeyLock"), diff --git a/patina_dxe_core/src/gcd/spin_locked_gcd/paging.rs b/patina_dxe_core/src/gcd/spin_locked_gcd/paging.rs index ab1624af32..8b7dba56e1 100644 --- a/patina_dxe_core/src/gcd/spin_locked_gcd/paging.rs +++ b/patina_dxe_core/src/gcd/spin_locked_gcd/paging.rs @@ -111,7 +111,6 @@ impl PageAllocator for PagingAllocator<'_> { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg(any(test, feature = "confidential_compute"))] pub(crate) struct AliasedMapping { pub(crate) virtual_address: u64, pub(crate) physical_address: u64, @@ -280,7 +279,6 @@ impl SpinLockedGcd { } } - #[cfg(any(test, feature = "confidential_compute"))] pub(crate) fn map_aliased_memory_region( &self, virtual_address: u64, @@ -312,7 +310,6 @@ impl SpinLockedGcd { Ok(()) } - #[cfg(any(test, feature = "confidential_compute"))] pub(crate) fn unmap_aliased_memory_region(&self, virtual_address: u64, len: u64) -> Result<(), EfiError> { if !self .aliased_mappings diff --git a/sdk/patina/src/arch.rs b/sdk/patina/src/arch.rs index 518b44feff..506ef28757 100644 --- a/sdk/patina/src/arch.rs +++ b/sdk/patina/src/arch.rs @@ -15,6 +15,7 @@ cfg_if::cfg_if! { #[cfg_attr(coverage, coverage(off))] pub mod stub; type Arch = stub::StubArch; + use self::stub as arch_impl; // Republish the compiled architecture stub module so that `patina::arch::aarch64` / // `patina::arch::x64` paths resolve on host builds. @@ -24,10 +25,12 @@ cfg_if::cfg_if! { #[cfg_attr(coverage, coverage(off))] // Architecture code cannot be unit tested. pub mod aarch64; type Arch = aarch64::AArch64; + use self::aarch64 as arch_impl; } else if #[cfg(target_arch = "x86_64")] { #[cfg_attr(coverage, coverage(off))] // Architecture code cannot be unit tested. pub mod x64; type Arch = x64::X64; + use self::x64 as arch_impl; } } @@ -36,6 +39,16 @@ cfg_if::cfg_if! { /// for code organization. trait ArchSupport: Interrupts + CacheMgmt + Timer {} +/// Returns whether the current environment is an enlightened confidential compute guest. +/// +/// Currently detects Intel TDX on x64 and returns `false` on `AArch64` and host stubs. +/// Platform exception handlers must be installed before calling this function so that +/// any virtualization exceptions raised during detection are properly handled. +#[cfg_attr(coverage, coverage(off))] +pub fn is_cc_guest() -> bool { + arch_impl::is_cc_guest() +} + // // Interrupts support. // diff --git a/sdk/patina/src/arch/aarch64.rs b/sdk/patina/src/arch/aarch64.rs index 98ed7b52ce..b90cc2d151 100644 --- a/sdk/patina/src/arch/aarch64.rs +++ b/sdk/patina/src/arch/aarch64.rs @@ -16,6 +16,11 @@ pub(super) struct AArch64; impl super::ArchSupport for AArch64 {} +/// Returns `false` because confidential guest detection is not yet supported on `AArch64`. +pub fn is_cc_guest() -> bool { + false +} + const DAIF_WR_IRQ_BIT: u64 = 0x2; /// Reads and returns the value of the given aarch64 system register. diff --git a/sdk/patina/src/arch/stub.rs b/sdk/patina/src/arch/stub.rs index c08253e210..8f36281a6f 100644 --- a/sdk/patina/src/arch/stub.rs +++ b/sdk/patina/src/arch/stub.rs @@ -20,6 +20,11 @@ pub(crate) struct StubArch; impl super::ArchSupport for StubArch {} +/// Returns `false` for host builds. +pub fn is_cc_guest() -> bool { + false +} + static INTERRUPTS_ENABLED: AtomicBool = AtomicBool::new(true); impl super::Interrupts for StubArch { diff --git a/sdk/patina/src/arch/x64.rs b/sdk/patina/src/arch/x64.rs index 460cd94944..7710aeffa6 100644 --- a/sdk/patina/src/arch/x64.rs +++ b/sdk/patina/src/arch/x64.rs @@ -11,6 +11,8 @@ use crate::{error::EfiError, pi::protocol::cpu_arch::CpuFlushType, standard::efi use core::arch::asm; use core::num::NonZeroU64; +mod tdx; + pub(crate) struct X64; impl super::ArchSupport for X64 {} @@ -18,6 +20,15 @@ impl super::ArchSupport for X64 {} /// Cache writeback granule for `x86_64`, using 4 bytes following precedence set by Tianocore. const CACHE_WRITEBACK_GRANULE: u32 = 4; +pub use tdx::is_td_enabled; + +/// Returns whether the current environment is an Intel TDX guest. +/// +/// Platform exception handlers must be installed before calling this function. +pub fn is_cc_guest() -> bool { + is_td_enabled() +} + /// Returns the active virtual address width selected by `CR4.LA57`. pub fn virtual_address_bits() -> u32 { let cr4: u64; diff --git a/sdk/patina/src/arch/x64/tdx.rs b/sdk/patina/src/arch/x64/tdx.rs new file mode 100644 index 0000000000..83741e0036 --- /dev/null +++ b/sdk/patina/src/arch/x64/tdx.rs @@ -0,0 +1,46 @@ +//! Intel TDX guest detection using CPUID. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. +//! +//! SPDX-License-Identifier: Apache-2.0 +//! + +use core::arch::x86_64::__cpuid_count; + +const CPUID_SIGNATURE: u32 = 0; +const CPUID_VERSION_INFO: u32 = 1; +const CPUID_GUEST_TD_RUNTIME_ENVIRONMENT: u32 = 0x21; +const CPUID_HYPERVISOR_PRESENT: u32 = 1 << 31; +const INTEL_VENDOR_EBX: u32 = u32::from_le_bytes(*b"Genu"); +const INTEL_VENDOR_EDX: u32 = u32::from_le_bytes(*b"ineI"); +const INTEL_VENDOR_ECX: u32 = u32::from_le_bytes(*b"ntel"); +const TDX_SIGNATURE_EBX: u32 = u32::from_le_bytes(*b"Inte"); +const TDX_SIGNATURE_EDX: u32 = u32::from_le_bytes(*b"lTDX"); +const TDX_SIGNATURE_ECX: u32 = u32::from_le_bytes(*b" "); + +/// Returns whether CPUID identifies an Intel TDX guest environment. +/// +/// Uses the Intel vendor signature, hypervisor-present bit, and TDX leaf 0x21 +/// signature, following EDK II's `TdIsEnabled` probe. Platform exception handlers +/// must be installed before calling this function if CPUID can raise #VE. +#[allow(unused_unsafe)] +pub fn is_td_enabled() -> bool { + #[allow(unused_unsafe)] + // SAFETY: CPUID with EAX=0 is always safe to call on x64. + let vendor = unsafe { __cpuid_count(CPUID_SIGNATURE, 0) }; + if vendor.ebx != INTEL_VENDOR_EBX || vendor.edx != INTEL_VENDOR_EDX || vendor.ecx != INTEL_VENDOR_ECX { + return false; + } + #[allow(unused_unsafe)] + // SAFETY: CPUID with EAX=1 is always safe to call on x64. + let version = unsafe { __cpuid_count(CPUID_VERSION_INFO, 0) }; + if version.ecx & CPUID_HYPERVISOR_PRESENT == 0 || vendor.eax < CPUID_GUEST_TD_RUNTIME_ENVIRONMENT { + return false; + } + #[allow(unused_unsafe)] + // SAFETY: CPUID with EAX=0x21 is always safe to call on x64. + let tdx = unsafe { __cpuid_count(CPUID_GUEST_TD_RUNTIME_ENVIRONMENT, 0) }; + tdx.ebx == TDX_SIGNATURE_EBX && tdx.edx == TDX_SIGNATURE_EDX && tdx.ecx == TDX_SIGNATURE_ECX +}