Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
39 changes: 0 additions & 39 deletions crates/cardwire-daemon/src/analyzer/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion crates/cardwire-ebpf/src/maps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub struct InodeState {
*/
#[map]
pub static CW_BLOCKED_INO: HashMap<u64, InodeState> =
HashMap::<u64, InodeState>::with_max_entries(4096, 0);
HashMap::<u64, InodeState>::with_max_entries(16384, 0);

/*
Map used to store blocked inodes from exp_nvidia
Expand Down
14 changes: 12 additions & 2 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@
- [Troubleshooting](diagnostics/troubleshooting.md)
- [NVIDIA Sleep issue](diagnostics/sleep.md)

# DBus Interfaces

- [Overview](development/dbus.md)
- [Manager](development/dbus/manager.md)
- [Mode](development/dbus/mode.md)
- [Config](development/dbus/config.md)
- [Gpu](development/dbus/gpu.md)
- [Logger](development/dbus/logger.md)
- [SmartPolicy](development/dbus/smart-policy.md)
- [Debug](development/dbus/debug.md)
- [Switcheroo Control](development/dbus/switcheroo.md)

# Development

- [DBus](development/dbus.md)
- [SwitcherooShim](development/switcheroo.md)
- [SmartMode](development/smart.md)
- [BPF](development/bpf.md)
- [Build&Dev](development/build-dev.md)
27 changes: 16 additions & 11 deletions docs/development/bpf.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Introduction

Cardwire uses Linux eBPF along with Linux Security Modules (LSM) and Syscall tracepoints to intercept and block applications. By intercepting these operations directly in the kernel, Cardwire provides a fast and seamless blocking without needing to unload drivers or modify user applications/files.
Cardwire uses Linux eBPF along with Linux Security Modules (LSM) and Syscall tracepoints to intercept and block applications. The eBPF program is written in Rust with aya-ebpf (`#![no_std]`) and loaded by the `cardwire-ebpf-userspace` crate. By intercepting these operations directly in the kernel, Cardwire provides a fast and seamless blocking without needing to unload drivers or modify user applications/files.

## eBPF Hooks

Expand All @@ -20,21 +20,26 @@ LSM hooks are used to intercept and block permission checks or file openings on

Tracepoints are used to monitor process lifecycle and manipulate the directory listings applications see.

- `tracepoint/sched/sched_process_exec`: In Smart mode, this signals the Cardwire daemon that a new process is starting so it can be analyzed.
- `tracepoint/sched/sched_process_exit`: Signals when a process dies, cleaning up its entries in the allowed process maps.
- `tp/syscalls/sys_enter_getdents64` and `sys_exit_getdents64`: Intercepts directory listings. This is the core magic behind dynamically hiding device files from applications.
- `tracepoint/sched/sched_process_exec`: In Smart and Manual modes, this signals the Cardwire daemon that a new process is starting so it can be analyzed. It first cleans the pid maps for the process.
- `tracepoint/sched/sched_process_exit`: Cleans up the process entries in the allowed and forced pid maps directly in the kernel.
- `tp/syscalls/sys_enter_getdents64` and `sys_exit_getdents64`: Intercepts directory listings. This is the core magic behind dynamically hiding device files from applications. Under kernel lockdown this pair degrades to a weakened state (directory hiding is skipped).

## eBPF Maps

The eBPF programs communicate with the Cardwire userspace daemon using several BPF maps:

- **`cw_mode`**: Stores the current Cardwire mode (0=Integrated, 1=Hybrid, 2=Manual, 3=Smart).
- **`cw_blocked_ino`**: A hash map containing the inodes of blocked DRM devices (`/dev/dri/cardX`, `/dev/dri/renderDX`). The value indicates the GPU ID (0 for iGPU, 1 for dGPU).
- **`cw_exp_blk_ino`**: Contains inodes of blocked NVIDIA-specific files when `experimental_nvidia_block` is enabled.
- **`cw_allowed_pid`**: Used in Smart mode. Contains the PIDs of applications that have been analyzed and allowed to use the dGPU. The stored value (`__u8`) is used to identify if PID is meant for iGPU(0) or dGPU(1)
- **`cw_allowed_comm`**: A whitelist of process names (like `udev` or `pacman`) that bypass blocking entirely.
- **`cw_daemon_pid`**: Cardwire's own PID so it doesn't block itself.
- **`cw_exec_events`**, **`cw_close_events`**, **`cw_report_events`**: Ring buffers used to send process and block events back to userspace.
- **`CW_MODE`**: Stores the current Cardwire mode (0=Integrated, 1=Hybrid, 2=Manual, 3=Smart).
- **`CW_BLOCKED_INO`**: A hash map (16384 entries) containing the inodes of blocked device files (`/dev/dri/cardX`, `/dev/dri/renderDX`, PCI sysfs, hwmon). The value is an `InodeState` struct `{ gpu_id: u32, blocked: u8, _padding: [u8; 3] }`.
- **`CW_EXP_BLK_INO`**: Contains inodes of blocked NVIDIA-specific files when `experimental_nvidia_block` is enabled. The value is the GPU id.
- **`CW_ALLOWED_PID`**: Used in Smart mode. Contains the PIDs of applications that have been analyzed and allowed to use the dGPU. The stored value is always `0`.
- **`CW_FORCED_PID`**: Used in Smart and Manual modes. Maps a PID to the GPU id it is forced to use.
- **`CW_ALLOWED_COMM`**: A whitelist of process names (like `udev` or `pacman`) that bypass blocking entirely.
- **`CW_DAEMON_PID`**: Cardwire's own PID so it doesn't block itself.
- **`CW_SETTINGS`**: Settings flags, key `0` gates the experimental NVIDIA blocking.
- **`CW_DIRENT`**: TID-keyed getdents64 state, used to pair the enter/exit hooks.
- **`CW_EXEC_EVENTS`**, **`CW_REPORT_EVENTS`**: Ring buffers used to send exec and blocked-access events back to userspace.

The decision algorithm (in `is_inode_blocked`) runs on every hook: it looks the inode up in `CW_BLOCKED_INO` (and `CW_EXP_BLK_INO` when the NVIDIA setting is on), then applies the mode-specific logic. In Manual mode a PID found in `CW_FORCED_PID` (itself or its parent) is allowed only on the forced GPU. In Smart mode allowed PIDs can use any GPU, forced PIDs only their GPU, and inodes of GPU 0 (the iGPU) are always allowed. Every block reports an event into `CW_REPORT_EVENTS`.

## Directory Hiding (`getdents64`)

Expand Down
16 changes: 10 additions & 6 deletions docs/development/build-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,38 @@ nix build
nix build .#checks.x86_64-linux.pre-commit-check

# Run integration tests in VM
nix build .#checks.x86_64-linux.vm-test
nix build .#checks.x86_64-linux.vm-ci-2gpu
nix build .#checks.x86_64-linux.vm-ci-3gpu
nix build .#checks.x86_64-linux.vm-ci-15gpu

# Build the vm and enter
nix run .#nixosConfigurations.x86_64-linux.config.system.build.vm
```

### Manual Compilation

If you don't use Nix, ensure you have `clang`, `libbpf (devel)`, hwdata and `cargo` installed (needed for eBPF compilation during the Rust build).
If you don't use Nix, ensure you have `clang`, `libbpf (devel)`, `libudev (devel)`, `pkg-config` and `cargo` installed (needed for eBPF compilation during the Rust build), plus `bpf-linker` and a pinned nightly toolchain (see `cardwire-ebpf-userspace/build.rs`). The GUI build additionally needs the Vulkan, EGL, Wayland and X11 development packages.

Formatting requires nightly `rustfmt` (the project uses nightly-only formatting options). Install it and run with:

```bash
rustup toolchain install nightly --component rustfmt
cargo +nightly fmt
cargo +nightly fmt --all --check
```

```bash
# Build the project
make

# Install binaries, systemd service, and D-Bus config (requires sudo)
# Install binaries, systemd service, D-Bus config, desktop file, icons and
# metainfo, and enable the systemd unit (requires sudo)
sudo make install
```

## Project Structure

- `crates/cardwire-cli`: User CLI to interact with the daemon
- `crates/cardwire-core`: Low-level GPU manager and IOMMU discovery
- `crates/cardwire-daemon`: System daemon managing state and D-Bus communication
- `crates/cardwire-ebpf`: BPF program and LSM hooks
- `crates/cardwire-ebpf`: BPF program and LSM hooks (never built directly, built by ebpf-userspace)
- `crates/cardwire-ebpf-userspace`: Loads the BPF program, compiles it at build time
- `crates/cardwire-gui`: The iced GUI and tray
158 changes: 16 additions & 142 deletions docs/development/dbus.md
Original file line number Diff line number Diff line change
@@ -1,157 +1,31 @@
# DBUS
# DBus Interfaces

Cardwire exposes several D-Bus interfaces on the system bus, one page per interface in this section.

## Service

- **Bus Name:** `org.opengamingcollective.cardwire`

> [!NOTE]
> Cardwire also implements the SwitcherooControl interface for desktop environment integration. See [switcheroo.md](switcheroo.md) for details.

---
> Cardwire also implements the SwitcherooControl interface for desktop environment integration. See [switcheroo.md](dbus/switcheroo.md) for details.

## Object Path

`/org/opengamingcollective/cardwire`

### Manager

`org.opengamingcollective.cardwire.Manager`

**Methods:**

- **`RefreshGpu`**
Refresh the internal GPU list from the system (Not implemented yet)
- **Inputs:** None
- **Outputs:** None

- **`Status`**
Simple dbus method to check if the daemon is alive
- **Inputs:** None
- **Outputs:** None

### Mode

`org.opengamingcollective.cardwire.Mode`

**Properties:**

- **`Mode`**
Controls the Cardwire's Mode
- **Type:** `u`
- **Access:** Read/Write
- **Emits:** `PropertiesChanged` on change
- **Values:**
- `0` Integrated: Block the dGPU. Requires exactly 2 GPUs
- `1` Hybrid: Unblock the dGPU. Requires exactly 2 GPUs
- `2` Manual: Allow per-GPU blocking via individual GPU objects. Applies saved GPU state on mode change if `auto_apply_gpu_state` is enabled
- `3` Smart: Block the dGPU by default but dynamically allow access per-application using eBPF. Requires exactly 2 GPUs

### Config

`org.opengamingcollective.cardwire.Config`

**Properties:**

- **`AutoApplyGpuState`**
Automatically applies the saved block/unblock states to GPUs
- **Type:** `b`
- **Access:** Read/Write

- **`BatteryAutoSwitch`**
Controls whether the daemon automatically switches modes when switching to battery power
- **Type:** `b`
- **Access:** Read/Write

- **`BatteryAutoSwitchMode`**
Controls which mode the daemon automatically switches
- **Type:** `u`
- **Access:** Read/Write

- **`ExperimentalNvidiaBlock`**
Toggles the experimental blocking for NVIDIA GPU, only works if the system has exactly 1 Nvidia GPU
- **Type:** `b`
- **Access:** Read/Write

- **`ExternalDisplayAutoSwitch`**
Temporarily switches Integrated and Smart modes to Hybrid when an external display is connected
to a dGPU-owned DRM connector. Hybrid and Manual modes are unchanged. The requested mode is
restored after disconnect.
- **Type:** `b`
- **Access:** Read/Write

### Debug

`org.opengamingcollective.cardwire.Debug`

**Methods:**

- **`GetPciDevices`**
Get a dictionary of all detected PCI devices.
- **Inputs:** None
- **Outputs:**
- (out): `a{s(sssssssss)}` -- A dictionary mapping PCI addresses to a struct containing:
- `iommu_group`: `s` - IOMMU group number (empty string if none)
- `vendor_id`: `s` - PCI vendor ID (empty string if unknown)
- `device_id`: `s` - PCI device ID (empty string if unknown)
- `vendor_name`: `s` - Vendor name (empty string if unknown)
- `device_name`: `s` - Device name (empty string if unknown)
- `driver`: `s` - Kernel driver in use (empty string if unknown)
- `class`: `s` - PCI class (empty string if unknown)
- `parent_pci`: `s` - Parent PCI address (empty string if unknown)
- `child_pci`: `s` - Child PCI address (empty string if unknown)

### Gpu

`/org/opengamingcollective/cardwire/Gpu/{id}`

Represents a single GPU device, where `{id}` is the numeric identifier of the GPU (0 is always the default one). These objects can be dynamically discovered by calling `GetManagedObjects` on the standard `org.freedesktop.DBus.ObjectManager` interface located at the root path (`/org/opengamingcollective/cardwire`)

**Properties:**

- **`Block`**
Set or get the block state for this specific GPU. Only writable when `Mode` is set to `Manual`. The default gpu cannot be blocked.
- **Type:** `b`
- **Access:** Read/Write

- **`Env`**
Environment variables to set when launching an application on this GPU (e.g., `["CARDWIRE_FORCE_DGPU", "1", "__NV_PRIME_RENDER_OFFLOAD", "1"]`).
- **Type:** `as`
- **Access:** Read

- **`Launchable`**
Whether this GPU can be targeted by an offload launch in the current mode: `true` when the GPU is available and not blocked, or blocked in `Smart` mode (where the smart policy can grant per-process access). On desktops and multi-GPU systems (`Manual`/`Hybrid` modes) blocked GPUs are never launchable. The daemon stays the single source of truth
- **Type:** `b`
- **Access:** Read

**Methods:**

- **`GetDevice`**
Get the detailed informations of this GPU
- **Inputs:** None
- **Outputs:**
- (out): `(ssuubbs)` -- A struct containing:
- `name`: `s` - GPU name
- `pci`: `s` - PCI address
- `render`: `u` - DRM render node minor number
- `card`: `u` - DRM card node minor number
- `default`: `b` - Whether this is the default display GPU
- `nvidia`: `b` - Whether the GPU is an NVIDIA device
- `nvidia_minor`: `s` - NVIDIA driver minor number (empty string if not applicable)
GPU objects live at `/org/opengamingcollective/cardwire/Gpu/{id}` and are exposed through the standard `org.freedesktop.DBus.ObjectManager` interface at the root path. Watch `InterfacesAdded` and `InterfacesRemoved` to track GPU hotplug.

- **`PowerState`**
Get the current power state of the GPU
- **Inputs:** None
- **Outputs:**
- (out): `s` -- The power state (e.g., "D0", "D3cold")
## Interfaces

- **`Lsof`**
Read file descriptors to find which applications have currently opened the GPU
- **Inputs:** None
- **Outputs:**
- (out): `a{sas}` -- A dictionary mapping file paths (like `/dev/dri/card0`) to an array of process names
- [Manager](dbus/manager.md) -- daemon liveness probe
- [Mode](dbus/mode.md) -- mode switching and available modes
- [Config](dbus/config.md) -- daemon settings
- [Gpu](dbus/gpu.md) -- per-GPU state, device info, environment, power state
- [Logger](dbus/logger.md) -- blocked GPU access attempts
- [SmartPolicy](dbus/smart-policy.md) -- per-application GPU policies
- [Debug](dbus/debug.md) -- PCI devices and GPU list refresh
- [Switcheroo Control](dbus/switcheroo.md) -- compatibility shim for desktop environments

**Signals:**
## Notes

- **`PowerStateChanged`**
Emitted when the power state of the GPU changes
- **Parameters:** `s` (string) -- The new power state
- `Option<T>` serializes as `a<T>` (empty or one-element array), not as a variant. This comes from the `option-as-array` zbus feature
34 changes: 34 additions & 0 deletions docs/development/dbus/config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Config

`org.opengamingcollective.cardwire.Config`

Served at the root object path `/org/opengamingcollective/cardwire`.

**Properties:**

- **`AutoApplyGpuState`**
Automatically applies the saved block/unblock states to GPUs
- **Type:** `b`
- **Access:** Read/Write

- **`BatteryAutoSwitch`**
Controls whether the daemon automatically switches modes when switching to battery power
- **Type:** `b`
- **Access:** Read/Write

- **`BatteryAutoSwitchMode`**
Controls which mode the daemon automatically switches
- **Type:** `u`
- **Access:** Read/Write

- **`ExperimentalNvidiaBlock`**
Toggles the experimental blocking for NVIDIA GPU, only works if the system has exactly 1 Nvidia GPU
- **Type:** `b`
- **Access:** Read/Write

- **`ExternalDisplayAutoSwitch`**
Temporarily switches Integrated and Smart modes to Hybrid when an external display is connected
to a dGPU-owned DRM connector. Hybrid and Manual modes are unchanged. The requested mode is
restored after disconnect.
- **Type:** `b`
- **Access:** Read/Write
Loading
Loading