1static_assertions::assert_cfg!(feature = "gpu");
8
9use std::collections::HashMap;
10use std::env;
11use std::path::PathBuf;
12
13use base::linux::move_proc_to_cgroup;
14use jail::*;
15use serde::Deserialize;
16use serde::Serialize;
17use serde_keyvalue::FromKeyValues;
18
19use super::*;
20use crate::crosvm::config::Config;
21
22pub struct GpuCacheInfo<'a> {
23 directory: Option<&'a str>,
24 environment: Vec<(&'a str, &'a str)>,
25}
26
27pub 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 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 env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
42
43 env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
44 } else if cfg!(target_arch = "aarch64") && sandbox {
45 warn!("shader caching not yet supported on ARM with sandbox enabled");
46 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 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 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
85pub 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 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 debug!("gpu fixed blob mapping disabled: not compatible with vulkano");
111 gpu_params.fixed_blob_mapping = false;
112 }
113 }
114
115 gpu_params.external_blob = is_sandboxed || gpu_params.fixed_blob_mapping;
121
122 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 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 config.remount_mode = Some(libc::MS_SLAVE);
166 let mut jail = create_gpu_minijail(
167 &jail_config.pivot_root,
168 &config,
169 false,
170 gpu_params.snapshot_scratch_path.as_deref(),
171 )?;
172
173 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 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 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")]
217pub 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
226fn 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 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 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 if !env.contains_key("ANV_QUEUE_OVERRIDE") {
259 env.insert("ANV_QUEUE_OVERRIDE".to_string(), "gc=2".to_string());
260 }
261
262 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 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
287pub 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 config.remount_mode = Some(libc::MS_SLAVE);
299 config.bind_mounts = true;
300 config.run_as = RunAsUser::Root;
303 let mut jail = create_gpu_minijail(
304 &jail_config.pivot_root,
305 &config,
306 true,
307 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 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 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 (Minijail::new().context("failed to create jail")?, None)
335 };
336
337 let inheritable_fds = [
338 server_socket.as_raw_descriptor(),
339 libc::STDOUT_FILENO,
340 libc::STDERR_FILENO,
341 ];
342
343 let cmd = &render_server_parameters.path;
344 let cmd_str = cmd
345 .to_str()
346 .ok_or_else(|| anyhow!("invalid render server path"))?;
347 let fd_str = server_socket.as_raw_descriptor().to_string();
348 let args = [cmd_str, "--socket-fd", &fd_str];
349
350 let env = Some(get_gpu_render_server_environment(
351 cache_info.as_ref(),
352 render_server_parameters.ld_preload_path.as_ref(),
353 )?);
354 let mut envp: Option<Vec<&str>> = None;
355 if let Some(ref env) = env {
356 envp = Some(env.iter().map(AsRef::as_ref).collect());
357 }
358
359 let render_server_pid = jail
360 .run_command(minijail::Command::new_for_path(
361 cmd,
362 &inheritable_fds,
363 &args,
364 envp.as_deref(),
365 )?)
366 .context("failed to start gpu render server")?;
367
368 if let Some(gpu_server_cgroup_path) = &cfg.gpu_server_cgroup_path {
369 move_proc_to_cgroup(gpu_server_cgroup_path.to_path_buf(), render_server_pid)?;
370 }
371
372 Ok((jail, SafeDescriptor::from(client_socket)))
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use crate::crosvm::config::from_key_values;
379
380 #[test]
381 fn parse_gpu_render_server_parameters() {
382 let res: GpuRenderServerParameters = from_key_values("path=/some/path").unwrap();
383 assert_eq!(
384 res,
385 GpuRenderServerParameters {
386 path: "/some/path".into(),
387 cache_path: None,
388 cache_size: None,
389 foz_db_list_path: None,
390 precompiled_cache_path: None,
391 ld_preload_path: None,
392 }
393 );
394
395 let res: GpuRenderServerParameters = from_key_values("/some/path").unwrap();
396 assert_eq!(
397 res,
398 GpuRenderServerParameters {
399 path: "/some/path".into(),
400 cache_path: None,
401 cache_size: None,
402 foz_db_list_path: None,
403 precompiled_cache_path: None,
404 ld_preload_path: None,
405 }
406 );
407
408 let res: GpuRenderServerParameters =
409 from_key_values("path=/some/path,cache-path=/cache/path,cache-size=16M").unwrap();
410 assert_eq!(
411 res,
412 GpuRenderServerParameters {
413 path: "/some/path".into(),
414 cache_path: Some("/cache/path".into()),
415 cache_size: Some("16M".into()),
416 foz_db_list_path: None,
417 precompiled_cache_path: None,
418 ld_preload_path: None,
419 }
420 );
421
422 let res: GpuRenderServerParameters = from_key_values(
423 "path=/some/path,cache-path=/cache/path,cache-size=16M,foz-db-list-path=/db/list/path,precompiled-cache-path=/precompiled/path",
424 )
425 .unwrap();
426 assert_eq!(
427 res,
428 GpuRenderServerParameters {
429 path: "/some/path".into(),
430 cache_path: Some("/cache/path".into()),
431 cache_size: Some("16M".into()),
432 foz_db_list_path: Some("/db/list/path".into()),
433 precompiled_cache_path: Some("/precompiled/path".into()),
434 ld_preload_path: None,
435 }
436 );
437
438 let res: GpuRenderServerParameters =
439 from_key_values("path=/some/path,ld-preload-path=/ld/preload/path").unwrap();
440 assert_eq!(
441 res,
442 GpuRenderServerParameters {
443 path: "/some/path".into(),
444 cache_path: None,
445 cache_size: None,
446 foz_db_list_path: None,
447 precompiled_cache_path: None,
448 ld_preload_path: Some("/ld/preload/path".into()),
449 }
450 );
451
452 let res =
453 from_key_values::<GpuRenderServerParameters>("cache-path=/cache/path,cache-size=16M");
454 assert!(res.is_err());
455
456 let res = from_key_values::<GpuRenderServerParameters>("");
457 assert!(res.is_err());
458 }
459}