* [PATCH v3 01/10] drm/nouveau/disp: move GSP head-timing ISR and vblank helpers to tu102.c
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
@ 2026-08-25 0:13 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 02/10] drm/nouveau/disp: move the GSP HDMI GCP AVMute write to engine/disp Mohamed Ahmed
` (8 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:13 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, stable
The GSP-RM display code in rm/r535/disp.c owns a handful of direct MMIO
routines: the head-timing (vblank) interrupt handler and the per-head
vblank enable/disable. They program display registers, not RM, so they
belong with the rest of the per-chip register code in engine/disp/.
Move them to tu102.c (Turing is the first GSP-capable generation) as
tu102_disp_intr() and tu102_head_vblank_get()/put(), exported for
rm/r535/disp.c, which keeps calling them by name for now. No functional
change.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../gpu/drm/nouveau/nvkm/engine/disp/head.h | 3 ++
.../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 1 +
.../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 50 ++++++++++++++++++
.../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 52 ++-----------------
4 files changed, 57 insertions(+), 49 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
index b642729c254f..986043e87554 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
@@ -56,6 +56,9 @@ int gv100_head_new(struct nvkm_disp *, int id);
void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state);
void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline);
+void tu102_head_vblank_get(struct nvkm_head *);
+void tu102_head_vblank_put(struct nvkm_head *);
+
#define HEAD_MSG(h,l,f,a...) do { \
struct nvkm_head *_h = (h); \
nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a); \
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
index a3fd7cb7c488..722ec340e12a 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
@@ -72,6 +72,7 @@ int gv100_disp_wndw_cnt(struct nvkm_disp *, unsigned long *);
int gv100_disp_caps_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int tu102_disp_init(struct nvkm_disp *);
+irqreturn_t tu102_disp_intr(struct nvkm_inth *);
void nv50_disp_dptmds_war_2(struct nvkm_disp *, struct dcb_output *);
void nv50_disp_dptmds_war_3(struct nvkm_disp *, struct dcb_output *);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
index dcb9f8ba374c..7b70b466fa36 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
@@ -104,6 +104,56 @@ tu102_sor_new(struct nvkm_disp *disp, int id)
return nvkm_ior_new_(&tu102_sor, disp, SOR, id, hda & BIT(id));
}
+/* The GSP-RM display path leaves head-timing (vblank) interrupts and their
+ * enables to us. These program the RM head-timing line (bit 1 of the
+ * per-head enable, not the bit nvkm's own gv100 path uses).
+ */
+void
+tu102_head_vblank_put(struct nvkm_head *head)
+{
+ struct nvkm_device *device = head->disp->engine.subdev.device;
+
+ nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
+}
+
+void
+tu102_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, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
+}
+
+static void
+tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head)
+{
+ struct nvkm_subdev *subdev = &disp->engine.subdev;
+ struct nvkm_device *device = subdev->device;
+ u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));
+
+ if (stat & 0x00000002) {
+ nvkm_disp_vblank(disp, head);
+
+ nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
+ }
+}
+
+irqreturn_t
+tu102_disp_intr(struct nvkm_inth *inth)
+{
+ struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
+ struct nvkm_subdev *subdev = &disp->engine.subdev;
+ struct nvkm_device *device = subdev->device;
+ unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
+ int head;
+
+ for_each_set_bit(head, &mask, 8)
+ tu102_disp_intr_head_timing(disp, head);
+
+ return IRQ_HANDLED;
+}
+
int
tu102_disp_init(struct nvkm_disp *disp)
{
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 e77733a5d9c3..8e57bb6519e5 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
@@ -608,29 +608,12 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
return 4;
}
-static void
-r535_head_vblank_put(struct nvkm_head *head)
-{
- struct nvkm_device *device = head->disp->engine.subdev.device;
-
- nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
-}
-
-static void
-r535_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, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
-}
-
static const struct nvkm_head_func
r535_head = {
.state = gv100_head_state,
.rgpos = gv100_head_rgpos,
- .vblank_get = r535_head_vblank_get,
- .vblank_put = r535_head_vblank_put,
+ .vblank_get = tu102_head_vblank_get,
+ .vblank_put = tu102_head_vblank_put,
};
static struct nvkm_conn *
@@ -1404,35 +1387,6 @@ static const struct nvkm_event_func
r535_disp_event = {
};
-static void
-r535_disp_intr_head_timing(struct nvkm_disp *disp, int head)
-{
- struct nvkm_subdev *subdev = &disp->engine.subdev;
- struct nvkm_device *device = subdev->device;
- u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));
-
- if (stat & 0x00000002) {
- nvkm_disp_vblank(disp, head);
-
- nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
- }
-}
-
-static irqreturn_t
-r535_disp_intr(struct nvkm_inth *inth)
-{
- struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
- struct nvkm_subdev *subdev = &disp->engine.subdev;
- struct nvkm_device *device = subdev->device;
- unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
- int head;
-
- for_each_set_bit(head, &mask, 8)
- r535_disp_intr_head_timing(disp, head);
-
- return IRQ_HANDLED;
-}
-
static void
r535_disp_fini(struct nvkm_disp *disp, bool suspend)
{
@@ -1708,7 +1662,7 @@ r535_disp_oneinit(struct nvkm_disp *disp)
return ret;
ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
- r535_disp_intr, &disp->engine.subdev.inth);
+ tu102_disp_intr, &disp->engine.subdev.inth);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 01/10] drm/nouveau/disp: move GSP head-timing ISR and vblank helpers to tu102.c
@ 2026-08-25 0:13 ` Mohamed Ahmed
0 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:13 UTC (permalink / raw)
To: linux-kernel
Cc: dri-devel, Danilo Krummrich, Maarten Lankhorst, Maxime Ripard,
Simona Vetter, Mary Guillemard, nouveau, Mohamed Ahmed, stable
The GSP-RM display code in rm/r535/disp.c owns a handful of direct MMIO
routines: the head-timing (vblank) interrupt handler and the per-head
vblank enable/disable. They program display registers, not RM, so they
belong with the rest of the per-chip register code in engine/disp/.
Move them to tu102.c (Turing is the first GSP-capable generation) as
tu102_disp_intr() and tu102_head_vblank_get()/put(), exported for
rm/r535/disp.c, which keeps calling them by name for now. No functional
change.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../gpu/drm/nouveau/nvkm/engine/disp/head.h | 3 ++
.../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 1 +
.../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 50 ++++++++++++++++++
.../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 52 ++-----------------
4 files changed, 57 insertions(+), 49 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
index b642729c254f..986043e87554 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
@@ -56,6 +56,9 @@ int gv100_head_new(struct nvkm_disp *, int id);
void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state);
void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline);
+void tu102_head_vblank_get(struct nvkm_head *);
+void tu102_head_vblank_put(struct nvkm_head *);
+
#define HEAD_MSG(h,l,f,a...) do { \
struct nvkm_head *_h = (h); \
nvkm_##l(&_h->disp->engine.subdev, "head-%d: "f"\n", _h->id, ##a); \
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
index a3fd7cb7c488..722ec340e12a 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
@@ -72,6 +72,7 @@ int gv100_disp_wndw_cnt(struct nvkm_disp *, unsigned long *);
int gv100_disp_caps_new(const struct nvkm_oclass *, void *, u32, struct nvkm_object **);
int tu102_disp_init(struct nvkm_disp *);
+irqreturn_t tu102_disp_intr(struct nvkm_inth *);
void nv50_disp_dptmds_war_2(struct nvkm_disp *, struct dcb_output *);
void nv50_disp_dptmds_war_3(struct nvkm_disp *, struct dcb_output *);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
index dcb9f8ba374c..7b70b466fa36 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
@@ -104,6 +104,56 @@ tu102_sor_new(struct nvkm_disp *disp, int id)
return nvkm_ior_new_(&tu102_sor, disp, SOR, id, hda & BIT(id));
}
+/* The GSP-RM display path leaves head-timing (vblank) interrupts and their
+ * enables to us. These program the RM head-timing line (bit 1 of the
+ * per-head enable, not the bit nvkm's own gv100 path uses).
+ */
+void
+tu102_head_vblank_put(struct nvkm_head *head)
+{
+ struct nvkm_device *device = head->disp->engine.subdev.device;
+
+ nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
+}
+
+void
+tu102_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, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
+}
+
+static void
+tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head)
+{
+ struct nvkm_subdev *subdev = &disp->engine.subdev;
+ struct nvkm_device *device = subdev->device;
+ u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));
+
+ if (stat & 0x00000002) {
+ nvkm_disp_vblank(disp, head);
+
+ nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
+ }
+}
+
+irqreturn_t
+tu102_disp_intr(struct nvkm_inth *inth)
+{
+ struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
+ struct nvkm_subdev *subdev = &disp->engine.subdev;
+ struct nvkm_device *device = subdev->device;
+ unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
+ int head;
+
+ for_each_set_bit(head, &mask, 8)
+ tu102_disp_intr_head_timing(disp, head);
+
+ return IRQ_HANDLED;
+}
+
int
tu102_disp_init(struct nvkm_disp *disp)
{
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 e77733a5d9c3..8e57bb6519e5 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
@@ -608,29 +608,12 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
return 4;
}
-static void
-r535_head_vblank_put(struct nvkm_head *head)
-{
- struct nvkm_device *device = head->disp->engine.subdev.device;
-
- nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
-}
-
-static void
-r535_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, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
-}
-
static const struct nvkm_head_func
r535_head = {
.state = gv100_head_state,
.rgpos = gv100_head_rgpos,
- .vblank_get = r535_head_vblank_get,
- .vblank_put = r535_head_vblank_put,
+ .vblank_get = tu102_head_vblank_get,
+ .vblank_put = tu102_head_vblank_put,
};
static struct nvkm_conn *
@@ -1404,35 +1387,6 @@ static const struct nvkm_event_func
r535_disp_event = {
};
-static void
-r535_disp_intr_head_timing(struct nvkm_disp *disp, int head)
-{
- struct nvkm_subdev *subdev = &disp->engine.subdev;
- struct nvkm_device *device = subdev->device;
- u32 stat = nvkm_rd32(device, 0x611c00 + (head * 0x04));
-
- if (stat & 0x00000002) {
- nvkm_disp_vblank(disp, head);
-
- nvkm_wr32(device, 0x611800 + (head * 0x04), 0x00000002);
- }
-}
-
-static irqreturn_t
-r535_disp_intr(struct nvkm_inth *inth)
-{
- struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
- struct nvkm_subdev *subdev = &disp->engine.subdev;
- struct nvkm_device *device = subdev->device;
- unsigned long mask = nvkm_rd32(device, 0x611ec0) & 0x000000ff;
- int head;
-
- for_each_set_bit(head, &mask, 8)
- r535_disp_intr_head_timing(disp, head);
-
- return IRQ_HANDLED;
-}
-
static void
r535_disp_fini(struct nvkm_disp *disp, bool suspend)
{
@@ -1708,7 +1662,7 @@ r535_disp_oneinit(struct nvkm_disp *disp)
return ret;
ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
- r535_disp_intr, &disp->engine.subdev.inth);
+ tu102_disp_intr, &disp->engine.subdev.inth);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 02/10] drm/nouveau/disp: move the GSP HDMI GCP AVMute write to engine/disp
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
2026-08-25 0:13 ` Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 03/10] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks Mohamed Ahmed
` (7 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
r535_sor_hdmi_audio() pairs two RM controls (a SET_OD_PACKET carrying
the same General Control Packet, and the audio mute-stream toggle)
with a direct write of the GCP AVMute bit through the SF GCP unit.
The controls are RM and stay, but the direct write is register
programming and moves next to the other per-chip display code as
tu102_sor_hdmi_gcp(). No functional change.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h | 1 +
drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 15 +++++++++++++++
.../drm/nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 9 +--------
3 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
index 3ba04bead2f9..5d682a774f2d 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ior.h
@@ -194,6 +194,7 @@ void gv100_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
void gv100_sor_dp_watermark(struct nvkm_ior *, int, u8);
extern const struct nvkm_ior_func_hda gv100_sor_hda;
+void tu102_sor_hdmi_gcp(struct nvkm_ior *, int, bool);
void tu102_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
int nv50_pior_cnt(struct nvkm_disp *, unsigned long *);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
index 7b70b466fa36..6cfd52c9056f 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
@@ -30,6 +30,21 @@
#include <nvif/class.h>
+/* General Control Packet: bracket an audio enable/disable with AVMute
+ * through the legacy GCP SF unit. Used by the GSP-RM path, which sends the
+ * equivalent packet via RM as well but keeps the direct write in sync.
+ */
+void
+tu102_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable)
+{
+ struct nvkm_device *device = sor->disp->engine.subdev.device;
+ const u32 hdmi = head * 0x400;
+
+ nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000);
+ nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010);
+ nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
+}
+
void
tu102_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned)
{
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 8e57bb6519e5..cd4451e62512 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
@@ -545,16 +545,9 @@ r535_sor_hdmi_ctrl_audio(struct nvkm_outp *outp, bool enable)
static void
r535_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). */
- nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000000);
- nvkm_wr32(device, 0x6f00cc + hdmi, !enable ? 0x00000001 : 0x00000010);
- nvkm_mask(device, 0x6f00c0 + hdmi, 0x00000001, 0x00000001);
+ tu102_sor_hdmi_gcp(sor, head, enable);
}
static void
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 03/10] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
2026-08-25 0:13 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 02/10] drm/nouveau/disp: move the GSP HDMI GCP AVMute write to engine/disp Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:25 ` sashiko-bot
2026-08-25 0:14 ` [PATCH v3 04/10] drm/nouveau/disp: fix HDMI vendor infoframes on GB20x Mohamed Ahmed
` (6 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
The GSP-RM display code in rm/r535/disp.c borrows a few
register-programming routines from engine/disp (the head-timing
interrupt handler, vblank enables, armed head state and scanout position
readback, the AVI/VSI infoframe writers and the GCP AVMute write) and so
far picked them by name, which means it has to know which chip it runs
on the moment a generation changes any of them.
Give nvkm_disp_func a .gsp table that each chip fills with exactly those
hooks, add tu102_gsp_disp (TU1xx) and ga102_gsp_disp (GA10x onwards)
carrying the current functions, hand them to r535_disp_new() instead of
the full hardware tables, and make rm/r535/disp.c call through the
hooks. The head hooks are a whole nvkm_head_func, so r535_head goes away
and the chip's own table is handed to nvkm_head_new_(). r535_sor_hdmi
gets infoframe forwarders, r535_sor_hdmi_audio() calls the GCP hook, and
the interrupt handler comes from the table. The tables are per chip even
though the two currently coincide, so a generation that changes a hook
only touches its own file.
rm/r535/disp.c no longer contains chip-specific register code, and a new
display generation only has to provide its own table. No functional
change.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../gpu/drm/nouveau/nvkm/engine/disp/ga102.c | 13 +++++++-
.../gpu/drm/nouveau/nvkm/engine/disp/head.h | 1 +
.../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 14 +++++++++
.../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 21 ++++++++++++-
.../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 31 +++++++++++--------
5 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c
index ab0a85c92430..820834b5ee9b 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/ga102.c
@@ -144,12 +144,23 @@ ga102_disp = {
},
};
+static const struct nvkm_disp_func
+ga102_gsp_disp = {
+ .uevent = &gv100_disp_chan_uevent,
+ .ramht_size = 0x2000,
+ .gsp.intr = tu102_disp_intr,
+ .gsp.head = &tu102_gsp_head,
+ .gsp.hdmi_gcp = tu102_sor_hdmi_gcp,
+ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
+ .gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
+};
+
int
ga102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
if (nvkm_gsp_rm(device->gsp))
- return r535_disp_new(&ga102_disp, device, type, inst, pdisp);
+ return r535_disp_new(&ga102_gsp_disp, device, type, inst, pdisp);
return nvkm_disp_new_(&ga102_disp, device, type, inst, pdisp);
}
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
index 986043e87554..784521c2aca1 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
@@ -58,6 +58,7 @@ void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline);
void tu102_head_vblank_get(struct nvkm_head *);
void tu102_head_vblank_put(struct nvkm_head *);
+extern const struct nvkm_head_func tu102_gsp_head;
#define HEAD_MSG(h,l,f,a...) do { \
struct nvkm_head *_h = (h); \
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
index 722ec340e12a..a9dbda67a7d4 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
@@ -5,6 +5,8 @@
#include <engine/disp.h>
#include <core/enum.h>
struct nvkm_head;
+struct nvkm_head_func;
+struct nvkm_ior;
struct nvkm_outp;
struct dcb_output;
@@ -34,6 +36,18 @@ struct nvkm_disp_func {
int (*new)(struct nvkm_disp *, int id);
} wndw, head, dac, sor, pior;
+ /* Register programming that the GSP-RM display path (rm/r535) needs from
+ * the chip, everything else on that path goes through RM. The hooks are
+ * called unconditionally and the head table is handed to nvkm_head_new_().
+ */
+ struct {
+ irqreturn_t (*intr)(struct nvkm_inth *);
+ const struct nvkm_head_func *head;
+ void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable);
+ void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size);
+ void (*hdmi_infoframe_vsi)(struct nvkm_ior *, int head, void *data, u32 size);
+ } gsp;
+
u16 ramht_size;
struct nvkm_sclass root;
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
index 6cfd52c9056f..948b1d2f954c 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
@@ -140,6 +140,14 @@ tu102_head_vblank_get(struct nvkm_head *head)
nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000002);
}
+const struct nvkm_head_func
+tu102_gsp_head = {
+ .state = gv100_head_state,
+ .rgpos = gv100_head_rgpos,
+ .vblank_get = tu102_head_vblank_get,
+ .vblank_put = tu102_head_vblank_put,
+};
+
static void
tu102_disp_intr_head_timing(struct nvkm_disp *disp, int head)
{
@@ -295,12 +303,23 @@ tu102_disp = {
},
};
+static const struct nvkm_disp_func
+tu102_gsp_disp = {
+ .uevent = &gv100_disp_chan_uevent,
+ .ramht_size = 0x2000,
+ .gsp.intr = tu102_disp_intr,
+ .gsp.head = &tu102_gsp_head,
+ .gsp.hdmi_gcp = tu102_sor_hdmi_gcp,
+ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
+ .gsp.hdmi_infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
+};
+
int
tu102_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
struct nvkm_disp **pdisp)
{
if (nvkm_gsp_rm(device->gsp))
- return r535_disp_new(&tu102_disp, device, type, inst, pdisp);
+ return r535_disp_new(&tu102_gsp_disp, device, type, inst, pdisp);
return nvkm_disp_new_(&tu102_disp, device, type, inst, pdisp);
}
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 cd4451e62512..bf97edcdfc95 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
@@ -547,7 +547,19 @@ r535_sor_hdmi_audio(struct nvkm_ior *sor, int head, bool enable)
{
r535_sor_hdmi_ctrl_audio(sor->asy.outp, enable);
r535_sor_hdmi_ctrl_audio_mute(sor->asy.outp, !enable);
- tu102_sor_hdmi_gcp(sor, head, enable);
+ sor->disp->func->gsp.hdmi_gcp(sor, head, enable);
+}
+
+static void
+r535_sor_hdmi_infoframe_avi(struct nvkm_ior *sor, int head, void *data, u32 size)
+{
+ sor->disp->func->gsp.hdmi_infoframe_avi(sor, head, data, size);
+}
+
+static void
+r535_sor_hdmi_infoframe_vsi(struct nvkm_ior *sor, int head, void *data, u32 size)
+{
+ sor->disp->func->gsp.hdmi_infoframe_vsi(sor, head, data, size);
}
static void
@@ -575,8 +587,8 @@ r535_sor_hdmi = {
.ctrl = r535_sor_hdmi_ctrl,
.scdc = r535_sor_hdmi_scdc,
/*TODO: SF_USER -> KMS. */
- .infoframe_avi = gv100_sor_hdmi_infoframe_avi,
- .infoframe_vsi = gv100_sor_hdmi_infoframe_vsi,
+ .infoframe_avi = r535_sor_hdmi_infoframe_avi,
+ .infoframe_vsi = r535_sor_hdmi_infoframe_vsi,
.audio = r535_sor_hdmi_audio,
};
@@ -601,14 +613,6 @@ r535_sor_cnt(struct nvkm_disp *disp, unsigned long *pmask)
return 4;
}
-static const struct nvkm_head_func
-r535_head = {
- .state = gv100_head_state,
- .rgpos = gv100_head_rgpos,
- .vblank_get = tu102_head_vblank_get,
- .vblank_put = tu102_head_vblank_put,
-};
-
static struct nvkm_conn *
r535_conn_new(struct nvkm_disp *disp, u32 id)
{
@@ -1606,7 +1610,7 @@ 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);
+ ret = nvkm_head_new_(disp->func->gsp.head, disp, i);
if (ret)
return ret;
}
@@ -1655,7 +1659,7 @@ r535_disp_oneinit(struct nvkm_disp *disp)
return ret;
ret = nvkm_inth_add(&device->vfn->intr, ret, NVKM_INTR_PRIO_NORMAL, &disp->engine.subdev,
- tu102_disp_intr, &disp->engine.subdev.inth);
+ disp->func->gsp.intr, &disp->engine.subdev.inth);
if (ret)
return ret;
@@ -1688,6 +1692,7 @@ r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device,
rm->uevent = hw->uevent;
rm->sor.cnt = r535_sor_cnt;
rm->sor.new = r535_sor_new;
+ rm->gsp = hw->gsp;
rm->ramht_size = hw->ramht_size;
rm->root.oclass = gpu->disp.class.root;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v3 03/10] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks
2026-08-25 0:14 ` [PATCH v3 03/10] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks Mohamed Ahmed
@ 2026-08-25 0:25 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-25 0:25 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] NULL pointer dereference and Use-After-Free on initialization failure in `r535_disp_new`.
--
--- Patch [3]: [PATCH v3 03/10] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks ---
commit b67572d820daaaf9db8443eb286700e054288fb6
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks
This patch refactors the GSP-RM display MMIO routing by introducing a
gsp table in nvkm_disp_func. This allows r535_disp_new() to call hooks
specific to each chip generation rather than relying on hardcoded
functions. The changes remove chip-specific register code from
rm/r535/disp.c without altering functionality.
> 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 cd4451e625122..bf97edcdfc959 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
[ ... ]
> @@ -1688,6 +1692,7 @@ r535_disp_new(const struct nvkm_disp_func *hw, struct nvkm_device *device,
> rm->uevent = hw->uevent;
> rm->sor.cnt = r535_sor_cnt;
> rm->sor.new = r535_sor_new;
> + rm->gsp = hw->gsp;
> rm->ramht_size = hw->ramht_size;
>
> rm->root.oclass = gpu->disp.class.root;
[ ... ]
> ret = nvkm_disp_new_(rm, device, type, inst, pdisp);
> if (ret)
> kfree(rm);
>
> mutex_init(&(*pdisp)->super.mutex); //XXX
[Severity: High]
This is a pre-existing issue, but I noticed a potential NULL pointer
dereference and use-after-free here on initialization failure.
If nvkm_disp_new_() fails early due to an allocation failure, it can leave
*pdisp set to NULL. Does this cause mutex_init(&(*pdisp)->super.mutex) to
unconditionally dereference NULL?
Additionally, if nvkm_disp_new_() fails after linking the subdev to the
device, freeing rm with kfree(rm) could leave a dangling disp->func pointer.
During teardown, could the system invoke the destructor via this freed
pointer, resulting in a use-after-free?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825001408.14219-1-mohamedahmedegypt2001@gmail.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 04/10] drm/nouveau/disp: fix HDMI vendor infoframes on GB20x
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (2 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 03/10] drm/nouveau/disp: route GSP-RM display MMIO through nvkm_disp_func hooks Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 05/10] drm/nouveau/disp: fix HDMI GCP AVMute register offsets " Mohamed Ahmed
` (5 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
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 HW. This affects HDMI-VIC signalling which
can impact some 4K modes for legacy HDMI 1.4 sinks.
GB20x (NVDisplay 5.0+) reorganised the SF HDMI packet units. Per NVIDIA's
published C971/CA71 DISP_SF_USER class headers, 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, whose 9-dword packet slots are loaded through the
shared data port 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 port with a zero inserted in HB3 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).
GB20x so far shared GA10x's display entry point. Give it its own,
gb202_disp_new(), with a gb202_gsp_disp table that supplies the VSI
writer to the GSP path and otherwise carries the same hooks as GA10x.
The following fixes fill in the rest of the GB20x differences there.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../drm/nouveau/include/nvkm/engine/disp.h | 1 +
.../gpu/drm/nouveau/nvkm/engine/device/base.c | 10 +--
.../gpu/drm/nouveau/nvkm/engine/disp/Kbuild | 1 +
.../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 88 +++++++++++++++++++
4 files changed, 95 insertions(+), 5 deletions(-)
create mode 100644 drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
diff --git a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
index 7903d7470d19..01145db32c53 100644
--- a/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
+++ b/drivers/gpu/drm/nouveau/include/nvkm/engine/disp.h
@@ -87,4 +87,5 @@ int gp102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct
int gv100_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
int tu102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
int ga102_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
+int gb202_disp_new(struct nvkm_device *, enum nvkm_subdev_type, int inst, struct nvkm_disp **);
#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c
index ea62dc97f118..96c8a5b29999 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/device/base.c
@@ -2846,7 +2846,7 @@ nv1b2_chipset = {
.pci = { 0x00000001, gh100_pci_new },
.timer = { 0x00000001, gk20a_timer_new },
.vfn = { 0x00000001, ga100_vfn_new },
- .disp = { 0x00000001, ga102_disp_new },
+ .disp = { 0x00000001, gb202_disp_new },
.fifo = { 0x00000001, ga102_fifo_new },
};
@@ -2862,7 +2862,7 @@ nv1b3_chipset = {
.pci = { 0x00000001, gh100_pci_new },
.timer = { 0x00000001, gk20a_timer_new },
.vfn = { 0x00000001, ga100_vfn_new },
- .disp = { 0x00000001, ga102_disp_new },
+ .disp = { 0x00000001, gb202_disp_new },
.fifo = { 0x00000001, ga102_fifo_new },
};
@@ -2878,7 +2878,7 @@ nv1b5_chipset = {
.pci = { 0x00000001, gh100_pci_new },
.timer = { 0x00000001, gk20a_timer_new },
.vfn = { 0x00000001, ga100_vfn_new },
- .disp = { 0x00000001, ga102_disp_new },
+ .disp = { 0x00000001, gb202_disp_new },
.fifo = { 0x00000001, ga102_fifo_new },
};
@@ -2894,7 +2894,7 @@ nv1b6_chipset = {
.pci = { 0x00000001, gh100_pci_new },
.timer = { 0x00000001, gk20a_timer_new },
.vfn = { 0x00000001, ga100_vfn_new },
- .disp = { 0x00000001, ga102_disp_new },
+ .disp = { 0x00000001, gb202_disp_new },
.fifo = { 0x00000001, ga102_fifo_new },
};
@@ -2910,7 +2910,7 @@ nv1b7_chipset = {
.pci = { 0x00000001, gh100_pci_new },
.timer = { 0x00000001, gk20a_timer_new },
.vfn = { 0x00000001, ga100_vfn_new },
- .disp = { 0x00000001, ga102_disp_new },
+ .disp = { 0x00000001, gb202_disp_new },
.fifo = { 0x00000001, ga102_fifo_new },
};
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..1e40de83e2bb
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2026 Valve Corp.
+ */
+#include "priv.h"
+#include "head.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().
+ */
+static 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 port at unit 1's slot. */
+ nvkm_mask(device, ctrl, 0x00800000, 0x00800000);
+ nvkm_wr32(device, 0x6f03f0 + hoff, 0x00000001);
+
+ /* The data port takes the raw packet, except that a zero is inserted
+ * in HB3 after the three header bytes. A slot is 9 dwords (HB0-3 plus
+ * up to 32 payload bytes). An HDMI infoframe carries at most PB0-27,
+ * so the tail stays zero, and we always write the whole slot.
+ */
+ 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 |
+ (u32)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);
+}
+
+/* GB20x is GSP-only. This table supplies the register programming the
+ * GSP-RM display path needs from the chip.
+ */
+static const struct nvkm_disp_func
+gb202_gsp_disp = {
+ .uevent = &gv100_disp_chan_uevent,
+ .ramht_size = 0x2000,
+ .gsp.intr = tu102_disp_intr,
+ .gsp.head = &tu102_gsp_head,
+ .gsp.hdmi_gcp = tu102_sor_hdmi_gcp,
+ /* The legacy AVI unit is unchanged on GB20x. */
+ .gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
+ .gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
+};
+
+int
+gb202_disp_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst,
+ struct nvkm_disp **pdisp)
+{
+ return r535_disp_new(&gb202_gsp_disp, device, type, inst, pdisp);
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 05/10] drm/nouveau/disp: fix HDMI GCP AVMute register offsets on GB20x
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (3 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 04/10] drm/nouveau/disp: fix HDMI vendor infoframes on GB20x Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 06/10] drm/nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware Mohamed Ahmed
` (4 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
The GSP path brackets audio enablement with a General Control Packet
AVMute toggle. r535_sor_hdmi_audio() calls the gsp.hdmi_gcp hook, which
every chip so far serves with tu102_sor_hdmi_gcp() and the legacy GCP
unit at 0x6f00c0/0x6f00cc. On GB20x the SF packet units were compacted
and the old generic and VSI units are gone (ACR keeps slot 2) 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 r535_sor_hdmi_audio()
already sends through the SET_OD_PACKET RM control.
Add a GB20x GCP writer using the new offsets and hook it into
gb202_gsp_disp, keeping the direct MMIO path in sync with the hardware
as on earlier chips.
Only SB0 (the AVMute bit) is written. On NVD5.0 the subpack register also
carries SB1_CTRL (bit 24), which selects where the deep-color CD/PP
fields are generated (hardware or from the driver, with the default being
HW). hdmiWriteGeneralCtrlPacketC871() likewise writes only SB0-SB2.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 20 ++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
index 1e40de83e2bb..face801af080 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
@@ -65,6 +65,24 @@ gb202_sor_hdmi_infoframe_vsi(struct nvkm_ior *ior, int head, void *data, u32 siz
nvkm_wr32(device, 0x6f03f8 + hoff, 0x00000002);
}
+/* General Control Packet AVMute bracket. The GCP unit moved to slot 1 on
+ * NVD5.0. Only SB0 (the AVMute bit) is ours to write so we must not do a
+ * full write here: SB1 carries the deep-color CD/PP fields, and SB1_CTRL
+ * (bit 24, new with clc871.h) controls where their generation happens (HW
+ * or driver) on these chips, with the default being HW.
+ */
+static void
+gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable)
+{
+ struct nvkm_device *device = sor->disp->engine.subdev.device;
+ const u32 hdmi = head * 0x400;
+
+ nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000000);
+ nvkm_mask(device, 0x6f004c + hdmi, 0x000000ff, !enable ? 0x00000001 :
+ 0x00000010);
+ nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001);
+}
+
/* GB20x is GSP-only. This table supplies the register programming the
* GSP-RM display path needs from the chip.
*/
@@ -74,7 +92,7 @@ gb202_gsp_disp = {
.ramht_size = 0x2000,
.gsp.intr = tu102_disp_intr,
.gsp.head = &tu102_gsp_head,
- .gsp.hdmi_gcp = tu102_sor_hdmi_gcp,
+ .gsp.hdmi_gcp = gb202_sor_hdmi_gcp,
/* The legacy AVI unit is unchanged on GB20x. */
.gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
.gsp.hdmi_infoframe_vsi = gb202_sor_hdmi_infoframe_vsi,
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 06/10] drm/nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (4 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 05/10] drm/nouveau/disp: fix HDMI GCP AVMute register offsets " Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 07/10] drm/nouveau/disp: fix head state readback on GB20x Mohamed Ahmed
` (3 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
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
except the trailing SST.bEnableAudioOverRightPanel (written as zero), 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.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 33 ++++++++--
.../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, 97 insertions(+), 7 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 bf97edcdfc95..7d1d4ee2af79 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
@@ -400,16 +400,16 @@ r535_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
r535_sor_dp_audio_mute(sor, false);
}
-static void
-r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u16 aligned_pbn)
+static int
+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;
ctrl = nvkm_gsp_rm_ctrl_get(&disp->rm.objcom,
NV0073_CTRL_CMD_DP_CONFIG_STREAM, sizeof(*ctrl));
- if (WARN_ON(IS_ERR(ctrl)))
- return;
+ if (IS_ERR(ctrl))
+ return PTR_ERR(ctrl);
ctrl->subDeviceInstance = 0;
ctrl->head = head;
@@ -429,12 +429,20 @@ r535_sor_dp_vcpi(struct nvkm_ior *sor, int head, u8 slot, u8 slot_nr, u16 pbn, u
ctrl->MST.sendACT = 0;
ctrl->MST.singleHeadMSTPipeline = 0;
ctrl->MST.bEnableAudioOverRightPanel = 0;
- WARN_ON(nvkm_gsp_rm_ctrl_wr(&disp->rm.objcom, ctrl));
+ return 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;
+
+ WARN_ON(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;
@@ -461,6 +469,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,
@@ -1734,6 +1751,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..8a23837f356e 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 int
+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 (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 = 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;
+ return 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 0fb0e67406c6..e9ac47d86b69 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);
+ int (*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] 14+ messages in thread* [PATCH v3 07/10] drm/nouveau/disp: fix head state readback on GB20x
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (5 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 06/10] drm/nouveau/gsp: use per-version DP_CONFIG_STREAM params on r570 firmware Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 08/10] drm/nouveau/gsp: fix vblank interrupts " Mohamed Ahmed
` (2 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
The GSP path reads armed head state and the RG scanout position through
gv100_head_state() and gv100_head_rgpos() on every generation.
gv100_head_state() reads the core channel's state mirror at a 0x400
per-head stride, which NVD5.0 (GB20x) doubled. Per NVIDIA's published
CA7D class header every HEAD_SET method sits at 0x2000 + head * 0x800,
while the mirror bases are unchanged (assembly at 0x680000, armed at
+0x8000, per OpenRM's v03_00 channel-user-base HAL which is still used on
DISPv0502).
Add gb202_head_state(), the same readback at the 0x800 stride, and a
gb202_gsp_head table to supply it.
gv100_head_rgpos() is kept. The RG registers keep their per-head 0x800
stride on NVD5.0, and OpenRM's kdispReadRgLineCountAndFrameCount_v03_00
still reads NV_PDISP_RG_DPCA on DISPv0502.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 51 ++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
index face801af080..765c42039a47 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
@@ -83,6 +83,55 @@ gb202_sor_hdmi_gcp(struct nvkm_ior *sor, int head, bool enable)
nvkm_mask(device, 0x6f0040 + hdmi, 0x00000001, 0x00000001);
}
+/* Same core-channel state mirror as gv100_head_state() (assembly at 0x680000,
+ * armed at +0x8000, per-head method offsets unchanged), but NVD5.0 spaces
+ * heads 0x800 apart (see NVCA7D_HEAD_SET_*(a) in clca7d.h).
+ */
+static void
+gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
+{
+ struct nvkm_device *device = head->disp->engine.subdev.device;
+ const u32 hoff = (state == &head->arm) * 0x8000 + head->id * 0x800;
+ u32 data;
+
+ data = nvkm_rd32(device, 0x682064 + hoff);
+ state->vtotal = (data & 0xffff0000) >> 16;
+ state->htotal = (data & 0x0000ffff);
+ data = nvkm_rd32(device, 0x682068 + hoff);
+ state->vsynce = (data & 0xffff0000) >> 16;
+ state->hsynce = (data & 0x0000ffff);
+ data = nvkm_rd32(device, 0x68206c + hoff);
+ state->vblanke = (data & 0xffff0000) >> 16;
+ state->hblanke = (data & 0x0000ffff);
+ data = nvkm_rd32(device, 0x682070 + hoff);
+ state->vblanks = (data & 0xffff0000) >> 16;
+ state->hblanks = (data & 0x0000ffff);
+ /* Bit 31 is ADJ1000DIV1001, not a HERTZ bit. We don't have enough bits
+ * to add the full clock in hz on Blackwell (35 bits), but state->hz
+ * is unused and obsolete under GSP so this is fine.
+ */
+ state->hz = nvkm_rd32(device, 0x68200c + hoff) & 0x7fffffff;
+
+ data = nvkm_rd32(device, 0x682004 + hoff);
+ switch ((data & 0x000000f0) >> 4) {
+ case 5: state->or.depth = 30; break;
+ case 4: state->or.depth = 24; break;
+ case 1: state->or.depth = 18; break;
+ default:
+ state->or.depth = 18;
+ WARN_ON(1);
+ break;
+ }
+}
+
+static const struct nvkm_head_func
+gb202_gsp_head = {
+ .state = gb202_head_state,
+ .rgpos = gv100_head_rgpos,
+ .vblank_get = tu102_head_vblank_get,
+ .vblank_put = tu102_head_vblank_put,
+};
+
/* GB20x is GSP-only. This table supplies the register programming the
* GSP-RM display path needs from the chip.
*/
@@ -91,7 +140,7 @@ gb202_gsp_disp = {
.uevent = &gv100_disp_chan_uevent,
.ramht_size = 0x2000,
.gsp.intr = tu102_disp_intr,
- .gsp.head = &tu102_gsp_head,
+ .gsp.head = &gb202_gsp_head,
.gsp.hdmi_gcp = gb202_sor_hdmi_gcp,
/* The legacy AVI unit is unchanged on GB20x. */
.gsp.hdmi_infoframe_avi = gv100_sor_hdmi_infoframe_avi,
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 08/10] drm/nouveau/gsp: fix vblank interrupts on GB20x
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (6 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 07/10] drm/nouveau/disp: fix head state readback on GB20x Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 09/10] drm/nouveau/dispnv50: program pixel clocks above 2.147GHz " Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid Mohamed Ahmed
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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, stable
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, renamed kernel_head_0502.c from
575.51.02 on, and v05_01 dev_disp.h).
- 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.
Supply GB20x vblank enables and an interrupt handler that re-arms the
vector after servicing through gb202_gsp_disp, translate the low-latency
interrupt table entry as a second NVKM_ENGINE_DISP instance, and add a
gsp.intr_low_latency flag so r535_disp_oneinit() attaches the handler to
that instance. GB20x was the last cross-file user of the TU1xx vblank
enables, so make those static and drop their head.h prototypes.
Fixes: 6cc6e08d4542 ("drm/nouveau/kms: add support for GB20x")
Cc: stable@vger.kernel.org
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
.../gpu/drm/nouveau/nvkm/engine/disp/gb202.c | 42 +++++++++++++++++--
.../gpu/drm/nouveau/nvkm/engine/disp/head.h | 2 -
.../gpu/drm/nouveau/nvkm/engine/disp/priv.h | 2 +
.../gpu/drm/nouveau/nvkm/engine/disp/tu102.c | 4 +-
.../nouveau/nvkm/subdev/gsp/rm/r535/disp.c | 10 ++++-
.../drm/nouveau/nvkm/subdev/gsp/rm/r570/gsp.c | 9 ++++
6 files changed, 61 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
index 765c42039a47..d0360610f9fa 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/gb202.c
@@ -124,12 +124,46 @@ gb202_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
}
}
+/* 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 irqreturn_t
+gb202_disp_intr(struct nvkm_inth *inth)
+{
+ struct nvkm_disp *disp = container_of(inth, typeof(*disp), engine.subdev.inth);
+ irqreturn_t ret = tu102_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 const struct nvkm_head_func
gb202_gsp_head = {
.state = gb202_head_state,
.rgpos = gv100_head_rgpos,
- .vblank_get = tu102_head_vblank_get,
- .vblank_put = tu102_head_vblank_put,
+ .vblank_get = gb202_head_vblank_get,
+ .vblank_put = gb202_head_vblank_put,
};
/* GB20x is GSP-only. This table supplies the register programming the
@@ -139,7 +173,9 @@ static const struct nvkm_disp_func
gb202_gsp_disp = {
.uevent = &gv100_disp_chan_uevent,
.ramht_size = 0x2000,
- .gsp.intr = tu102_disp_intr,
+ /* Head timing arrives on the dedicated low-latency vector. */
+ .gsp.intr = gb202_disp_intr,
+ .gsp.intr_low_latency = true,
.gsp.head = &gb202_gsp_head,
.gsp.hdmi_gcp = gb202_sor_hdmi_gcp,
/* The legacy AVI unit is unchanged on GB20x. */
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
index 784521c2aca1..5976498da909 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/head.h
@@ -56,8 +56,6 @@ int gv100_head_new(struct nvkm_disp *, int id);
void gv100_head_state(struct nvkm_head *head, struct nvkm_head_state *state);
void gv100_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline);
-void tu102_head_vblank_get(struct nvkm_head *);
-void tu102_head_vblank_put(struct nvkm_head *);
extern const struct nvkm_head_func tu102_gsp_head;
#define HEAD_MSG(h,l,f,a...) do { \
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
index a9dbda67a7d4..fde321dbd7c8 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/priv.h
@@ -42,6 +42,8 @@ struct nvkm_disp_func {
*/
struct {
irqreturn_t (*intr)(struct nvkm_inth *);
+ /* Head-timing interrupts arrive on a second DISP vector. */
+ bool intr_low_latency;
const struct nvkm_head_func *head;
void (*hdmi_gcp)(struct nvkm_ior *, int head, bool enable);
void (*hdmi_infoframe_avi)(struct nvkm_ior *, int head, void *data, u32 size);
diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
index 948b1d2f954c..f6c163072ff6 100644
--- a/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
+++ b/drivers/gpu/drm/nouveau/nvkm/engine/disp/tu102.c
@@ -123,7 +123,7 @@ tu102_sor_new(struct nvkm_disp *disp, int id)
* enables to us. These program the RM head-timing line (bit 1 of the
* per-head enable, not the bit nvkm's own gv100 path uses).
*/
-void
+static void
tu102_head_vblank_put(struct nvkm_head *head)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
@@ -131,7 +131,7 @@ tu102_head_vblank_put(struct nvkm_head *head)
nvkm_mask(device, 0x611d80 + (head->id * 4), 0x00000002, 0x00000000);
}
-void
+static void
tu102_head_vblank_get(struct nvkm_head *head)
{
struct nvkm_device *device = head->disp->engine.subdev.device;
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 7d1d4ee2af79..f5f22173fc2c 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
@@ -1671,7 +1671,15 @@ 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);
+ /* Chips that raise head-timing interrupts on a separate low-latency
+ * vector report it as a second DISP interrupt table entry, exposed
+ * as instance 1 by the RM engine-index translation (see
+ * r570_gsp_xlat_mc_engine_idx()). Their high-latency vector
+ * (instance 0) is left unhandled as no event nouveau enables is
+ * routed to it, and without a handler it stays masked.
+ */
+ ret = nvkm_gsp_intr_stall(gsp, disp->engine.subdev.type,
+ disp->func->gsp.intr_low_latency ? 1 : disp->engine.subdev.inst);
if (ret < 0)
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 3e391646d8f7..b45781cd0dfd 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,15 @@ 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. r535_disp_oneinit() attaches the
+ * handler to it when the chip's gsp.intr_low_latency is set.
+ */
+ *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] 14+ messages in thread* [PATCH v3 09/10] drm/nouveau/dispnv50: program pixel clocks above 2.147GHz on GB20x
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (7 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 08/10] drm/nouveau/gsp: fix vblank interrupts " Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:14 ` [PATCH v3 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid Mohamed Ahmed
9 siblings, 0 replies; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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>
Reviewed-by: Lyude Paul <lyude@redhat.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..678cbc78ca9f 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, 14);
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()). Truncating would scan 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)),
+
+ 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..5754878ddc69 100644
--- a/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
+++ b/drivers/gpu/drm/nouveau/include/nvhw/class/clca7d.h
@@ -734,6 +734,10 @@
#define NVCA7D_HEAD_SET_CONTROL_CURSOR_COMPOSITION_BYPASS 20:20
#define NVCA7D_HEAD_SET_CONTROL_CURSOR_COMPOSITION_BYPASS_DISABLE (0x00000000)
#define NVCA7D_HEAD_SET_CONTROL_CURSOR_COMPOSITION_BYPASS_ENABLE (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_SURFACE_ADDRESS_HI_CRC(a) (0x00002150 + (a)*0x00000800)
#define NVCA7D_HEAD_SET_SURFACE_ADDRESS_HI_CRC_ADDRESS_HI 31:0
#define NVCA7D_HEAD_SET_SURFACE_ADDRESS_LO_CRC(a) (0x00002154 + (a)*0x00000800)
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid
2026-08-25 0:13 [PATCH v3 00/10] nouveau: assorted display fixes (GB20x, r570 DP_CONFIG_STREAM, HF-EEODB EDIDs) Mohamed Ahmed
` (8 preceding siblings ...)
2026-08-25 0:14 ` [PATCH v3 09/10] drm/nouveau/dispnv50: program pixel clocks above 2.147GHz " Mohamed Ahmed
@ 2026-08-25 0:14 ` Mohamed Ahmed
2026-08-25 0:27 ` sashiko-bot
9 siblings, 1 reply; 14+ messages in thread
From: Mohamed Ahmed @ 2026-08-25 0:14 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 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.
The mode list then tops out at the CTA VICs.
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.
A failed RM read now takes the same "no EDID" path as an empty DDC read
which stops it from leaking the runtime-PM reference detect() holds. The
property, display_info, and mode list go through
drm_edid_connector_update()/_add_modes() and every EDID source is
funneled through nouveau_connector_set_edid() so all of them reach the
property that drm_edid_connector_add_modes() reads from.
nouveau_acpi_edid() now returns a drm_edid sized from the EDID's
extension count but never past the length _DDC actually returned, so
padded buffers pass and truncated ones are rejected, and every
firmware-provided EDID (OF, ACPI, VBIOS-embedded) is validated with
drm_edid_valid(). An invalid one is treated as no EDID. get_modes()
re-syncs the property from the connector's copy when the probe helper
has cleared it for a forced-off connector, before adding modes.
The few raw EDID readers are converted along with it, so no raw struct
edid pointer is kept. Ownership and freeing move to the drm_edid. MST
connectors keep their separate legacy path.
Signed-off-by: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---
drivers/gpu/drm/nouveau/dispnv04/dfp.c | 5 +-
drivers/gpu/drm/nouveau/dispnv50/disp.c | 4 +-
drivers/gpu/drm/nouveau/nouveau_acpi.c | 32 ++++-
drivers/gpu/drm/nouveau/nouveau_acpi.h | 10 +-
drivers/gpu/drm/nouveau/nouveau_connector.c | 148 +++++++++++++++-----
drivers/gpu/drm/nouveau/nouveau_connector.h | 12 +-
6 files changed, 163 insertions(+), 48 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/dispnv04/dfp.c b/drivers/gpu/drm/nouveau/dispnv04/dfp.c
index c9f96ec8455d..00eb2aacbe93 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/dfp.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/dfp.c
@@ -344,9 +344,8 @@ static void nv04_dfp_mode_set(struct drm_encoder *encoder,
regp->fp_control |= (2 << 24);
if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS) {
bool duallink = false, dummy;
- if (nv_connector->edid &&
- nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
- duallink = (((u8 *)nv_connector->edid)[121] == 2);
+ if (nv_connector->spwg_links) {
+ duallink = nv_connector->spwg_links == 2;
} else {
nouveau_bios_parse_lvds_table(dev, output_mode->clock,
&duallink, &dummy);
diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 2c66e480b511..add19d479ebe 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -1803,8 +1803,8 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_commit *st
lvds_dual = bios->fp.dual_link;
lvds_8bpc = bios->fp.if_is_24bit;
} else {
- if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
- if (((u8 *)nv_connector->edid)[121] == 2)
+ if (nv_connector->spwg_links) {
+ if (nv_connector->spwg_links == 2)
lvds_dual = true;
} else
if (mode->clock >= bios->fp.duallink_transition_clk) {
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c
index 21b56cc7605c..9e6906490db5 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.c
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c
@@ -360,9 +360,10 @@ void nouveau_unregister_dsm_handler(void) {}
void nouveau_switcheroo_optimus_dsm(void) {}
#endif
-void *
+const struct drm_edid *
nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector)
{
+ const struct drm_edid *drm_edid;
struct acpi_device *acpidev;
int type, ret;
void *edid;
@@ -384,7 +385,34 @@ nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector)
if (ret < 0)
return NULL;
- return edid;
+ /* Take what _DDC returned as-is first as its length is the only one that
+ * can describe an HF-EEODB EDID, whose block count is not the byte 126
+ * one. Only if that does not validate fall back to trimming the buffer
+ * to the byte 126 size, which is what the padding some firmware appends
+ * needs.
+ */
+ drm_edid = drm_edid_alloc(edid, ret);
+ if (drm_edid && !drm_edid_valid(drm_edid)) {
+ drm_edid_free(drm_edid);
+ drm_edid = NULL;
+
+ if (ret > EDID_LENGTH) {
+ int trimmed = EDID_LENGTH *
+ (1 + ((const struct edid *)edid)->extensions);
+
+ if (trimmed < ret)
+ drm_edid = drm_edid_alloc(edid, trimmed);
+ if (drm_edid && !drm_edid_valid(drm_edid)) {
+ drm_edid_free(drm_edid);
+ drm_edid = NULL;
+ }
+ }
+ }
+ kfree(edid);
+
+ if (!drm_edid)
+ drm_dbg_kms(dev, "Invalid EDID from ACPI _DDC\n");
+ return drm_edid;
}
bool nouveau_acpi_video_backlight_use_native(void)
diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.h b/drivers/gpu/drm/nouveau/nouveau_acpi.h
index e39dd8b94b8b..bc3ccca7b338 100644
--- a/drivers/gpu/drm/nouveau/nouveau_acpi.h
+++ b/drivers/gpu/drm/nouveau/nouveau_acpi.h
@@ -4,13 +4,15 @@
#define ROM_BIOS_PAGE 4096
+struct drm_edid;
+
#if defined(CONFIG_ACPI) && defined(CONFIG_X86)
bool nouveau_is_optimus(void);
bool nouveau_is_v1_dsm(void);
void nouveau_register_dsm_handler(void);
void nouveau_unregister_dsm_handler(void);
void nouveau_switcheroo_optimus_dsm(void);
-void *nouveau_acpi_edid(struct drm_device *, struct drm_connector *);
+const struct drm_edid *nouveau_acpi_edid(struct drm_device *, struct drm_connector *);
bool nouveau_acpi_video_backlight_use_native(void);
void nouveau_acpi_video_register_backlight(void);
#else
@@ -19,7 +21,11 @@ static inline bool nouveau_is_v1_dsm(void) { return false; };
static inline void nouveau_register_dsm_handler(void) {}
static inline void nouveau_unregister_dsm_handler(void) {}
static inline void nouveau_switcheroo_optimus_dsm(void) {}
-static inline void *nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector) { return NULL; }
+static inline const struct drm_edid *
+nouveau_acpi_edid(struct drm_device *dev, struct drm_connector *connector)
+{
+ return NULL;
+}
static inline bool nouveau_acpi_video_backlight_use_native(void) { return true; }
static inline void nouveau_acpi_video_register_backlight(void) {}
#endif
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index b0b0ad9a0c24..3738c23215c5 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)
@@ -469,6 +469,36 @@ nouveau_connector_ddc_detect(struct drm_connector *connector)
return found;
}
+static void
+nouveau_connector_set_edid(struct nouveau_connector *nv_connector,
+ const struct drm_edid *drm_edid)
+{
+ if (nv_connector->drm_edid == drm_edid)
+ return;
+
+ /* 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 the
+ * high-refresh timings.
+ */
+ drm_edid_connector_update(&nv_connector->base, drm_edid);
+
+ drm_edid_free(nv_connector->drm_edid);
+ nv_connector->drm_edid = drm_edid;
+
+ /* The SPWG link-count byte lives in a vendor descriptor drm has no
+ * accessor for. Peek at it once here so nothing else needs the raw
+ * EDID.
+ */
+ nv_connector->spwg_links = 0;
+ if (nv_connector->type == DCB_CONNECTOR_LVDS_SPWG) {
+ const u8 *raw = (const u8 *)drm_edid_raw(drm_edid);
+
+ if (raw)
+ nv_connector->spwg_links = raw[121] == 2 ? 2 : 1;
+ }
+}
+
static struct nouveau_encoder *
nouveau_connector_of_detect(struct drm_connector *connector)
{
@@ -490,8 +520,17 @@ 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) {
- nv_connector->edid =
- kmemdup(edid, EDID_LENGTH, GFP_KERNEL);
+ const struct drm_edid *drm_edid =
+ drm_edid_alloc(edid, EDID_LENGTH);
+
+ /* Firmware-provided, so validate it like the DDC
+ * readers would.
+ */
+ if (drm_edid && !drm_edid_valid(drm_edid)) {
+ drm_edid_free(drm_edid);
+ drm_edid = NULL;
+ }
+ nouveau_connector_set_edid(nv_connector, drm_edid);
return nv_encoder;
}
}
@@ -546,17 +585,23 @@ nouveau_connector_set_encoder(struct drm_connector *connector,
}
}
-static void
-nouveau_connector_set_edid(struct nouveau_connector *nv_connector,
- struct edid *edid)
+struct nouveau_rm_edid {
+ u8 *data;
+ size_t size;
+};
+
+static int
+nouveau_connector_rm_edid_block(void *context, u8 *buf, unsigned int block,
+ size_t len)
{
- if (nv_connector->edid != edid) {
- struct edid *old_edid = nv_connector->edid;
+ struct nouveau_rm_edid *rm = context;
+ size_t offset = (size_t)block * EDID_LENGTH;
- drm_connector_update_edid_property(&nv_connector->base, edid);
- kfree(old_edid);
- nv_connector->edid = edid;
- }
+ if (offset + len > rm->size)
+ return -EINVAL;
+
+ memcpy(buf, rm->data + offset, len);
+ return 0;
}
static enum drm_connector_status
@@ -590,22 +635,37 @@ 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);
- if (ret < 0)
- return connector_status_disconnected;
+ struct nouveau_rm_edid rm = {};
+
+ /* RM (which owns the DDC pads on GSP boards) reads the
+ * EDID whole and returns its true size, which for an
+ * HF-EEODB EDID exceeds what byte 126 admits. Serve it
+ * through drm's block reader so EEODB sizing, block
+ * validation, and the debugfs EDID override all apply.
+ * A failed read is treated like an empty DDC read,
+ * which releases the runtime-PM reference.
+ */
+ ret = nvif_outp_edid_get(&nv_encoder->outp, &rm.data);
+ if (ret >= 0) {
+ 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) {
+ if (!nv_connector->drm_edid) {
NV_ERROR(drm, "DDC responded, but no EDID for %s\n",
connector->name);
goto detect_analog;
@@ -626,7 +686,7 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
nv_partner->dcb->type == DCB_OUTPUT_TMDS) ||
(nv_encoder->dcb->type == DCB_OUTPUT_TMDS &&
nv_partner->dcb->type == DCB_OUTPUT_ANALOG))) {
- if (nv_connector->edid->input & DRM_EDID_INPUT_DIGITAL)
+ if (drm_edid_is_digital(nv_connector->drm_edid))
type = DCB_OUTPUT_TMDS;
else
type = DCB_OUTPUT_ANALOG;
@@ -638,7 +698,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
conn_status = connector_status_connected;
if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
- drm_dp_cec_set_edid(&nv_connector->aux, nv_connector->edid);
+ drm_dp_cec_attach(&nv_connector->aux,
+ connector->display_info.source_physical_address);
goto out;
} else {
@@ -670,7 +731,7 @@ nouveau_connector_detect(struct drm_connector *connector, bool force)
}
out:
- if (!nv_connector->edid)
+ if (!nv_connector->drm_edid)
drm_dp_cec_unset_edid(&nv_connector->aux);
pm_runtime_mark_last_busy(dev->dev);
@@ -686,7 +747,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 +758,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;
}
}
@@ -733,11 +794,20 @@ 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);
+ /* The panel is there either way, so report it
+ * connected and let the probe helper fall back to a
+ * default mode if the EDID itself is unusable.
+ */
if (edid)
status = connector_status_connected;
+ if (edid && !drm_edid_valid(edid)) {
+ drm_edid_free(edid);
+ edid = NULL;
+ }
}
}
@@ -886,7 +956,7 @@ nouveau_connector_detect_depth(struct drm_connector *connector)
bool duallink;
/* if the edid is feeling nice enough to provide this info, use it */
- if (nv_connector->edid && connector->display_info.bpc)
+ if (nv_connector->drm_edid && connector->display_info.bpc)
return;
/* EDID 1.4 is *supposed* to be supported on eDP, but, Apple... */
@@ -913,9 +983,8 @@ nouveau_connector_detect_depth(struct drm_connector *connector)
/* LVDS: DDC panel, need to first determine the number of links to
* know which if_is_24bit flag to check...
*/
- if (nv_connector->edid &&
- nv_connector->type == DCB_CONNECTOR_LVDS_SPWG)
- duallink = ((u8 *)nv_connector->edid)[121] == 2;
+ if (nv_connector->spwg_links)
+ duallink = nv_connector->spwg_links == 2;
else
duallink = mode->clock >= bios->fp.duallink_transition_clk;
@@ -973,12 +1042,17 @@ 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);
- else
- if (nv_encoder->dcb->type == DCB_OUTPUT_LVDS &&
- (nv_encoder->dcb->lvdsconf.use_straps_for_mode ||
- drm->vbios.fp_no_ddc) && nouveau_bios_fp_mode(dev, NULL)) {
+ if (nv_connector->drm_edid) {
+ /* The probe helper clears the property and display_info for
+ * a forced-off connector without calling detect(). Re-sync
+ * from our copy then, since add_modes() reads the property.
+ */
+ if (!connector->edid_blob_ptr)
+ drm_edid_connector_update(connector, 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 ||
+ drm->vbios.fp_no_ddc) && nouveau_bios_fp_mode(dev, NULL)) {
struct drm_display_mode mode;
nouveau_bios_fp_mode(dev, &mode);
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h
index 0608cabed058..eb292d2ba4bc 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.h
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.h
@@ -43,7 +43,7 @@
struct nvkm_i2c_port;
struct dcb_output;
-struct edid;
+struct drm_edid;
#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
struct nouveau_backlight {
@@ -121,6 +121,13 @@ struct nouveau_connector {
struct drm_connector base;
enum dcb_connector_type type;
u8 index;
+ /* LVDS_SPWG panels state their link count in EDID descriptor 4 (SPWG
+ * byte 0x79), cached by nouveau_connector_set_edid() so nothing else
+ * needs the raw EDID. 0 = unknown (not an SPWG panel, or no EDID) and
+ * callers fall back to their transition-clock/VBIOS heuristics;
+ * 1 = single link; 2 = dual link.
+ */
+ u8 spwg_links;
struct nvif_conn conn;
u64 hpd_pending;
@@ -137,7 +144,8 @@ struct nouveau_connector {
int scaling_mode;
struct nouveau_encoder *detected_encoder;
- 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] 14+ messages in thread* Re: [PATCH v3 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid
2026-08-25 0:14 ` [PATCH v3 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid Mohamed Ahmed
@ 2026-08-25 0:27 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-08-25 0:27 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] Potential out-of-bounds read when copying the EDID from a Device Tree property.
--
--- Patch [10]: [PATCH v3 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid ---
commit a90c1b47260c1e8987750f3b4670510323a260f4
Author: Mohamed Ahmed <mohamedahmedegypt2001@gmail.com>
drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid
This commit converts the nouveau driver to use the struct drm_edid API
instead of the legacy EDID APIs. This allows the driver to properly honor
HF-EEODB EDIDs by tracking their true block size instead of truncating them.
The patch updates various EDID retrieval and parsing paths to use the new
allocation and validation infrastructure.
> diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
> index b0b0ad9a0c248..3738c23215c58 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_connector.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
> @@ -490,8 +520,17 @@ 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) {
> - nv_connector->edid =
> - kmemdup(edid, EDID_LENGTH, GFP_KERNEL);
> + const struct drm_edid *drm_edid =
> + drm_edid_alloc(edid, EDID_LENGTH);
[Severity: High]
This isn't a bug introduced by this patch, but is it possible for the device
tree to provide an EDID property smaller than EDID_LENGTH?
In nouveau_connector_of_detect, of_get_property is called with a NULL length
pointer, discarding the actual size of the firmware property. If the property
is shorter than 128 bytes, drm_edid_alloc (and the previous kmemdup call)
will perform an out-of-bounds read which could potentially trigger a panic if
the memory access crosses an unmapped page boundary.
Would it make sense to pass a length pointer to of_get_property and verify
the property is at least EDID_LENGTH bytes before attempting to allocate it?
> +
> + /* Firmware-provided, so validate it like the DDC
> + * readers would.
> + */
> + if (drm_edid && !drm_edid_valid(drm_edid)) {
> + drm_edid_free(drm_edid);
> + drm_edid = NULL;
> + }
> + nouveau_connector_set_edid(nv_connector, drm_edid);
> return nv_encoder;
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825001408.14219-1-mohamedahmedegypt2001@gmail.com?part=10
^ permalink raw reply [flat|nested] 14+ messages in thread