Path: blob/main/devices/src/virtio/vhost_user_backend/gpu/sys/linux.rs
5394 views
// Copyright 2022 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::cell::RefCell;5use std::collections::BTreeMap;6use std::path::PathBuf;7use std::rc::Rc;8use std::sync::Arc;910use anyhow::Context;11use argh::FromArgs;12use base::clone_descriptor;13use base::error;14use base::RawDescriptor;15use base::SafeDescriptor;16use base::Tube;17use base::UnixSeqpacketListener;18use base::UnlinkUnixSeqpacketListener;19use cros_async::AsyncWrapper;20use cros_async::Executor;21use cros_async::IoSource;22use hypervisor::ProtectionType;23use sync::Mutex;2425use crate::virtio;26use crate::virtio::gpu;27use crate::virtio::gpu::ProcessDisplayResult;28use crate::virtio::vhost_user_backend::gpu::GpuBackend;29use crate::virtio::vhost_user_backend::wl::parse_wayland_sock;30use crate::virtio::vhost_user_backend::BackendConnection;31use crate::virtio::Gpu;32use crate::virtio::GpuDisplayParameters;33use crate::virtio::GpuParameters;34use crate::virtio::Interrupt;3536async fn run_display(37display: IoSource<AsyncWrapper<SafeDescriptor>>,38state: Rc<RefCell<gpu::Frontend>>,39) {40loop {41if let Err(e) = display.wait_readable().await {42error!(43"Failed to wait for display context to become readable: {}",44e45);46break;47}4849match state.borrow_mut().process_display() {50ProcessDisplayResult::Error(e) => {51error!("Failed to process display events: {}", e);52break;53}54ProcessDisplayResult::CloseRequested => break,55ProcessDisplayResult::Success => {}56}57}58}5960async fn run_resource_bridge(tube: IoSource<Tube>, state: Rc<RefCell<gpu::Frontend>>) {61loop {62if let Err(e) = tube.wait_readable().await {63error!(64"Failed to wait for resource bridge tube to become readable: {}",65e66);67break;68}6970if let Err(e) = state.borrow_mut().process_resource_bridge(tube.as_source()) {71error!("Failed to process resource bridge: {:#}", e);72break;73}74}75}7677impl GpuBackend {78pub fn start_platform_workers(&mut self, _interrupt: Interrupt) -> anyhow::Result<()> {79let state = self80.state81.as_ref()82.context("frontend state wasn't set")?83.clone();8485// Start handling the resource bridges.86for bridge in self.resource_bridges.lock().drain(..) {87let tube = self88.ex89.async_from(bridge)90.context("failed to create async tube")?;91let task = self92.ex93.spawn_local(run_resource_bridge(tube, state.clone()));94self.platform_worker_tx95.unbounded_send(task)96.context("sending the run_resource_bridge task")?;97}9899// Start handling the display.100let display = clone_descriptor(&*state.borrow_mut().display().borrow())101.map(AsyncWrapper::new)102.context("failed to clone inner WaitContext for gpu display")103.and_then(|ctx| {104self.ex105.async_from(ctx)106.context("failed to create async WaitContext")107})?;108109let task = self.ex.spawn_local(run_display(display, state));110self.platform_worker_tx111.unbounded_send(task)112.context("sending the run_display task")?;113114Ok(())115}116}117fn gpu_parameters_from_str(input: &str) -> Result<GpuParameters, String> {118serde_json::from_str(input).map_err(|e| e.to_string())119}120121#[derive(FromArgs)]122/// GPU device123#[argh(subcommand, name = "gpu")]124pub struct Options {125#[argh(option, arg_name = "PATH", hidden_help)]126/// deprecated - please use --socket-path instead127socket: Option<String>,128#[argh(option, arg_name = "PATH")]129/// path to the vhost-user socket to bind to.130/// If this flag is set, --fd cannot be specified.131socket_path: Option<String>,132#[argh(option, arg_name = "FD")]133/// file descriptor of a connected vhost-user socket.134/// If this flag is set, --socket-path cannot be specified.135fd: Option<RawDescriptor>,136137#[argh(option, from_str_fn(parse_wayland_sock), arg_name = "PATH[,name=NAME]")]138/// path to one or more Wayland sockets. The unnamed socket is139/// used for displaying virtual screens while the named ones are used for IPC140wayland_sock: Vec<(String, PathBuf)>,141#[argh(option, arg_name = "PATH")]142/// path to one or more bridge sockets for communicating with143/// other graphics devices (wayland, video, etc)144resource_bridge: Vec<String>,145#[argh(option, arg_name = "DISPLAY")]146/// X11 display name to use147x_display: Option<String>,148#[argh(149option,150from_str_fn(gpu_parameters_from_str),151default = "Default::default()",152arg_name = "JSON"153)]154/// a JSON object of virtio-gpu parameters155params: GpuParameters,156}157158pub fn run_gpu_device(opts: Options) -> anyhow::Result<()> {159let Options {160x_display,161params: mut gpu_parameters,162resource_bridge,163socket,164socket_path,165fd,166wayland_sock,167} = opts;168169let channels: BTreeMap<_, _> = wayland_sock.into_iter().collect();170171let resource_bridge_listeners = resource_bridge172.into_iter()173.map(|p| {174UnixSeqpacketListener::bind(&p)175.map(UnlinkUnixSeqpacketListener)176.with_context(|| format!("failed to bind socket at path {p}"))177})178.collect::<anyhow::Result<Vec<_>>>()?;179180if gpu_parameters.display_params.is_empty() {181gpu_parameters182.display_params183.push(GpuDisplayParameters::default());184}185186let ex = Executor::new().context("failed to create executor")?;187188// We don't know the order in which other devices are going to connect to the resource bridges189// so start listening for all of them on separate threads. Any devices that connect after the190// gpu device starts its queues will not have its resource bridges processed. In practice this191// should be fine since the devices that use the resource bridge always try to connect to the192// gpu device before handling messages from the VM.193let resource_bridges = Arc::new(Mutex::new(Vec::with_capacity(194resource_bridge_listeners.len(),195)));196for listener in resource_bridge_listeners {197let resource_bridges = Arc::clone(&resource_bridges);198ex.spawn_blocking(move || match listener.accept() {199Ok(stream) => resource_bridges200.lock()201.push(Tube::try_from(stream).unwrap()),202Err(e) => {203let path = listener204.path()205.unwrap_or_else(|_| PathBuf::from("{unknown}"));206error!(207"Failed to accept resource bridge connection for socket {}: {}",208path.display(),209e210);211}212})213.detach();214}215216// TODO(b/232344535): Read side of the tube is ignored currently.217// Complete the implementation by polling `exit_evt_rdtube` and218// kill the sibling VM.219let (exit_evt_wrtube, _) =220Tube::directional_pair().context("failed to create vm event tube")?;221222let (gpu_control_tube, _) = Tube::pair().context("failed to create gpu control tube")?;223224let mut display_backends = vec![225virtio::DisplayBackend::X(x_display),226virtio::DisplayBackend::Stub,227];228if let Some(p) = channels.get("") {229display_backends.insert(0, virtio::DisplayBackend::Wayland(Some(p.to_owned())));230}231232// These are only used when there is an input device.233let event_devices = Vec::new();234235let base_features = virtio::base_features(ProtectionType::Unprotected);236237let conn = BackendConnection::from_opts(socket.as_deref(), socket_path.as_deref(), fd)?;238239let gpu = Rc::new(RefCell::new(Gpu::new(240exit_evt_wrtube,241gpu_control_tube,242Vec::new(), // resource_bridges, handled separately by us243display_backends,244&gpu_parameters,245/* rutabaga_server_descriptor */246None,247event_devices,248base_features,249&channels,250/* gpu_cgroup_path */251None,252)));253254let (platform_worker_tx, platform_worker_rx) = futures::channel::mpsc::unbounded();255let backend = GpuBackend {256ex: ex.clone(),257gpu,258resource_bridges,259state: None,260fence_state: Default::default(),261queue_workers: Default::default(),262platform_worker_rx,263platform_worker_tx,264shmem_mapper: Arc::new(Mutex::new(None)),265};266267// Run until the backend is finished.268let _ = ex.run_until(conn.run_backend(backend, &ex))?;269270// Process any tasks from the backend's destructor.271Ok(ex.run_until(async {})?)272}273274275