devices/virtio/vhost_user_frontend/
worker.rs

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