• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! GPU related things
6 //! depends on "gpu" feature
7 use std::collections::HashSet;
8 use std::env;
9 use std::path::PathBuf;
10 
11 use devices::virtio::vhost::user::vmm::Gpu as VhostUserGpu;
12 
13 use crate::{JailConfig, VhostUserOption};
14 
15 use super::*;
16 
create_vhost_user_gpu_device( cfg: &Config, opt: &VhostUserOption, gpu_tubes: (Tube, Tube), device_control_tube: Tube, ) -> DeviceResult17 pub fn create_vhost_user_gpu_device(
18     cfg: &Config,
19     opt: &VhostUserOption,
20     gpu_tubes: (Tube, Tube),
21     device_control_tube: Tube,
22 ) -> DeviceResult {
23     // The crosvm gpu device expects us to connect the tube before it will accept a vhost-user
24     // connection.
25     let dev = VhostUserGpu::new(
26         virtio::base_features(cfg.protected_vm),
27         &opt.socket,
28         gpu_tubes,
29         device_control_tube,
30     )
31     .context("failed to set up vhost-user gpu device")?;
32 
33     Ok(VirtioDeviceStub {
34         dev: Box::new(dev),
35         // no sandbox here because virtqueue handling is exported to a different process.
36         jail: None,
37     })
38 }
39 
gpu_jail(jail_config: &Option<JailConfig>, policy: &str) -> Result<Option<Minijail>>40 pub fn gpu_jail(jail_config: &Option<JailConfig>, policy: &str) -> Result<Option<Minijail>> {
41     match simple_jail(jail_config, policy)? {
42         Some(mut jail) => {
43             // Create a tmpfs in the device's root directory so that we can bind mount the
44             // dri directory into it.  The size=67108864 is size=64*1024*1024 or size=64MB.
45             jail.mount_with_data(
46                 Path::new("none"),
47                 Path::new("/"),
48                 "tmpfs",
49                 (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
50                 "size=67108864",
51             )?;
52 
53             // Device nodes required for DRM.
54             let sys_dev_char_path = Path::new("/sys/dev/char");
55             jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)?;
56             let sys_devices_path = Path::new("/sys/devices");
57             jail.mount_bind(sys_devices_path, sys_devices_path, false)?;
58 
59             let drm_dri_path = Path::new("/dev/dri");
60             if drm_dri_path.exists() {
61                 jail.mount_bind(drm_dri_path, drm_dri_path, false)?;
62             }
63 
64             // If the ARM specific devices exist on the host, bind mount them in.
65             let mali0_path = Path::new("/dev/mali0");
66             if mali0_path.exists() {
67                 jail.mount_bind(mali0_path, mali0_path, true)?;
68             }
69 
70             let pvr_sync_path = Path::new("/dev/pvr_sync");
71             if pvr_sync_path.exists() {
72                 jail.mount_bind(pvr_sync_path, pvr_sync_path, true)?;
73             }
74 
75             // If the udmabuf driver exists on the host, bind mount it in.
76             let udmabuf_path = Path::new("/dev/udmabuf");
77             if udmabuf_path.exists() {
78                 jail.mount_bind(udmabuf_path, udmabuf_path, true)?;
79             }
80 
81             // Libraries that are required when mesa drivers are dynamically loaded.
82             jail_mount_bind_if_exists(
83                 &mut jail,
84                 &[
85                     "/usr/lib",
86                     "/usr/lib64",
87                     "/lib",
88                     "/lib64",
89                     "/usr/share/drirc.d",
90                     "/usr/share/glvnd",
91                     "/usr/share/vulkan",
92                 ],
93             )?;
94 
95             // pvr driver requires read access to /proc/self/task/*/comm.
96             let proc_path = Path::new("/proc");
97             jail.mount(
98                 proc_path,
99                 proc_path,
100                 "proc",
101                 (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_RDONLY) as usize,
102             )?;
103 
104             // To enable perfetto tracing, we need to give access to the perfetto service IPC
105             // endpoints.
106             let perfetto_path = Path::new("/run/perfetto");
107             if perfetto_path.exists() {
108                 jail.mount_bind(perfetto_path, perfetto_path, true)?;
109             }
110 
111             Ok(Some(jail))
112         }
113         None => Ok(None),
114     }
115 }
116 
117 pub struct GpuCacheInfo<'a> {
118     directory: Option<&'a str>,
119     environment: Vec<(&'a str, &'a str)>,
120 }
121 
get_gpu_cache_info<'a>( cache_dir: Option<&'a String>, cache_size: Option<&'a String>, sandbox: bool, ) -> GpuCacheInfo<'a>122 pub fn get_gpu_cache_info<'a>(
123     cache_dir: Option<&'a String>,
124     cache_size: Option<&'a String>,
125     sandbox: bool,
126 ) -> GpuCacheInfo<'a> {
127     let mut dir = None;
128     let mut env = Vec::new();
129 
130     if let Some(cache_dir) = cache_dir {
131         if !Path::new(cache_dir).exists() {
132             warn!("shader caching dir {} does not exist", cache_dir);
133             env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
134         } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
135             warn!("shader caching not yet supported on ARM with sandbox enabled");
136             env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
137         } else {
138             dir = Some(cache_dir.as_str());
139 
140             env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
141             env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));
142             if let Some(cache_size) = cache_size {
143                 env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));
144             }
145         }
146     }
147 
148     GpuCacheInfo {
149         directory: dir,
150         environment: env,
151     }
152 }
153 
create_gpu_device( cfg: &Config, exit_evt: &Event, gpu_device_tube: Tube, resource_bridges: Vec<Tube>, wayland_socket_path: Option<&PathBuf>, x_display: Option<String>, render_server_fd: Option<SafeDescriptor>, event_devices: Vec<EventDevice>, map_request: Arc<Mutex<Option<ExternalMapping>>>, ) -> DeviceResult154 pub fn create_gpu_device(
155     cfg: &Config,
156     exit_evt: &Event,
157     gpu_device_tube: Tube,
158     resource_bridges: Vec<Tube>,
159     wayland_socket_path: Option<&PathBuf>,
160     x_display: Option<String>,
161     render_server_fd: Option<SafeDescriptor>,
162     event_devices: Vec<EventDevice>,
163     map_request: Arc<Mutex<Option<ExternalMapping>>>,
164 ) -> DeviceResult {
165     let mut display_backends = vec![
166         virtio::DisplayBackend::X(x_display),
167         virtio::DisplayBackend::Stub,
168     ];
169 
170     let wayland_socket_dirs = cfg
171         .wayland_socket_paths
172         .iter()
173         .map(|(_name, path)| path.parent())
174         .collect::<Option<Vec<_>>>()
175         .ok_or_else(|| anyhow!("wayland socket path has no parent or file name"))?;
176 
177     if let Some(socket_path) = wayland_socket_path {
178         display_backends.insert(
179             0,
180             virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
181         );
182     }
183 
184     let dev = virtio::Gpu::new(
185         exit_evt.try_clone().context("failed to clone event")?,
186         Some(gpu_device_tube),
187         resource_bridges,
188         display_backends,
189         cfg.gpu_parameters.as_ref().unwrap(),
190         render_server_fd,
191         event_devices,
192         map_request,
193         cfg.jail_config.is_some(),
194         virtio::base_features(cfg.protected_vm),
195         cfg.wayland_socket_paths.clone(),
196     );
197 
198     let jail = match gpu_jail(&cfg.jail_config, "gpu_device")? {
199         Some(mut jail) => {
200             // Prepare GPU shader disk cache directory.
201             let (cache_dir, cache_size) = cfg
202                 .gpu_parameters
203                 .as_ref()
204                 .map(|params| (params.cache_path.as_ref(), params.cache_size.as_ref()))
205                 .unwrap();
206             let cache_info = get_gpu_cache_info(cache_dir, cache_size, cfg.jail_config.is_some());
207 
208             if let Some(dir) = cache_info.directory {
209                 jail.mount_bind(dir, dir, true)?;
210             }
211             for (key, val) in cache_info.environment {
212                 env::set_var(key, val);
213             }
214 
215             // Bind mount the wayland socket's directory into jail's root. This is necessary since
216             // each new wayland context must open() the socket. If the wayland socket is ever
217             // destroyed and remade in the same host directory, new connections will be possible
218             // without restarting the wayland device.
219             for dir in &wayland_socket_dirs {
220                 jail.mount_bind(dir, dir, true)?;
221             }
222 
223             add_current_user_to_jail(&mut jail)?;
224 
225             Some(jail)
226         }
227         None => None,
228     };
229 
230     Ok(VirtioDeviceStub {
231         dev: Box::new(dev),
232         jail,
233     })
234 }
235 
236 #[derive(Debug)]
237 pub struct GpuRenderServerParameters {
238     pub path: PathBuf,
239     pub cache_path: Option<String>,
240     pub cache_size: Option<String>,
241 }
242 
get_gpu_render_server_environment(cache_info: &GpuCacheInfo) -> Result<Vec<String>>243 fn get_gpu_render_server_environment(cache_info: &GpuCacheInfo) -> Result<Vec<String>> {
244     let mut env = Vec::new();
245 
246     let mut cache_env_keys = HashSet::with_capacity(cache_info.environment.len());
247     for (key, val) in cache_info.environment.iter() {
248         env.push(format!("{}={}", key, val));
249         cache_env_keys.insert(*key);
250     }
251 
252     for (key_os, val_os) in env::vars_os() {
253         // minijail should accept OsStr rather than str...
254         let into_string_err = |_| anyhow!("invalid environment key/val");
255         let key = key_os.into_string().map_err(into_string_err)?;
256         let val = val_os.into_string().map_err(into_string_err)?;
257 
258         if !cache_env_keys.contains(key.as_str()) {
259             env.push(format!("{}={}", key, val));
260         }
261     }
262 
263     Ok(env)
264 }
265 
start_gpu_render_server( cfg: &Config, render_server_parameters: &GpuRenderServerParameters, ) -> Result<(Minijail, SafeDescriptor)>266 pub fn start_gpu_render_server(
267     cfg: &Config,
268     render_server_parameters: &GpuRenderServerParameters,
269 ) -> Result<(Minijail, SafeDescriptor)> {
270     let (server_socket, client_socket) =
271         UnixSeqpacket::pair().context("failed to create render server socket")?;
272 
273     let mut env = None;
274     let jail = match gpu_jail(&cfg.jail_config, "gpu_render_server")? {
275         Some(mut jail) => {
276             let cache_info = get_gpu_cache_info(
277                 render_server_parameters.cache_path.as_ref(),
278                 render_server_parameters.cache_size.as_ref(),
279                 true,
280             );
281 
282             if let Some(dir) = cache_info.directory {
283                 jail.mount_bind(dir, dir, true)?;
284             }
285 
286             if !cache_info.environment.is_empty() {
287                 env = Some(get_gpu_render_server_environment(&cache_info)?);
288             }
289 
290             // bind mount /dev/log for syslog
291             let log_path = Path::new("/dev/log");
292             if log_path.exists() {
293                 jail.mount_bind(log_path, log_path, true)?;
294             }
295 
296             // Run as root in the jail to keep capabilities after execve, which is needed for
297             // mounting to work.  All capabilities will be dropped afterwards.
298             add_current_user_as_root_to_jail(&mut jail)?;
299 
300             jail
301         }
302         None => Minijail::new().context("failed to create jail")?,
303     };
304 
305     let inheritable_fds = [
306         server_socket.as_raw_descriptor(),
307         libc::STDOUT_FILENO,
308         libc::STDERR_FILENO,
309     ];
310 
311     let cmd = &render_server_parameters.path;
312     let cmd_str = cmd
313         .to_str()
314         .ok_or_else(|| anyhow!("invalid render server path"))?;
315     let fd_str = server_socket.as_raw_descriptor().to_string();
316     let args = [cmd_str, "--socket-fd", &fd_str];
317 
318     let mut envp: Option<Vec<&str>> = None;
319     if let Some(ref env) = env {
320         envp = Some(env.iter().map(AsRef::as_ref).collect());
321     }
322 
323     jail.run_command(minijail::Command::new_for_path(
324         cmd,
325         &inheritable_fds,
326         &args,
327         envp.as_deref(),
328     )?)
329     .context("failed to start gpu render server")?;
330 
331     Ok((jail, SafeDescriptor::from(client_socket)))
332 }
333