All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/7] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs)
@ 2026-08-14 23:56 ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:56 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

Several fixes for GSP-driven displays consisting of four independent
groups. These were created as part of ongoing HDMI 2.1 and display
handling clean-up work, so some of them are not issues now (e.g., the
2.147GHz pixel clock cap) but fixing them is required to get advanced
features such as FRL, DSC, VRR, etc working properly.

Patches 1-3 fix HDMI infoframes on GB20x. Blackwell removed the legacy
SF vendor-infoframe and GCP packet registers, so the MMIO writes land on
dead or moved offsets. Patch 1 adds a vendor infoframe writer for the new
layout, patch 2 switches GB20x over to it, and patch 3 corrects the GCP
AVMute offsets.

Patch 4 fixes DP modesets on r570 firmware. r570 shrank
NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS, so every call built against the
r535 struct is sent with a wrong paramsSize. Route the .sst/.vcpi paths
through per-version RM API hooks built against each firmware's struct.

Patch 5 fixes vblank interrupts on GB20x. The vblank enable moved to a
second top level FE interrupt enable and fires on a separate low-latency
vector that also needs a retrigger write. Without both, heads don't
report vblanks.

Patch 6 fixes pixel clocks above 2.147GHz on GB20x.
HEAD_SET_PIXEL_CLOCK_FREQUENCY carries 31 bits of Hz, and the CA7D class
adds FREQUENCY_HI for the upper bits. Program it instead of truncating
the clock.

Patch 7 converts nouveau's EDID handling to struct drm_edid, reading
through drm_edid_read_custom() on the GSP path. HDMI 2.1 sinks that
declare their real extension-block count through the EEODB no longer have
their EDIDs truncated to the base block's count.

Mohamed Ahmed (7):
  nouveau/disp: add GB20x HDMI vendor infoframe writer
  nouveau/gsp: fix HDMI vendor infoframes on GB20x
  nouveau/gsp: fix HDMI GCP AVMute register offsets on GB20x
  nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware
  nouveau/gsp: fix vblank interrupts on GB20x
  nouveau/dispnv50: program pixel clocks above 2.147GHz on GB20x
  nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid

 drivers/gpu/drm/nouveau/dispnv50/disp.c       |   2 +-
 drivers/gpu/drm/nouveau/dispnv50/headca7d.c   |  21 ++-
 .../drm/nouveau/include/nvhw/class/clca7d.h   |   4 +
 drivers/gpu/drm/nouveau/nouveau_connector.c   |  95 ++++++++++---
 drivers/gpu/drm/nouveau/nouveau_connector.h   |   7 +-
 .../gpu/drm/nouveau/nvkm/engine/disp/Kbuild   |   1 +
 .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c  |  62 ++++++++
 .../gpu/drm/nouveau/nvkm/engine/disp/ior.h    |   2 +
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 132 +++++++++++++++++-
 .../nouveau/nvkm/subdev/gsp/rm/r570/disp.c    |  64 +++++++++
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c |   8 ++
 .../nvkm/subdev/gsp/rm/r570/nvrm/disp.h       |   2 +
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |   5 +
 13 files changed, 371 insertions(+), 34 deletions(-)
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 0/7] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs)
@ 2026-08-14 23:56 ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:56 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

Several fixes for GSP-driven displays consisting of four independent
groups. These were created as part of ongoing HDMI 2.1 and display
handling clean-up work, so some of them are not issues now (e.g., the
2.147GHz pixel clock cap) but fixing them is required to get advanced
features such as FRL, DSC, VRR, etc working properly.

Patches 1-3 fix HDMI infoframes on GB20x. Blackwell removed the legacy
SF vendor-infoframe and GCP packet registers, so the MMIO writes land on
dead or moved offsets. Patch 1 adds a vendor infoframe writer for the new
layout, patch 2 switches GB20x over to it, and patch 3 corrects the GCP
AVMute offsets.

Patch 4 fixes DP modesets on r570 firmware. r570 shrank
NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS, so every call built against the
r535 struct is sent with a wrong paramsSize. Route the .sst/.vcpi paths
through per-version RM API hooks built against each firmware's struct.

Patch 5 fixes vblank interrupts on GB20x. The vblank enable moved to a
second top level FE interrupt enable and fires on a separate low-latency
vector that also needs a retrigger write. Without both, heads don't
report vblanks.

Patch 6 fixes pixel clocks above 2.147GHz on GB20x.
HEAD_SET_PIXEL_CLOCK_FREQUENCY carries 31 bits of Hz, and the CA7D class
adds FREQUENCY_HI for the upper bits. Program it instead of truncating
the clock.

Patch 7 converts nouveau's EDID handling to struct drm_edid, reading
through drm_edid_read_custom() on the GSP path. HDMI 2.1 sinks that
declare their real extension-block count through the EEODB no longer have
their EDIDs truncated to the base block's count.

Mohamed Ahmed (7):
  nouveau/disp: add GB20x HDMI vendor infoframe writer
  nouveau/gsp: fix HDMI vendor infoframes on GB20x
  nouveau/gsp: fix HDMI GCP AVMute register offsets on GB20x
  nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware
  nouveau/gsp: fix vblank interrupts on GB20x
  nouveau/dispnv50: program pixel clocks above 2.147GHz on GB20x
  nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid

 drivers/gpu/drm/nouveau/dispnv50/disp.c       |   2 +-
 drivers/gpu/drm/nouveau/dispnv50/headca7d.c   |  21 ++-
 .../drm/nouveau/include/nvhw/class/clca7d.h   |   4 +
 drivers/gpu/drm/nouveau/nouveau_connector.c   |  95 ++++++++++---
 drivers/gpu/drm/nouveau/nouveau_connector.h   |   7 +-
 .../gpu/drm/nouveau/nvkm/engine/disp/Kbuild   |   1 +
 .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c  |  62 ++++++++
 .../gpu/drm/nouveau/nvkm/engine/disp/ior.h    |   2 +
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 132 +++++++++++++++++-
 .../nouveau/nvkm/subdev/gsp/rm/r570/disp.c    |  64 +++++++++
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c |   8 ++
 .../nvkm/subdev/gsp/rm/r570/nvrm/disp.h       |   2 +
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |   5 +
 13 files changed, 371 insertions(+), 34 deletions(-)
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 1/7] nouveau/disp: add GB20x HDMI vendor infoframe writer
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:56   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:56 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

GB20x (NVDisplay 5.0) reorganised the SF HDMI packet units and the
legacy VSI unit at 0x6f0100 no longer exists. Per NVIDIA's published
CA71 DISP_SF_USER class header, only three legacy units remain (AVI
at +0x000, GCP at +0x040, ACR at +0x080), and vendor infoframes must
instead be sent through the shared generic infoframe units at
+0x130, which feed a 9-dword data FIFO at +0x3f0/+0x3f4.

Add a VSI writer using the same programming sequence OpenRM uses on
these chips (nvhdmipkt_C971.c, programAdvancedInfoframeC971()): disable
the unit and wait for it to idle, clear the SENT status, write the packet
through the data FIFO with a zero HB3 inserted after the three header
bytes, then enable the unit for every-frame transmission during vblank.

Generic unit 1 is used for the VSI, matching the slot assignment in
NVIDIA's nvkms (NVHDMIPKT_TYPE_SHARED_GENERIC2, unit 0 is reserved
for extended metadata packets and unit 2 for the HDR DRM infoframe,
if those are wired up later).

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../gpu/drm/nouveau/nvkm/engine/disp/Kbuild   |  1 +
 .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c  | 62 +++++++++++++++++++
 .../gpu/drm/nouveau/nvkm/engine/disp/ior.h    |  2 +
 3 files changed, 65 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c

diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
index e1aecd3fe96c..98d6ca5ac311 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
@@ -27,6 +27,7 @@ nvkm-y += nvkm/engine/disp/gp102.o
 nvkm-y += nvkm/engine/disp/gv100.o
 nvkm-y += nvkm/engine/disp/tu102.o
 nvkm-y += nvkm/engine/disp/ga102.o
+nvkm-y += nvkm/engine/disp/gb202.o
 
 nvkm-y += nvkm/engine/disp/udisp.o
 nvkm-y += nvkm/engine/disp/uconn.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
new file mode 100644
index 000000000000..8fd72bcb6f04
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2026 Valve Corp.
+ */
+#include "priv.h"
+#include "ior.h"
+
+#include <subdev/timer.h>
+
+/* GB20x (NVD5.0) reorganised the SF HDMI packet units. The AVI unit is
+ * unchanged from GV100, but the legacy VSI unit is gone. Vendor infoframes
+ * are sent through the shared generic infoframe units instead. Register
+ * layout per NVIDIA's clc971.h/clca71.h, programming sequence per
+ * nvhdmipkt_C971.c:programAdvancedInfoframeC971().
+ */
+void
+gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
+{
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 hoff = head * 0x400;
+	/* Generic infoframe unit 1, the slot NVIDIA's driver uses for the VSI. */
+	const u32 ctrl = 0x6f0138 + hoff;
+	u8 buf[36] = {};
+	int i;
+
+	/* Disable the unit and wait for it to go idle. */
+	nvkm_mask(device, ctrl, 0x00000001, 0x00000000);
+	if (nvkm_msec(device, 2000,
+		if (!(nvkm_rd32(device, ctrl) & 0x00400000))
+			break;
+	) < 0)
+		return;
+
+	if (!size)
+		return;
+
+	/* Clear SENT status, and point the data FIFO at unit 1's slot. */
+	nvkm_mask(device, ctrl, 0x00800000, 0x00800000);
+	nvkm_wr32(device, 0x6f03f0 + hoff, 0x00000001);
+
+	/* The FIFO takes the raw packet, except that a zero HB3 is inserted
+	 * after the three header bytes. A unit sends 36 bytes.
+	 */
+	size = min_t(u32, size, 31);
+	memcpy(buf, data, min_t(u32, size, 3));
+	if (size > 3)
+		memcpy(&buf[4], (u8 *)data + 3, size - 3);
+
+	for (i = 0; i < 36; i += 4) {
+		nvkm_wr32(device, 0x6f03f4 + hoff, buf[i + 0] | buf[i + 1] << 8 |
+						   buf[i + 2] << 16 | buf[i + 3] << 24);
+	}
+
+	/* No flip ID or scanline matching. */
+	nvkm_wr32(device, 0x6f013c + hoff, 0x00000000);
+
+	/* ENABLE | RUN_MODE=ALWAYS | LOC=VBLANK | OFFSET=1 | SIZE=0. */
+	nvkm_wr32(device, ctrl, 0x00000041);
+
+	/* Audio priority low (the init value). */
+	nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
index 3ba04bead2f9..8906fe8ef404 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
@@ -196,6 +196,8 @@ extern const struct nvkm_ior_func_hda gv100_sor_hda;
 
 void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
 
+void gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *, int, void *, u32);
+
 int nv50_pior_cnt(struct nvkm_disp *, unsigned long *);
 int nv50_pior_new(struct nvkm_disp *, int);
 void nv50_pior_depth(struct nvkm_ior *, struct nvkm_ior_state *, u32 ctrl);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 1/7] nouveau/disp: add GB20x HDMI vendor infoframe writer
@ 2026-08-14 23:56   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:56 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

GB20x (NVDisplay 5.0) reorganised the SF HDMI packet units and the
legacy VSI unit at 0x6f0100 no longer exists. Per NVIDIA's published
CA71 DISP_SF_USER class header, only three legacy units remain (AVI
at +0x000, GCP at +0x040, ACR at +0x080), and vendor infoframes must
instead be sent through the shared generic infoframe units at
+0x130, which feed a 9-dword data FIFO at +0x3f0/+0x3f4.

Add a VSI writer using the same programming sequence OpenRM uses on
these chips (nvhdmipkt_C971.c, programAdvancedInfoframeC971()): disable
the unit and wait for it to idle, clear the SENT status, write the packet
through the data FIFO with a zero HB3 inserted after the three header
bytes, then enable the unit for every-frame transmission during vblank.

Generic unit 1 is used for the VSI, matching the slot assignment in
NVIDIA's nvkms (NVHDMIPKT_TYPE_SHARED_GENERIC2, unit 0 is reserved
for extended metadata packets and unit 2 for the HDR DRM infoframe,
if those are wired up later).

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../gpu/drm/nouveau/nvkm/engine/disp/Kbuild   |  1 +
 .../gpu/drm/nouveau/nvkm/engine/disp/gb202.c  | 62 +++++++++++++++++++
 .../gpu/drm/nouveau/nvkm/engine/disp/ior.h    |  2 +
 3 files changed, 65 insertions(+)
 create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c

diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
index e1aecd3fe96c..98d6ca5ac311 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/Kbuild
@@ -27,6 +27,7 @@ nvkm-y += nvkm/engine/disp/gp102.o
 nvkm-y += nvkm/engine/disp/gv100.o
 nvkm-y += nvkm/engine/disp/tu102.o
 nvkm-y += nvkm/engine/disp/ga102.o
+nvkm-y += nvkm/engine/disp/gb202.o
 
 nvkm-y += nvkm/engine/disp/udisp.o
 nvkm-y += nvkm/engine/disp/uconn.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
new file mode 100644
index 000000000000..8fd72bcb6f04
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2026 Valve Corp.
+ */
+#include "priv.h"
+#include "ior.h"
+
+#include <subdev/timer.h>
+
+/* GB20x (NVD5.0) reorganised the SF HDMI packet units. The AVI unit is
+ * unchanged from GV100, but the legacy VSI unit is gone. Vendor infoframes
+ * are sent through the shared generic infoframe units instead. Register
+ * layout per NVIDIA's clc971.h/clca71.h, programming sequence per
+ * nvhdmipkt_C971.c:programAdvancedInfoframeC971().
+ */
+void
+gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
+{
+	struct nvkm_device *device = ior->disp->engine.subdev.device;
+	const u32 hoff = head * 0x400;
+	/* Generic infoframe unit 1, the slot NVIDIA's driver uses for the VSI. */
+	const u32 ctrl = 0x6f0138 + hoff;
+	u8 buf[36] = {};
+	int i;
+
+	/* Disable the unit and wait for it to go idle. */
+	nvkm_mask(device, ctrl, 0x00000001, 0x00000000);
+	if (nvkm_msec(device, 2000,
+		if (!(nvkm_rd32(device, ctrl) & 0x00400000))
+			break;
+	) < 0)
+		return;
+
+	if (!size)
+		return;
+
+	/* Clear SENT status, and point the data FIFO at unit 1's slot. */
+	nvkm_mask(device, ctrl, 0x00800000, 0x00800000);
+	nvkm_wr32(device, 0x6f03f0 + hoff, 0x00000001);
+
+	/* The FIFO takes the raw packet, except that a zero HB3 is inserted
+	 * after the three header bytes. A unit sends 36 bytes.
+	 */
+	size = min_t(u32, size, 31);
+	memcpy(buf, data, min_t(u32, size, 3));
+	if (size > 3)
+		memcpy(&buf[4], (u8 *)data + 3, size - 3);
+
+	for (i = 0; i < 36; i += 4) {
+		nvkm_wr32(device, 0x6f03f4 + hoff, buf[i + 0] | buf[i + 1] << 8 |
+						   buf[i + 2] << 16 | buf[i + 3] << 24);
+	}
+
+	/* No flip ID or scanline matching. */
+	nvkm_wr32(device, 0x6f013c + hoff, 0x00000000);
+
+	/* ENABLE | RUN_MODE=ALWAYS | LOC=VBLANK | OFFSET=1 | SIZE=0. */
+	nvkm_wr32(device, ctrl, 0x00000041);
+
+	/* Audio priority low (the init value). */
+	nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002);
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
index 3ba04bead2f9..8906fe8ef404 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
@@ -196,6 +196,8 @@ extern const struct nvkm_ior_func_hda gv100_sor_hda;
 
 void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
 
+void gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *, int, void *, u32);
+
 int nv50_pior_cnt(struct nvkm_disp *, unsigned long *);
 int nv50_pior_new(struct nvkm_disp *, int);
 void nv50_pior_depth(struct nvkm_ior *, struct nvkm_ior_state *, u32 ctrl);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 2/7] nouveau/gsp: fix HDMI vendor infoframes on GB20x
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:57   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

The GSP path reuses the GV100 direct-MMIO infoframe writers on every
chip. On GB20x that is only half right as while the legacy SF AVI unit is
unchanged, the legacy VSI unit at 0x6f0100 was removed, so
gv100_sor_hdmi_infoframe_vsi() writes into a reserved area and no vendor
infoframe ever reaches the wire. This affects HDMI-VIC signalling which
can impact some 4K modes for legacy HDMI 1.4 sinks.

Select the SOR functions per GPU family in r535_sor_new(), keyed on
the display root class from the RM GPU table and use the new
generic-infoframe VSI writer on GB20x. Everything else stays on the
r535 table.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 29 ++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index 1155f079b0c3..96febd01da78 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -36,6 +36,8 @@
 
 #include <rm/gpu.h>
 
+#include <nvif/class.h>
+
 #include <nvhw/drf.h>
 
 #include "nvrm/disp.h"
@@ -587,6 +589,16 @@ r535_sor_hdmi = {
 	.audio = r535_sor_hdmi_audio,
 };
 
+static const struct nvkm_ior_func_hdmi
+gb202_sor_hdmi = {
+	.ctrl = r535_sor_hdmi_ctrl,
+	/* The legacy AVI unit is unchanged on GB20x. */
+	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
+	.infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
+	.audio = r535_sor_hdmi_audio,
+	.frl_train = r535_sor_frl_train,
+};
+
 static const struct nvkm_ior_func
 r535_sor = {
 	.hdmi = &r535_sor_hdmi,
@@ -595,10 +607,25 @@ r535_sor = {
 	.bl = &r535_sor_bl,
 };
 
+static const struct nvkm_ior_func
+gb202_sor = {
+	.hdmi = &gb202_sor_hdmi,
+	.dp = &r535_sor_dp,
+	.hda = &r535_sor_hda,
+	.bl = &r535_sor_bl,
+};
+
 static int
 r535_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_ior_new_(&r535_sor, disp, SOR, id, true/*XXX: hda cap*/);
+	const struct nvkm_rm_gpu *gpu = disp->engine.subdev.device->gsp->rm->gpu;
+	const struct nvkm_ior_func *func = &r535_sor;
+
+	/* NVD5.0 (GB20x and later) reorganised the SF HDMI packet units. */
+	if (gpu->disp.class.root >= GB202_DISP)
+		func = &gb202_sor;
+
+	return nvkm_ior_new_(func, disp, SOR, id, true/*XXX: hda cap*/);
 }
 
 static int
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 2/7] nouveau/gsp: fix HDMI vendor infoframes on GB20x
@ 2026-08-14 23:57   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

The GSP path reuses the GV100 direct-MMIO infoframe writers on every
chip. On GB20x that is only half right as while the legacy SF AVI unit is
unchanged, the legacy VSI unit at 0x6f0100 was removed, so
gv100_sor_hdmi_infoframe_vsi() writes into a reserved area and no vendor
infoframe ever reaches the wire. This affects HDMI-VIC signalling which
can impact some 4K modes for legacy HDMI 1.4 sinks.

Select the SOR functions per GPU family in r535_sor_new(), keyed on
the display root class from the RM GPU table and use the new
generic-infoframe VSI writer on GB20x. Everything else stays on the
r535 table.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 29 ++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index 1155f079b0c3..96febd01da78 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -36,6 +36,8 @@
 
 #include <rm/gpu.h>
 
+#include <nvif/class.h>
+
 #include <nvhw/drf.h>
 
 #include "nvrm/disp.h"
@@ -587,6 +589,16 @@ r535_sor_hdmi = {
 	.audio = r535_sor_hdmi_audio,
 };
 
+static const struct nvkm_ior_func_hdmi
+gb202_sor_hdmi = {
+	.ctrl = r535_sor_hdmi_ctrl,
+	/* The legacy AVI unit is unchanged on GB20x. */
+	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
+	.infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
+	.audio = r535_sor_hdmi_audio,
+	.frl_train = r535_sor_frl_train,
+};
+
 static const struct nvkm_ior_func
 r535_sor = {
 	.hdmi = &r535_sor_hdmi,
@@ -595,10 +607,25 @@ r535_sor = {
 	.bl = &r535_sor_bl,
 };
 
+static const struct nvkm_ior_func
+gb202_sor = {
+	.hdmi = &gb202_sor_hdmi,
+	.dp = &r535_sor_dp,
+	.hda = &r535_sor_hda,
+	.bl = &r535_sor_bl,
+};
+
 static int
 r535_sor_new(struct nvkm_disp *disp, int id)
 {
-	return nvkm_ior_new_(&r535_sor, disp, SOR, id, true/*XXX: hda cap*/);
+	const struct nvkm_rm_gpu *gpu = disp->engine.subdev.device->gsp->rm->gpu;
+	const struct nvkm_ior_func *func = &r535_sor;
+
+	/* NVD5.0 (GB20x and later) reorganised the SF HDMI packet units. */
+	if (gpu->disp.class.root >= GB202_DISP)
+		func = &gb202_sor;
+
+	return nvkm_ior_new_(func, disp, SOR, id, true/*XXX: hda cap*/);
 }
 
 static int
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/7] nouveau/gsp: fix HDMI GCP AVMute register offsets on GB20x
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:57   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

r535_sor_hdmi_audio() brackets audio enablement with a General
Control Packet AVMute toggle written through the legacy GCP unit at
0x6f00c0/0x6f00cc. On GB20x the SF packet units were compacted and the
old generic and audio-infoframe units are gone and the GCP unit moved
from slot 3 to slot 1 (control 0x6f0040 and subpack 0x6f004c from
NVIDIA's published clc971.h. The same offsets are also used by OpenRM's
hdmiWriteGeneralCtrlPacketC871() on these chips). The old addresses are
reserved on GB20x, so the AVMute writes were silent no-ops and mitigated
only by the equivalent GCP this function already sends through the
SET_OD_PACKET RM control.

Add a GB20x variant of the audio function using the new offsets,
keeping the direct MMIO path in sync with the hardware as on
earlier chips.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c  | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index 96febd01da78..f51ad7c754d8 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -559,6 +559,21 @@ r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
 	nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
 }
 
+static void
+gb202_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 hdmi = head * 0x400;
+
+	r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable);
+	r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable);
+
+	/* General Control (GCP): the unit moved to slot 1 on NVD5.0. */
+	nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000000);
+	nvkm_wr32(device, 0x6f004c + hdmi, !enable ? 0x00000001 : 0x00000010);
+	nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001);
+}
+
 static void
 r535_sor_hdmi_ctrl(struct nvkm_ior *sor, int head, bool enable, u8 max_ac_packet, u8 rekey)
 {
@@ -595,7 +610,7 @@ gb202_sor_hdmi = {
 	/* The legacy AVI unit is unchanged on GB20x. */
 	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
 	.infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
-	.audio = r535_sor_hdmi_audio,
+	.audio = gb202_sor_hdmi_audio,
 	.frl_train = r535_sor_frl_train,
 };
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/7] nouveau/gsp: fix HDMI GCP AVMute register offsets on GB20x
@ 2026-08-14 23:57   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

r535_sor_hdmi_audio() brackets audio enablement with a General
Control Packet AVMute toggle written through the legacy GCP unit at
0x6f00c0/0x6f00cc. On GB20x the SF packet units were compacted and the
old generic and audio-infoframe units are gone and the GCP unit moved
from slot 3 to slot 1 (control 0x6f0040 and subpack 0x6f004c from
NVIDIA's published clc971.h. The same offsets are also used by OpenRM's
hdmiWriteGeneralCtrlPacketC871() on these chips). The old addresses are
reserved on GB20x, so the AVMute writes were silent no-ops and mitigated
only by the equivalent GCP this function already sends through the
SET_OD_PACKET RM control.

Add a GB20x variant of the audio function using the new offsets,
keeping the direct MMIO path in sync with the hardware as on
earlier chips.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c  | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index 96febd01da78..f51ad7c754d8 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -559,6 +559,21 @@ r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
 	nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
 }
 
