Skip to content

ExternalDisplayAutoSwitch is unreliable, does not restore Smart mode, and can crash Mutter on NVIDIA HDMI hotplug #179

Description

@Eponeshnikov

Describe the bug

On a hybrid AMD + NVIDIA laptop where the HDMI port is wired to the NVIDIA dGPU, ExternalDisplayAutoSwitch does not provide reliable display hotplug behavior from Smart mode.

I observed three related failures:

  1. Plugging HDMI while in Smart mode often leaves Cardwire in Smart. The NVIDIA GPU remains blocked and the monitor is not exposed to GNOME.
  2. On another attempt Cardwire automatically switched to Hybrid, but the external display still did not appear until I logged out and back in. If the GNOME session starts in Smart, Mutter only enumerates the AMD GPU.
  3. Repeated unplug/replug attempts caused reproducible gnome-shell SIGSEGV crashes in Mutter's KMS hotplug path, returning the session to GDM. This happened twice with coredumps present.

There is also a separate restore problem: after an automatic switch to Hybrid, unplugging HDMI did not restore the persisted Smart mode. cardwire get continued to report Hybrid even though the NVIDIA HDMI connector was disconnected and the dGPU had returned to D3Cold.

To Reproduce

  1. Use a Wayland GNOME session on a hybrid laptop whose HDMI port is owned by the NVIDIA dGPU.

  2. Enable external-display switching:

    ExternalDisplayAutoSwitch: true
    
  3. Select Smart mode and wait for the NVIDIA GPU to reach D3Cold.

  4. Plug an HDMI monitor.

  5. Observe one of the following:

    • Cardwire remains in Smart and the display does not appear.
    • Cardwire switches to Hybrid, but the display still does not appear in GNOME Settings.
  6. Unplug and reconnect HDMI. In my case, repeating this caused gnome-shell to segfault and the graphical session to terminate.

  7. If Cardwire did switch automatically to Hybrid, unplug HDMI and wait. It remains in Hybrid instead of restoring Smart.

The external monitor is detected reliably if I start/login to GNOME while already in Hybrid with HDMI connected, but requiring logout/login defeats hotplug.

Expected behavior

  • HDMI connection should be detected reliably while Smart mode has blocked and runtime-suspended the dGPU.
  • Cardwire should temporarily expose the dGPU early enough for the running Wayland compositor to use it.
  • The external monitor should appear without logout/login.
  • Disconnecting the last dGPU-owned external display should restore the persisted Smart mode.
  • Repeated hotplug must not produce conflicting DRM/KMS state changes or crash Mutter.

Environment details

OS: Fedora Linux 44 (Workstation Edition) x86_64
Host: ROG Zephyrus G14 GA402XV_GA402XV (1.0)
Kernel: Linux 7.1.7-200.fc44.x86_64
DE: GNOME 50.4
WM: Mutter 50.4 (Wayland)
CPU: AMD Ryzen 9 7940HS
iGPU: AMD Radeon 780M Graphics (amdgpu), PCI 0000:65:00.0
dGPU: NVIDIA GeForce RTX 4060 Laptop GPU, PCI 0000:01:00.0
NVIDIA driver: 610.57.04 (open kernel module)
External monitor: Philips PHL32M1N5500V over HDMI, port owned by NVIDIA card0

Cardwire version (cardwire --version):

cardwire-cli 0.12.0
RPM: cardwire-0.12.0-1.fc44.x86_64

Relevant configuration:

ExternalDisplayAutoSwitch: true
ExperimentalNvidiaBlock: true
Available Mode: integrated, hybrid, smart

cardwire list output

Captured in Smart mode:

ID  NAME                                PCI           RENDER      CARD   DEFAULT DISCRETE  BLOCKED
--  ----------------------------------  ------------  ----------  -----  -------  -------  -------
0   AMD Radeon 780M Graphics            0000:65:00.0  renderD128  card1  (*)      ( )      false
1   NVIDIA GeForce RTX 4060 Laptop GPU  0000:01:00.0  renderD129  card0  ( )      (*)      true

ls -la /dev/dri/by-path/

Run with CARDWIRE_ALLOW=1 because Smart mode hides the NVIDIA entries from an unclassified process:

total 0
drwxr-xr-x. 2 root root 120 Aug 10 13:03 .
drwxr-xr-x. 3 root root 140 Aug 10 13:03 ..
lrwxrwxrwx. 1 root root   8 Aug 10 13:03 pci-0000:01:00.0-card -> ../card0
lrwxrwxrwx. 1 root root  13 Aug 10 13:03 pci-0000:01:00.0-render -> ../renderD129
lrwxrwxrwx. 1 root root   8 Aug 10 13:03 pci-0000:65:00.0-card -> ../card1
lrwxrwxrwx. 1 root root  13 Aug 10 13:03 pci-0000:65:00.0-render -> ../renderD128

