You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug]: dGPU still resumed every 2 s on 6.4.0 — #294's guard is bypassed via the dGPU's sibling audio function, and iGPU telemetry reports the dGPU #302
Affected Component: rog-control-center (GUI / Slint / System Tray) — root cause in rog-platform Version: 6.4.0.r0.ge6c1469c-1, and identical on main (main is currently the 6.4.0 tag, 0 commits ahead) Session: Wayland (Hyprland)
Issue Description
This is a follow-up to #293, which #294 fixed and closed. The guard #294 added is correct and it
does fire — but there is a second path into NVML that never reaches it, so on 6.4.0 the dGPU is
still resumed roughly every 2 seconds while the RCC window is open.
The guard keys on the Device::is_dgpu flag. Device::find() also returns the dGPU's sibling PCI
function — on this machine 0000:01:00.1, the NVIDIA HD Audio controller (10DE:228E, class 040300) — with is_dgpu: false but pci_id: "10DE:228E". Every guard in gpu_pci.rs is written if self.is_dgpu && runtime_status != Active, while the NVML fallback below it is written if self.pci_id.starts_with("10DE"). A non-display NVIDIA function passes between the two: the
guard skips it because the flag is false, and the fallback claims it because the vendor matches.
get_gpu_telemetry() then treats that audio function as the integrated GPU, calls get_temp() and get_usage_pct() on it, falls through to NVML, and Nvml::init() + device_by_index(0) resumes the
sleeping dGPU. This also produces a second, user-visible symptom: the iGPU temperature and usage
shown in the GUI are the dGPU's NVML values, not the integrated GPU's.
Improvement over 6.3.11 is real but small: the card now manages about 6% sleep with the window open,
where before it managed 0%.
Root cause
1. A non-display sibling function enters the device list as a non-dGPU NVIDIA device
rog-platform/src/gpu_pci.rs, in Device::find():
if dgpu || (!parent.is_empty() && sysname.contains(&parent)){// line 476if dgpu {info!("Found dgpu {id} at {:?}", device.sysname());}else{info!("Found additional device {id} at {:?}", device.sysname());}
parent = get_parent(&device);
devices.push(Self{dev_path:PathBuf::from(device.syspath()),is_dgpu: dgpu,// false for 01:00.1pci_id: id.to_string(),// but still "10DE:228E"});}
There is no class check on the sysname.contains(&parent) branch, so the audio function of the GPU
is admitted alongside it.
2. The guard checks the flag, the fallback checks the vendor
fnprobe_hwmon(&self,read:fn(&Path) -> Option<f32>,nvml:fn() -> Option<f32>) -> Option<f32>{ifself.is_dgpu// line 281 — false here, guard skipped
&& self.get_runtime_status().unwrap_or(GfxPower::Unknown) != GfxPower::Active{returnNone;}// ... 01:00.1 has no hwmon and no DRM node, so both lookups miss ...ifself.pci_id.to_uppercase().starts_with(NVIDIA_PCI_VENDOR){// line 315 — matchesreturnnvml();// -> Nvml::init(), device_by_index(0)}None}
get_usage_pct() has the same shape: guard at line 333, NVML fallback at line 372.
3. get_gpu_telemetry() routes it through the iGPU branch
4. The break ends enumeration before the real iGPU is ever seen
if !parent.is_empty() && !sysname.contains(&parent){// line 492break;}
On this machine the dGPU is 0000:01:00.0 and the integrated Radeon 680M is 0000:05:00.0. Once 01:00.1 has been consumed, the next PCI device does not match parent and the scan stops — the
iGPU is never enumerated at all. This is why the iGPU fields have nothing left to report but the
NVML value picked up in step 2. get_gpu_names() uses a separate scan that filters on display class
and has no break, which is why the GUI shows the correct iGPU name next to the wrong numbers.
5. The NVML handle now persists for the process lifetime
fn nvml() -> Option<&'static nvml_wrapper::Nvml>{staticNVML:OnceLock<nvml_wrapper::Nvml> = OnceLock::new();// line 182
This is a good change in itself, but it means one unguarded call is permanent: after the first Nvml::init() the NVIDIA device nodes stay open for as long as RCC runs. In #293/dev/nvidia0 was
open in 3.4% of /proc/<pid>/fd scans; on 6.4.0 it is open in 100% of them.
Open ROG Control Center and leave the window visible.
Watch power/runtime_status and power/runtime_suspended_time.
Compare the "iGPU" temperature in the GUI against the integrated GPU's own hwmon
(/sys/bus/pci/devices/<igpu>/hwmon/hwmon*/temp1_input).
Reproduces on any machine where the dGPU's audio function is enumerated (i.e. essentially every
Optimus laptop) — nothing here is specific to this board.
Measurements
All sampling reads only sysfs and /proc/<pid>/fd.
The card is still held awake. 60 s with the window open, against a 60 s control with RCC closed:
window open sleep gained: 3937 ms / 60 s (6.0%) active gained: 62148 ms
RCC closed sleep gained: 60079 ms / 60 s (100.0%) active gained: 0 ms
NVML is entered while the card is still asleep./proc/<pid>/fd scanned from process start
(206140 scans in 30 s), timestamps relative to launch:
0.000 runtime_status = suspended
0.404 /dev/nvidiactl opened <- card still 'suspended'; the guard never saw this call
0.674 runtime_status -> resuming
2.152 runtime_status -> active
2.153 /dev/nvidia0, /dev/nvidia-caps/nvidia-cap2, /dev/nvidia-uvm opened
22.357 runtime_status -> suspending
22.973 runtime_status -> suspended
24.625 runtime_status -> resuming <- next telemetry tick wakes it again
26.103 runtime_status -> active
It is NVML, not the renderer. The only NVIDIA library mapped into the process is /usr/lib/libnvidia-ml.so.610.57.04; the EGL vendor actually loaded is libEGL_mesa.so, with no libGLX_nvidia, no libEGL_nvidia and no Vulkan loader present.
RCC's own log names the device. With RUST_LOG=rog_platform=trace:
[INFO ] Found dgpu 10DE:2520 at "0000:01:00.0"
[INFO ] Found additional device 10DE:228E at "0000:01:00.1"
[TRACE] Looking at PCI device "0000:01:00.0"
[TRACE] Looking at PCI device "0000:01:00.1" <- last device of every scan; 05:00.0 never reached
(Minor: the trace!("Matched dGPU {id} ... by checking display connections") line is also printed
for 10DE:228E, because it is logged before is_display_class() decides. Harmless, but misleading
when reading logs.)
The iGPU fields report the dGPU. Under a CPU/APU load, so that the two sensors separate:
real integrated GPU (amdgpu hwmon, 0000:05:00.0) 62 °C
dGPU (nvidia-smi) 50 °C
GUI "iGPU Status | AMD Radeon 680M" 50 °C <- follows the dGPU
GUI "dGPU Status | GA106M [RTX 3060 Mobile]" 50 °C
Idle, both GUI fields read exactly the same number at every sample.
Expected behaviour
Opening the GUI should not resume a sleeping dGPU. Any NVML entry point should be gated on the
dGPU's runtime_status, whichever device object happens to reach it.
The iGPU temperature/usage fields should report the integrated GPU, or nothing — never the dGPU's
NVML readings.
Suggested fix
Do not admit non-display functions to the device list. Add the class check to the sibling
branch at line 476, e.g. if dgpu || (!parent.is_empty() && sysname.contains(&parent) && is_display_class(&class)).
The audio function has no hwmon, no DRM node and no telemetry to contribute.
Gate the NVML fallback on the vendor test that reaches it, not on is_dgpu. Wherever pci_id starting with 10DE is enough to call NVML (lines 315 and 372), the dGPU's runtime_status should be the thing that decides — get_gpu_power_status() == GfxPower::Active,
a passive sysfs read that (as measured in [Bug]: rog-control-center telemetry loop calls NVML unconditionally, resuming a runtime-suspended dGPU while the window is open #293) does not wake the device. That keeps the guard
correct even if some other non-dGPU NVIDIA function shows up on another board.
Reconsider the break at line 492. It assumes the iGPU is enumerated before the dGPU. On
boards where the dGPU comes first, enumeration stops after the dGPU's own functions and the iGPU
is never found, which is what leaves the iGPU telemetry fields to be filled by whatever the
fallback returns.
System details
Distro: Arch Linux
Kernel: 7.1.8-zen1-3-zen
Desktop: Hyprland (Wayland)
Model: ROG Strix G513RM (board G513RM)
dGPU: NVIDIA GA106M [GeForce RTX 3060 Mobile / Max-Q], PCI 0000:01:00.0 (audio function 0000:01:00.1)
asusctl / rog-control-center: 6.4.0.r0.ge6c1469c-1 (from the [ogc] repo)
The investigation, the measurements above and this report were produced with the assistance of
Claude (Anthropic's Claude Code). Every figure was measured on the system described above; nothing
is inferred or estimated.
Affected Component: rog-control-center (GUI / Slint / System Tray) — root cause in rog-platform
Version: 6.4.0.r0.ge6c1469c-1, and identical on
main(mainis currently the 6.4.0 tag, 0 commits ahead)Session: Wayland (Hyprland)
Issue Description
This is a follow-up to #293, which #294 fixed and closed. The guard #294 added is correct and it
does fire — but there is a second path into NVML that never reaches it, so on 6.4.0 the dGPU is
still resumed roughly every 2 seconds while the RCC window is open.
The guard keys on the
Device::is_dgpuflag.Device::find()also returns the dGPU's sibling PCIfunction — on this machine
0000:01:00.1, the NVIDIA HD Audio controller (10DE:228E, class040300) — withis_dgpu: falsebutpci_id: "10DE:228E". Every guard ingpu_pci.rsis writtenif self.is_dgpu && runtime_status != Active, while the NVML fallback below it is writtenif self.pci_id.starts_with("10DE"). A non-display NVIDIA function passes between the two: theguard skips it because the flag is false, and the fallback claims it because the vendor matches.
get_gpu_telemetry()then treats that audio function as the integrated GPU, callsget_temp()andget_usage_pct()on it, falls through to NVML, andNvml::init()+device_by_index(0)resumes thesleeping dGPU. This also produces a second, user-visible symptom: the iGPU temperature and usage
shown in the GUI are the dGPU's NVML values, not the integrated GPU's.
Improvement over 6.3.11 is real but small: the card now manages about 6% sleep with the window open,
where before it managed 0%.
Root cause
1. A non-display sibling function enters the device list as a non-dGPU NVIDIA device
rog-platform/src/gpu_pci.rs, inDevice::find():There is no class check on the
sysname.contains(&parent)branch, so the audio function of the GPUis admitted alongside it.
2. The guard checks the flag, the fallback checks the vendor
get_usage_pct()has the same shape: guard at line 333, NVML fallback at line 372.3.
get_gpu_telemetry()routes it through the iGPU branch4. The
breakends enumeration before the real iGPU is ever seenOn this machine the dGPU is
0000:01:00.0and the integrated Radeon 680M is0000:05:00.0. Once01:00.1has been consumed, the next PCI device does not matchparentand the scan stops — theiGPU is never enumerated at all. This is why the iGPU fields have nothing left to report but the
NVML value picked up in step 2.
get_gpu_names()uses a separate scan that filters on display classand has no
break, which is why the GUI shows the correct iGPU name next to the wrong numbers.5. The NVML handle now persists for the process lifetime
This is a good change in itself, but it means one unguarded call is permanent: after the first
Nvml::init()the NVIDIA device nodes stay open for as long as RCC runs. In #293/dev/nvidia0wasopen in 3.4% of
/proc/<pid>/fdscans; on 6.4.0 it is open in 100% of them.Steps to reproduce
cat /sys/bus/pci/devices/<dgpu>/power/runtime_status→suspendedpower/runtime_statusandpower/runtime_suspended_time.(
/sys/bus/pci/devices/<igpu>/hwmon/hwmon*/temp1_input).Reproduces on any machine where the dGPU's audio function is enumerated (i.e. essentially every
Optimus laptop) — nothing here is specific to this board.
Measurements
All sampling reads only sysfs and
/proc/<pid>/fd.The card is still held awake. 60 s with the window open, against a 60 s control with RCC closed:
NVML is entered while the card is still asleep.
/proc/<pid>/fdscanned from process start(206140 scans in 30 s), timestamps relative to launch:
It is NVML, not the renderer. The only NVIDIA library mapped into the process is
/usr/lib/libnvidia-ml.so.610.57.04; the EGL vendor actually loaded islibEGL_mesa.so, with nolibGLX_nvidia, nolibEGL_nvidiaand no Vulkan loader present.RCC's own log names the device. With
RUST_LOG=rog_platform=trace:(Minor: the
trace!("Matched dGPU {id} ... by checking display connections")line is also printedfor
10DE:228E, because it is logged beforeis_display_class()decides. Harmless, but misleadingwhen reading logs.)
The iGPU fields report the dGPU. Under a CPU/APU load, so that the two sensors separate:
Idle, both GUI fields read exactly the same number at every sample.
Expected behaviour
dGPU's
runtime_status, whichever device object happens to reach it.NVML readings.
Suggested fix
branch at line 476, e.g.
if dgpu || (!parent.is_empty() && sysname.contains(&parent) && is_display_class(&class)).The audio function has no hwmon, no DRM node and no telemetry to contribute.
is_dgpu. Whereverpci_idstarting with10DEis enough to call NVML (lines 315 and 372), the dGPU'sruntime_statusshould be the thing that decides —get_gpu_power_status() == GfxPower::Active,a passive sysfs read that (as measured in [Bug]: rog-control-center telemetry loop calls NVML unconditionally, resuming a runtime-suspended dGPU while the window is open #293) does not wake the device. That keeps the guard
correct even if some other non-dGPU NVIDIA function shows up on another board.
breakat line 492. It assumes the iGPU is enumerated before the dGPU. Onboards where the dGPU comes first, enumeration stops after the dGPU's own functions and the iGPU
is never found, which is what leaves the iGPU telemetry fields to be filled by whatever the
fallback returns.
System details
0000:01:00.0(audio function0000:01:00.1)0000:05:00.0/etc/modprobe.d:options nvidia NVreg_EnableS0ixPowerManagement=1 NVreg_DynamicPowerManagement=0x02[ogc]repo)The investigation, the measurements above and this report were produced with the assistance of
Claude (Anthropic's Claude Code). Every figure was measured on the system described above; nothing
is inferred or estimated.