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