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")]
#[error("Failed to set sev control register: {0}")]
SetSevControlRegister(#[source] anyhow::Error),
/// Error injecting NMI
///
#[error("Failed to inject NMI")]
Nmi(#[source] anyhow::Error),
}
#[derive(Debug)]
@ -505,4 +510,12 @@ pub trait Vcpu: Send + Sync {
fn set_sev_control_register(&self, _reg: u64) -> Result<()> {
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")]
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")]
const KVM_EXIT_TDX: u32 = 50;
#[cfg(feature = "tdx")]
@ -2312,6 +2321,23 @@ impl cpu::Vcpu for KvmVcpu {
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 {

View File

@ -405,6 +405,13 @@ paths:
405:
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:
put:
summary: Restore a VM from a snapshot.

View File

@ -188,6 +188,10 @@ pub enum Error {
#[cfg(feature = "sev_snp")]
#[error("Failed to set sev control register: {0}")]
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>;
@ -477,6 +481,7 @@ pub struct CpuManager {
vm: Arc<dyn hypervisor::Vm>,
vcpus_kill_signalled: Arc<AtomicBool>,
vcpus_pause_signalled: Arc<AtomicBool>,
vcpus_kick_signalled: Arc<AtomicBool>,
exit_evt: EventFd,
#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
reset_evt: EventFd,
@ -721,6 +726,7 @@ impl CpuManager {
vm,
vcpus_kill_signalled: Arc::new(AtomicBool::new(false)),
vcpus_pause_signalled: Arc::new(AtomicBool::new(false)),
vcpus_kick_signalled: Arc::new(AtomicBool::new(false)),
vcpu_states,
exit_evt,
reset_evt,
@ -934,6 +940,7 @@ impl CpuManager {
let panic_exit_evt = self.exit_evt.try_clone().unwrap();
let vcpu_kill_signalled = self.vcpus_kill_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_run_interrupted = self.vcpu_states[usize::from(vcpu_id)]
@ -1058,6 +1065,18 @@ impl CpuManager {
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
if vcpu_kill_signalled.load(Ordering::SeqCst)
|| vcpu_kill.load(Ordering::SeqCst)
@ -1848,6 +1867,18 @@ impl CpuManager {
pub(crate) fn sev_snp_enabled(&self) -> bool {
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 {

View File

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

View File

@ -145,6 +145,7 @@ mod kvm {
pub const KVM_CREATE_DEVICE: u64 = 0xc00c_aee0;
pub const KVM_GET_REG_LIST: u64 = 0xc008_aeb0;
pub const KVM_MEMORY_ENCRYPT_OP: u64 = 0xc008_aeba;
pub const KVM_NMI: u64 = 0xae9a;
}
#[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_USER_MEMORY_REGION,)?],
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_USER_MEMORY_REGION,)?],
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")]
#[error("Cannot load the igvm into memory: {0}")]
IgvmLoad(#[source] igvm_loader::Error),
#[error("Error injecting NMI")]
ErrorNmi,
}
pub type Result<T> = result::Result<T, Error>;
@ -2446,6 +2449,15 @@ impl Vm {
+ note_size 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 {