Re: [PATCH] drm/nouveau/disp: Prevent division-by-zero during DisplayPort link training transitions

From: lyude

Date: Wed Aug 05 2026 - 13:34:55 EST


JFYI - going to try to review/test this ASAP, just been sidetracked
chasing some big runtime PM issues in nouveau atm :S

On Fri, 2026-07-17 at 10:15 +0200, Arjan Filius wrote:
> Hello Lyude,
>
> Yes, I did use generative AI to help refine and structure this (old)
> patch.
> It is completely based on work/debug/fixing I had done manually few
> years back.
> Generative AI helped me to send the message, but re-used my old
> patched code, brushed up a bit.
> And tested it this week on kernel 7.1.3, but previous tested on
> 6.8.0.x and 6.9.9 kernels.
>
> In 2024 i submitted a bug report/fix/workaround (to prevent a full
> lockup every day) on the Freedesktop GitLab under Issue 377:
> https://gitlab.freedesktop.org/drm/nouveau/-/issues/377
>
> Back then, my local workaround was a bit less elegant, and had a few
> extra checks/debug statements.
> I had almost forgotten about the issue since my workaround was
> running fine.
> When I recently booted up a clean mainline 7.1.3 kernel and saw the
> system freeze again within a day and every day,
> and had to fsck my disks due to it, I remembered the fix I once
> debugged and posted in 2024.
> I wanted to see if I could find a cleaner, more elegant solution and
> used AI for it, to see if the patch could be in a nicer place,
> but ended up to basically reproduce the fix i had written in 2024
> (without AI).
>
>
> Regarding the two patches (A and B):
> * **Patch B** was the version I compiled and tested locally with the
> `nvkm_warn()` statement
>   so I could verify in `dmesg` that the standby transitions were
> actually triggering the block and being safely bypassed.
> * **Patch A** is the exact same check but silent (without the warning
> logs).
>
> To avoid any confusion i repeat my patch, which does not produce any
> logging every time,
> but just avoids the lockup referencing null pointers when external
> displays goes to sleep.
>
>
> ```diff
> diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> index 51c045bdfcbb..ab1bc0898def 100644
> --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> @@ -1157,6 +1157,10 @@ nv50_disp_super_2_2_dp(struct nvkm_head *head,
> struct nvkm_ior *ior)
>  {
>   struct nvkm_subdev *subdev = &head->disp->engine.subdev;
>  
> + /* If the DisplayPort link is inactive (0 lanes), skip
> configuration */
> + if (ior->dp.nr == 0)
> + return;
> +
>   const u32 khz = head->asy.hz / 1000;
>   const u32 linkKBps = ior->dp.bw * 27000;
>   const u32 symbol = 100000;
> ```
>
> This completely fixes my lenovo T430(NVIDIA NVS 5400M) + 4338 Dock
> and 2 external (multi-input) monitors freeze, proven over some years.
> At the time the nvidia kernel driver on my T430 became an issue wih
> newer kernels, and notice nouveau driver did pretty well too.
>
> Hope this claries it.
>
> Best regards,
> Arjan Filius
>
>
> > On 16 Jul 2026, at 22:40, lyude@xxxxxxxxxx wrote:
> >
> > Hi, can you please clarify whether or not you used generative AI
> > for
> > this patch series? If so, this needs to be noted as part of the
> > commit
> > message. Additionally, both of these patches look to be identical
> > except that one prints a message and the other doesn't. You can
> > just
> > send a single version, we will figure out how to handle that part
> > after.
> >
> > On Thu, 2026-07-16 at 16:55 +0200, Arjan Filius wrote:
> > > Dear Nouveau Maintainers,
> > >  
> > > Please find below a proposed patch to prevent a division-by-zero
> > > kernel oops and subsequent lockup in the Nouveau display
> > > supervisor
> > > engine when DisplayPort links transition to 0 active lanes. This
> > > bug
> > > was observed and patched/tested against the mainline kernel
> > > version
> > > **7.1.3**, but is present in all current upstream code.
> > >  
> > > ---
> > >  
> > > ### BRIEF EXPLANATION
> > >  
> > > During screen sleep transitions or multi-input display switches
> > > (KVM
> > > switching), the active lane count of a DisplayPort link can
> > > temporarily drop to 0 (`ior->dp.nr == 0`).
> > >  
> > > When this occurs, the DisplayPort supervisor function
> > > `nv50_disp_super_2_2_dp` is invoked via modeset interrupts.
> > > Without
> > > an early-exit guard, the function attempts to perform
> > > symbols/hblank
> > > and link rate calculations that divide by `ior->dp.nr`, resulting
> > > in
> > > a kernel division-by-zero math exception, lockup, and system
> > > freeze.
> > >  
> > > This patch adds an early-exit check at the beginning of
> > > `nv50_disp_super_2_2_dp` to abort display configuration when the
> > > link
> > > training lanes count is zero.
> > >
> > > *   **Tested Kernel Version:** `7.1.3` (mainline stable branch)
> > >
> > > ---
> > >
> > > ### EXTREMELY DETAILED TECHNICAL BREAKDOWN
> > >
> > > #### 1. Hardware Context
> > > This bug has been consistently reproduced on a Lenovo ThinkPad
> > > T430
> > > (Intel HD 4000 + discrete NVIDIA NVS 5400M / GF108 chipset)
> > > connected
> > > to a Lenovo ThinkPad Mini Dock Plus Series 3 (Model 4338) with
> > > dual
> > > external monitors on dock DisplayPorts (`DP-2` and `DP-3`).
> > >
> > > #### 2. Root Cause Analysis (Code Level)
> > > Inside `drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c`, when
> > > `ior-
> > > > dp.nr == 0`, the driver executes the following lines in
> > > `nv50_disp_super_2_2_dp()`:
> > >
> > > *   **Line 1179:** Calculations for horizontal blank symbols:
> > >     ```c
> > >     h = h - (3 * ior->dp.ef) - (12 / ior->dp.nr);
> > >     ```
> > > *   **Line 1185:** Calculations for vertical blank symbols:
> > >     ```c
> > >     v = v - ((36 / ior->dp.nr) + 3) - 1;
> > >     ```
> > > *   **Line 1190:** Calculations for active link data rate:
> > >     ```c
> > >     link_data_rate = (khz * head->asy.or.depth / 8) / ior->dp.nr;
> > >     ```
> > >
> > > In all three instances, the denominator `ior->dp.nr` is `0`,
> > > causing
> > > an immediate kernel math exception in the display workqueue
> > > (`nouveau_display_hpd_work` or `nv50_disp_super`), locking up the
> > > graphics pipeline, freezing the screen, and hanging any reboot
> > > attempts (requiring a physical hard reset).
> > >
> > > #### 3. Daily Workday Trigger Scenario
> > > 1.  The T430 (via dock) and a secondary workstation are both
> > > connected to different inputs on the same external monitors.
> > > 2.  The monitors' input sources are switched to the secondary
> > > workstation.
> > > 3.  At the end of the day, the secondary workstation goes to
> > > sleep or
> > > is shut down.
> > > 4.  With no active inputs, the external monitors enter power-
> > > saving
> > > standby mode.
> > > 5.  When the user subsequently wakes up or interacts with the
> > > T430,
> > > the driver attempts a modeset refresh. Since the monitors are
> > > asleep
> > > and inputs are switched away, the lanes count is read as `0`,
> > > triggering the division-by-zero oops.
> > >
> > > #### 4. Stack Trace (Oops Log)
> > > ```text
> > > [di jul 23 10:28:25 2024] nouveau 0000:01:00.0: DRM: DDC
> > > responded,
> > > but no EDID for DP-2
> > > [di jul 23 10:28:25 2024] nouveau 0000:01:00.0: DRM: DDC
> > > responded,
> > > but no EDID for DP-3
> > > [di jul 23 10:28:26 2024] Workqueue: nvkm-disp nv50_disp_super
> > > [nouveau]
> > > [di jul 23 10:28:26 2024] RIP:
> > > 0010:nv50_disp_super_2_2_dp+0xa7/0x320
> > > [nouveau]
> > > [di jul 23 10:28:26 2024]  ? nv50_disp_super_2_2_dp+0xa7/0x320
> > > [nouveau]
> > > [di jul 23 10:28:26 2024]  nv50_disp_super_2_2+0xf2/0x160
> > > [nouveau]
> > > [di jul 23 10:28:26 2024]  nv50_disp_super+0x139/0x280 [nouveau]
> > > ```
> > >
> > > #### 5. Verification Proof (Bypass Log)
> > > With the patch applied and running on kernel `7.1.3-custom #2`,
> > > the
> > > screen standby transition was bypassed cleanly. The driver caught
> > > the
> > > lane count of 0, bypassed display configuration, and proceeded to
> > > put
> > > the graphics chip into its low-power runtime suspend state
> > > (`suspended`) without locking up the OS:
> > > ```text
> > > [45162.232371] nouveau 0000:01:00.0: disp: DP link is inactive
> > > (lanes
> > > = 0), skipping display configuration.
> > > [45162.232402] nouveau 0000:01:00.0: disp: DP link is inactive
> > > (lanes
> > > = 0), skipping display configuration.
> > > [45178.605627] nouveau 0000:01:00.0: timeout
> > > [45178.605629] WARNING: nv50_disp_dmac_fini+0x168/0x1c0 [nouveau]
> > > [45180.608524] nouveau 0000:01:00.0: disp: ch 1 fini timeout,
> > > 8e061008
> > > [45182.608521] nouveau 0000:01:00.0: timeout
> > > [45182.608523] WARNING: nv50_disp_core_fini+0x143/0x190 [nouveau]
> > > ```
> > >
> > > ---
> > >
> > > Signed-off-by: Arjan Filius <iafilius@xxxxxxxxx>
> > > ---
> > >
> > > ### CHOOSE ONE OF THE PATCH OPTIONS BELOW TO APPEND:
> > >
> > > #### OPTION A: The Silent Production Patch (RECOMMENDED FOR
> > > UPSTREAM)
> > > *Note: Kernel maintainers generally reject warnings or print spam
> > > for
> > > expected hardware sleep/disconnect transitions. A silent return
> > > is
> > > the preferred upstream pattern.*
> > >
> > > ```diff
> > > diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > index 51c045bdfcbb..ab1bc0898def 100644
> > > --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > @@ -1157,6 +1157,10 @@ nv50_disp_super_2_2_dp(struct nvkm_head
> > > *head,
> > > struct nvkm_ior *ior)
> > >  {
> > >   struct nvkm_subdev *subdev = &head->disp->engine.subdev;
> > >  
> > > + /* If the DisplayPort link is inactive (0 lanes), skip
> > > configuration */
> > > + if (ior->dp.nr == 0)
> > > + return;
> > > +
> > >   const u32 khz = head->asy.hz / 1000;
> > >   const u32 linkKBps = ior->dp.bw * 27000;
> > >   const u32 symbol = 100000;
> > > ```
> > >
> > > #### OPTION B: The Warning/Verbose Patch (What was tested
> > > locally)
> > > *Note: Prints warning logs to the kernel buffer for validation
> > > and
> > > troubleshooting.*
> > >
> > > ```diff
> > > diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > index 51c045bdfcbb..ab1bc0898def 100644
> > > --- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/nv50.c
> > > @@ -1157,6 +1157,12 @@ nv50_disp_super_2_2_dp(struct nvkm_head
> > > *head,
> > > struct nvkm_ior *ior)
> > >  {
> > >   struct nvkm_subdev *subdev = &head->disp->engine.subdev;
> > >  
> > > + /* If the DisplayPort link is inactive (0 lanes), skip
> > > configuration */
> > > + if (ior->dp.nr == 0) {
> > > + nvkm_warn(subdev, "DP link is inactive (lanes =
> > > 0),
> > > skipping display configuration.\n");
> > > + return;
> > > + }
> > > +
> > >   const u32 khz = head->asy.hz / 1000;
> > >   const u32 linkKBps = ior->dp.bw * 27000;
> > >   const u32 symbol = 100000;
> > > ```
> >