summary refs log tree commit diff
diff options
context:
space:
mode:
authorZide Chen <zide.chen@intel.corp-partner.google.com>2019-10-15 14:32:23 -0700
committerCommit Bot <commit-bot@chromium.org>2019-10-25 23:59:45 +0000
commit8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb (patch)
tree67764e974ad0e28021f7e7352ab6e1e759e650cc
parent3185ae95dd58f556a836f9e146dfe7b8450749b2 (diff)
downloadcrosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.tar
crosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.tar.gz
crosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.tar.bz2
crosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.tar.lz
crosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.tar.xz
crosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.tar.zst
crosvm-8a7e4e902a4950b060ea23b40c0dfce7bfa1b2cb.zip
devices: implement dedicated Interrupt struct for virtio Worker
The code to inject interrupt to the guest can be generic to all
virtio devices. This patch:

- move those guest interrupt related fields out of Worker structure and
  put in a separate file, making the worker code cleaner.
- remove redandant functions across virtio devices: signal_used_queue(),
  signal_config_changed(), etc.

BUG=chromium:854765
TEST=sanity test on eve and Linux
TEST=cargo test -p devices

Change-Id: I8e9f760f2057f192fdc74d16a59fea2e6b08c194
Signed-off-by: Zide Chen <zide.chen@intel.corp-partner.google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1869553
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
-rw-r--r--devices/src/virtio/balloon.rs55
-rw-r--r--devices/src/virtio/block.rs51
-rw-r--r--devices/src/virtio/gpu/mod.rs53
-rw-r--r--devices/src/virtio/input/mod.rs36
-rw-r--r--devices/src/virtio/interrupt.rs83
-rw-r--r--devices/src/virtio/mod.rs2
-rw-r--r--devices/src/virtio/net.rs45
-rw-r--r--devices/src/virtio/p9.rs35
-rw-r--r--devices/src/virtio/pmem.rs36
-rw-r--r--devices/src/virtio/rng.rs34
-rw-r--r--devices/src/virtio/tpm.rs35
-rw-r--r--devices/src/virtio/vhost/worker.rs42
-rw-r--r--devices/src/virtio/wl.rs55
13 files changed, 266 insertions, 296 deletions
diff --git a/devices/src/virtio/balloon.rs b/devices/src/virtio/balloon.rs
index 460aba5..eef70fb 100644
--- a/devices/src/virtio/balloon.rs
+++ b/devices/src/virtio/balloon.rs
@@ -19,8 +19,7 @@ use sys_util::{
 use vm_control::{BalloonControlCommand, BalloonControlResponseSocket};
 
 use super::{
-    copy_config, Queue, Reader, VirtioDevice, INTERRUPT_STATUS_CONFIG_CHANGED,
-    INTERRUPT_STATUS_USED_RING, TYPE_BALLOON, VIRTIO_F_VERSION_1,
+    copy_config, Interrupt, Queue, Reader, VirtioDevice, TYPE_BALLOON, VIRTIO_F_VERSION_1,
 };
 
 #[derive(Debug)]
@@ -73,12 +72,10 @@ struct BalloonConfig {
 }
 
 struct Worker {
+    interrupt: Interrupt,
     mem: GuestMemory,
     inflate_queue: Queue,
     deflate_queue: Queue,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
     config: Arc<BalloonConfig>,
     command_socket: BalloonControlResponseSocket,
 }
@@ -144,18 +141,6 @@ impl Worker {
         needs_interrupt
     }
 
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
-    fn signal_config_changed(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_CONFIG_CHANGED as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
     fn run(&mut self, mut queue_evts: Vec<EventFd>, kill_evt: EventFd) {
         #[derive(PartialEq, PollToken)]
         enum Token {
@@ -173,7 +158,7 @@ impl Worker {
             (&inflate_queue_evt, Token::Inflate),
             (&deflate_queue_evt, Token::Deflate),
             (&self.command_socket, Token::CommandSocket),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ]) {
             Ok(pc) => pc,
@@ -192,7 +177,8 @@ impl Worker {
                 }
             };
 
-            let mut needs_interrupt = false;
+            let mut needs_interrupt_inflate = false;
+            let mut needs_interrupt_deflate = false;
             for event in events.iter_readable() {
                 match event.token() {
                     Token::Inflate => {
@@ -200,14 +186,14 @@ impl Worker {
                             error!("failed reading inflate queue EventFd: {}", e);
                             break 'poll;
                         }
-                        needs_interrupt |= self.process_inflate_deflate(true);
+                        needs_interrupt_inflate |= self.process_inflate_deflate(true);
                     }
                     Token::Deflate => {
                         if let Err(e) = deflate_queue_evt.read() {
                             error!("failed reading deflate queue EventFd: {}", e);
                             break 'poll;
                         }
-                        needs_interrupt |= self.process_inflate_deflate(false);
+                        needs_interrupt_deflate |= self.process_inflate_deflate(false);
                     }
                     Token::CommandSocket => {
                         if let Ok(req) = self.command_socket.recv() {
@@ -218,16 +204,13 @@ impl Worker {
                                     info!("ballon config changed to consume {} pages", num_pages);
 
                                     self.config.num_pages.store(num_pages, Ordering::Relaxed);
-                                    self.signal_config_changed();
+                                    self.interrupt.signal_config_changed();
                                 }
                             };
                         }
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
@@ -239,8 +222,13 @@ impl Worker {
                     let _ = poll_ctx.delete(&self.command_socket);
                 }
             }
-            if needs_interrupt {
-                self.signal_used_queue();
+
+            if needs_interrupt_inflate {
+                self.interrupt.signal_used_queue(self.inflate_queue.vector);
+            }
+
+            if needs_interrupt_deflate {
+                self.interrupt.signal_used_queue(self.deflate_queue.vector);
             }
         }
     }
@@ -334,7 +322,7 @@ impl VirtioDevice for Balloon {
         mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         queue_evts: Vec<EventFd>,
@@ -358,12 +346,15 @@ impl VirtioDevice for Balloon {
             .name("virtio_balloon".to_string())
             .spawn(move || {
                 let mut worker = Worker {
+                    interrupt: Interrupt::new(
+                        status,
+                        interrupt_evt,
+                        interrupt_resample_evt,
+                        msix_config,
+                    ),
                     mem,
                     inflate_queue: queues.remove(0),
                     deflate_queue: queues.remove(0),
-                    interrupt_status: status,
-                    interrupt_evt,
-                    interrupt_resample_evt,
                     command_socket,
                     config,
                 };
diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs
index 26be384..a2a1d3d 100644
--- a/devices/src/virtio/block.rs
+++ b/devices/src/virtio/block.rs
@@ -7,7 +7,7 @@ use std::io::{self, Seek, SeekFrom, Write};
 use std::mem::size_of;
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::result;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use std::time::Duration;
@@ -24,8 +24,8 @@ use sys_util::{error, info, warn, EventFd, GuestMemory, PollContext, PollToken,
 use vm_control::{DiskControlCommand, DiskControlResponseSocket, DiskControlResult};
 
 use super::{
-    copy_config, DescriptorChain, DescriptorError, Queue, Reader, VirtioDevice, Writer,
-    INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING, TYPE_BLOCK, VIRTIO_F_VERSION_1,
+    copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer,
+    TYPE_BLOCK, VIRTIO_F_VERSION_1,
 };
 
 const QUEUE_SIZE: u16 = 256;
@@ -272,15 +272,12 @@ impl ExecuteError {
 }
 
 struct Worker {
+    interrupt: Interrupt,
     queues: Vec<Queue>,
     mem: GuestMemory,
     disk_image: Box<dyn DiskFile>,
     disk_size: Arc<Mutex<u64>>,
     read_only: bool,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
-    msix_config: Option<Arc<Mutex<MsixConfig>>>,
 }
 
 impl Worker {
@@ -383,25 +380,6 @@ impl Worker {
         DiskControlResult::Ok
     }
 
-    fn signal_used_queue(&self, vector: u16) {
-        if let Some(msix_config) = &self.msix_config {
-            let mut msix_config = msix_config.lock();
-            if msix_config.enabled() {
-                msix_config.trigger(vector);
-                return;
-            }
-        }
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
-    fn signal_config_changed(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_CONFIG_CHANGED as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
     fn run(
         &mut self,
         queue_evt: EventFd,
@@ -430,7 +408,7 @@ impl Worker {
             (&flush_timer, Token::FlushTimer),
             (&queue_evt, Token::QueueAvailable),
             (&control_socket, Token::ControlRequest),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ]) {
             Ok(pc) => pc,
@@ -468,7 +446,7 @@ impl Worker {
                             break 'poll;
                         }
                         if self.process_queue(0, &mut flush_timer, &mut flush_timer_armed) {
-                            self.signal_used_queue(self.queues[0].vector);
+                            self.interrupt.signal_used_queue(self.queues[0].vector);
                         }
                     }
                     Token::ControlRequest => {
@@ -493,16 +471,13 @@ impl Worker {
                         }
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
             }
             if needs_config_interrupt {
-                self.signal_config_changed();
+                self.interrupt.signal_config_changed();
             }
         }
     }
@@ -818,15 +793,17 @@ impl VirtioDevice for Block {
                         .name("virtio_blk".to_string())
                         .spawn(move || {
                             let mut worker = Worker {
+                                interrupt: Interrupt::new(
+                                    status,
+                                    interrupt_evt,
+                                    interrupt_resample_evt,
+                                    msix_config,
+                                ),
                                 queues,
                                 mem,
                                 disk_image,
                                 disk_size,
                                 read_only,
-                                interrupt_status: status,
-                                interrupt_evt,
-                                interrupt_resample_evt,
-                                msix_config,
                             };
                             worker.run(queue_evts.remove(0), kill_evt, control_socket);
                         });
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 1e545eb..210f815 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -14,7 +14,7 @@ use std::num::NonZeroU8;
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::PathBuf;
 use std::rc::Rc;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use std::time::Duration;
@@ -32,8 +32,8 @@ use gpu_renderer::{Renderer, RendererFlags};
 use resources::Alloc;
 
 use super::{
-    copy_config, resource_bridge::*, DescriptorChain, Queue, Reader, VirtioDevice, Writer,
-    INTERRUPT_STATUS_USED_RING, TYPE_GPU, VIRTIO_F_VERSION_1,
+    copy_config, resource_bridge::*, DescriptorChain, Interrupt, Queue, Reader, VirtioDevice,
+    Writer, TYPE_GPU, VIRTIO_F_VERSION_1,
 };
 
 use self::backend::Backend;
@@ -475,11 +475,9 @@ impl Frontend {
 }
 
 struct Worker {
+    interrupt: Interrupt,
     exit_evt: EventFd,
     mem: GuestMemory,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
-    interrupt_status: Arc<AtomicUsize>,
     ctrl_queue: Queue,
     ctrl_evt: EventFd,
     cursor_queue: Queue,
@@ -490,12 +488,6 @@ struct Worker {
 }
 
 impl Worker {
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        let _ = self.interrupt_evt.write(1);
-    }
-
     fn run(&mut self) {
         #[derive(PollToken)]
         enum Token {
@@ -511,7 +503,7 @@ impl Worker {
             (&self.ctrl_evt, Token::CtrlQueue),
             (&self.cursor_evt, Token::CursorQueue),
             (&*self.state.display().borrow(), Token::Display),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&self.kill_evt, Token::Kill),
         ]) {
             Ok(pc) => pc,
@@ -552,7 +544,8 @@ impl Worker {
                     break;
                 }
             };
-            let mut signal_used = false;
+            let mut signal_used_cursor = false;
+            let mut signal_used_ctrl = false;
             let mut ctrl_available = false;
 
             // Clear the old values and re-initialize with false.
@@ -570,7 +563,7 @@ impl Worker {
                     Token::CursorQueue => {
                         let _ = self.cursor_evt.read();
                         if self.state.process_queue(&self.mem, &mut self.cursor_queue) {
-                            signal_used = true;
+                            signal_used_cursor = true;
                         }
                     }
                     Token::Display => {
@@ -581,10 +574,7 @@ impl Worker {
                     }
                     Token::ResourceBridge { index } => process_resource_bridge[index] = true,
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => {
                         break 'poll;
@@ -595,18 +585,18 @@ impl Worker {
             // All cursor commands go first because they have higher priority.
             while let Some(desc) = self.state.return_cursor() {
                 self.cursor_queue.add_used(&self.mem, desc.index, desc.len);
-                signal_used = true;
+                signal_used_cursor = true;
             }
 
             if ctrl_available && self.state.process_queue(&self.mem, &mut self.ctrl_queue) {
-                signal_used = true;
+                signal_used_ctrl = true;
             }
 
             self.state.fence_poll();
 
             while let Some(desc) = self.state.return_ctrl() {
                 self.ctrl_queue.add_used(&self.mem, desc.index, desc.len);
-                signal_used = true;
+                signal_used_ctrl = true;
             }
 
             // Process the entire control queue before the resource bridge in case a resource is
@@ -623,8 +613,12 @@ impl Worker {
                 }
             }
 
-            if signal_used {
-                self.signal_used_queue();
+            if signal_used_ctrl {
+                self.interrupt.signal_used_queue(self.ctrl_queue.vector);
+            }
+
+            if signal_used_cursor {
+                self.interrupt.signal_used_queue(self.cursor_queue.vector);
             }
         }
     }
@@ -830,7 +824,7 @@ impl VirtioDevice for Gpu {
         mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         interrupt_status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         mut queue_evts: Vec<EventFd>,
@@ -877,11 +871,14 @@ impl VirtioDevice for Gpu {
                             };
 
                         Worker {
+                            interrupt: Interrupt::new(
+                                interrupt_status,
+                                interrupt_evt,
+                                interrupt_resample_evt,
+                                msix_config,
+                            ),
                             exit_evt,
                             mem,
-                            interrupt_evt,
-                            interrupt_resample_evt,
-                            interrupt_status,
                             ctrl_queue,
                             ctrl_evt,
                             cursor_queue,
diff --git a/devices/src/virtio/input/mod.rs b/devices/src/virtio/input/mod.rs
index 706a563..a4561e5 100644
--- a/devices/src/virtio/input/mod.rs
+++ b/devices/src/virtio/input/mod.rs
@@ -17,15 +17,15 @@ use sys_util::{error, warn, EventFd, GuestMemory, PollContext, PollToken};
 
 use self::event_source::{input_event, EvdevEventSource, EventSource, SocketEventSource};
 use super::{
-    copy_config, DescriptorChain, DescriptorError, Queue, Reader, VirtioDevice, Writer,
-    INTERRUPT_STATUS_USED_RING, TYPE_INPUT,
+    copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer,
+    TYPE_INPUT,
 };
 use std::collections::BTreeMap;
 use std::fmt::{self, Display};
 use std::io::Read;
 use std::io::Write;
 use std::mem::size_of;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use sync::Mutex;
@@ -363,22 +363,14 @@ impl virtio_input_event {
 }
 
 struct Worker<T: EventSource> {
+    interrupt: Interrupt,
     event_source: T,
     event_queue: Queue,
     status_queue: Queue,
     guest_memory: GuestMemory,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
 }
 
 impl<T: EventSource> Worker<T> {
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
     // Fills a virtqueue with events from the source.  Returns the number of bytes written.
     fn fill_event_virtqueue(
         event_source: &mut T,
@@ -499,7 +491,7 @@ impl<T: EventSource> Worker<T> {
             (&event_queue_evt_fd, Token::EventQAvailable),
             (&status_queue_evt_fd, Token::StatusQAvailable),
             (&self.event_source, Token::InputEventsAvailable),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ]) {
             Ok(poll_ctx) => poll_ctx,
@@ -543,10 +535,7 @@ impl<T: EventSource> Worker<T> {
                         Ok(_cnt) => needs_interrupt |= self.send_events(),
                     },
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => {
                         let _ = kill_evt.read();
@@ -555,7 +544,7 @@ impl<T: EventSource> Worker<T> {
                 }
             }
             if needs_interrupt {
-                self.signal_used_queue();
+                self.interrupt.signal_used_queue(self.event_queue.vector);
             }
         }
 
@@ -620,7 +609,7 @@ where
         mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         mut queue_evts: Vec<EventFd>,
@@ -650,13 +639,16 @@ where
                 .name(String::from("virtio_input"))
                 .spawn(move || {
                     let mut worker = Worker {
+                        interrupt: Interrupt::new(
+                            status,
+                            interrupt_evt,
+                            interrupt_resample_evt,
+                            msix_config,
+                        ),
                         event_source: source,
                         event_queue,
                         status_queue,
                         guest_memory: mem,
-                        interrupt_status: status,
-                        interrupt_evt,
-                        interrupt_resample_evt,
                     };
                     worker.run(event_queue_evt_fd, status_queue_evt_fd, kill_evt);
                 });
diff --git a/devices/src/virtio/interrupt.rs b/devices/src/virtio/interrupt.rs
new file mode 100644
index 0000000..3a3d7e2
--- /dev/null
+++ b/devices/src/virtio/interrupt.rs
@@ -0,0 +1,83 @@
+// 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.
+
+use super::{INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING};
+use crate::pci::MsixConfig;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+use sync::Mutex;
+use sys_util::EventFd;
+
+pub struct Interrupt {
+    interrupt_status: Arc<AtomicUsize>,
+    interrupt_evt: EventFd,
+    interrupt_resample_evt: EventFd,
+    msix_config: Option<Arc<Mutex<MsixConfig>>>,
+}
+
+impl Interrupt {
+    pub fn new(
+        interrupt_status: Arc<AtomicUsize>,
+        interrupt_evt: EventFd,
+        interrupt_resample_evt: EventFd,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
+    ) -> Interrupt {
+        Interrupt {
+            interrupt_status,
+            interrupt_evt,
+            interrupt_resample_evt,
+            msix_config,
+        }
+    }
+
+    /// Virtqueue Interrupts From The Device
+    ///
+    /// If MSI-X is enabled in this device, MSI-X interrupt is preferred.
+    /// Write to the irqfd to VMM to deliver virtual interrupt to the guest
+    pub fn signal_used_queue(&self, vector: u16) {
+        // Don't need to set ISR for MSI-X interrupts
+        if let Some(msix_config) = &self.msix_config {
+            let mut msix_config = msix_config.lock();
+            if msix_config.enabled() {
+                msix_config.trigger(vector);
+                return;
+            }
+        }
+
+        // Don't need to inject the interrupt if the guest hasn't processed it.
+        if self
+            .interrupt_status
+            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst)
+            & INTERRUPT_STATUS_USED_RING as usize
+            == 0
+        {
+            // Set BIT0 in ISR and write to irqfd to inject INTx interrupt
+            self.interrupt_status
+                .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
+            self.interrupt_evt.write(1).unwrap();
+        }
+    }
+
+    /// Notification of Device Configuration Changes
+    /// Set BIT1 in ISR and write to irqfd
+    pub fn signal_config_changed(&self) {
+        self.interrupt_status
+            .fetch_or(INTERRUPT_STATUS_CONFIG_CHANGED as usize, Ordering::SeqCst);
+        self.interrupt_evt.write(1).unwrap();
+    }
+
+    /// Handle interrupt resampling event
+    pub fn interrupt_resample(&self) {
+        let _ = self.interrupt_resample_evt.read();
+        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
+            self.interrupt_evt.write(1).unwrap();
+        }
+    }
+
+    /// Return the reference of interrupt_resample_evt
+    /// To keep the interface clean, this member is private.
+    pub fn get_resample_evt(&self) -> &EventFd {
+        &self.interrupt_resample_evt
+    }
+}
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 49f4355..ca3511f 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -10,6 +10,7 @@ mod descriptor_utils;
 #[cfg(feature = "gpu")]
 mod gpu;
 mod input;
+mod interrupt;
 mod net;
 mod p9;
 mod pmem;
@@ -32,6 +33,7 @@ pub use self::descriptor_utils::*;
 #[cfg(feature = "gpu")]
 pub use self::gpu::*;
 pub use self::input::*;
+pub use self::interrupt::*;
 pub use self::net::*;
 pub use self::p9::*;
 pub use self::pmem::*;
diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
index 51d2039..abbaa98 100644
--- a/devices/src/virtio/net.rs
+++ b/devices/src/virtio/net.rs
@@ -7,7 +7,7 @@ use std::io::{self, Read, Write};
 use std::mem;
 use std::net::Ipv4Addr;
 use std::os::unix::io::{AsRawFd, RawFd};
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use sync::Mutex;
@@ -21,7 +21,7 @@ use sys_util::{error, warn, EventFd, GuestMemory, PollContext, PollToken};
 use virtio_sys::virtio_net::virtio_net_hdr_v1;
 use virtio_sys::{vhost, virtio_net};
 
-use super::{Queue, Reader, VirtioDevice, Writer, INTERRUPT_STATUS_USED_RING, TYPE_NET};
+use super::{Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_NET};
 
 /// The maximum buffer size when segmentation offload is enabled. This
 /// includes the 12-byte virtio net header.
@@ -88,14 +88,11 @@ impl Display for NetError {
 }
 
 struct Worker<T: TapT> {
+    interrupt: Interrupt,
     mem: GuestMemory,
     rx_queue: Queue,
     tx_queue: Queue,
     tap: T,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
-    msix_config: Option<Arc<Mutex<MsixConfig>>>,
     rx_buf: [u8; MAX_BUFFER_SIZE],
     rx_count: usize,
     deferred_rx: bool,
@@ -109,19 +106,6 @@ impl<T> Worker<T>
 where
     T: TapT,
 {
-    fn signal_used_queue(&self, vector: u16) {
-        if let Some(msix_config) = &self.msix_config {
-            let mut msix_config = msix_config.lock();
-            if msix_config.enabled() {
-                msix_config.trigger(vector);
-                return;
-            }
-        }
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
     // Copies a single frame from `self.rx_buf` into the guest. Returns true
     // if a buffer was used, and false if the frame must be deferred until a buffer
     // is made available by the driver.
@@ -174,7 +158,7 @@ where
                         self.deferred_rx = true;
                         break;
                     } else if first_frame {
-                        self.signal_used_queue(self.rx_queue.vector);
+                        self.interrupt.signal_used_queue(self.rx_queue.vector);
                         first_frame = false;
                     } else {
                         needs_interrupt = true;
@@ -234,7 +218,7 @@ where
             self.tx_queue.add_used(&self.mem, index, 0);
         }
 
-        self.signal_used_queue(self.tx_queue.vector);
+        self.interrupt.signal_used_queue(self.tx_queue.vector);
     }
 
     fn run(
@@ -261,7 +245,7 @@ where
             (&self.tap, Token::RxTap),
             (&rx_queue_evt, Token::RxQueue),
             (&tx_queue_evt, Token::TxQueue),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ])
         .map_err(NetError::CreatePollContext)?;
@@ -319,16 +303,13 @@ where
                         self.process_tx();
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
 
                 if needs_interrupt_rx {
-                    self.signal_used_queue(self.rx_queue.vector);
+                    self.interrupt.signal_used_queue(self.rx_queue.vector);
                 }
             }
         }
@@ -528,14 +509,16 @@ where
                             let rx_queue = queues.remove(0);
                             let tx_queue = queues.remove(0);
                             let mut worker = Worker {
+                                interrupt: Interrupt::new(
+                                    status,
+                                    interrupt_evt,
+                                    interrupt_resample_evt,
+                                    msix_config,
+                                ),
                                 mem,
                                 rx_queue,
                                 tx_queue,
                                 tap,
-                                interrupt_status: status,
-                                interrupt_evt,
-                                msix_config,
-                                interrupt_resample_evt,
                                 rx_buf: [0u8; MAX_BUFFER_SIZE],
                                 rx_count: 0,
                                 deferred_rx: false,
diff --git a/devices/src/virtio/p9.rs b/devices/src/virtio/p9.rs
index 83aa84e..d891d22 100644
--- a/devices/src/virtio/p9.rs
+++ b/devices/src/virtio/p9.rs
@@ -8,7 +8,7 @@ use std::mem;
 use std::os::unix::io::RawFd;
 use std::path::{Path, PathBuf};
 use std::result;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use sync::Mutex;
@@ -19,8 +19,7 @@ use sys_util::{error, warn, Error as SysError, EventFd, GuestMemory, PollContext
 use virtio_sys::vhost::VIRTIO_F_VERSION_1;
 
 use super::{
-    copy_config, DescriptorError, Queue, Reader, VirtioDevice, Writer, INTERRUPT_STATUS_USED_RING,
-    TYPE_9P,
+    copy_config, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_9P,
 };
 
 const QUEUE_SIZE: u16 = 128;
@@ -89,21 +88,13 @@ impl Display for P9Error {
 pub type P9Result<T> = result::Result<T, P9Error>;
 
 struct Worker {
+    interrupt: Interrupt,
     mem: GuestMemory,
     queue: Queue,
     server: p9::Server,
-    irq_status: Arc<AtomicUsize>,
-    irq_evt: EventFd,
-    interrupt_resample_evt: EventFd,
 }
 
 impl Worker {
-    fn signal_used_queue(&self) -> P9Result<()> {
-        self.irq_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.irq_evt.write(1).map_err(P9Error::SignalUsedQueue)
-    }
-
     fn process_queue(&mut self) -> P9Result<()> {
         while let Some(avail_desc) = self.queue.pop(&self.mem) {
             let mut reader = Reader::new(&self.mem, avail_desc.clone())
@@ -119,7 +110,7 @@ impl Worker {
                 .add_used(&self.mem, avail_desc.index, writer.bytes_written() as u32);
         }
 
-        self.signal_used_queue()?;
+        self.interrupt.signal_used_queue(self.queue.vector);
 
         Ok(())
     }
@@ -137,7 +128,7 @@ impl Worker {
 
         let poll_ctx: PollContext<Token> = PollContext::build_with(&[
             (&queue_evt, Token::QueueReady),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ])
         .map_err(P9Error::CreatePollContext)?;
@@ -151,10 +142,7 @@ impl Worker {
                         self.process_queue()?;
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.irq_status.load(Ordering::SeqCst) != 0 {
-                            self.irq_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => return Ok(()),
                 }
@@ -241,7 +229,7 @@ impl VirtioDevice for P9 {
         guest_mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         mut queue_evts: Vec<EventFd>,
@@ -265,12 +253,15 @@ impl VirtioDevice for P9 {
                     .name("virtio_9p".to_string())
                     .spawn(move || {
                         let mut worker = Worker {
+                            interrupt: Interrupt::new(
+                                status,
+                                interrupt_evt,
+                                interrupt_resample_evt,
+                                msix_config,
+                            ),
                             mem: guest_mem,
                             queue: queues.remove(0),
                             server,
-                            irq_status: status,
-                            irq_evt: interrupt_evt,
-                            interrupt_resample_evt,
                         };
 
                         worker.run(queue_evts.remove(0), kill_evt)
diff --git a/devices/src/virtio/pmem.rs b/devices/src/virtio/pmem.rs
index bc96f94..07ed137 100644
--- a/devices/src/virtio/pmem.rs
+++ b/devices/src/virtio/pmem.rs
@@ -6,7 +6,7 @@ use std::fmt::{self, Display};
 use std::fs::File;
 use std::io;
 use std::os::unix::io::{AsRawFd, RawFd};
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use sync::Mutex;
@@ -19,8 +19,8 @@ use data_model::{DataInit, Le32, Le64};
 use crate::pci::MsixConfig;
 
 use super::{
-    copy_config, DescriptorChain, DescriptorError, Queue, Reader, VirtioDevice, Writer,
-    INTERRUPT_STATUS_USED_RING, TYPE_PMEM, VIRTIO_F_VERSION_1,
+    copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer,
+    TYPE_PMEM, VIRTIO_F_VERSION_1,
 };
 
 const QUEUE_SIZE: u16 = 256;
@@ -85,12 +85,10 @@ impl ::std::error::Error for Error {}
 type Result<T> = ::std::result::Result<T, Error>;
 
 struct Worker {
+    interrupt: Interrupt,
     queue: Queue,
     memory: GuestMemory,
     disk_image: File,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_event: EventFd,
-    interrupt_resample_event: EventFd,
 }
 
 impl Worker {
@@ -149,12 +147,6 @@ impl Worker {
         needs_interrupt
     }
 
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_event.write(1).unwrap();
-    }
-
     fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) {
         #[derive(PollToken)]
         enum Token {
@@ -165,7 +157,7 @@ impl Worker {
 
         let poll_ctx: PollContext<Token> = match PollContext::build_with(&[
             (&queue_evt, Token::QueueAvailable),
-            (&self.interrupt_resample_event, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ]) {
             Ok(pc) => pc,
@@ -195,16 +187,13 @@ impl Worker {
                         needs_interrupt |= self.process_queue();
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_event.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_event.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
             }
             if needs_interrupt {
-                self.signal_used_queue();
+                self.interrupt.signal_used_queue(self.queue.vector);
             }
         }
     }
@@ -281,7 +270,7 @@ impl VirtioDevice for Pmem {
         memory: GuestMemory,
         interrupt_event: EventFd,
         interrupt_resample_event: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         mut queue_events: Vec<EventFd>,
@@ -308,12 +297,15 @@ impl VirtioDevice for Pmem {
                 .name("virtio_pmem".to_string())
                 .spawn(move || {
                     let mut worker = Worker {
+                        interrupt: Interrupt::new(
+                            status,
+                            interrupt_event,
+                            interrupt_resample_event,
+                            msix_config,
+                        ),
                         memory,
                         disk_image,
                         queue,
-                        interrupt_status: status,
-                        interrupt_event,
-                        interrupt_resample_event,
                     };
                     worker.run(queue_event, kill_event);
                 });
diff --git a/devices/src/virtio/rng.rs b/devices/src/virtio/rng.rs
index 41e1b4b..9fa2e59 100644
--- a/devices/src/virtio/rng.rs
+++ b/devices/src/virtio/rng.rs
@@ -7,14 +7,14 @@ use std::fmt::{self, Display};
 use std::fs::File;
 use std::io;
 use std::os::unix::io::{AsRawFd, RawFd};
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use sync::Mutex;
 
 use sys_util::{error, warn, EventFd, GuestMemory, PollContext, PollToken};
 
-use super::{Queue, VirtioDevice, Writer, INTERRUPT_STATUS_USED_RING, TYPE_RNG};
+use super::{Interrupt, Queue, VirtioDevice, Writer, TYPE_RNG};
 
 use crate::pci::MsixConfig;
 
@@ -39,12 +39,10 @@ impl Display for RngError {
 }
 
 struct Worker {
+    interrupt: Interrupt,
     queue: Queue,
     mem: GuestMemory,
     random_file: File,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
 }
 
 impl Worker {
@@ -73,12 +71,6 @@ impl Worker {
         needs_interrupt
     }
 
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
     fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) {
         #[derive(PollToken)]
         enum Token {
@@ -89,7 +81,7 @@ impl Worker {
 
         let poll_ctx: PollContext<Token> = match PollContext::build_with(&[
             (&queue_evt, Token::QueueAvailable),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ]) {
             Ok(pc) => pc,
@@ -119,16 +111,13 @@ impl Worker {
                         needs_interrupt |= self.process_queue();
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
             }
             if needs_interrupt {
-                self.signal_used_queue();
+                self.interrupt.signal_used_queue(self.queue.vector);
             }
         }
     }
@@ -190,7 +179,7 @@ impl VirtioDevice for Rng {
         mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         mut queue_evts: Vec<EventFd>,
@@ -216,12 +205,15 @@ impl VirtioDevice for Rng {
                     .name("virtio_rng".to_string())
                     .spawn(move || {
                         let mut worker = Worker {
+                            interrupt: Interrupt::new(
+                                status,
+                                interrupt_evt,
+                                interrupt_resample_evt,
+                                msix_config,
+                            ),
                             queue,
                             mem,
                             random_file,
-                            interrupt_status: status,
-                            interrupt_evt,
-                            interrupt_resample_evt,
                         };
                         worker.run(queue_evts.remove(0), kill_evt);
                     });
diff --git a/devices/src/virtio/tpm.rs b/devices/src/virtio/tpm.rs
index f5637d3..22d39a2 100644
--- a/devices/src/virtio/tpm.rs
+++ b/devices/src/virtio/tpm.rs
@@ -9,7 +9,7 @@ use std::io::{self, Read, Write};
 use std::ops::BitOrAssign;
 use std::os::unix::io::RawFd;
 use std::path::PathBuf;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 
@@ -19,8 +19,7 @@ use sys_util::{error, EventFd, GuestMemory, PollContext, PollToken};
 use tpm2;
 
 use super::{
-    DescriptorChain, DescriptorError, Queue, Reader, VirtioDevice, Writer,
-    INTERRUPT_STATUS_USED_RING, TYPE_TPM,
+    DescriptorChain, DescriptorError, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_TPM,
 };
 
 // A single queue of size 2. The guest kernel driver will enqueue a single
@@ -35,13 +34,11 @@ const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
 const TPM_BUFSIZE: usize = 4096;
 
 struct Worker {
+    interrupt: Interrupt,
     queue: Queue,
     mem: GuestMemory,
-    interrupt_status: Arc<AtomicUsize>,
     queue_evt: EventFd,
     kill_evt: EventFd,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
     device: Device,
 }
 
@@ -107,12 +104,6 @@ impl Worker {
         NeedsInterrupt::Yes
     }
 
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        let _ = self.interrupt_evt.write(1);
-    }
-
     fn run(mut self) {
         #[derive(PollToken, Debug)]
         enum Token {
@@ -126,7 +117,7 @@ impl Worker {
 
         let poll_ctx = match PollContext::build_with(&[
             (&self.queue_evt, Token::QueueAvailable),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&self.kill_evt, Token::Kill),
         ]) {
             Ok(pc) => pc,
@@ -156,16 +147,13 @@ impl Worker {
                         needs_interrupt |= self.process_queue();
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            let _ = self.interrupt_evt.write(1);
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
             }
             if needs_interrupt == NeedsInterrupt::Yes {
-                self.signal_used_queue();
+                self.interrupt.signal_used_queue(self.queue.vector);
             }
         }
     }
@@ -218,7 +206,7 @@ impl VirtioDevice for Tpm {
         mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         interrupt_status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         mut queue_evts: Vec<EventFd>,
@@ -249,12 +237,15 @@ impl VirtioDevice for Tpm {
         self.kill_evt = Some(self_kill_evt);
 
         let worker = Worker {
+            interrupt: Interrupt::new(
+                interrupt_status,
+                interrupt_evt,
+                interrupt_resample_evt,
+                msix_config,
+            ),
             queue,
             mem,
-            interrupt_status,
             queue_evt,
-            interrupt_evt,
-            interrupt_resample_evt,
             kill_evt,
             device: Device { simulator },
         };
diff --git a/devices/src/virtio/vhost/worker.rs b/devices/src/virtio/vhost/worker.rs
index 26c1449..e88b929 100644
--- a/devices/src/virtio/vhost/worker.rs
+++ b/devices/src/virtio/vhost/worker.rs
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 use std::os::raw::c_ulonglong;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use sync::Mutex;
 
@@ -12,20 +12,17 @@ use vhost::Vhost;
 
 use super::{Error, Result};
 use crate::pci::MsixConfig;
-use crate::virtio::{Queue, INTERRUPT_STATUS_USED_RING};
+use crate::virtio::{Interrupt, Queue};
 
 /// Worker that takes care of running the vhost device.  This mainly involves forwarding interrupts
 /// from the vhost driver to the guest VM because crosvm only supports the virtio-mmio transport,
 /// which requires a bit to be set in the interrupt status register before triggering the interrupt
 /// and the vhost driver doesn't do this for us.
 pub struct Worker<T: Vhost> {
+    interrupt: Interrupt,
     queues: Vec<Queue>,
     vhost_handle: T,
     vhost_interrupt: Vec<EventFd>,
-    interrupt_status: Arc<AtomicUsize>,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
-    msix_config: Option<Arc<Mutex<MsixConfig>>>,
     acked_features: u64,
 }
 
@@ -41,31 +38,19 @@ impl<T: Vhost> Worker<T> {
         acked_features: u64,
     ) -> Worker<T> {
         Worker {
+            interrupt: Interrupt::new(
+                interrupt_status,
+                interrupt_evt,
+                interrupt_resample_evt,
+                msix_config,
+            ),
             queues,
             vhost_handle,
             vhost_interrupt,
-            interrupt_status,
-            interrupt_evt,
-            interrupt_resample_evt,
-            msix_config,
             acked_features,
         }
     }
 
-    fn signal_used_queue(&self, vector: u16) {
-        if let Some(msix_config) = &self.msix_config {
-            let mut msix_config = msix_config.lock();
-            if msix_config.enabled() {
-                msix_config.trigger(vector);
-                return;
-            }
-        }
-
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        self.interrupt_evt.write(1).unwrap();
-    }
-
     pub fn run<F>(
         &mut self,
         queue_evts: Vec<EventFd>,
@@ -133,7 +118,7 @@ impl<T: Vhost> Worker<T> {
         }
 
         let poll_ctx: PollContext<Token> = PollContext::build_with(&[
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
             (&kill_evt, Token::Kill),
         ])
         .map_err(Error::CreatePollContext)?;
@@ -153,13 +138,10 @@ impl<T: Vhost> Worker<T> {
                         self.vhost_interrupt[index]
                             .read()
                             .map_err(Error::VhostIrqRead)?;
-                        self.signal_used_queue(self.queues[index].vector);
+                        self.interrupt.signal_used_queue(self.queues[index].vector);
                     }
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                     Token::Kill => break 'poll,
                 }
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index 4531511..319d70e 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -44,7 +44,7 @@ use std::os::unix::net::UnixStream;
 use std::path::{Path, PathBuf};
 use std::rc::Rc;
 use std::result;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::AtomicUsize;
 use std::sync::Arc;
 use std::thread;
 use std::time::Duration;
@@ -71,9 +71,7 @@ use sys_util::{
 use sys_util::ioctl_with_ref;
 
 use super::resource_bridge::*;
-use super::{
-    DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_WL, VIRTIO_F_VERSION_1,
-};
+use super::{DescriptorChain, Interrupt, Queue, VirtioDevice, TYPE_WL, VIRTIO_F_VERSION_1};
 use crate::pci::MsixConfig;
 use vm_control::{MaybeOwnedFd, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse};
 
@@ -1472,10 +1470,8 @@ impl WlState {
 }
 
 struct Worker {
+    interrupt: Interrupt,
     mem: GuestMemory,
-    interrupt_evt: EventFd,
-    interrupt_resample_evt: EventFd,
-    interrupt_status: Arc<AtomicUsize>,
     in_queue: Queue,
     out_queue: Queue,
     state: WlState,
@@ -1488,6 +1484,7 @@ impl Worker {
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
         interrupt_status: Arc<AtomicUsize>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         in_queue: Queue,
         out_queue: Queue,
         wayland_path: PathBuf,
@@ -1496,10 +1493,13 @@ impl Worker {
         resource_bridge: Option<ResourceRequestSocket>,
     ) -> Worker {
         Worker {
+            interrupt: Interrupt::new(
+                interrupt_status,
+                interrupt_evt,
+                interrupt_resample_evt,
+                msix_config,
+            ),
             mem,
-            interrupt_evt,
-            interrupt_resample_evt,
-            interrupt_status,
             in_queue,
             out_queue,
             state: WlState::new(
@@ -1512,12 +1512,6 @@ impl Worker {
         }
     }
 
-    fn signal_used_queue(&self) {
-        self.interrupt_status
-            .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
-        let _ = self.interrupt_evt.write(1);
-    }
-
     fn run(&mut self, mut queue_evts: Vec<EventFd>, kill_evt: EventFd) {
         let in_queue_evt = queue_evts.remove(0);
         let out_queue_evt = queue_evts.remove(0);
@@ -1535,7 +1529,7 @@ impl Worker {
             (&out_queue_evt, Token::OutQueue),
             (&kill_evt, Token::Kill),
             (&self.state.poll_ctx, Token::State),
-            (&self.interrupt_resample_evt, Token::InterruptResample),
+            (self.interrupt.get_resample_evt(), Token::InterruptResample),
         ]) {
             Ok(pc) => pc,
             Err(e) => {
@@ -1545,7 +1539,8 @@ impl Worker {
         };
 
         'poll: loop {
-            let mut signal_used = false;
+            let mut signal_used_in = false;
+            let mut signal_used_out = false;
             let events = match poll_ctx.wait() {
                 Ok(v) => v,
                 Err(e) => {
@@ -1578,7 +1573,7 @@ impl Worker {
                                 }
                             }));
                         for &reject in &rejects[..rejects_len] {
-                            signal_used = true;
+                            signal_used_in = true;
                             self.in_queue.add_used(&self.mem, reject, 0);
                         }
                     }
@@ -1609,24 +1604,21 @@ impl Worker {
                                             encode_resp(resp_mem, resp).unwrap_or_default();
 
                                         self.out_queue.add_used(&self.mem, desc.index, used_len);
-                                        signal_used = true;
+                                        signal_used_out = true;
                                     }
                                 }
                             } else {
                                 // Chains that are unusable get sent straight back to the used
                                 // queue.
                                 self.out_queue.add_used(&self.mem, desc.index, 0);
-                                signal_used = true;
+                                signal_used_out = true;
                             }
                         }
                     }
                     Token::Kill => break 'poll,
                     Token::State => self.state.process_poll_context(),
                     Token::InterruptResample => {
-                        let _ = self.interrupt_resample_evt.read();
-                        if self.interrupt_status.load(Ordering::SeqCst) != 0 {
-                            self.interrupt_evt.write(1).unwrap();
-                        }
+                        self.interrupt.interrupt_resample();
                     }
                 }
             }
@@ -1652,7 +1644,7 @@ impl Worker {
                             0
                         }
                     };
-                    signal_used = true;
+                    signal_used_in = true;
                     self.in_queue.add_used(&self.mem, index, len);
                 } else {
                     break;
@@ -1662,8 +1654,12 @@ impl Worker {
                 }
             }
 
-            if signal_used {
-                self.signal_used_queue();
+            if signal_used_in {
+                self.interrupt.signal_used_queue(self.in_queue.vector);
+            }
+
+            if signal_used_out {
+                self.interrupt.signal_used_queue(self.out_queue.vector);
             }
         }
     }
@@ -1745,7 +1741,7 @@ impl VirtioDevice for Wl {
         mem: GuestMemory,
         interrupt_evt: EventFd,
         interrupt_resample_evt: EventFd,
-        _msix_config: Option<Arc<Mutex<MsixConfig>>>,
+        msix_config: Option<Arc<Mutex<MsixConfig>>>,
         status: Arc<AtomicUsize>,
         mut queues: Vec<Queue>,
         queue_evts: Vec<EventFd>,
@@ -1776,6 +1772,7 @@ impl VirtioDevice for Wl {
                             interrupt_evt,
                             interrupt_resample_evt,
                             status,
+                            msix_config,
                             queues.remove(0),
                             queues.remove(0),
                             wayland_path,