summary refs log tree commit diff
path: root/devices/src/virtio/vhost
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2017-10-06 15:26:46 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-10-09 17:39:05 -0700
commitd169a8d9ed63ae33b8678821c9675963bcee3f92 (patch)
tree03f436cbaeeb74f8fa49e02232162fabed442ba5 /devices/src/virtio/vhost
parent94bf1bf6b4a7791757937e2ffb2a81e797b82922 (diff)
downloadcrosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.gz
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.bz2
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.lz
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.xz
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.tar.zst
crosvm-d169a8d9ed63ae33b8678821c9675963bcee3f92.zip
Move crosvm/hw to a new devices module
Moving the devices to their own module makes it easier to add tests that
use them.

Change-Id: I61bfef4037d16b20145b5fddce604835cdc4f67b
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/706559
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'devices/src/virtio/vhost')
-rw-r--r--devices/src/virtio/vhost/mod.rs72
-rw-r--r--devices/src/virtio/vhost/net.rs204
-rw-r--r--devices/src/virtio/vhost/vsock.rs280
-rw-r--r--devices/src/virtio/vhost/worker.rs132
4 files changed, 688 insertions, 0 deletions
diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs
new file mode 100644
index 0000000..1a45c5b
--- /dev/null
+++ b/devices/src/virtio/vhost/mod.rs
@@ -0,0 +1,72 @@
+// Copyright 2017 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.
+
+//! Implements vhost-based virtio devices.
+
+use std;
+
+use net_util::Error as TapError;
+use sys_util::Error as SysError;
+use vhost::Error as VhostError;
+
+mod net;
+mod vsock;
+mod worker;
+
+pub use self::net::Net;
+pub use self::vsock::Vsock;
+
+#[derive(Debug)]
+pub enum Error {
+    /// Creating kill eventfd failed.
+    CreateKillEventFd(SysError),
+    /// Cloning kill eventfd failed.
+    CloneKillEventFd(SysError),
+    /// Error while polling for events.
+    PollError(SysError),
+    /// Open tap device failed.
+    TapOpen(TapError),
+    /// Setting tap IP failed.
+    TapSetIp(TapError),
+    /// Setting tap netmask failed.
+    TapSetNetmask(TapError),
+    /// Setting tap interface offload flags failed.
+    TapSetOffload(TapError),
+    /// Setting vnet header size failed.
+    TapSetVnetHdrSize(TapError),
+    /// Enabling tap interface failed.
+    TapEnable(TapError),
+    /// Failed to open vhost device.
+    VhostOpen(VhostError),
+    /// Set owner failed.
+    VhostSetOwner(VhostError),
+    /// Get features failed.
+    VhostGetFeatures(VhostError),
+    /// Set features failed.
+    VhostSetFeatures(VhostError),
+    /// Set mem table failed.
+    VhostSetMemTable(VhostError),
+    /// Set vring num failed.
+    VhostSetVringNum(VhostError),
+    /// Set vring addr failed.
+    VhostSetVringAddr(VhostError),
+    /// Set vring base failed.
+    VhostSetVringBase(VhostError),
+    /// Set vring call failed.
+    VhostSetVringCall(VhostError),
+    /// Set vring kick failed.
+    VhostSetVringKick(VhostError),
+    /// Net set backend failed.
+    VhostNetSetBackend(VhostError),
+    /// Failed to set CID for guest.
+    VhostVsockSetCid(VhostError),
+    /// Failed to start vhost-vsock driver.
+    VhostVsockStart(VhostError),
+    /// Failed to create vhost eventfd.
+    VhostIrqCreate(SysError),
+    /// Failed to read vhost eventfd.
+    VhostIrqRead(SysError),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs
new file mode 100644
index 0000000..6fc978e
--- /dev/null
+++ b/devices/src/virtio/vhost/net.rs
@@ -0,0 +1,204 @@
+// Copyright 2017 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 std::mem;
+use std::net::Ipv4Addr;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::sync::Arc;
+use std::sync::atomic::AtomicUsize;
+use std::thread::spawn;
+
+use net_sys;
+use net_util::Tap;
+use sys_util::{EventFd, GuestMemory};
+use vhost::Net as VhostNetHandle;
+use vhost::net::NetT;
+use virtio_sys::{vhost, virtio_net};
+
+use super::{Error, Result};
+use super::super::{Queue, VirtioDevice, TYPE_NET};
+use super::worker::Worker;
+
+const QUEUE_SIZE: u16 = 256;
+const NUM_QUEUES: usize = 2;
+const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE; NUM_QUEUES];
+
+pub struct Net {
+    workers_kill_evt: Option<EventFd>,
+    kill_evt: EventFd,
+    tap: Option<Tap>,
+    vhost_net_handle: Option<VhostNetHandle>,
+    vhost_interrupt: Option<EventFd>,
+    avail_features: u64,
+    acked_features: u64,
+}
+
+impl Net {
+    /// Create a new virtio network device with the given IP address and
+    /// netmask.
+    pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr, mem: &GuestMemory) -> Result<Net> {
+        let kill_evt = EventFd::new().map_err(Error::CreateKillEventFd)?;
+
+        let tap = Tap::new().map_err(Error::TapOpen)?;
+        tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
+        tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
+
+        // Set offload flags to match the virtio features below.
+        tap.set_offload(
+            net_sys::TUN_F_CSUM | net_sys::TUN_F_UFO | net_sys::TUN_F_TSO4 | net_sys::TUN_F_TSO6,
+        ).map_err(Error::TapSetOffload)?;
+
+        // We declare VIRTIO_NET_F_MRG_RXBUF, so set the vnet hdr size to match.
+        let vnet_hdr_size = mem::size_of::<virtio_net::virtio_net_hdr_mrg_rxbuf>() as i32;
+        tap.set_vnet_hdr_size(vnet_hdr_size).map_err(Error::TapSetVnetHdrSize)?;
+
+        tap.enable().map_err(Error::TapEnable)?;
+        let vhost_net_handle = VhostNetHandle::new(mem).map_err(Error::VhostOpen)?;
+
+        let avail_features =
+            1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM | 1 << virtio_net::VIRTIO_NET_F_CSUM |
+                1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4 |
+                1 << virtio_net::VIRTIO_NET_F_GUEST_UFO |
+                1 << virtio_net::VIRTIO_NET_F_HOST_TSO4 |
+                1 << virtio_net::VIRTIO_NET_F_HOST_UFO |
+                1 << virtio_net::VIRTIO_NET_F_MRG_RXBUF |
+                1 << vhost::VIRTIO_RING_F_INDIRECT_DESC |
+                1 << vhost::VIRTIO_RING_F_EVENT_IDX |
+                1 << vhost::VIRTIO_F_NOTIFY_ON_EMPTY | 1 << vhost::VIRTIO_F_VERSION_1;
+
+        Ok(Net {
+            workers_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?),
+            kill_evt: kill_evt,
+            tap: Some(tap),
+            vhost_net_handle: Some(vhost_net_handle),
+            vhost_interrupt: Some(EventFd::new().map_err(Error::VhostIrqCreate)?),
+            avail_features: avail_features,
+            acked_features: 0u64,
+        })
+    }
+}
+
+impl Drop for Net {
+    fn drop(&mut self) {
+        // Only kill the child if it claimed its eventfd.
+        if self.workers_kill_evt.is_none() {
+            // Ignore the result because there is nothing we can do about it.
+            let _ = self.kill_evt.write(1);
+        }
+    }
+}
+
+impl VirtioDevice for Net {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        let mut keep_fds = Vec::new();
+
+        if let Some(ref tap) = self.tap {
+            keep_fds.push(tap.as_raw_fd());
+        }
+
+        if let Some(ref vhost_net_handle) = self.vhost_net_handle {
+            keep_fds.push(vhost_net_handle.as_raw_fd());
+        }
+
+        if let Some(ref vhost_interrupt) = self.vhost_interrupt {
+            keep_fds.push(vhost_interrupt.as_raw_fd());
+        }
+
+        if let Some(ref workers_kill_evt) = self.workers_kill_evt {
+            keep_fds.push(workers_kill_evt.as_raw_fd());
+        }
+
+        keep_fds
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_NET
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn features(&self, page: u32) -> u32 {
+        match page {
+            0 => self.avail_features as u32,
+            1 => (self.avail_features >> 32) as u32,
+            _ => {
+                warn!("net: virtio net got request for features page: {}", page);
+                0u32
+            },
+        }
+    }
+
+    fn ack_features(&mut self, page: u32, value: u32) {
+        let mut v = match page {
+            0 => value as u64,
+            1 => (value as u64) << 32,
+            _ => {
+                warn!(
+                    "net: virtio net device cannot ack unknown feature page: {}",
+                    page
+                );
+                0u64
+            },
+        };
+
+        // Check if the guest is ACK'ing a feature that we didn't claim to have.
+        let unrequested_features = v & !self.avail_features;
+        if unrequested_features != 0 {
+            warn!("net: virtio net got unknown feature ack: {:x}", v);
+
+            // Don't count these features as acked.
+            v &= !unrequested_features;
+        }
+        self.acked_features |= v;
+    }
+
+    fn activate(
+        &mut self,
+        _: GuestMemory,
+        interrupt_evt: EventFd,
+        status: Arc<AtomicUsize>,
+        queues: Vec<Queue>,
+        queue_evts: Vec<EventFd>,
+    ) {
+        if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
+            error!("net: expected {} queues, got {}", NUM_QUEUES, queues.len());
+            return;
+        }
+
+        if let Some(vhost_net_handle) = self.vhost_net_handle.take() {
+            if let Some(tap) = self.tap.take() {
+                if let Some(vhost_interrupt) = self.vhost_interrupt.take() {
+                    if let Some(kill_evt) = self.workers_kill_evt.take() {
+                        let acked_features = self.acked_features;
+                        spawn(move || {
+                            let mut worker = Worker::new(
+                                queues,
+                                vhost_net_handle,
+                                vhost_interrupt,
+                                status,
+                                interrupt_evt,
+                                acked_features,
+                            );
+                            let activate_vqs = |handle: &VhostNetHandle| -> Result<()> {
+                                for idx in 0..NUM_QUEUES {
+                                    handle
+                                        .set_backend(idx, &tap)
+                                        .map_err(Error::VhostNetSetBackend)?;
+                                }
+                                Ok(())
+                            };
+                            let result =
+                                worker.run(queue_evts, QUEUE_SIZES, kill_evt, activate_vqs);
+                            if let Err(e) = result {
+                                error!("net worker thread exited with error: {:?}", e);
+                            }
+                        });
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/devices/src/virtio/vhost/vsock.rs b/devices/src/virtio/vhost/vsock.rs
new file mode 100644
index 0000000..a3c24aa
--- /dev/null
+++ b/devices/src/virtio/vhost/vsock.rs
@@ -0,0 +1,280 @@
+// Copyright 2017 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 std::os::unix::io::{AsRawFd, RawFd};
+use std::sync::Arc;
+use std::sync::atomic::AtomicUsize;
+use std::thread::spawn;
+
+use byteorder::{ByteOrder, LittleEndian};
+
+use sys_util::{EventFd, GuestMemory};
+use vhost::Vsock as VhostVsockHandle;
+use virtio_sys::vhost;
+
+use super::{Error, Result};
+use super::super::{Queue, VirtioDevice, TYPE_VSOCK};
+use super::worker::Worker;
+
+const QUEUE_SIZE: u16 = 256;
+const NUM_QUEUES: usize = 3;
+const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE; NUM_QUEUES];
+
+pub struct Vsock {
+    worker_kill_evt: Option<EventFd>,
+    kill_evt: Option<EventFd>,
+    vhost_handle: Option<VhostVsockHandle>,
+    cid: u64,
+    interrupt: Option<EventFd>,
+    avail_features: u64,
+    acked_features: u64,
+}
+
+impl Vsock {
+    /// Create a new virtio-vsock device with the given VM cid.
+    pub fn new(cid: u64, mem: &GuestMemory) -> Result<Vsock> {
+        let kill_evt = EventFd::new().map_err(Error::CreateKillEventFd)?;
+        let handle = VhostVsockHandle::new(mem).map_err(Error::VhostOpen)?;
+
+        let avail_features =
+            1 << vhost::VIRTIO_F_NOTIFY_ON_EMPTY | 1 << vhost::VIRTIO_RING_F_INDIRECT_DESC |
+                1 << vhost::VIRTIO_RING_F_EVENT_IDX | 1 << vhost::VHOST_F_LOG_ALL |
+                1 << vhost::VIRTIO_F_ANY_LAYOUT | 1 << vhost::VIRTIO_F_VERSION_1;
+
+        Ok(Vsock {
+            worker_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEventFd)?),
+            kill_evt: Some(kill_evt),
+            vhost_handle: Some(handle),
+            cid: cid,
+            interrupt: Some(EventFd::new().map_err(Error::VhostIrqCreate)?),
+            avail_features: avail_features,
+            acked_features: 0,
+        })
+    }
+
+    pub fn new_for_testing(cid: u64, features: u64) -> Vsock {
+        Vsock {
+            worker_kill_evt: None,
+            kill_evt: None,
+            vhost_handle: None,
+            cid: cid,
+            interrupt: None,
+            avail_features: features,
+            acked_features: 0,
+        }
+    }
+
+    pub fn acked_features(&self) -> u64 {
+        self.acked_features
+    }
+}
+
+impl Drop for Vsock {
+    fn drop(&mut self) {
+        // Only kill the child if it claimed its eventfd.
+        if self.worker_kill_evt.is_none() {
+            if let Some(ref kill_evt) = self.kill_evt {
+                // Ignore the result because there is nothing we can do about it.
+                let _ = kill_evt.write(1);
+            }
+        }
+    }
+}
+
+impl VirtioDevice for Vsock {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        let mut keep_fds = Vec::new();
+
+        if let Some(ref handle) = self.vhost_handle {
+            keep_fds.push(handle.as_raw_fd());
+        }
+
+        if let Some(ref interrupt) = self.interrupt {
+            keep_fds.push(interrupt.as_raw_fd());
+        }
+
+        if let Some(ref worker_kill_evt) = self.worker_kill_evt {
+            keep_fds.push(worker_kill_evt.as_raw_fd());
+        }
+
+        keep_fds
+    }
+
+    fn device_type(&self) -> u32 {
+        TYPE_VSOCK
+    }
+
+    fn queue_max_sizes(&self) -> &[u16] {
+        QUEUE_SIZES
+    }
+
+    fn features(&self, page: u32) -> u32 {
+        match page {
+            // Get the lower 32-bits of the features bitfield.
+            0 => self.avail_features as u32,
+            // Get the upper 32-bits of the features bitfield.
+            1 => (self.avail_features >> 32) as u32,
+            _ => {
+                warn!(
+                    "vsock: virtio-vsock got request for features page: {}",
+                    page
+                );
+                0u32
+            },
+        }
+    }
+
+    fn read_config(&self, offset: u64, data: &mut [u8]) {
+        match offset {
+            0 if data.len() == 8 => LittleEndian::write_u64(data, self.cid),
+            0 if data.len() == 4 => LittleEndian::write_u32(data, (self.cid & 0xffffffff) as u32),
+            4 if data.len() == 4 => {
+                LittleEndian::write_u32(data, ((self.cid >> 32) & 0xffffffff) as u32)
+            },
+            _ => warn!(
+                "vsock: virtio-vsock received invalid read request of {} bytes at offset {}",
+                data.len(),
+                offset
+            ),
+        }
+    }
+
+    fn ack_features(&mut self, page: u32, value: u32) {
+        let mut v = match page {
+            0 => value as u64,
+            1 => (value as u64) << 32,
+            _ => {
+                warn!(
+                    "vsock: virtio-vsock device cannot ack unknown feature page: {}",
+                    page
+                );
+                0u64
+            },
+        };
+
+        // Check if the guest is ACK'ing a feature that we didn't claim to have.
+        let unrequested_features = v & !self.avail_features;
+        if unrequested_features != 0 {
+            warn!("vsock: virtio-vsock got unknown feature ack: {:x}", v);
+
+            // Don't count these features as acked.
+            v &= !unrequested_features;
+        }
+        self.acked_features |= v;
+    }
+
+    fn activate(
+        &mut self,
+        _: GuestMemory,
+        interrupt_evt: EventFd,
+        status: Arc<AtomicUsize>,
+        queues: Vec<Queue>,
+        queue_evts: Vec<EventFd>,
+    ) {
+        if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
+            error!("net: expected {} queues, got {}", NUM_QUEUES, queues.len());
+            return;
+        }
+
+        if let Some(vhost_handle) = self.vhost_handle.take() {
+            if let Some(interrupt) = self.interrupt.take() {
+                if let Some(kill_evt) = self.worker_kill_evt.take() {
+                    let acked_features = self.acked_features;
+                    let cid = self.cid;
+                    spawn(move || {
+                        // The third vq is an event-only vq that is not handled by the vhost
+                        // subsystem (but still needs to exist).  Split it off here.
+                        let vhost_queues = queues[..2].to_vec();
+                        let mut worker = Worker::new(
+                            vhost_queues,
+                            vhost_handle,
+                            interrupt,
+                            status,
+                            interrupt_evt,
+                            acked_features,
+                        );
+                        let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> {
+                            handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?;
+                            handle.start().map_err(Error::VhostVsockStart)?;
+                            Ok(())
+                        };
+                        let result = worker.run(queue_evts, QUEUE_SIZES, kill_evt, activate_vqs);
+                        if let Err(e) = result {
+                            error!("vsock worker thread exited with error: {:?}", e);
+                        }
+                    });
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use byteorder::{ByteOrder, LittleEndian};
+    #[test]
+    fn ack_features() {
+        let cid = 5;
+        let features: u64 = (1 << 20) | (1 << 49) | (1 << 2) | (1 << 19);
+        let mut acked_features: u64 = 0;
+        let mut unavailable_features: u64 = 0;
+
+        let mut vsock = Vsock::new_for_testing(cid, features);
+        assert_eq!(acked_features, vsock.acked_features());
+
+        acked_features |= 1 << 2;
+        vsock.ack_features(0, (acked_features & 0xffffffff) as u32);
+        assert_eq!(acked_features, vsock.acked_features());
+
+        acked_features |= 1 << 49;
+        vsock.ack_features(1, (acked_features >> 32) as u32);
+        assert_eq!(acked_features, vsock.acked_features());
+
+        acked_features |= 1 << 60;
+        unavailable_features |= 1 << 60;
+        vsock.ack_features(1, (acked_features >> 32) as u32);
+        assert_eq!(acked_features & !unavailable_features, vsock.acked_features());
+
+        acked_features |= 1 << 1;
+        unavailable_features |= 1 << 1;
+        vsock.ack_features(0, (acked_features & 0xffffffff) as u32);
+        assert_eq!(acked_features & !unavailable_features, vsock.acked_features());
+    }
+
+    #[test]
+    fn read_config() {
+        let cid = 0xfca9a559fdcb9756;
+        let vsock = Vsock::new_for_testing(cid, 0);
+
+        let mut buf = [0 as u8; 8];
+        vsock.read_config(0, &mut buf);
+        assert_eq!(cid, LittleEndian::read_u64(&buf));
+
+        vsock.read_config(0, &mut buf[..4]);
+        assert_eq!((cid & 0xffffffff) as u32, LittleEndian::read_u32(&buf[..4]));
+
+        vsock.read_config(4, &mut buf[..4]);
+        assert_eq!((cid >> 32) as u32, LittleEndian::read_u32(&buf[..4]));
+
+        let data: [u8; 8] = [8, 226, 5, 46, 159, 59, 89, 77];
+        buf.copy_from_slice(&data);
+
+        vsock.read_config(12, &mut buf);
+        assert_eq!(&buf, &data);
+    }
+
+    #[test]
+    fn features() {
+        let cid = 5;
+        let features: u64 = 0xfc195ae8db88cff9;
+
+        let vsock = Vsock::new_for_testing(cid, features);
+        assert_eq!((features & 0xffffffff) as u32, vsock.features(0));
+        assert_eq!((features >> 32) as u32, vsock.features(1));
+        assert_eq!(0, vsock.features(559));
+        assert_eq!(0, vsock.features(3));
+    }
+}
diff --git a/devices/src/virtio/vhost/worker.rs b/devices/src/virtio/vhost/worker.rs
new file mode 100644
index 0000000..12d879a
--- /dev/null
+++ b/devices/src/virtio/vhost/worker.rs
@@ -0,0 +1,132 @@
+// Copyright 2017 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 std::os::raw::c_ulonglong;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use sys_util::{EventFd, Poller};
+use vhost::Vhost;
+
+use super::{Error, Result};
+use super::super::{Queue, INTERRUPT_STATUS_USED_RING};
+
+/// 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> {
+    queues: Vec<Queue>,
+    vhost_handle: T,
+    vhost_interrupt: EventFd,
+    interrupt_status: Arc<AtomicUsize>,
+    interrupt_evt: EventFd,
+    acked_features: u64,
+}
+
+impl<T: Vhost> Worker<T> {
+    pub fn new(
+        queues: Vec<Queue>,
+        vhost_handle: T,
+        vhost_interrupt: EventFd,
+        interrupt_status: Arc<AtomicUsize>,
+        interrupt_evt: EventFd,
+        acked_features: u64,
+    ) -> Worker<T> {
+        Worker {
+            queues: queues,
+            vhost_handle: vhost_handle,
+            vhost_interrupt: vhost_interrupt,
+            interrupt_status: interrupt_status,
+            interrupt_evt: interrupt_evt,
+            acked_features: acked_features,
+        }
+    }
+
+    fn signal_used_queue(&self) {
+        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>,
+        queue_sizes: &[u16],
+        kill_evt: EventFd,
+        activate_vqs: F,
+    ) -> Result<()>
+    where
+        F: FnOnce(&T) -> Result<()>,
+    {
+        // Preliminary setup for vhost net.
+        self.vhost_handle.set_owner().map_err(Error::VhostSetOwner)?;
+
+        let avail_features = self.vhost_handle.get_features().map_err(Error::VhostGetFeatures)?;
+
+        let features: c_ulonglong = self.acked_features & avail_features;
+        self.vhost_handle.set_features(features).map_err(Error::VhostSetFeatures)?;
+
+        self.vhost_handle.set_mem_table().map_err(Error::VhostSetMemTable)?;
+
+        for (queue_index, ref queue) in self.queues.iter().enumerate() {
+            self.vhost_handle
+                .set_vring_num(queue_index, queue.max_size)
+                .map_err(Error::VhostSetVringNum)?;
+
+            self.vhost_handle
+                .set_vring_addr(
+                    queue_sizes[queue_index],
+                    queue.actual_size(),
+                    queue_index,
+                    0,
+                    queue.desc_table,
+                    queue.used_ring,
+                    queue.avail_ring,
+                    None,
+                )
+                .map_err(Error::VhostSetVringAddr)?;
+            self.vhost_handle
+                .set_vring_base(queue_index, 0)
+                .map_err(Error::VhostSetVringBase)?;
+            self.vhost_handle
+                .set_vring_call(queue_index, &self.vhost_interrupt)
+                .map_err(Error::VhostSetVringCall)?;
+            self.vhost_handle
+                .set_vring_kick(queue_index, &queue_evts[queue_index])
+                .map_err(Error::VhostSetVringKick)?;
+        }
+
+        activate_vqs(&self.vhost_handle)?;
+
+        const VHOST_IRQ: u32 = 1;
+        const KILL: u32 = 2;
+
+        let mut poller = Poller::new(2);
+
+        'poll: loop {
+            let tokens = match poller.poll(&[(VHOST_IRQ, &self.vhost_interrupt), (KILL, &kill_evt)])
+            {
+                Ok(v) => v,
+                Err(e) => return Err(Error::PollError(e)),
+            };
+
+            let mut needs_interrupt = false;
+            for &token in tokens {
+                match token {
+                    VHOST_IRQ => {
+                        needs_interrupt = true;
+                        self.vhost_interrupt.read().map_err(Error::VhostIrqRead)?;
+                    },
+                    KILL => break 'poll,
+                    _ => unreachable!(),
+                }
+            }
+            if needs_interrupt {
+                self.signal_used_queue();
+            }
+        }
+        Ok(())
+    }
+}