diff --git a/crates/cardwire-daemon/src/analyzer/README.md b/crates/cardwire-daemon/src/analyzer/README.md deleted file mode 100644 index 41dd421e..00000000 --- a/crates/cardwire-daemon/src/analyzer/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Cardwire Analyzer - -The goal of the analyzer is to allow or block an app on the fly. - -The analyzer combines a database, and both dynamic and static analysis to determine -if an app should be allowed or not. - -The database stores known entities (apps discovered via static analysis) and their -policy. Dynamic results are never stored. - -## Modules - -- `models.rs` — the `CardwireAnalyzer` runtime: eBPF ring buffer consumers, process - evaluation (`evaluate_app`) and app discovery (`discover_app`), blocked-event - reporting. -- `dynamic_analysis.rs` — runtime checks: `CARDWIRE_*` environment parsing, GPU env - detection, Steam app id detection, wayland app id lookup. -- `static_analysis.rs` — FDO desktop entry scanning, builds the `AppMetadata` map. -- `helpers.rs` — generic proc/cmdline helpers shared by the runtime: real process - name parsing (wine/proton, java, flatpak, steam), kernel comm decoding, proc - checks - -## Evaluation order - -When a process exec is reported by eBPF, `evaluate_app` runs: - -1. `CARDWIRE_ALLOW=1` - allow -2. `CARDWIRE_FORCE_DGPU=value` - force dGPU -3. `CARDWIRE_FORCE_GPU=value` - force the given GPU -4. `DRI_PRIME=1` / `__NV_PRIME_RENDER_OFFLOAD=1` - allow -5. Database lookup by app name (or `steam_app_` when `SteamAppId` is set): - - `Blocked` - block - - `Allowed` - allow - - `Forced` force -6. XDG list lookup - app is new: persist it to the database (blocked by default), - then block -7. Steam fallback - unknown `steam_app_`: persist and block - -If static says blocked but dynamic says allow, the app is allowed. diff --git a/crates/cardwire-ebpf/src/maps.rs b/crates/cardwire-ebpf/src/maps.rs index 4c9da3db..9f4754f8 100644 --- a/crates/cardwire-ebpf/src/maps.rs +++ b/crates/cardwire-ebpf/src/maps.rs @@ -41,7 +41,7 @@ pub struct InodeState { */ #[map] pub static CW_BLOCKED_INO: HashMap = - HashMap::::with_max_entries(4096, 0); + HashMap::::with_max_entries(16384, 0); /* Map used to store blocked inodes from exp_nvidia diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index ca9e61a4..79d8c3ec 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -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) diff --git a/docs/development/bpf.md b/docs/development/bpf.md index 535844ea..9ec57aa2 100644 --- a/docs/development/bpf.md +++ b/docs/development/bpf.md @@ -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 @@ -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`) diff --git a/docs/development/build-dev.md b/docs/development/build-dev.md index 94917644..b0de6c24 100644 --- a/docs/development/build-dev.md +++ b/docs/development/build-dev.md @@ -15,7 +15,9 @@ 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 @@ -23,26 +25,28 @@ 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 \ No newline at end of file +- `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 diff --git a/docs/development/dbus.md b/docs/development/dbus.md index 69528c51..05ba2590 100644 --- a/docs/development/dbus.md +++ b/docs/development/dbus.md @@ -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` serializes as `a` (empty or one-element array), not as a variant. This comes from the `option-as-array` zbus feature diff --git a/docs/development/dbus/config.md b/docs/development/dbus/config.md new file mode 100644 index 00000000..acc681d9 --- /dev/null +++ b/docs/development/dbus/config.md @@ -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 \ No newline at end of file diff --git a/docs/development/dbus/debug.md b/docs/development/dbus/debug.md new file mode 100644 index 00000000..a67ae997 --- /dev/null +++ b/docs/development/dbus/debug.md @@ -0,0 +1,27 @@ +# Debug + +`org.opengamingcollective.cardwire.Debug` + +Served at the root object path `/org/opengamingcollective/cardwire`. + +**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) + +- **`RefreshGpu`** + Refresh the internal GPU list from the system. Performs a full re-enumeration, re-serves the GPU objects and re-applies the current mode. Useful after a hotplug event. + - **Inputs:** None + - **Outputs:** None diff --git a/docs/development/dbus/gpu.md b/docs/development/dbus/gpu.md new file mode 100644 index 00000000..491b7d61 --- /dev/null +++ b/docs/development/dbus/gpu.md @@ -0,0 +1,62 @@ +# Gpu + +`org.opengamingcollective.cardwire.Gpu` + +Object path `/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`). New objects appear on `InterfacesAdded` and disappear on `InterfacesRemoved` when GPUs are hotplugged. + +**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`** + The GPU launch environment, as flat key/value pairs. This is the same environment the [Switcheroo shim](switcheroo.md) exposes, and it is what `cardwire launch` applies to child processes. + - **Type:** `as` + - **Access:** Read-only + +- **`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-only + +**Methods:** + +- **`GetDevice`** + Get the detailed informations of this GPU + - **Inputs:** None + - **Outputs:** + - (out): `(ssuubbbbssbs)` -- 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 + - `discrete`: `b` - Whether the GPU is a discrete GPU + - `virtual_gpu`: `b` - Whether the GPU is a virtual device (virtio/qemu) + - `available`: `b` - Whether the GPU is usable by cardwire (eg non-available if GPU bound to vfio) + - `vendor`: `s` - GPU vendor name + - `driver`: `s` - Kernel driver in use ("none" if not applicable) + - `nvidia`: `b` - Whether the GPU is an NVIDIA device + - `nvidia_minor`: `s` - NVIDIA driver minor number ("none" if not applicable) + +- **`PowerState`** + Get the current power state of the GPU + - **Inputs:** None + - **Outputs:** + - (out): `s` -- The raw power state file content (e.g., "D0", "D3cold") + +- **`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 + +**Signals:** + +- **`PowerStateChanged`** + Emitted when the power state of the GPU changes + - **Parameters:** `s` (string) -- The new power state as a parsed enum value (e.g., "D0", "D3Cold", "D3Hot", "Unknown") diff --git a/docs/development/dbus/logger.md b/docs/development/dbus/logger.md new file mode 100644 index 00000000..c73c3a27 --- /dev/null +++ b/docs/development/dbus/logger.md @@ -0,0 +1,24 @@ +# Logger + +`org.opengamingcollective.cardwire.Logger` + +Served at the root object path `/org/opengamingcollective/cardwire`. + +**Methods:** + +- **`ProcessBlocked`** + Get the recent blocked GPU access attempts + - **Inputs:** None + - **Outputs:** + - (out): `a(tusus)` -- An array of `LogEntry` structs, where timestamp is in seconds: + - `timestamp`: `t` - Unix timestamp in seconds + - `pid`: `u` - Process id + - `comm`: `s` - Process name + - `gpu_id`: `u` - The blocked GPU id + - `wayland_app_id`: `s` - The Wayland app id (empty string if unknown) + +**Signals:** + +- **`ProcessBlockedChanged`** + Emitted when a new blocked GPU access attempt is logged + - **Parameters:** `(tusus)` -- A single `LogEntry` struct diff --git a/docs/development/dbus/manager.md b/docs/development/dbus/manager.md new file mode 100644 index 00000000..fdc62b17 --- /dev/null +++ b/docs/development/dbus/manager.md @@ -0,0 +1,13 @@ +# Manager + +`org.opengamingcollective.cardwire.Manager` + +Served at the root object path `/org/opengamingcollective/cardwire`. + +**Methods:** + +- **`Status`** + Simple dbus method to check if the daemon is alive + - **Inputs:** None + - **Outputs:** None + diff --git a/docs/development/dbus/mode.md b/docs/development/dbus/mode.md new file mode 100644 index 00000000..3052dc12 --- /dev/null +++ b/docs/development/dbus/mode.md @@ -0,0 +1,28 @@ +# Mode + +`org.opengamingcollective.cardwire.Mode` + +Served at the root object path `/org/opengamingcollective/cardwire`. + +**Methods:** + +- **`AvailableModes`** + List the modes the current system supports + - **Inputs:** None + - **Outputs:** + - (out): `au` -- Laptop systems: `[0, 1, 3]`, Desktop/Manual systems: `[1, 2]` + +**Properties:** + +- **`Mode`** + Controls the Cardwire's Mode + - **Type:** `u` + - **Access:** Read/Write + - **Emits:** `PropertiesChanged` on change + - **Values:** + - `0` Integrated: Block the dGPU. Laptop only, requires exactly 2 GPUs + - `1` Hybrid: Unblock all GPUs. Available on any system, this is the default + - `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. Laptop only, requires exactly 2 GPUs + +See the [Smart Mode](../smart.md) page for the smart-mode policy engine. diff --git a/docs/development/dbus/smart-policy.md b/docs/development/dbus/smart-policy.md new file mode 100644 index 00000000..da113785 --- /dev/null +++ b/docs/development/dbus/smart-policy.md @@ -0,0 +1,44 @@ +# SmartPolicy + +`org.opengamingcollective.cardwire.SmartPolicy` + +Served at the root object path `/org/opengamingcollective/cardwire`. This is the per-application policy API of [Smart Mode](../smart.md). + +**Methods:** + +- **`RequestProcessAccess`** + Request a policy for a process + - **Inputs:** + - `pid`: `u` - The process id + - `policy`: `s` - One of `"Default"`, `"Allow_dGPU"`, `"Force_dGPU"`, `"Force_GPU"` + - `value`: `u` - GPU id (used by `Force_GPU`) + - **Outputs:** None + - **Notes:** + - The process must already exist (`/proc/`). It can be called right after the process is spawned or on a running process, this makes it equivalent to the `CARDWIRE_*` environment variables for launch-time routing. + - `"Default"` is a no-op. `"Allow_dGPU"` is equivalent to `CARDWIRE_ALLOW=1`, `"Force_dGPU"` to `CARDWIRE_FORCE_DGPU=` and `"Force_GPU"` to `CARDWIRE_FORCE_GPU=` + +- **`GetProcessStatus`** + Get the current policy for a process + - **Inputs:** + - `pid`: `u` - The process id + - **Outputs:** + - (out): `(sau)` -- `("Allowed" | "Forced" | "")` and the optional forced GPU id. An empty string means the process is unclassified + +- **`GetAppPolicies`** + Get all known applications and their policies + - **Inputs:** None + - **Outputs:** + - (out): `a{s(sasasu)}` -- A dictionary mapping app ids to `DbusAppMetadata` structs + +- **`SetAppPolicy`** + Set the policy for a known application + - **Inputs:** + - `app_id`: `s` - The application id + - `policy`: `i` - `0` Blocked, `1` Allowed + - **Outputs:** None + +**Signals:** + +- **`NewAppAdded`** + Emitted when a new application is discovered + - **Parameters:** `(s(sasasu))` -- The app id and its `DbusAppMetadata` diff --git a/docs/development/switcheroo.md b/docs/development/dbus/switcheroo.md similarity index 52% rename from docs/development/switcheroo.md rename to docs/development/dbus/switcheroo.md index 082a3770..87c77e8c 100644 --- a/docs/development/switcheroo.md +++ b/docs/development/dbus/switcheroo.md @@ -1,12 +1,13 @@ -# Switcheroo Shim +# Switcheroo Control -Cardwire implements a compatibility shim for the `net.hadess.SwitcherooControl` D-Bus interface. This allows desktop environments (like GNOME(gio-launch-desktop) and KDE) to natively offer "Launch using Discrete Graphics Card" options in their application menus without needing any Cardwire-specific plugins. - -_(Having our own integration would've been better tbh)_ +Cardwire implements a compatibility shim for the `net.hadess.SwitcherooControl` D-Bus interface. This allows desktop environments to natively offer "Launch using Discrete Graphics Card" options in their application menus without needing any Cardwire-specific plugins. ## Service - **Interface:** `net.hadess.SwitcherooControl` +- **Object path:** `/net/hadess/SwitcherooControl` + +The shim is served on a second D-Bus connection with `replace_existing_names(true)`, so it takes the name over from an installed upstream switcheroo-control service. Failure to serve it is non-fatal, the daemon logs a warning and keeps running. --- @@ -15,6 +16,7 @@ _(Having our own integration would've been better tbh)_ ### `HasDualGpu` Indicates whether the system has exactly two GPUs. + - **Type:** `b` (boolean) - **Access:** Read @@ -23,6 +25,7 @@ Blocked GPUs are excluded from the count, except in Smart mode where the blocked ### `NumGPUs` The number of GPUs detected on the system. + - **Type:** `u` (uint32) - **Access:** Read @@ -31,6 +34,7 @@ Same rules as `HasDualGpu`: blocked GPUs are excluded, except in Smart mode. ### `GPUs` A list of all available GPUs and their configurations. + - **Type:** `aa{sv}` (Array of dictionaries mapping strings to variants) - **Access:** Read - **Dictionary Keys:** @@ -41,6 +45,8 @@ A list of all available GPUs and their configurations. Blocked GPUs are excluded from the list, except in Smart mode: the blocked dGPU is still advertised so desktop environments keep offering the "Launch using Discrete Graphics Card" option, with its normal full `Environment`. +The shim is served on a second D-Bus connection. It manually emits `org.freedesktop.DBus.Properties.PropertiesChanged` whenever the GPU list changes (for example after a hotplug refresh or a mode change). + --- ## Environment Variables Explained @@ -49,16 +55,28 @@ The `Environment` property provides the exact environment variables the desktop ### `CARDWIRE_FORCE_DGPU=1` -This is provided when the user selects the **Discrete GPU**. +This is provided when the user selects a **Discrete GPU** on a 2-GPU system where the discrete GPU is not the default one. On systems with 3 or more GPUs, the routing variable is `CARDWIRE_FORCE_GPU=` instead. When Cardwire detects this environment variable during the application's launch in Smart Mode, it does two things: + 1. **Unblocks the dGPU**: The eBPF hooks allow the application to access the discrete GPU's device files. -2. **Hides the iGPU**: It actively intercepts and blocks the application from seeing the integrated GPU. +2. **Hides the iGPU**: It actively intercepts and blocks the application from seeing the integrated GPU. Hiding the iGPU ensures that the application is forced to use the discrete GPU, preventing issues where an application might get confused by seeing two GPUs and accidentally select the weaker one. -### `CARDWIRE_ALLOW=0` +### `CARDWIRE_ALLOW` + +This is provided when the user selects the **Default GPU**. It is set to `1` when the default GPU is discrete (desktop), and to `0` when the default GPU is the iGPU (laptop). + +The analyzer only allows the dGPU when the value is `1`. Any other value falls through to the regular policy checks, it is not an explicit keep-blocked directive. + +### Vendor environment variables + +Depending on the GPU vendor, the environment also carries the offload variables used by the graphics stacks: -This is provided when the user selects the **Default/Integrated GPU**. +- **NVIDIA**: `__NV_PRIME_RENDER_OFFLOAD=1`, `__GLX_VENDOR_LIBRARY_NAME=nvidia`, `__VK_LAYER_NV_optimus=NVIDIA_only`, `VK_LOADER_DRIVERS_SELECT=*nvidia*,*nouveau*` +- **AMD**: `DRI_PRIME=pci-0000_xx_xx_x`, `VK_LOADER_DRIVERS_SELECT=*radeon*` +- **Intel**: `DRI_PRIME`, `VK_LOADER_DRIVERS_SELECT=*intel*` -It explicitly tells Cardwire's Smart Mode to keep the dGPU blocked for this application, ensuring it runs solely on the integrated graphics to save power. \ No newline at end of file +> [!CAUTION] +> These ENV were inherited from switcheroo-control old API, if they are obsolete/non-necessary they may get dropped in a future cardwire release diff --git a/docs/development/smart.md b/docs/development/smart.md index a46bb615..1a7e51bd 100644 --- a/docs/development/smart.md +++ b/docs/development/smart.md @@ -1,5 +1,16 @@ # Smart +## Goal and integration + +Cardwire owns its per-application policy. It does not depend on desktop environment heuristics like `PrefersNonDefaultGPU` or on `DRI_PRIME`, `__NV_PRIME_RENDER_OFFLOAD` or SteamAppId being present in the app environment. Those auto-approval inputs were dropped in 0.12.0 and replaced by the internal application list, which makes cardwire self-sufficient while staying compatible with desktop environments through the [Switcheroo shim](dbus/switcheroo.md). + +Third parties that want to integrate with cardwire get three surfaces: + +- **Per-process**: the `CARDWIRE_*` environment variables and `RequestProcessAccess` are equivalent ways to route a process to a GPU. Set the env vars on the process before launch, call `RequestProcessAccess` with the pid right after spawning it, or apply it to a process that is already running (Caution, App often scan for GPUs at launch). Both insert the pid into the same eBPF maps. The per-GPU environment can be fetched from the `Env` property of the [Gpu interface](dbus/gpu.md). +- **Management**: the [SmartPolicy D-Bus interface](dbus/smart-policy.md) lists known applications (`GetAppPolicies`), changes their persistent policy (`SetAppPolicy`) and announces discoveries (`NewAppAdded`). + +One caveat applies to both routes: the eBPF program clears both pid maps at every exec, so a process that execs again after being classified starts from a clean slate and is re-evaluated. + ## Introduction Having an integrated and hybrid mode is good, but what if we could have the best of both worlds? @@ -8,9 +19,9 @@ This is what cardwire's smart mode was made for. Cardwire uses a mix of kernel-s ### Kernel-Space -Using the eBPF program and the `tracepoint/sched/sched_process_exec` hooks, the kernel program notifies `cardwired` when a new process is executed, sending its pid using `cw_exec_events` RING_BUF, once the process is received by `cardwired`, it will be analyzed in real-time and if it's a process that should be allowed, its pid will be inserted into the `cw_allowed_pid` map +Using the eBPF program and the `tracepoint/sched/sched_process_exec` hooks, the kernel program notifies `cardwired` when a new process is executed, sending its pid using the `CW_EXEC_EVENTS` RING_BUF (in Smart and Manual modes). Once the process is received by `cardwired`, it will be analyzed in real-time and its pid will be inserted into the `CW_ALLOWED_PID` map (value always `0`) or the `CW_FORCED_PID` map (value is the GPU id) -When a process exits, a notification is sent to `cardwired`, cardwired will remove the PID from its map to prevent the map from overflowing +When a process exits, the kernel's `tracepoint/sched/sched_process_exit` removes the pid from both maps directly, preventing the maps from overflowing. If you want to dive deeper into the kernel code, take a look at [BPF](bpf.md) @@ -18,13 +29,17 @@ If you want to dive deeper into the kernel code, take a look at [BPF](bpf.md) The userspace of Smart mode acts as the brain. It is responsible for making the actual decisions about whether a process is allowed to use a GPU. It is divided into three main components: -- **`CardwireAnalyzer`**: A dedicated background task that listens to the `cw_exec_events` and `cw_close_events` ring buffers. When it receives a new PID from the kernel, it invokes the analysis helpers. If the application passes, it populates the `cw_allowed_pid` map with a value of `1` (normal) or `0` (`iGPU`). -- **`dynamic_analysis.rs`**: A set of helper functions used to analyze a process in real-time. By reading `/proc//environ` and `/proc//cmdline`, it checks for explicitly requested GPUs (like `CARDWIRE_ALLOW=1`, `CARDWIRE_FORCE_DGPU=1`, `DRI_PRIME=1`) or implicit signs like Steam games (`SteamAppId`) and Flatpak wrappers. -- **`static_analysis.rs`**: A set of helper functions that analyze system data when the daemon starts. Specifically, it scans the XDG data directories for `.desktop` files containing `PrefersNonDefaultGPU=true` or `X-KDE-RunOnDiscreteGpu=true`, building a whitelist of application names that should automatically be granted dGPU access when they launch. +- **`CardwireAnalyzer`**: A dedicated background task that listens to the `CW_EXEC_EVENTS` ring buffer (and the `CW_REPORT_EVENTS` ring for blocked-access logging). When it receives a new PID from the kernel, it invokes the analysis helpers. If the application passes, it populates the `CW_ALLOWED_PID` map (value always `0`) or the `CW_FORCED_PID` map (value is the GPU id). +- **`dynamic_analysis.rs`**: A set of helper functions used to analyze a process in real-time. By reading `/proc//environ` and `/proc//cmdline`, it checks for explicitly requested GPUs (like `CARDWIRE_ALLOW=1`, `CARDWIRE_FORCE_DGPU=1`, `CARDWIRE_FORCE_GPU=`) or implicit signs like Steam games (`SteamAppId`, the `0` and `769` ids are excluded). +- **`static_analysis.rs`**: A set of helper functions that analyze system data when the daemon starts. It scans the XDG data directories and watches them with inotify so new apps are picked up at install time. Every discovered app is blocked by default until the user allows it. The `xdg-desktop-portal` process is always blocked. + +#### Notes + +Technically, it's a pure race condition between the cardwire analyzer and the process, cardwire scans and allow a process in ~60-100 microseconds, from my testing, no process initialized its render before cardwire allowed it ## Complete Execution Flow -Here is a comprehensive breakdown of how the Kernel and Userspace interact in real-time when an application launches: +Here is a comprehensive breakdown of how the Kernel and Userspace interact in real-time when an application launches: (Please zoom on it) ```mermaid sequenceDiagram @@ -40,30 +55,40 @@ sequenceDiagram Note over Daemon: 2. Real-time Analysis Daemon->>Daemon: Read /proc//environ & cmdline - Daemon->>Daemon: Check env vars, Steam, Flatpak, XDG lists + Daemon->>Daemon: Check CARDWIRE_* env vars, Steam, XDG lists, SQLite policies alt Is Allowed? Daemon->>Map: Insert PID into cw_allowed_pid + else Is Forced? + Daemon->>Map: Insert PID into cw_forced_pid with the GPU id else Not Allowed Daemon->>Daemon: Do nothing end Note over Proc,Kernel: 3. GPU Access & Directory Listing Proc->>Kernel: getdents64 / file_open (/dev/dri/) - Kernel->>Map: Check cw_allowed_pid + Kernel->>Map: Check cw_allowed_pid and cw_forced_pid - alt PID not in cw_allowed_pid + alt PID not in any map Kernel-->>Proc: hide GPU (Return -ENOENT) Kernel->>Daemon: Send block event (cw_report_events) - else PID in cw_allowed_pid (Value 1 = Normal) + else PID in cw_allowed_pid Kernel-->>Proc: Allow dGPU and iGPU - else PID in cw_allowed_pid (Value 0 = FORCE_DGPU) - Kernel-->>Proc: Allow dGPU, Hide iGPU (-ENOENT) + else PID in cw_forced_pid (value = GPU id) + Kernel-->>Proc: Allow the forced GPU, hide the others (-ENOENT) end Note over Proc,Daemon: 4. Application Exit Proc->>Kernel: sched_process_exit - Kernel->>Map: Send PID via cw_close_events (RingBuf) - Map->Daemon: Listen to cw_close_events and wait for new events - Daemon->>Map: Remove PID from cw_allowed_pid + Kernel->>Kernel: Remove PID from cw_allowed_pid and cw_forced_pid ``` + +## Application policies + +Smart mode is only available on laptops (`SystemType::Laptop`). Per-application policies are stored in the `app_policies` table of the daemon's SQLite database, with two values: `Blocked` and `Allowed`. Known apps are blocked by default until the user allows them, and newly discovered apps are announced through the `NewAppAdded` D-Bus signal. + +The policy for a process can be overridden at runtime through the `org.opengamingcollective.cardwire.SmartPolicy` D-Bus interface (`RequestProcessAccess`, `GetProcessStatus`, `GetAppPolicies`, `SetAppPolicy`). Note that `GetProcessStatus` returns an empty string (not `"Default"`) for unclassified processes. + +For now there is no plan to adapt the per-application policy for non-laptop systems, unless the demand is present. + +Force_GPU can be used on all systems with the Manual mode. diff --git a/docs/diagnostics/sleep.md b/docs/diagnostics/sleep.md index 97973c20..e4acab31 100644 --- a/docs/diagnostics/sleep.md +++ b/docs/diagnostics/sleep.md @@ -31,7 +31,7 @@ The most important section should be `Runtime D3 status`. If Runtime D3 status is disabled, your GPU will never sleep. -To enable it, follow this method (only tested on Arch; please adapt it for other distros): +To enable it, follow this method (only tested on Arch, please adapt it for other distros): >[!CAUTION] > If you lack the knowledge, or you fear you will break your system, you can always make a post on the Discord to get assistance. diff --git a/docs/diagnostics/troubleshooting.md b/docs/diagnostics/troubleshooting.md index 57c06687..d535d214 100644 --- a/docs/diagnostics/troubleshooting.md +++ b/docs/diagnostics/troubleshooting.md @@ -27,7 +27,15 @@ gpu_mux_mode: current: [(0),1] ``` -> 0 means that the MUX is enabled, the dGPU **IS** the default GPU in this case +> 0 means that the MUX is disabled, the dGPU **IS** the default GPU in this case + +To enable it: + +```bash +asusctl armoury set gpu_mux_mode 1 +``` + +> A reboot is required for the change to take effect. ### Non ROG Laptop @@ -47,7 +55,7 @@ cat /sys/class/drm/*/status When switching to integrated mode on NVIDIA hardware, you may see errors or failures related to the `nvidia-powerd` service. This is a known quirk caused by the GPU entering `D3Cold` (a deep sleep state) which prevents `nvidia-powerd` from communicating with it. -Service must be restarted: +Since v0.12.0, cardwired restarts `nvidia-powerd` automatically after every mode change (only when the service is enabled), so this is usually fixed without any action. If the problem persists, restart it manually: ```bash sudo systemctl restart nvidia-powerd.service diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 11b6f23e..93a7775d 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -118,9 +118,11 @@ sudo systemctl enable cardwired --now Install build dependencies: ```bash -sudo apt install clang libbpf-dev linux-headers-$(uname -r) +sudo apt install libbpf-dev libudev-dev pkg-config libegl1-mesa-dev libvulkan-dev libglvnd-dev libwayland-dev libxkbcommon-dev libx11-dev libxcb1-dev libx11-xcb-dev gcc-multilib ``` +The eBPF program also requires `bpf-linker` and a pinned nightly Rust toolchain with the `rust-src` component (see `cardwire-ebpf-userspace/build.rs`). The devshell provided by `nix develop` bundles all of these (excepted bpf-linker which need to be installed using `cargo install bpf-linker`) + Install Rust (if not already installed): ```bash @@ -137,14 +139,15 @@ sudo make install ## Other distros -For now, other distros must clone the repo and use `make` to build and install Cardwire. You will also need to enable BPF LSM manually — see the [Enabling BPF LSM](#enabling-bpf-lsm) section above. +For now, other distros must clone the repo and use `make` to build and install Cardwire. You will also need to enable BPF LSM manually, see the [Enabling BPF LSM (with GRUB)](#enabling-bpf-lsm-with-grub) section above. Build dependencies: -- cargo -- clang +- cargo (plus `bpf-linker` and a pinned nightly toolchain for the eBPF program) - libbpf - libudev-dev +- pkg-config +- Vulkan, EGL, Wayland and X11 development packages (GUI build) ```bash git clone https://github.com/OpenGamingCollective/cardwire.git diff --git a/docs/getting-started/requirements.md b/docs/getting-started/requirements.md index 4089c4cb..d79dc1b1 100644 --- a/docs/getting-started/requirements.md +++ b/docs/getting-started/requirements.md @@ -71,6 +71,16 @@ zcat /proc/config.gz | grep CONFIG_LSM= > Outputs e.g. `lsm=landlock,yama,apparmor,bpf` or `CONFIG_LSM="landlock,lockdown,yama,integrity,apparmor,bpf"`. > If it contains 'bpf', bpf is already enabled and usable in your system! +### 3. Verify BPF LSM is active at runtime + +The cardwire daemon refuses to start without this. Check the list of active LSMs: + +```bash +cat /sys/kernel/security/lsm +``` + +> Must contain `bpf`. If it does, eBPF LSM is ready even if the boot cmdline looks different. + ### Enabling BPF LSM (with GRUB) If `bpf` is not in your boot cmdline, edit `/etc/default/grub` and append `bpf` to `GRUB_CMDLINE_LINUX_DEFAULT`, keeping all existing entries: @@ -80,7 +90,7 @@ GRUB_CMDLINE_LINUX_DEFAULT="quiet splash lsm=landlock,lockdown,yama,integrity,ap ``` > [!IMPORTANT] -> Do not set `lsm=bpf` alone — that drops other active security policies. Always append `bpf` to the existing list from the command above. +> Do not set `lsm=bpf` alone, that drops other active security policies. Always append `bpf` to the existing list from the command above. Apply and reboot: diff --git a/docs/getting-started/usage.md b/docs/getting-started/usage.md index 18403d9f..9bd804bb 100644 --- a/docs/getting-started/usage.md +++ b/docs/getting-started/usage.md @@ -16,20 +16,30 @@ For each detected GPU, the command will return: - The associated render node (`RENDER`) - The associated device node (`CARD`) - Whether the GPU has been identified as the default GPU (`DEFAULT`). Default GPUs will remain available when cardwire is set to integrated. +- Whether the GPU is a discrete GPU (`DISCRETE`) - Whether the GPU is currently blocked (`BLOCKED`) +`--json` prints the full device map as JSON. +`--full` prints the full pci device map as JSON. + Example: ```bash $ cardwire list -ID NAME PCI RENDER CARD DEFAULT BLOCKED --- ------------------------------------------- ------------ ---------- ----- ------- ------- -0 Rembrandt [Radeon 680M] 0000:07:00.0 renderD129 card2 (*) false -1 Navi 23 [Radeon RX 6650 XT / 6700S / 6800S] 0000:03:00.0 renderD128 card1 ( ) true +ID NAME PCI RENDER CARD DEFAULT DISCRETE BLOCKED +-- ------------------------------------------- ------------ ---------- ----- ------- -------- ------- +0 Rembrandt [Radeon 680M] 0000:07:00.0 renderD129 card2 (*) ( ) false +1 Navi 23 [Radeon RX 6650 XT / 6700S / 6800S] 0000:03:00.0 renderD128 card1 ( ) (*) true ``` ## Mode switching +To print the current mode: + +```bash +cardwire get +``` + GPU modes can be switched using the `cardwire set` command. ### Integrated @@ -67,9 +77,12 @@ When launching apps in Smart mode, cardwire checks for the following to allow th - `CARDWIRE_ALLOW=1` env var (highest priority, unblocks the GPU but doesn't force the app to use it) - `CARDWIRE_FORCE_DGPU=1` env var (unblocks the GPU, forces the app to use it, and completely hides the iGPU) -- Steam games (`SteamAppId=`) -- Flatpak apps with XDG `PrefersNonDefaultGpu=true` (Only on system that does not implement switcheroo/cardwire) -- Explicit GPU env vars (`DRI_PRIME=1`, `__NV_PRIME_RENDER_OFFLOAD=1`) +- `CARDWIRE_FORCE_GPU=` env var (unblocks a specific GPU and forces the app to use it) +- Steam games, identified by `SteamAppId`, are discovered into the internal application list and blocked by default until allowed +- The per-app policies stored in cardwire's internal application list + +> [!NOTE] +> The former auto-approval inputs are deprecated in favor of the internal application list. Steam auto-allow, `PrefersNonDefaultGpu` desktop entries and the automatic approval of GPU environment variables (`DRI_PRIME`, `__NV_PRIME_RENDER_OFFLOAD`) are no longer evaluated. ```bash cardwire set smart @@ -89,7 +102,7 @@ If more granular control over several GPUs is required, cardwire also allows man cardwire set manual ``` -Once set to manual, GPU states can then be set by ID; to find the correct ID, see [Querying GPUs](#Querying-GPUs). +Once set to manual, GPU states can then be set by ID. To find the correct ID, see [Querying GPUs](#Querying-GPUs). To block the GPU with ID `1`: @@ -103,6 +116,39 @@ To unblock: cardwire gpu 1 --unblock ``` +## Launching apps on a specific GPU + +`cardwire launch` starts a program with the right GPU environment set, without switching modes: + +```bash +cardwire launch --gpu 1 nvtop +cardwire launch glxgears +``` + +Without `--gpu`, cardwire picks the best GPU for the job, in this order: a discrete non-default GPU, a discrete GPU, the default GPU, then the first available one. The command fetches the launch environment from the daemon. + +Launching on a blocked or unavailable GPU is refused with a clear error, switch to Smart mode or unblock the GPU first: + +## System information + +To check that the daemon is running: + +```bash +cardwire manager status +``` + +To refresh the GPU list held by the daemon (useful after a hotplug event): + +```bash +cardwire debug refresh-gpu +``` + +To check the power state of a GPU (for example whether it reached `D3Cold`): + +```bash +cardwire gpu 1 --power +``` + ## Configuration ### Experimental Nvidia Block diff --git a/docs/introduction.md b/docs/introduction.md index d517c1bd..16ae1b87 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -16,7 +16,7 @@ Furthermore, unlike older managers, **Cardwire never unbinds PCI devices or kern | :--------------------- | :----------------------------------------------------------- | :------------------------- | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | | **Cardwire** | **eBPF LSM hooks** block file/device access dynamically. | **No** (Seamless) | **No** | Actively prevents rogue apps from waking the dGPU. Emulates switcheroo-control for seamless GNOME/KDE integration. | | **switcheroo-control** | Sets environment variables (e.g. `DRI_PRIME`). | **No** | No | The desktop default. Good for launching, but doesn't actively block apps, meaning the dGPU can still be woken up by background tasks. | -| **supergfxctl** | Modprobe blacklisting, udev rules, stopping display manager. | **Yes** (Prone to crashes) | Logout (often) | Deprecated. The predecessor to Cardwire; inflexible and often required restarting the graphical session. | +| **supergfxctl** | Modprobe blacklisting, udev rules, stopping display manager. | **Yes** (Prone to crashes) | Logout (often) | Deprecated. The predecessor to Cardwire, inflexible and often required restarting the graphical session. | | **optimus-manager** | Generates specific Xorg configurations. | **Yes** | Logout | Built heavily around X11, making it problematic for modern Wayland compositors. | | **envycontrol** | Modprobe blacklisting and udev rules. | **Yes** | Reboot | Very reliable but inflexible, as it requires a full system restart to apply any mode changes. | @@ -28,9 +28,9 @@ Cardwire provides several GPU management modes: - **Hybrid mode** -- Removes the blocks, letting the system function normally with both integrated and dedicated GPUs available. -- **Manual mode** -- Allows users to manually block or unblock individual GPUs by ID for granular control. +- **Manual mode** -- Allows users to manually block or unblock individual GPUs by ID for granular control. It is only available on desktop systems and never blocks the default GPU. -- **Smart mode** -- Like integrated mode it blocks the dGPU by default, but uses eBPF to analyze each application at launch and selectively allow GPU access for approved applications. +- **Smart mode** -- Like integrated mode it blocks the dGPU by default, but a userspace analyzer inspects each application at launch and selectively allows GPU access for approved applications. It is only available on laptops. Switching between modes is fast and does not require reboots or logouts. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index 7296861b..00000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,52 +0,0 @@ -# Troubleshooting - -## Name is not activable/cli not working - -Is the daemon running? - -```bash -systemctl status cardwired.service -``` - -> If it's not running, enable the daemon with `systemctl enable cardwired.service` and reboot your device. - -## dGPU is detected as the default gpu - -### On ROG laptop - -Is the ASUS MUX enabled? - -```bash -asusctl armoury list -``` - -then find - -```bash -gpu_mux_mode: - current: [(0),1] -``` - -> 0 means that the MUX is enabled, the dGPU **IS** the default GPU in this case - -To disable it: - -```bash -asusctl armoury set gpu_mux_mode 1 -``` - -> A reboot is required for the change to take effect. - -### Non ROG Laptop - -This shouldn't happen, please create an issue with the output of - -```bash -ls /sys/class/drm -``` - -and - -```bash -cat /sys/class/drm/*/status -```