diff options
author | Dylan Reid <dgreid@chromium.org> | 2017-10-06 15:26:46 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-10-09 17:39:05 -0700 |
commit | d169a8d9ed63ae33b8678821c9675963bcee3f92 (patch) | |
tree | 03f436cbaeeb74f8fa49e02232162fabed442ba5 /devices/src/virtio/vhost | |
parent | 94bf1bf6b4a7791757937e2ffb2a81e797b82922 (diff) | |
download | crosvm-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.rs | 72 | ||||
-rw-r--r-- | devices/src/virtio/vhost/net.rs | 204 | ||||
-rw-r--r-- | devices/src/virtio/vhost/vsock.rs | 280 | ||||
-rw-r--r-- | devices/src/virtio/vhost/worker.rs | 132 |
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(()) + } +} |