Skip to content

Commit a45c35b

Browse files
committed
Implement InterruptHandle API
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent b88df32 commit a45c35b

File tree

6 files changed

+550
-128
lines changed

6 files changed

+550
-128
lines changed

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ extern crate mshv_bindings3 as mshv_bindings;
2525
extern crate mshv_ioctls3 as mshv_ioctls;
2626

2727
use std::fmt::{Debug, Formatter};
28+
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
29+
use std::sync::Arc;
2830

2931
use log::{error, LevelFilter};
3032
#[cfg(mshv2)]
@@ -46,7 +48,7 @@ use mshv_bindings::{
4648
hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES,
4749
hv_partition_synthetic_processor_features,
4850
};
49-
use mshv_ioctls::{Mshv, VcpuFd, VmFd};
51+
use mshv_ioctls::{Mshv, MshvError, VcpuFd, VmFd};
5052
use tracing::{instrument, Span};
5153

5254
use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
@@ -56,8 +58,9 @@ use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, MshvDebu
5658
use super::handlers::DbgMemAccessHandlerWrapper;
5759
use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
5860
use super::{
59-
Hypervisor, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR,
60-
CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE,
61+
Hypervisor, InterruptHandle, LinuxInterruptHandle, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE,
62+
CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX,
63+
EFER_SCE,
6164
};
6265
use crate::hypervisor::HyperlightExit;
6366
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
@@ -285,13 +288,14 @@ pub(crate) fn is_hypervisor_present() -> bool {
285288

286289
/// A Hypervisor driver for HyperV-on-Linux. This hypervisor is often
287290
/// called the Microsoft Hypervisor (MSHV)
288-
pub(super) struct HypervLinuxDriver {
291+
pub(crate) struct HypervLinuxDriver {
289292
_mshv: Mshv,
290293
vm_fd: VmFd,
291294
vcpu_fd: VcpuFd,
292295
entrypoint: u64,
293296
mem_regions: Vec<MemoryRegion>,
294297
orig_rsp: GuestPtr,
298+
interrupt_handle: Arc<LinuxInterruptHandle>,
295299

296300
#[cfg(gdb)]
297301
debug: Option<MshvDebug>,
@@ -309,7 +313,7 @@ impl HypervLinuxDriver {
309313
/// `apply_registers` method to do that, or more likely call
310314
/// `initialise` to do it for you.
311315
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
312-
pub(super) fn new(
316+
pub(crate) fn new(
313317
mem_regions: Vec<MemoryRegion>,
314318
entrypoint_ptr: GuestPtr,
315319
rsp_ptr: GuestPtr,
@@ -389,6 +393,12 @@ impl HypervLinuxDriver {
389393
mem_regions,
390394
entrypoint: entrypoint_ptr.absolute()?,
391395
orig_rsp: rsp_ptr,
396+
interrupt_handle: Arc::new(LinuxInterruptHandle {
397+
running: AtomicBool::new(false),
398+
cancel_requested: AtomicBool::new(false),
399+
tid: AtomicU64::new(unsafe { libc::pthread_self() }),
400+
dropped: AtomicBool::new(false),
401+
}),
392402

393403
#[cfg(gdb)]
394404
debug,
@@ -572,15 +582,57 @@ impl Hypervisor for HypervLinuxDriver {
572582
#[cfg(gdb)]
573583
const EXCEPTION_INTERCEPT: hv_message_type = hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT;
574584

575-
#[cfg(mshv2)]
576-
let run_result = {
577-
let hv_message: hv_message = Default::default();
578-
&self.vcpu_fd.run(hv_message)
585+
self.interrupt_handle
586+
.tid
587+
.store(unsafe { libc::pthread_self() as u64 }, Ordering::Relaxed);
588+
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
589+
// Then this is fine since `cancel_requested` is set to true, so we will skip the `VcpuFd::run()` call
590+
self.interrupt_handle.running.store(true, Ordering::Relaxed);
591+
// Don't run the vcpu if `cancel_requested` is true
592+
//
593+
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
594+
// Then this is fine since `cancel_requested` is set to true, so we will skip the `VcpuFd::run()` call
595+
let exit_reason = if self
596+
.interrupt_handle
597+
.cancel_requested
598+
.load(Ordering::Relaxed)
599+
{
600+
Err(MshvError::Errno(vmm_sys_util::errno::Error::new(
601+
libc::EINTR,
602+
)))
603+
} else {
604+
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
605+
// Then the vcpu will run, but we will keep sending signals to this thread
606+
// to interrupt it until `running` is set to false. The `vcpu_fd::run()` call will
607+
// return either normally with an exit reason, or from being "kicked" by out signal handler, with an EINTR error,
608+
// both of which are fine.
609+
#[cfg(mshv2)]
610+
{
611+
let hv_message: hv_message = Default::default();
612+
self.vcpu_fd.run(hv_message)
613+
}
614+
#[cfg(mshv3)]
615+
self.vcpu_fd.run()
579616
};
580-
#[cfg(mshv3)]
581-
let run_result = &self.vcpu_fd.run();
582-
583-
let result = match run_result {
617+
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
618+
// Then signals will be sent to this thread until `running` is set to false.
619+
// This is fine since the signal handler is a no-op.
620+
let cancel_requested = self
621+
.interrupt_handle
622+
.cancel_requested
623+
.swap(false, Ordering::Relaxed);
624+
// Note: if a `InterruptHandle::kill()` called while this thread is **here**
625+
// Then `cancel_requested` will be set to true again, which will cancel the **next vcpu run**.
626+
// Additionally signals will be sent to this thread until `running` is set to false.
627+
// This is fine since the signal handler is a no-op.
628+
self.interrupt_handle
629+
.running
630+
.store(false, Ordering::Relaxed);
631+
// At this point, `running` is false so no more signals will be sent to this thread,
632+
// but we may still receive async signals that were sent before this point.
633+
// To prevent those signals from interrupting subsequent calls to `run()`,
634+
// we make sure to check `cancel_requested` before cancelling (see `libc::EINTR` match-arm below).
635+
let result = match exit_reason {
584636
Ok(m) => match m.header.message_type {
585637
HALT_MESSAGE => {
586638
crate::debug!("mshv - Halt Details : {:#?}", &self);
@@ -657,7 +709,15 @@ impl Hypervisor for HypervLinuxDriver {
657709
},
658710
Err(e) => match e.errno() {
659711
// we send a signal to the thread to cancel execution this results in EINTR being returned by KVM so we return Cancelled
660-
libc::EINTR => HyperlightExit::Cancelled(),
712+
libc::EINTR => {
713+
// If cancellation was not requested for this specific vm, the vcpu was interrupted because of stale signal
714+
// that was meant to be delivered to a previous/other vcpu on this same thread, so let's ignore it
715+
if !cancel_requested {
716+
HyperlightExit::Retry()
717+
} else {
718+
HyperlightExit::Cancelled()
719+
}
720+
}
661721
libc::EAGAIN => HyperlightExit::Retry(),
662722
_ => {
663723
crate::debug!("mshv Error - Details: Error: {} \n {:#?}", e, &self);
@@ -673,6 +733,10 @@ impl Hypervisor for HypervLinuxDriver {
673733
self as &mut dyn Hypervisor
674734
}
675735

736+
fn interrupt_handle(&self) -> Arc<dyn InterruptHandle> {
737+
self.interrupt_handle.clone()
738+
}
739+
676740
#[cfg(crashdump)]
677741
fn get_memory_regions(&self) -> &[MemoryRegion] {
678742
&self.mem_regions
@@ -727,6 +791,7 @@ impl Hypervisor for HypervLinuxDriver {
727791
impl Drop for HypervLinuxDriver {
728792
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
729793
fn drop(&mut self) {
794+
self.interrupt_handle.dropped.store(true, Ordering::Relaxed);
730795
for region in &self.mem_regions {
731796
let mshv_region: mshv_user_mem_region = region.to_owned().into();
732797
match self.vm_fd.unmap_user_memory(mshv_region) {

src/hyperlight_host/src/hypervisor/hyperv_windows.rs

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ use core::ffi::c_void;
1818
use std::fmt;
1919
use std::fmt::{Debug, Formatter};
2020
use std::string::String;
21+
use std::sync::atomic::{AtomicBool, Ordering};
22+
use std::sync::Arc;
2123

2224
use hyperlight_common::mem::PAGE_SIZE_USIZE;
2325
use log::LevelFilter;
2426
use tracing::{instrument, Span};
2527
use windows::Win32::System::Hypervisor::{
26-
WHvX64RegisterCr0, WHvX64RegisterCr3, WHvX64RegisterCr4, WHvX64RegisterCs, WHvX64RegisterEfer,
27-
WHV_MEMORY_ACCESS_TYPE, WHV_PARTITION_HANDLE, WHV_REGISTER_VALUE, WHV_RUN_VP_EXIT_CONTEXT,
28-
WHV_RUN_VP_EXIT_REASON, WHV_X64_SEGMENT_REGISTER, WHV_X64_SEGMENT_REGISTER_0,
28+
WHvCancelRunVirtualProcessor, WHvX64RegisterCr0, WHvX64RegisterCr3, WHvX64RegisterCr4,
29+
WHvX64RegisterCs, WHvX64RegisterEfer, WHV_MEMORY_ACCESS_TYPE, WHV_PARTITION_HANDLE,
30+
WHV_REGISTER_VALUE, WHV_RUN_VP_EXIT_CONTEXT, WHV_RUN_VP_EXIT_REASON, WHV_X64_SEGMENT_REGISTER,
31+
WHV_X64_SEGMENT_REGISTER_0,
2932
};
3033

3134
use super::fpu::{FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
@@ -37,8 +40,9 @@ use super::surrogate_process_manager::*;
3740
use super::windows_hypervisor_platform::{VMPartition, VMProcessor};
3841
use super::wrappers::{HandleWrapper, WHvFPURegisters};
3942
use super::{
40-
HyperlightExit, Hypervisor, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP,
41-
CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE,
43+
HyperlightExit, Hypervisor, InterruptHandle, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE,
44+
CR0_PE, CR0_PG, CR0_WP, CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX,
45+
EFER_SCE,
4246
};
4347
use crate::hypervisor::fpu::FP_CONTROL_WORD_DEFAULT;
4448
use crate::hypervisor::wrappers::WHvGeneralRegisters;
@@ -55,6 +59,7 @@ pub(crate) struct HypervWindowsDriver {
5559
entrypoint: u64,
5660
orig_rsp: GuestPtr,
5761
mem_regions: Vec<MemoryRegion>,
62+
interrupt_handle: Arc<WindowsInterruptHandle>,
5863
}
5964
/* This does not automatically impl Send/Sync because the host
6065
* address of the shared memory region is a raw pointer, which are
@@ -89,6 +94,7 @@ impl HypervWindowsDriver {
8994

9095
let mut proc = VMProcessor::new(partition)?;
9196
Self::setup_initial_sregs(&mut proc, pml4_address)?;
97+
let partition_handle = proc.get_partition_hdl();
9298

9399
// subtract 2 pages for the guard pages, since when we copy memory to and from surrogate process,
94100
// we don't want to copy the guard pages themselves (that would cause access violation)
@@ -101,6 +107,12 @@ impl HypervWindowsDriver {
101107
entrypoint,
102108
orig_rsp: GuestPtr::try_from(RawPtr::from(rsp))?,
103109
mem_regions,
110+
interrupt_handle: Arc::new(WindowsInterruptHandle {
111+
running: AtomicBool::new(false),
112+
cancel_requested: AtomicBool::new(false),
113+
partition_handle,
114+
dropped: AtomicBool::new(false),
115+
}),
104116
})
105117
}
106118

@@ -150,11 +162,6 @@ impl HypervWindowsDriver {
150162
error.push_str(&format!("Registers: \n{:#?}", self.processor.get_regs()?));
151163
Ok(error)
152164
}
153-
154-
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
155-
pub(crate) fn get_partition_hdl(&self) -> WHV_PARTITION_HANDLE {
156-
self.processor.get_partition_hdl()
157-
}
158165
}
159166

160167
impl Debug for HypervWindowsDriver {
@@ -402,7 +409,29 @@ impl Hypervisor for HypervWindowsDriver {
402409

403410
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
404411
fn run(&mut self) -> Result<super::HyperlightExit> {
405-
let exit_context: WHV_RUN_VP_EXIT_CONTEXT = self.processor.run()?;
412+
self.interrupt_handle.running.store(true, Ordering::Relaxed);
413+
414+
// Don't run the vcpu if `cancel_requested` is true
415+
let exit_context = if self
416+
.interrupt_handle
417+
.cancel_requested
418+
.load(Ordering::Relaxed)
419+
{
420+
WHV_RUN_VP_EXIT_CONTEXT {
421+
ExitReason: WHV_RUN_VP_EXIT_REASON(8193i32), // WHvRunVpExitReasonCanceled
422+
VpContext: Default::default(),
423+
Anonymous: Default::default(),
424+
Reserved: Default::default(),
425+
}
426+
} else {
427+
self.processor.run()?
428+
};
429+
self.interrupt_handle
430+
.cancel_requested
431+
.store(false, Ordering::Relaxed);
432+
self.interrupt_handle
433+
.running
434+
.store(false, Ordering::Relaxed);
406435

407436
let result = match exit_context.ExitReason {
408437
// WHvRunVpExitReasonX64IoPortAccess
@@ -476,8 +505,8 @@ impl Hypervisor for HypervWindowsDriver {
476505
Ok(result)
477506
}
478507

479-
fn get_partition_handle(&self) -> WHV_PARTITION_HANDLE {
480-
self.processor.get_partition_hdl()
508+
fn interrupt_handle(&self) -> Arc<dyn InterruptHandle> {
509+
self.interrupt_handle.clone()
481510
}
482511

483512
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
@@ -491,28 +520,28 @@ impl Hypervisor for HypervWindowsDriver {
491520
}
492521
}
493522

494-
#[cfg(test)]
495-
pub mod tests {
496-
use std::sync::{Arc, Mutex};
523+
impl Drop for HypervWindowsDriver {
524+
fn drop(&mut self) {
525+
self.interrupt_handle.dropped.store(true, Ordering::Relaxed);
526+
}
527+
}
497528

498-
use serial_test::serial;
529+
pub struct WindowsInterruptHandle {
530+
// `WHvCancelRunVirtualProcessor()` will return Ok even if the vcpu is not running, which is the reason we need this flag.
531+
running: AtomicBool,
532+
cancel_requested: AtomicBool,
533+
partition_handle: WHV_PARTITION_HANDLE,
534+
dropped: AtomicBool,
535+
}
499536

500-
use crate::hypervisor::handlers::{MemAccessHandler, OutBHandler};
501-
use crate::hypervisor::tests::test_initialise;
502-
use crate::Result;
537+
impl InterruptHandle for WindowsInterruptHandle {
538+
fn kill(&self) -> bool {
539+
self.cancel_requested.store(true, Ordering::Relaxed);
540+
self.running.load(Ordering::Relaxed)
541+
&& unsafe { WHvCancelRunVirtualProcessor(self.partition_handle, 0, 0).is_ok() }
542+
}
503543

504-
#[test]
505-
#[serial]
506-
fn test_init() {
507-
let outb_handler = {
508-
let func: Box<dyn FnMut(u16, u32) -> Result<()> + Send> =
509-
Box::new(|_, _| -> Result<()> { Ok(()) });
510-
Arc::new(Mutex::new(OutBHandler::from(func)))
511-
};
512-
let mem_access_handler = {
513-
let func: Box<dyn FnMut() -> Result<()> + Send> = Box::new(|| -> Result<()> { Ok(()) });
514-
Arc::new(Mutex::new(MemAccessHandler::from(func)))
515-
};
516-
test_initialise(outb_handler, mem_access_handler).unwrap();
544+
fn dropped(&self) -> bool {
545+
self.dropped.load(Ordering::Relaxed)
517546
}
518547
}

0 commit comments

Comments
 (0)