Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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=y if 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.service and 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:

  • 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

com.github.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

Config

com.github.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

Methods:

  • SaveToFile Save the current daemon configuration (properties above) to the cardwire.toml config file
    • Inputs: None
    • Outputs: None

Debug

com.github.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

/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:

  • 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

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)
  • PowerState Get the current power state of the GPU

    • Inputs: None
    • Outputs:
      • (out): s – The power state (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

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 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