diff --git a/virtio-devices/src/epoll_helper.rs b/virtio-devices/src/epoll_helper.rs new file mode 100644 index 000000000..ec893edb5 --- /dev/null +++ b/virtio-devices/src/epoll_helper.rs @@ -0,0 +1,134 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Portions 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-BSD-3-Clause file. +// +// Copyright © 2020 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +use std::fs::File; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use vmm_sys_util::eventfd::EventFd; + +pub struct EpollHelper { + pause_evt: EventFd, + epoll_file: File, +} + +#[derive(Debug)] +pub enum EpollHelperError { + CreateFd(std::io::Error), + Ctl(std::io::Error), + Wait(std::io::Error), +} + +pub const EPOLL_HELPER_EVENT_PAUSE: u16 = 0; +pub const EPOLL_HELPER_EVENT_KILL: u16 = 1; +pub const EPOLL_HELPER_EVENT_LAST: u16 = EPOLL_HELPER_EVENT_KILL; + +pub trait EpollHelperHandler { + // Return true if execution of the loop should be stopped + fn handle_event(&mut self, helper: &mut EpollHelper, event: u16) -> bool; +} + +impl EpollHelper { + pub fn new( + kill_evt: &EventFd, + pause_evt: &EventFd, + ) -> std::result::Result { + // Create the epoll file descriptor + let epoll_fd = epoll::create(true).map_err(EpollHelperError::CreateFd)?; + // Use 'File' to enforce closing on 'epoll_fd' + let epoll_file = unsafe { File::from_raw_fd(epoll_fd) }; + + let mut helper = Self { + pause_evt: pause_evt.try_clone().unwrap(), + epoll_file, + }; + + helper.add_event(kill_evt.as_raw_fd(), EPOLL_HELPER_EVENT_KILL)?; + helper.add_event(pause_evt.as_raw_fd(), EPOLL_HELPER_EVENT_PAUSE)?; + Ok(helper) + } + + pub fn add_event(&mut self, fd: RawFd, id: u16) -> std::result::Result<(), EpollHelperError> { + epoll::ctl( + self.epoll_file.as_raw_fd(), + epoll::ControlOptions::EPOLL_CTL_ADD, + fd, + epoll::Event::new(epoll::Events::EPOLLIN, id.into()), + ) + .map_err(EpollHelperError::Ctl) + } + + pub fn run( + &mut self, + paused: Arc, + handler: &mut dyn EpollHelperHandler, + ) -> std::result::Result<(), EpollHelperError> { + const EPOLL_EVENTS_LEN: usize = 100; + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + + // Before jumping into the epoll loop, check if the device is expected + // to be in a paused state. This is helpful for the restore code path + // as the device thread should not start processing anything before the + // device has been resumed. + while paused.load(Ordering::SeqCst) { + thread::park(); + } + + loop { + let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) { + Ok(res) => res, + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + // It's well defined from the epoll_wait() syscall + // documentation that the epoll loop can be interrupted + // before any of the requested events occurred or the + // timeout expired. In both those cases, epoll_wait() + // returns an error of type EINTR, but this should not + // be considered as a regular error. Instead it is more + // appropriate to retry, by calling into epoll_wait(). + continue; + } + return Err(EpollHelperError::Wait(e)); + } + }; + + for event in events.iter().take(num_events) { + let ev_type = event.data as u16; + + match ev_type { + EPOLL_HELPER_EVENT_KILL => { + debug!("KILL_EVENT received, stopping epoll loop"); + return Ok(()); + } + EPOLL_HELPER_EVENT_PAUSE => { + debug!("PAUSE_EVENT received, pausing epoll loop"); + // We loop here to handle spurious park() returns. + // Until we have not resumed, the paused boolean will + // be true. + while paused.load(Ordering::SeqCst) { + thread::park(); + } + + // Drain pause event after the device has been resumed. + // This ensures the pause event has been seen by each + // and every thread related to this virtio device. + let _ = self.pause_evt.read(); + } + id => { + if handler.handle_event(self, id) { + return Ok(()); + } + } + } + } + } + } +} diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index f4960517e..0841285ec 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -32,6 +32,7 @@ mod device; pub mod balloon; pub mod block; mod console; +pub mod epoll_helper; mod iommu; pub mod mem; pub mod net; @@ -46,6 +47,7 @@ pub use self::balloon::*; pub use self::block::*; pub use self::console::*; pub use self::device::*; +pub use self::epoll_helper::*; pub use self::iommu::*; pub use self::mem::*; pub use self::net::*;