summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatt Delco <delco@chromium.org>2019-09-19 10:30:41 -0700
committerCommit Bot <commit-bot@chromium.org>2019-10-31 06:18:09 +0000
commitac0b9b71d142f381d39162a1ac52c7d143700a1b (patch)
treee9a6cabc3feae921b0514354fc3e86dbb778c853
parent5bff67d485f22fcbd391231dad1666cc849deb36 (diff)
downloadcrosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.tar
crosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.tar.gz
crosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.tar.bz2
crosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.tar.lz
crosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.tar.xz
crosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.tar.zst
crosvm-ac0b9b71d142f381d39162a1ac52c7d143700a1b.zip
crosvm: pre-cache answers to plugin get calls
This change tries to improve the performance of a plugin-based VM
by adding a hint API that allows crosvm to proactively push cpu
state to the plugin when certain ports for hypercalls are accessed
by the VM.

BUG=None
TEST=build and run. See performance increase significantly.

Change-Id: I71af24ebc034095ffea42eedb9ffda0afc719cd6
Signed-off-by: Matt Delco <delco@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1873005
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
-rw-r--r--crosvm_plugin/crosvm.h75
-rw-r--r--crosvm_plugin/src/lib.rs160
-rw-r--r--protos/src/plugin.proto29
-rw-r--r--src/plugin/process.rs32
-rw-r--r--src/plugin/vcpu.rs87
-rw-r--r--tests/plugin.policy2
-rw-r--r--tests/plugin_hint.c313
-rw-r--r--tests/plugins.rs5
8 files changed, 692 insertions, 11 deletions
diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h
index d7a036c..700ab0c 100644
--- a/crosvm_plugin/crosvm.h
+++ b/crosvm_plugin/crosvm.h
@@ -47,7 +47,7 @@ extern "C" {
  * do not indicate anything about what version of crosvm is running.
  */
 #define CROSVM_API_MAJOR 0
-#define CROSVM_API_MINOR 17
+#define CROSVM_API_MINOR 18
 #define CROSVM_API_PATCH 0
 
 enum crosvm_address_space {
@@ -229,6 +229,79 @@ static_assert(sizeof(struct crosvm_irq_route) == 24,
 int crosvm_set_irq_routing(struct crosvm*, uint32_t __route_count,
                            const struct crosvm_irq_route* __routes);
 
+/* Hint on what information is queried for a particular hypercall. */
+struct crosvm_hint_detail {
+  bool match_rax;
+  bool match_rbx;
+  bool match_rcx;
+  bool match_rdx;
+  uint8_t _reserved[4];
+  uint64_t rax;
+  uint64_t rbx;
+  uint64_t rcx;
+  uint64_t rdx;
+  bool send_sregs;
+  bool send_debugregs;
+  uint8_t _reserved2[6];
+};
+
+#ifdef static_assert
+static_assert(sizeof(struct crosvm_hint_detail) == 48,
+              "extra padding in struct crosvm_hint_detail");
+#endif
+
+/* Maximum # of hints that can be passed to crosvm_set_hypercall_hint(). */
+#define CROSVM_MAX_HINT_COUNT 1
+
+/* Maximum # of hint details that can be provided for a hint. */
+#define CROSVM_MAX_HINT_DETAIL_COUNT 32
+
+#define CROSVM_HINT_ON_WRITE 0x1
+
+/* Hint on what information is queried for a particular hypercall. */
+struct crosvm_hint {
+  uint32_t hint_version;  /* For now only 0 is defined. */
+  uint32_t _reserved;     /* Must be zero. */
+  uint32_t address_space; /* Value from crosvm_address_space. */
+  uint16_t address_flags; /* 0: read/in; CROSVM_HINT_ON_WRITE: write/out. */
+  uint16_t details_count; /* # of elements in |details|. */
+  uint64_t address;
+  union {
+    struct crosvm_hint_detail *details;
+    uint64_t _reserved2; /* forcing pointer length to 64-bit */
+  };
+};
+
+#ifdef static_assert
+static_assert(sizeof(struct crosvm_hint) == 32,
+              "extra padding in struct crosvm_hint");
+#endif
+
+/*
+ * Sets performance hint(s) for a hypercall port.
+ *
+ * If a VM does an io access the specified |address_space|, |address|
+ * (|address| must be non-zero), and direction (|address_flags|), then
+ * crosvm will assume the plugin is likely to call crosvm_vcpu_get_regs()
+ * (and thus utilize a cache to improve performance).
+ *
+ * Additional hints can be provided via |details| (the element length of
+ * |details| is limited to CROSVM_MAX_HINT_DETAIL_COUNT) on when to also cache
+ * information for crosvm_vcpu_get_sregs() and crosvm_vcpu_get_debugregs()
+ * based on values in the vcpu registers.  |match_XXX| indicates which of
+ * 1 or more of |XXX| needs to be equal to the vcpu registers to be a match.
+ * On a match |send_sregs| and |send_debugregs| are used to determine what
+ * data to proactively cache for the plugin's use.  Once a match is found
+ * the remaining hints are not consulted.
+ *
+ * To remove all hints, pass 0 for |__hint_count|.  The value of
+ * |__hint_count| can be at most CROSVM_MAX_HINT_COUNT.  Currently the API
+ * is limited to 1 hint (i.e., |__hint_count| must be 0 or 1).  Each call
+ * to this API will replace the values specified by any prior call to this API.
+ */
+int crosvm_set_hypercall_hint(struct crosvm *, uint32_t __hints_count,
+                              const struct crosvm_hint* __hints);
+
 /* Gets the state of interrupt controller in a VM. */
 int crosvm_get_pic_state(struct crosvm *, bool __primary,
                          struct kvm_pic_state *__pic_state);
diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs
index 1eb22b5..fa6ce2f 100644
--- a/crosvm_plugin/src/lib.rs
+++ b/crosvm_plugin/src/lib.rs
@@ -96,6 +96,37 @@ pub struct crosvm_irq_route {
     route: anon_route,
 }
 
+const CROSVM_MAX_HINT_COUNT: u32 = 1;
+const CROSVM_MAX_HINT_DETAIL_COUNT: u32 = 32;
+const CROSVM_HINT_ON_WRITE: u16 = 1;
+
+#[repr(C)]
+pub struct crosvm_hint {
+    hint_version: u32,
+    reserved: u32,
+    address_space: u32,
+    address_flags: u16,
+    details_count: u16,
+    address: u64,
+    details: *const crosvm_hint_detail,
+}
+
+#[repr(C)]
+pub struct crosvm_hint_detail {
+    match_rax: bool,
+    match_rbx: bool,
+    match_rcx: bool,
+    match_rdx: bool,
+    reserved1: [u8; 4],
+    rax: u64,
+    rbx: u64,
+    rcx: u64,
+    rdx: u64,
+    send_sregs: bool,
+    send_debugregs: bool,
+    reserved2: [u8; 6],
+}
+
 fn proto_error_to_int(e: protobuf::ProtobufError) -> c_int {
     match e {
         protobuf::ProtobufError::IoError(e) => e.raw_os_error().unwrap_or(EINVAL),
@@ -174,6 +205,7 @@ enum Stat {
     VcpuGetVcpuEvents,
     VcpuSetVcpuEvents,
     NewConnection,
+    SetHypercallHint,
 
     Count,
 }
@@ -543,6 +575,38 @@ impl crosvm {
         Ok(())
     }
 
+    fn set_hint(
+        &mut self,
+        space: u32,
+        addr: u64,
+        on_write: bool,
+        hints: &[crosvm_hint_detail],
+    ) -> result::Result<(), c_int> {
+        let mut r = MainRequest::new();
+        let req: &mut MainRequest_SetCallHint = r.mut_set_call_hint();
+        let set_hints: &mut RepeatedField<MainRequest_SetCallHint_RegHint> = req.mut_hints();
+        for hint in hints {
+            let mut entry = MainRequest_SetCallHint_RegHint::new();
+            entry.match_rax = hint.match_rax;
+            entry.match_rbx = hint.match_rbx;
+            entry.match_rcx = hint.match_rcx;
+            entry.match_rdx = hint.match_rdx;
+            entry.rax = hint.rax;
+            entry.rbx = hint.rbx;
+            entry.rcx = hint.rcx;
+            entry.rdx = hint.rdx;
+            entry.send_sregs = hint.send_sregs;
+            entry.send_debugregs = hint.send_debugregs;
+            set_hints.push(entry);
+        }
+        req.space = AddressSpace::from_i32(space as i32).ok_or(EINVAL)?;
+        req.address = addr;
+        req.on_write = on_write;
+
+        self.main_transaction(&r, &[])?;
+        Ok(())
+    }
+
     fn get_state(
         &mut self,
         state_set: MainRequest_StateSet,
@@ -922,7 +986,17 @@ pub struct crosvm_vcpu_event {
     event: anon_vcpu_event,
 }
 
+// |get| tracks if the |cache| contains a cached value that can service get()
+// requests.  A set() call will populate |cache| and |set| to true to record
+// that the next resume() should apply the state.  We've got two choices on
+// what to do about |get| on a set(): 1) leave it as true, or 2) clear it and
+// have any call to get() first apply any pending set.  Currently #2 is used
+// to favor correctness over performance (it gives KVM a chance to
+// modify/massage the values input to the set call). A plugin will rarely
+// (if ever) issue a get() after a set() on the same vcpu exit, so opting for
+// #1 is unlikely to provide a tangible performance gain.
 pub struct crosvm_vcpu_reg_cache {
+    get: bool,
     set: bool,
     cache: Vec<u8>,
 }
@@ -950,14 +1024,17 @@ impl crosvm_vcpu {
             response_buffer: vec![0; MAX_DATAGRAM_SIZE],
             resume_data: Vec::new(),
             regs: crosvm_vcpu_reg_cache {
+                get: false,
                 set: false,
                 cache: vec![],
             },
             sregs: crosvm_vcpu_reg_cache {
+                get: false,
                 set: false,
                 cache: vec![],
             },
             debugregs: crosvm_vcpu_reg_cache {
+                get: false,
                 set: false,
                 cache: vec![],
             },
@@ -1008,6 +1085,9 @@ impl crosvm_vcpu {
         let wait: &mut VcpuResponse_Wait = response.mut_wait();
         if wait.has_init() {
             event.kind = CROSVM_VCPU_EVENT_KIND_INIT;
+            self.regs.get = false;
+            self.sregs.get = false;
+            self.debugregs.get = false;
             Ok(())
         } else if wait.has_io() {
             let mut io: VcpuResponse_Wait_Io = wait.take_io();
@@ -1022,11 +1102,26 @@ impl crosvm_vcpu {
                 __reserved1: Default::default(),
             };
             self.resume_data = io.data;
+            self.regs.get = !io.regs.is_empty();
+            if self.regs.get {
+                swap(&mut self.regs.cache, &mut io.regs);
+            }
+            self.sregs.get = !io.sregs.is_empty();
+            if self.sregs.get {
+                swap(&mut self.sregs.cache, &mut io.sregs);
+            }
+            self.debugregs.get = !io.debugregs.is_empty();
+            if self.debugregs.get {
+                swap(&mut self.debugregs.cache, &mut io.debugregs);
+            }
             Ok(())
         } else if wait.has_user() {
             let user: &VcpuResponse_Wait_User = wait.get_user();
             event.kind = CROSVM_VCPU_EVENT_KIND_PAUSED;
             event.event.user = user.user as *mut c_void;
+            self.regs.get = false;
+            self.sregs.get = false;
+            self.debugregs.get = false;
             Ok(())
         } else {
             Err(EPROTO)
@@ -1347,6 +1442,41 @@ pub unsafe extern "C" fn crosvm_set_irq_routing(
 }
 
 #[no_mangle]
+pub unsafe extern "C" fn crosvm_set_hypercall_hint(
+    self_: *mut crosvm,
+    hints_count: u32,
+    hints: *const crosvm_hint,
+) -> c_int {
+    let _u = STATS.record(Stat::SetHypercallHint);
+    let self_ = &mut (*self_);
+
+    if hints_count < 1 {
+        let ret = self_.set_hint(0, 0, false, &[]);
+        return to_crosvm_rc(ret);
+    }
+    if hints_count > CROSVM_MAX_HINT_COUNT {
+        return -EINVAL;
+    }
+    let hints = slice::from_raw_parts(hints, hints_count as usize);
+    let hint = &hints[0];
+    if hint.hint_version != 0
+        || hint.reserved != 0
+        || hint.address == 0
+        || (hint.address_flags != 0 && hint.address_flags != CROSVM_HINT_ON_WRITE)
+        || hint.details_count > CROSVM_MAX_HINT_DETAIL_COUNT as u16
+    {
+        return -EINVAL;
+    }
+    let ret = self_.set_hint(
+        hint.address_space,
+        hint.address,
+        hint.address_flags == CROSVM_HINT_ON_WRITE,
+        slice::from_raw_parts(hint.details, hint.details_count as usize),
+    );
+    return to_crosvm_rc(ret);
+}
+
+#[no_mangle]
 pub unsafe extern "C" fn crosvm_get_pic_state(
     this: *mut crosvm,
     primary: bool,
@@ -1531,8 +1661,13 @@ pub unsafe extern "C" fn crosvm_vcpu_get_regs(
         }
     }
     let regs = from_raw_parts_mut(regs as *mut u8, size_of::<kvm_regs>());
-    let ret = this.get_state(VcpuRequest_StateSet::REGS, regs);
-    to_crosvm_rc(ret)
+    if this.regs.get {
+        regs.copy_from_slice(&this.regs.cache);
+        0
+    } else {
+        let ret = this.get_state(VcpuRequest_StateSet::REGS, regs);
+        to_crosvm_rc(ret)
+    }
 }
 
 #[no_mangle]
@@ -1542,6 +1677,7 @@ pub unsafe extern "C" fn crosvm_vcpu_set_regs(
 ) -> c_int {
     let _u = STATS.record(Stat::VcpuSetRegs);
     let this = &mut *this;
+    this.regs.get = false;
     let regs = from_raw_parts(regs as *mut u8, size_of::<kvm_regs>());
     this.regs.set = true;
     this.regs.cache = regs.to_vec();
@@ -1561,8 +1697,13 @@ pub unsafe extern "C" fn crosvm_vcpu_get_sregs(
         }
     }
     let sregs = from_raw_parts_mut(sregs as *mut u8, size_of::<kvm_sregs>());
-    let ret = this.get_state(VcpuRequest_StateSet::SREGS, sregs);
-    to_crosvm_rc(ret)
+    if this.sregs.get {
+        sregs.copy_from_slice(&this.sregs.cache);
+        0
+    } else {
+        let ret = this.get_state(VcpuRequest_StateSet::SREGS, sregs);
+        to_crosvm_rc(ret)
+    }
 }
 
 #[no_mangle]
@@ -1572,6 +1713,7 @@ pub unsafe extern "C" fn crosvm_vcpu_set_sregs(
 ) -> c_int {
     let _u = STATS.record(Stat::VcpuSetSregs);
     let this = &mut *this;
+    this.sregs.get = false;
     let sregs = from_raw_parts(sregs as *mut u8, size_of::<kvm_sregs>());
     this.sregs.set = true;
     this.sregs.cache = sregs.to_vec();
@@ -1609,8 +1751,13 @@ pub unsafe extern "C" fn crosvm_vcpu_get_debugregs(
         }
     }
     let dregs = from_raw_parts_mut(dregs as *mut u8, size_of::<kvm_debugregs>());
-    let ret = this.get_state(VcpuRequest_StateSet::DEBUGREGS, dregs);
-    to_crosvm_rc(ret)
+    if this.debugregs.get {
+        dregs.copy_from_slice(&this.debugregs.cache);
+        0
+    } else {
+        let ret = this.get_state(VcpuRequest_StateSet::DEBUGREGS, dregs);
+        to_crosvm_rc(ret)
+    }
 }
 
 #[no_mangle]
@@ -1620,6 +1767,7 @@ pub unsafe extern "C" fn crosvm_vcpu_set_debugregs(
 ) -> c_int {
     let _u = STATS.record(Stat::SetDebugRegs);
     let this = &mut *this;
+    this.debugregs.get = false;
     let dregs = from_raw_parts(dregs as *mut u8, size_of::<kvm_debugregs>());
     this.debugregs.set = true;
     this.debugregs.cache = dregs.to_vec();
diff --git a/protos/src/plugin.proto b/protos/src/plugin.proto
index b000bf4..7b0600d 100644
--- a/protos/src/plugin.proto
+++ b/protos/src/plugin.proto
@@ -151,6 +151,27 @@ message MainRequest {
     message GetVcpus {}
     message Start {}
 
+    message SetCallHint {
+        message RegHint {
+	    bool match_rax = 1;
+	    bool match_rbx = 2;
+	    bool match_rcx = 3;
+	    bool match_rdx = 4;
+	    uint64 rax = 5;
+	    uint64 rbx = 6;
+	    uint64 rcx = 7;
+	    uint64 rdx = 8;
+	    bool send_sregs = 9;
+	    bool send_debugregs = 10;
+	}
+
+        AddressSpace space = 1;
+        uint64 address = 2;
+        bool on_write = 3;
+
+        repeated RegHint hints = 4;
+    }
+
     message MemoryDirtyLog {
         uint32 id = 1;
     }
@@ -178,6 +199,7 @@ message MainRequest {
         PauseVcpus pause_vcpus = 16;
         GetVcpus get_vcpus = 17;
         Start start = 18;
+        SetCallHint set_call_hint = 19;
         // Method for a Memory type object for retrieving the dirty bitmap. Only valid if the memory
         // object was created with dirty_log set.
         MemoryDirtyLog dirty_log = 101;
@@ -225,6 +247,7 @@ message MainResponse {
     // socket. The VcpuRequest/VcpuResponse protocol is run over each of the returned fds.
     message GetVcpus {}
     message Start {}
+    message SetCallHint {}
     message MemoryDirtyLog {
         bytes bitmap = 1;
     }
@@ -252,6 +275,7 @@ message MainResponse {
         PauseVcpus pause_vcpus = 17;
         GetVcpus get_vcpus = 18;
         Start start = 19;
+        SetCallHint set_call_hint = 20;
         MemoryDirtyLog dirty_log = 101;
     }
 }
@@ -361,6 +385,11 @@ message VcpuResponse  {
             uint64 address = 2;
             bool is_write = 3;
             bytes data = 4;
+
+            // The following can be eagerly provided.
+            bytes regs = 5;
+            bytes sregs = 6;
+            bytes debugregs = 7;
         }
 
         // This type of wait reason is only generated after a PuaseVcpus request on this VCPU.
diff --git a/src/plugin/process.rs b/src/plugin/process.rs
index 50b4465..2869847 100644
--- a/src/plugin/process.rs
+++ b/src/plugin/process.rs
@@ -418,6 +418,35 @@ impl Process {
         vm.set_gsi_routing(&routes[..])
     }
 
+    fn handle_set_call_hint(&mut self, hints: &MainRequest_SetCallHint) -> SysResult<()> {
+        let mut regs: Vec<CallHintDetails> = vec![];
+        for hint in &hints.hints {
+            regs.push(CallHintDetails {
+                match_rax: hint.match_rax,
+                match_rbx: hint.match_rbx,
+                match_rcx: hint.match_rcx,
+                match_rdx: hint.match_rdx,
+                rax: hint.rax,
+                rbx: hint.rbx,
+                rcx: hint.rcx,
+                rdx: hint.rdx,
+                send_sregs: hint.send_sregs,
+                send_debugregs: hint.send_debugregs,
+            });
+        }
+        match self.shared_vcpu_state.write() {
+            Ok(mut lock) => {
+                let space = match hints.space {
+                    AddressSpace::IOPORT => IoSpace::Ioport,
+                    AddressSpace::MMIO => IoSpace::Mmio,
+                };
+                lock.set_hint(space, hints.address, hints.on_write, regs);
+                Ok(())
+            }
+            Err(_) => Err(SysError::new(EDEADLK)),
+        }
+    }
+
     fn handle_pause_vcpus(&self, vcpu_handles: &[JoinHandle<()>], cpu_mask: u64, user_data: u64) {
         for (cpu_id, (handle, per_cpu_state)) in
             vcpu_handles.iter().zip(&self.per_vcpu_states).enumerate()
@@ -631,6 +660,9 @@ impl Process {
                 }
                 None => Err(SysError::new(ENODATA)),
             }
+        } else if request.has_set_call_hint() {
+            response.mut_set_call_hint();
+            self.handle_set_call_hint(request.get_set_call_hint())
         } else if request.has_dirty_log() {
             let dirty_log_response = response.mut_dirty_log();
             match self.objects.get(&request.get_dirty_log().id) {
diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs
index 05970f7..03d63b4 100644
--- a/src/plugin/vcpu.rs
+++ b/src/plugin/vcpu.rs
@@ -30,7 +30,7 @@ use sys_util::{error, LayoutAllocation};
 use super::*;
 
 /// Identifier for an address space in the VM.
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, PartialEq)]
 pub enum IoSpace {
     Ioport,
     Mmio,
@@ -137,11 +137,32 @@ fn set_vcpu_state(vcpu: &Vcpu, state_set: VcpuRequest_StateSet, state: &[u8]) ->
     }
 }
 
+pub struct CallHintDetails {
+    pub match_rax: bool,
+    pub match_rbx: bool,
+    pub match_rcx: bool,
+    pub match_rdx: bool,
+    pub rax: u64,
+    pub rbx: u64,
+    pub rcx: u64,
+    pub rdx: u64,
+    pub send_sregs: bool,
+    pub send_debugregs: bool,
+}
+
+pub struct CallHint {
+    io_space: IoSpace,
+    addr: u64,
+    on_write: bool,
+    regs: Vec<CallHintDetails>,
+}
+
 /// State shared by every VCPU, grouped together to make edits to the state coherent across VCPUs.
 #[derive(Default)]
 pub struct SharedVcpuState {
     ioport_regions: BTreeSet<Range>,
     mmio_regions: BTreeSet<Range>,
+    hint: Option<CallHint>,
 }
 
 impl SharedVcpuState {
@@ -191,6 +212,26 @@ impl SharedVcpuState {
         }
     }
 
+    pub fn set_hint(
+        &mut self,
+        space: IoSpace,
+        addr: u64,
+        on_write: bool,
+        regs: Vec<CallHintDetails>,
+    ) {
+        if addr == 0 {
+            self.hint = None;
+        } else {
+            let hint = CallHint {
+                io_space: space,
+                addr,
+                on_write,
+                regs,
+            };
+            self.hint = Some(hint);
+        }
+    }
+
     fn is_reserved(&self, space: IoSpace, addr: u64) -> bool {
         if let Some(Range(start, len)) = self.first_before(space, addr) {
             let offset = addr - start;
@@ -212,6 +253,28 @@ impl SharedVcpuState {
             None => None,
         }
     }
+
+    fn matches_hint(&self, io_space: IoSpace, addr: u64, is_write: bool) -> bool {
+        if let Some(hint) = &self.hint {
+            return io_space == hint.io_space && addr == hint.addr && is_write == hint.on_write;
+        }
+        false
+    }
+
+    fn check_hint_details(&self, regs: &kvm_regs) -> (bool, bool) {
+        if let Some(hint) = &self.hint {
+            for entry in hint.regs.iter() {
+                if (!entry.match_rax || entry.rax == regs.rax)
+                    && (!entry.match_rbx || entry.rbx == regs.rbx)
+                    && (!entry.match_rcx || entry.rcx == regs.rcx)
+                    && (!entry.match_rdx || entry.rdx == regs.rdx)
+                {
+                    return (entry.send_sregs, entry.send_debugregs);
+                }
+            }
+        }
+        (false, false)
+    }
 }
 
 /// State specific to a VCPU, grouped so that each `PluginVcpu` object will share a canonical
@@ -338,9 +401,6 @@ impl PluginVcpu {
         };
 
         let first_before_addr = vcpu_state_lock.first_before(io_space, addr);
-        // Drops the read lock as soon as possible, to prevent holding lock while blocked in
-        // `handle_until_resume`.
-        drop(vcpu_state_lock);
 
         match first_before_addr {
             Some(Range(start, len)) => {
@@ -358,6 +418,25 @@ impl PluginVcpu {
                 io.address = addr;
                 io.is_write = data.is_write();
                 io.data = data.as_slice().to_vec();
+                if vcpu_state_lock.matches_hint(io_space, addr, io.is_write) {
+                    if let Ok(regs) = vcpu.get_regs() {
+                        let (has_sregs, has_debugregs) = vcpu_state_lock.check_hint_details(&regs);
+                        io.regs = VcpuRegs(regs).as_slice().to_vec();
+                        if has_sregs {
+                            if let Ok(state) = get_vcpu_state(vcpu, VcpuRequest_StateSet::SREGS) {
+                                io.sregs = state;
+                            }
+                        }
+                        if has_debugregs {
+                            if let Ok(state) = get_vcpu_state(vcpu, VcpuRequest_StateSet::DEBUGREGS)
+                            {
+                                io.debugregs = state;
+                            }
+                        }
+                    }
+                }
+                // don't hold lock while blocked in `handle_until_resume`.
+                drop(vcpu_state_lock);
 
                 self.wait_reason.set(Some(wait_reason));
                 match self.handle_until_resume(vcpu) {
diff --git a/tests/plugin.policy b/tests/plugin.policy
index a3ab2f7..19e7102 100644
--- a/tests/plugin.policy
+++ b/tests/plugin.policy
@@ -8,6 +8,7 @@ dup2: 1
 execve: 1
 exit_group: 1
 futex: 1
+kill: 1
 lseek: 1
 mprotect: 1
 munmap: 1
@@ -22,6 +23,7 @@ write: 1
 eventfd2: 1
 poll: 1
 getpid: 1
+getppid: 1
 # Allow PR_SET_NAME only.
 prctl: arg0 == 15
 access: 1
diff --git a/tests/plugin_hint.c b/tests/plugin_hint.c
new file mode 100644
index 0000000..f8d6310
--- /dev/null
+++ b/tests/plugin_hint.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2019 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/memfd.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "crosvm.h"
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+
+#ifndef F_ADD_SEALS
+#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
+#endif
+
+#ifndef F_SEAL_SHRINK
+#define F_SEAL_SHRINK 0x0002
+#endif
+
+#define KILL_ADDRESS   0x3f9
+#define HINT_ADDRESS   0x500
+#define EAX_HINT_VALUE 0x77
+
+int g_kill_evt;
+int got_regs = 0;
+
+void *vcpu_thread(void *arg) {
+    struct crosvm_vcpu *vcpu = arg;
+    struct crosvm_vcpu_event evt;
+    while (crosvm_vcpu_wait(vcpu, &evt) == 0) {
+        if (evt.kind == CROSVM_VCPU_EVENT_KIND_INIT) {
+            struct kvm_sregs sregs;
+            crosvm_vcpu_get_sregs(vcpu, &sregs);
+            sregs.cs.base = 0;
+            sregs.cs.selector = 0;
+            sregs.es.base = KILL_ADDRESS;
+            sregs.es.selector = 0;
+            crosvm_vcpu_set_sregs(vcpu, &sregs);
+
+            struct kvm_regs regs;
+            crosvm_vcpu_get_regs(vcpu, &regs);
+            regs.rip = 0x1000;
+            regs.rax = 2;
+            regs.rbx = 7;
+            regs.rflags = 2;
+            crosvm_vcpu_set_regs(vcpu, &regs);
+        }
+        if (evt.kind == CROSVM_VCPU_EVENT_KIND_IO_ACCESS) {
+            if (evt.io_access.address_space == CROSVM_ADDRESS_SPACE_IOPORT &&
+                evt.io_access.address == HINT_ADDRESS &&
+                evt.io_access.is_write &&
+                evt.io_access.length == 1) {
+              struct kvm_regs regs = {0};
+              struct kvm_sregs sregs = {0};
+              struct kvm_debugregs debugregs = {0};
+
+              /*
+               * In a properly running test the following
+               * get and set calls will return success despite
+               * crosvm being halted.
+               */
+              if (kill(getppid(), SIGSTOP)) {
+                fprintf(stderr, "failed to send stop to crosvm\n");
+                exit(1);
+              }
+
+              printf("get regs query on crosvm\n");
+              if (crosvm_vcpu_get_regs(vcpu, &regs)) {
+                /*
+                 * The failure mode for this test is that crosvm remains
+                 * halted (since the plugin hasn't returned from
+                 * crosvm_vcpu_[g|s]et_regs() to resume crosvm) and
+                 * the test times out.
+                 */
+                fprintf(stderr, "failed to query regs on hint port\n");
+                exit(1);
+              }
+
+              printf("set regs query on crosvm\n");
+              if (crosvm_vcpu_set_regs(vcpu, &regs)) {
+                fprintf(stderr, "failed to set regs on hint port\n");
+                exit(1);
+              }
+
+              printf("get sregs query on crosvm\n");
+              if (crosvm_vcpu_get_sregs(vcpu, &sregs)) {
+                fprintf(stderr, "failed to query sregs on hint port\n");
+                exit(1);
+              }
+              printf("set sregs query on crosvm\n");
+              if (crosvm_vcpu_set_sregs(vcpu, &sregs)) {
+                fprintf(stderr, "failed to set sregs on hint port\n");
+                exit(1);
+              }
+
+              printf("get debugregs query on crosvm\n");
+              if (crosvm_vcpu_get_debugregs(vcpu, &debugregs)) {
+                fprintf(stderr, "failed to query debugregs on hint port\n");
+                exit(1);
+              }
+              printf("set debugregs query on crosvm\n");
+              if (crosvm_vcpu_set_debugregs(vcpu, &debugregs)) {
+                fprintf(stderr, "failed to set debugregs on hint port\n");
+                exit(1);
+              }
+
+              got_regs = 1;
+
+              if (kill(getppid(), SIGCONT)) {
+                fprintf(stderr, "failed to send continue to crosvm\n");
+                exit(1);
+              }
+            }
+            if (evt.io_access.address_space == CROSVM_ADDRESS_SPACE_IOPORT &&
+                evt.io_access.address == KILL_ADDRESS &&
+                evt.io_access.is_write &&
+                evt.io_access.length == 1 &&
+                evt.io_access.data[0] == 1)
+            {
+                uint64_t dummy = 1;
+                write(g_kill_evt, &dummy, sizeof(dummy));
+                return NULL;
+            }
+        }
+
+        crosvm_vcpu_resume(vcpu);
+    }
+
+    return NULL;
+}
+
+int main(int argc, char** argv) {
+    const uint8_t code[] = {
+    /*
+    B007    mov al,0x7
+    BA0005  mov dx,0x500
+    EE      out dx,al
+    BAF903  mov dx,0x3f9
+    B001    mov al,0x1
+    EE      out dx,al
+    F4      hlt
+    */
+        0xb0, EAX_HINT_VALUE,
+        0xba, (HINT_ADDRESS & 0xFF), ((HINT_ADDRESS >> 8) & 0xFF),
+        0xee,
+        0xba, (KILL_ADDRESS & 0xFF), ((KILL_ADDRESS >> 8) & 0xFF),
+        0xb0, 0x01,
+        0xee,
+        0xf4
+    };
+
+    struct crosvm *crosvm;
+    int ret = crosvm_connect(&crosvm);
+    if (ret) {
+        fprintf(stderr, "failed to connect to crosvm: %d\n", ret);
+        return 1;
+    }
+
+    /*
+     * Not strictly necessary, but demonstrates we can have as many connections
+     * as we please.
+     */
+    struct crosvm *extra_crosvm;
+    ret = crosvm_new_connection(crosvm, &extra_crosvm);
+    if (ret) {
+        fprintf(stderr, "failed to make new socket: %d\n", ret);
+        return 1;
+    }
+
+    /* We needs this eventfd to know when to exit before being killed. */
+    g_kill_evt = crosvm_get_shutdown_eventfd(crosvm);
+    if (g_kill_evt < 0) {
+        fprintf(stderr, "failed to get kill eventfd: %d\n", g_kill_evt);
+        return 1;
+    }
+
+    ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT,
+                               HINT_ADDRESS, 1);
+    if (ret) {
+        fprintf(stderr, "failed to reserve hint ioport range: %d\n", ret);
+        return 1;
+    }
+
+    ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT,
+                               KILL_ADDRESS, 1);
+    if (ret) {
+        fprintf(stderr, "failed to reserve kill ioport range: %d\n", ret);
+        return 1;
+    }
+
+    struct crosvm_hint_detail details = {0};
+    details.match_rax = 1;
+    details.rax = EAX_HINT_VALUE;
+    details.send_sregs = 1;
+    details.send_debugregs = 1;
+
+    struct crosvm_hint hint = {0};
+    hint.address_space = CROSVM_ADDRESS_SPACE_IOPORT;
+    hint.address = HINT_ADDRESS;
+    hint.address_flags = CROSVM_HINT_ON_WRITE;
+    hint.details_count = 1;
+    hint.details = &details;
+
+    ret = crosvm_set_hypercall_hint(crosvm, 1, &hint);
+    if (ret) {
+        fprintf(stderr, "failed to set hypercall hint: %d\n", ret);
+        return 1;
+    }
+
+    int mem_size = 0x2000;
+    int mem_fd = syscall(SYS_memfd_create, "guest_mem",
+                         MFD_CLOEXEC | MFD_ALLOW_SEALING);
+    if (mem_fd < 0) {
+        fprintf(stderr, "failed to create guest memfd: %d\n", errno);
+        return 1;
+    }
+    ret = ftruncate(mem_fd, mem_size);
+    if (ret) {
+        fprintf(stderr, "failed to set size of guest memory: %d\n", errno);
+        return 1;
+    }
+    uint8_t *mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED,
+                        mem_fd, 0x1000);
+    if (mem == MAP_FAILED) {
+        fprintf(stderr, "failed to mmap guest memory: %d\n", errno);
+        return 1;
+    }
+    fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK);
+    memcpy(mem, code, sizeof(code));
+
+    struct crosvm_memory *mem_obj;
+    ret = crosvm_create_memory(crosvm, mem_fd, 0x1000, 0x1000, 0x1000, false,
+                               false, &mem_obj);
+    if (ret) {
+        fprintf(stderr, "failed to create memory in crosvm: %d\n", ret);
+        return 1;
+    }
+
+    /* get and creat a thread for each vcpu */
+    struct crosvm_vcpu *vcpus[32];
+    pthread_t vcpu_threads[32];
+    uint32_t vcpu_count;
+    for (vcpu_count = 0; vcpu_count < 32; vcpu_count++) {
+        ret = crosvm_get_vcpu(crosvm, vcpu_count, &vcpus[vcpu_count]);
+        if (ret == -ENOENT)
+            break;
+
+        if (ret) {
+            fprintf(stderr, "error while getting all vcpus: %d\n", ret);
+            return 1;
+        }
+        pthread_create(&vcpu_threads[vcpu_count], NULL, vcpu_thread,
+                       vcpus[vcpu_count]);
+    }
+
+    ret = crosvm_start(extra_crosvm);
+    if (ret) {
+        fprintf(stderr, "failed to tell crosvm to start: %d\n", ret);
+        return 1;
+    }
+
+    /* Wait for crosvm to request that we exit otherwise we will be killed. */
+    uint64_t dummy;
+    read(g_kill_evt, &dummy, 8);
+
+    ret = crosvm_destroy_memory(crosvm, &mem_obj);
+    if (ret) {
+        fprintf(stderr, "failed to destroy memory in crosvm: %d\n", ret);
+        return 1;
+    }
+
+    ret = crosvm_set_hypercall_hint(crosvm, 0, NULL);
+    if (ret) {
+        fprintf(stderr, "failed to clear hypercall hint: %d\n", ret);
+        return 1;
+    }
+
+    ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT,
+                               HINT_ADDRESS, 0);
+    if (ret) {
+        fprintf(stderr, "failed to unreserve hint ioport range: %d\n", ret);
+        return 1;
+    }
+
+    ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT,
+                               KILL_ADDRESS, 0);
+    if (ret) {
+        fprintf(stderr, "failed to unreserve kill ioport range: %d\n", ret);
+        return 1;
+    }
+
+    if (!got_regs) {
+      fprintf(stderr, "vm ran to completion without reg query\n");
+      return 1;
+    }
+
+    return 0;
+}
diff --git a/tests/plugins.rs b/tests/plugins.rs
index 50abea8..47b0ca3 100644
--- a/tests/plugins.rs
+++ b/tests/plugins.rs
@@ -226,6 +226,11 @@ fn test_adder() {
 }
 
 #[test]
+fn test_hint() {
+    test_plugin(include_str!("plugin_hint.c"));
+}
+
+#[test]
 fn test_dirty_log() {
     test_plugin(include_str!("plugin_dirty_log.c"));
 }