| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #[macro_use] |
| extern crate lazy_static; |
| |
| #[cfg(test)] |
| mod test; |
| |
| mod common; |
| mod dbus_wrapper; |
| mod dlc_queue; |
| mod service; |
| mod shader_cache_mount; |
| |
| use common::*; |
| |
| use anyhow::Result; |
| use dbus::channel::MatchingReceiver; |
| use dbus::message::MatchRule; |
| use dbus::MethodErr; |
| use dbus_crossroads::Crossroads; |
| use dbus_wrapper::dbus_constants; |
| use libchromeos::syslog; |
| use log::{debug, error, info, warn}; |
| use tokio::signal::unix::{signal, SignalKind}; |
| |
| const BINARY_IDENTITY: &str = "shadercached"; |
| |
| #[tokio::main] |
| pub async fn main() -> Result<()> { |
| libchromeos::panic_handler::install_memfd_handler(); |
| |
| let identity = |
| syslog::get_ident_from_process().unwrap_or_else(|| String::from(BINARY_IDENTITY)); |
| if let Err(e) = syslog::init_with_level(identity, false, syslog::LevelFilter::Info) { |
| println!("Failed to initialize syslog: {}", e); |
| std::process::exit(1); |
| } |
| |
| info!("Starting shadercached..."); |
| if *BOOT_ID == *OS_BUILD_ID { |
| warn!("Failed to digest OS build id, falling back to boot id"); |
| } |
| |
| // Mount points are VM GPU cache mounting destinations. Each mount point has |
| // metadata on what is last requested to be mounted there and current mount |
| // status. |
| // Note: MountPoints is Arc-ed (cloning returns pointer to the object, |
| // thread safe). |
| // TODO(b/271776528): Query current sessions and pre-populate for logged in |
| // users. |
| // TODO(b/271776528): Listen to "SessionStateChanged:started" and |
| // pre-populate. |
| // TODO(b/271776528): Refactor shader cache mount to encapsulate cryptohome |
| // operation. |
| // Ex. user_id -> cryptohome |
| // cryptohome.get(vm_id) -> ShaderCacheMount |
| let mount_map = shader_cache_mount::new_mount_map(); |
| // TODO(b/271776528): Export dlc queue before exiting |
| let dlc_queue = dlc_queue::new_queue(); |
| |
| debug!( |
| "GPU PCI device ID is {:04x}, DLC variant {}", |
| *GPU_DEVICE_ID, *GPU_DEVICE_DLC_VARIANT |
| ); |
| |
| let (resource, c) = dbus_tokio::connection::new_system_sync()?; |
| // If D-Bus connection drops unexpectedly, cleanup the mount points then |
| // exit. |
| let mount_map_resource = mount_map.clone(); |
| tokio::spawn(async { |
| let err = resource.await; |
| attempt_unmount_all(mount_map_resource).await; |
| error!("Lost connection to system D-Bus: {}", err); |
| std::process::exit(1); |
| }); |
| let dbus_conn = dbus_wrapper::DbusConnection::new(c.clone()); |
| |
| // Get the service name from system D-Bus. |
| c.request_name(dbus_constants::SERVICE_NAME, false, true, false) |
| .await?; |
| |
| // Setup crossroads with async support. |
| let mut cr = Crossroads::new(); |
| cr.set_async_support(Some(( |
| c.clone(), |
| Box::new(|x| { |
| tokio::spawn(x); |
| }), |
| ))); |
| |
| // D-Bus interface for ShaderCache service |
| let iface_token = cr.register(dbus_constants::INTERFACE_NAME, |builder| { |
| let mount_map_handle_install = mount_map.clone(); |
| let dlc_queue_handle_install = dlc_queue.clone(); |
| let dbus_conn_handle_install = dbus_conn.clone(); |
| // Method Install |
| builder.method_with_cr_async( |
| dbus_constants::INSTALL_METHOD, |
| ("install_request_proto",), |
| ("install_response_proto",), |
| move |mut ctx, _, (raw_bytes,): (Vec<u8>,)| { |
| debug!("Received install request"); |
| let handler = service::handle_install( |
| raw_bytes, |
| mount_map_handle_install.clone(), |
| dlc_queue_handle_install.clone(), |
| dbus_conn_handle_install.clone(), |
| ); |
| async move { |
| match handler.await.map_err(to_method_err) { |
| Ok(result) => ctx.reply(Ok((result,))), |
| Err(e) => ctx.reply(Err(e)), |
| } |
| } |
| }, |
| ); |
| |
| let dlc_queue_handle_uninstall = dlc_queue.clone(); |
| // Method Uninstall |
| builder.method_with_cr_async( |
| dbus_constants::UNINSTALL_METHOD, |
| ("uninstall_request_proto",), |
| (), |
| move |mut ctx, _, (raw_bytes,): (Vec<u8>,)| { |
| debug!("Received uninstall request"); |
| let handler = |
| service::handle_uninstall(raw_bytes, dlc_queue_handle_uninstall.clone()); |
| async move { |
| match handler.await.map_err(to_method_err) { |
| Ok(result) => ctx.reply(Ok(result)), |
| Err(e) => ctx.reply(Err(e)), |
| } |
| } |
| }, |
| ); |
| |
| let dbus_conn_handle_purge = dbus_conn.clone(); |
| let mount_map_handle_purge = mount_map.clone(); |
| // Method Purge |
| builder.method_with_cr_async( |
| dbus_constants::PURGE_METHOD, |
| ("purge_request_proto",), |
| (), |
| move |mut ctx, _, (raw_bytes,): (Vec<u8>,)| { |
| info!("Received purge request"); |
| let handler = service::handle_purge( |
| raw_bytes, |
| mount_map_handle_purge.clone(), |
| dbus_conn_handle_purge.clone(), |
| ); |
| async move { |
| match handler.await.map_err(to_method_err) { |
| Ok(result) => ctx.reply(Ok(result)), |
| Err(e) => ctx.reply(Err(e)), |
| } |
| } |
| }, |
| ); |
| |
| let mount_map_handle_unmount = mount_map.clone(); |
| // Method umount only |
| builder.method_with_cr_async( |
| dbus_constants::UNMOUNT_METHOD, |
| ("unmount_request_proto",), |
| (), |
| move |mut ctx, _, (raw_bytes,): (Vec<u8>,)| { |
| debug!("Received unmount request"); |
| let handler = service::handle_unmount(raw_bytes, mount_map_handle_unmount.clone()); |
| async move { |
| match handler.await.map_err(to_method_err) { |
| Ok(result) => ctx.reply(Ok(result)), |
| Err(e) => ctx.reply(Err(e)), |
| } |
| } |
| }, |
| ); |
| |
| let mount_map_handle_prepare_shader_cache = mount_map.clone(); |
| // Method umount only |
| builder.method_with_cr_async( |
| dbus_constants::PREPARE_SHADER_CACHE_METHOD, |
| ("prepare_shader_cache_request_proto",), |
| ("prepare_shader_cache_response_proto",), |
| move |mut ctx, _, (raw_bytes,): (Vec<u8>,)| { |
| debug!("Received prepare shader cache request"); |
| let handler = service::handle_prepare_shader_cache( |
| raw_bytes, |
| mount_map_handle_prepare_shader_cache.clone(), |
| ); |
| async move { |
| match handler.await.map_err(to_method_err) { |
| Ok(result) => ctx.reply(Ok((result,))), |
| Err(e) => ctx.reply(Err(e)), |
| } |
| } |
| }, |
| ); |
| }); |
| cr.insert(dbus_constants::PATH_NAME, &[iface_token], ()); |
| |
| // Periodic unmounter |
| let dbus_conn_unmounter = dbus_conn.clone(); |
| let mount_map_unmounter = mount_map.clone(); |
| tokio::spawn(async move { |
| // Periodic unmount |
| debug!( |
| "Periodic unmounter thread stated with interval {:?}", |
| UNMOUNTER_INTERVAL |
| ); |
| loop { |
| tokio::time::sleep(UNMOUNTER_INTERVAL).await; |
| let mut mount_map = mount_map_unmounter.write().await; |
| for (vm_id, shader_cache_mount) in mount_map.iter_mut() { |
| let mut mount_statuses = shader_cache_mount.process_unmount_queue(); |
| for status in mount_statuses.iter_mut() { |
| status.vm_name = vm_id.vm_name.clone(); |
| status.vm_owner_id = vm_id.vm_owner_id.clone(); |
| } |
| |
| if let Err(e) = service::signal::signal_mount_status( |
| mount_statuses, |
| dbus_conn_unmounter.clone(), |
| ) { |
| error!("{}", e); |
| } |
| } |
| } |
| }); |
| |
| // DLC handler |
| let dbus_conn_dlc_handler = dbus_conn.clone(); |
| let mount_map_dlc_handler = mount_map.clone(); |
| let dlc_queue_dlc_handler = dlc_queue.clone(); |
| tokio::spawn(async move { |
| // Periodically install or uninstall DLCs |
| debug!( |
| "Periodic dlc handler thread stated with interval {:?}", |
| DLC_HANDLER_INTERVAL |
| ); |
| loop { |
| tokio::time::sleep(DLC_HANDLER_INTERVAL).await; |
| { |
| service::periodic_dlc_handler( |
| mount_map_dlc_handler.clone(), |
| dlc_queue_dlc_handler.clone(), |
| dbus_conn_dlc_handler.clone(), |
| ) |
| .await; |
| } |
| } |
| }); |
| |
| // We need to create a new connection to receive signals explicitly. |
| // Reusing existing connection rejects the D-Bus signals. |
| let mount_map_listener = mount_map.clone(); |
| let (resource_listen, c_listen) = dbus_tokio::connection::new_system_sync()?; |
| tokio::spawn(async { |
| let err = resource_listen.await; |
| attempt_unmount_all(mount_map_listener).await; |
| error!("Lost connection to system D-Bus: {}", err); |
| std::process::exit(1); |
| }); |
| |
| // Listen to DlcService DlcStateChanged |
| let mr_dlc_service_dlc_state_changed = MatchRule::new_signal( |
| dbus_constants::dlc_service::INTERFACE_NAME, |
| dbus_constants::dlc_service::DLC_STATE_CHANGED_SIGNAL, |
| ); |
| debug!( |
| "Matching DlcService signal: {}", |
| mr_dlc_service_dlc_state_changed.match_str() |
| ); |
| // For sending signals, we still have to use existing object with correct |
| // service name. |
| let dbus_conn_dlc_listener = dbus_conn.clone(); |
| let mount_map_dlc_listener = mount_map.clone(); |
| let dlc_queue_dlc_listener = dlc_queue.clone(); |
| // |msg_match| should remain in this scope to serve |
| let dlc_service_match = c_listen |
| .add_match(mr_dlc_service_dlc_state_changed) |
| .await? |
| .cb(move |_, (raw_bytes,): (Vec<u8>,)| { |
| tokio::spawn(service::handle_dlc_state_changed( |
| raw_bytes, |
| mount_map_dlc_listener.clone(), |
| dlc_queue_dlc_listener.clone(), |
| dbus_conn_dlc_listener.clone(), |
| )); |
| true |
| }); |
| |
| // Listen to VM stopped signals |
| let mr_concierge_vm_stopping = MatchRule::new_signal( |
| dbus_constants::vm_concierge::INTERFACE_NAME, |
| dbus_constants::vm_concierge::VM_STOPPING_SIGNAL, |
| ); |
| debug!( |
| "Matching Concierge signal: {}", |
| mr_concierge_vm_stopping.match_str() |
| ); |
| let mount_map_concierge_listener = mount_map.clone(); |
| // |msg_match| should remain in this scope to serve |
| let concierge_match = c_listen.add_match(mr_concierge_vm_stopping).await?.cb( |
| move |_, (raw_bytes,): (Vec<u8>,)| { |
| tokio::spawn(service::handle_vm_stopped( |
| raw_bytes, |
| mount_map_concierge_listener.clone(), |
| )); |
| true |
| }, |
| ); |
| |
| // Listen to Spaced StatefulDiskSpaceUpdate |
| let mr_spaced_stateful_disk_space_update = MatchRule::new_signal( |
| dbus_constants::spaced::INTERFACE_NAME, |
| dbus_constants::spaced::STATEFUL_DISK_SPACE_UPDATE, |
| ); |
| debug!( |
| "Matching Spaced signal: {}", |
| mr_spaced_stateful_disk_space_update.match_str() |
| ); |
| let mount_map_spaced_listener = mount_map.clone(); |
| let dbus_conn_spaced_listener = dbus_conn.clone(); |
| let spaced_match = c_listen |
| .add_match(mr_spaced_stateful_disk_space_update) |
| .await? |
| .cb(move |_, (raw_bytes,): (Vec<u8>,)| { |
| tokio::spawn(service::handle_disk_space_update( |
| raw_bytes, |
| mount_map_spaced_listener.clone(), |
| dbus_conn_spaced_listener.clone(), |
| )); |
| true |
| }); |
| |
| // Start serving D-Bus methods |
| let receive_token = c.start_receive( |
| MatchRule::new_method_call(), |
| Box::new(move |msg, conn| { |
| cr.handle_message(msg, conn).unwrap(); |
| true |
| }), |
| ); |
| info!("shadercached serving!"); |
| |
| // Wait for process exit |
| signal(SignalKind::terminate()).unwrap().recv().await; |
| |
| info!("Cleaning up..."); |
| // Stop receiving connections |
| c.stop_receive(receive_token); |
| // Delete |msg_match| to stop listening to DlcService signals |
| drop(dlc_service_match); |
| drop(concierge_match); |
| drop(spaced_match); |
| |
| attempt_unmount_all(mount_map).await; |
| |
| info!("Exiting with successful cleanup!"); |
| Ok(()) |
| } |
| |
| async fn attempt_unmount_all(mount_map: shader_cache_mount::ShaderCacheMountMapPtr) { |
| match mount_map.clear_all_mounts(None).await { |
| Ok(_) => { |
| if let Err(e) = mount_map |
| .wait_unmount_completed(None, UNMOUNTER_INTERVAL) |
| .await |
| { |
| error!("Failed to wait for unmounts to complete: {}", e) |
| } |
| } |
| Err(e) => error!("Failed to queue unmounts: {}", e), |
| } |
| } |
| |
| fn to_method_err<T: std::fmt::Display>(result: T) -> MethodErr { |
| MethodErr::failed(&result) |
| } |