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