55// Use of this source code is governed by a BSD-style license that can be
66// found in the THIRD-PARTY file.
77
8+ use libc:: { c_int, c_void, siginfo_t} ;
9+ use std:: cell:: Cell ;
810use std:: io;
911use std:: result;
12+ use std:: sync:: atomic:: { fence, Ordering } ;
1013use std:: sync:: { Arc , Barrier } ;
1114
1215use super :: TimestampUs ;
@@ -23,6 +26,7 @@ use kvm_ioctls::*;
2326use logger:: { LogOption , Metric , LOGGER , METRICS } ;
2427use memory_model:: { Address , GuestAddress , GuestMemory , GuestMemoryError } ;
2528use utils:: eventfd:: EventFd ;
29+ use utils:: signal:: { register_signal_handler, SignalHandler } ;
2630#[ cfg( target_arch = "x86_64" ) ]
2731use vmm_config:: machine_config:: { CpuFeaturesTemplate , VmConfig } ;
2832
@@ -34,6 +38,8 @@ const MAGIC_IOPORT_SIGNAL_GUEST_BOOT_COMPLETE: u64 = 0x03f0;
3438const MAGIC_IOPORT_SIGNAL_GUEST_BOOT_COMPLETE : u64 = 0x40000000 ;
3539const MAGIC_VALUE_SIGNAL_GUEST_BOOT_COMPLETE : u8 = 123 ;
3640
41+ pub ( crate ) const VCPU_RTSIG_OFFSET : i32 = 0 ;
42+
3743/// Errors associated with the wrappers over KVM ioctls.
3844#[ derive( Debug ) ]
3945pub enum Error {
@@ -86,6 +92,10 @@ pub enum Error {
8692 Irq ( kvm_ioctls:: Error ) ,
8793 /// Cannot spawn a new vCPU thread.
8894 VcpuSpawn ( io:: Error ) ,
95+ /// Cannot clean init vcpu TLS.
96+ VcpuTlsInit ,
97+ /// Vcpu not present in TLS.
98+ VcpuTlsNotPresent ,
8999 /// Unexpected KVM_RUN exit reason
90100 VcpuUnhandledKvmExit ,
91101 #[ cfg( target_arch = "aarch64" ) ]
@@ -267,6 +277,9 @@ impl Vm {
267277 }
268278}
269279
280+ // Using this for easier explicit type-casting to help IDEs interpret the code.
281+ type VcpuCell = Cell < Option < * const Vcpu > > ;
282+
270283/// A wrapper around creating and using a kvm-based VCPU.
271284pub struct Vcpu {
272285 #[ cfg( target_arch = "x86_64" ) ]
@@ -282,6 +295,95 @@ pub struct Vcpu {
282295}
283296
284297impl Vcpu {
298+ thread_local ! ( static TLS_VCPU_PTR : VcpuCell = Cell :: new( None ) ) ;
299+
300+ /// Associates `self` with the current thread.
301+ ///
302+ /// It is a prerequisite to successfully run `init_thread_local_data()` before using
303+ /// `run_on_thread_local()` on the current thread.
304+ /// This function will return an error if there already is a `Vcpu` present in the TLS.
305+ fn init_thread_local_data ( & mut self ) -> Result < ( ) > {
306+ Self :: TLS_VCPU_PTR . with ( |cell : & VcpuCell | {
307+ if cell. get ( ) . is_some ( ) {
308+ return Err ( Error :: VcpuTlsInit ) ;
309+ }
310+ cell. set ( Some ( self as * const Vcpu ) ) ;
311+ Ok ( ( ) )
312+ } )
313+ }
314+
315+ /// Deassociates `self` from the current thread.
316+ ///
317+ /// Should be called if the current `self` had called `init_thread_local_data()` and
318+ /// now needs to move to a different thread.
319+ ///
320+ /// Fails if `self` was not previously associated with the current thread.
321+ fn reset_thread_local_data ( & mut self ) -> Result < ( ) > {
322+ // Best-effort to clean up TLS. If the `Vcpu` was moved to another thread
323+ // _before_ running this, then there is nothing we can do.
324+ Self :: TLS_VCPU_PTR . with ( |cell : & VcpuCell | {
325+ if let Some ( vcpu_ptr) = cell. get ( ) {
326+ if vcpu_ptr == self as * const Vcpu {
327+ Self :: TLS_VCPU_PTR . with ( |cell : & VcpuCell | cell. take ( ) ) ;
328+ return Ok ( ( ) ) ;
329+ }
330+ }
331+ Err ( Error :: VcpuTlsNotPresent )
332+ } )
333+ }
334+
335+ /// Runs `func` for the `Vcpu` associated with the current thread.
336+ ///
337+ /// It requires that `init_thread_local_data()` was run on this thread.
338+ ///
339+ /// Fails if there is no `Vcpu` associated with the current thread.
340+ ///
341+ /// # Safety
342+ ///
343+ /// This is marked unsafe as it allows temporary aliasing through
344+ /// dereferencing from pointer an already borrowed `Vcpu`.
345+ unsafe fn run_on_thread_local < F > ( func : F ) -> Result < ( ) >
346+ where
347+ F : FnOnce ( & Vcpu ) ,
348+ {
349+ Self :: TLS_VCPU_PTR . with ( |cell : & VcpuCell | {
350+ if let Some ( vcpu_ptr) = cell. get ( ) {
351+ // Dereferencing here is safe since `TLS_VCPU_PTR` is populated/non-empty,
352+ // and it is being cleared on `Vcpu::drop` so there is no dangling pointer.
353+ let vcpu_ref: & Vcpu = & * vcpu_ptr;
354+ func ( vcpu_ref) ;
355+ Ok ( ( ) )
356+ } else {
357+ Err ( Error :: VcpuTlsNotPresent )
358+ }
359+ } )
360+ }
361+
362+ /// Registers a signal handler which makes use of TLS and kvm immediate exit to
363+ /// kick the vcpu running on the current thread, if there is one.
364+ pub fn register_kick_signal_handler ( ) {
365+ extern "C" fn handle_signal ( _: c_int , _: * mut siginfo_t , _: * mut c_void ) {
366+ // This is safe because it's temporarily aliasing the `Vcpu` object, but we are
367+ // only reading `vcpu.fd` which does not change for the lifetime of the `Vcpu`.
368+ unsafe {
369+ let _ = Vcpu :: run_on_thread_local ( |vcpu| {
370+ vcpu. fd . set_kvm_immediate_exit ( 1 ) ;
371+ fence ( Ordering :: Release ) ;
372+ } ) ;
373+ }
374+ }
375+ unsafe {
376+ // This uses an async signal safe handler to kill the vcpu handles.
377+ register_signal_handler (
378+ VCPU_RTSIG_OFFSET ,
379+ SignalHandler :: Siginfo ( handle_signal) ,
380+ true ,
381+ libc:: SA_SIGINFO ,
382+ )
383+ . expect ( "Failed to register vcpu signal handler" ) ;
384+ }
385+ }
386+
285387 /// Constructs a new VCPU for `vm`.
286388 ///
287389 /// # Arguments
@@ -439,29 +541,32 @@ impl Vcpu {
439541 }
440542 }
441543
442- fn run_emulation ( & mut self ) -> Result < ( ) > {
544+ /// Runs the vCPU in KVM context and handles the kvm exit reason.
545+ ///
546+ /// Returns error or enum specifying whether emulation was handled or interrupted.
547+ fn run_emulation ( & mut self ) -> Result < VcpuEmulation > {
443548 match self . fd . run ( ) {
444549 Ok ( run) => match run {
445550 #[ cfg( target_arch = "x86_64" ) ]
446551 VcpuExit :: IoIn ( addr, data) => {
447552 self . io_bus . read ( u64:: from ( addr) , data) ;
448553 METRICS . vcpu . exit_io_in . inc ( ) ;
449- Ok ( ( ) )
554+ Ok ( VcpuEmulation :: Handled )
450555 }
451556 #[ cfg( target_arch = "x86_64" ) ]
452557 VcpuExit :: IoOut ( addr, data) => {
453558 self . check_boot_complete_signal ( u64:: from ( addr) , data) ;
454559
455560 self . io_bus . write ( u64:: from ( addr) , data) ;
456561 METRICS . vcpu . exit_io_out . inc ( ) ;
457- Ok ( ( ) )
562+ Ok ( VcpuEmulation :: Handled )
458563 }
459564 VcpuExit :: MmioRead ( addr, data) => {
460565 if let Some ( ref mmio_bus) = self . mmio_bus {
461566 mmio_bus. read ( addr, data) ;
462567 METRICS . vcpu . exit_mmio_read . inc ( ) ;
463568 }
464- Ok ( ( ) )
569+ Ok ( VcpuEmulation :: Handled )
465570 }
466571 VcpuExit :: MmioWrite ( addr, data) => {
467572 if let Some ( ref mmio_bus) = self . mmio_bus {
@@ -471,7 +576,7 @@ impl Vcpu {
471576 mmio_bus. write ( addr, data) ;
472577 METRICS . vcpu . exit_mmio_write . inc ( ) ;
473578 }
474- Ok ( ( ) )
579+ Ok ( VcpuEmulation :: Handled )
475580 }
476581 VcpuExit :: Hlt => {
477582 info ! ( "Received KVM_EXIT_HLT signal" ) ;
@@ -505,8 +610,12 @@ impl Vcpu {
505610 // error in our code in which case it is better to panic.
506611 Err ( ref e) => {
507612 match e. errno ( ) {
508- // Why do we check for these if we only return EINVAL?
509- libc:: EAGAIN | libc:: EINTR => Ok ( ( ) ) ,
613+ libc:: EAGAIN => Ok ( VcpuEmulation :: Handled ) ,
614+ libc:: EINTR => {
615+ self . fd . set_kvm_immediate_exit ( 0 ) ;
616+ // Notify that this KVM_RUN was interrupted.
617+ Ok ( VcpuEmulation :: Interrupted )
618+ }
510619 _ => {
511620 METRICS . vcpu . failures . inc ( ) ;
512621 error ! ( "Failure during vcpu run: {}" , e) ;
@@ -528,6 +637,9 @@ impl Vcpu {
528637 seccomp_level : u32 ,
529638 vcpu_exit_evt : EventFd ,
530639 ) {
640+ self . init_thread_local_data ( )
641+ . expect ( "Cannot cleanly initialize vcpu TLS." ) ;
642+
531643 // Load seccomp filters for this vCPU thread.
532644 // Execution panics if filters cannot be loaded, use --seccomp-level=0 if skipping filters
533645 // altogether is the desired behaviour.
@@ -550,12 +662,24 @@ impl Vcpu {
550662 }
551663}
552664
665+ impl Drop for Vcpu {
666+ fn drop ( & mut self ) {
667+ let _ = self . reset_thread_local_data ( ) ;
668+ }
669+ }
670+
671+ enum VcpuEmulation {
672+ Handled ,
673+ Interrupted ,
674+ }
675+
553676#[ cfg( test) ]
554677mod tests {
555678 use std:: fs:: File ;
556679
557680 use super :: super :: devices;
558681 use super :: * ;
682+ use utils:: signal:: Killable ;
559683
560684 // Auxiliary function being used throughout the tests.
561685 fn setup_vcpu ( ) -> ( Vm , Vcpu ) {
@@ -740,4 +864,85 @@ mod tests {
740864 assert_eq ! ( m1. dev( ) , m2. dev( ) ) ;
741865 assert_eq ! ( m1. ino( ) , m2. ino( ) ) ;
742866 }
867+
868+ #[ test]
869+ fn test_vcpu_tls ( ) {
870+ let ( _, mut vcpu) = setup_vcpu ( ) ;
871+
872+ // Running on the TLS vcpu should fail before we actually initialize it.
873+ unsafe {
874+ assert ! ( Vcpu :: run_on_thread_local( |_| ( ) ) . is_err( ) ) ;
875+ }
876+
877+ // Initialize vcpu TLS.
878+ vcpu. init_thread_local_data ( ) . unwrap ( ) ;
879+
880+ // Validate TLS vcpu is the local vcpu by changing the `id` then validating against
881+ // the one in TLS.
882+ vcpu. id = 12 ;
883+ unsafe {
884+ assert ! ( Vcpu :: run_on_thread_local( |v| assert_eq!( v. id, 12 ) ) . is_ok( ) ) ;
885+ }
886+
887+ // Reset vcpu TLS.
888+ assert ! ( vcpu. reset_thread_local_data( ) . is_ok( ) ) ;
889+
890+ // Running on the TLS vcpu after TLS reset should fail.
891+ unsafe {
892+ assert ! ( Vcpu :: run_on_thread_local( |_| ( ) ) . is_err( ) ) ;
893+ }
894+
895+ // Second reset should return error.
896+ assert ! ( vcpu. reset_thread_local_data( ) . is_err( ) ) ;
897+ }
898+
899+ #[ test]
900+ fn test_invalid_tls ( ) {
901+ let ( _, mut vcpu) = setup_vcpu ( ) ;
902+ // Initialize vcpu TLS.
903+ vcpu. init_thread_local_data ( ) . unwrap ( ) ;
904+ // Trying to initialize non-empty TLS should error.
905+ vcpu. init_thread_local_data ( ) . unwrap_err ( ) ;
906+ }
907+
908+ #[ test]
909+ fn test_vcpu_kick ( ) {
910+ Vcpu :: register_kick_signal_handler ( ) ;
911+ let ( vm, mut vcpu) = setup_vcpu ( ) ;
912+
913+ let kvm_run =
914+ KvmRunWrapper :: mmap_from_fd ( & vcpu. fd , vm. fd . run_size ( ) ) . expect ( "cannot mmap kvm-run" ) ;
915+ let success = Arc :: new ( std:: sync:: atomic:: AtomicBool :: new ( false ) ) ;
916+ let vcpu_success = success. clone ( ) ;
917+ let barrier = Arc :: new ( Barrier :: new ( 2 ) ) ;
918+ let vcpu_barrier = barrier. clone ( ) ;
919+ // Start Vcpu thread which will be kicked with a signal.
920+ let handle = std:: thread:: Builder :: new ( )
921+ . name ( "test_vcpu_kick" . to_string ( ) )
922+ . spawn ( move || {
923+ vcpu. init_thread_local_data ( ) . unwrap ( ) ;
924+ // Notify TLS was populated.
925+ vcpu_barrier. wait ( ) ;
926+ // Loop for max 1 second to check if the signal handler has run.
927+ for _ in 0 ..10 {
928+ if kvm_run. as_mut_ref ( ) . immediate_exit == 1 {
929+ // Signal handler has run and set immediate_exit to 1.
930+ vcpu_success. store ( true , Ordering :: Release ) ;
931+ break ;
932+ }
933+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 100 ) ) ;
934+ }
935+ } )
936+ . expect ( "cannot start thread" ) ;
937+
938+ // Wait for the vcpu to initialize its TLS.
939+ barrier. wait ( ) ;
940+ // Kick the Vcpu using the custom signal.
941+ handle
942+ . kill ( VCPU_RTSIG_OFFSET )
943+ . expect ( "failed to signal thread" ) ;
944+ handle. join ( ) . expect ( "failed to join thread" ) ;
945+ // Verify that the Vcpu saw its kvm immediate-exit as set.
946+ assert ! ( success. load( Ordering :: Acquire ) ) ;
947+ }
743948}
0 commit comments