-
Notifications
You must be signed in to change notification settings - Fork 186
SNP: Test and set guest busy bit atomically. #2695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,8 @@ | |
| use std::array; | ||
| use std::ops::Deref; | ||
| use std::ops::DerefMut; | ||
| use std::sync::atomic::AtomicU64; | ||
| use std::sync::atomic::Ordering; | ||
| use x86defs::snp::SevEventInjectInfo; | ||
| use x86defs::snp::SevFeatures; | ||
| use x86defs::snp::SevSelector; | ||
|
|
@@ -96,6 +98,16 @@ impl<T: DerefMut<Target = SevVmsa>> VmsaWrapper<'_, T> { | |
| | ((self.set_u64((v >> 64) as u64, offset + 8) as u128) << 64) | ||
| } | ||
|
|
||
| /// Gets an atomic reference to the v_intr_cntrl field. | ||
| /// # Safety | ||
| /// v_intr_cntrl must not be accessed by other VPs. | ||
| unsafe fn v_intr_cntrl_atomic(&mut self) -> &AtomicU64 { | ||
| // SAFETY: the vintr control field is on the VMSA which is per-VP per-VTL. | ||
| // As VPs cannot run on multiple processors at the same time there is a | ||
| // limited need for synchronization on the vintr control field. | ||
| unsafe { &*(core::ptr::from_ref(&self.vmsa.v_intr_cntrl).cast::<AtomicU64>()) } | ||
|
Comment on lines
+105
to
+108
|
||
| } | ||
|
|
||
| /// Create a new VMSA | ||
| pub fn reset(&mut self, vmsa_reg_prot: bool) { | ||
| *self.vmsa = FromZeros::new_zeroed(); | ||
|
|
@@ -157,6 +169,15 @@ impl<T: DerefMut<Target = SevVmsa>> VmsaWrapper<'_, T> { | |
| self.vmsa.x87_registers[i] = val; | ||
| } | ||
| } | ||
|
|
||
| /// Atomically test and set the guest busy bit in v_intr_cntrl. | ||
| pub fn guest_busy_bit_test_and_set(&mut self) -> bool { | ||
| const VINTR_GUEST_BUSYBIT_MASK: u64 = 1u64 << 63; | ||
| // SAFETY: v_intr_cntrl is only accessed by this VP. | ||
| let prev = unsafe { self.v_intr_cntrl_atomic() } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the only place where this atomic access is needed? If so can we just inline it?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd definitely feel more comfortable with inlining this method instead of having it separate, it removes a lot of safety concerns. |
||
| .fetch_or(VINTR_GUEST_BUSYBIT_MASK, Ordering::SeqCst); | ||
| (prev & VINTR_GUEST_BUSYBIT_MASK) != 0 | ||
| } | ||
|
Comment on lines
+174
to
+180
|
||
| } | ||
|
|
||
| /// Check bitmap to see if a register is included in masking. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,8 +97,12 @@ enum SnpGhcbError { | |
| } | ||
|
|
||
| #[derive(Debug, Error)] | ||
| #[error("failed to run")] | ||
| struct SnpRunVpError(#[source] hcl::ioctl::Error); | ||
| enum SnpRunVpError { | ||
| #[error("Guest AVIC backing page is not validated or cannot be accessed.")] | ||
|
|
||
| VpNotRestartableError, | ||
| #[error("failed to run")] | ||
| RunVpError(#[source] hcl::ioctl::Error), | ||
| } | ||
|
|
||
| /// A backing for SNP partitions. | ||
| #[derive(InspectMut)] | ||
|
|
@@ -1256,31 +1260,50 @@ impl UhProcessor<'_, SnpBacked> { | |
| let mut has_intercept = self | ||
| .runner | ||
| .run() | ||
| .map_err(|e| dev.fatal_error(SnpRunVpError(e).into()))?; | ||
| .map_err(|e| dev.fatal_error(SnpRunVpError::RunVpError(e).into()))?; | ||
|
|
||
| let entered_from_vtl = next_vtl; | ||
| let mut vmsa = self.runner.vmsa_mut(entered_from_vtl); | ||
| let exit_int_info_trace = SevEventInjectInfo::from(vmsa.exit_int_info()); | ||
|
|
||
| // TODO SNP: The guest busy bit needs to be tested and set atomically. | ||
| let inject = if vmsa.sev_features().alternate_injection() { | ||
| if vmsa.v_intr_cntrl().guest_busy() { | ||
| if vmsa.sev_features().alternate_injection() { | ||
| let was_busy = vmsa.guest_busy_bit_test_and_set(); | ||
| if was_busy { | ||
| self.backing.general_stats[entered_from_vtl] | ||
| .guest_busy | ||
| .increment(); | ||
| // Software interrupts/exceptions cannot be automatically re-injected, but RIP still | ||
| // points to the instruction and the event should be re-generated when the | ||
| // instruction is re-executed. Note that hardware does not provide instruction | ||
| // length in this case so it's impossible to directly re-inject a software event if | ||
| // delivery generates an intercept. | ||
| // | ||
| // TODO SNP: Handle ICEBP. | ||
| let exit_int_info = SevEventInjectInfo::from(vmsa.exit_int_info()); | ||
| assert!( | ||
| exit_int_info.valid(), | ||
| "event inject info should be valid {exit_int_info:x?}" | ||
| ); | ||
|
|
||
| match exit_int_info.interruption_type() { | ||
| let sev_error_code = SevExitCode(vmsa.guest_error_code()); | ||
| match sev_error_code { | ||
| SevExitCode::NOT_RESTARTABLE => { | ||
| // The guest APIC backing page is not validated in the RMP. | ||
| return Err(dev.fatal_error(SnpRunVpError::VpNotRestartableError.into())); | ||
| } | ||
| SevExitCode::NPF => { | ||
| let exit_info = SevNpfInfo::from(vmsa.exit_info1()); | ||
| if exit_info.not_restartable() { | ||
| // An access to the guest's APIC backing page by AVIC hardware resulted | ||
| // in a nested page fault. | ||
|
Comment on lines
+1279
to
+1286
|
||
| return Err( | ||
| dev.fatal_error(SnpRunVpError::VpNotRestartableError.into()) | ||
| ); | ||
| } | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| // Software interrupts/exceptions cannot be automatically re-injected, but RIP still | ||
| // points to the instruction and the event should be re-generated when the | ||
| // instruction is re-executed. Note that hardware does not provide instruction | ||
| // length in this case so it's impossible to directly re-inject a software event if | ||
| // delivery generates an intercept. | ||
| // | ||
| // TODO SNP: Handle ICEBP. | ||
| let exit_int_info = SevEventInjectInfo::from(vmsa.exit_int_info()); | ||
|
|
||
| if exit_int_info.valid() { | ||
| let inject = match exit_int_info.interruption_type() { | ||
| x86defs::snp::SEV_INTR_TYPE_EXCEPT => { | ||
| if exit_int_info.vector() != 3 && exit_int_info.vector() != 4 { | ||
| // If the event is an exception, we can inject it. | ||
|
|
@@ -1291,21 +1314,23 @@ impl UhProcessor<'_, SnpBacked> { | |
| } | ||
| x86defs::snp::SEV_INTR_TYPE_SW => None, | ||
| _ => Some(exit_int_info), | ||
| }; | ||
|
|
||
| if let Some(inject) = inject { | ||
| vmsa.set_event_inject(inject); | ||
| } | ||
|
|
||
| // Since the exit interrupt information was processed, it must be | ||
| // cleared so that it is not examined again on a subsequent reentry to | ||
| // the HCL. | ||
| vmsa.set_exit_int_info(0); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember this code well enough, but is it safe to clear the exit_int_info here? Looks like it's used below #Resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes added a comment to clarify |
||
| } else { | ||
| None | ||
| // Any previously injected event has been consumed. | ||
| } | ||
| } else { | ||
| unimplemented!("Only alternate injection is supported for SNP") | ||
| }; | ||
|
|
||
| if let Some(inject) = inject { | ||
| vmsa.set_event_inject(inject); | ||
| } | ||
| if vmsa.sev_features().alternate_injection() { | ||
| vmsa.v_intr_cntrl_mut().set_guest_busy(true); | ||
| } | ||
|
|
||
| if last_interrupt_ctrl.irq() && !vmsa.v_intr_cntrl().irq() { | ||
| self.backing.general_stats[entered_from_vtl] | ||
| .int_ack | ||
|
|
@@ -1624,7 +1649,7 @@ impl UhProcessor<'_, SnpBacked> { | |
| tracing::error!( | ||
| CVM_CONFIDENTIAL, | ||
| "SEV exit code {sev_error_code:x?} sev features {:x?} v_intr_control {:x?} event inject {:x?} \ | ||
| vmpl {:x?} cpl {:x?} exit_info1 {:x?} exit_info2 {:x?} exit_int_info {:x?} virtual_tom {:x?} \ | ||
| vmpl {:x?} cpl {:x?} exit_info1 {:x?} exit_info2 {:x?} exit_int_info {:x?} virtual_tom {:x?} \ | ||
| efer {:x?} cr4 {:x?} cr3 {:x?} cr0 {:x?} rflag {:x?} rip {:x?} next rip {:x?}", | ||
| vmsa.sev_features(), | ||
| vmsa.v_intr_cntrl(), | ||
|
|
@@ -1633,7 +1658,7 @@ impl UhProcessor<'_, SnpBacked> { | |
| vmsa.cpl(), | ||
| vmsa.exit_info1(), | ||
| vmsa.exit_info2(), | ||
| vmsa.exit_int_info(), | ||
| exit_int_info_trace, | ||
| vmsa.virtual_tom(), | ||
| vmsa.efer(), | ||
| vmsa.cr4(), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not just other VPs, no normal rust reference can exist to it while this one does, as modifying something behind a normal reference is UB. And actually, other VPs accessing it would be fine so long as they are also doing so atomically. Is it possible to restrict access to this field so that it can only be accessed atomically?