virtio-devices: Introduce EpollHelper

This is a helper for implementing the worker thread for virtio devices
and in particular handles special behaviour for pause and kill events.

The device specific event handling (for the queues themselves) is
delegated to a method invoked on a new EpollHelperHandler trait. This
method is passed the event as well as the EpollHelper so that it can
operate on the handler in order to manage events itself (required for
virtio-net.)

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2020-07-22 14:34:36 +01:00
parent aae5d988e1
commit 7d9dc4013e
2 changed files with 136 additions and 0 deletions

View File

@ -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<Self, EpollHelperError> {
// 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<AtomicBool>,
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(());
}
}
}
}
}
}
}

View File

@ -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::*;