blob: 284f6d4e4371bf98d1359be2629099889f617f95 [file] [log] [blame]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! GPU related things
//! depends on "gpu" feature
static_assertions::assert_cfg!(feature = "gpu");
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use base::platform::move_proc_to_cgroup;
use jail::*;
use serde::Deserialize;
use serde::Serialize;
use serde_keyvalue::FromKeyValues;
use super::*;
use crate::crosvm::config::Config;
pub struct GpuCacheInfo<'a> {
directory: Option<&'a str>,
environment: Vec<(&'a str, &'a str)>,
}
pub fn 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> {
let mut dir = None;
let mut env = Vec::new();
// TODO (renatopereyra): Remove deprecated env vars once all src/third_party/mesa* are updated.
if let Some(cache_dir) = cache_dir {
if !Path::new(cache_dir).exists() {
warn!("shader caching dir {} does not exist", cache_dir);
// Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
} else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
warn!("shader caching not yet supported on ARM with sandbox enabled");
// Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
env.push(("MESA_SHADER_CACHE_DISABLE", "true"));
} else {
dir = Some(cache_dir.as_str());
// Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));
env.push(("MESA_SHADER_CACHE_DISABLE", "false"));
env.push(("MESA_SHADER_CACHE_DIR", cache_dir.as_str()));
env.push(("MESA_DISK_CACHE_DATABASE", "1"));
if let Some(foz_db_list_path) = foz_db_list_path {
env.push(("MESA_DISK_CACHE_COMBINE_RW_WITH_RO_FOZ", "1"));
env.push((
"MESA_DISK_CACHE_READ_ONLY_FOZ_DBS_DYNAMIC_LIST",
foz_db_list_path,
));
}
if let Some(cache_size) = cache_size {
// Deprecated in https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/15390
env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));
env.push(("MESA_SHADER_CACHE_MAX_SIZE", cache_size.as_str()));
}
}
}
GpuCacheInfo {
directory: dir,
environment: env,
}
}
pub fn create_gpu_device(
cfg: &Config,
exit_evt_wrtube: &SendTube,
gpu_control_tube: Tube,
resource_bridges: Vec<Tube>,
render_server_fd: Option<SafeDescriptor>,
event_devices: Vec<EventDevice>,
) -> DeviceResult {
let is_sandboxed = cfg.jail_config.is_some();
let mut gpu_params = cfg.gpu_parameters.clone().unwrap();
gpu_params.external_blob = is_sandboxed;
let mut display_backends = vec![
virtio::DisplayBackend::X(cfg.x_display.clone()),
virtio::DisplayBackend::Stub,
];
// Use the unnamed socket for GPU display screens.
if let Some(socket_path) = cfg.wayland_socket_paths.get("") {
display_backends.insert(
0,
virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
);
}
let dev = virtio::Gpu::new(
exit_evt_wrtube
.try_clone()
.context("failed to clone tube")?,
gpu_control_tube,
resource_bridges,
display_backends,
&gpu_params,
render_server_fd,
event_devices,
virtio::base_features(cfg.protection_type),
&cfg.wayland_socket_paths,
cfg.gpu_cgroup_path.as_ref(),
);
let jail = if let Some(jail_config) = &cfg.jail_config {
let mut config = SandboxConfig::new(jail_config, "gpu_device");
config.bind_mounts = true;
// Allow changes made externally take effect immediately to allow shaders to be dynamically
// added by external processes.
config.remount_mode = Some(libc::MS_SLAVE);
let mut jail = create_gpu_minijail(&jail_config.pivot_root, &config)?;
// Prepare GPU shader disk cache directory.
let cache_info = get_gpu_cache_info(
gpu_params.cache_path.as_ref(),
gpu_params.cache_size.as_ref(),
None,
cfg.jail_config.is_some(),
);
if let Some(dir) = cache_info.directory {
// Manually bind mount recursively to allow DLC shader caches
// to be propagated to the GPU process.
jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
}
for (key, val) in cache_info.environment {
env::set_var(key, val);
}
// Bind mount the wayland socket's directory into jail's root. This is necessary since
// each new wayland context must open() the socket. If the wayland socket is ever
// destroyed and remade in the same host directory, new connections will be possible
// without restarting the wayland device.
for socket_path in cfg.wayland_socket_paths.values() {
let dir = socket_path.parent().with_context(|| {
format!(
"wayland socket path '{}' has no parent",
socket_path.display(),
)
})?;
jail.mount_bind(dir, dir, true)?;
}
Some(jail)
} else {
None
};
Ok(VirtioDeviceStub {
dev: Box::new(dev),
jail,
})
}
#[derive(Debug, Deserialize, Serialize, FromKeyValues, PartialEq, Eq)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct GpuRenderServerParameters {
pub path: PathBuf,
pub cache_path: Option<String>,
pub cache_size: Option<String>,
pub foz_db_list_path: Option<String>,
pub precompiled_cache_path: Option<String>,
}
fn get_gpu_render_server_environment(cache_info: Option<&GpuCacheInfo>) -> Result<Vec<String>> {
let mut env = HashMap::<String, String>::new();
let os_env_len = env::vars_os().count();
if let Some(cache_info) = cache_info {
env.reserve(os_env_len + cache_info.environment.len());
for (key, val) in cache_info.environment.iter() {
env.insert(key.to_string(), val.to_string());
}
} else {
env.reserve(os_env_len);
}
for (key_os, val_os) in env::vars_os() {
// minijail should accept OsStr rather than str...
let into_string_err = |_| anyhow!("invalid environment key/val");
let key = key_os.into_string().map_err(into_string_err)?;
let val = val_os.into_string().map_err(into_string_err)?;
env.entry(key).or_insert(val);
}
// TODO(b/237493180): workaround to enable ETC2 format emulation in RADV for ARCVM
if !env.contains_key("radv_require_etc2") {
env.insert("radv_require_etc2".to_string(), "true".to_string());
}
Ok(env.iter().map(|(k, v)| format!("{}={}", k, v)).collect())
}
pub fn start_gpu_render_server(
cfg: &Config,
render_server_parameters: &GpuRenderServerParameters,
) -> Result<(Minijail, SafeDescriptor)> {
let (server_socket, client_socket) =
UnixSeqpacket::pair().context("failed to create render server socket")?;
let (jail, cache_info) = if let Some(jail_config) = &cfg.jail_config {
let mut config = SandboxConfig::new(jail_config, "gpu_render_server");
// Allow changes made externally take effect immediately to allow shaders to be dynamically
// added by external processes.
config.remount_mode = Some(libc::MS_SLAVE);
config.bind_mounts = true;
// Run as root in the jail to keep capabilities after execve, which is needed for
// mounting to work. All capabilities will be dropped afterwards.
config.run_as = RunAsUser::Root;
let mut jail = create_gpu_minijail(&jail_config.pivot_root, &config)?;
let cache_info = get_gpu_cache_info(
render_server_parameters.cache_path.as_ref(),
render_server_parameters.cache_size.as_ref(),
render_server_parameters.foz_db_list_path.as_ref(),
true,
);
if let Some(dir) = cache_info.directory {
// Manually bind mount recursively to allow DLC shader caches
// to be propagated to the GPU process.
jail.mount(dir, dir, "", (libc::MS_BIND | libc::MS_REC) as usize)?;
}
if let Some(precompiled_cache_dir) = &render_server_parameters.precompiled_cache_path {
jail.mount_bind(precompiled_cache_dir, precompiled_cache_dir, true)?;
}
// bind mount /dev/log for syslog
let log_path = Path::new("/dev/log");
if log_path.exists() {
jail.mount_bind(log_path, log_path, true)?;
}
(jail, Some(cache_info))
} else {
(Minijail::new().context("failed to create jail")?, None)
};
let inheritable_fds = [
server_socket.as_raw_descriptor(),
libc::STDOUT_FILENO,
libc::STDERR_FILENO,
];
let cmd = &render_server_parameters.path;
let cmd_str = cmd
.to_str()
.ok_or_else(|| anyhow!("invalid render server path"))?;
let fd_str = server_socket.as_raw_descriptor().to_string();
let args = [cmd_str, "--socket-fd", &fd_str];
let env = Some(get_gpu_render_server_environment(cache_info.as_ref())?);
let mut envp: Option<Vec<&str>> = None;
if let Some(ref env) = env {
envp = Some(env.iter().map(AsRef::as_ref).collect());
}
let render_server_pid = jail
.run_command(minijail::Command::new_for_path(
cmd,
&inheritable_fds,
&args,
envp.as_deref(),
)?)
.context("failed to start gpu render server")?;
if let Some(gpu_server_cgroup_path) = &cfg.gpu_server_cgroup_path {
move_proc_to_cgroup(gpu_server_cgroup_path.to_path_buf(), render_server_pid)?;
}
Ok((jail, SafeDescriptor::from(client_socket)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crosvm::config::from_key_values;
#[test]
fn parse_gpu_render_server_parameters() {
let res: GpuRenderServerParameters = from_key_values("path=/some/path").unwrap();
assert_eq!(
res,
GpuRenderServerParameters {
path: "/some/path".into(),
cache_path: None,
cache_size: None,
foz_db_list_path: None,
precompiled_cache_path: None,
}
);
let res: GpuRenderServerParameters = from_key_values("/some/path").unwrap();
assert_eq!(
res,
GpuRenderServerParameters {
path: "/some/path".into(),
cache_path: None,
cache_size: None,
foz_db_list_path: None,
precompiled_cache_path: None,
}
);
let res: GpuRenderServerParameters =
from_key_values("path=/some/path,cache-path=/cache/path,cache-size=16M").unwrap();
assert_eq!(
res,
GpuRenderServerParameters {
path: "/some/path".into(),
cache_path: Some("/cache/path".into()),
cache_size: Some("16M".into()),
foz_db_list_path: None,
precompiled_cache_path: None,
}
);
let res: GpuRenderServerParameters = from_key_values(
"path=/some/path,cache-path=/cache/path,cache-size=16M,foz-db-list-path=/db/list/path,precompiled-cache-path=/precompiled/path",
)
.unwrap();
assert_eq!(
res,
GpuRenderServerParameters {
path: "/some/path".into(),
cache_path: Some("/cache/path".into()),
cache_size: Some("16M".into()),
foz_db_list_path: Some("/db/list/path".into()),
precompiled_cache_path: Some("/precompiled/path".into()),
}
);
let res =
from_key_values::<GpuRenderServerParameters>("cache-path=/cache/path,cache-size=16M");
assert!(res.is_err());
let res = from_key_values::<GpuRenderServerParameters>("");
assert!(res.is_err());
}
}