Skip to content

nvRmMuxPre()/nvRmMuxPost() overwrite params.flags, making SR_ENTER_SKIP unreachable #1311

Description

@ProgrmerJack

NVIDIA Open GPU Kernel Modules Version

610.57.04 (distribution package, not a self-built commit; the code quoted below is unchanged on main at the time of writing)

Please confirm this issue does not happen with the proprietary driver (of the same version). This issue tracker is only for bugs specific to the open kernel driver.

  • I confirm that this does not happen with the proprietary driver package.

Operating System and Version

CachyOS

Kernel Release

Linux 7.1.8-1-cachyos #1 SMP PREEMPT_DYNAMIC Tue, 11 Aug 2026 09:10:42 +0000 x86_64 GNU/Linux (distribution kernel, not self-built)

Please confirm you are running a stable release kernel (e.g. not a -rc). We do not accept bug reports for unreleased kernels.

  • I am running on a stable kernel release.

Hardware: GPU

GPU 0: NVIDIA GeForce RTX 5070 Laptop GPU

Describe the bug

In src/nvidia-modeset/src/nvkms-rm.c, nvRmMuxPre() sets the skip field and
then immediately overwrites the whole flags word:

params.flags = DRF_DEF(0073_CTRL_DFP, _DISP_MUX_FLAGS, _SR_ENTER_SKIP, _NO);

if (state == MUX_STATE_DISCRETE) {
    params.flags = NV0073_CTRL_DFP_DISP_MUX_FLAGS_SWITCH_TYPE_IGPU_TO_DGPU;
} else if (state == MUX_STATE_INTEGRATED) {
    params.flags = NV0073_CTRL_DFP_DISP_MUX_FLAGS_SWITCH_TYPE_DGPU_TO_IGPU;
}

The second assignment should be |=. Because _SR_ENTER_SKIP_NO and
_SWITCH_TYPE_IGPU_TO_DGPU are both 0, the resulting value is identical
today, so there is no visible regression - but it means SR_ENTER_SKIP can
never be set by the driver, and that field is therefore dead code.

That matters on this machine. NV0073_CTRL_CMD_DFP_RUN_PRE_DISP_MUX_OPERATIONS
returns NV_ERR_NOT_SUPPORTED (0x56) with the flags the driver sends, which
reads like the platform not implementing the entry point at all. Sweeping the
documented flag space shows otherwise (displayId 0x200, run twice, identical
both times):

PRE  i->d  sr=no   bl=no   flags=0x0 -> 0x56 NOT_SUPPORTED
PRE  i->d  sr=YES  bl=no   flags=0x2 -> 0x0  OK
PRE  i->d  sr=no   bl=YES  flags=0x8 -> 0x56 NOT_SUPPORTED
PRE  i->d  sr=YES  bl=YES  flags=0xa -> 0x0  OK
PRE  d->i  sr=no   bl=no   flags=0x1 -> 0x56 NOT_SUPPORTED
PRE  d->i  sr=YES  bl=no   flags=0x3 -> 0x0  OK
PRE  d->i  sr=no   bl=YES  flags=0x9 -> 0x56 NOT_SUPPORTED
PRE  d->i  sr=YES  bl=YES  flags=0xb -> 0x0  OK
POST (all eight combinations)         -> 0x0  OK

So the entry point is implemented, and only the PSR-entry sub-step is refused
on this platform. The remainder of pre-ops documented in ctrl0073dfp.h (SOR
sequencer, BL GPIO control, LCD VDD / BL EN / PWM MUX handover) works. The one
flag combination the firmware accepts is exactly the one the driver cannot
send.

nvRmMuxPost() contains the same overwrite, and additionally passes the
_SR_ENTER_SKIP name where the field is _SR_EXIT_SKIP. Same bit position, so
that part is a naming slip rather than a behaviour change.

Suggested fix:

params.flags = DRF_DEF(0073_CTRL_DFP, _DISP_MUX_FLAGS, _SR_ENTER_SKIP, _NO);
if (state == MUX_STATE_DISCRETE) {
    params.flags |= NV0073_CTRL_DFP_DISP_MUX_FLAGS_SWITCH_TYPE_IGPU_TO_DGPU;
} else if (state == MUX_STATE_INTEGRATED) {
    params.flags |= NV0073_CTRL_DFP_DISP_MUX_FLAGS_SWITCH_TYPE_DGPU_TO_IGPU;
} else {
    return FALSE;
}

plus a way for the caller to request SR_ENTER_SKIP_YES, so pre-ops can still
run on platforms where PSR entry is unavailable.

To Reproduce

Preconditions:

  • A laptop with a dynamic display mux whose internal panel is wired to both
    GPUs, with firmware reporting mux mode DYNAMIC. Verified here on an Acer
    Predator PHN16S-71 (Intel Arrow Lake-S + RTX 5070 Laptop, BIOS V1.26,
    PI3DPX8121 mux).
  • The mux RM controls are RMCTRL_FLAGS_KERNEL_PRIVILEGED, so they cannot be
    called from userspace at any uid. Reaching them needs a kernel-side RM client
    (here: a small out-of-tree module using the exported nvidia_get_rm_ops,
    whose .op dispatches NV04_CONTROL at RS_PRIV_LEVEL_KERNEL).

Steps:

  • Resolve the muxed display id with
    NV0073_CTRL_CMD_SYSTEM_QUERY_DISPLAY_IDS_WITH_MUX (returns 0x200 here).
  • Call NV0073_CTRL_CMD_SPECIFIC_SET_ACPI_ID_MAPPING, then
    NV0073_CTRL_CMD_DFP_INIT_MUX_DATA with the panel's real EDID
    manfId/productId. Both return NV_OK.
  • Call NV0073_CTRL_CMD_DFP_RUN_PRE_DISP_MUX_OPERATIONS with
    flags = _SWITCH_TYPE_IGPU_TO_DGPU (that is, 0x0, the value the driver
    sends). It returns NV_ERR_NOT_SUPPORTED.
  • Repeat with bit 1 set (_SR_ENTER_SKIP_YES, flags = 0x2). It returns
    NV_OK.
  • Repeat both for the d->i direction (flags = 0x1 and 0x3) for the same
    result.

Bug Incidence

Always

nvidia-bug-report.log.gz

nvidia-bug-report.log.gz

More Info

Expected: RUN_PRE_DISP_MUX_OPERATIONS either performs the non-PSR portion of
pre-ops, or reports something more specific than NV_ERR_NOT_SUPPORTED when
only the PSR sub-step is unavailable.

Actual: it refuses the whole call, and the flag that makes it succeed cannot be
set through the driver.

Related, and possibly worth its own issue: MuxInit() populates
pDispEvo->muxDisplays only from a hardcoded ACPI-id table that currently holds
one entry ({.acpiId = 0x8001a420, .displayId = 0x1000, .dodIndex = 0}). On any
other machine the nvDpyIdIsInDpyIdList() check in
nvRmMuxPre/Switch/Post fails, so NVKMS_IOCTL_SWITCH_MUX is unreachable.
The in-tree comment already describes it as "a poor-man's alternative to the
WDDM driver's CDisplayMgr::NVInitializeACPIToDeviceMaskMap()". Deriving the
mapping from _DOD at runtime would make the existing API usable much more
widely.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions