vmm: support injecting NMI

Inject NMI interrupt when needed, by call ioctl KVM_NMI.

Signed-off-by: Yi Wang <foxywang@tencent.com>
This commit is contained in:
Yi Wang 2023-12-11 17:57:25 +08:00 committed by Rob Bradford
parent f40dd4a993
commit c72bf0b32d
7 changed files with 93 additions and 2 deletions

View File

@ -278,6 +278,11 @@ pub enum HypervisorCpuError {
#[cfg(feature = "sev_snp")] #[cfg(feature = "sev_snp")]
#[error("Failed to set sev control register: {0}")] #[error("Failed to set sev control register: {0}")]
SetSevControlRegister(#[source] anyhow::Error), SetSevControlRegister(#[source] anyhow::Error),
/// Error injecting NMI
///
#[error("Failed to inject NMI")]
Nmi(#[source] anyhow::Error),
} }
#[derive(Debug)] #[derive(Debug)]
@ -505,4 +510,12 @@ pub trait Vcpu: Send + Sync {
fn set_sev_control_register(&self, _reg: u64) -> Result<()> { fn set_sev_control_register(&self, _reg: u64) -> Result<()> {
unimplemented!() unimplemented!()
} }
#[cfg(target_arch = "x86_64")]
///
/// Trigger NMI interrupt
///
fn nmi(&self) -> Result<()> {
unimplemented!()
}
} }

View File

@ -105,6 +105,15 @@ pub use {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
const KVM_CAP_SGX_ATTRIBUTE: u32 = 196; const KVM_CAP_SGX_ATTRIBUTE: u32 = 196;
#[cfg(target_arch = "x86_64")]
use vmm_sys_util::ioctl_io_nr;
#[cfg(all(not(feature = "tdx"), target_arch = "x86_64"))]
use vmm_sys_util::ioctl_ioc_nr;
#[cfg(target_arch = "x86_64")]
ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a);
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
const KVM_EXIT_TDX: u32 = 50; const KVM_EXIT_TDX: u32 = 50;
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
@ -2312,6 +2321,23 @@ impl cpu::Vcpu for KvmVcpu {
Ok(_) => Ok(()), Ok(_) => Ok(()),
} }
} }
#[cfg(target_arch = "x86_64")]
///
/// Trigger NMI interrupt
///
fn nmi(&self) -> cpu::Result<()> {
match self.fd.nmi() {
Err(e) => {
if e.errno() == libc::EIO {
Ok(())
} else {
Err(cpu::HypervisorCpuError::Nmi(e.into()))
}
}
Ok(_) => Ok(()),
}
}
} }
impl KvmVcpu { impl KvmVcpu {

View File

@ -405,6 +405,13 @@ paths:
405: 405:
description: The VM instance could not be coredumped because it is not booted. description: The VM instance could not be coredumped because it is not booted.
/vmm.nmi:
put:
summary: Inject an NMI.
responses:
204:
description: The NMI successfully injected.
/vm.restore: /vm.restore:
put: put:
summary: Restore a VM from a snapshot. summary: Restore a VM from a snapshot.

View File

@ -188,6 +188,10 @@ pub enum Error {
#[cfg(feature = "sev_snp")] #[cfg(feature = "sev_snp")]
#[error("Failed to set sev control register: {0}")] #[error("Failed to set sev control register: {0}")]
SetSevControlRegister(#[source] hypervisor::HypervisorCpuError), SetSevControlRegister(#[source] hypervisor::HypervisorCpuError),
#[cfg(target_arch = "x86_64")]
#[error("Failed to inject NMI")]
NmiError(hypervisor::HypervisorCpuError),
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@ -477,6 +481,7 @@ pub struct CpuManager {
vm: Arc<dyn hypervisor::Vm>, vm: Arc<dyn hypervisor::Vm>,
vcpus_kill_signalled: Arc<AtomicBool>, vcpus_kill_signalled: Arc<AtomicBool>,
vcpus_pause_signalled: Arc<AtomicBool>, vcpus_pause_signalled: Arc<AtomicBool>,
vcpus_kick_signalled: Arc<AtomicBool>,
exit_evt: EventFd, exit_evt: EventFd,
#[cfg_attr(target_arch = "aarch64", allow(dead_code))] #[cfg_attr(target_arch = "aarch64", allow(dead_code))]
reset_evt: EventFd, reset_evt: EventFd,
@ -721,6 +726,7 @@ impl CpuManager {
vm, vm,
vcpus_kill_signalled: Arc::new(AtomicBool::new(false)), vcpus_kill_signalled: Arc::new(AtomicBool::new(false)),
vcpus_pause_signalled: Arc::new(AtomicBool::new(false)), vcpus_pause_signalled: Arc::new(AtomicBool::new(false)),
vcpus_kick_signalled: Arc::new(AtomicBool::new(false)),
vcpu_states, vcpu_states,
exit_evt, exit_evt,
reset_evt, reset_evt,
@ -934,6 +940,7 @@ impl CpuManager {
let panic_exit_evt = self.exit_evt.try_clone().unwrap(); let panic_exit_evt = self.exit_evt.try_clone().unwrap();
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone(); let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone(); let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
let vcpu_kick_signalled = self.vcpus_kick_signalled.clone();
let vcpu_kill = self.vcpu_states[usize::from(vcpu_id)].kill.clone(); let vcpu_kill = self.vcpu_states[usize::from(vcpu_id)].kill.clone();
let vcpu_run_interrupted = self.vcpu_states[usize::from(vcpu_id)] let vcpu_run_interrupted = self.vcpu_states[usize::from(vcpu_id)]
@ -1058,6 +1065,18 @@ impl CpuManager {
vcpu_run_interrupted.store(false, Ordering::SeqCst); vcpu_run_interrupted.store(false, Ordering::SeqCst);
} }
if vcpu_kick_signalled.load(Ordering::SeqCst) {
vcpu_run_interrupted.store(true, Ordering::SeqCst);
#[cfg(target_arch = "x86_64")]
match vcpu.lock().as_ref().unwrap().vcpu.nmi() {
Ok(()) => {},
Err(e) => {
error!("Error when inject nmi {}", e);
break;
}
}
}
// We've been told to terminate // We've been told to terminate
if vcpu_kill_signalled.load(Ordering::SeqCst) if vcpu_kill_signalled.load(Ordering::SeqCst)
|| vcpu_kill.load(Ordering::SeqCst) || vcpu_kill.load(Ordering::SeqCst)
@ -1848,6 +1867,18 @@ impl CpuManager {
pub(crate) fn sev_snp_enabled(&self) -> bool { pub(crate) fn sev_snp_enabled(&self) -> bool {
self.sev_snp_enabled self.sev_snp_enabled
} }
pub(crate) fn nmi(&self) -> Result<()> {
self.vcpus_kick_signalled.store(true, Ordering::SeqCst);
for state in self.vcpu_states.iter() {
state.signal_thread();
}
self.vcpus_kick_signalled.store(false, Ordering::SeqCst);
Ok(())
}
} }
struct Cpu { struct Cpu {

View File

@ -1842,8 +1842,7 @@ impl RequestHandler for Vmm {
fn vm_nmi(&mut self) -> result::Result<(), VmError> { fn vm_nmi(&mut self) -> result::Result<(), VmError> {
if let Some(ref mut vm) = self.vm { if let Some(ref mut vm) = self.vm {
info!("nmi"); vm.nmi()
vm.power_button()
} else { } else {
Err(VmError::VmNotRunning) Err(VmError::VmNotRunning)
} }

View File

@ -145,6 +145,7 @@ mod kvm {
pub const KVM_CREATE_DEVICE: u64 = 0xc00c_aee0; pub const KVM_CREATE_DEVICE: u64 = 0xc00c_aee0;
pub const KVM_GET_REG_LIST: u64 = 0xc008_aeb0; pub const KVM_GET_REG_LIST: u64 = 0xc008_aeb0;
pub const KVM_MEMORY_ENCRYPT_OP: u64 = 0xc008_aeba; pub const KVM_MEMORY_ENCRYPT_OP: u64 = 0xc008_aeba;
pub const KVM_NMI: u64 = 0xae9a;
} }
#[cfg(feature = "kvm")] #[cfg(feature = "kvm")]
@ -275,6 +276,7 @@ fn create_vmm_ioctl_seccomp_rule_common_kvm() -> Result<Vec<SeccompRule>, Backen
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_REGS)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_REGS)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_VCPU_EVENTS,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_VCPU_EVENTS,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?],
]) ])
} }
@ -688,6 +690,7 @@ fn create_vcpu_ioctl_seccomp_rule_kvm() -> Result<Vec<SeccompRule>, BackendError
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_GSI_ROUTING,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_GSI_ROUTING,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_SET_USER_MEMORY_REGION,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_RUN,)?], and![Cond::new(1, ArgLen::Dword, Eq, KVM_RUN,)?],
and![Cond::new(1, ArgLen::Dword, Eq, KVM_NMI)?],
]) ])
} }

View File

@ -316,6 +316,9 @@ pub enum Error {
#[cfg(feature = "igvm")] #[cfg(feature = "igvm")]
#[error("Cannot load the igvm into memory: {0}")] #[error("Cannot load the igvm into memory: {0}")]
IgvmLoad(#[source] igvm_loader::Error), IgvmLoad(#[source] igvm_loader::Error),
#[error("Error injecting NMI")]
ErrorNmi,
} }
pub type Result<T> = result::Result<T, Error>; pub type Result<T> = result::Result<T, Error>;
@ -2446,6 +2449,15 @@ impl Vm {
+ note_size as u64 + note_size as u64
+ size_of::<elf::Elf64_Phdr>() as u64 * phdr_num as u64 + size_of::<elf::Elf64_Phdr>() as u64 * phdr_num as u64
} }
pub fn nmi(&self) -> Result<()> {
return self
.cpu_manager
.lock()
.unwrap()
.nmi()
.map_err(|_| Error::ErrorNmi);
}
} }
impl Pausable for Vm { impl Pausable for Vm {