+static void
+gb202_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
+{
+	struct nvkm_device *device = sor->disp->engine.subdev.device;
+	const u32 hdmi = head * 0x400;
+
+	r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable);
+	r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable);
+
+	/* General Control (GCP): the unit moved to slot 1 on NVD5.0. */
+	nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000000);
+	nvkm_wr32(device, 0x6f004c + hdmi, !enable ? 0x00000001 : 0x00000010);
+	nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001);
+}
+
 static void
 r535_sor_hdmi_ctrl(struct nvkm_ior *sor, int head, bool enable, u8 max_ac_packet, u8 rekey)
 {
@@ -595,7 +610,7 @@ gb202_sor_hdmi = {
 	/* The legacy AVI unit is unchanged on GB20x. */
 	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
 	.infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
-	.audio = r535_sor_hdmi_audio,
+	.audio = gb202_sor_hdmi_audio,
 	.frl_train = r535_sor_frl_train,
 };
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 4/7] nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:57   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

NVIDIA removed the deprecated actualPclkHz/linkClkFreqHz fields and the
whole Legacy{activeCnt, activeFrac, activePolarity, mvidWarEnabled,
MvidWarParams} block from the SST sub-struct of
NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS between the 535 and 570 releases
(compared in OpenRM tags 535.113.01 vs 570.144), shrinking the struct.

Everything nouveau writes sits at identical offsets in both layouts but
the size is wrong on r570, which means r535_sor_dp_sst() and
r535_sor_dp_vcpi() are sent with an incorrect size.

Route the .sst/.vcpi IOR functions through nvkm_rm_api_disp the same way
bl_ctrl and dp.get_caps/set_indexed_link_rates already are. Keep the
existing implementation for r535 and add an r570 implementation built
against the 570.144 layout, which already exists in r570/nvrm/disp.h but
was unused until now. Also add the NV0073_CTRL_CMD_DP_CONFIG_STREAM
define that was missing from the layout.

Other DP controls sent through shared r535 code did not change layout
between the tags.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 25 +++++++-
 .../nouveau/nvkm/subdev/gsp/rm/r570/disp.c    | 64 +++++++++++++++++++
 .../nvkm/subdev/gsp/rm/r570/nvrm/disp.h       |  2 +
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |  5 ++
 4 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index f51ad7c754d8..dd632767aea5 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -403,7 +403,7 @@ r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
 }
 
 static void
-r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+r535_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
 {
 	struct nvkm_disp *disp = sor->disp;
 	struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
@@ -434,9 +434,17 @@ r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u
 	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
 }
 
+static void
+r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+{
+	const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api;
+
+	rmapi->disp->dp.vcpi(sor, head, slot, slot_nr, pbn, aligned_pbn);
+}
+
 static int
-r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
-		u32 watermark, u32 hblanksym, u32 vblanksym)
+r535_dp_sst(struct nvkm_ior *sor, int head, bool ef,
+	    u32 watermark, u32 hblanksym, u32 vblanksym)
 {
 	struct nvkm_disp *disp = sor->disp;
 	struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
@@ -463,6 +471,15 @@ r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
 	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
 }
 
