From: Marek Czernohous <mczernohous@gmail.com>
To: Lyude Paul <lyude@redhat.com>, Danilo Krummrich <dakr@kernel.org>
Cc: dri-devel@lists.freedesktop.org, nouveau@lists.freedesktop.org,
linux-kernel@vger.kernel.org, Fab Stz <fabstz-it@yahoo.fr>
Subject: [PATCH v3 2/2] drm/nouveau/kms: guard NULL crtc in nv50_sor_atomic_disable()
Date: Thu, 11 Jun 2026 14:45:35 +0200 [thread overview]
Message-ID: <20260611124535.527275-3-mczernohous@gmail.com> (raw)
In-Reply-To: <20260611124535.527275-1-mczernohous@gmail.com>
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.
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
Signed-off-by: Marek Czernohous <marek@czernohous.de>
---
v2 -> v3: hoisted the guard above the CONFIG_DRM_NOUVEAU_BACKLIGHT
block and made the old-connector lookup NULL-safe (reported by the
Sashiko AI review on the v2 posting: the unconditional
nv_connector->backlight initializer ran before the guard); folded
review nits (pointer wording, release-loop note, comment). Tested-by
carried over; the changes stay confined to the inconsistent-state
path, normal-path behavior is unchanged.
v1 -> v2: dropped the nvif_outp_release() call from the early return
(the release is owned by the commit_tail release loop; calling it here
released twice and detached the OR before the disable flush); turned
the silent return into drm_WARN_ON_ONCE(); reworded the commit message.
v2: https://lore.kernel.org/all/66ea307dd4fa080db27b2b9a0caa31b562d72c2b.1781162589.git.marek@czernohous.de/
v1: https://lore.kernel.org/nouveau/20260409172126.115441-3-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 6c3a871..47e15a4 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_state *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
+ * .atomic_enable (and by the boot-time hardware readback) and
+ * 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.
+ */
+ if (drm_WARN_ON_ONCE(encoder->dev, !nv_encoder->crtc))
+ return;
+ head = nv50_head(nv_encoder->crtc);
+
+#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);
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);
--
2.53.0
prev parent reply other threads:[~2026-06-11 12:45 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-11 12:45 [PATCH v3 0/2] drm/nouveau: NVAC (MCP79) MSI rearm + SOR-disable NULL guard Marek Czernohous
2026-06-11 12:45 ` [PATCH v3 1/2] drm/nouveau/pci: use config-space MSI rearm on MCP79/MCP7A (NVAC) Marek Czernohous
2026-06-11 12:45 ` Marek Czernohous [this message]
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=20260611124535.527275-3-mczernohous@gmail.com \
--to=mczernohous@gmail.com \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=fabstz-it@yahoo.fr \
--cc=linux-kernel@vger.kernel.org \
--cc=lyude@redhat.com \
--cc=nouveau@lists.freedesktop.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®