Introduction
Cardwire is GPU manager for Linux systems with multiple GPUs. It allows users to smoothly and safely switch between “integrated” and “hybrid” GPU modes. It was created as a successor to the deprecated supergfxctl project.
In “Integrated” mode, cardwire uses eBPF LSM hooks to block applications from accessing dedicated GPUs. This saves power by preventing the GPU from being woken up and allowing it to enter an extremely energy-efficient sleep state (D3Cold). In “Hybrid” mode, these blocks are removed and the system functions as it usually would. As a safety option and for users who require more granular control, cardwire additionally allows users to manually block and unblock individual GPUs by ID. Switching is fast and does not require reboots or logouts to take effect.
Caution
Cardwire is in an early development stage, expect breaking changes and instability.
Getting Started
To get started with cardwire, please take a look at the requirements to make sure your system is supported, and then head over to the installation instructions.
Requirements
Requirements to be able to run Cardwire:
Kernel
Version 5.7 or later
uname -r
Built with CONFIG_BPF_LSM enabled
zcat /proc/config.gz | grep CONFIG_BPF_LSM
returns
CONFIG_BPF_LSM=yif it’s enabled
Enabled in the boot cmdline
cat /proc/cmdline
Must contains
lsm=bpf(If empty/doesnt contain bpf, please read Caution)
Caution
Most distros already enable bpf, only change the cmdline if cardwired doesnt launch
Installation
Arch
using AUR:
yay -S cardwire
then enable and start the service
sudo systemctl enable cardwired.service
sudo systemctl start cardwired.service
Note
cardwire-git is also available, this one is built from the dev branch, rather than a fixed release tag
Important
i’m also looking for an official maintainer for both AUR, since i do not use Arch.
Nix
Using the repo’s flake:
flake.nix:
cardwire = {
url = "github:opengamingcollective/cardwire";
inputs.nixpkgs.follows = "nixpkgs";
};
configuration.nix:
imports = [ inputs.cardwire.nixosModules.default ];
services.cardwire = {
enable = true;
settings = {
auto_apply_gpu_state = true;
experimental_nvidia_block = true;
battery_auto_switch = true;
};
};
Fedora
Using Terra
sudo dnf install cardwire
sudo systemctl enable cardwired.service
sudo systemctl start cardwired.service
Other distros
For now, other distros must clone the repo and use make to build and install Cardwire.
Build dependencies:
- cargo
- clang
- libbpf
git clone https://github.com/OpenGamingCollective/cardwire.git
make build
sudo make install
Caution
Makefile wasn’t tested, use with caution.
Important
For mainstream distros, i will be making an official install methods, like a copr for Fedora and a .deb for Debian based.
Non-systemd distros
Warning
Cardwire only supports systemd-based distros. If you want to use it on a non-systemd distro, either open a PR with patches for non-systemd or get it working on your setup.
Usage
Querying GPUs
To have cardwire list all detected GPUs, use:
cardwire list
For each detected GPU, the command will return:
- An identifier (
ID). These are used for manual blocking and unblocking. - The GPU’s name (
NAME) - The GPU’s PCI address (
PCI) - 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 currently blocked (
BLOCKED)
Mode switching
GPU modes can be switched using the cardwire set command.
To have cardwire block all GPUs except the default GPU, use:
cardwire set integrated
To have cardwire allow access to all GPUs, use
cardwire set hybrid
Manual blocking and unblocking
Important
To prevent system breakage, cardwire will not block default GPUs, even when explicitly instructed to do so.
If more granular control over several GPUs is required, cardwire also allows manually blocking individual GPUs by ID. To do so, it needs to be set to manual mode:
cardwire set manual
Once set to manual, GPU states can then be set by ID; to find the correct ID, see Querying GPUs.
To block the GPU with ID 1:
cardwire gpu 1 --block
To unblock:
cardwire gpu 1 --unblock
Troubleshooting
Name is not activable
Is the daemon running?
systemctl status cardwired.service
If it’s not running, enable the daemon with
systemctl enable cardwired.serviceand reboot your device.
dGPU is detected as the default gpu
On ROG laptop
is the asus MUX enabled?
asusctl armoury list
then find
gpu_mux_mode:
current: [(0),1]
0 means that the MUX is enabled, the dGPU IS the default GPU in this case
Non ROG Laptop
This shouldn’t happen, please create an issue with the output of
ls /sys/class/drm
and
cat /sys/class/drm/*/status
Sleep
How to diagnose a dGPU that won’t sleep
Your NVIDIA dGPU won’t sleep? Here’s how to find and fix the issue.
Check your NVIDIA GPU power information
Before this, please set cardwire to unblock your dGPU.
Replace the PCI with yours.
cat /proc/driver/nvidia/gpus/0000:01:00.0/power
Runtime D3 status: Enabled (fine-grained)
Video Memory: Off
GPU Hardware Support:
Video Memory Self Refresh: Supported
Video Memory Off: Supported
S0ix Power Management:
Platform Support: Supported
Status: Enabled
Notebook Dynamic Boost: Not Supported
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):
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.
Go to https://gitlab.com/asus-linux/nvidia-laptop-power-cfg.
We will need two files:
- nvidia.rules
- nvidia.conf
You will need to copy them to their respective directory: For nvidia.conf:
/etc/modprobe.d/nvidia.conf
For nvidia.rules:
/usr/lib/udev/rules.d/80-nvidia-pm.rules
Once it’s done, execute:
sudo mkinitcpio -P
and restart your computer.
RTX 2000 Series
If it’s not working and you own an RTX 2000 GPU, it’s a known issue.
You must use driver 580 and add NVreg_EnableGpuFirmware=0 to /etc/modprobe.d/nvidia.conf.
Check the PCI control value
DBUS
Service
- Bus Name:
com.github.opengamingcollective.cardwire
Object Path
/com/github/opengamingcollective/cardwire
Manager
com.github.opengamingcollective.cardwire.Manager
Methods:
-
RefreshGpuRefresh the internal GPU list from the system (Not implemented yet)- Inputs: None
- Outputs: None
-
StatusSimple dbus method to check if the daemon is alive- Inputs: None
- Outputs: None
Mode
com.github.opengamingcollective.cardwire.Mode
Properties:
ModeControls the Cardwire’s Mode- Type:
u - Access: Read/Write
- Emits:
PropertiesChangedon change - Values:
0Integrated: Block the dGPU. Requires exactly 2 GPUs1Hybrid: Unblock the dGPU. Requires exactly 2 GPUs2Manual: Allow per-GPU blocking via individual GPU objects. Applies saved GPU state on mode change ifauto_apply_gpu_stateis enabled3Smart
- Type:
Config
com.github.opengamingcollective.cardwire.Config
Properties:
-
AutoApplyGpuStateAutomatically applies the saved block/unblock states to GPUs- Type:
b - Access: Read/Write
- Type:
-
BatteryAutoSwitchControls whether the daemon automatically switches modes when switching to battery power- Type:
b - Access: Read/Write
- Type:
-
BatteryAutoSwitchModeControls which mode the daemon automatically switches- Type:
u - Access: Read/Write
- Type:
-
ExperimentalNvidiaBlockToggles the experimental blocking for NVIDIA GPU, only works if the system has exactly 1 Nvidia GPU- Type:
b - Access: Read/Write
- Type:
Methods:
SaveToFileSave the current daemon configuration (properties above) to thecardwire.tomlconfig file- Inputs: None
- Outputs: None
Debug
com.github.opengamingcollective.cardwire.Debug
Methods:
GetPciDevicesGet 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)
- (out):
Gpu
/com/github/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 (/com/github/opengamingcollective/cardwire)
Properties:
BlockSet or get the block state for this specific GPU. Only writable whenModeis set toManual. The default gpu cannot be blocked.- Type:
b - Access: Read/Write
- Type:
Methods:
-
GetDeviceGet the detailed informations of this GPU- Inputs: None
- Outputs:
- (out):
(ssuubbs)– A struct containing:name:s- GPU namepci:s- PCI addressrender:u- DRM render node minor numbercard:u- DRM card node minor numberdefault:b- Whether this is the default display GPUnvidia:b- Whether the GPU is an NVIDIA devicenvidia_minor:s- NVIDIA driver minor number (empty string if not applicable)
- (out):
-
PowerStateGet the current power state of the GPU- Inputs: None
- Outputs:
- (out):
s– The power state (e.g., “D0”, “D3cold”)
- (out):
-
LsofRead 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
- (out):
Signals:
PowerStateChangedEmitted when the power state of the GPU changes- Parameters:
s(string) – The new power state
- Parameters:
BPF
Introduction
Cardwire use the Kernel eBPF + LSM features to block syscall to the dGPU
List of used LSM
- lsm/file_open
- lsm/inode_permission
- lsm/inode_getattr
List of used MAPS
BLOCKED_RENDERID
- Used for the renderD minor
BLOCKED_CARDID
- For the card minor
BLOCKED_PCI
- For the PCI address
BLOCKED_PCI_FILES
- For the list of blocked PCI files
BLOCKED_NVIDIA_FILES
- For the list of blocked NVIDIA files
SETTINGS
- For experimental_nvidia_block
Block list
PCI files
Files that get blocked when a gpu’s PCI address is blocked:
- config
- current_link_speed
- current_link_width
- max_link_speed
- max_link_width
NVIDIA files
These files are only blocked when the experimental_nvidia_block setting is enabled
- libGLX_nvidia.so.0
- nvidia_icd.json
- nvidia_icd.x86_64.json
- nvidiactl
/dev/nvidia? using the minor
Example:
/dev/nvidia0
Will be blocked using the major 195 and the minor 0
DRM
DRM node (card + renderD) are blocked using their major + minor ID
Example:
/dev/dri/card1
/dev/dri/renderD128
Will be blocked using the major 226 and the minor 1 || 128
Building and Development
Building and Development
Using Nix
# Enter development shell
nix develop
# Build the project
nix build
# Run formatting checks
nix build .#checks.x86_64-linux.pre-commit-check
# Run integration tests in VM
nix build .#checks.x86_64-linux.vm-test
# 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)
# Build the project
make
# Install binaries, systemd service, and D-Bus config (requires sudo)
sudo make install
Project Structure
crates/cardwire-cli: User CLI to interact with the daemoncrates/cardwire-core: Low-level GPU manager and IOMMU discoverycrates/cardwire-daemon: System daemon managing state and D-Bus communicationcrates/cardwire-ebpf: BPF program and LSM hooks