PCIe power management and error handling for a Thunderbolt eGPU: ASPM, D3cold, runtime PM, AER and DPC

PCIe power management and error handling for a Thunderbolt eGPU: ASPM, D3cold, runtime PM, AER and DPC

Which PCIe power-management and error-handling knobs actually matter for a Thunderbolt-tunnelled GPU on Linux — ASPM and pcie_aspm=off versus pcie_aspm.policy, port runtime PM and D3cold, NVIDIA’s runtime D3 parameters, USB4 CL states, reading AER and DPC evidence, and suspend/resume — separating the fix from the insurance from the cargo cult.


name: pcie-power-management-aer-dpc-egpu-linux title: PCIe Power Management, AER & DPC for Thunderbolt/USB4 eGPUs on Linux description: > Expert reference for PCIe power management and error handling of Thunderbolt/USB4-tunnelled devices (eGPUs) on Linux: ASPM (pcie_aspm=off vs pcie_aspm.policy), PCIe port runtime PM (pcie_port_pm, pci_bridge_d3_possible, D3cold, power/control), NVIDIA runtime D3 (NVreg_DynamicPowerManagement, RTD3), USB4 CL states (thunderbolt.clx), AER/DPC reading (native vs firmware-first, pci=noaer, UESta/CESta), pciehp+PM interactions, and eGPU suspend/resume. TRIGGER: eGPU falls off the bus, Xid 79, “D3cold to D0 device inaccessible”, which kernel params matter, AER/DPC log triage, s2idle hang with eGPU. SKIP: Thunderbolt authorization/boltctl basics, CUDA stack setup, Mac eGPU. verified-as-of: 2026-09-24

PCIe Power Management, AER & DPC for Thunderbolt/USB4 eGPUs on Linux

Verified-as-of 2026-09-24. Every claim is tagged [SOURCED url] (read from the cited page during research) or [INFERRED] (derived from sourced facts, code reading from memory, or the worked case; verify before relying on it). Kernel-parameter names, sysfs attribute names, and NVIDIA module-parameter names are quoted only where sourced.

Worked case (anchor)

Intel NUC 15 Pro (Arrow Lake-P, TB4) → Razer Core X V2 (Intel JHL9480 “Barlow Ridge” switch, PCI ID 8086:5786) → RTX 5080, Ubuntu 26.04.1, kernel 7.0.0-34, NVIDIA 610.57.04-open.

Working cmdline: thunderbolt.host_reset=0 pci=realloc=off pcie_ports=native pcie_port_pm=off pcie_aspm=off thunderbolt.clx=0 iommu=pt /etc/modprobe.d/nvidia-egpu-pm.conf: NVreg_DynamicPowerManagement=0x00 NVreg_PreserveVideoMemoryAllocations=0

Good-boot sysfs: GPU power/control=on, d3cold_allowed=1, runtime_status=active; switch bridges 02:00.0/03:00.0 control=auto; root port 00:07.0 LnkCtl: ASPM L1 Enabled while GPU and switch ports show ASPM Disabled. Failing boots: GPU audio function logged Unable to change power state from D3cold to D0, device inaccessible ~4.7 s into boot. pci=noaer was previously present (now removed).

Verdict (summary; details in each section):

