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