diff --git a/hypervisor/src/cpu.rs b/hypervisor/src/cpu.rs index 43a3f8887..4a4738e5e 100644 --- a/hypervisor/src/cpu.rs +++ b/hypervisor/src/cpu.rs @@ -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!() + } } diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 6d91452f7..28edfd628 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -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 { diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 81d5a6a81..9b4799232 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -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. diff --git a/vmm/src/cpu.rs b/vmm/src/cpu.rs index 5c31dfac1..5cd8eddeb 100644 --- a/vmm/src/cpu.rs +++ b/vmm/src/cpu.rs @@ -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 = result::Result; @@ -477,6 +481,7 @@ pub struct CpuManager { vm: Arc, vcpus_kill_signalled: Arc, vcpus_pause_signalled: Arc, + vcpus_kick_signalled: Arc, 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 { diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 3cc4560a6..3a5b85a08 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -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) } diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index a15a1134e..6669c39eb 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -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, 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, 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)?], ]) } diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index da8a627e3..df9f87c0c 100644 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -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 = result::Result; @@ -2446,6 +2449,15 @@ impl Vm { + note_size as u64 + size_of::() 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 {