• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::sync::Arc;
6 
7 use anyhow::bail;
8 use anyhow::Context;
9 use base::info;
10 use base::warn;
11 #[cfg(windows)]
12 use base::CloseNotifier;
13 use base::Event;
14 use base::EventToken;
15 use base::EventType;
16 use base::ReadNotifier;
17 use base::WaitContext;
18 use sync::Mutex;
19 use vmm_vhost::BackendClient;
20 use vmm_vhost::Error as VhostError;
21 
22 use crate::virtio::vhost_user_frontend::handler::BackendReqHandler;
23 use crate::virtio::Interrupt;
24 use crate::virtio::VIRTIO_MSI_NO_VECTOR;
25 
26 pub struct Worker {
27     pub kill_evt: Event,
28     pub non_msix_evt: Event,
29     pub backend_req_handler: Option<BackendReqHandler>,
30     pub backend_client: Arc<Mutex<BackendClient>>,
31 }
32 
33 impl Worker {
run(&mut self, interrupt: Interrupt) -> anyhow::Result<()>34     pub fn run(&mut self, interrupt: Interrupt) -> anyhow::Result<()> {
35         #[derive(EventToken)]
36         enum Token {
37             Kill,
38             NonMsixEvt,
39             ReqHandlerRead,
40             #[cfg(target_os = "windows")]
41             ReqHandlerClose,
42             // monitor whether backend_client_fd is broken
43             BackendCloseNotify,
44         }
45         let wait_ctx = WaitContext::build_with(&[
46             (&self.non_msix_evt, Token::NonMsixEvt),
47             (&self.kill_evt, Token::Kill),
48         ])
49         .context("failed to build WaitContext")?;
50 
51         if let Some(backend_req_handler) = self.backend_req_handler.as_mut() {
52             wait_ctx
53                 .add(
54                     backend_req_handler.get_read_notifier(),
55                     Token::ReqHandlerRead,
56                 )
57                 .context("failed to add backend req handler to WaitContext")?;
58 
59             #[cfg(target_os = "windows")]
60             wait_ctx
61                 .add(
62                     backend_req_handler.get_close_notifier(),
63                     Token::ReqHandlerClose,
64                 )
65                 .context("failed to add backend req handler close notifier to WaitContext")?;
66         }
67 
68         #[cfg(any(target_os = "android", target_os = "linux"))]
69         wait_ctx
70             .add_for_event(
71                 self.backend_client.lock().get_read_notifier(),
72                 EventType::None,
73                 Token::BackendCloseNotify,
74             )
75             .context("failed to add backend client close notifier to WaitContext")?;
76         #[cfg(target_os = "windows")]
77         wait_ctx
78             .add(
79                 self.backend_client.lock().get_close_notifier(),
80                 Token::BackendCloseNotify,
81             )
82             .context("failed to add backend client close notifier to WaitContext")?;
83 
84         'wait: loop {
85             let events = wait_ctx.wait().context("WaitContext::wait() failed")?;
86             for event in events {
87                 match event.token {
88                     Token::Kill => {
89                         break 'wait;
90                     }
91                     Token::NonMsixEvt => {
92                         // The vhost-user protocol allows the backend to signal events, but for
93                         // non-MSI-X devices, a device must also update the interrupt status mask.
94                         // `non_msix_evt` proxies events from the vhost-user backend to update the
95                         // status mask.
96                         let _ = self.non_msix_evt.wait();
97 
98                         // The parameter vector of signal_used_queue is used only when msix is
99                         // enabled.
100                         interrupt.signal_used_queue(VIRTIO_MSI_NO_VECTOR);
101                     }
102                     Token::ReqHandlerRead => {
103                         let Some(backend_req_handler) = self.backend_req_handler.as_mut() else {
104                             continue;
105                         };
106 
107                         match backend_req_handler.handle_request() {
108                             Ok(_) => (),
109                             Err(VhostError::ClientExit) | Err(VhostError::Disconnect) => {
110                                 info!("backend req handler connection closed");
111                                 // Stop monitoring `backend_req_handler` as the client closed
112                                 // the connection.
113                                 let _ = wait_ctx.delete(backend_req_handler.get_read_notifier());
114                                 #[cfg(target_os = "windows")]
115                                 let _ = wait_ctx.delete(backend_req_handler.get_close_notifier());
116                                 self.backend_req_handler = None;
117                             }
118                             Err(e) => return Err(e).context("failed to handle vhost-user request"),
119                         }
120                     }
121                     #[cfg(target_os = "windows")]
122                     Token::ReqHandlerClose => {
123                         let Some(backend_req_handler) = self.backend_req_handler.as_mut() else {
124                             continue;
125                         };
126 
127                         info!("backend req handler connection closed");
128                         let _ = wait_ctx.delete(backend_req_handler.get_read_notifier());
129                         let _ = wait_ctx.delete(backend_req_handler.get_close_notifier());
130                         self.backend_req_handler = None;
131                     }
132                     Token::BackendCloseNotify => {
133                         // For linux domain socket, the close notifier fd is same with read/write
134                         // notifier We need check whether the event is caused by socket broken.
135                         #[cfg(any(target_os = "android", target_os = "linux"))]
136                         if !event.is_hungup {
137                             warn!("event besides hungup should not be notified");
138                             continue;
139                         }
140                         bail!("Backend device disconnected early");
141                     }
142                 }
143             }
144         }
145 
146         Ok(())
147     }
148 }
149