• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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 //! GPU related things
6 //! depends on "gpu" feature
7 static_assertions::assert_cfg!(feature = "gpu");
8 
9 use std::collections::HashMap;
10 use std::env;
11 use std::path::PathBuf;
12 
13 use base::linux::move_proc_to_cgroup;
14 use jail::*;
15 use serde::Deserialize;
16 use serde::Serialize;
17 use serde_keyvalue::FromKeyValues;
18 
19 use super::*;
20 use crate::crosvm::config::Config;
21 
22 pub struct GpuCacheInfo<'a> {
23     directory: Option<&'a str>,
24     environment: Vec<(&'a str, &'a str)>,
25 }
26 
get_gpu_cache_info<'a>( cache_dir: Option<&'a String>, cache_size: Option<&'a String>, foz_db_list_path: Option<&'a String>, sandbox: bool, ) -> GpuCacheInfo<'a>27 pub fn get_gpu_cache_info<'a>(
28     cache_dir: Option<&'a String>,
29     cache_size: Option<&'a String>,
30     foz_db_list_path: Option<&'a String>,
31     sandbox: bool,
32 ) -> GpuCacheInfo<'a> {
33     let mut dir = None;
34     let mut env = Vec::new();
35 
36     // TODO (renatopereyra): Remove deprecated env vars once all src/third_party/mesa* are updated.
37     if let Some(cache_dir) = cache_dir {
38         if !Path::new(cache_dir).exists() {
39             warn!("shader caching dir {} does not exist", cache_dir);
40             // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
41             env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
42 
43             env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
44         } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
45             warn!("shader caching not yet supported on ARM with sandbox enabled");
46             // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
47             env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
48 
49             env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
50         } else {
51             dir = Some(cache_dir.as_str());
52 
53             // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
54             env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
55             env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));
56 
57             env.push(("MESA_SHADER_CACHE_DISABLE", "false"));
58             env.push(("MESA_SHADER_CACHE_DIR", cache_dir.as_str()));
59 
60             env.push(("MESA_DISK_CACHE_DATABASE", "1"));
61 
62             if let Some(foz_db_list_path) = foz_db_list_path {
63                 env.push(("MESA_DISK_CACHE_COMBINE_RW_WITH_RO_FOZ", "1"));
64                 env.push((
65                     "MESA_DISK_CACHE_READ_ONLY_FOZ_DBS_DYNAMIC_LIST",
66                     foz_db_list_path,
67                 ));
68             }
69 
70             if let Some(cache_size) = cache_size {
71                 // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
72                 env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));
73 
74                 env.push(("MESA_SHADER_CACHE_MAX_SIZE", cache_size.as_str()));
75             }
76         }
77     }
78 
79     GpuCacheInfo {
80         directory: dir,
81         environment: env,
82     }
83 }
84 
create_gpu_device( cfg: &Config, exit_evt_wrtube: &SendTube, gpu_control_tube: Tube, resource_bridges: Vec<Tube>, render_server_fd: Option<SafeDescriptor>, has_vfio_gfx_device: bool, event_devices: Vec<EventDevice>, ) -> DeviceResult85 pub fn create_gpu_device(
86     cfg: &Config,
87     exit_evt_wrtube: &SendTube,
88     gpu_control_tube: Tube,
89     resource_bridges: Vec<Tube>,
90     render_server_fd: Option<SafeDescriptor>,
91     has_vfio_gfx_device: bool,
92     event_devices: Vec<EventDevice>,
93 ) -> DeviceResult {
94     let is_sandboxed = cfg.jail_config.is_some();
95     let mut gpu_params = cfg.gpu_parameters.clone().unwrap();
96 
97     if is_sandboxed {
98         gpu_params.snapshot_scratch_path = Some(Path::new("/tmpfs-gpu-snapshot").to_path_buf());
99     }
100 
101     if gpu_params.fixed_blob_mapping {
102         if has_vfio_gfx_device {
103             // TODO(b/323368701): make fixed_blob_mapping compatible with vfio dma_buf mapping for
104             // GPU pci passthrough.
105             debug!("gpu fixed blob mapping disabled: not compatible with passthrough GPU.");
106             gpu_params.fixed_blob_mapping = false;
107         } else if cfg!(feature = "vulkano") {
108             // TODO(b/244591751): make fixed_blob_mapping compatible with vulkano for opaque_fd blob
109             // mapping.
110             debug!("gpu fixed blob mapping disabled: not compatible with vulkano");
111             gpu_params.fixed_blob_mapping = false;
112         }
113     }
114 
115     // external_blob must be enforced to ensure that a blob can be exported to a mappable descriptor
116     // (dma_buf, shmem, ...), since:
117     //   - is_sandboxed implies that blob mapping will be done out-of-process by the crosvm
118     //     hypervisor process.
119     //   - fixed_blob_mapping is not yet compatible with VmMemorySource::ExternalMapping
120     gpu_params.external_blob = is_sandboxed || gpu_params.fixed_blob_mapping;
121 
122     // Implicit launch is not allowed when sandboxed. A socket fd from a separate sandboxed
123     // render_server process must be provided instead.
124     gpu_params.allow_implicit_render_server_exec =
125         gpu_params.allow_implicit_render_server_exec && !is_sandboxed;
126 
127     let mut display_backends = vec![
128         virtio::DisplayBackend::X(cfg.x_display.clone()),
129         virtio::DisplayBackend::Stub,
130     ];
131 
132     #[cfg(feature = "android_display")]
133     if let Some(service_name) = &cfg.android_display_service {
134         display_backends.insert(0, virtio::DisplayBackend::Android(service_name.to_string()));
135     }
136 
137     // Use the unnamed socket for GPU display screens.
138     if let Some(socket_path) = cfg.wayland_socket_paths.get("") {
139         display_backends.insert(
140             0,
141             virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
142         );
143     }
144 
145     let dev = virtio::Gpu::new(
146         exit_evt_wrtube
147             .try_clone()
148             .context("failed to clone tube")?,
149         gpu_control_tube,
150         resource_bridges,
151         display_backends,
152         &gpu_params,
153         render_server_fd,
154         event_devices,
155         virtio::base_features(cfg.protection_type),
156         &cfg.wayland_socket_paths,
157         cfg.gpu_cgroup_path.as_ref(),
158     );
159 
160     let jail = if let Some(jail_config) = cfg.jail_config.as_ref() {
161         let mut config = SandboxConfig::new(jail_config, "gpu_device");
162         config.bind_mounts = true;
163         // Allow changes made externally take effect immediately to allow shaders to be dynamically
164         // added by external processes.
165         config.remount_mode = Some(libc::MS_SLAVE);
166         let mut jail = create_gpu_minijail(
167             &jail_config.pivot_root,
168             &config,
169             /* render_node_only= */ false,
170             gpu_params.snapshot_scratch_path.as_deref(),
171         )?;
172 
173         // Prepare GPU shader disk cache directory.
174         let cache_info = get_gpu_cache_info(
175             gpu_params.cache_path.as_ref(),
176             gpu_params.cache_size.as_ref(),
177             None,
178             cfg.jail_config.is_some(),
179         );
180 
181         if let Some(dir) = cache_info.directory {
182             // Manually bind mount recursively to allow DLC shader caches
183             // to be propagated to the GPU process.
184             jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
185         }
186         for (key, val) in cache_info.environment {
187             env::set_var(key, val);
188         }
189 
190         // Bind mount the wayland socket's directory into jail's root. This is necessary since
191         // each new wayland context must open() the socket. If the wayland socket is ever
192         // destroyed and remade in the same host directory, new connections will be possible
193         // without restarting the wayland device.
194         for socket_path in cfg.wayland_socket_paths.values() {
195             let dir = socket_path.parent().with_context(|| {
196                 format!(
197                     "wayland socket path '{}' has no parent",
198                     socket_path.display(),
199                 )
200             })?;
201             jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
202         }
203 
204         Some(jail)
205     } else {
206         None
207     };
208 
209     Ok(VirtioDeviceStub {
210         dev: Box::new(dev),
211         jail,
212     })
213 }
214 
215 #[derive(Debug, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
216 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
217 pub struct GpuRenderServerParameters {
218     pub path: PathBuf,
219     pub cache_path: Option<String>,
220     pub cache_size: Option<String>,
221     pub foz_db_list_path: Option<String>,
222     pub precompiled_cache_path: Option<String>,
223     pub ld_preload_path: Option<String>,
224 }
225 
get_gpu_render_server_environment( cache_info: Option<&GpuCacheInfo>, ld_preload_path: Option<&String>, ) -> Result<Vec<String>>226 fn get_gpu_render_server_environment(
227     cache_info: Option<&GpuCacheInfo>,
228     ld_preload_path: Option<&String>,
229 ) -> Result<Vec<String>> {
230     let mut env = HashMap::<String, String>::new();
231     let os_env_len = env::vars_os().count();
232 
233     if let Some(cache_info) = cache_info {
234         env.reserve(os_env_len + cache_info.environment.len());
235         for (key, val) in cache_info.environment.iter() {
236             env.insert(key.to_string(), val.to_string());
237         }
238     } else {
239         env.reserve(os_env_len);
240     }
241 
242     for (key_os, val_os) in env::vars_os() {
243         // minijail should accept OsStr rather than str...
244         let into_string_err = |_| anyhow!("invalid environment key/val");
245         let key = key_os.into_string().map_err(into_string_err)?;
246         let val = val_os.into_string().map_err(into_string_err)?;
247         env.entry(key).or_insert(val);
248     }
249 
250     // for debugging purpose, avoid appending if LD_PRELOAD has been set outside
251     if !env.contains_key("LD_PRELOAD") {
252         if let Some(ld_preload_path) = ld_preload_path {
253             env.insert("LD_PRELOAD".to_string(), ld_preload_path.to_string());
254         }
255     }
256 
257     // TODO(b/323284290): workaround to advertise 2 graphics queues in ANV
258     if !env.contains_key("ANV_QUEUE_OVERRIDE") {
259         env.insert("ANV_QUEUE_OVERRIDE".to_string(), "gc=2".to_string());
260     }
261 
262     // TODO(b/237493180, b/284517235): workaround to enable ETC2/ASTC format emulation in Mesa
263     // TODO(b/284361281, b/328827736): workaround to enable legacy sparse binding in RADV
264     let driconf_options = [
265         "radv_legacy_sparse_binding",
266         "radv_require_etc2",
267         "vk_require_etc2",
268         "vk_require_astc",
269     ];
270     for opt in driconf_options {
271         if !env.contains_key(opt) {
272             env.insert(opt.to_string(), "true".to_string());
273         }
274     }
275 
276     // TODO(b/339766043): workaround to disable Vulkan protected memory feature in Mali
277     if !env.contains_key("MALI_BASE_PROTECTED_MEMORY_HEAP_SIZE") {
278         env.insert(
279             "MALI_BASE_PROTECTED_MEMORY_HEAP_SIZE".to_string(),
280             "0".to_string(),
281         );
282     }
283 
284     Ok(env.iter().map(|(k, v)| format!("{}={}", k, v)).collect())
285 }
286 
start_gpu_render_server( cfg: &Config, render_server_parameters: &GpuRenderServerParameters, ) -> Result<(Minijail, SafeDescriptor)>287 pub fn start_gpu_render_server(
288     cfg: &Config,
289     render_server_parameters: &GpuRenderServerParameters,
290 ) -> Result<(Minijail, SafeDescriptor)> {
291     let (server_socket, client_socket) =
292         UnixSeqpacket::pair().context("failed to create render server socket")?;
293 
294     let (jail, cache_info) = if let Some(jail_config) = cfg.jail_config.as_ref() {
295         let mut config = SandboxConfig::new(jail_config, "gpu_render_server");
296         // Allow changes made externally take effect immediately to allow shaders to be dynamically
297         // added by external processes.
298         config.remount_mode = Some(libc::MS_SLAVE);
299         config.bind_mounts = true;
300         // Run as root in the jail to keep capabilities after execve, which is needed for
301         // mounting to work.  All capabilities will be dropped afterwards.
302         config.run_as = RunAsUser::Root;
303         let mut jail = create_gpu_minijail(
304             &jail_config.pivot_root,
305             &config,
306             /* render_node_only= */ true,
307             /* snapshot_scratch_path= */ None,
308         )?;
309 
310         let cache_info = get_gpu_cache_info(
311             render_server_parameters.cache_path.as_ref(),
312             render_server_parameters.cache_size.as_ref(),
313             render_server_parameters.foz_db_list_path.as_ref(),
314             true,
315         );
316 
317         if let Some(dir) = cache_info.directory {
318             // Manually bind mount recursively to allow DLC shader caches
319             // to be propagated to the GPU process.
320             jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
321         }
322         if let Some(precompiled_cache_dir) = &render_server_parameters.precompiled_cache_path {
323             jail.mount_bind(precompiled_cache_dir, precompiled_cache_dir, true)?;
324         }
325 
326         // bind mount /dev/log for syslog
327         let log_path = Path::new("/dev/log");
328         if log_path.exists() {
329             jail.mount_bind(log_path, log_path, true)?;
330         }
331 
332         (jail, Some(cache_info))
333     } else {
334         (
335             create_default_minijail().context("failed to create jail")?,
336             None,
337         )
338     };
339 
340     let inheritable_fds = [
341         server_socket.as_raw_descriptor(),
342         libc::STDOUT_FILENO,
343         libc::STDERR_FILENO,
344     ];
345 
346     let cmd = &render_server_parameters.path;
347     let cmd_str = cmd
348         .to_str()
349         .ok_or_else(|| anyhow!("invalid render server path"))?;
350     let fd_str = server_socket.as_raw_descriptor().to_string();
351     let args = [cmd_str, "--socket-fd", &fd_str];
352 
353     let env = Some(get_gpu_render_server_environment(
354         cache_info.as_ref(),
355         render_server_parameters.ld_preload_path.as_ref(),
356     )?);
357     let mut envp: Option<Vec<&str>> = None;
358     if let Some(ref env) = env {
359         envp = Some(env.iter().map(AsRef::as_ref).collect());
360     }
361 
362     let render_server_pid = jail
363         .run_command(minijail::Command::new_for_path(
364             cmd,
365             &inheritable_fds,
366             &args,
367             envp.as_deref(),
368         )?)
369         .context("failed to start gpu render server")?;
370 
371     if let Some(gpu_server_cgroup_path) = &cfg.gpu_server_cgroup_path {
372         move_proc_to_cgroup(gpu_server_cgroup_path.to_path_buf(), render_server_pid)?;
373     }
374 
375     Ok((jail, SafeDescriptor::from(client_socket)))
376 }
377 
378 #[cfg(test)]
379 mod tests {
380     use super::*;
381     use crate::crosvm::config::from_key_values;
382 
383     #[test]
parse_gpu_render_server_parameters()384     fn parse_gpu_render_server_parameters() {
385         let res: GpuRenderServerParameters = from_key_values("path=/some/path").unwrap();
386         assert_eq!(
387             res,
388             GpuRenderServerParameters {
389                 path: "/some/path".into(),
390                 cache_path: None,
391                 cache_size: None,
392                 foz_db_list_path: None,
393                 precompiled_cache_path: None,
394                 ld_preload_path: None,
395             }
396         );
397 
398         let res: GpuRenderServerParameters = from_key_values("/some/path").unwrap();
399         assert_eq!(
400             res,
401             GpuRenderServerParameters {
402                 path: "/some/path".into(),
403                 cache_path: None,
404                 cache_size: None,
405                 foz_db_list_path: None,
406                 precompiled_cache_path: None,
407                 ld_preload_path: None,
408             }
409         );
410 
411         let res: GpuRenderServerParameters =
412             from_key_values("path=/some/path,cache-path=/cache/path,cache-size=16M").unwrap();
413         assert_eq!(
414             res,
415             GpuRenderServerParameters {
416                 path: "/some/path".into(),
417                 cache_path: Some("/cache/path".into()),
418                 cache_size: Some("16M".into()),
419                 foz_db_list_path: None,
420                 precompiled_cache_path: None,
421                 ld_preload_path: None,
422             }
423         );
424 
425         let res: GpuRenderServerParameters = from_key_values(
426             "path=/some/path,cache-path=/cache/path,cache-size=16M,foz-db-list-path=/db/list/path,precompiled-cache-path=/precompiled/path",
427         )
428         .unwrap();
429         assert_eq!(
430             res,
431             GpuRenderServerParameters {
432                 path: "/some/path".into(),
433                 cache_path: Some("/cache/path".into()),
434                 cache_size: Some("16M".into()),
435                 foz_db_list_path: Some("/db/list/path".into()),
436                 precompiled_cache_path: Some("/precompiled/path".into()),
437                 ld_preload_path: None,
438             }
439         );
440 
441         let res: GpuRenderServerParameters =
442             from_key_values("path=/some/path,ld-preload-path=/ld/preload/path").unwrap();
443         assert_eq!(
444             res,
445             GpuRenderServerParameters {
446                 path: "/some/path".into(),
447                 cache_path: None,
448                 cache_size: None,
449                 foz_db_list_path: None,
450                 precompiled_cache_path: None,
451                 ld_preload_path: Some("/ld/preload/path".into()),
452             }
453         );
454 
455         let res =
456             from_key_values::<GpuRenderServerParameters>("cache-path=/cache/path,cache-size=16M");
457         assert!(res.is_err());
458 
459         let res = from_key_values::<GpuRenderServerParameters>("");
460         assert!(res.is_err());
461     }
462 }
463