+static int
+r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
+		u32 watermark, u32 hblanksym, u32 vblanksym)
+{
+	const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api;
+
+	return rmapi->disp->dp.sst(sor, head, ef, watermark, hblanksym, vblanksym);
+}
+
 static const struct nvkm_ior_func_dp
 r535_sor_dp = {
 	.sst = r535_sor_dp_sst,
@@ -1828,6 +1845,8 @@ r535_disp = {
 	.dp = {
 		.get_caps = r535_dp_get_caps,
 		.set_indexed_link_rates = r535_dp_set_indexed_link_rates,
+		.sst = r535_dp_sst,
+		.vcpi = r535_dp_vcpi,
 	},
 	.chan = {
 		.set_pushbuf = r535_disp_chan_set_pushbuf,
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
index a96e31c2d80b..61f82e15ff39 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
@@ -5,6 +5,7 @@
 #include <rm/rm.h>
 
 #include <engine/disp.h>
+#include <engine/disp/ior.h>
 #include <engine/disp/outp.h>
 
 #include "nvhw/drf.h"
@@ -74,6 +75,67 @@ r570_disp_chan_set_pushbuf(struct nvkm_disp *disp, s32 oclass, int inst, struct
 	return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl);
 }
 
+static void
+r570_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+{
+	struct nvkm_disp *disp = sor->disp;
+	NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
+
+	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
+				    NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
+	if (WARN_ON(IS_ERR(ctrl)))
+		return;
+
+	ctrl->subDeviceInstance = 0;
+	ctrl->head = head;
+	ctrl->sorIndex = sor->id;
+	ctrl->dpLink = sor->asy.link == 2;
+	ctrl->bEnableOverride = 1;
+	ctrl->bMST = 1;
+	ctrl->hBlankSym = 0;
+	ctrl->vBlankSym = 0;
+	ctrl->colorFormat = 0;
+	ctrl->bEnableTwoHeadOneOr = 0;
+	ctrl->singleHeadMultistreamMode = 0;
+	ctrl->MST.slotStart = slot;
+	ctrl->MST.slotEnd = slot + slot_nr - 1;
+	ctrl->MST.PBN = pbn;
+	ctrl->MST.Timeslice = aligned_pbn;
+	ctrl->MST.sendACT = 0;
+	ctrl->MST.singleHeadMSTPipeline = 0;
+	ctrl->MST.bEnableAudioOverRightPanel = 0;
+	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
+}
+
+static int
+r570_dp_sst(struct nvkm_ior *sor, int head, bool ef,
+	    u32 watermark, u32 hblanksym, u32 vblanksym)
+{
+	struct nvkm_disp *disp = sor->disp;
+	NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
+
+	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
+				    NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
+	if (IS_ERR(ctrl))
+		return PTR_ERR(ctrl);
+
+	ctrl->subDeviceInstance = 0;
+	ctrl->head = head;
+	ctrl->sorIndex = sor->id;
+	ctrl->dpLink = sor->asy.link == 2;
+	ctrl->bEnableOverride = 1;
+	ctrl->bMST = 0;
+	ctrl->hBlankSym = hblanksym;
+	ctrl->vBlankSym = vblanksym;
+	ctrl->colorFormat = 0;
+	ctrl->bEnableTwoHeadOneOr = 0;
+	ctrl->SST.bEnhancedFraming = ef;
+	ctrl->SST.tuSize = 64;
+	ctrl->SST.waterMark = watermark;
+	ctrl->SST.bEnableAudioOverRightPanel = 0;
+	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
+}
+
 static int
 r570_dp_set_indexed_link_rates(struct nvkm_outp *outp)
 {
@@ -255,6 +317,8 @@ r570_disp = {
 	.dp = {
 		.get_caps = r570_dp_get_caps,
 		.set_indexed_link_rates = r570_dp_set_indexed_link_rates,
+		.sst = r570_dp_sst,
+		.vcpi = r570_dp_vcpi,
 	},
 	.chan = {
 		.set_pushbuf = r570_disp_chan_set_pushbuf,
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h
index 06e972835d77..742b25a2a12d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h
@@ -256,6 +256,8 @@ typedef struct NV0073_CTRL_DP_CTRL_PARAMS {
     NvU32 eightLaneDpcdBaseAddr;
 } NV0073_CTRL_DP_CTRL_PARAMS;
 
+#define NV0073_CTRL_CMD_DP_CONFIG_STREAM                   (0x731362U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_DP_INTERFACE_ID << 8) | NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS_MESSAGE_ID" */
+
 typedef struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS {
     NvU32  subDeviceInstance;
     NvU32  head;
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
index a9af94adf9ef..e0c10a7e3452 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
@@ -6,6 +6,7 @@
 #ifndef __NVKM_RM_H__
 #define __NVKM_RM_H__
 #include "handles.h"
+struct nvkm_ior;
 struct nvkm_outp;
 struct r535_gr;
 
@@ -93,6 +94,10 @@ struct nvkm_rm_api {
 		struct {
 			int (*get_caps)(struct nvkm_disp *, int *link_bw, bool *mst, bool *wm);
 			int (*set_indexed_link_rates)(struct nvkm_outp *);
+			int (*sst)(struct nvkm_ior *, int head, bool ef,
+				   u32 watermark, u32 hblanksym, u32 vblanksym);
+			void (*vcpi)(struct nvkm_ior *, int head,
+				     u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn);
 		} dp;
 
 		struct {
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 4/7] nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware
@ 2026-08-14 23:57   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

NVIDIA removed the deprecated actualPclkHz/linkClkFreqHz fields and the
whole Legacy{activeCnt, activeFrac, activePolarity, mvidWarEnabled,
MvidWarParams} block from the SST sub-struct of
NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS between the 535 and 570 releases
(compared in OpenRM tags 535.113.01 vs 570.144), shrinking the struct.

Everything nouveau writes sits at identical offsets in both layouts but
the size is wrong on r570, which means r535_sor_dp_sst() and
r535_sor_dp_vcpi() are sent with an incorrect size.

Route the .sst/.vcpi IOR functions through nvkm_rm_api_disp the same way
bl_ctrl and dp.get_caps/set_indexed_link_rates already are. Keep the
existing implementation for r535 and add an r570 implementation built
against the 570.144 layout, which already exists in r570/nvrm/disp.h but
was unused until now. Also add the NV0073_CTRL_CMD_DP_CONFIG_STREAM
define that was missing from the layout.

Other DP controls sent through shared r535 code did not change layout
between the tags.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 25 +++++++-
 .../nouveau/nvkm/subdev/gsp/rm/r570/disp.c    | 64 +++++++++++++++++++
 .../nvkm/subdev/gsp/rm/r570/nvrm/disp.h       |  2 +
 .../gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h   |  5 ++
 4 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index f51ad7c754d8..dd632767aea5 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -403,7 +403,7 @@ r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
 }
 
 static void
-r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+r535_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
 {
 	struct nvkm_disp *disp = sor->disp;
 	struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
@@ -434,9 +434,17 @@ r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u
 	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
 }
 
+static void
+r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+{
+	const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api;
+
+	rmapi->disp->dp.vcpi(sor, head, slot, slot_nr, pbn, aligned_pbn);
+}
+
 static int
-r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
-		u32 watermark, u32 hblanksym, u32 vblanksym)
+r535_dp_sst(struct nvkm_ior *sor, int head, bool ef,
+	    u32 watermark, u32 hblanksym, u32 vblanksym)
 {
 	struct nvkm_disp *disp = sor->disp;
 	struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
@@ -463,6 +471,15 @@ r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
 	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
 }
 
+static int
+r535_sor_dp_sst(struct nvkm_ior *sor, int head, bool ef,
+		u32 watermark, u32 hblanksym, u32 vblanksym)
+{
+	const struct nvkm_rm_api *rmapi = sor->disp->engine.subdev.device->gsp->rm->api;
+
+	return rmapi->disp->dp.sst(sor, head, ef, watermark, hblanksym, vblanksym);
+}
+
 static const struct nvkm_ior_func_dp
 r535_sor_dp = {
 	.sst = r535_sor_dp_sst,
@@ -1828,6 +1845,8 @@ r535_disp = {
 	.dp = {
 		.get_caps = r535_dp_get_caps,
 		.set_indexed_link_rates = r535_dp_set_indexed_link_rates,
+		.sst = r535_dp_sst,
+		.vcpi = r535_dp_vcpi,
 	},
 	.chan = {
 		.set_pushbuf = r535_disp_chan_set_pushbuf,
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
index a96e31c2d80b..61f82e15ff39 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
@@ -5,6 +5,7 @@
 #include <rm/rm.h>
 
 #include <engine/disp.h>
+#include <engine/disp/ior.h>
 #include <engine/disp/outp.h>
 
 #include "nvhw/drf.h"
@@ -74,6 +75,67 @@ r570_disp_chan_set_pushbuf(struct nvkm_disp *disp, s32 oclass, int inst, struct
 	return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl);
 }
 
+static void
+r570_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+{
+	struct nvkm_disp *disp = sor->disp;
+	NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
+
+	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
+				    NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
+	if (WARN_ON(IS_ERR(ctrl)))
+		return;
+
+	ctrl->subDeviceInstance = 0;
+	ctrl->head = head;
+	ctrl->sorIndex = sor->id;
+	ctrl->dpLink = sor->asy.link == 2;
+	ctrl->bEnableOverride = 1;
+	ctrl->bMST = 1;
+	ctrl->hBlankSym = 0;
+	ctrl->vBlankSym = 0;
+	ctrl->colorFormat = 0;
+	ctrl->bEnableTwoHeadOneOr = 0;
+	ctrl->singleHeadMultistreamMode = 0;
+	ctrl->MST.slotStart = slot;
+	ctrl->MST.slotEnd = slot + slot_nr - 1;
+	ctrl->MST.PBN = pbn;
+	ctrl->MST.Timeslice = aligned_pbn;
+	ctrl->MST.sendACT = 0;
+	ctrl->MST.singleHeadMSTPipeline = 0;
+	ctrl->MST.bEnableAudioOverRightPanel = 0;
+	WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
+}
+
+static int
+r570_dp_sst(struct nvkm_ior *sor, int head, bool ef,
+	    u32 watermark, u32 hblanksym, u32 vblanksym)
+{
+	struct nvkm_disp *disp = sor->disp;
+	NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
+
+	ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
+				    NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
+	if (IS_ERR(ctrl))
+		return PTR_ERR(ctrl);
+
+	ctrl->subDeviceInstance = 0;
+	ctrl->head = head;
+	ctrl->sorIndex = sor->id;
+	ctrl->dpLink = sor->asy.link == 2;
+	ctrl->bEnableOverride = 1;
+	ctrl->bMST = 0;
+	ctrl->hBlankSym = hblanksym;
+	ctrl->vBlankSym = vblanksym;
+	ctrl->colorFormat = 0;
+	ctrl->bEnableTwoHeadOneOr = 0;
+	ctrl->SST.bEnhancedFraming = ef;
+	ctrl->SST.tuSize = 64;
+	ctrl->SST.waterMark = watermark;
+	ctrl->SST.bEnableAudioOverRightPanel = 0;
+	return nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl);
+}
+
 static int
 r570_dp_set_indexed_link_rates(struct nvkm_outp *outp)
 {
@@ -255,6 +317,8 @@ r570_disp = {
 	.dp = {
 		.get_caps = r570_dp_get_caps,
 		.set_indexed_link_rates = r570_dp_set_indexed_link_rates,
+		.sst = r570_dp_sst,
+		.vcpi = r570_dp_vcpi,
 	},
 	.chan = {
 		.set_pushbuf = r570_disp_chan_set_pushbuf,
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h
index 06e972835d77..742b25a2a12d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/nvrm/disp.h
@@ -256,6 +256,8 @@ typedef struct NV0073_CTRL_DP_CTRL_PARAMS {
     NvU32 eightLaneDpcdBaseAddr;
 } NV0073_CTRL_DP_CTRL_PARAMS;
 
+#define NV0073_CTRL_CMD_DP_CONFIG_STREAM                   (0x731362U) /* finn: Evaluated from "(FINN_NV04_DISPLAY_COMMON_DP_INTERFACE_ID << 8) | NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS_MESSAGE_ID" */
+
 typedef struct NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS {
     NvU32  subDeviceInstance;
     NvU32  head;
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
index a9af94adf9ef..e0c10a7e3452 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/rm.h
@@ -6,6 +6,7 @@
 #ifndef __NVKM_RM_H__
 #define __NVKM_RM_H__
 #include "handles.h"
+struct nvkm_ior;
 struct nvkm_outp;
 struct r535_gr;
 
@@ -93,6 +94,10 @@ struct nvkm_rm_api {
 		struct {
 			int (*get_caps)(struct nvkm_disp *, int *link_bw, bool *mst, bool *wm);
 			int (*set_indexed_link_rates)(struct nvkm_outp *);
+			int (*sst)(struct nvkm_ior *, int head, bool ef,
+				   u32 watermark, u32 hblanksym, u32 vblanksym);
+			void (*vcpi)(struct nvkm_ior *, int head,
+				     u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn);
 		} dp;
 
 		struct {
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 5/7] nouveau/gsp: fix vblank interrupts on GB20x
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:57   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

The GSP path programs per-head timing (vblank) interrupts the same way on
every generation. NVD5.0 (GB20x) reworked the FE interrupt frontend
around four message-based kernel vectors (high latency, low latency, PMU,
and GSP) and moved RM head-timing interrupts to the dedicated low-latency
vector:

 - The enable is NV_PDISP_FE_RM_INTR_EN1_HEAD_TIMING, 0x611ef0 +
   head*4 (570.144 kernel_head_0501.c and v05_01 dev_disp.h. (Renamed
   kernel_head_0502.c in 610 releases)).

 - The vector is reported as a separate interrupt table entry,
   MC_ENGINE_IDX_DISP_LOW (intr_gb202.c, intrCacheDispIntrVectors).

 - The vector must be re-armed through NV_PDISP_FE_INTR_RETRIGGER(1)
   at 0x611f34 after servicing (kdispServiceInterrupt ->
   kdispIntrRetrigger_v05_01).

The event latch (0x611800), per-head status (0x611c00), and dispatch
summary (0x611ec0) the interrupt handler uses are unchanged on GB20x
(kheadReadPendingVblank_v03_00 and kheadResetPendingLastData_v03_00
remain for DISPv0502+).

On GB20x the old code enables head timing onto the legacy vector, leaves
its handler there, and never re-arms the message-based vectors. Page
flips still complete (nv50 sends those events from the commit path), so
the desktop looks fine while DRM vblank waits and vblank sequence queries
are affected.

Select the head functions and the interrupt handler per generation in
r535_disp_oneinit(), keyed on the display root class from the RM GPU
table like the SOR split. Translate the low-latency interrupt table as a
second NVKM_ENGINE_DISP instance, attach the handler to it on GB20x, and
retrigger the vector after servicing.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 63 ++++++++++++++++++-
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c |  8 +++
 2 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index dd632767aea5..248ed6368e48 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -696,6 +696,33 @@ r535_head = {
 	.vblank_put = r535_head_vblank_put,
 };
 
+/* NVD5.0 (GB20x and later) moved the RM head-timing interrupt enable to
+ * the low-latency vector's EN1 block. The event latch is unchanged.
+ */
+static void
+gb202_head_vblank_put(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+
+	nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000000);
+}
+
+static void
+gb202_head_vblank_get(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+
+	nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002);
+	nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000002);
+}
+
+static const struct nvkm_head_func
+gb202_head = {
+	.state = r535_head_state,
+	.vblank_get = gb202_head_vblank_get,
+	.vblank_put = gb202_head_vblank_put,
+};
+
 static struct nvkm_conn *
 r535_conn_new(struct nvkm_disp *disp, u32 id)
 {
@@ -1496,6 +1523,20 @@ r535_disp_intr(struct nvkm_inth *inth)
 	return IRQ_HANDLED;
 }
 
+static irqreturn_t
+gb202_disp_intr(struct nvkm_inth *inth)
+{
+	struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
+	irqreturn_t ret = r535_disp_intr(inth);
+
+	/* The FE interrupt vectors are message-based on NVD5.0. Re-arm the
+	 * low-latency vector so it fires again for any event that latched
+	 * while we were servicing.
+	 */
+	nvkm_wr32(disp->engine.subdev.device, 0x611f34, 0x00000001);
+	return ret;
+}
+
 static void
 r535_disp_fini(struct nvkm_disp *disp, bool suspend)
 {
@@ -1568,7 +1609,9 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 	struct nvkm_device *device = disp->engine.subdev.device;
 	struct nvkm_gsp *gsp = device->gsp;
 	const struct nvkm_rm_api *rmapi = gsp->rm->api;
+	const struct nvkm_rm_gpu *gpu = gsp->rm->gpu;
 	NV2080_CTRL_INTERNAL_DISPLAY_WRITE_INST_MEM_PARAMS *ctrl;
+	nvkm_inth_func intr_func;
 	unsigned long mask;
 	int ret, i;
 
@@ -1722,7 +1765,12 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
 
 		for_each_set_bit(i, &disp->head.mask, disp->head.nr) {
-			ret = nvkm_head_new_(&r535_head, disp, i);
+			const struct nvkm_head_func *func = &r535_head;
+
+			if (gpu->disp.class.root >= GB202_DISP)
+				func = &gb202_head;
+
+			ret = nvkm_head_new_(func, disp, i);
 			if (ret)
 				return ret;
 		}
@@ -1766,12 +1814,21 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 	if (ret)
 		return ret;
 
-	ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst);
+	if (gpu->disp.class.root >= GB202_DISP) {
+		/* GB20x deliver head-timing interrupts on the display's
+		 * separate low-latency vector (interrupt table instance 1).
+		 */
+		ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, 1);
+		intr_func = gb202_disp_intr;
+	} else {
+		ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst);
+		intr_func = r535_disp_intr;
+	}
 	if (ret < 0)
 		return ret;
 
 	ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
-			    r535_disp_intr, &disp->engine.subdev.inth);
+			    intr_func, &disp->engine.subdev.inth);
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c
index 996941c668ba..2590b22663cb 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c
@@ -44,6 +44,14 @@ r570_gsp_xlat_mc_engine_idx(u32 mc_engine_idx, enum nvkm_subdev_type *ptype, int
 		*ptype = NVKM_ENGINE_DISP;
 		*pinst = 0;
 		return true;
+	case MC_ENGINE_IDX_DISP_LOW:
+		/* GB20x+ report a separate low-latency display vector, used
+		 * for head-timing interrupts. Expose it as a second DISP
+		 * interrupt instance.
+		 */
+		*ptype = NVKM_ENGINE_DISP;
+		*pinst = 1;
+		return true;
 	case MC_ENGINE_IDX_CE0 ... MC_ENGINE_IDX_CE19:
 		*ptype = NVKM_ENGINE_CE;
 		*pinst = mc_engine_idx - MC_ENGINE_IDX_CE0;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 5/7] nouveau/gsp: fix vblank interrupts on GB20x
@ 2026-08-14 23:57   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

The GSP path programs per-head timing (vblank) interrupts the same way on
every generation. NVD5.0 (GB20x) reworked the FE interrupt frontend
around four message-based kernel vectors (high latency, low latency, PMU,
and GSP) and moved RM head-timing interrupts to the dedicated low-latency
vector:

 - The enable is NV_PDISP_FE_RM_INTR_EN1_HEAD_TIMING, 0x611ef0 +
   head*4 (570.144 kernel_head_0501.c and v05_01 dev_disp.h. (Renamed
   kernel_head_0502.c in 610 releases)).

 - The vector is reported as a separate interrupt table entry,
   MC_ENGINE_IDX_DISP_LOW (intr_gb202.c, intrCacheDispIntrVectors).

 - The vector must be re-armed through NV_PDISP_FE_INTR_RETRIGGER(1)
   at 0x611f34 after servicing (kdispServiceInterrupt ->
   kdispIntrRetrigger_v05_01).

The event latch (0x611800), per-head status (0x611c00), and dispatch
summary (0x611ec0) the interrupt handler uses are unchanged on GB20x
(kheadReadPendingVblank_v03_00 and kheadResetPendingLastData_v03_00
remain for DISPv0502+).

On GB20x the old code enables head timing onto the legacy vector, leaves
its handler there, and never re-arms the message-based vectors. Page
flips still complete (nv50 sends those events from the commit path), so
the desktop looks fine while DRM vblank waits and vblank sequence queries
are affected.

Select the head functions and the interrupt handler per generation in
r535_disp_oneinit(), keyed on the display root class from the RM GPU
table like the SOR split. Translate the low-latency interrupt table as a
second NVKM_ENGINE_DISP instance, attach the handler to it on GB20x, and
retrigger the vector after servicing.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 .../nouveau/nvkm/subdev/gsp/rm/r535/disp.c    | 63 ++++++++++++++++++-
 .../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c |  8 +++
 2 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
index dd632767aea5..248ed6368e48 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
@@ -696,6 +696,33 @@ r535_head = {
 	.vblank_put = r535_head_vblank_put,
 };
 
+/* NVD5.0 (GB20x and later) moved the RM head-timing interrupt enable to
+ * the low-latency vector's EN1 block. The event latch is unchanged.
+ */
+static void
+gb202_head_vblank_put(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+
+	nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000000);
+}
+
+static void
+gb202_head_vblank_get(struct nvkm_head *head)
+{
+	struct nvkm_device *device = head->disp->engine.subdev.device;
+
+	nvkm_wr32(device, 0x611800 + (head->id * 4), 0x00000002);
+	nvkm_mask(device, 0x611ef0 + (head->id * 4), 0x00000002, 0x00000002);
+}
+
+static const struct nvkm_head_func
+gb202_head = {
+	.state = r535_head_state,
+	.vblank_get = gb202_head_vblank_get,
+	.vblank_put = gb202_head_vblank_put,
+};
+
 static struct nvkm_conn *
 r535_conn_new(struct nvkm_disp *disp, u32 id)
 {
@@ -1496,6 +1523,20 @@ r535_disp_intr(struct nvkm_inth *inth)
 	return IRQ_HANDLED;
 }
 
+static irqreturn_t
+gb202_disp_intr(struct nvkm_inth *inth)
+{
+	struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
+	irqreturn_t ret = r535_disp_intr(inth);
+
+	/* The FE interrupt vectors are message-based on NVD5.0. Re-arm the
+	 * low-latency vector so it fires again for any event that latched
+	 * while we were servicing.
+	 */
+	nvkm_wr32(disp->engine.subdev.device, 0x611f34, 0x00000001);
+	return ret;
+}
+
 static void
 r535_disp_fini(struct nvkm_disp *disp, bool suspend)
 {
@@ -1568,7 +1609,9 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 	struct nvkm_device *device = disp->engine.subdev.device;
 	struct nvkm_gsp *gsp = device->gsp;
 	const struct nvkm_rm_api *rmapi = gsp->rm->api;
+	const struct nvkm_rm_gpu *gpu = gsp->rm->gpu;
 	NV2080_CTRL_INTERNAL_DISPLAY_WRITE_INST_MEM_PARAMS *ctrl;
+	nvkm_inth_func intr_func;
 	unsigned long mask;
 	int ret, i;
 
@@ -1722,7 +1765,12 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 		nvkm_gsp_rm_ctrl_done(&disp->rm.objcom, ctrl);
 
 		for_each_set_bit(i, &disp->head.mask, disp->head.nr) {
-			ret = nvkm_head_new_(&r535_head, disp, i);
+			const struct nvkm_head_func *func = &r535_head;
+
+			if (gpu->disp.class.root >= GB202_DISP)
+				func = &gb202_head;
+
+			ret = nvkm_head_new_(func, disp, i);
 			if (ret)
 				return ret;
 		}
@@ -1766,12 +1814,21 @@ r535_disp_oneinit(struct nvkm_disp *disp)
 	if (ret)
 		return ret;
 
-	ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst);
+	if (gpu->disp.class.root >= GB202_DISP) {
+		/* GB20x deliver head-timing interrupts on the display's
+		 * separate low-latency vector (interrupt table instance 1).
+		 */
+		ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, 1);
+		intr_func = gb202_disp_intr;
+	} else {
+		ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type, disp->engine.subdev.inst);
+		intr_func = r535_disp_intr;
+	}
 	if (ret < 0)
 		return ret;
 
 	ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
-			    r535_disp_intr, &disp->engine.subdev.inth);
+			    intr_func, &disp->engine.subdev.inth);
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c
index 996941c668ba..2590b22663cb 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c
@@ -44,6 +44,14 @@ r570_gsp_xlat_mc_engine_idx(u32 mc_engine_idx, enum nvkm_subdev_type *ptype, int
 		*ptype = NVKM_ENGINE_DISP;
 		*pinst = 0;
 		return true;
+	case MC_ENGINE_IDX_DISP_LOW:
+		/* GB20x+ report a separate low-latency display vector, used
+		 * for head-timing interrupts. Expose it as a second DISP
+		 * interrupt instance.
+		 */
+		*ptype = NVKM_ENGINE_DISP;
+		*pinst = 1;
+		return true;
 	case MC_ENGINE_IDX_CE0 ... MC_ENGINE_IDX_CE19:
 		*ptype = NVKM_ENGINE_CE;
 		*pinst = mc_engine_idx - MC_ENGINE_IDX_CE0;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 6/7] nouveau/dispnv50: program pixel clocks above 2.147GHz on GB20x
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:57   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