Knob Role in the fix Why
thunderbolt.host_reset=0 Root cause fix Kernel ≥6.8.8 resets the USB4 host router at tb_start(), tearing down firmware-built tunnels; a boot-time-enumerated eGPU is yanked and re-plugged, and GPU drivers are not hot-removal-safe. [SOURCED https://ratatoskr.run/linux-usb/2026/08/17480231/t]
pci=realloc=off Root cause fix (companion) Stops the kernel from re-assigning BIOS bridge windows; after a tunnel teardown/rebuild a large-BAR GPU behind a fresh hotplug bridge can be re-laid-out in a way the firmware-sized hierarchy cannot satisfy. Semantics sourced; the exact interaction on this box is [INFERRED]. [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/1202.2/03626.html]
pcie_ports=native Stability insurance (conditional) Only matters if _OSC withheld hotplug/AER/DPC from the OS; check dmesg _OSC: OS now controls [...]. [SOURCED kernel-parameters via https://docs.kernel.org/admin-guide/kernel-parameters.html]
pcie_port_pm=off Stability insurance Prevents PCIe ports from being put into D3; proven workaround for the Barlow Ridge tunnel-runtime-suspend loss on AMD hosts (CachyOS #1057). Not the cause here. [SOURCED https://github.com/CachyOS/linux-cachyos/issues/1057]
pcie_aspm=off Does not disable ASPM; harmless as “don’t touch” Documented as “Don’t touch ASPM configuration at all. Leave any configuration done by firmware unchanged.” That is exactly why the root port still shows L1 Enabled. [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/2404.3/06648.html]
thunderbolt.clx=0 Link-integrity insurance Disables USB4 CL0s/CL1/CL2 low-power link states; 2026 quirk patches disable CL states on specific routers for stability. [SOURCED https://www.spinics.net/lists/linux-usb/msg219384.html]
NVreg_DynamicPowerManagement=0x00 Explicit no-op Default 0x03 already means “disabled” on desktop-class systems; 0x00 makes it explicit. [SOURCED https://download.nvidia.com/XFree86/Linux-x86_64/580.65.06/README/dynamicpowermanagement.html]
NVreg_PreserveVideoMemoryAllocations=0 Suspend-path insurance Default is already 0; keeps the driver from copying 16 GB of eGPU VRAM through the tunnel on suspend. [SOURCED https://download.nvidia.com/XFree86/Linux-x86_64/580.65.06/README/powermanagement.html]
pci=noaer (removed) Anti-pattern Silenced the only precursor telemetry (corrected errors) you had. [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/0710.0/1857.html]
iommu=pt Out of scope (DMA/perf, not PM) Not analysed here.

Core Concepts

  1. ASPM vs device power states. ASPM (L0s, L1, L1.1, L1.2) is link power management negotiated per link; D0–D3hot–D3cold are device states. D3cold means “Vcc removed” and “It is not possible to program a PCI device to go into D3cold” from config space; it needs a platform method (ACPI power resources, or the port upstream going to D3). [SOURCED https://www.kernel.org/doc/html/latest/power/pci.html]
  2. PCIe port runtime PM. Since 4.7 the kernel may put PCIe ports in D3 when everything below is suspended (bridge_d3 flag). pci_bridge_d3_possible() gates this: false for conventional PCI bridges, false when pcie_port_pm=off, true when forced, false for native-hotplug ports on x86 (not validated until ~2018), and otherwise BIOS-year ≥2015 or a Thunderbolt-attached port. [SOURCED https://www.mail-archive.com/[email protected]/msg1614295.html; https://ratatoskr.run/lkml/2026/07/17337075/t; https://patchwork.kernel.org/project/linux-pci/patch/10206fe7f3967aa73ade5b24dc729de0e94c3b7f.1529173804.git.lukas@wunner.de/ (title only; page body was not readable during research)]
  3. USB4 CL states. CL0s/CL1/CL2 are USB4 high-speed-lane low-power states enabled by the thunderbolt driver; clx module param (bool, default true) gates them. [SOURCED https://www.spinics.net/lists/linux-usb/msg219384.html; https://patchwork.kernel.org/project/linux-usb/patch/[email protected]/]
  4. Host router reset at driver start. The host_reset module param in drivers/thunderbolt/nhi.c is static bool host_reset = true; module_param(host_reset, bool, 0444) with description “reset USB4 host router (default: true)”. The reset tears down tunnels created by boot firmware. [SOURCED https://github.com/torvalds/linux/blob/master/drivers/thunderbolt/nhi.c; https://ratatoskr.run/linux-usb/2026/08/17480231/t]
  5. AER control is negotiated. “Linux does not handle AER events unless the firmware grants AER control to the OS via the ACPI _OSC method.” pcie_ports=native overrides that. DPC control is linked to AER control per PCIe r5.0 sec 6.2.10; pcie_ports=dpc-native decouples it. [SOURCED https://www.kernel.org/doc/html/latest/PCI/pcieaer-howto.html; https://www.spinics.net/lists/linux-pci/msg88329.html]
  6. Error classes. Correctable errors “pose no impacts on the functionality of the interface” and are only logged/cleared; uncorrectable non-fatal → error_detected(dev, pci_channel_io_normal); fatal → error_detected(dev, pci_channel_io_frozen) and “performing a reset at upstream is necessary”. [SOURCED https://www.kernel.org/doc/html/latest/PCI/pcieaer-howto.html]
  7. DPC = containment. “On platforms supporting Downstream Port Containment (PCIe r7.0 sec 6.2.11), the link to the sub-hierarchy with the faulting device is disabled”; devices are inaccessible until link reset/slot_reset. [SOURCED https://www.kernel.org/doc/html/latest/PCI/pci-error-recovery.html]
  8. “D3cold → D0, device inaccessible” is a symptom, not a PM decision. pci_raw_set_power_state() uses the PM capability in config space; config reads that return ~0 were historically misread as “device in D3hot/D3cold”. The message means config space is unreachable — the device is gone (tunnel torn down, link down, port suspended). [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/1908.2/07271.html]
  9. Removable detection. dev_is_removable() depends on the ACPI ExternalFacingPort property on the upstream bridge; some firmware omits it on Thunderbolt root ports, so drivers miss eGPU-specific paths. A 2026 patch adds a pci_is_thunderbolt_attached() fallback. amdgpu now disables runtime PM when either is true (7.4). [SOURCED https://ratatoskr.run/lkml/2026/04/3535291/t; https://ratatoskr.run/amd-gfx/2026/08/17400523/t]
  10. Why ASPM shows “Disabled” on the eGPU but “L1 Enabled” on the root port. For hotplugged Thunderbolt/USB4 devices “the BIOS may not have configured ASPM since the device wasn’t present at boot time”; Linux leaves them as found (Windows enables L1). A May-2026 patch to enable L0s/L1 for removable devices is still under discussion. [SOURCED https://ratatoskr.run/linux-pci/2026/05/3543749/t]

Knob Table

Parameter What it really does Matters for eGPU? Cost
pcie_aspm=off “Don’t touch ASPM configuration at all. Leave any configuration done by firmware unchanged.” [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/2404.3/06648.html] Only as “prevent Linux from enabling more” — relevant once the 2025-BIOS powersupersave default lands (RFC v2, May 2026, sets policy for BIOS ≥2025 unless user chose a policy or set pcie_aspm). [SOURCED https://lkml.iu.edu/2605.1/06202.html] Zero on this box; but it does not disable firmware-enabled L1 on the root port.
pcie_aspm.policy=performance Selects the “performance” policy (also writable at /sys/module/pcie_aspm/parameters/policy); default follows firmware. [SOURCED https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/power_management_guide/aspm] Use this if you actually want Linux to configure links to no-ASPM. Whether it clears firmware-enabled L1 on the root port: [INFERRED yes — policy is applied when Linux initialises link state; verify with lspci -vv after boot]. Idle power on all links.
pcie_aspm=force Enables ASPM even on devices that do not advertise it. [SOURCED RHEL doc above] Never for eGPU. Instability.
pcie_port_pm=off Disables power management of all PCIe ports (pci_bridge_d3_disable); force enables regardless of BIOS date. [SOURCED https://www.mail-archive.com/[email protected]/msg1614295.html] Insurance. Proven effective for CachyOS #1057 (AMD USB4 tunnel port runtime-suspends → downstream JHL9480 hierarchy vanishes); a per-port power/control=on udev rule is the surgical alternative. [SOURCED https://github.com/CachyOS/linux-cachyos/issues/1057] All root/downstream ports stay in D0 → higher idle power; observed control=auto on bridges is expected [INFERRED: portdrv’s runtime-suspend callback refuses when bridge_d3 is false, so auto is inert].
pcie_ports=native Use native PME/AER/DPC/hotplug “even if the platform doesn’t give the OS permission”, “may cause conflicts”. compat disables them; dpc-native = DPC only. [SOURCED https://docs.kernel.org/admin-guide/kernel-parameters.html; https://www.spinics.net/lists/linux-pci/msg88329.html] Conditional. If dmesg shows _OSC: OS now controls [PCIeHotplug ... AER ... DPC] already, it is a no-op. Firmware-first platforms: duplicate/undefined error handling.
pci=noaer Disables PCIe AER entirely (2007 option “to work around hardware or software problems”). [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/0710.0/1857.html] Anti-pattern for diagnosis. 2026 proposals add pci=noaer_recovery (log but don’t recover) and pci=nodpc; the PCI maintainer pushed back (“we should fix them”). [SOURCED https://ratatoskr.run/linux-doc/2026/07/17238362/t] Blind to corrected-error precursors.
pci=realloc=off “enables or disables reallocating PCI bridge resources if allocations done by BIOS are too small”; on/off override auto-detection. [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/1202.2/03626.html] Root-cause companion here (with host_reset). If BIOS windows really are too small, devices behind a bridge lose BARs.
thunderbolt.host_reset=0 Skip the USB4 host-router reset at tb_start(); keeps firmware-created tunnels. [SOURCED https://ratatoskr.run/linux-usb/2026/08/17480231/t; https://github.com/torvalds/linux/blob/master/drivers/thunderbolt/nhi.c] Root cause for boot-attached eGPU disconnects since 6.8.8 (Framework + RX 7600 / RTX 5060 reports; Thunderbolt driver maintainer: GPU drivers “Some of them are prepared for PCIe hot-removal, some are not yet”). Loses whatever the reset was protecting against (Tx-ring hang on some host routers; [INFERRED from 2026 nhi patches]).
thunderbolt.clx=0 Boolean, default true, “allows CLx on the High-Speed link”; 0 = no CL0s/CL1/CL2. [SOURCED https://www.spinics.net/lists/linux-usb/msg219384.html] Link-integrity insurance; 2026 quirks disable CL states on routers with firmware bugs. [SOURCED https://ratatoskr.run/linux-usb/2026/02/8892987/t] Slightly higher idle power on the USB4 link.
NVreg_DynamicPowerManagement=0x00/0x01/0x02/0x03 0x00 disable RTD3 (“only use the GPU’s built-in power management so it always is powered on”); 0x01 coarse; 0x02 fine-grained; 0x03 default = fine-grained on Ampere+ notebooks, disabled on pre-Ampere notebooks and “For desktop computers, irrespective of the GPU(s) used”. [SOURCED https://download.nvidia.com/XFree86/Linux-x86_64/580.65.06/README/dynamicpowermanagement.html] 0x00 is explicit no-op on a desktop-class host; never use 0x01/0x02 with an eGPU behind a tunnel (D3cold via the port is exactly the failure mode above). None.
NVreg_DynamicPowerManagementVideoMemoryThreshold Default 200 MB, max 1024 MB; VRAM above it blocks RTD3. [SOURCED same] Irrelevant with 0x00. —
NVreg_PreserveVideoMemoryAllocations=1 “save and restore all video memory allocations”; requires /proc/driver/nvidia/suspend + nvidia-suspend/hibernate/resume services; NVreg_TemporaryFilePath (default /tmp) must hold the VRAM. [SOURCED https://download.nvidia.com/XFree86/Linux-x86_64/580.65.06/README/powermanagement.html] Keep 0 on an eGPU unless CUDA contexts must survive suspend. Suspend copies up to full VRAM through the tunnel.
NVreg_EnableS0ixPowerManagement=1 + NVreg_S0ixPowerManagementVideoMemoryThreshold (default 256 MB) S0ix-based s2idle handling: copy VRAM or keep in self-refresh depending on usage. [SOURCED same] Notebook-oriented; untested for tunnelled eGPUs [INFERRED]. —
nvidia-persistenced (UVM persistence mode) “PCI-Express Runtime D3 (RTD3) Power Management will be disabled” when it runs with UVM persistence. [SOURCED dynamicpowermanagement.html] A second, independent way to pin the GPU in D0 for compute boxes. Daemon.
mem_sleep_default= / /sys/power/mem_sleep Chooses s2idle/shallow/deep; default is deep where S2RAM exists, or s2idle (ACPI may prefer s2idle regardless). [SOURCED https://www.kernel.org/doc/html/latest/admin-guide/pm/sleep-states.html] See Suspend/Resume. —
amdgpu.runpm=0 AMD equivalent: from 7.4 amdgpu disables runtime PM for eGPUs automatically. [SOURCED https://ratatoskr.run/linux-usb/2026/08/17480231/t; https://ratatoskr.run/amd-gfx/2026/08/17400523/t] For AMD eGPUs. —

ASPM & CL States

Runtime PM & D3cold

NVIDIA Runtime D3

AER & DPC Reading Guide

Where to read

Three signatures

  1. Link-integrity precursor. Growing CESta/aer_dev_correctable counts of RxErr, BadDLLP, BadTLP, Rollover, Timeout on the eGPU link (GPU function or the switch downstream port), often with LnkSta speed/width downtrain, minutes-to-hours before an Xid 79 or a fatal Surprise Down. Correctable errors are by definition recovered (“pose no impacts”), so the logger is your early warning. Actions: thunderbolt.clx=0, swap/shorten the cable, avoid L1 substates on that link (policy), reseat the enclosure card. [SOURCED AER classes: https://www.kernel.org/doc/html/latest/PCI/pcieaer-howto.html; field names: sysfs-bus-pci-devices-aer; forum practice of disabling ASPM for Xid 79: https://forums.developer.nvidia.com/t/xid-79-gpu-has-fallen-off-the-bus/359509] Precursor-to-Xid causality is [INFERRED].
  2. Zero-precursor Xid 79. NVRM: Xid (PCI:...): 79, GPU has fallen off the bus with no AER history, typically with pciehp ...: Slot(N): Link Down / Card not present and follow-on Unable to change power state from D3cold to D0, device inaccessible. Means the device vanished electrically or logically: PSU/enclosure power, tunnel torn down, or the port runtime-suspended (see #1057). Happens under load (power) or at idle (PM). Actions: check power_state/runtime_status of the tunnel port, PSU, then host_reset/tunnel. [SOURCED Xid 79 = “GPU has fallen off the bus” and load/idle variants: https://forums.developer.nvidia.com/t/xid-79-gpu-has-fallen-off-the-bus/359509, https://github.com/NVIDIA/open-gpu-kernel-modules/issues/900; catalog index: https://docs.nvidia.com/deploy/xid-errors/index.html]
  3. host_reset / tunnel rebuild at boot. Sequence: firmware-enumerated GPU visible at early boot → thunderbolt driver loads → USB4 host router reset tears down the firmware tunnel → pciehp Link Down on the TB downstream port → re-enumeration under a fresh bridge (and, with realloc, re-assigned windows) → driver probes race the rebuild → D3cold to D0, device inaccessible on a function, or “fallen off the bus” from the GPU driver. Fix: thunderbolt.host_reset=0 (+ pci=realloc=off when windows move). Exact log strings around the reset are [INFERRED]; the mechanism and the workaround are [SOURCED https://ratatoskr.run/linux-usb/2026/08/17480231/t].

DPC specifics

Suspend/Resume

udev Templates

Find BDFs first: lspci -tv; the USB4 PCIe tunnel port is the host-side root port the enclosure hangs off (on the worked case: 00:07.0), the enclosure switch is 8086:5786.

A. Keep a tunnel/root port in D0 (CachyOS #1057 pattern). Needed when runtime_status=suspended on the port coincides with a missing downstream hierarchy; the surgical alternative to pcie_port_pm=off.

# /etc/udev/rules.d/90-egpu-port-pm.rules
# Host-side USB4 PCIe tunnel port (replace vendor/device with yours; AMD Phoenix example 1022:14ef)
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x1022", ATTR{device}=="0x14ef", TEST=="power/control", ATTR{power/control}="on"
# Or pin by path (Intel NUC worked case root port 00:07.0)
ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:00:07.0", TEST=="power/control", ATTR{power/control}="on"
# Barlow Ridge downstream/upstream ports inside the enclosure
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x8086", ATTR{device}=="0x5786", TEST=="power/control", ATTR{power/control}="on"

[SOURCED effective workaround: https://github.com/CachyOS/linux-cachyos/issues/1057; attribute semantics: sysfs-devices-power]

B. Forbid D3cold for the eGPU and its functions. Needed when the GPU/audio function is being put in D3cold by the platform (ACPI power resources on the port) rather than by a suspended port — verify with power_state. Not needed when RTD3 is 0x00 and the port stays in D0 (worked case: d3cold_allowed=1 was fine).

# /etc/udev/rules.d/91-egpu-no-d3cold.rules
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", ATTR{d3cold_allowed}="0"
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{d3cold_allowed}="0"

[SOURCED semantics: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci; when-needed logic is INFERRED]

C. Neutralise the notebook-oriented NVIDIA PM rules on an eGPU box (if 80-nvidia-pm.rules is installed): copy it to /etc/udev/rules.d/80-nvidia-pm.rules with the audio-removal line deleted and the bind→auto lines changed to on, or mask it with an empty file of the same name. [SOURCED rule content: dynamicpowermanagement.html; the override technique is standard udev practice, INFERRED]

Apply: udevadm control --reload && udevadm trigger --subsystem-match=pci (or re-plug). Verify: grep . /sys/bus/pci/devices/<BDF>/power/{control,runtime_status} /sys/bus/pci/devices/<BDF>/{d3cold_allowed,power_state}.

Anti-patterns

  1. pci=noaer as a “fix”. It removes the only precursor telemetry; the 2026 noaer_recovery/nodpc proposal exists precisely because people want logging without recovery, and even that got “we should fix them” pushback. [SOURCED https://ratatoskr.run/linux-doc/2026/07/17238362/t]
  2. Believing pcie_aspm=off disables ASPM. It is documented as “leave any configuration done by firmware unchanged”. Want links in L0 only? pcie_aspm.policy=performance. [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/2404.3/06648.html]
  3. pcie_aspm=force on an eGPU chain. It enables ASPM on devices that do not advertise it, inviting instability. [SOURCED RHEL ASPM doc]
  4. Global pcie_port_pm=off when one port rule suffices — CachyOS #1057 calls the global switch “undesirable”. [SOURCED https://github.com/CachyOS/linux-cachyos/issues/1057]
  5. pcie_ports=native on a firmware-first platform without checking _OSC — documented “may cause conflicts”. [SOURCED kernel-parameters]
  6. RTD3 (0x01/0x02) or power/control=auto on a tunnelled eGPU. The port-D3 path is the known loss mode; amdgpu now disables runtime PM for eGPUs outright. [SOURCED https://ratatoskr.run/amd-gfx/2026/08/17400523/t]
  7. PreserveVideoMemoryAllocations=1 with a 16 GB eGPU and default /tmp. Saved VRAM must fit in NVreg_TemporaryFilePath, so size it for the full 16 GB first. [SOURCED powermanagement.html requirements]
  8. Stacking every parameter from forum posts, then crediting the stack. In the worked case only two of seven cmdline knobs were causal; keep the rest as labelled insurance and re-test removal one at a time on a kernel upgrade.
  9. Treating “D3cold to D0, device inaccessible” as a PM misconfiguration. It is the PCI core reporting unreachable config space. [SOURCED https://lkml.iu.edu/hypermail/linux/kernel/1908.2/07271.html]
  10. Hot-unplugging an in-use eGPU expecting the driver to cope. NVIDIA: not guaranteed; the Thunderbolt driver maintainer: some GPU drivers “are not yet” prepared. [SOURCED egpu.html; host_reset thread]

Related references added later: egpu-idle-power-and-energy-accounting-linux.md (idle watts, persistence mode, power limits and energy cost).

Sources