From: lyude@redhat.com
To: Marek Czernohous <mczernohous@gmail.com>,
nouveau@lists.freedesktop.org, dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org, Danilo Krummrich <dakr@kernel.org>,
Simona Vetter <simona@ffwll.ch>
Subject: Re: [PATCH v4 2/2] drm/nouveau/kms: guard NULL crtc in nv50_sor_atomic_disable()
Date: Tue, 18 Aug 2026 16:44:08 -0400 [thread overview]
Message-ID: <e428a3e8d4c0500c112d8deb756fdec884790623.camel@redhat.com> (raw)
In-Reply-To: <178688574402.522643.5471719764843371376@gmail.com>
This isn't correct
On Sun, 2026-08-16 at 15:09 +0200, Marek Czernohous wrote:
> From: Marek Czernohous <marek@czernohous.de>
>
> nv50_sor_atomic_disable() unconditionally computes
> nv50_head(nv_encoder->crtc) and dereferences the result a few lines
> later. nv_encoder->crtc is nouveau's own shadow pointer, set in
> .atomic_enable and cleared at the end of .atomic_disable.
> Commit f575f2bdb6c3 ("drm/nouveau/kms/nv50-: Remove (nv_encoder-
> >crtc)
> checks in ->disable callbacks") removed the NULL check here,
> reasoning that the atomic helpers never call ->disable without a
> crtc. On NVAC (MCP79) under Wayland sessions (observed with Weston's
> DRM backend and with labwc/wlroots) we have hit the NULL case in
> practice during session teardown and VT switches: disable runs
> without
> (or after) its matching enable, and because nv50_head() is
> container_of(), the NULL does not stay NULL but becomes a bogus
> non-NULL pointer, so the subsequent head dereferences fault and the
> kernel oopses.
In the future you should probably put a backtrace if you've seen this
in the wild
>
> Restore the guard, as drm_WARN_ON_ONCE() instead of a silent return:
> a NULL crtc here still indicates a state-tracking inconsistency that
> should stay visible. Return without touching the output; in this
> path
> either enable never ran (nothing to tear down) or an earlier disable
> already did the teardown, and the encoder release is handled by the
> commit_tail release loop in both cases. (That loop then rejects the
> release of a never-acquired output with -EINVAL in the nvif layer,
> which is harmless; the vanilla code oopsed before ever reaching it.)
>
> The same inconsistent-state path can also leave the encoder without
> an
> old connector state, in which case nv50_outp_get_old_connector()
> returns NULL while the backlight teardown dereferenced it
> unconditionally, so the oops would only have moved there. Hoist the
> guard above all of that and look at the old connector only after
> checking it.
>
> Fixes: f575f2bdb6c3 ("drm/nouveau/kms/nv50-: Remove (nv_encoder-
> >crtc) checks in ->disable callbacks")
> Cc: <stable@vger.kernel.org>
> Tested-by: Fab Stz <fabstz-it@yahoo.fr>
> Assisted-by: Claude:claude-opus-4-7
> Assisted-by: Claude:claude-opus-4-8
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Marek Czernohous <marek@czernohous.de>
> ---
> drivers/gpu/drm/nouveau/dispnv50/disp.c | 30 ++++++++++++++++++++---
> --
> 1 file changed, 25 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> index 364227f5456f..f532b0ed8880 100644
> --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> @@ -1565,16 +1565,36 @@ static void
> nv50_sor_atomic_disable(struct drm_encoder *encoder, struct
> drm_atomic_commit *state)
> {
> struct nouveau_encoder *nv_encoder =
> nouveau_encoder(encoder);
> - struct nv50_head *head = nv50_head(nv_encoder->crtc);
> + struct nv50_head *head;
> #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
> - struct nouveau_connector *nv_connector =
> nv50_outp_get_old_connector(state, nv_encoder);
> + struct nouveau_connector *nv_connector;
> struct nouveau_drm *drm = nouveau_drm(nv_encoder-
> >base.base.dev);
> - struct nouveau_backlight *backlight = nv_connector-
> >backlight;
> - struct drm_dp_aux *aux = &nv_connector->aux;
> + struct nouveau_backlight *backlight;
> int ret;
> +#endif
>
> + /* nv_encoder->crtc is the driver's shadow pointer, set in
It's actually an outdated variable from the pre-atomic days
> + * .atomic_enable (and by the boot-time hardware readback)
> and
We don't set nv_encoder->crtc at the boot-time hardware state readback.
> + * cleared at the end of this function. NULL here
> + * means disable-without-enable or a double disable; bail
> before
> + * container_of() turns it into a bogus head pointer
> (checking the
> + * result would not work, container_of(NULL) is never
> NULL). The
> + * encoder release is handled by the commit_tail release
> loop, so
> + * there is nothing to clean up here.
> + */
this is way too verbose
> + if (drm_WARN_ON_ONCE(encoder->dev, !nv_encoder->crtc))
> + return;
> + head = nv50_head(nv_encoder->crtc);
this isn't the bug, the bug is that we're reading nv_encoder->crtc at
all here.
> +
> +#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
> + /* The same inconsistent-state path can leave us without an
> old
> + * connector state, so check before touching it.
> + */
> + nv_connector = nv50_outp_get_old_connector(state,
> nv_encoder);
> + backlight = nv_connector ? nv_connector->backlight : NULL;
> if (backlight && backlight->uses_dpcd) {
> - ret = drm_edp_backlight_disable(aux, &backlight-
> >edp_info);
> + ret = drm_edp_backlight_disable(&nv_connector->aux,
> + &backlight-
> >edp_info);
This doesn't need changing either if we just fix where the encoder came
from.
I'll send out proper fixes for this in a moment
> if (ret < 0)
> NV_ERROR(drm, "Failed to disable backlight
> on [CONNECTOR:%d:%s]: %d\n",
> nv_connector->base.base.id,
> nv_connector->base.name, ret);
WARNING: multiple messages have this Message-ID (diff)
From: lyude@redhat.com
To: Marek Czernohous <mczernohous@gmail.com>,
nouveau@lists.freedesktop.org, dri-devel@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org, Danilo Krummrich <dakr@kernel.org>,
David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>
Subject: Re: [PATCH v4 2/2] drm/nouveau/kms: guard NULL crtc in nv50_sor_atomic_disable()
Date: Tue, 18 Aug 2026 16:44:08 -0400 [thread overview]
Message-ID: <e428a3e8d4c0500c112d8deb756fdec884790623.camel@redhat.com> (raw)
In-Reply-To: <178688574402.522643.5471719764843371376@gmail.com>
This isn't correct
On Sun, 2026-08-16 at 15:09 +0200, Marek Czernohous wrote:
> From: Marek Czernohous <marek@czernohous.de>
>
> nv50_sor_atomic_disable() unconditionally computes
> nv50_head(nv_encoder->crtc) and dereferences the result a few lines
> later. nv_encoder->crtc is nouveau's own shadow pointer, set in
> .atomic_enable and cleared at the end of .atomic_disable.
> Commit f575f2bdb6c3 ("drm/nouveau/kms/nv50-: Remove (nv_encoder-
> >crtc)
> checks in ->disable callbacks") removed the NULL check here,
> reasoning that the atomic helpers never call ->disable without a
> crtc. On NVAC (MCP79) under Wayland sessions (observed with Weston's
> DRM backend and with labwc/wlroots) we have hit the NULL case in
> practice during session teardown and VT switches: disable runs
> without
> (or after) its matching enable, and because nv50_head() is
> container_of(), the NULL does not stay NULL but becomes a bogus
> non-NULL pointer, so the subsequent head dereferences fault and the
> kernel oopses.
In the future you should probably put a backtrace if you've seen this
in the wild
>
> Restore the guard, as drm_WARN_ON_ONCE() instead of a silent return:
> a NULL crtc here still indicates a state-tracking inconsistency that
> should stay visible. Return without touching the output; in this
> path
> either enable never ran (nothing to tear down) or an earlier disable
> already did the teardown, and the encoder release is handled by the
> commit_tail release loop in both cases. (That loop then rejects the
> release of a never-acquired output with -EINVAL in the nvif layer,
> which is harmless; the vanilla code oopsed before ever reaching it.)
>
> The same inconsistent-state path can also leave the encoder without
> an
> old connector state, in which case nv50_outp_get_old_connector()
> returns NULL while the backlight teardown dereferenced it
> unconditionally, so the oops would only have moved there. Hoist the
> guard above all of that and look at the old connector only after
> checking it.
>
> Fixes: f575f2bdb6c3 ("drm/nouveau/kms/nv50-: Remove (nv_encoder-
> >crtc) checks in ->disable callbacks")
> Cc: <stable@vger.kernel.org>
> Tested-by: Fab Stz <fabstz-it@yahoo.fr>
> Assisted-by: Claude:claude-opus-4-7
> Assisted-by: Claude:claude-opus-4-8
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Marek Czernohous <marek@czernohous.de>
> ---
> drivers/gpu/drm/nouveau/dispnv50/disp.c | 30 ++++++++++++++++++++---
> --
> 1 file changed, 25 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> index 364227f5456f..f532b0ed8880 100644
> --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> @@ -1565,16 +1565,36 @@ static void
> nv50_sor_atomic_disable(struct drm_encoder *encoder, struct
> drm_atomic_commit *state)
> {
> struct nouveau_encoder *nv_encoder =
> nouveau_encoder(encoder);
> - struct nv50_head *head = nv50_head(nv_encoder->crtc);
> + struct nv50_head *head;
> #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
> - struct nouveau_connector *nv_connector =
> nv50_outp_get_old_connector(state, nv_encoder);
> + struct nouveau_connector *nv_connector;
> struct nouveau_drm *drm = nouveau_drm(nv_encoder-
> >base.base.dev);
> - struct nouveau_backlight *backlight = nv_connector-
> >backlight;
> - struct drm_dp_aux *aux = &nv_connector->aux;
> + struct nouveau_backlight *backlight;
> int ret;
> +#endif
>
> + /* nv_encoder->crtc is the driver's shadow pointer, set in
It's actually an outdated variable from the pre-atomic days
> + * .atomic_enable (and by the boot-time hardware readback)
> and
We don't set nv_encoder->crtc at the boot-time hardware state readback.
> + * cleared at the end of this function. NULL here
> + * means disable-without-enable or a double disable; bail
> before
> + * container_of() turns it into a bogus head pointer
> (checking the
> + * result would not work, container_of(NULL) is never
> NULL). The
> + * encoder release is handled by the commit_tail release
> loop, so
> + * there is nothing to clean up here.
> + */
this is way too verbose
> + if (drm_WARN_ON_ONCE(encoder->dev, !nv_encoder->crtc))
> + return;
> + head = nv50_head(nv_encoder->crtc);
this isn't the bug, the bug is that we're reading nv_encoder->crtc at
all here.
> +
> +#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
> + /* The same inconsistent-state path can leave us without an
> old
> + * connector state, so check before touching it.
> + */
> + nv_connector = nv50_outp_get_old_connector(state,
> nv_encoder);
> + backlight = nv_connector ? nv_connector->backlight : NULL;
> if (backlight && backlight->uses_dpcd) {
> - ret = drm_edp_backlight_disable(aux, &backlight-
> >edp_info);
> + ret = drm_edp_backlight_disable(&nv_connector->aux,
> + &backlight-
> >edp_info);
This doesn't need changing either if we just fix where the encoder came
from.
I'll send out proper fixes for this in a moment
> if (ret < 0)
> NV_ERROR(drm, "Failed to disable backlight
> on [CONNECTOR:%d:%s]: %d\n",
> nv_connector->base.base.id,
> nv_connector->base.name, ret);
next prev parent reply other threads:[~2026-08-18 20:44 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-16 13:09 [PATCH v4 0/2] drm/nouveau: NVAC (MCP79) stability: MSI rearm and a NULL crtc guard Marek Czernohous
2026-08-16 13:09 ` Marek Czernohous
2026-08-16 13:09 ` [PATCH v4 1/2] drm/nouveau/pci: use config-space MSI rearm on MCP79/MCP7A (NVAC) Marek Czernohous
2026-08-16 13:09 ` Marek Czernohous
2026-08-18 20:28 ` lyude
2026-08-18 20:28 ` lyude
2026-08-16 13:09 ` [PATCH v4 2/2] drm/nouveau/kms: guard NULL crtc in nv50_sor_atomic_disable() Marek Czernohous
2026-08-16 13:09 ` Marek Czernohous
2026-08-16 13:17 ` sashiko-bot
2026-08-16 16:49 ` Marek Czernohous
2026-08-16 16:49 ` Marek Czernohous
2026-08-18 20:44 ` lyude [this message]
2026-08-18 20:44 ` lyude
2026-08-18 22:32 ` lyude
2026-08-18 22:32 ` lyude
2026-08-19 0:02 ` [PATCH v4 0/2] drm/nouveau: NVAC (MCP79) stability: MSI rearm and a NULL crtc guard lyude
2026-08-19 0:02 ` lyude
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=e428a3e8d4c0500c112d8deb756fdec884790623.camel@redhat.com \
--to=lyude@redhat.com \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mczernohous@gmail.com \
--cc=nouveau@lists.freedesktop.org \
--cc=simona@ffwll.ch \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.