The HEAD_SET_PIXEL_CLOCK_FREQUENCY(_MAX) methods carry only 31 HERTZ
bits. Starting with C97D the upper bits live in separate
HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(_MAX) methods, which nouveau never
programmed and headca7d_mode() computed m->clock * 1000 into the 31-bit
field. NVVAL's mask then silently truncates anything past 2^31 Hz, which
means that every mode scanned out at pclk modulo 2^31.

No mode nouveau can currently commit crosses the boundary (an
uncompressed HDMI FRL mode tops out around 1.78GHz at 8bpc), but this is
a prerequisite for the upcoming DSC work, which makes 2.147GHz+ modes
reachable.

Program the full value split across the low and HI methods, exactly
as OpenRM's EvoSetRasterParams9() does (nvkms-evo4.c, 31-bit low word
plus the 4 HI HERTZ bits, giving 35 bits of range).

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/dispnv50/headca7d.c   | 21 ++++++++++++++++---
 .../drm/nouveau/include/nvhw/class/clca7d.h   |  4 ++++
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
index eeaeb15aa664..2046e38a4d79 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
@@ -219,10 +219,11 @@ headca7d_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nvif_push *push = &head->disp->core->chan.push;
 	struct nv50_head_mode *m = &asyh->mode;
+	const u64 hz = (u64)m->clock * 1000;
 	const int i = head->base.index;
 	int ret;
 
-	ret = PUSH_WAIT(push, 11);
+	ret = PUSH_WAIT(push, 15);
 	if (ret)
 		return ret;
 
@@ -245,11 +246,25 @@ headca7d_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 	PUSH_MTHD(push, NVCA7D, HEAD_SET_CONTROL(i),
 		  NVDEF(NVCA7D, HEAD_SET_CONTROL, STRUCTURE, PROGRESSIVE));
 
+	/* The FREQUENCY methods carry only 31 HERTZ bits; the upper bits
+	 * of anything past 2.147GHz live in the HI methods
+	 * (EvoSetRasterParams9()). Truncation here scans out at pclk modulo 2^31.
+	 */
 	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY(i),
-		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ, m->clock * 1000));
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ,
+			(u32)(hz & 0x7fffffff)));
 
 	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX(i),
-		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX, HERTZ, m->clock * 1000));
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX, HERTZ,
+			(u32)(hz & 0x7fffffff)));
+
+	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(i),
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI, HERTZ,
+			(u32)(hz >> 31)));
+
+	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX(i),
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX, HERTZ,
+			(u32)(hz >> 31)));
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
index 0fec6fc21d44..1ab12d91c9d9 100644
--- a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
+++ b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
@@ -653,6 +653,10 @@
 #define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX_ADJ1000DIV1001                31:31
 #define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX_ADJ1000DIV1001_FALSE          (0x00000000)
 #define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX_ADJ1000DIV1001_TRUE           (0x00000001)
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(a)                             (0x000020C0 + (a)*0x00000800)
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_HERTZ                          3:0
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX(a)                         (0x000020C4 + (a)*0x00000800)
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX_HERTZ                      3:0
 #define NVCA7D_HEAD_SET_HEAD_USAGE_BOUNDS(a)                                    (0x00002030 + (a)*0x00000800)
 #define NVCA7D_HEAD_SET_HEAD_USAGE_BOUNDS_CURSOR                                2:0
 #define NVCA7D_HEAD_SET_HEAD_USAGE_BOUNDS_CURSOR_USAGE_NONE                     (0x00000000)
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 6/7] nouveau/dispnv50: program pixel clocks above 2.147GHz on GB20x
@ 2026-08-14 23:57   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

The HEAD_SET_PIXEL_CLOCK_FREQUENCY(_MAX) methods carry only 31 HERTZ
bits. Starting with C97D the upper bits live in separate
HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(_MAX) methods, which nouveau never
programmed and headca7d_mode() computed m->clock * 1000 into the 31-bit
field. NVVAL's mask then silently truncates anything past 2^31 Hz, which
means that every mode scanned out at pclk modulo 2^31.

No mode nouveau can currently commit crosses the boundary (an
uncompressed HDMI FRL mode tops out around 1.78GHz at 8bpc), but this is
a prerequisite for the upcoming DSC work, which makes 2.147GHz+ modes
reachable.

Program the full value split across the low and HI methods, exactly
as OpenRM's EvoSetRasterParams9() does (nvkms-evo4.c, 31-bit low word
plus the 4 HI HERTZ bits, giving 35 bits of range).

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/dispnv50/headca7d.c   | 21 ++++++++++++++++---
 .../drm/nouveau/include/nvhw/class/clca7d.h   |  4 ++++
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
index eeaeb15aa664..2046e38a4d79 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/headca7d.c
@@ -219,10 +219,11 @@ headca7d_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 {
 	struct nvif_push *push = &head->disp->core->chan.push;
 	struct nv50_head_mode *m = &asyh->mode;
+	const u64 hz = (u64)m->clock * 1000;
 	const int i = head->base.index;
 	int ret;
 
-	ret = PUSH_WAIT(push, 11);
+	ret = PUSH_WAIT(push, 15);
 	if (ret)
 		return ret;
 
@@ -245,11 +246,25 @@ headca7d_mode(struct nv50_head *head, struct nv50_head_atom *asyh)
 	PUSH_MTHD(push, NVCA7D, HEAD_SET_CONTROL(i),
 		  NVDEF(NVCA7D, HEAD_SET_CONTROL, STRUCTURE, PROGRESSIVE));
 
+	/* The FREQUENCY methods carry only 31 HERTZ bits; the upper bits
+	 * of anything past 2.147GHz live in the HI methods
+	 * (EvoSetRasterParams9()). Truncation here scans out at pclk modulo 2^31.
+	 */
 	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY(i),
-		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ, m->clock * 1000));
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY, HERTZ,
+			(u32)(hz & 0x7fffffff)));
 
 	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX(i),
-		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX, HERTZ, m->clock * 1000));
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX, HERTZ,
+			(u32)(hz & 0x7fffffff)));
+
+	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(i),
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI, HERTZ,
+			(u32)(hz >> 31)));
+
+	PUSH_MTHD(push, NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX(i),
+		  NVVAL(NVCA7D, HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX, HERTZ,
+			(u32)(hz >> 31)));
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
index 0fec6fc21d44..1ab12d91c9d9 100644
--- a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
+++ b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
@@ -653,6 +653,10 @@
 #define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX_ADJ1000DIV1001                31:31
 #define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX_ADJ1000DIV1001_FALSE          (0x00000000)
 #define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_MAX_ADJ1000DIV1001_TRUE           (0x00000001)
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI(a)                             (0x000020C0 + (a)*0x00000800)
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_HERTZ                          3:0
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX(a)                         (0x000020C4 + (a)*0x00000800)
+#define NVCA7D_HEAD_SET_PIXEL_CLOCK_FREQUENCY_HI_MAX_HERTZ                      3:0
 #define NVCA7D_HEAD_SET_HEAD_USAGE_BOUNDS(a)                                    (0x00002030 + (a)*0x00000800)
 #define NVCA7D_HEAD_SET_HEAD_USAGE_BOUNDS_CURSOR                                2:0
 #define NVCA7D_HEAD_SET_HEAD_USAGE_BOUNDS_CURSOR_USAGE_NONE                     (0x00000000)
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 7/7] nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid
  2026-08-14 23:56 ` Mohamed Ahmed
@ 2026-08-14 23:57   ` Mohamed Ahmed
  -1 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
	Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed

HDMI 2.1 sinks ship HF-EEODB EDIDs. Byte 126 deliberately claims one
extension block for legacy sources, and the true count lives in the CTA
block's first data block. Their high-refresh timings (e.g., 4K240, 6K165,
etc) sit in DisplayID extension blocks 2+.

The kernel already reads these EDIDs whole, and on GSP boards RM returns
the full EDID with its true size. However, nouveau then hands the buffer
to the legacy API which sizes the EDID from byte 126 so the DisplayID
blocks are not parsed/exposed and the sysfs blob truncates to 256 bytes.

Convert to the struct drm_edid API, which carries the real size.
Read via drm_edid_read_ddc()/drm_edid_read_switcheroo() where an
adapter exists, and serve RM's buffer through drm_edid_read_custom()
with a block-reading callback where not, which also routes the GSP
path through drm's block validation and the debugfs EDID override
(previously dead on GSP boards, since nothing consulted it outside
the i2c readers).

