Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ pub fn check_cardwire_allow(environ: &[u8]) -> Option<bool> {
// Not present
None
}

pub fn check_cardwire_force_dgpu(environ: &[u8]) -> Option<bool> {
for var in environ.split(|&b| b == 0) {
if var.starts_with(b"CARDWIRE_FORCE_DGPU=") {
if var.get(20) == Some(&b'1') {
return Some(true); // CARDWIRE_FORCE_DGPU=1
} else {
return Some(false); // CARDWIRE_FORCE_DGPU=0
}
}
}
// Not present
None
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

pub fn check_gpu_env(environ: &[u8]) -> bool {
for var in environ.split(|&b| b == 0) {
if var.starts_with(b"DRI_PRIME=") {
Expand Down
34 changes: 21 additions & 13 deletions crates/cardwire-daemon/src/analyzer/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tokio::{

use crate::analyzer::{
dynamic_analysis::{
check_cardwire_allow, check_fdo_app_id, check_for_flatpak_run, check_gamemode, check_gpu_env, check_steam_environ, get_app_id_wayland
check_cardwire_allow, check_cardwire_force_dgpu, check_fdo_app_id, check_for_flatpak_run, check_gamemode, check_gpu_env, check_steam_environ, get_app_id_wayland
}, static_analysis
};
#[repr(C)]
Expand Down Expand Up @@ -155,16 +155,20 @@ impl CardwireAnalyzer {
None => return,
};
if let Some(result) = self.evaluate_app(event.pid, &real_app_name).await
&& result
&& result.0
{
info!(
"ALLOW: pid: {} process: {} in {}us",
event.pid,
&real_app_name,
time.elapsed().as_micros()
);
match result.1 {
0 => info!("FORCE: pid: {} process: {} ", event.pid, &real_app_name),
1 => info!(
"ALLOW: pid: {} process: {} in {}us",
event.pid,
&real_app_name,
time.elapsed().as_micros()
),
_ => {}
}
let mut pid_map = self.pid_map.write().await;
if let Err(e) = pid_map.insert(event.pid, 1, 0) {
if let Err(e) = pid_map.insert(event.pid, result.1, 0) {
warn!("Failed to insert into eBPF map: {}", e);
}
}
Expand All @@ -176,16 +180,20 @@ impl CardwireAnalyzer {
}
}

/// Default app are blocked, try to find if it's a game or a gpu intensive app
async fn evaluate_app(&self, pid: u32, comm: &str) -> Option<bool> {
/// Default app are blocked, try to find if it's a game or a gpu intensive app, the u8 is the
/// gpu id
async fn evaluate_app(&self, pid: u32, comm: &str) -> Option<(bool, u8)> {
let path = format!("/proc/{}/environ", pid);
let environ = match fs::read(path) {
Ok(content) => content,
Err(_) => return None,
};
// First check CARDWIRE_ALLOW, if None continue
if let Some(allow) = check_cardwire_allow(&environ) {
return Some(allow);
return Some((allow, 1));
}
if let Some(value) = check_cardwire_force_dgpu(&environ) {
return Some((value, 0));
}
let xdg_list = self.xdg_list.read().await;

Expand All @@ -211,7 +219,7 @@ impl CardwireAnalyzer {
};
result = check_gamemode(&map);
}
Some(result)
Some((result, 1))
}

#[allow(dead_code)]
Expand Down
2 changes: 1 addition & 1 deletion crates/cardwire-daemon/src/interface/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl DebugInterface {
"GPU {} should be blocked, re-applying block on hotplug",
gpu.device.name()
);
if let Err(e) = gpu.block_gpu().await {
if let Err(e) = gpu.block_gpu(1).await {
warn!(
"failed to automatically re-block {}: {}",
gpu.device.name(),
Expand Down
18 changes: 9 additions & 9 deletions crates/cardwire-daemon/src/interface/gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,20 @@ impl GpuInterface {
}

impl GpuInterface {
/// block the gpu
pub async fn block_gpu(&mut self) -> fdo::Result<()> {
/// block the gpu, 1 = dGPU, 0 = iGPU
pub async fn block_gpu(&mut self, value: u8) -> fdo::Result<()> {
let mut blocker = self.blocker.write().await;
let pci_list = self.pci_list.read().await;
// First block the card id
match card_to_inode(*self.device.card()) {
Ok(inode) => blocker.block_inode(inode).into_fdo()?,
Ok(inode) => blocker.block_inode(inode, value).into_fdo()?,
Err(err) => {
error!("failed to block card{}: {}", *self.device.card(), err);
return Err(err).into_fdo();
}
};
match render_to_inode(*self.device.render()) {
Ok(inode) => blocker.block_inode(inode).into_fdo()?,
Ok(inode) => blocker.block_inode(inode, value).into_fdo()?,
Err(err) => {
error!("failed to block render{}: {}", *self.device.render(), err);
return Err(err).into_fdo();
Expand All @@ -81,7 +81,7 @@ impl GpuInterface {
) {
Ok(inodes) => {
for inode in inodes {
if let Err(err) = blocker.block_inode(inode) {
if let Err(err) = blocker.block_inode(inode, value) {
error!("failed to block inode(pci) {}: {}", inode, err);
return Err(err).into_fdo();
}
Expand All @@ -100,7 +100,7 @@ impl GpuInterface {
match sys_drm_inodes(*self.device.render(), *self.device.card()) {
Ok(inodes) => {
for inode in inodes {
if let Err(err) = blocker.block_inode(inode) {
if let Err(err) = blocker.block_inode(inode, value) {
error!("failed to block inode(drm) {}: {}", inode, err);
return Err(err).into_fdo();
}
Expand All @@ -120,14 +120,14 @@ impl GpuInterface {
&& let Some(minor) = self.device.nvidia_minor()
{
match nvidia_to_inode(*minor) {
Ok(inode) => blocker.block_inode(inode).into_fdo()?,
Ok(inode) => blocker.block_inode(inode, value).into_fdo()?,
Err(err) => {
error!("failed to block nvidia{}: {}", *self.device.render(), err);
return Err(err).into_fdo();
}
};
match backlight_to_inode(*minor) {
Ok(inode) => blocker.block_inode(inode).into_fdo()?,
Ok(inode) => blocker.block_inode(inode, value).into_fdo()?,
Err(err) => {
error!(
"(ignoring) failed to block backlight nvidia_{}: {}",
Expand Down Expand Up @@ -291,7 +291,7 @@ impl GpuInterface {
)));
}
// Now block
self.block_gpu().await?;
self.block_gpu(1).await?;
info!("Set GPU {} block={}", self.device.name(), block);
// save new state to file
let mut gpu_state = self.gpu_state.write().await;
Expand Down
11 changes: 9 additions & 2 deletions crates/cardwire-daemon/src/interface/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,18 @@ impl ModeInterface {
for gpu in gpu_list.values_mut() {
if !gpu.device.is_default() {
if mode == Modes::Integrated || mode == Modes::Smart {
gpu.block_gpu().await?;
// Here we block the dGPU
gpu.block_gpu(1).await?;
} else {
gpu.unblock_gpu().await?;
}
};

// If we in smart mode and is the default gpu, push it to the blocked inode map
// but it wont get blocked, trust
if mode == Modes::Smart && gpu.device.is_default() {
gpu.block_gpu(0).await?;
}
}
}

Expand All @@ -152,7 +159,7 @@ impl ModeInterface {
gpu.unblock_gpu().await?;
} else {
info!("blocking: {} ", gpu.device.pci().pci_address());
gpu.block_gpu().await?;
gpu.block_gpu(1).await?;
}
} else {
gpu.unblock_gpu().await?;
Expand Down
24 changes: 20 additions & 4 deletions crates/cardwire-ebpf/src/c/bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,16 @@ int cardwire_sys_enter_getdents64(struct trace_event_raw_sys_enter *ctx)
// if we in smart mode and the pid is allowed, skip
if (is_smart()) {
if (is_pid_allowed(pid, ppid)) {
// if allowed, skip
return 0;
__u8 *allow_val =
bpf_map_lookup_elem(&cw_allowed_pid, &pid);
if (!allow_val) {
allow_val = bpf_map_lookup_elem(&cw_allowed_pid,
&ppid);
}
// If force_dgpu is NOT enabled, we can safely skip patching
if (allow_val && *allow_val != 0) {
return 0;
}
Comment on lines +152 to +161

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Casting the same lookup spell twice, in two scrolls.

Both getdents64 hooks repeat: is_pid_allowed(pid,ppid) (which itself looks up cw_allowed_pid by pid/ppid) followed by an independent re-lookup of the same map to fetch the value. On a syscall hot path (getdents64 fires on every directory read), that's redundant map work performed twice per call site, copy-pasted verbatim across both functions.

♻️ Suggested shared helper
static __always_inline int should_skip_getdents_patch(__u32 pid, __u32 ppid)
{
	if (!is_pid_allowed(pid, ppid))
		return 0;

	__u8 *allow_val = bpf_map_lookup_elem(&cw_allowed_pid, &pid);
	if (!allow_val)
		allow_val = bpf_map_lookup_elem(&cw_allowed_pid, &ppid);

	// If force_dgpu is NOT enabled, we can safely skip patching
	return allow_val && *allow_val != 0;
}

Then both call sites collapse to:

if (is_smart() && should_skip_getdents_patch(pid, ppid)) {
	return 0;
}

Also applies to: 196-205

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cardwire-ebpf/src/c/bpf.c` around lines 152 - 161, Introduce a shared
always-inline helper, such as should_skip_getdents_patch, that combines the
is_pid_allowed check with the cw_allowed_pid lookups and returns whether
patching should be skipped. Replace the duplicated allow_val logic in both
getdents64 hook call sites with this helper while preserving the existing
is_smart guard and return behavior.

}
}

Expand Down Expand Up @@ -185,8 +193,16 @@ int cardwire_sys_exit_getdents64(struct trace_event_raw_sys_exit *ctx)
// if we in smart mode and the pid is allowed, skip
if (is_smart()) {
if (is_pid_allowed(pid, ppid)) {
// if allowed, skip
return 0;
__u8 *allow_val =
bpf_map_lookup_elem(&cw_allowed_pid, &pid);
if (!allow_val) {
allow_val = bpf_map_lookup_elem(&cw_allowed_pid,
&ppid);
}
// If force_dgpu is NOT enabled, we can safely skip patching
if (allow_val && *allow_val != 0) {
return 0;
}
}
}

Expand Down
74 changes: 68 additions & 6 deletions crates/cardwire-ebpf/src/c/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,15 @@ static __always_inline int is_blocked_device(struct dentry *d)
bool blocked = false;

struct inode *inode = BPF_CORE_READ(d, d_inode);
__u8 *map_val = 0;
// Match card/render/nvidia minor
if (inode) {
__u64 d_ino = BPF_CORE_READ(inode, i_ino);
if (d_ino) {
// if it's a blocked inode, go to end
if (bpf_map_lookup_elem(&cw_blocked_ino, &d_ino)) {
map_val = bpf_map_lookup_elem(&cw_blocked_ino, &d_ino);
// if inode is in the map, blocked
// we store the value to identify the dGPU/iGPU
if (map_val) {
blocked = true;
goto end;
}
Expand Down Expand Up @@ -126,6 +129,25 @@ static __always_inline int is_blocked_device(struct dentry *d)

// if smart, check the pid list
if (*mode == 3) {
// Check if the inode is linked to the iGPU
if (map_val && *map_val == 0) {
__u8 *allow_map_value =
bpf_map_lookup_elem(&cw_allowed_pid, &pid);
if (!allow_map_value) {
allow_map_value = bpf_map_lookup_elem(
&cw_allowed_pid, &ppid);
}
// IF the inode is linked to a iGPU and the map_key exist
if (allow_map_value) {
// This mean the inode is allowed, but to check if we should force the dGPU or not,value should be at 0
if (*allow_map_value == 0) {
// We should hide the iGPU
return -ENOENT;
}
}
return 0;
}

if (!bpf_map_lookup_elem(&cw_allowed_pid, &pid) &&
!bpf_map_lookup_elem(&cw_allowed_pid, &ppid)) {
// Neither pid nor ppid is allowed, block and report the event
Expand Down Expand Up @@ -178,9 +200,9 @@ static __always_inline int patch_dirent_if_found(__u32 _,
bpf_probe_read_user_str(dirname, sizeof(dirname), dirent->d_name);

// Check if this is a file we want to hide
if (bpf_map_lookup_elem(&cw_blocked_ino, &d_inode) ||
(is_nvidia_enabled() &&
bpf_map_lookup_elem(&cw_exp_blk_ino, &d_inode))) {
__u8 *map_val = bpf_map_lookup_elem(&cw_blocked_ino, &d_inode);
if (map_val || (is_nvidia_enabled() &&
bpf_map_lookup_elem(&cw_exp_blk_ino, &d_inode))) {
if (data->last_visible_bpos != 0xFFFFFFFF) {
struct linux_dirent64 *visible_dirent =
(struct linux_dirent64
Expand All @@ -191,12 +213,51 @@ static __always_inline int patch_dirent_if_found(__u32 _,
&visible_dirent->d_reclen);

__u16 new_reclen = visible_reclen + data->d_reclen;
// check for iGPU
if (is_smart()) {
__u32 pid = bpf_get_current_pid_tgid() >> 32;
struct task_struct *task =
(struct task_struct *)
bpf_get_current_task();
__u32 ppid =
BPF_CORE_READ(task, real_parent, tgid);

__u8 *allow_map_value = bpf_map_lookup_elem(
&cw_allowed_pid, &pid);

if (map_val && *map_val == 0) {
// It's the iGPU
if (allow_map_value) {
// map is valid and value isn't 1 (dGPU)
if (*allow_map_value != 1) {
// force dGPU is active: hide the iGPU
bpf_probe_write_user(
&visible_dirent
->d_reclen,
&new_reclen,
sizeof(new_reclen));
goto end;
} else {
// allowed process, no force: show iGPU
goto not_hidden;
}
} else {
goto not_hidden;
}
} else {
// It's the dGPU
if (allow_map_value) {
// Allowed process: must see the dGPU!
goto not_hidden;
}
// Unallowed process: fall through to hide the dGPU
}
}
// Overwrite the visible file's length so it skips over the hidden file
bpf_probe_write_user(&visible_dirent->d_reclen,
&new_reclen, sizeof(new_reclen));
}

end:
Comment thread
coderabbitai[bot] marked this conversation as resolved.
data->bpos += data->d_reclen;
// Reserve space, return if it fails
struct report_t *rb_report = bpf_ringbuf_reserve(
Expand All @@ -212,6 +273,7 @@ static __always_inline int patch_dirent_if_found(__u32 _,
return 0; // Continue loop
}

not_hidden:
// Not a hidden file, update last_visible_bpos and advance
data->last_visible_bpos = data->bpos;
data->bpos += data->d_reclen;
Expand Down
9 changes: 6 additions & 3 deletions crates/cardwire-ebpf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ impl EbpfBlocker {
}
}

pub fn block_inode(&mut self, inode: u64) -> CardwireEbpfResult<()> {
/// Block an inode, value should be a either 0(iGPU) or 1 (dGPU)
pub fn block_inode(&mut self, inode: u64, value: u8) -> CardwireEbpfResult<()> {
// Also insert hardcoded values for now
let mut inode_map: HashMap<_, u64, u8> = HashMap::try_from(
self.ebpf
Expand All @@ -125,7 +126,7 @@ impl EbpfBlocker {
)
.map_err(CardwireEbpfError::aya)?;
inode_map
.insert(inode, 1, 0)
.insert(inode, value, 0)
.map_err(CardwireEbpfError::aya)?;
Ok(())
}
Expand Down Expand Up @@ -155,7 +156,9 @@ impl EbpfBlocker {
)
.map_err(CardwireEbpfError::aya)?;
match inode_map.get(&inode, 0) {
Ok(_) => Ok(true),
// 1 = dGPU, 0 = iGPU, if the inode is in the map it means the dGPU is meant to be
// blocked so true
Ok(value) => Ok(value == 1),
Err(MapError::KeyNotFound) => Ok(false),
Err(err) => Err(CardwireEbpfError::aya(err)),
}
Expand Down