[PATCH 1/5] drm/nouveau/kms/nv50-: reject DP modesets the link can't carry

From: David R. Hedges via B4 Relay

Date: Tue Nov 03 2026 - 15:06:09 EST


From: "David R. Hedges" <linuxdev844@xxxxxxxxxxxxxxxx>

nv50_outp_atomic_fix_depth() lowers the bpc of a DP mode until it fits
the link's capabilities, but carries on at 6 bpc if it still doesn't.
nouveau_dp_train() then finds no lane count and link rate fast enough,
trains nothing, and the output is enabled anyway.

Modes are checked against the link when they're probed, so this takes
a sink whose capabilities have dropped since, or that couldn't be
probed at all. An IGM L28TN4K on a GA102 (GSP-RM 570.144) manages
both: for a moment after being switched on with its power button it
advertises DPCD 1.1 and HBR rather than DPCD 1.2 and HBR2, and the
3840x2160@60 mode it had been running needs 1336500 kB/s even at
6 bpc, against 1080000 kB/s for 4 x HBR; and while it's switched off
it keeps HPD asserted but doesn't answer AUX, which leaves its link
capabilities at zero. Enabling the output in either state ended in
"core notifier timeout", after which GSP stopped answering RPCs.

Fail the modeset in that case, so that userspace can pick a mode that
fits. A commit that only turns the CRTC back on mustn't fail (see
struct drm_crtc_state), so that case is left to the commit path, which
the next patch deals with.

Link: https://gitlab.freedesktop.org/drm/nouveau/-/work_items/515
Signed-off-by: David R. Hedges <linuxdev844@xxxxxxxxxxxxxxxx>
---
drivers/gpu/drm/nouveau/dispnv50/disp.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 7daecaecccac..8af01de822d5 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -373,7 +373,8 @@ nv50_outp_atomic_check_view(struct drm_encoder *encoder,
return 0;
}

-static void
+/* Returns false if the link can't carry the mode even at the lowest bpc. */
+static bool
nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state)
{
struct nv50_head_atom *asyh = nv50_head_atom(crtc_state);
@@ -389,17 +390,26 @@ nv50_outp_atomic_fix_depth(struct drm_encoder *encoder, struct drm_crtc_state *c
asyh->or.bpc = min_t(u8, asyh->or.bpc, 10);

/* reduce the bpc until it works out */
- while (asyh->or.bpc > 6) {
+ for (;;) {
mode_rate = DIV_ROUND_UP(mode->clock * asyh->or.bpc * 3, 8);
if (mode_rate <= max_rate)
break;

+ if (asyh->or.bpc <= 6) {
+ drm_dbg_kms(encoder->dev,
+ "[ENCODER:%d:%s] mode needs %u kB/s, link carries %u kB/s\n",
+ encoder->base.id, encoder->name, mode_rate, max_rate);
+ return false;
+ }
+
asyh->or.bpc -= 2;
}
break;
default:
break;
}
+
+ return true;
}

static int
@@ -423,8 +433,13 @@ nv50_outp_atomic_check(struct drm_encoder *encoder,
if (crtc_state->mode_changed || crtc_state->connectors_changed)
asyh->or.bpc = connector->display_info.bpc ?: 8;

- /* We might have to reduce the bpc */
- nv50_outp_atomic_fix_depth(encoder, crtc_state);
+ /* We might have to reduce the bpc. If the link can't carry the mode
+ * at all, fail a modeset, but not a plain DPMS on, which userspace
+ * expects to always succeed.
+ */
+ if (!nv50_outp_atomic_fix_depth(encoder, crtc_state) &&
+ (crtc_state->mode_changed || crtc_state->connectors_changed))
+ return -EINVAL;

return 0;
}

--
2.55.0