The connector keeps a borrowed const drm_edid_raw() view in ->edid, same
as i915's detect_edid so the raw-EDID consumers (SAD parsing, deep-color
flags, the DVI dual-link byte, DP CEC) are untouched. Ownership and
freeing move to the drm_edid. The OF path additionally now frees any
previous EDID itself rather than relying on the callers. MST connectors
keep their separate legacy path.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/dispnv50/disp.c     |  2 +-
 drivers/gpu/drm/nouveau/nouveau_connector.c | 95 ++++++++++++++++-----
 drivers/gpu/drm/nouveau/nouveau_connector.h |  7 +-
 3 files changed, 80 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 364227f5456f..c3b7fd29c077 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -1804,7 +1804,7 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st
 			lvds_8bpc = bios->fp.if_is_24bit;
 		} else {
 			if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
-				if (((u8 *)nv_connector->edid)[121] == 2)
+				if (((const u8 *)nv_connector->edid)[121] == 2)
 					lvds_dual = true;
 			} else
 			if (mode->clock >= bios->fp.duallink_transition_clk) {
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index b0b0ad9a0c24..4105555151c9 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -397,7 +397,7 @@ nouveau_connector_destroy(struct drm_connector *connector)
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
 	nvif_event_dtor(&nv_connector->irq);
 	nvif_event_dtor(&nv_connector->hpd);
-	kfree(nv_connector->edid);
+	drm_edid_free(nv_connector->drm_edid);
 	drm_connector_unregister(connector);
 	drm_connector_cleanup(connector);
 	if (nv_connector->aux.transfer)
@@ -490,8 +490,11 @@ nouveau_connector_of_detect(struct drm_connector *connector)
 		int idx = name ? name[strlen(name) - 1] - 'A' : 0;
 
 		if (nv_encoder->dcb->i2c_index == idx && edid) {
+			drm_edid_free(nv_connector->drm_edid);
+			nv_connector->drm_edid =
+				drm_edid_alloc(edid, EDID_LENGTH);
 			nv_connector->edid =
-				kmemdup(edid, EDID_LENGTH, GFP_KERNEL);
+				drm_edid_raw(nv_connector->drm_edid);
 			return nv_encoder;
 		}
 	}
@@ -546,17 +549,42 @@ nouveau_connector_set_encoder(struct drm_connector *connector,
 	}
 }
 
+struct nouveau_rm_edid {
+	u8 *data;
+	int size;
+};
+
+static int
+nouveau_connector_rm_edid_block(void *context, u8 *buf, unsigned int block,
+				size_t len)
+{
+	struct nouveau_rm_edid *rm = context;
+	size_t offset = (size_t)block * EDID_LENGTH;
+
+	if (offset + len > rm->size)
+		return -EINVAL;
+
+	memcpy(buf, rm->data + offset, len);
+	return 0;
+}
+
 static void
 nouveau_connector_set_edid(struct nouveau_connector *nv_connector,
-			   struct edid *edid)
+			   const struct drm_edid *drm_edid)
 {
-	if (nv_connector->edid != edid) {
-		struct edid *old_edid = nv_connector->edid;
+	if (nv_connector->drm_edid == drm_edid)
+		return;
 
-		drm_connector_update_edid_property(&nv_connector->base, edid);
-		kfree(old_edid);
-		nv_connector->edid = edid;
-	}
+	/* Updates the EDID property and display_info with HF-EEODB-aware
+	 * sizing. The legacy helpers truncate both to what EDID byte 126
+	 * admits, hiding the DisplayID extension blocks that carry every
+	 * timing a DTD can't express (>655.35 MHz).
+	 */
+	drm_edid_connector_update(&nv_connector->base, drm_edid);
+
+	drm_edid_free(nv_connector->drm_edid);
+	nv_connector->drm_edid = drm_edid;
+	nv_connector->edid = drm_edid_raw(drm_edid);
 }
 
 static enum drm_connector_status
@@ -590,20 +618,36 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 
 	nv_encoder = nouveau_connector_ddc_detect(connector);
 	if (nv_encoder) {
-		struct edid *new_edid = NULL;
+		const struct drm_edid *new_edid = NULL;
 
 		if (nv_encoder->i2c) {
 			if ((vga_switcheroo_handler_flags() & VGA_SWITCHEROO_CAN_SWITCH_DDC) &&
 			    nv_connector->type == DCB_CONNECTOR_LVDS)
-				new_edid = drm_get_edid_switcheroo(connector, nv_encoder->i2c);
+				new_edid = drm_edid_read_switcheroo(connector, nv_encoder->i2c);
 			else
-				new_edid = drm_get_edid(connector, nv_encoder->i2c);
+				new_edid = drm_edid_read_ddc(connector, nv_encoder->i2c);
 		} else {
-			ret = nvif_outp_edid_get(&nv_encoder->outp, (u8 **)&new_edid);
+			struct nouveau_rm_edid rm = {};
+
+			ret = nvif_outp_edid_get(&nv_encoder->outp, &rm.data);
 			if (ret < 0)
 				return connector_status_disconnected;
+
+			/* ret is RM's true buffer size: an HF-EEODB EDID is
+			 * larger than its byte 126 admits, and RM (which
+			 * owns the DDC pads on GSP boards) reads it whole.
+			 * Serve it through drm's block reader so EEODB
+			 * sizing, block validation, and the debugfs EDID
+			 * override all apply.
+			 */
+			rm.size = ret;
+			new_edid = drm_edid_read_custom(connector,
+							nouveau_connector_rm_edid_block,
+							&rm);
+			kfree(rm.data);
 		}
 
+
 		nouveau_connector_set_edid(nv_connector, new_edid);
 		if (!nv_connector->edid) {
 			NV_ERROR(drm, "DDC responded, but no EDID for %s\n",
@@ -686,7 +730,7 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
 	struct nouveau_encoder *nv_encoder = NULL;
-	struct edid *edid = NULL;
+	const struct drm_edid *edid = NULL;
 	enum drm_connector_status status = connector_status_disconnected;
 
 	nv_encoder = find_encoder(connector, DCB_OUTPUT_LVDS);
@@ -697,7 +741,7 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	if (!drm->vbios.fp_no_ddc) {
 		status = nouveau_connector_detect(connector, force);
 		if (status == connector_status_connected) {
-			edid = nv_connector->edid;
+			edid = nv_connector->drm_edid;
 			goto out;
 		}
 	}
@@ -712,7 +756,13 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	 * valid - it's not (rh#613284)
 	 */
 	if (nv_encoder->dcb->lvdsconf.use_acpi_for_edid) {
-		edid = nouveau_acpi_edid(dev, connector);
+		struct edid *raw = nouveau_acpi_edid(dev, connector);
+
+		if (raw) {
+			edid = drm_edid_alloc(raw,
+					      EDID_LENGTH * (1 + raw->extensions));
+			kfree(raw);
+		}
 		if (edid) {
 			status = connector_status_connected;
 			goto out;
@@ -733,9 +783,10 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	 * stored for the panel stored in them.
 	 */
 	if (!drm->vbios.fp_no_ddc) {
-		edid = (struct edid *)nouveau_bios_embedded_edid(dev);
-		if (edid) {
-			edid = kmemdup(edid, EDID_LENGTH, GFP_KERNEL);
+		const void *embedded = nouveau_bios_embedded_edid(dev);
+
+		if (embedded) {
+			edid = drm_edid_alloc(embedded, EDID_LENGTH);
 			if (edid)
 				status = connector_status_connected;
 		}
@@ -915,7 +966,7 @@ nouveau_connector_detect_depth(struct drm_connector *connector)
 	 */
 	if (nv_connector->edid &&
 	    nv_connector->type == DCB_CONNECTOR_LVDS_SPWG)
-		duallink = ((u8 *)nv_connector->edid)[121] == 2;
+		duallink = ((const u8 *)nv_connector->edid)[121] == 2;
 	else
 		duallink = mode->clock >= bios->fp.duallink_transition_clk;
 
@@ -973,8 +1024,8 @@ nouveau_connector_get_modes(struct drm_connector *connector)
 		nv_connector->native_mode = NULL;
 	}
 
-	if (nv_connector->edid)
-		ret = drm_add_edid_modes(connector, nv_connector->edid);
+	if (nv_connector->drm_edid)
+		ret = drm_edid_connector_add_modes(connector);
 	else
 	if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS &&
 	    (nv_encoder->dcb->lvdsconf.use_straps_for_mode ||
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index 0608cabed058..ca8f310b3680 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -137,7 +137,12 @@ struct nouveau_connector {
 	int scaling_mode;
 
 	struct nouveau_encoder *detected_encoder;
-	struct edid *edid;
+	/* Borrowed decoded view of ->drm_edid for the raw-EDID consumers
+	 * (SADs, deep-colour flags, dual-link byte); do not free.
+	 */
+	const struct edid *edid;
+	/* Owner of the sink's EDID, HF-EEODB-complete. */
+	const struct drm_edid *drm_edid;
 	struct drm_display_mode *native_mode;
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
 	struct nouveau_backlight *backlight;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 7/7] nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid
@ 2026-08-14 23:57   ` Mohamed Ahmed
  0 siblings, 0 replies; 21+ messages in thread
From: Mohamed Ahmed @ 2026-08-14 23:57 UTC (permalink / raw)
  To: linux-kernel
  Cc: dri-devel, Lyude Paul, Danilo Krummrich, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
	Mary Guillemard, nouveau, Mohamed Ahmed

HDMI 2.1 sinks ship HF-EEODB EDIDs. Byte 126 deliberately claims one
extension block for legacy sources, and the true count lives in the CTA
block's first data block. Their high-refresh timings (e.g., 4K240, 6K165,
etc) sit in DisplayID extension blocks 2+.

The kernel already reads these EDIDs whole, and on GSP boards RM returns
the full EDID with its true size. However, nouveau then hands the buffer
to the legacy API which sizes the EDID from byte 126 so the DisplayID
blocks are not parsed/exposed and the sysfs blob truncates to 256 bytes.

Convert to the struct drm_edid API, which carries the real size.
Read via drm_edid_read_ddc()/drm_edid_read_switcheroo() where an
adapter exists, and serve RM's buffer through drm_edid_read_custom()
with a block-reading callback where not, which also routes the GSP
path through drm's block validation and the debugfs EDID override
(previously dead on GSP boards, since nothing consulted it outside
the i2c readers).

The connector keeps a borrowed const drm_edid_raw() view in ->edid, same
as i915's detect_edid so the raw-EDID consumers (SAD parsing, deep-color
flags, the DVI dual-link byte, DP CEC) are untouched. Ownership and
freeing move to the drm_edid. The OF path additionally now frees any
previous EDID itself rather than relying on the callers. MST connectors
keep their separate legacy path.

Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
---
 drivers/gpu/drm/nouveau/dispnv50/disp.c     |  2 +-
 drivers/gpu/drm/nouveau/nouveau_connector.c | 95 ++++++++++++++++-----
 drivers/gpu/drm/nouveau/nouveau_connector.h |  7 +-
 3 files changed, 80 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 364227f5456f..c3b7fd29c077 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -1804,7 +1804,7 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st
 			lvds_8bpc = bios->fp.if_is_24bit;
 		} else {
 			if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
-				if (((u8 *)nv_connector->edid)[121] == 2)
+				if (((const u8 *)nv_connector->edid)[121] == 2)
 					lvds_dual = true;
 			} else
 			if (mode->clock >= bios->fp.duallink_transition_clk) {
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index b0b0ad9a0c24..4105555151c9 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -397,7 +397,7 @@ nouveau_connector_destroy(struct drm_connector *connector)
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
 	nvif_event_dtor(&nv_connector->irq);
 	nvif_event_dtor(&nv_connector->hpd);
-	kfree(nv_connector->edid);
+	drm_edid_free(nv_connector->drm_edid);
 	drm_connector_unregister(connector);
 	drm_connector_cleanup(connector);
 	if (nv_connector->aux.transfer)
@@ -490,8 +490,11 @@ nouveau_connector_of_detect(struct drm_connector *connector)
 		int idx = name ? name[strlen(name) - 1] - 'A' : 0;
 
 		if (nv_encoder->dcb->i2c_index == idx && edid) {
+			drm_edid_free(nv_connector->drm_edid);
+			nv_connector->drm_edid =
+				drm_edid_alloc(edid, EDID_LENGTH);
 			nv_connector->edid =
-				kmemdup(edid, EDID_LENGTH, GFP_KERNEL);
+				drm_edid_raw(nv_connector->drm_edid);
 			return nv_encoder;
 		}
 	}
@@ -546,17 +549,42 @@ nouveau_connector_set_encoder(struct drm_connector *connector,
 	}
 }
 
+struct nouveau_rm_edid {
+	u8 *data;
+	int size;
+};
+
+static int
+nouveau_connector_rm_edid_block(void *context, u8 *buf, unsigned int block,
+				size_t len)
+{
+	struct nouveau_rm_edid *rm = context;
+	size_t offset = (size_t)block * EDID_LENGTH;
+
+	if (offset + len > rm->size)
+		return -EINVAL;
+
+	memcpy(buf, rm->data + offset, len);
+	return 0;
+}
+
 static void
 nouveau_connector_set_edid(struct nouveau_connector *nv_connector,
-			   struct edid *edid)
+			   const struct drm_edid *drm_edid)
 {
-	if (nv_connector->edid != edid) {
-		struct edid *old_edid = nv_connector->edid;
+	if (nv_connector->drm_edid == drm_edid)
+		return;
 
-		drm_connector_update_edid_property(&nv_connector->base, edid);
-		kfree(old_edid);
-		nv_connector->edid = edid;
-	}
+	/* Updates the EDID property and display_info with HF-EEODB-aware
+	 * sizing. The legacy helpers truncate both to what EDID byte 126
+	 * admits, hiding the DisplayID extension blocks that carry every
+	 * timing a DTD can't express (>655.35 MHz).
+	 */
+	drm_edid_connector_update(&nv_connector->base, drm_edid);
+
+	drm_edid_free(nv_connector->drm_edid);
+	nv_connector->drm_edid = drm_edid;
+	nv_connector->edid = drm_edid_raw(drm_edid);
 }
 
 static enum drm_connector_status
@@ -590,20 +618,36 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
 
 	nv_encoder = nouveau_connector_ddc_detect(connector);
 	if (nv_encoder) {
-		struct edid *new_edid = NULL;
+		const struct drm_edid *new_edid = NULL;
 
 		if (nv_encoder->i2c) {
 			if ((vga_switcheroo_handler_flags() & VGA_SWITCHEROO_CAN_SWITCH_DDC) &&
 			    nv_connector->type == DCB_CONNECTOR_LVDS)
-				new_edid = drm_get_edid_switcheroo(connector, nv_encoder->i2c);
+				new_edid = drm_edid_read_switcheroo(connector, nv_encoder->i2c);
 			else
-				new_edid = drm_get_edid(connector, nv_encoder->i2c);
+				new_edid = drm_edid_read_ddc(connector, nv_encoder->i2c);
 		} else {
-			ret = nvif_outp_edid_get(&nv_encoder->outp, (u8 **)&new_edid);
+			struct nouveau_rm_edid rm = {};
+
+			ret = nvif_outp_edid_get(&nv_encoder->outp, &rm.data);
 			if (ret < 0)
 				return connector_status_disconnected;
+
+			/* ret is RM's true buffer size: an HF-EEODB EDID is
+			 * larger than its byte 126 admits, and RM (which
+			 * owns the DDC pads on GSP boards) reads it whole.
+			 * Serve it through drm's block reader so EEODB
+			 * sizing, block validation, and the debugfs EDID
+			 * override all apply.
+			 */
+			rm.size = ret;
+			new_edid = drm_edid_read_custom(connector,
+							nouveau_connector_rm_edid_block,
+							&rm);
+			kfree(rm.data);
 		}
 
+
 		nouveau_connector_set_edid(nv_connector, new_edid);
 		if (!nv_connector->edid) {
 			NV_ERROR(drm, "DDC responded, but no EDID for %s\n",
@@ -686,7 +730,7 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nouveau_connector *nv_connector = nouveau_connector(connector);
 	struct nouveau_encoder *nv_encoder = NULL;
-	struct edid *edid = NULL;
+	const struct drm_edid *edid = NULL;
 	enum drm_connector_status status = connector_status_disconnected;
 
 	nv_encoder = find_encoder(connector, DCB_OUTPUT_LVDS);
@@ -697,7 +741,7 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	if (!drm->vbios.fp_no_ddc) {
 		status = nouveau_connector_detect(connector, force);
 		if (status == connector_status_connected) {
-			edid = nv_connector->edid;
+			edid = nv_connector->drm_edid;
 			goto out;
 		}
 	}
@@ -712,7 +756,13 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	 * valid - it's not (rh#613284)
 	 */
 	if (nv_encoder->dcb->lvdsconf.use_acpi_for_edid) {
-		edid = nouveau_acpi_edid(dev, connector);
+		struct edid *raw = nouveau_acpi_edid(dev, connector);
+
+		if (raw) {
+			edid = drm_edid_alloc(raw,
+					      EDID_LENGTH * (1 + raw->extensions));
+			kfree(raw);
+		}
 		if (edid) {
 			status = connector_status_connected;
 			goto out;
@@ -733,9 +783,10 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
 	 * stored for the panel stored in them.
 	 */
 	if (!drm->vbios.fp_no_ddc) {
-		edid = (struct edid *)nouveau_bios_embedded_edid(dev);
-		if (edid) {
-			edid = kmemdup(edid, EDID_LENGTH, GFP_KERNEL);
+		const void *embedded = nouveau_bios_embedded_edid(dev);
+
+		if (embedded) {
+			edid = drm_edid_alloc(embedded, EDID_LENGTH);
 			if (edid)
 				status = connector_status_connected;
 		}
@@ -915,7 +966,7 @@ nouveau_connector_detect_depth(struct drm_connector *connector)
 	 */
 	if (nv_connector->edid &&
 	    nv_connector->type == DCB_CONNECTOR_LVDS_SPWG)
-		duallink = ((u8 *)nv_connector->edid)[121] == 2;
+		duallink = ((const u8 *)nv_connector->edid)[121] == 2;
 	else
 		duallink = mode->clock >= bios->fp.duallink_transition_clk;
 
@@ -973,8 +1024,8 @@ nouveau_connector_get_modes(struct drm_connector *connector)
 		nv_connector->native_mode = NULL;
 	}
 
-	if (nv_connector->edid)
-		ret = drm_add_edid_modes(connector, nv_connector->edid);
+	if (nv_connector->drm_edid)
+		ret = drm_edid_connector_add_modes(connector);
 	else
 	if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS &&
 	    (nv_encoder->dcb->lvdsconf.use_straps_for_mode ||
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index 0608cabed058..ca8f310b3680 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -137,7 +137,12 @@ struct nouveau_connector {
 	int scaling_mode;
 
 	struct nouveau_encoder *detected_encoder;
-	struct edid *edid;
+	/* Borrowed decoded view of ->drm_edid for the raw-EDID consumers
+	 * (SADs, deep-colour flags, dual-link byte); do not free.
+	 */
+	const struct edid *edid;
+	/* Owner of the sink's EDID, HF-EEODB-complete. */
+	const struct drm_edid *drm_edid;
 	struct drm_display_mode *native_mode;
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
 	struct nouveau_backlight *backlight;
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/7] nouveau/disp: add GB20x HDMI vendor infoframe writer
  2026-08-14 23:56   ` Mohamed Ahmed
  (?)
@ 2026-08-15  0:08   ` sashiko-bot
  -1 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-15  0:08 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The newly added `gb202_sor_hdmi_infoframe_vsi` function is never hooked into any display structures, rendering it dead code. Consequently, GB20x chips will incorrectly fall back to the legacy `gv100_sor_hdmi_infoframe_vsi` function, writing to the removed 0x6f0100 registers.
--

--- Patch [1]: [PATCH 1/7] nouveau/disp: add GB20x HDMI vendor infoframe writer ---
commit 722f58f6d8bd3ed70d39357b7f9c41339c88e50d
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

nouveau/disp: add GB20x HDMI vendor infoframe writer

This commit adds a new HDMI vendor infoframe writer specifically for GB20x 
chips. It handles the hardware change where the legacy VSI unit at 0x6f0100 
was removed and replaced by shared generic infoframe units.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
[ ... ]
> +void
> +gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 size)
> +{

[Severity: High]
Is this new function ever called? It appears it is not hooked into any of the
display structures, leaving it as dead code.

Without wiring this up, won't GB20x chips still incorrectly fall back to the 
legacy function when sending an HDMI vendor infoframe? For example, 
r535_sor_hdmi in drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c 
continues to unconditionally use the legacy callback:

    .infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,

Could this cause MMIO faults or fail to send the vendor infoframe when 
the driver attempts to access the removed 0x6f0100 registers on GB20x
hardware?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260814235705.59132-1-mohamedahmedegypt2001@gmail.com?part=1

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 7/7] nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid
  2026-08-14 23:57   ` Mohamed Ahmed
  (?)
@ 2026-08-15  0:08   ` sashiko-bot
  -1 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-15  0:08 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:

New issues:
- [High] OpenFirmware (OF) EDID parsing is broken because the EDID property blob is never updated.

Pre-existing issues:
- [High] Unprotected access to `nv_connector->edid` can cause a kernel panic via a NULL pointer dereference.
- [High] PM runtime autosuspend reference leak on firmware EDID read failure.
- [High] Concurrent execution of asynchronous atomic commit and hotplug detection leads to a Use-After-Free of EDID.
- [High] Potential out-of-bounds heap read during ACPI EDID allocation due to untrusted length calculations.
--

--- Patch [7]: [PATCH 7/7] nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid ---
commit 8141f1386a9c223f5c8dc8688b041f5d4419ab2c
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Subject: nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid

This commit converts the nouveau display driver to use the struct drm_edid
API to correctly handle HF-EEODB EDIDs. It updates EDID retrieval paths to
use drm_edid_read helpers and a custom block reader for GSP boards. The
legacy edid pointer is retained as a borrowed view for raw-EDID consumers.

> diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> @@ -1804,7 +1804,7 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st
>  			lvds_8bpc = bios->fp.if_is_24bit;
>  		} else {
>  			if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
> -				if (((u8 *)nv_connector->edid)[121] == 2)
> +				if (((const u8 *)nv_connector->edid)[121] == 2)

[Severity: High]
This is a pre-existing issue, but can this cause a NULL pointer dereference
if the EDID retrieval failed?

If a display mode is forced on via sysfs or a hardcoded VBIOS mode without a
valid EDID, nv_connector->edid will be NULL. Unconditionally dereferencing
it here to check byte 121 could crash the kernel.

[Severity: High]
This is another pre-existing issue, but is there a use-after-free risk here
during concurrent hotplug events?

During an asynchronous atomic commit, nv50_sor_atomic_enable() reads
nv_connector->edid locklessly outside the connection_mutex. If a concurrent
hotplug worker calls nouveau_connector_set_edid() and frees the EDID
memory, this access could corrupt memory. Should atomic drivers read EDID
information from the protected drm_connector_state instead of the mutable
connector state?

>  					lvds_dual = true;
>  			} else
>  			if (mode->clock >= bios->fp.duallink_transition_clk) {

> diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
> --- a/drivers/gpu/drm/nouveau/nouveau_connector.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
[ ... ]
> @@ -490,8 +490,11 @@ nouveau_connector_of_detect(struct drm_connector *connector)
>  		int idx = name ? name[strlen(name) - 1] - 'A' : 0;
>  
>  		if (nv_encoder->dcb->i2c_index == idx && edid) {
> +			drm_edid_free(nv_connector->drm_edid);
> +			nv_connector->drm_edid =
> +				drm_edid_alloc(edid, EDID_LENGTH);
> +			nv_connector->edid =
> +				drm_edid_raw(nv_connector->drm_edid);
>  			return nv_encoder;

[Severity: High]
Does this bypass updating the core connector's blob pointer, breaking OF
EDID parsing?

Because nouveau_connector_of_detect() manually assigns nv_connector->drm_edid
instead of calling nouveau_connector_set_edid(), it skips the
drm_edid_connector_update() call. This leaves the core connector's
edid_blob_ptr as NULL.

>  		}
>  	}
[ ... ]
> @@ -590,20 +618,36 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
>  
>  	nv_encoder = nouveau_connector_ddc_detect(connector);
>  	if (nv_encoder) {
> -		struct edid *new_edid = NULL;
> +		const struct drm_edid *new_edid = NULL;
>  
>  		if (nv_encoder->i2c) {
>  			if ((vga_switcheroo_handler_flags() & VGA_SWITCHEROO_CAN_SWITCH_DDC) &&
>  			    nv_connector->type == DCB_CONNECTOR_LVDS)
> -				new_edid = drm_get_edid_switcheroo(connector, nv_encoder->i2c);
> +				new_edid = drm_edid_read_switcheroo(connector, nv_encoder->i2c);
>  			else
> -				new_edid = drm_get_edid(connector, nv_encoder->i2c);
> +				new_edid = drm_edid_read_ddc(connector, nv_encoder->i2c);
>  		} else {
> -			ret = nvif_outp_edid_get(&nv_encoder->outp, (u8 **)&new_edid);
> +			struct nouveau_rm_edid rm = {};
> +
> +			ret = nvif_outp_edid_get(&nv_encoder->outp, &rm.data);
>  			if (ret < 0)
>  				return connector_status_disconnected;

[Severity: High]
This isn't a bug introduced by this patch, but does this early return leak a
PM runtime reference?

At the beginning of nouveau_connector_detect(), a PM runtime reference is
acquired. If execution enters this fallback path and nvif_outp_edid_get()
returns < 0, the early return bypasses the out label where
pm_runtime_put_autosuspend() is called. Repeated failures here could
permanently prevent the device from entering low-power states.

> +
> +			/* ret is RM's true buffer size: an HF-EEODB EDID is
[ ... ]
> @@ -712,7 +756,13 @@ nouveau_connector_detect_lvds(struct drm_connector *connector, bool force)
>  	 * valid - it's not (rh#613284)
>  	 */
>  	if (nv_encoder->dcb->lvdsconf.use_acpi_for_edid) {
> -		edid = nouveau_acpi_edid(dev, connector);
> +		struct edid *raw = nouveau_acpi_edid(dev, connector);
> +
> +		if (raw) {
> +			edid = drm_edid_alloc(raw,
> +					      EDID_LENGTH * (1 + raw->extensions));
> +			kfree(raw);
> +		}

[Severity: High]
This is a pre-existing issue, but could this lead to an out-of-bounds heap
read if the firmware provides a malformed ACPI table?

The allocation size is computed by trusting the raw->extensions byte. If the
underlying ACPI buffer is physically smaller than what the extensions byte
claims, drm_edid_alloc() will read past the end of the raw buffer when
copying the data.

>  		if (edid) {
>  			status = connector_status_connected;
>  			goto out;
[ ... ]
> @@ -973,8 +1024,8 @@ nouveau_connector_get_modes(struct drm_connector *connector)
>  		nv_connector->native_mode = NULL;
>  	}
>  
> -	if (nv_connector->edid)
> -		ret = drm_add_edid_modes(connector, nv_connector->edid);
> +	if (nv_connector->drm_edid)
> +		ret = drm_edid_connector_add_modes(connector);

[Severity: High]
Since nouveau_connector_of_detect() left the core connector's edid_blob_ptr
NULL, will this fail to parse any display modes?

The drm_edid_connector_add_modes() helper relies exclusively on
connector->edid_blob_ptr. If it remains NULL, this will silently fail and
break display output for OpenFirmware displays.

>  	else
>  	if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS &&
>  	    (nv_encoder->dcb->lvdsconf.use_straps_for_mode ||

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260814235705.59132-1-mohamedahmedegypt2001@gmail.com?part=7

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 5/7] nouveau/gsp: fix vblank interrupts on GB20x
  2026-08-14 23:57   ` Mohamed Ahmed
  (?)
@ 2026-08-15  0:08   ` sashiko-bot
  -1 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-15  0:08 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] NULL pointer dereference in `nvkm_uhead_mthd_scanoutpos` due to missing `.rgpos` callback in `gb202_head`.
- [Low] Build failure due to undeclared identifier `r535_head_state` in `gb202_head` struct.
--

--- Patch [5]: [PATCH 5/7] nouveau/gsp: fix vblank interrupts on GB20x ---
commit f0249296d0fe470f2e8d8e3f163056c84c1f8f5d
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

nouveau/gsp: fix vblank interrupts on GB20x

The GSP path programs per-head timing (vblank) interrupts the same way on
every generation. NVD5.0 (GB20x) reworked the FE interrupt frontend
around four message-based kernel vectors and moved RM head-timing
interrupts to the dedicated low-latency vector. This patch selects the
head functions and interrupt handler per generation to fix vblank interrupts.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
> index fd473b4d12a82..bc91d46e646d5 100644
> --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
[ ... ]
> @@ -692,6 +692,33 @@ r535_head = {
>  	.vblank_put = r535_head_vblank_put,
>  };
>  
[ ... ]
> +static const struct nvkm_head_func
> +gb202_head = {
> +	.state = r535_head_state,

[Severity: Low]
Does r535_head_state exist? It appears this might cause a build failure
due to an undeclared identifier. The existing r535_head struct uses
gv100_head_state for this field.

[Severity: High]
Is it safe to omit the rgpos callback here?

When userspace queries the head scanout position via NVIF_HEAD_V0_SCANOUTPOS,
execution goes to nvkm_uhead_mthd_scanoutpos(), which unconditionally
dereferences head->func->rgpos:

    head->func->rgpos(head, &args->v0.hline, &args->v0.vline);

Without rgpos being set on gb202_head, will this cause a NULL pointer
dereference when accessed from userspace?

> +	.vblank_get = gb202_head_vblank_get,
> +	.vblank_put = gb202_head_vblank_put,
> +};
> +

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260814235705.59132-1-mohamedahmedegypt2001@gmail.com?part=5

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 2/7] nouveau/gsp: fix HDMI vendor infoframes on GB20x
  2026-08-14 23:57   ` Mohamed Ahmed
  (?)
@ 2026-08-15  0:08   ` sashiko-bot
  -1 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-15  0:08 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `gb202_sor_hdmi` structure omits the `.scdc` callback, which breaks HDMI 2.0+ (SCDC) support and high-bandwidth modes (like 4K@60Hz) on GB20x GPUs.
- [Low] The patch assigns an undeclared function `r535_sor_frl_train` to a non-existent struct member `.frl_train` in `gb202_sor_hdmi`, causing a hard build failure.
--

--- Patch [2]: [PATCH 2/7] nouveau/gsp: fix HDMI vendor infoframes on GB20x ---
commit bab0e0425109e6d57a40d900a50e9f7f22ac57de
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

nouveau/gsp: fix HDMI vendor infoframes on GB20x

This commit selects the SOR functions per GPU family in r535_sor_new() based
on the display root class. It introduces gb202_sor_hdmi to use the new
generic-infoframe VSI writer on GB20x GPUs.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
> index e77733a5d9c3c..6b78619840c9c 100644
> --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c
> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c

[ ... ]

> @@ -587,6 +589,16 @@ r535_sor_hdmi = {
>  	.audio = r535_sor_hdmi_audio,
>  };
>  
> +static const struct nvkm_ior_func_hdmi
> +gb202_sor_hdmi = {
> +	.ctrl = r535_sor_hdmi_ctrl,
> +	/* The legacy AVI unit is unchanged on GB20x. */
> +	.infoframe_avi = gv100_sor_hdmi_infoframe_avi,
> +	.infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
> +	.audio = r535_sor_hdmi_audio,

[Severity: High]
Does this initialization omit the .scdc callback mapping?

Looking at nvkm_uoutp_mthd_hdmi(), if userspace requests a high-bandwidth
mode that depends on SCDC scrambling, the modeset aborts if the callback is
missing:

drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c:nvkm_uoutp_mthd_hdmi() {
	if (!ior->func->hdmi ||
	    args->v0.max_ac_packet > 0x1f ||
	    args->v0.rekey > 0x7f ||
	    (args->v0.scdc && !ior->func->hdmi->scdc))
		return -EINVAL;
	...
}

Can this result in broken HDMI output or black screens for high-bandwidth
modes (like 4K@60Hz) on GB20x GPUs?

> +	.frl_train = r535_sor_frl_train,

[Severity: Low]
Does this cause a build failure?

Looking at the definition of struct nvkm_ior_func_hdmi in
drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h, it does not contain a
.frl_train member.

Additionally, it appears r535_sor_frl_train is undeclared in this scope.

> +};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260814235705.59132-1-mohamedahmedegypt2001@gmail.com?part=2

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 4/7] nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware
  2026-08-14 23:57   ` Mohamed Ahmed
  (?)
@ 2026-08-15  0:13   ` sashiko-bot
  -1 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-15  0:13 UTC (permalink / raw)
  To: Mohamed Ahmed; +Cc: dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Integer underflow when computing MST.slotEnd for disabled outputs, leading to a massive slotEnd value (0xFFFFFFFF).
--

--- Patch [4]: [PATCH 4/7] nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware ---
commit d855125b895d27bafd2aaed0c330ae6eae007aef
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>

nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware

This commit separates the DP_CONFIG_STREAM_PARAMS structure and its related
functions into r535 and r570 variants. This handles the structure shrinking
that occurred in the 570 firmware version.

> diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
> index a96e31c2d80b2..61f82e15ff398 100644
> --- a/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
> +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/gsp/rm/r570/disp.c
[ ... ]
> @@ -74,6 +75,67 @@ r570_disp_chan_set_pushbuf(struct nvkm_disp *disp, s32 oclass, int inst, struct
>  	return nvkm_gsp_rm_ctrl_wr(&gsp->internal.device.subdevice, ctrl);
>  }
>  
> +static void
> +r570_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
> +{
> +	struct nvkm_disp *disp = sor->disp;
> +	NV0073_CTRL_CMD_DP_CONFIG_STREAM_PARAMS *ctrl;
[ ... ]
> +	ctrl->MST.slotStart = slot;
> +	ctrl->MST.slotEnd = slot + slot_nr - 1;

[Severity: High]
This isn't a bug introduced by this patch, but this preexisting issue gets
copied into the new r570_dp_vcpi() function from the r535 code.

When an MST output is disabled, nv50_msto_prepare() (in dispnv50/disp.c)
calls nvif_outp_dp_mst_vcpi() with num_slots=0. This propagates here, setting
slot_nr to 0.

With slot_nr = 0, does the expression "slot + slot_nr - 1" evaluate to -1?
Since MST.slotEnd is an NvU32, this would underflow to 0xFFFFFFFF, passing
a maximal bound for a time slot range to the GSP firmware. 

Could this potentially cause out-of-bounds accesses or hangs in the firmware?
Should this logic be updated to handle slot_nr = 0 safely before it evaluates
to -1 and underflows?

> +	ctrl->MST.PBN = pbn;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260814235705.59132-1-mohamedahmedegypt2001@gmail.com?part=4

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2026-08-15  0:13 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-14 23:56 [PATCH 0/7] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
2026-08-14 23:56 ` Mohamed Ahmed
2026-08-14 23:56 ` [PATCH 1/7] nouveau/disp: add GB20x HDMI vendor infoframe writer Mohamed Ahmed
2026-08-14 23:56   ` Mohamed Ahmed
2026-08-15  0:08   ` sashiko-bot
2026-08-14 23:57 ` [PATCH 2/7] nouveau/gsp: fix HDMI vendor infoframes on GB20x Mohamed Ahmed
2026-08-14 23:57   ` Mohamed Ahmed
2026-08-15  0:08   ` sashiko-bot
2026-08-14 23:57 ` [PATCH 3/7] nouveau/gsp: fix HDMI GCP AVMute register offsets " Mohamed Ahmed
2026-08-14 23:57   ` Mohamed Ahmed
2026-08-14 23:57 ` [PATCH 4/7] nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware Mohamed Ahmed
2026-08-14 23:57   ` Mohamed Ahmed
2026-08-15  0:13   ` sashiko-bot
2026-08-14 23:57 ` [PATCH 5/7] nouveau/gsp: fix vblank interrupts on GB20x Mohamed Ahmed
2026-08-14 23:57   ` Mohamed Ahmed
2026-08-15  0:08   ` sashiko-bot
2026-08-14 23:57 ` [PATCH 6/7] nouveau/dispnv50: program pixel clocks above 2.147GHz " Mohamed Ahmed
2026-08-14 23:57   ` Mohamed Ahmed
2026-08-14 23:57 ` [PATCH 7/7] nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid Mohamed Ahmed
2026-08-14 23:57   ` Mohamed Ahmed
2026-08-15  0:08   ` sashiko-bot

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.