diff --git a/openhcl/virt_mshv_vtl/src/processor/apic.rs b/openhcl/virt_mshv_vtl/src/processor/apic.rs new file mode 100644 index 000000000..bce3b54c6 --- /dev/null +++ b/openhcl/virt_mshv_vtl/src/processor/apic.rs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![cfg(guest_arch = "x86_64")] + +use super::LapicState; +use super::UhRunVpError; +use hcl::GuestVtl; +use virt::vp::MpState; +use virt_support_apic::ApicWork; + +pub(crate) trait ApicBacking { + fn handle_init(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError>; + fn handle_sipi(&mut self, vtl: GuestVtl, vector: u8) -> Result<(), UhRunVpError>; + fn handle_nmi(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError>; + fn handle_interrupt(&mut self, vtl: GuestVtl, vector: u8) -> Result<(), UhRunVpError>; + fn handle_extint(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError>; +} + +pub(crate) fn poll_apic_core( + processor: &mut T, + scan: impl Fn(&mut T) -> ApicWork, + proxy_irr: impl Fn(&mut T) -> Option<[u32; 8]>, + lapic_access: impl Fn(&mut T) -> &mut LapicState, + vtl1_enabled: impl Fn(&mut T) -> bool, + vtl: GuestVtl, +) -> Result<(), UhRunVpError> { + // Check for interrupt requests from the host and kernel offload. + if vtl == GuestVtl::Vtl0 { + if let Some(irr) = proxy_irr(processor) { + // We can't put the interrupts directly into offload (where supported) because we might need + // to clear the tmr state. This can happen if a vector was previously used for a level + // triggered interrupt, and is now being used for an edge-triggered interrupt. + lapic_access(processor).lapic.request_fixed_interrupts(irr); + } + } + + let ApicWork { + init, + extint, + sipi, + nmi, + interrupt, + } = scan(processor); + + // An INIT/SIPI targeted at a VP with more than one guest VTL enabled is ignored. + // Check VTL enablement inside each block to avoid taking a lock on the hot path, + // INIT and SIPI are quite cold. + if init { + if !vtl1_enabled(processor) { + processor.handle_init(vtl)?; + } + } + + if let Some(vector) = sipi { + if !vtl1_enabled(processor) { + processor.handle_sipi(vtl, vector)?; + } + } + + // Interrupts are ignored while waiting for SIPI. + let lapic = lapic_access(processor); + if lapic.activity != MpState::WaitForSipi { + if nmi || lapic.nmi_pending { + lapic.nmi_pending = true; + processor.handle_nmi(vtl)?; + } + + if let Some(vector) = interrupt { + processor.handle_interrupt(vtl, vector)?; + } + + if extint { + processor.handle_extint(vtl)?; + } + } + + Ok(()) +} diff --git a/openhcl/virt_mshv_vtl/src/processor/mod.rs b/openhcl/virt_mshv_vtl/src/processor/mod.rs index 5e4098cb5..37a252ad0 100644 --- a/openhcl/virt_mshv_vtl/src/processor/mod.rs +++ b/openhcl/virt_mshv_vtl/src/processor/mod.rs @@ -4,6 +4,7 @@ //! This module contains Underhill specific functionality and implementations of require traits //! in order to plug into the rest of the common HvLite code. +mod apic; pub mod mshv; mod nice; mod vp_state; diff --git a/openhcl/virt_mshv_vtl/src/processor/mshv/x64.rs b/openhcl/virt_mshv_vtl/src/processor/mshv/x64.rs index 53704ce30..a6ea112be 100644 --- a/openhcl/virt_mshv_vtl/src/processor/mshv/x64.rs +++ b/openhcl/virt_mshv_vtl/src/processor/mshv/x64.rs @@ -14,6 +14,7 @@ use super::super::vp_state::UhVpStateAccess; use super::super::BackingPrivate; use super::super::UhEmulationState; use super::super::UhRunVpError; +use crate::processor::apic::ApicBacking; use crate::processor::from_seg; use crate::processor::LapicState; use crate::processor::SidecarExitReason; @@ -70,7 +71,6 @@ use virt::StopVp; use virt::VpHaltReason; use virt::VpIndex; use virt_support_apic::ApicClient; -use virt_support_apic::ApicWork; use virt_support_x86emu::emulate::EmuCheckVtlAccessError; use virt_support_x86emu::emulate::EmuTranslateError; use virt_support_x86emu::emulate::EmuTranslateResult; @@ -323,48 +323,22 @@ impl BackingPrivate for HypervisorBackedX86 { vtl: GuestVtl, scan_irr: bool, ) -> Result<(), UhRunVpError> { - let Some(lapics) = this.backing.lapics.as_mut() else { + if this.backing.lapics.is_none() { return Ok(()); - }; - - let ApicWork { - init, - extint, - sipi, - nmi, - interrupt, - } = lapics[vtl].lapic.scan(&mut this.vmtime, scan_irr); - - // TODO WHP GUEST VSM: An INIT/SIPI targeted at a VP with more than one guest VTL enabled is ignored. - if init { - this.handle_init(vtl)?; - } - - if let Some(vector) = sipi { - this.handle_sipi(vtl, vector)?; } - let Some(lapics) = this.backing.lapics.as_mut() else { - unreachable!() - }; - - // Interrupts are ignored while waiting for SIPI. - if lapics[vtl].activity != MpState::WaitForSipi { - if nmi || lapics[vtl].nmi_pending { - lapics[vtl].nmi_pending = true; - this.handle_nmi(vtl)?; - } - - if let Some(vector) = interrupt { - this.handle_interrupt(vtl, vector)?; - } - - if extint { - todo!(); - } - } - - Ok(()) + super::super::apic::poll_apic_core( + this, + |this| { + this.backing.lapics.as_mut().unwrap()[vtl] + .lapic + .scan(&mut this.vmtime, scan_irr) + }, + |_| None, + |this| &mut this.backing.lapics.as_mut().unwrap()[vtl], + |_| false, // TODO WHP GUEST VSM + vtl, + ) } fn halt_in_usermode(this: &mut UhProcessor<'_, Self>, target_vtl: GuestVtl) -> bool { @@ -937,7 +911,7 @@ impl<'a, 'b> InterceptHandler<'a, 'b> { } } -impl UhProcessor<'_, HypervisorBackedX86> { +impl ApicBacking for UhProcessor<'_, HypervisorBackedX86> { fn handle_interrupt(&mut self, vtl: GuestVtl, vector: u8) -> Result<(), UhRunVpError> { const NAMES: &[HvX64RegisterName] = &[ HvX64RegisterName::Rflags, @@ -1112,6 +1086,12 @@ impl UhProcessor<'_, HypervisorBackedX86> { Ok(()) } + fn handle_extint(&mut self, _vtl: GuestVtl) -> Result<(), UhRunVpError> { + todo!() + } +} + +impl UhProcessor<'_, HypervisorBackedX86> { fn set_rip(&mut self, vtl: GuestVtl, rip: u64) -> Result<(), VpHaltReason> { self.runner .set_vp_register(vtl, HvX64RegisterName::Rip, rip.into()) diff --git a/openhcl/virt_mshv_vtl/src/processor/snp/mod.rs b/openhcl/virt_mshv_vtl/src/processor/snp/mod.rs index 64049412a..cae3c0848 100644 --- a/openhcl/virt_mshv_vtl/src/processor/snp/mod.rs +++ b/openhcl/virt_mshv_vtl/src/processor/snp/mod.rs @@ -3,6 +3,7 @@ //! Processor support for SNP partitions. +use super::apic::ApicBacking; use super::from_seg; use super::hardware_cvm; use super::private::BackingParams; @@ -52,7 +53,6 @@ use virt::Processor; use virt::VpHaltReason; use virt::VpIndex; use virt_support_apic::ApicClient; -use virt_support_apic::ApicWork; use virt_support_x86emu::emulate::emulate_io; use virt_support_x86emu::emulate::emulate_translate_gva; use virt_support_x86emu::emulate::EmulatorSupport as X86EmulatorSupport; @@ -392,61 +392,22 @@ impl BackingPrivate for SnpBacked { vtl: GuestVtl, scan_irr: bool, ) -> Result<(), UhRunVpError> { - // Check for interrupt requests from the host. - // TODO SNP GUEST VSM supporting VTL 1 proxy irrs requires kernel changes - if vtl == GuestVtl::Vtl0 { - if let Some(irr) = this.runner.proxy_irr() { - // TODO SNP: filter proxy IRRs. - this.backing.cvm.lapics[vtl] - .lapic - .request_fixed_interrupts(irr); - } - } - // Clear any pending interrupt. this.runner.vmsa_mut(vtl).v_intr_cntrl_mut().set_irq(false); - let ApicWork { - init, - extint, - sipi, - nmi, - interrupt, - } = this.backing.cvm.lapics[vtl] - .lapic - .scan(&mut this.vmtime, scan_irr); - - // An INIT/SIPI targeted at a VP with more than one guest VTL enabled is ignored. - // Check VTL enablement inside each block to avoid taking a lock on the hot path, - // INIT and SIPI are quite cold. - if init { - if !*this.inner.hcvm_vtl1_enabled.lock() { - this.handle_init(vtl)?; - } - } - - if let Some(vector) = sipi { - if !*this.inner.hcvm_vtl1_enabled.lock() { - this.handle_sipi(vtl, vector)?; - } - } - - // Interrupts are ignored while waiting for SIPI. - if this.backing.cvm.lapics[vtl].activity != MpState::WaitForSipi { - if nmi { - this.handle_nmi(vtl); - } - - if let Some(vector) = interrupt { - this.handle_interrupt(vtl, vector); - } - - if extint { - tracelimit::warn_ratelimited!("extint not supported"); - } - } - - Ok(()) + // TODO SNP: filter proxy IRRs. + super::apic::poll_apic_core( + this, + |this| { + this.backing.cvm.lapics[vtl] + .lapic + .scan(&mut this.vmtime, scan_irr) + }, + |this| this.runner.proxy_irr(), + |this| &mut this.backing.cvm.lapics[vtl], + |this| *this.inner.hcvm_vtl1_enabled.lock(), + vtl, + ) } fn request_extint_readiness(_this: &mut UhProcessor<'_, Self>) { @@ -773,17 +734,18 @@ impl HypercallIo for GhcbEnlightenedHypercall<'_, '_, '_, T> { } } -impl UhProcessor<'_, SnpBacked> { - fn handle_interrupt(&mut self, vtl: GuestVtl, vector: u8) { +impl ApicBacking for UhProcessor<'_, SnpBacked> { + fn handle_interrupt(&mut self, vtl: GuestVtl, vector: u8) -> Result<(), UhRunVpError> { let mut vmsa = self.runner.vmsa_mut(vtl); vmsa.v_intr_cntrl_mut().set_vector(vector); vmsa.v_intr_cntrl_mut().set_priority((vector >> 4).into()); vmsa.v_intr_cntrl_mut().set_ignore_tpr(false); vmsa.v_intr_cntrl_mut().set_irq(true); self.backing.cvm.lapics[vtl].activity = MpState::Running; + Ok(()) } - fn handle_nmi(&mut self, vtl: GuestVtl) { + fn handle_nmi(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError> { // TODO SNP: support virtual NMI injection // For now, just inject an NMI and hope for the best. // Don't forget to update handle_cross_vtl_interrupts if this code changes. @@ -795,6 +757,7 @@ impl UhProcessor<'_, SnpBacked> { .with_valid(true), ); self.backing.cvm.lapics[vtl].activity = MpState::Running; + Ok(()) } fn handle_init(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError> { @@ -822,6 +785,13 @@ impl UhProcessor<'_, SnpBacked> { Ok(()) } + fn handle_extint(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError> { + tracelimit::warn_ratelimited!(?vtl, "extint not supported"); + Ok(()) + } +} + +impl UhProcessor<'_, SnpBacked> { fn handle_synic_deliverable_exit(&mut self) { let message = hvdef::HvX64SynicSintDeliverableMessage::ref_from_prefix( self.runner.exit_message().payload(), diff --git a/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs b/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs index f6eaa6dfa..f0ccde9af 100644 --- a/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs +++ b/openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs @@ -5,6 +5,7 @@ mod tlb_flush; +use super::apic::ApicBacking; use super::hardware_cvm; use super::private::BackingPrivate; use super::vp_state; @@ -60,7 +61,6 @@ use virt::Processor; use virt::VpHaltReason; use virt::VpIndex; use virt_support_apic::ApicClient; -use virt_support_apic::ApicWork; use virt_support_apic::OffloadNotSupported; use virt_support_x86emu::emulate::emulate_io; use virt_support_x86emu::emulate::emulate_translate_gva; @@ -880,81 +880,47 @@ impl UhProcessor<'_, TdxBacked> { /// Returns `Ok(false)` if the APIC offload needs to be disabled and the /// poll retried. fn try_poll_apic(&mut self, vtl: GuestVtl, scan_irr: bool) -> Result { - // Check for interrupt requests from the host and kernel IPI offload. - // TODO TDX GUEST VSM supporting VTL 1 proxy irrs requires kernel changes - if vtl == GuestVtl::Vtl0 { - // TODO TDX: filter proxy IRRs by setting the `proxy_irr_blocked` field of the run page - if let Some(irr) = self.runner.proxy_irr() { - // We can't put the interrupts directly on the APIC page because we might need - // to clear the tmr state. This can happen if a vector was previously used for a level - // triggered interrupt, and is now being used for an edge-triggered interrupt. - self.backing.cvm.lapics[vtl] - .lapic - .request_fixed_interrupts(irr); - } - } + let mut scan = TdxApicScanner { + processor_controls: self.backing.vtls[vtl] + .processor_controls + .with_nmi_window_exiting(false) + .with_interrupt_window_exiting(false), + processor: self, + tpr_threshold: 0, + }; - let ApicWork { - init, - extint, - sipi, - nmi, - interrupt, - } = self.backing.cvm.lapics[vtl] - .lapic - .scan(&mut self.vmtime, scan_irr); - - let mut new_processor_controls = self.backing.vtls[vtl] - .processor_controls - .with_nmi_window_exiting(false) - .with_interrupt_window_exiting(false); - - // An INIT/SIPI targeted at a VP with more than one guest VTL enabled is ignored. - // Check VTL enablement inside each block to avoid taking a lock on the hot path, - // INIT and SIPI are quite cold. - if init { - if !*self.inner.hcvm_vtl1_enabled.lock() { - self.handle_init(vtl)?; - } - } + // TODO TDX: filter proxy IRRs by setting the `proxy_irr_blocked` field of the run page + super::apic::poll_apic_core( + &mut scan, + |this| { + this.processor.backing.cvm.lapics[vtl] + .lapic + .scan(&mut this.processor.vmtime, scan_irr) + }, + |this| this.processor.runner.proxy_irr(), + |this| &mut this.processor.backing.cvm.lapics[vtl], + |this| *this.processor.inner.hcvm_vtl1_enabled.lock(), + vtl, + )?; - if let Some(vector) = sipi { - if !*self.inner.hcvm_vtl1_enabled.lock() { - self.handle_sipi(vtl, vector); - } - } + let TdxApicScanner { + processor: _, + processor_controls: new_processor_controls, + tpr_threshold: new_tpr_threshold, + } = scan; // Interrupts are ignored while waiting for SIPI. - if self.backing.cvm.lapics[vtl].activity != MpState::WaitForSipi { - self.backing.cvm.lapics[vtl].nmi_pending |= nmi; - if self.backing.cvm.lapics[vtl].nmi_pending { - self.handle_nmi(vtl, &mut new_processor_controls); - } - - if extint { - tracelimit::warn_ratelimited!("extint not supported"); - } - - let mut new_tpr_threshold = 0; - if let Some(vector) = interrupt { - self.handle_interrupt( - vector, - vtl, - &mut new_processor_controls, - &mut new_tpr_threshold, - ); - } - - if self.backing.vtls[vtl].tpr_threshold != new_tpr_threshold { - tracing::trace!(new_tpr_threshold, ?vtl, "setting tpr threshold"); - self.runner.write_vmcs32( - vtl, - VmcsField::VMX_VMCS_TPR_THRESHOLD, - !0, - new_tpr_threshold.into(), - ); - self.backing.vtls[vtl].tpr_threshold = new_tpr_threshold; - } + if self.backing.cvm.lapics[vtl].activity != MpState::WaitForSipi + && self.backing.vtls[vtl].tpr_threshold != new_tpr_threshold + { + tracing::trace!(new_tpr_threshold, ?vtl, "setting tpr threshold"); + self.runner.write_vmcs32( + vtl, + VmcsField::VMX_VMCS_TPR_THRESHOLD, + !0, + new_tpr_threshold.into(), + ); + self.backing.vtls[vtl].tpr_threshold = new_tpr_threshold; } if self.backing.vtls[vtl].processor_controls != new_processor_controls { @@ -1119,80 +1085,89 @@ impl UhProcessor<'_, TdxBacked> { .set_valid(false); } } +} - fn handle_interrupt( - &mut self, - vector: u8, - vtl: GuestVtl, - processor_controls: &mut ProcessorControls, - tpr_threshold: &mut u8, - ) { +struct TdxApicScanner<'a, 'b> { + processor: &'a mut UhProcessor<'b, TdxBacked>, + processor_controls: ProcessorControls, + tpr_threshold: u8, +} + +impl ApicBacking for TdxApicScanner<'_, '_> { + fn handle_interrupt(&mut self, vtl: GuestVtl, vector: u8) -> Result<(), UhRunVpError> { // Exit idle when an interrupt is received, regardless of IF - if self.backing.cvm.lapics[vtl].activity == MpState::Idle { - self.backing.cvm.lapics[vtl].activity = MpState::Running; + if self.processor.backing.cvm.lapics[vtl].activity == MpState::Idle { + self.processor.backing.cvm.lapics[vtl].activity = MpState::Running; } // If there is a higher-priority pending event of some kind, then // just request an exit after it has resolved, after which we will // try again. - if self.backing.vtls[vtl].interruption_information.valid() - && self.backing.vtls[vtl] + if self.processor.backing.vtls[vtl] + .interruption_information + .valid() + && self.processor.backing.vtls[vtl] .interruption_information .interruption_type() != INTERRUPT_TYPE_EXTERNAL { - processor_controls.set_interrupt_window_exiting(true); - return; + self.processor_controls.set_interrupt_window_exiting(true); + return Ok(()); } // Ensure the interrupt is not blocked by RFLAGS.IF or interrupt shadow. let interruptibility: Interruptibility = self + .processor .runner .read_vmcs32(vtl, VmcsField::VMX_VMCS_GUEST_INTERRUPTIBILITY) .into(); - let rflags = RFlags::from(self.runner.tdx_enter_guest_state().rflags); + let rflags = RFlags::from(self.processor.runner.tdx_enter_guest_state().rflags); if !rflags.interrupt_enable() || interruptibility.blocked_by_sti() || interruptibility.blocked_by_movss() { - processor_controls.set_interrupt_window_exiting(true); - return; + self.processor_controls.set_interrupt_window_exiting(true); + return Ok(()); } let priority = vector >> 4; - let apic: &ApicPage = zerocopy::transmute_ref!(self.runner.tdx_apic_page()); + let apic: &ApicPage = zerocopy::transmute_ref!(self.processor.runner.tdx_apic_page()); if (apic.tpr.value as u8 >> 4) >= priority { - *tpr_threshold = priority; - return; + self.tpr_threshold = priority; + return Ok(()); } - self.backing.vtls[vtl].interruption_information = InterruptionInformation::new() + self.processor.backing.vtls[vtl].interruption_information = InterruptionInformation::new() .with_valid(true) .with_vector(vector) .with_interruption_type(INTERRUPT_TYPE_EXTERNAL); - self.backing.cvm.lapics[vtl].activity = MpState::Running; + self.processor.backing.cvm.lapics[vtl].activity = MpState::Running; + Ok(()) } - fn handle_nmi(&mut self, vtl: GuestVtl, processor_controls: &mut ProcessorControls) { + fn handle_nmi(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError> { // Exit idle when an interrupt is received, regardless of IF - if self.backing.cvm.lapics[vtl].activity == MpState::Idle { - self.backing.cvm.lapics[vtl].activity = MpState::Running; + if self.processor.backing.cvm.lapics[vtl].activity == MpState::Idle { + self.processor.backing.cvm.lapics[vtl].activity = MpState::Running; } // If there is a higher-priority pending event of some kind, then // just request an exit after it has resolved, after which we will // try again. - if self.backing.vtls[vtl].interruption_information.valid() - && self.backing.vtls[vtl] + if self.processor.backing.vtls[vtl] + .interruption_information + .valid() + && self.processor.backing.vtls[vtl] .interruption_information .interruption_type() != INTERRUPT_TYPE_EXTERNAL { - processor_controls.set_nmi_window_exiting(true); - return; + self.processor_controls.set_nmi_window_exiting(true); + return Ok(()); } let interruptibility: Interruptibility = self + .processor .runner .read_vmcs32(vtl, VmcsField::VMX_VMCS_GUEST_INTERRUPTIBILITY) .into(); @@ -1201,46 +1176,56 @@ impl UhProcessor<'_, TdxBacked> { || interruptibility.blocked_by_sti() || interruptibility.blocked_by_movss() { - processor_controls.set_nmi_window_exiting(true); - return; + self.processor_controls.set_nmi_window_exiting(true); + return Ok(()); } - self.backing.vtls[vtl].interruption_information = InterruptionInformation::new() + self.processor.backing.vtls[vtl].interruption_information = InterruptionInformation::new() .with_valid(true) .with_vector(2) .with_interruption_type(INTERRUPT_TYPE_NMI); - self.backing.cvm.lapics[vtl].activity = MpState::Running; + self.processor.backing.cvm.lapics[vtl].activity = MpState::Running; + Ok(()) } fn handle_init(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError> { - let vp_info = self.inner.vp_info; + let vp_info = self.processor.inner.vp_info; { - let mut access = self.access_state(vtl.into()); + let mut access = self.processor.access_state(vtl.into()); vp::x86_init(&mut access, &vp_info).map_err(UhRunVpError::State)?; } Ok(()) } - fn handle_sipi(&mut self, vtl: GuestVtl, vector: u8) { - if self.backing.cvm.lapics[vtl].activity == MpState::WaitForSipi { + fn handle_sipi(&mut self, vtl: GuestVtl, vector: u8) -> Result<(), UhRunVpError> { + if self.processor.backing.cvm.lapics[vtl].activity == MpState::WaitForSipi { let address = (vector as u64) << 12; - self.write_segment( - vtl, - TdxSegmentReg::Cs, - SegmentRegister { - base: address, - limit: 0xffff, - selector: (address >> 4) as u16, - attributes: 0x9b, - }, - ) - .unwrap(); - self.runner.tdx_enter_guest_state_mut().rip = 0; - self.backing.cvm.lapics[vtl].activity = MpState::Running; + self.processor + .write_segment( + vtl, + TdxSegmentReg::Cs, + SegmentRegister { + base: address, + limit: 0xffff, + selector: (address >> 4) as u16, + attributes: 0x9b, + }, + ) + .unwrap(); + self.processor.runner.tdx_enter_guest_state_mut().rip = 0; + self.processor.backing.cvm.lapics[vtl].activity = MpState::Running; } + Ok(()) + } + + fn handle_extint(&mut self, vtl: GuestVtl) -> Result<(), UhRunVpError> { + tracelimit::warn_ratelimited!(?vtl, "extint not supported"); + Ok(()) } +} +impl UhProcessor<'_, TdxBacked> { async fn run_vp_tdx(&mut self, dev: &impl CpuIo) -> Result<(), VpHaltReason> { let next_vtl = self.backing.cvm.exit_vtl;