From 10e3217eb531afd32a0870a37add53a9ba3b151b Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 7 Aug 2026 09:25:08 +0200 Subject: [PATCH 1/4] feat(cardwired): add a new smart policy interface --- crates/cardwire-daemon/src/analyzer/models.rs | 6 +- crates/cardwire-daemon/src/daemon.rs | 6 ++ crates/cardwire-daemon/src/interface/mod.rs | 2 + crates/cardwire-daemon/src/interface/smart.rs | 96 +++++++++++++++++++ crates/cardwire-daemon/src/models.rs | 10 +- crates/cardwire-ebpf-userspace/src/lib.rs | 31 ++++-- 6 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 crates/cardwire-daemon/src/interface/smart.rs diff --git a/crates/cardwire-daemon/src/analyzer/models.rs b/crates/cardwire-daemon/src/analyzer/models.rs index 4a9280af..7cf36588 100644 --- a/crates/cardwire-daemon/src/analyzer/models.rs +++ b/crates/cardwire-daemon/src/analyzer/models.rs @@ -75,8 +75,8 @@ impl CardwireAnalyzer { let exec_ring = blocker.get_exec_ring()?; let close_ring = blocker.get_close_ring()?; let report_ring = blocker.get_report_ring()?; - let pid_map = blocker.get_pid_map()?; - let forced_map = blocker.get_forced_pid_map()?; + let pid_map = Arc::clone(&blocker.pid_map); + let forced_map = Arc::clone(&blocker.forced_map); let ebpf_logger = blocker.get_ebpf_logger()?; let exec_ring = AsyncFd::new(exec_ring)?; @@ -85,8 +85,6 @@ impl CardwireAnalyzer { // Now Rwlock -> Arc let exec_ring = Arc::new(Mutex::new(exec_ring)); - let pid_map = Arc::new(RwLock::new(pid_map)); - let forced_map = Arc::new(RwLock::new(forced_map)); let close_ring = Arc::new(Mutex::new(close_ring)); let report_ring = Arc::new(Mutex::new(report_ring)); let ebpf_logger: Arc>>> = diff --git a/crates/cardwire-daemon/src/daemon.rs b/crates/cardwire-daemon/src/daemon.rs index 27e9ca6a..bc784abf 100644 --- a/crates/cardwire-daemon/src/daemon.rs +++ b/crates/cardwire-daemon/src/daemon.rs @@ -120,6 +120,12 @@ async fn spawn_dbus_api( .interface::<_, crate::interface::LoggerInterface>(path) .await?; daemon.logger_signal = Some(logger_ref.signal_emitter().clone()); + + // Cardwire Smart Policy + object_server + .at(path, daemon.smart_policy_interface.clone()) + .await?; + drop(power_tasks); // drop gpu list to prevent deadlock drop(gpu_interfaces); diff --git a/crates/cardwire-daemon/src/interface/mod.rs b/crates/cardwire-daemon/src/interface/mod.rs index 91a28347..d12d5a34 100644 --- a/crates/cardwire-daemon/src/interface/mod.rs +++ b/crates/cardwire-daemon/src/interface/mod.rs @@ -3,6 +3,7 @@ mod debug; mod gpu; mod logger; mod mode; +mod smart; mod switcheroo; pub use config::{ConfigInterface, ConfigMemory}; @@ -10,4 +11,5 @@ pub use debug::DebugInterface; pub use gpu::{GpuInterface, GpuInterfaceSignals}; pub use logger::{LogEntry, LoggerInterface, LoggerInterfaceSignals}; pub use mode::{ModeInterface, Modes}; +pub use smart::SmartPolicyInterface; pub use switcheroo::SwitcherooInterface; diff --git a/crates/cardwire-daemon/src/interface/smart.rs b/crates/cardwire-daemon/src/interface/smart.rs new file mode 100644 index 00000000..8f5ab718 --- /dev/null +++ b/crates/cardwire-daemon/src/interface/smart.rs @@ -0,0 +1,96 @@ +use aya::maps::HashMap as AyaHashMap; +use cardwire_ebpf_userspace::EbpfBlocker; +use std::{path::Path, sync::Arc}; + +use tokio::sync::RwLock; +use zbus::{ + fdo::{ + self, Error::{Failed, InvalidArgs} + }, interface +}; + +#[derive(Clone, Debug)] +pub struct SmartPolicyInterface { + pid_map: Arc>>, + forced_map: Arc>>, +} + +impl SmartPolicyInterface { + pub fn build(blocker: &mut EbpfBlocker) -> Self { + let pid_map = Arc::clone(&blocker.pid_map); + let forced_map = Arc::clone(&blocker.forced_map); + + Self { + pid_map, + forced_map, + } + } +} + +#[interface(name = "org.opengamingcollective.cardwire.SmartPolicy")] +impl SmartPolicyInterface { + /// Authorized a pid to access a specific GPU + pub async fn request_process_access( + &self, + pid: u32, + policy: String, + value: u32, + ) -> Result<(), fdo::Error> { + // Check if the process exists, leave if it doesnt + if !Path::new(&format!("/proc/{}", pid)).exists() { + return Err(fdo::Error::Failed("process doesn't exist".to_string())); + } + // Match the policy and add the pid to the corresponding ebpf map + match policy.as_str() { + // Default, do nothing + "Default" => Ok(()), + // Equivalent to CARDWIRE_ALLOW=1, show both iGPU and dGPU + "Allow_dGPU" => { + let mut pid_map = self.pid_map.write().await; + let _res = pid_map + .insert(pid, 0, 0) + .map_err(|err| fdo::Error::Failed(err.to_string()))?; + Ok(()) + } + // Equivalent to CARDWIRE_FORCE_DGPU=value + "Force_dGPU" => { + let mut force_map = self.forced_map.write().await; + force_map + .insert(pid, value, 0) + .map_err(|err| Failed(err.to_string())) + } + // Equivalent to CARDWIRE_FORCE_GPU=value + "Force_GPU" => { + let mut force_map = self.forced_map.write().await; + force_map + .insert(pid, value, 0) + .map_err(|err| Failed(err.to_string())) + } + _ => Err(fdo::Error::InvalidArgs(format!("invalid arg: {}", policy))), + } + } + + /// Check if the process is inside PID or FORCED map, and return the map type with the gpu_id + /// associed + pub async fn get_process_status(&self, pid: u32) -> Result<(String, Option), fdo::Error> { + let mut status = String::new(); + let mut gpu_id: Option = None; + + { + let pid_map = self.pid_map.read().await; + if let Ok(gpu) = pid_map.get(&pid, 0) { + status = "Allowed".to_string(); + gpu_id = Some(gpu) + } + } + { + let forced_map = self.forced_map.read().await; + if let Ok(gpu) = forced_map.get(&pid, 0) { + status = "Forced".to_string(); + gpu_id = Some(gpu) + } + } + + Ok((status, gpu_id)) + } +} diff --git a/crates/cardwire-daemon/src/models.rs b/crates/cardwire-daemon/src/models.rs index b674242c..ace81472 100644 --- a/crates/cardwire-daemon/src/models.rs +++ b/crates/cardwire-daemon/src/models.rs @@ -3,7 +3,7 @@ use crate::{ analyzer::CardwireAnalyzer, core::{ gpu::{GpuEnumerator, GpuVendor}, inode::exp_nvidia_inodes, pci::{self} }, file::{CardwireConfig, CardwireGpuState, CardwireModeState}, interface::{ - ConfigInterface, ConfigMemory, DebugInterface, GpuInterface, LoggerInterface, ModeInterface, Modes, SwitcherooInterface + ConfigInterface, ConfigMemory, DebugInterface, GpuInterface, LoggerInterface, ModeInterface, Modes, SmartPolicyInterface, SwitcherooInterface }, tasks }; use anyhow::{Context, Result}; @@ -35,6 +35,7 @@ pub struct DaemonManager { pub switcheroo_interface: SwitcherooInterface, pub logger_interface: LoggerInterface, pub logger_signal: Option>, + pub smart_policy_interface: SmartPolicyInterface, pub inner: DaemonInner, } @@ -59,7 +60,11 @@ impl DaemonManager { let pci_list: Arc>> = Arc::new(RwLock::new(pci_devices)); - let blocker = Arc::new(RwLock::new(EbpfBlocker::new()?)); + let mut blocker = EbpfBlocker::new()?; + + let smart_policy_interface = SmartPolicyInterface::build(&mut blocker); + + let blocker = Arc::new(RwLock::new(blocker)); let power_tasks = Arc::new(RwLock::new(BTreeMap::new())); @@ -112,6 +117,7 @@ impl DaemonManager { switcheroo_interface: SwitcherooInterface::build(Arc::clone(&gpu_interfaces)), logger_interface, logger_signal: None, + smart_policy_interface, inner: DaemonInner { mode_state: Arc::clone(&mode_state), gpu_state: Arc::clone(&gpu_state), diff --git a/crates/cardwire-ebpf-userspace/src/lib.rs b/crates/cardwire-ebpf-userspace/src/lib.rs index 07d603d9..68f89283 100644 --- a/crates/cardwire-ebpf-userspace/src/lib.rs +++ b/crates/cardwire-ebpf-userspace/src/lib.rs @@ -1,7 +1,7 @@ //! main lib code of cardwire-ebpf mod errors; -use std::{fs, path::Path}; +use std::{fs, path::Path, sync::Arc}; pub use crate::errors::{CardwireEbpfError, CardwireEbpfResult}; use aya::{ @@ -9,7 +9,9 @@ use aya::{ }; use aya_log::EbpfLogger; use log::{Log, error, info, warn}; -use tokio::io::{Interest, unix::AsyncFd}; +use tokio::{ + io::{Interest, unix::AsyncFd}, sync::RwLock +}; pub enum EbpfSettings { ExperimentalNvidia, @@ -17,6 +19,8 @@ pub enum EbpfSettings { pub struct EbpfBlocker { ebpf: Ebpf, + pub pid_map: Arc>>, + pub forced_map: Arc>>, } impl EbpfBlocker { @@ -131,7 +135,18 @@ impl EbpfBlocker { } }; } - Ok(Self { ebpf }) + + let pid_map = Self::get_pid_map(&mut ebpf)?; + let forced_map = Self::get_forced_pid_map(&mut ebpf)?; + + let pid_map = Arc::new(RwLock::new(pid_map)); + let forced_map = Arc::new(RwLock::new(forced_map)); + + Ok(Self { + ebpf, + pid_map, + forced_map, + }) } /// whitelist cardwire's pid to prevent self-locking in ebpf @@ -322,9 +337,11 @@ impl EbpfBlocker { } /// take the CW_ALLOWED_PID HashMap map from the blocker - pub fn get_pid_map(&mut self) -> CardwireEbpfResult> { + pub fn get_pid_map( + ebpf: &mut Ebpf, + ) -> CardwireEbpfResult> { let map_str = "CW_ALLOWED_PID"; - let map = match self.ebpf.take_map(map_str) { + let map = match ebpf.take_map(map_str) { Some(map) => map, None => { error!("error while trying to take map {}", map_str); @@ -345,10 +362,10 @@ impl EbpfBlocker { /// take the CW_FORCED_PID HashMap map from the blocker pub fn get_forced_pid_map( - &mut self, + ebpf: &mut Ebpf, ) -> CardwireEbpfResult> { let map_str = "CW_FORCED_PID"; - let map = match self.ebpf.take_map(map_str) { + let map = match ebpf.take_map(map_str) { Some(map) => map, None => { error!("error while trying to take map {}", map_str); From bb1ffc661eada751fe228964f6f0acff85f746ca Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 7 Aug 2026 09:26:00 +0200 Subject: [PATCH 2/4] chore(cardwired): clean up smart api code --- crates/cardwire-daemon/src/interface/smart.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/smart.rs b/crates/cardwire-daemon/src/interface/smart.rs index 8f5ab718..67354132 100644 --- a/crates/cardwire-daemon/src/interface/smart.rs +++ b/crates/cardwire-daemon/src/interface/smart.rs @@ -4,9 +4,7 @@ use std::{path::Path, sync::Arc}; use tokio::sync::RwLock; use zbus::{ - fdo::{ - self, Error::{Failed, InvalidArgs} - }, interface + fdo::{self, Error::Failed}, interface }; #[derive(Clone, Debug)] @@ -47,10 +45,9 @@ impl SmartPolicyInterface { // Equivalent to CARDWIRE_ALLOW=1, show both iGPU and dGPU "Allow_dGPU" => { let mut pid_map = self.pid_map.write().await; - let _res = pid_map + pid_map .insert(pid, 0, 0) - .map_err(|err| fdo::Error::Failed(err.to_string()))?; - Ok(()) + .map_err(|err| fdo::Error::Failed(err.to_string())) } // Equivalent to CARDWIRE_FORCE_DGPU=value "Force_dGPU" => { From 9bde3d355a3445bc8130aa11f91c887f54cf9d1c Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 7 Aug 2026 09:58:01 +0200 Subject: [PATCH 3/4] fix(cardwired): handle aya map error --- crates/cardwire-daemon/src/interface/smart.rs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/cardwire-daemon/src/interface/smart.rs b/crates/cardwire-daemon/src/interface/smart.rs index 67354132..5c33523c 100644 --- a/crates/cardwire-daemon/src/interface/smart.rs +++ b/crates/cardwire-daemon/src/interface/smart.rs @@ -1,4 +1,4 @@ -use aya::maps::HashMap as AyaHashMap; +use aya::maps::{HashMap as AyaHashMap, MapError as AyaMapError}; use cardwire_ebpf_userspace::EbpfBlocker; use std::{path::Path, sync::Arc}; @@ -75,16 +75,38 @@ impl SmartPolicyInterface { { let pid_map = self.pid_map.read().await; - if let Ok(gpu) = pid_map.get(&pid, 0) { - status = "Allowed".to_string(); - gpu_id = Some(gpu) + match pid_map.get(&pid, 0) { + Ok(id) => { + status = "Allowed".to_string(); + gpu_id = Some(id) + } + Err(err) => match err { + AyaMapError::KeyNotFound => {} + _ => { + return Err(fdo::Error::Failed(format!( + "Couldn't read PID MAP: {}", + err + ))); + } + }, } } { let forced_map = self.forced_map.read().await; - if let Ok(gpu) = forced_map.get(&pid, 0) { - status = "Forced".to_string(); - gpu_id = Some(gpu) + match forced_map.get(&pid, 0) { + Ok(id) => { + status = "Forced".to_string(); + gpu_id = Some(id) + } + Err(err) => match err { + AyaMapError::KeyNotFound => {} + _ => { + return Err(fdo::Error::Failed(format!( + "Couldn't read FORCED MAP: {}", + err + ))); + } + }, } } From 57046d0b851290292bb19e9f269ce47dcf90af01 Mon Sep 17 00:00:00 2001 From: luytan Date: Fri, 7 Aug 2026 10:01:52 +0200 Subject: [PATCH 4/4] fix(cardwired): remove PID from the contrary map before inserting --- crates/cardwire-daemon/src/interface/smart.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/cardwire-daemon/src/interface/smart.rs b/crates/cardwire-daemon/src/interface/smart.rs index 5c33523c..73f4acb5 100644 --- a/crates/cardwire-daemon/src/interface/smart.rs +++ b/crates/cardwire-daemon/src/interface/smart.rs @@ -44,6 +44,13 @@ impl SmartPolicyInterface { "Default" => Ok(()), // Equivalent to CARDWIRE_ALLOW=1, show both iGPU and dGPU "Allow_dGPU" => { + { + // First remove the PID from the other map if present + let mut forced_map = self.forced_map.write().await; + forced_map + .remove(&pid) + .map_err(|err| fdo::Error::Failed(err.to_string()))?; + } let mut pid_map = self.pid_map.write().await; pid_map .insert(pid, 0, 0) @@ -51,6 +58,13 @@ impl SmartPolicyInterface { } // Equivalent to CARDWIRE_FORCE_DGPU=value "Force_dGPU" => { + { + // First remove the PID from the other map if present + let mut pid_map = self.pid_map.write().await; + pid_map + .remove(&pid) + .map_err(|err| fdo::Error::Failed(err.to_string()))?; + } let mut force_map = self.forced_map.write().await; force_map .insert(pid, value, 0) @@ -58,6 +72,13 @@ impl SmartPolicyInterface { } // Equivalent to CARDWIRE_FORCE_GPU=value "Force_GPU" => { + { + // First remove the PID from the other map if present + let mut pid_map = self.pid_map.write().await; + pid_map + .remove(&pid) + .map_err(|err| fdo::Error::Failed(err.to_string()))?; + } let mut force_map = self.forced_map.write().await; force_map .insert(pid, value, 0)