cardwired logs

First automatic transition and the first crash sequence:

Aug 10 13:03:53 cardwired[996]: gnome-shell[4561] tried to access GPU 1 (blocked by cardwire)
Aug 10 13:28:51 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D0
Aug 10 13:28:56 cardwired[996]: (ignoring) failed to get inode for hwmon 0000:01:00.0: No such file or directory
Aug 10 13:28:56 cardwired[996]: Switched to Hybrid
...
Aug 10 13:44:03 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D0
Aug 10 13:44:06 cardwired[996]: Switched to Hybrid
Aug 10 13:44:40 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D3Cold
Aug 10 13:44:41 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D0
Aug 10 13:44:43 kernel: gnome-shell[60435]: segfault ...
Aug 10 13:44:45 systemd-coredump: Process 60435 (gnome-shell) dumped core

Second reproduction:

Aug 10 13:47:54 cardwired[996]: Switched to Smart
Aug 10 13:47:56 cardwired[996]: gnome-shell[81907] tried to access GPU 1 (blocked by cardwire)
Aug 10 13:48:03 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D3Cold
Aug 10 13:49:58 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D0
Aug 10 13:50:01 cardwired[996]: Switched to Hybrid
Aug 10 13:50:20 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D3Cold
Aug 10 13:51:09 cardwired[996]: NVIDIA GeForce RTX 4060 Laptop GPU: Power state changed: D0
Aug 10 13:51:10 kernel: gnome-shell[81907]: segfault ... in libmutter-18.so.0.0.0
Aug 10 13:51:12 systemd-coredump: Process 81907 (gnome-shell) dumped core

Mutter coredumps

Two coredumps are present:

13:44:43  PID 60435  gnome-shell  SIGSEGV
13:51:10  PID 81907  gnome-shell  SIGSEGV

The first main-thread stack:

#0  __strlen_evex
#1  __printf_buffer
#2  __vasprintf_internal
#3  g_vasprintf
#4  g_strdup_vprintf
#5  g_error_new_valist
#6  g_set_error
#7  meta_crtc_kms_assign_extra
#8  assign_monitor_crtc
#9  meta_monitor_mode_foreach_crtc
#10 meta_monitor_config_manager_assign
#11 meta_monitor_manager_native_apply_monitors_config
#12 meta_monitor_manager_apply_monitors_config
#13 meta_monitor_manager_ensure_configured
#14 on_kms_resources_changed
#20 on_udev_hotplug
#25 on_uevent

The second reproduction crashed even more directly in the same path:

#0  meta_kms_device_get_planes
#1  find_unassigned_plane
#2  meta_crtc_kms_assign_extra
#3  assign_monitor_crtc
#4  meta_monitor_mode_foreach_crtc
#5  meta_monitor_config_manager_assign
#6  meta_monitor_manager_native_apply_monitors_config
#7  meta_monitor_manager_apply_monitors_config
#8  meta_monitor_manager_ensure_configured
#9  on_kms_resources_changed
#15 on_udev_hotplug
#20 on_uevent

No NVIDIA Xid, GPU reset, amdgpu reset, OOM event, or kernel panic was recorded around either crash.

Additional context / suspected implementation issues

  1. In Smart mode, an unclassified process cannot see the NVIDIA connector paths at all. For example, /sys/class/drm/card0-HDMI-A-1/status returns No such file or directory, while the same connector exists in Hybrid. Because is_gpu_active() enumerates /sys/class/drm, it may be racing with or affected by Cardwire's own GPU hiding. This would explain why HDMI detection sometimes does nothing until another process wakes the dGPU.

  2. The restore logic appears unable to run after the temporary override. internal_set_mode(Hybrid, false) updates the in-memory mode_state to Hybrid. On disconnect, reconcile_gpu() reads that applied mode and matches Modes::Hybrid, whose branch does nothing. Therefore it never reaches the Smart/Integrated branch that reloads the persisted mode from mode.json.

  3. internal_set_mode() emits DRM change uevents for each GPU after applying a mode. Physical HDMI hotplug also produces uevents. The two coredumps show Mutter crashing while processing KMS resource changes shortly after the dGPU transitions from D3Cold to D0. This may indicate an unsafe or inconsistent topology sequence, though the immediate null/stale-object handling failure is inside Mutter.

Relevant source files:

  • crates/cardwire-daemon/src/tasks/monitor_display.rs
  • crates/cardwire-daemon/src/core/gpu/display.rs
  • crates/cardwire-daemon/src/interface/mode.rs
  • crates/cardwire-daemon/src/file/state.rs

I can provide the full Cardwire journal and both systemd coredumps if needed.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions