• 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 
8 #[cfg(feature = "gpu")]
9 use std::collections::HashMap;
10 use std::env;
11 use std::path::PathBuf;
12 
13 #[cfg(feature = "gpu")]
14 use base::platform::move_proc_to_cgroup;
15 use jail::*;
16 use serde::Deserialize;
17 use serde::Serialize;
18 use serde_keyvalue::FromKeyValues;
19 
20 use super::*;
21 use crate::crosvm::config::Config;
22 
23 pub struct GpuCacheInfo<'a> {
24     directory: Option<&'a str>,
25     environment: Vec<(&'a str, &'a str)>,
26 }
27 
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>28 pub fn get_gpu_cache_info<'a>(
29     cache_dir: Option<&'a String>,
30     cache_size: Option<&'a String>,
31     foz_db_list_path: Option<&'a String>,
32     sandbox: bool,
33 ) -> GpuCacheInfo<'a> {
34     let mut dir = None;
35     let mut env = Vec::new();
36 
37     // TODO (renatopereyra): Remove deprecated env vars once all src/third_party/mesa* are updated.
38     if let Some(cache_dir) = cache_dir {
39         if !Path::new(cache_dir).exists() {
40             warn!("shader caching dir {} does not exist", cache_dir);
41             // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
42             env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
43 
44             env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
45         } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
46             warn!("shader caching not yet supported on ARM with sandbox enabled");
47             // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
48             env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
49 
50             env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
51         } else {
52             dir = Some(cache_dir.as_str());
53 
54             // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
55             env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
56             env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));
57 
58             env.push(("MESA_SHADER_CACHE_DISABLE", "false"));
59             env.push(("MESA_SHADER_CACHE_DIR", cache_dir.as_str()));
60 
61             env.push(("MESA_DISK_CACHE_DATABASE", "1"));
62 
63             if let Some(foz_db_list_path) = foz_db_list_path {
64                 env.push(("MESA_DISK_CACHE_COMBINE_RW_WITH_RO_FOZ", "1"));
65                 env.push((
66                     "MESA_DISK_CACHE_READ_ONLY_FOZ_DBS_DYNAMIC_LIST",
67                     foz_db_list_path,
68                 ));
69             }
70 
71             if let Some(cache_size) = cache_size {
72                 // Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
73                 env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));
74 
75                 env.push(("MESA_SHADER_CACHE_MAX_SIZE", cache_size.as_str()));
76             }
77         }
78     }
79 
80     GpuCacheInfo {
81         directory: dir,
82         environment: env,
83     }
84 }
85 
create_gpu_device( cfg: &Config, exit_evt_wrtube: &SendTube, gpu_control_tube: Tube, resource_bridges: Vec<Tube>, wayland_socket_path: Option<&PathBuf>, x_display: Option<String>, render_server_fd: Option<SafeDescriptor>, event_devices: Vec<EventDevice>, ) -> DeviceResult86 pub fn create_gpu_device(
87     cfg: &Config,
88     exit_evt_wrtube: &SendTube,
89     gpu_control_tube: Tube,
90     resource_bridges: Vec<Tube>,
91     wayland_socket_path: Option<&PathBuf>,
92     x_display: Option<String>,
93     render_server_fd: Option<SafeDescriptor>,
94     event_devices: Vec<EventDevice>,
95 ) -> DeviceResult {
96     let mut display_backends = vec![
97         virtio::DisplayBackend::X(x_display),
98         virtio::DisplayBackend::Stub,
99     ];
100 
101     if let Some(socket_path) = wayland_socket_path {
102         display_backends.insert(
103             0,
104             virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
105         );
106     }
107 
108     let dev = virtio::Gpu::new(
109         exit_evt_wrtube
110             .try_clone()
111             .context("failed to clone tube")?,
112         gpu_control_tube,
113         resource_bridges,
114         display_backends,
115         cfg.gpu_parameters.as_ref().unwrap(),
116         render_server_fd,
117         event_devices,
118         /*external_blob=*/ cfg.jail_config.is_some(),
119         /*system_blob=*/ false,
120         virtio::base_features(cfg.protection_type),
121         cfg.wayland_socket_paths.clone(),
122         cfg.gpu_cgroup_path.as_ref(),
123     );
124 
125     let jail = if let Some(jail_config) = &cfg.jail_config {
126         let mut config = SandboxConfig::new(jail_config, "gpu_device");
127         config.bind_mounts = true;
128         // Allow changes made externally take effect immediately to allow shaders to be dynamically
129         // added by external processes.
130         config.remount_mode = Some(libc::MS_SLAVE);
131         let mut jail = create_gpu_minijail(&jail_config.pivot_root, &config)?;
132 
133         // Prepare GPU shader disk cache directory.
134         let (cache_dir, cache_size) = cfg
135             .gpu_parameters
136             .as_ref()
137             .map(|params| (params.cache_path.as_ref(), params.cache_size.as_ref()))
138             .unwrap();
139         let cache_info = get_gpu_cache_info(cache_dir, cache_size, None, cfg.jail_config.is_some());
140 
141         if let Some(dir) = cache_info.directory {
142             // Manually bind mount recursively to allow DLC shader caches
143             // to be propagated to the GPU process.
144             jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
145         }
146         for (key, val) in cache_info.environment {
147             env::set_var(key, val);
148         }
149 
150         // Bind mount the wayland socket's directory into jail's root. This is necessary since
151         // each new wayland context must open() the socket. If the wayland socket is ever
152         // destroyed and remade in the same host directory, new connections will be possible
153         // without restarting the wayland device.
154         for socket_path in cfg.wayland_socket_paths.values() {
155             let dir = socket_path.parent().with_context(|| {
156                 format!(
157                     "wayland socket path '{}' has no parent",
158                     socket_path.display(),
159                 )
160             })?;
161             jail.mount_bind(dir, dir, true)?;
162         }
163 
164         Some(jail)
165     } else {
166         None
167     };
168 
169     Ok(VirtioDeviceStub {
170         dev: Box::new(dev),
171         jail,
172     })
173 }
174 
175 #[derive(Debug, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
176 #[serde(deny_unknown_fields, rename_all = "kebab-case")]
177 pub struct GpuRenderServerParameters {
178     pub path: PathBuf,
179     pub cache_path: Option<String>,
180     pub cache_size: Option<String>,
181     pub foz_db_list_path: Option<String>,
182     pub precompiled_cache_path: Option<String>,
183 }
184 
185 #[cfg(feature = "gpu")]
get_gpu_render_server_environment(cache_info: Option<&GpuCacheInfo>) -> Result<Vec<String>>186 fn get_gpu_render_server_environment(cache_info: Option<&GpuCacheInfo>) -> Result<Vec<String>> {
187     let mut env = HashMap::<String, String>::new();
188     let os_env_len = env::vars_os().count();
189 
190     if let Some(cache_info) = cache_info {
191         env.reserve(os_env_len + cache_info.environment.len());
192         for (key, val) in cache_info.environment.iter() {
193             env.insert(key.to_string(), val.to_string());
194         }
195     } else {
196         env.reserve(os_env_len);
197     }
198 
199     for (key_os, val_os) in env::vars_os() {
200         // minijail should accept OsStr rather than str...
201         let into_string_err = |_| anyhow!("invalid environment key/val");
202         let key = key_os.into_string().map_err(into_string_err)?;
203         let val = val_os.into_string().map_err(into_string_err)?;
204         env.entry(key).or_insert(val);
205     }
206 
207     // TODO(b/237493180): workaround to enable ETC2 format emulation in RADV for ARCVM
208     if !env.contains_key("radv_require_etc2") {
209         env.insert("radv_require_etc2".to_string(), "true".to_string());
210     }
211 
212     Ok(env.iter().map(|(k, v)| format!("{}={}", k, v)).collect())
213 }
214 
215 #[cfg(feature = "gpu")]
start_gpu_render_server( cfg: &Config, render_server_parameters: &GpuRenderServerParameters, ) -> Result<(Minijail, SafeDescriptor)>216 pub fn start_gpu_render_server(
217     cfg: &Config,
218     render_server_parameters: &GpuRenderServerParameters,
219 ) -> Result<(Minijail, SafeDescriptor)> {
220     let (server_socket, client_socket) =
221         UnixSeqpacket::pair().context("failed to create render server socket")?;
222 
223     let (jail, cache_info) = if let Some(jail_config) = &cfg.jail_config {
224         let mut config = SandboxConfig::new(jail_config, "gpu_render_server");
225         // Allow changes made externally take effect immediately to allow shaders to be dynamically
226         // added by external processes.
227         config.remount_mode = Some(libc::MS_SLAVE);
228         config.bind_mounts = true;
229         // Run as root in the jail to keep capabilities after execve, which is needed for
230         // mounting to work.  All capabilities will be dropped afterwards.
231         config.run_as = RunAsUser::Root;
232         let mut jail = create_gpu_minijail(&jail_config.pivot_root, &config)?;
233 
234         let cache_info = get_gpu_cache_info(
235             render_server_parameters.cache_path.as_ref(),
236             render_server_parameters.cache_size.as_ref(),
237             render_server_parameters.foz_db_list_path.as_ref(),
238             true,
239         );
240 
241         if let Some(dir) = cache_info.directory {
242             // Manually bind mount recursively to allow DLC shader caches
243             // to be propagated to the GPU process.
244             jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
245         }
246         if let Some(precompiled_cache_dir) = &render_server_parameters.precompiled_cache_path {
247             jail.mount_bind(precompiled_cache_dir, precompiled_cache_dir, true)?;
248         }
249 
250         // bind mount /dev/log for syslog
251         let log_path = Path::new("/dev/log");
252         if log_path.exists() {
253             jail.mount_bind(log_path, log_path, true)?;
254         }
255 
256         (jail, Some(cache_info))
257     } else {
258         (Minijail::new().context("failed to create jail")?, None)
259     };
260 
261     let inheritable_fds = [
262         server_socket.as_raw_descriptor(),
263         libc::STDOUT_FILENO,
264         libc::STDERR_FILENO,
265     ];
266 
267     let cmd = &render_server_parameters.path;
268     let cmd_str = cmd
269         .to_str()
270         .ok_or_else(|| anyhow!("invalid render server path"))?;
271     let fd_str = server_socket.as_raw_descriptor().to_string();
272     let args = [cmd_str, "--socket-fd", &fd_str];
273 
274     let env = Some(get_gpu_render_server_environment(cache_info.as_ref())?);
275     let mut envp: Option<Vec<&str>> = None;
276     if let Some(ref env) = env {
277         envp = Some(env.iter().map(AsRef::as_ref).collect());
278     }
279 
280     let render_server_pid = jail
281         .run_command(minijail::Command::new_for_path(
282             cmd,
283             &inheritable_fds,
284             &args,
285             envp.as_deref(),
286         )?)
287         .context("failed to start gpu render server")?;
288 
289     if let Some(gpu_server_cgroup_path) = &cfg.gpu_server_cgroup_path {
290         move_proc_to_cgroup(gpu_server_cgroup_path.to_path_buf(), render_server_pid)?;
291     }
292 
293     Ok((jail, SafeDescriptor::from(client_socket)))
294 }
295 
296 #[cfg(test)]
297 mod tests {
298     use super::*;
299     use crate::crosvm::config::from_key_values;
300 
301     #[test]
parse_gpu_render_server_parameters()302     fn parse_gpu_render_server_parameters() {
303         let res: GpuRenderServerParameters = from_key_values("path=/some/path").unwrap();
304         assert_eq!(
305             res,
306             GpuRenderServerParameters {
307                 path: "/some/path".into(),
308                 cache_path: None,
309                 cache_size: None,
310                 foz_db_list_path: None,
311                 precompiled_cache_path: None,
312             }
313         );
314 
315         let res: GpuRenderServerParameters = from_key_values("/some/path").unwrap();
316         assert_eq!(
317             res,
318             GpuRenderServerParameters {
319                 path: "/some/path".into(),
320                 cache_path: None,
321                 cache_size: None,
322                 foz_db_list_path: None,
323                 precompiled_cache_path: None,
324             }
325         );
326 
327         let res: GpuRenderServerParameters =
328             from_key_values("path=/some/path,cache-path=/cache/path,cache-size=16M").unwrap();
329         assert_eq!(
330             res,
331             GpuRenderServerParameters {
332                 path: "/some/path".into(),
333                 cache_path: Some("/cache/path".into()),
334                 cache_size: Some("16M".into()),
335                 foz_db_list_path: None,
336                 precompiled_cache_path: None,
337             }
338         );
339 
340         let res: GpuRenderServerParameters = from_key_values(
341             "path=/some/path,cache-path=/cache/path,cache-size=16M,foz-db-list-path=/db/list/path,precompiled-cache-path=/precompiled/path",
342         )
343         .unwrap();
344         assert_eq!(
345             res,
346             GpuRenderServerParameters {
347                 path: "/some/path".into(),
348                 cache_path: Some("/cache/path".into()),
349                 cache_size: Some("16M".into()),
350                 foz_db_list_path: Some("/db/list/path".into()),
351                 precompiled_cache_path: Some("/precompiled/path".into()),
352             }
353         );
354 
355         let res =
356             from_key_values::<GpuRenderServerParameters>("cache-path=/cache/path,cache-size=16M");
357         assert!(res.is_err());
358 
359         let res = from_key_values::<GpuRenderServerParameters>("");
360         assert!(res.is_err());
361     }
362 }
363