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