devices/virtio/vhost_user_backend/
gpu.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
5pub mod sys;
6
7use std::cell::RefCell;
8use std::rc::Rc;
9use std::sync::Arc;
10
11use anyhow::anyhow;
12use anyhow::bail;
13use anyhow::Context;
14use base::error;
15use base::warn;
16use base::Tube;
17use cros_async::EventAsync;
18use cros_async::Executor;
19use cros_async::TaskHandle;
20use futures::FutureExt;
21use futures::StreamExt;
22use snapshot::AnySnapshot;
23use sync::Mutex;
24pub use sys::run_gpu_device;
25pub use sys::Options;
26use vm_memory::GuestMemory;
27use vmm_vhost::message::VhostUserProtocolFeatures;
28use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
29
30use crate::virtio::device_constants::gpu::NUM_QUEUES;
31use crate::virtio::gpu;
32use crate::virtio::gpu::QueueReader;
33use crate::virtio::vhost_user_backend::handler::Error as DeviceError;
34use crate::virtio::vhost_user_backend::handler::VhostBackendReqConnection;
35use crate::virtio::vhost_user_backend::handler::VhostUserDevice;
36use crate::virtio::vhost_user_backend::handler::WorkerState;
37use crate::virtio::DescriptorChain;
38use crate::virtio::Gpu;
39use crate::virtio::Queue;
40use crate::virtio::SharedMemoryMapper;
41use crate::virtio::SharedMemoryRegion;
42use crate::virtio::VirtioDevice;
43
44const MAX_QUEUE_NUM: usize = NUM_QUEUES;
45
46#[derive(Clone)]
47struct SharedReader {
48    queue: Arc<Mutex<Queue>>,
49}
50
51impl gpu::QueueReader for SharedReader {
52    fn pop(&self) -> Option<DescriptorChain> {
53        self.queue.lock().pop()
54    }
55
56    fn add_used(&self, desc_chain: DescriptorChain, len: u32) {
57        self.queue
58            .lock()
59            .add_used_with_bytes_written(desc_chain, len)
60    }
61
62    fn signal_used(&self) {
63        self.queue.lock().trigger_interrupt();
64    }
65}
66
67async fn run_ctrl_queue(
68    reader: SharedReader,
69    mem: GuestMemory,
70    kick_evt: EventAsync,
71    state: Rc<RefCell<gpu::Frontend>>,
72) {
73    loop {
74        if let Err(e) = kick_evt.next_val().await {
75            error!("Failed to read kick event for ctrl queue: {}", e);
76            break;
77        }
78
79        let mut state = state.borrow_mut();
80        let needs_interrupt = state.process_queue(&mem, &reader);
81
82        if needs_interrupt {
83            reader.signal_used();
84        }
85    }
86}
87
88struct GpuBackend {
89    ex: Executor,
90    gpu: Rc<RefCell<Gpu>>,
91    resource_bridges: Arc<Mutex<Vec<Tube>>>,
92    state: Option<Rc<RefCell<gpu::Frontend>>>,
93    fence_state: Arc<Mutex<gpu::FenceState>>,
94    queue_workers: [Option<WorkerState<Arc<Mutex<Queue>>, ()>>; MAX_QUEUE_NUM],
95    // In the downstream, we may add platform workers after start_platform_workers returns.
96    platform_worker_tx: futures::channel::mpsc::UnboundedSender<TaskHandle<()>>,
97    platform_worker_rx: futures::channel::mpsc::UnboundedReceiver<TaskHandle<()>>,
98    shmem_mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
99    /// Frontend state shared with the GPU control socket handlers, which live for the lifetime
100    /// of the process. It is `None` while the device is stopped, suspended or reset.
101    #[cfg(any(target_os = "android", target_os = "linux"))]
102    gpu_control_state: sys::linux::SharedGpuControlState,
103}
104
105impl GpuBackend {
106    fn stop_non_queue_workers(&mut self) -> anyhow::Result<()> {
107        // Drop the frontend state so that it can be destroyed along with the workers. The GPU
108        // control socket handlers outlive them and report ENODEV until the device starts again.
109        #[cfg(any(target_os = "android", target_os = "linux"))]
110        self.gpu_control_state.borrow_mut().take();
111
112        self.ex
113            .run_until(async {
114                while let Some(Some(handle)) = self.platform_worker_rx.next().now_or_never() {
115                    handle.cancel().await;
116                }
117            })
118            .context("stopping the non-queue workers for GPU")?;
119        Ok(())
120    }
121}
122
123impl VhostUserDevice for GpuBackend {
124    fn max_queue_num(&self) -> usize {
125        MAX_QUEUE_NUM
126    }
127
128    fn features(&self) -> u64 {
129        self.gpu.borrow().features() | 1 << VHOST_USER_F_PROTOCOL_FEATURES
130    }
131
132    fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
133        self.gpu.borrow_mut().ack_features(value);
134        Ok(())
135    }
136
137    fn protocol_features(&self) -> VhostUserProtocolFeatures {
138        VhostUserProtocolFeatures::CONFIG
139            | VhostUserProtocolFeatures::BACKEND_REQ
140            | VhostUserProtocolFeatures::MQ
141            | VhostUserProtocolFeatures::SHMEM
142            | VhostUserProtocolFeatures::DEVICE_STATE
143    }
144
145    fn read_config(&self, offset: u64, dst: &mut [u8]) {
146        self.gpu.borrow().read_config(offset, dst)
147    }
148
149    fn write_config(&self, offset: u64, data: &[u8]) {
150        self.gpu.borrow_mut().write_config(offset, data)
151    }
152
153    fn start_queue(&mut self, idx: usize, queue: Queue, mem: GuestMemory) -> anyhow::Result<()> {
154        if self.queue_workers[idx].is_some() {
155            warn!("Starting new queue handler without stopping old handler");
156            self.stop_queue(idx)?;
157        }
158
159        let doorbell = queue.interrupt().clone();
160
161        // Create a refcounted queue. The GPU control queue uses a SharedReader which allows us to
162        // handle fences in the RutabagaFenceHandler, and also handle queue messages in
163        // `run_ctrl_queue`.
164        // For the cursor queue, we still create the refcounted queue to support retrieving the
165        // queue for snapshotting (but don't handle any messages).
166        let queue = Arc::new(Mutex::new(queue));
167
168        // Spawn a worker for the queue.
169        let queue_task = match idx {
170            0 => {
171                // Set up worker for the control queue.
172                let kick_evt = queue
173                    .lock()
174                    .event()
175                    .try_clone()
176                    .context("failed to clone queue event")?;
177                let kick_evt = EventAsync::new(kick_evt, &self.ex)
178                    .context("failed to create EventAsync for kick_evt")?;
179                let reader = SharedReader {
180                    queue: queue.clone(),
181                };
182
183                let state = if let Some(s) = self.state.as_ref() {
184                    s.clone()
185                } else {
186                    let fence_handler_resources =
187                        Arc::new(Mutex::new(Some(gpu::FenceHandlerActivationResources {
188                            mem: mem.clone(),
189                            ctrl_queue: reader.clone(),
190                        })));
191                    let fence_handler = gpu::create_fence_handler(
192                        fence_handler_resources,
193                        self.fence_state.clone(),
194                    );
195
196                    let state = Rc::new(RefCell::new(
197                        self.gpu
198                            .borrow_mut()
199                            .initialize_frontend(
200                                self.fence_state.clone(),
201                                fence_handler,
202                                Arc::clone(&self.shmem_mapper),
203                            )
204                            .ok_or_else(|| anyhow!("failed to initialize gpu frontend"))?,
205                    ));
206                    self.state = Some(state.clone());
207                    state
208                };
209
210                // Start handling platform-specific workers.
211                self.start_platform_workers(doorbell)?;
212
213                // Start handling the control queue.
214                self.ex
215                    .spawn_local(run_ctrl_queue(reader, mem, kick_evt, state))
216            }
217            1 => {
218                // For the cursor queue, spawn an empty worker, as we don't process it at all.
219                // We don't handle the cursor queue because no current users of vhost-user GPU pass
220                // any messages on it.
221                self.ex.spawn_local(async {})
222            }
223            _ => bail!("attempted to start unknown queue: {}", idx),
224        };
225
226        self.queue_workers[idx] = Some(WorkerState { queue_task, queue });
227        Ok(())
228    }
229
230    fn stop_queue(&mut self, idx: usize) -> anyhow::Result<Queue> {
231        if let Some(worker) = self.queue_workers.get_mut(idx).and_then(Option::take) {
232            // Wait for queue_task to be aborted.
233            let _ = self.ex.run_until(worker.queue_task.cancel());
234
235            if idx == 0 {
236                // Stop the non-queue workers if this is the control queue (where we start them).
237                self.stop_non_queue_workers()?;
238
239                // After we stop all workers, we have only one reference left to self.state.
240                // Clearing it allows the GPU state to be destroyed, which gets rid of the
241                // remaining control queue reference from RutabagaFenceHandler.
242                // This allows our worker.queue to be recovered as it has no further references.
243                self.state = None;
244            }
245
246            let queue = match Arc::try_unwrap(worker.queue) {
247                Ok(queue_mutex) => queue_mutex.into_inner(),
248                Err(_) => panic!("failed to recover queue from worker"),
249            };
250
251            Ok(queue)
252        } else {
253            Err(anyhow::Error::new(DeviceError::WorkerNotFound))
254        }
255    }
256
257    fn enter_suspended_state(&mut self) -> anyhow::Result<()> {
258        self.stop_non_queue_workers()?;
259        Ok(())
260    }
261
262    fn reset(&mut self) {
263        self.stop_non_queue_workers()
264            .expect("Failed to stop platform workers.");
265
266        for queue_num in 0..self.max_queue_num() {
267            // The cursor queue is never used, so we should check if the queue is set before
268            // stopping.
269            if self.queue_workers[queue_num].is_some() {
270                if let Err(e) = self.stop_queue(queue_num) {
271                    error!("Failed to stop_queue during reset: {}", e);
272                }
273            }
274        }
275    }
276
277    fn get_shared_memory_region(&self) -> Option<SharedMemoryRegion> {
278        self.gpu.borrow().get_shared_memory_region()
279    }
280
281    fn set_backend_req_connection(&mut self, conn: VhostBackendReqConnection) {
282        if self
283            .shmem_mapper
284            .lock()
285            .replace(conn.shmem_mapper().unwrap())
286            .is_some()
287        {
288            warn!("Connection already established. Overwriting shmem_mapper");
289        }
290    }
291
292    fn snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
293        // TODO(b/289431114): Snapshot more fields if needed. Right now we just need a bare bones
294        // snapshot of the GPU to create a POC.
295        AnySnapshot::to_any(())
296    }
297
298    fn restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
299        let () = AnySnapshot::from_any(data)?;
300        Ok(())
301    }
302}
303
304impl Drop for GpuBackend {
305    fn drop(&mut self) {
306        // Workers are detached and will leak unless they are aborted. Aborting marks the
307        // Abortable task, then wakes it up. This means the executor should be asked to continue
308        // running for one more step after the backend is destroyed.
309        self.reset();
310    }
311}