xref: /cloud-hypervisor/virtio-devices/src/epoll_helper.rs (revision 7d7bfb2034001d4cb15df2ddc56d2d350c8da30f)
1 // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 //
3 // Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
4 // Use of this source code is governed by a BSD-style license that can be
5 // found in the LICENSE-BSD-3-Clause file.
6 //
7 // Copyright © 2020 Intel Corporation
8 //
9 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
10 
11 use std::fs::File;
12 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
13 use std::sync::atomic::{AtomicBool, Ordering};
14 use std::sync::{Arc, Barrier};
15 use std::thread;
16 use vmm_sys_util::eventfd::EventFd;
17 
18 pub struct EpollHelper {
19     pause_evt: EventFd,
20     epoll_file: File,
21 }
22 
23 #[derive(Debug)]
24 pub enum EpollHelperError {
25     CreateFd(std::io::Error),
26     Ctl(std::io::Error),
27     IoError(std::io::Error),
28     Wait(std::io::Error),
29     QueueRingIndex(virtio_queue::Error),
30 }
31 
32 pub const EPOLL_HELPER_EVENT_PAUSE: u16 = 0;
33 pub const EPOLL_HELPER_EVENT_KILL: u16 = 1;
34 pub const EPOLL_HELPER_EVENT_LAST: u16 = 15;
35 
36 pub trait EpollHelperHandler {
37     // Return true if the loop execution should be stopped
38     fn handle_event(&mut self, helper: &mut EpollHelper, event: &epoll::Event) -> bool;
39 }
40 
41 impl EpollHelper {
42     pub fn new(
43         kill_evt: &EventFd,
44         pause_evt: &EventFd,
45     ) -> std::result::Result<Self, EpollHelperError> {
46         // Create the epoll file descriptor
47         let epoll_fd = epoll::create(true).map_err(EpollHelperError::CreateFd)?;
48         // Use 'File' to enforce closing on 'epoll_fd'
49         let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
50 
51         let mut helper = Self {
52             pause_evt: pause_evt.try_clone().unwrap(),
53             epoll_file,
54         };
55 
56         helper.add_event(kill_evt.as_raw_fd(), EPOLL_HELPER_EVENT_KILL)?;
57         helper.add_event(pause_evt.as_raw_fd(), EPOLL_HELPER_EVENT_PAUSE)?;
58         Ok(helper)
59     }
60 
61     pub fn add_event(&mut self, fd: RawFd, id: u16) -> std::result::Result<(), EpollHelperError> {
62         self.add_event_custom(fd, id, epoll::Events::EPOLLIN)
63     }
64 
65     pub fn add_event_custom(
66         &mut self,
67         fd: RawFd,
68         id: u16,
69         evts: epoll::Events,
70     ) -> std::result::Result<(), EpollHelperError> {
71         epoll::ctl(
72             self.epoll_file.as_raw_fd(),
73             epoll::ControlOptions::EPOLL_CTL_ADD,
74             fd,
75             epoll::Event::new(evts, id.into()),
76         )
77         .map_err(EpollHelperError::Ctl)
78     }
79 
80     pub fn del_event_custom(
81         &mut self,
82         fd: RawFd,
83         id: u16,
84         evts: epoll::Events,
85     ) -> std::result::Result<(), EpollHelperError> {
86         epoll::ctl(
87             self.epoll_file.as_raw_fd(),
88             epoll::ControlOptions::EPOLL_CTL_DEL,
89             fd,
90             epoll::Event::new(evts, id.into()),
91         )
92         .map_err(EpollHelperError::Ctl)
93     }
94 
95     pub fn run(
96         &mut self,
97         paused: Arc<AtomicBool>,
98         paused_sync: Arc<Barrier>,
99         handler: &mut dyn EpollHelperHandler,
100     ) -> std::result::Result<(), EpollHelperError> {
101         const EPOLL_EVENTS_LEN: usize = 100;
102         let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
103 
104         // Before jumping into the epoll loop, check if the device is expected
105         // to be in a paused state. This is helpful for the restore code path
106         // as the device thread should not start processing anything before the
107         // device has been resumed.
108         while paused.load(Ordering::SeqCst) {
109             thread::park();
110         }
111 
112         loop {
113             let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) {
114                 Ok(res) => res,
115                 Err(e) => {
116                     if e.kind() == std::io::ErrorKind::Interrupted {
117                         // It's well defined from the epoll_wait() syscall
118                         // documentation that the epoll loop can be interrupted
119                         // before any of the requested events occurred or the
120                         // timeout expired. In both those cases, epoll_wait()
121                         // returns an error of type EINTR, but this should not
122                         // be considered as a regular error. Instead it is more
123                         // appropriate to retry, by calling into epoll_wait().
124                         continue;
125                     }
126                     return Err(EpollHelperError::Wait(e));
127                 }
128             };
129 
130             for event in events.iter().take(num_events) {
131                 let ev_type = event.data as u16;
132 
133                 match ev_type {
134                     EPOLL_HELPER_EVENT_KILL => {
135                         info!("KILL_EVENT received, stopping epoll loop");
136                         return Ok(());
137                     }
138                     EPOLL_HELPER_EVENT_PAUSE => {
139                         info!("PAUSE_EVENT received, pausing epoll loop");
140 
141                         // Acknowledge the pause is effective by using the
142                         // paused_sync barrier.
143                         paused_sync.wait();
144 
145                         // We loop here to handle spurious park() returns.
146                         // Until we have not resumed, the paused boolean will
147                         // be true.
148                         while paused.load(Ordering::SeqCst) {
149                             thread::park();
150                         }
151 
152                         // Drain pause event after the device has been resumed.
153                         // This ensures the pause event has been seen by each
154                         // thread related to this virtio device.
155                         let _ = self.pause_evt.read();
156                     }
157                     _ => {
158                         if handler.handle_event(self, event) {
159                             return Ok(());
160                         }
161                     }
162                 }
163             }
164         }
165     }
166 }
167 
168 impl AsRawFd for EpollHelper {
169     fn as_raw_fd(&self) -> RawFd {
170         self.epoll_file.as_raw_fd()
171     }
172 }
173