* [PATCH v2 0/6] drm/msm: fix SMMU fault dumps
@ 2026-09-08 15:24 Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 1/6] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
` (5 more replies)
0 siblings, 6 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
In several cases the drm/msm can cause an SMMU fault on modesetting (due
to the display controller still scanning the BO which is being
unmapped). Fix three cases which I stumbled upon.
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
Changes in v2:
- New patch: lock the framebuffer pin state, it was updated locklessly
(Sashiko)
- Do not defer the release on an inactive crtc, it was leaked there
(Sashiko)
- Flush retired framebuffers from msm_drm_kms_uninit(), before kms->vm is
dropped, and only for crtcs which have a vblank work (Sashiko)
- Clean the flip works up after destroy_workqueue() (Sashiko)
- Fixed the long-standing issue of the msm driver roguely setting
allow_modeset, which started to manifest in timeouts and SMMU errors.
- Link to v1: https://patch.msgid.link/20260903-fd-kms-fix-smmu-v1-0-608d02491666@oss.qualcomm.com
To: Rob Clark <robin.clark@oss.qualcomm.com>
To: Dmitry Baryshkov <lumag@kernel.org>
To: Abhinav Kumar <abhinav.kumar@linux.dev>
To: Jessica Zhang <jesszhan0024@gmail.com>
To: Sean Paul <sean@poorly.run>
To: Marijn Suijten <marijn.suijten@somainline.org>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Antonino Maniscalco <antomani103@gmail.com>
To: Kalyan Thota <quic_kalyant@quicinc.com>
To: Federico Amedeo Izzo <federico@izzo.pro>
To: Helen Koike <helen.fornazier@gmail.com>
To: Vignesh Raman <vignesh.raman@collabora.com>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
Cc: linux-arm-msm@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Cc: freedreno@lists.freedesktop.org
Cc: linux-kernel@vger.kernel.org
Cc: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
Dmitry Baryshkov (6):
drm/msm: serialise framebuffer pin state
drm/msm: fix framebuffer pin refcount leak on prepare failure
drm/msm: release scanout framebuffers only after a vblank
drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
drm/msm/dpu: only reassign resources when the encoder is reprogrammed
drm/ci: mark pixel-format tests as passing on SC7180
.../xfails/msm-sc7180-trogdor-kingoftown-fails.txt | 2 -
.../msm-sc7180-trogdor-lazor-limozeen-fails.txt | 2 -
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 34 ++++++-
.../gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c | 2 +-
drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c | 3 +-
drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c | 2 +-
drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c | 2 +-
drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c | 2 +-
drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c | 2 +-
drivers/gpu/drm/msm/msm_atomic.c | 17 +---
drivers/gpu/drm/msm/msm_drv.h | 3 +-
drivers/gpu/drm/msm/msm_fb.c | 63 ++++++++++---
drivers/gpu/drm/msm/msm_kms.c | 103 +++++++++++++++++++++
drivers/gpu/drm/msm/msm_kms.h | 27 ++++++
14 files changed, 221 insertions(+), 43 deletions(-)
---
base-commit: 140b13475302601368c0cf4e193e66126a49feb3
change-id: 20260902-fd-kms-fix-smmu-2d4baaf460b0
Best regards,
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 1/6] drm/msm: serialise framebuffer pin state
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
@ 2026-09-08 15:24 ` Dmitry Baryshkov
2026-09-08 15:38 ` sashiko-bot
2026-09-08 15:24 ` [PATCH v2 2/6] drm/msm: fix framebuffer pin refcount leak on prepare failure Dmitry Baryshkov
` (4 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
prepare_count, iova[] and the pins they describe are updated locklessly,
but drm_atomic_helper_commit() prepares a new state in parallel with the
completion of the previous one -- stall_checks() only stalls on the second
previous commit. Commit N+1's ->prepare_fb() thus runs while commit N is
in ->cleanup_fb(), and for a double-buffered flip that is the same
framebuffer:
cleanup: prepare_count 1 -> 0
prepare: prepare_count 0 -> 1, pins, stores iova[]
cleanup: memset(iova, 0)
leaving the plane programmed with a NULL base address:
arm-smmu 15000000.iommu: Unhandled context fault: fsr=0x402,
iova=0x00000100, fsynr=0x3e0023, cbfrsynra=0x1c00, cb=11
The opposite order is broken too since commit 8ac37c88f991 ("drm/msm:
Refcount framebuffer pins"): a prepare which finds the count non-zero
returns at once, assuming iova[] is populated.
Both callbacks may sleep, so a mutex will do.
Fixes: 8ac37c88f991 ("drm/msm: Refcount framebuffer pins")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/msm_fb.c | 40 +++++++++++++++++++++++++++++++---------
1 file changed, 31 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
index 60c108d35d2a..d69591245231 100644
--- a/drivers/gpu/drm/msm/msm_fb.c
+++ b/drivers/gpu/drm/msm/msm_fb.c
@@ -22,9 +22,12 @@ struct msm_framebuffer {
/* Count of # of attached planes which need dirtyfb: */
refcount_t dirtyfb;
+ /* Protects the pin state below: */
+ struct mutex lock;
+
/* Framebuffer per-plane address, if pinned, else zero: */
uint64_t iova[DRM_FORMAT_MAX_PLANES];
- atomic_t prepare_count;
+ unsigned int prepare_count;
};
#define to_msm_framebuffer(x) container_of(x, struct msm_framebuffer, base)
@@ -45,9 +48,17 @@ static int msm_framebuffer_dirtyfb(struct drm_framebuffer *fb,
clips, num_clips);
}
+static void msm_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+ struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
+
+ mutex_destroy(&msm_fb->lock);
+ drm_gem_fb_destroy(fb);
+}
+
static const struct drm_framebuffer_funcs msm_framebuffer_funcs = {
.create_handle = drm_gem_fb_create_handle,
- .destroy = drm_gem_fb_destroy,
+ .destroy = msm_framebuffer_destroy,
.dirty = msm_framebuffer_dirtyfb,
};
@@ -76,13 +87,15 @@ int msm_framebuffer_prepare(struct drm_framebuffer *fb, bool needs_dirtyfb)
struct msm_drm_private *priv = fb->dev->dev_private;
struct drm_gpuvm *vm = priv->kms->vm;
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
- int ret, i, n = fb->format->num_planes;
+ int ret = 0, i, n = fb->format->num_planes;
if (needs_dirtyfb)
refcount_inc(&msm_fb->dirtyfb);
- if (atomic_inc_return(&msm_fb->prepare_count) > 1)
- return 0;
+ mutex_lock(&msm_fb->lock);
+
+ if (msm_fb->prepare_count++)
+ goto out;
for (i = 0; i < n; i++) {
msm_gem_vma_get(fb->obj[i]);
@@ -90,10 +103,13 @@ int msm_framebuffer_prepare(struct drm_framebuffer *fb, bool needs_dirtyfb)
drm_dbg_state(fb->dev, "FB[%u]: iova[%d]: %08llx (%d)\n",
fb->base.id, i, msm_fb->iova[i], ret);
if (ret)
- return ret;
+ break;
}
- return 0;
+out:
+ mutex_unlock(&msm_fb->lock);
+
+ return ret;
}
void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
@@ -106,8 +122,10 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
if (needed_dirtyfb)
refcount_dec(&msm_fb->dirtyfb);
- if (atomic_dec_return(&msm_fb->prepare_count))
- return;
+ mutex_lock(&msm_fb->lock);
+
+ if (--msm_fb->prepare_count)
+ goto out;
memset(msm_fb->iova, 0, sizeof(msm_fb->iova));
@@ -115,6 +133,9 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
msm_gem_unpin_iova(fb->obj[i], vm);
msm_gem_vma_put(fb->obj[i]);
}
+
+out:
+ mutex_unlock(&msm_fb->lock);
}
uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane)
@@ -206,6 +227,7 @@ msm_framebuffer_init(struct drm_device *dev, const struct drm_format_info *info,
}
refcount_set(&msm_fb->dirtyfb, 1);
+ mutex_init(&msm_fb->lock);
drm_dbg_state(dev, "create: FB ID: %d (%p)\n", fb->base.id, fb);
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 2/6] drm/msm: fix framebuffer pin refcount leak on prepare failure
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 1/6] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
@ 2026-09-08 15:24 ` Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 3/6] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
` (3 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
msm_framebuffer_prepare() bumps prepare_count before pinning, but returns
straight out of the pin loop on error, leaking the count, the
msm_gem_vma_get() reference and any planes already pinned.
drm_atomic_helper_prepare_planes() does not call cleanup_fb() for the
plane whose prepare_fb() failed, so nothing ever drops it.
Since commit 8ac37c88f991 ("drm/msm: Refcount framebuffer pins") a
prepare which finds the count already non-zero returns early, assuming
iova[] is populated. With the count stuck, every later prepare of that
framebuffer reports success while iova[] is still zero, and DPU scans out
from a NULL base address:
arm-smmu 15000000.iommu: Unhandled context fault: fsr=0x402,
iova=0x00000100, fsynr=0x3e0023, cbfrsynra=0x1c00, cb=11
Unwind properly on failure instead.
Fixes: 8ac37c88f991 ("drm/msm: Refcount framebuffer pins")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/msm_fb.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
index d69591245231..dfe040d5260b 100644
--- a/drivers/gpu/drm/msm/msm_fb.c
+++ b/drivers/gpu/drm/msm/msm_fb.c
@@ -102,13 +102,28 @@ int msm_framebuffer_prepare(struct drm_framebuffer *fb, bool needs_dirtyfb)
ret = msm_gem_get_and_pin_iova(fb->obj[i], vm, &msm_fb->iova[i]);
drm_dbg_state(fb->dev, "FB[%u]: iova[%d]: %08llx (%d)\n",
fb->base.id, i, msm_fb->iova[i], ret);
- if (ret)
+ if (ret) {
+ msm_gem_vma_put(fb->obj[i]);
break;
+ }
+ }
+
+ if (ret) {
+ while (i--) {
+ msm_gem_unpin_iova(fb->obj[i], vm);
+ msm_gem_vma_put(fb->obj[i]);
+ }
+
+ memset(msm_fb->iova, 0, sizeof(msm_fb->iova));
+ msm_fb->prepare_count--;
}
out:
mutex_unlock(&msm_fb->lock);
+ if (ret && needs_dirtyfb)
+ refcount_dec(&msm_fb->dirtyfb);
+
return ret;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 3/6] drm/msm: release scanout framebuffers only after a vblank
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 1/6] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 2/6] drm/msm: fix framebuffer pin refcount leak on prepare failure Dmitry Baryshkov
@ 2026-09-08 15:24 ` Dmitry Baryshkov
2026-09-08 15:42 ` sashiko-bot
2026-09-08 15:24 ` [PATCH v2 4/6] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
` (2 subsequent siblings)
5 siblings, 1 reply; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
msm_framebuffer_cleanup() releases a framebuffer as soon as
drm_atomic_helper_cleanup_planes() runs. msm_atomic_commit_tail() waits
only for ->wait_flush() before that, which for video mode waits for
CTL_FLUSH to read back zero, ie. for the new configuration to be latched;
the frame in flight with the old one is still being fetched. Since
commit 111fdd2198e6 ("drm/msm: drm_gpuvm conversion") the unpin also
detaches the vma, so the display is left reading unmapped memory:
arm-smmu 15000000.iommu: Unhandled context fault: fsr=0x402,
iova=0x007eb100, fsynr=0x3f0023, cbfrsynra=0xc20, cb=27
Defer the release: hand the retired framebuffer to the crtc and drop the
pin and the vma reference from a drm_flip_work committed from the vblank
irq, as mdp4 and mdp5 already do for their LM cursor buffers. A vblank
reference is held while work is outstanding.
An inactive crtc is not fetching and has no vblank to defer to, and
drm_crtc_vblank_off() sets vblank->inmodeset so drm_vblank_work_schedule()
does not fail there, so key that off the crtc state and release directly.
Outstanding work is flushed from msm_drm_kms_uninit(), which still runs
before the driver drops kms->vm.
Fixes: 111fdd2198e6 ("drm/msm: drm_gpuvm conversion")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 2 +-
.../gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c | 2 +-
drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c | 3 +-
drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c | 2 +-
drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c | 2 +-
drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c | 2 +-
drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c | 2 +-
drivers/gpu/drm/msm/msm_drv.h | 3 +-
drivers/gpu/drm/msm/msm_fb.c | 6 +-
drivers/gpu/drm/msm/msm_kms.c | 103 +++++++++++++++++++++
drivers/gpu/drm/msm/msm_kms.h | 27 ++++++
11 files changed, 145 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
index 42d0a529b4d5..bf593020e8e4 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
@@ -1213,7 +1213,7 @@ static void dpu_crtc_disable(struct drm_crtc *crtc,
}
/* Disable/save vblank irq handling */
- drm_crtc_vblank_off(crtc);
+ msm_crtc_vblank_off(crtc);
drm_for_each_encoder_mask(encoder, crtc->dev,
old_crtc_state->encoder_mask) {
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c
index 22433bfbea1e..5db33e49c345 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_wb.c
@@ -615,7 +615,7 @@ static void dpu_encoder_phys_wb_cleanup_wb_job(struct dpu_encoder_phys *phys_enc
if (!job->fb)
return;
- msm_framebuffer_cleanup(job->fb, false);
+ msm_framebuffer_cleanup(job->fb, NULL, false);
wb_enc->wb_job = NULL;
wb_enc->wb_conn = NULL;
}
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c
index 7b92082d35a6..0e986b533bf0 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c
@@ -684,7 +684,8 @@ static void dpu_plane_cleanup_fb(struct drm_plane *plane,
DPU_DEBUG_PLANE(pdpu, "FB[%u]\n", old_state->fb->base.id);
- msm_framebuffer_cleanup(old_state->fb, old_pstate->needs_dirtyfb);
+ msm_framebuffer_cleanup(old_state->fb, old_state->crtc,
+ old_pstate->needs_dirtyfb);
}
static int dpu_plane_check_inline_rotation(struct dpu_plane *pdpu,
diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c
index 57dfce58450b..195ee6b4a0c6 100644
--- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c
+++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c
@@ -267,7 +267,7 @@ static void mdp4_crtc_atomic_disable(struct drm_crtc *crtc,
return;
/* Disable/save vblank irq handling before power is disabled */
- drm_crtc_vblank_off(crtc);
+ msm_crtc_vblank_off(crtc);
mdp_irq_unregister(&mdp4_kms->base, &mdp4_crtc->err);
mdp4_disable(mdp4_kms);
diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c
index 9459f70ce0ba..5f669a02d798 100644
--- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c
+++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c
@@ -97,7 +97,7 @@ static void mdp4_plane_cleanup_fb(struct drm_plane *plane,
return;
DBG("%s: cleanup: FB[%u]", mdp4_plane->name, fb->base.id);
- msm_framebuffer_cleanup(fb, false);
+ msm_framebuffer_cleanup(fb, old_state->crtc, false);
}
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
index 4c4a897fc1ee..547f6fdb83d5 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c
@@ -499,7 +499,7 @@ static void mdp5_crtc_atomic_disable(struct drm_crtc *crtc,
return;
/* Disable/save vblank irq handling before power is disabled */
- drm_crtc_vblank_off(crtc);
+ msm_crtc_vblank_off(crtc);
if (mdp5_cstate->cmd_mode)
mdp_irq_unregister(&mdp5_kms->base, &mdp5_crtc->pp_done);
diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c
index 841f444a8d68..dacb387d9bb6 100644
--- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c
+++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c
@@ -155,7 +155,7 @@ static void mdp5_plane_cleanup_fb(struct drm_plane *plane,
return;
DBG("%s: cleanup: FB[%u]", plane->name, fb->base.id);
- msm_framebuffer_cleanup(fb, needed_dirtyfb);
+ msm_framebuffer_cleanup(fb, old_state->crtc, needed_dirtyfb);
}
static int mdp5_plane_atomic_check_with_state(struct drm_crtc_state *crtc_state,
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index eb4bbae8557b..18a9728838fd 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -254,7 +254,8 @@ int msm_gem_prime_pin(struct drm_gem_object *obj);
void msm_gem_prime_unpin(struct drm_gem_object *obj);
int msm_framebuffer_prepare(struct drm_framebuffer *fb, bool needs_dirtyfb);
-void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb);
+void msm_framebuffer_cleanup(struct drm_framebuffer *fb, struct drm_crtc *crtc,
+ bool needed_dirtyfb);
uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane);
struct drm_gem_object *msm_framebuffer_bo(struct drm_framebuffer *fb, int plane);
const struct msm_format *msm_framebuffer_format(struct drm_framebuffer *fb);
diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
index dfe040d5260b..2e317bec9fbb 100644
--- a/drivers/gpu/drm/msm/msm_fb.c
+++ b/drivers/gpu/drm/msm/msm_fb.c
@@ -127,7 +127,8 @@ int msm_framebuffer_prepare(struct drm_framebuffer *fb, bool needs_dirtyfb)
return ret;
}
-void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
+void msm_framebuffer_cleanup(struct drm_framebuffer *fb, struct drm_crtc *crtc,
+ bool needed_dirtyfb)
{
struct msm_drm_private *priv = fb->dev->dev_private;
struct drm_gpuvm *vm = priv->kms->vm;
@@ -144,6 +145,9 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
memset(msm_fb->iova, 0, sizeof(msm_fb->iova));
+ if (crtc && msm_crtc_queue_fb_unpin(crtc, fb))
+ goto out;
+
for (i = 0; i < n; i++) {
msm_gem_unpin_iova(fb->obj[i], vm);
msm_gem_vma_put(fb->obj[i]);
diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
index e5d0ea629448..6a857ad3464c 100644
--- a/drivers/gpu/drm/msm/msm_kms.c
+++ b/drivers/gpu/drm/msm/msm_kms.c
@@ -11,6 +11,7 @@
#include <uapi/linux/sched/types.h>
#include <drm/drm_drv.h>
+#include <drm/drm_framebuffer.h>
#include <drm/drm_mode_config.h>
#include <drm/drm_vblank.h>
#include <drm/clients/drm_client_setup.h>
@@ -165,6 +166,102 @@ void msm_crtc_disable_vblank(struct drm_crtc *crtc)
vblank_ctrl_queue_work(priv, crtc, false);
}
+void msm_kms_fb_unpin_worker(struct drm_flip_work *work, void *val)
+{
+ struct drm_framebuffer *fb = val;
+ struct msm_drm_private *priv = fb->dev->dev_private;
+ struct drm_gpuvm *vm = priv->kms->vm;
+ int i, n = fb->format->num_planes;
+
+ for (i = 0; i < n; i++) {
+ msm_gem_unpin_iova(fb->obj[i], vm);
+ msm_gem_vma_put(fb->obj[i]);
+ }
+
+ drm_framebuffer_put(fb);
+}
+
+static void msm_kms_fb_unpin_vblank(struct kthread_work *work)
+{
+ struct msm_kms_fb_unpin *fb_unpin =
+ container_of(to_drm_vblank_work(work), struct msm_kms_fb_unpin,
+ vblank_work);
+
+ drm_flip_work_commit(&fb_unpin->work, fb_unpin->kms->wq);
+}
+
+int msm_kms_init_fb_unpin(struct drm_device *dev)
+{
+ struct msm_drm_private *priv = dev->dev_private;
+ struct msm_kms *kms = priv->kms;
+ struct drm_crtc *crtc;
+
+ drm_for_each_crtc(crtc, dev) {
+ unsigned int idx = drm_crtc_index(crtc);
+
+ if (idx >= ARRAY_SIZE(kms->fb_unpin))
+ return -EINVAL;
+
+ drm_vblank_work_init(&kms->fb_unpin[idx].vblank_work, crtc,
+ msm_kms_fb_unpin_vblank);
+ }
+
+ return 0;
+}
+
+void msm_kms_fini_fb_unpin(struct drm_device *dev)
+{
+ struct msm_drm_private *priv = dev->dev_private;
+ struct msm_kms *kms = priv->kms;
+ struct drm_crtc *crtc;
+
+ drm_for_each_crtc(crtc, dev) {
+ unsigned int idx = drm_crtc_index(crtc);
+
+ if (idx >= ARRAY_SIZE(kms->fb_unpin))
+ continue;
+
+ drm_vblank_work_cancel_sync(&kms->fb_unpin[idx].vblank_work);
+ drm_flip_work_commit(&kms->fb_unpin[idx].work, kms->wq);
+ }
+}
+
+void msm_crtc_vblank_off(struct drm_crtc *crtc)
+{
+ struct msm_drm_private *priv = crtc->dev->dev_private;
+ struct msm_kms *kms = priv->kms;
+ unsigned int idx = drm_crtc_index(crtc);
+
+ if (kms && idx < ARRAY_SIZE(kms->fb_unpin)) {
+ drm_vblank_work_cancel_sync(&kms->fb_unpin[idx].vblank_work);
+ drm_flip_work_commit(&kms->fb_unpin[idx].work, kms->wq);
+ }
+
+ drm_crtc_vblank_off(crtc);
+}
+
+bool msm_crtc_queue_fb_unpin(struct drm_crtc *crtc, struct drm_framebuffer *fb)
+{
+ struct msm_drm_private *priv = crtc->dev->dev_private;
+ struct msm_kms *kms = priv->kms;
+ unsigned int idx = drm_crtc_index(crtc);
+
+ if (!kms || idx >= ARRAY_SIZE(kms->fb_unpin))
+ return false;
+
+ if (!crtc->state->active)
+ return false;
+
+ drm_framebuffer_get(fb);
+ drm_flip_work_queue(&kms->fb_unpin[idx].work, fb);
+
+ if (drm_vblank_work_schedule(&kms->fb_unpin[idx].vblank_work,
+ drm_crtc_vblank_count(crtc) + 1, true) < 0)
+ drm_flip_work_commit(&kms->fb_unpin[idx].work, kms->wq);
+
+ return true;
+}
+
static int msm_kms_fault_handler(void *arg, unsigned long iova, int flags, void *data)
{
struct msm_kms *kms = arg;
@@ -240,6 +337,8 @@ void msm_drm_kms_uninit(struct device *dev)
* irq after uninstall has disabled it.
*/
+ msm_kms_fini_fb_unpin(ddev);
+
flush_workqueue(kms->wq);
/* clean up event worker threads */
@@ -323,6 +422,10 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
goto err_msm_uninit;
}
+ ret = msm_kms_init_fb_unpin(ddev);
+ if (ret)
+ goto err_msm_uninit;
+
pm_runtime_get_sync(dev);
ret = msm_irq_install(ddev, kms->irq);
pm_runtime_put_sync(dev);
diff --git a/drivers/gpu/drm/msm/msm_kms.h b/drivers/gpu/drm/msm/msm_kms.h
index f25b31e502d2..8d95457cb8de 100644
--- a/drivers/gpu/drm/msm/msm_kms.h
+++ b/drivers/gpu/drm/msm/msm_kms.h
@@ -11,6 +11,9 @@
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
+#include <drm/drm_flip_work.h>
+#include <drm/drm_vblank_work.h>
+
#include "msm_drv.h"
#ifdef CONFIG_DRM_MSM_KMS
@@ -135,6 +138,16 @@ struct msm_drm_thread {
struct kthread_worker *worker;
};
+struct msm_kms_fb_unpin {
+ struct drm_flip_work work;
+ struct drm_vblank_work vblank_work;
+ struct msm_kms *kms;
+};
+
+void msm_kms_fb_unpin_worker(struct drm_flip_work *work, void *val);
+int msm_kms_init_fb_unpin(struct drm_device *dev);
+void msm_kms_fini_fb_unpin(struct drm_device *dev);
+
struct msm_kms {
const struct msm_kms_funcs *funcs;
struct drm_device *dev;
@@ -170,8 +183,13 @@ struct msm_kms {
struct workqueue_struct *wq;
struct msm_drm_thread event_thread[MAX_CRTCS];
+
+ struct msm_kms_fb_unpin fb_unpin[MAX_CRTCS];
};
+bool msm_crtc_queue_fb_unpin(struct drm_crtc *crtc, struct drm_framebuffer *fb);
+void msm_crtc_vblank_off(struct drm_crtc *crtc);
+
static inline int msm_kms_init(struct msm_kms *kms,
const struct msm_kms_funcs *funcs)
{
@@ -193,6 +211,12 @@ static inline int msm_kms_init(struct msm_kms *kms,
}
}
+ for (i = 0; i < ARRAY_SIZE(kms->fb_unpin); i++) {
+ kms->fb_unpin[i].kms = kms;
+ drm_flip_work_init(&kms->fb_unpin[i].work, "fb unpin",
+ msm_kms_fb_unpin_worker);
+ }
+
return 0;
}
@@ -204,6 +228,9 @@ static inline void msm_kms_destroy(struct msm_kms *kms)
msm_atomic_destroy_pending_timer(&kms->pending_timers[i]);
destroy_workqueue(kms->wq);
+
+ for (i = 0; i < ARRAY_SIZE(kms->fb_unpin); i++)
+ drm_flip_work_cleanup(&kms->fb_unpin[i].work);
}
#define for_each_crtc_mask(dev, crtc, crtc_mask) \
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 4/6] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (2 preceding siblings ...)
2026-09-08 15:24 ` [PATCH v2 3/6] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
@ 2026-09-08 15:24 ` Dmitry Baryshkov
2026-09-08 15:42 ` sashiko-bot
2026-09-08 15:24 ` [PATCH v2 5/6] drm/msm/dpu: only reassign resources when the encoder is reprogrammed Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 6/6] drm/ci: mark pixel-format tests as passing on SC7180 Dmitry Baryshkov
5 siblings, 1 reply; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
dpu_crtc_assign_resources() only stores mixers[i].hw_dspp when the new
topology has a DSPP for that mixer, and dpu_crtc_duplicate_state() copies
the state with kmemdup(), so a crtc which once used a DSPP keeps a pointer
to it for the rest of its life -- across reservations which no longer own
that block.
Assign NULL when there is no DSPP for the mixer.
Fixes: 4259ff7ae509 ("drm/msm/dpu: add support for pcc color block in dpu driver")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
index bf593020e8e4..5dc0c9aeaa5d 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
@@ -1480,8 +1480,8 @@ static int dpu_crtc_assign_resources(struct drm_crtc *crtc,
cstate->mixers[i].hw_lm = to_dpu_hw_mixer(hw_lm[i]);
cstate->mixers[i].lm_ctl = to_dpu_hw_ctl(hw_ctl[ctl_idx]);
- if (i < num_dspp)
- cstate->mixers[i].hw_dspp = to_dpu_hw_dspp(hw_dspp[i]);
+ cstate->mixers[i].hw_dspp = i < num_dspp ?
+ to_dpu_hw_dspp(hw_dspp[i]) : NULL;
}
cstate->num_mixers = num_lm;
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 5/6] drm/msm/dpu: only reassign resources when the encoder is reprogrammed
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (3 preceding siblings ...)
2026-09-08 15:24 ` [PATCH v2 4/6] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
@ 2026-09-08 15:24 ` Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 6/6] drm/ci: mark pixel-format tests as passing on SC7180 Dmitry Baryshkov
5 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
Running kms_plane@pixel-format-source-clamping causes SMMU faults
(fsr=0x402, iova=0x0001fb00): it sets a CTM, after which the display stops
updating, so the test times out waiting for a CRC and the pipe is still
fetching the framebuffers released when the runner kills it.
dpu_crtc_atomic_check() reassigns resources whenever color_mgmt_changed is
set, but crtc_set_mode() only calls ->atomic_mode_set() for mode_changed or
connectors_changed. A colour management update therefore re-reserves and can
get a different CTL: cstate->mixers[].lm_ctl points at the new one while
phys->hw_ctl still points at the old, so the flush mask is accumulated on one
and triggered on the other. CTL_FLUSH is written with zero, so the pipe is
not reprogrammed again until the next modeset.
Reassign only when the encoder is reprogrammed too. Enabling colour
management still needs a modeset, as that is when DSPPs join the topology, so
ask for one in dpu_crtc_check_mode_changed() and only there. Drop the
msm_atomic_check() hack, which forced a modeset both ways, ignored GAMMA_LUT
and overrode allow_modeset behind userspace's back.
Fixes: 39a750ff5fc9 ("drm/msm/dpu: Add DSPP GC driver to provide GAMMA_LUT DRM property")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c | 28 ++++++++++++++++++++++++++--
drivers/gpu/drm/msm/msm_atomic.c | 17 +----------------
2 files changed, 27 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
index 5dc0c9aeaa5d..a3b1d2d7669a 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
@@ -1496,6 +1496,26 @@ static int dpu_crtc_assign_resources(struct drm_crtc *crtc,
*
* Check if the changes in the object properties demand full mode set.
*/
+static bool dpu_crtc_needs_dspp(const struct drm_crtc_state *crtc_state)
+{
+ return crtc_state->ctm || crtc_state->gamma_lut;
+}
+
+static bool dpu_crtc_has_dspp(const struct drm_crtc_state *crtc_state)
+{
+ const struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc_state);
+ unsigned int i;
+
+ if (!cstate->num_mixers)
+ return false;
+
+ for (i = 0; i < cstate->num_mixers; i++)
+ if (!cstate->mixers[i].hw_dspp)
+ return false;
+
+ return true;
+}
+
int dpu_crtc_check_mode_changed(struct drm_crtc_state *old_crtc_state,
struct drm_crtc_state *new_crtc_state)
{
@@ -1506,6 +1526,11 @@ int dpu_crtc_check_mode_changed(struct drm_crtc_state *old_crtc_state,
DRM_DEBUG_ATOMIC("%d\n", crtc->base.id);
+ /* DSPPs are only reserved during a modeset */
+ if (dpu_crtc_needs_dspp(new_crtc_state) &&
+ !dpu_crtc_has_dspp(old_crtc_state))
+ new_crtc_state->mode_changed = true;
+
/* there might be cases where encoder needs a modeset too */
drm_for_each_encoder_mask(drm_enc, crtc->dev, new_crtc_state->encoder_mask) {
if (dpu_encoder_needs_modeset(drm_enc, new_crtc_state->state))
@@ -1535,8 +1560,7 @@ static int dpu_crtc_atomic_check(struct drm_crtc *crtc,
bool needs_dirtyfb = dpu_crtc_needs_dirtyfb(crtc_state);
/* don't reallocate resources if only ACTIVE has beeen changed */
- if (crtc_state->mode_changed || crtc_state->connectors_changed ||
- crtc_state->color_mgmt_changed) {
+ if (crtc_state->mode_changed || crtc_state->connectors_changed) {
rc = dpu_crtc_assign_resources(crtc, crtc_state);
if (rc < 0)
return rc;
diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c
index a8babf1dbe0d..7b480981becc 100644
--- a/drivers/gpu/drm/msm/msm_atomic.c
+++ b/drivers/gpu/drm/msm/msm_atomic.c
@@ -185,22 +185,7 @@ int msm_atomic_check(struct drm_device *dev, struct drm_atomic_commit *state)
{
struct msm_drm_private *priv = dev->dev_private;
struct msm_kms *kms = priv->kms;
- struct drm_crtc_state *old_crtc_state, *new_crtc_state;
- struct drm_crtc *crtc;
- int i, ret = 0;
-
- /*
- * FIXME: stop setting allow_modeset and move this check to the DPU
- * driver.
- */
- for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
- new_crtc_state, i) {
- if ((old_crtc_state->ctm && !new_crtc_state->ctm) ||
- (!old_crtc_state->ctm && new_crtc_state->ctm)) {
- new_crtc_state->mode_changed = true;
- state->allow_modeset = true;
- }
- }
+ int ret = 0;
if (kms && kms->funcs && kms->funcs->check_mode_changed)
ret = kms->funcs->check_mode_changed(kms, state);
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2 6/6] drm/ci: mark pixel-format tests as passing on SC7180
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (4 preceding siblings ...)
2026-09-08 15:24 ` [PATCH v2 5/6] drm/msm/dpu: only reassign resources when the encoder is reprogrammed Dmitry Baryshkov
@ 2026-09-08 15:24 ` Dmitry Baryshkov
5 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-09-08 15:24 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Antonino Maniscalco, Kalyan Thota, Federico Amedeo Izzo,
Helen Koike, Vignesh Raman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann
Cc: linux-arm-msm, dri-devel, freedreno, linux-kernel
kms_plane@pixel-format and kms_plane@pixel-format-source-clamping timed out
waiting for the first CRC once the test had set a legacy gamma LUT. They
pass since "drm/msm/dpu: only reassign resources when the encoder is
reprogrammed", so drop them from the expected failures.
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-kingoftown-fails.txt | 2 --
drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-lazor-limozeen-fails.txt | 2 --
2 files changed, 4 deletions(-)
diff --git a/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-kingoftown-fails.txt b/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-kingoftown-fails.txt
index 2daf4ab879a9..ea3013faa37c 100644
--- a/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-kingoftown-fails.txt
+++ b/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-kingoftown-fails.txt
@@ -18,5 +18,3 @@ kms_pipe_crc_basic@compare-crc-sanitycheck-nv12,Fail
kms_plane_alpha_blend@alpha-7efc,Fail
kms_plane_alpha_blend@coverage-7efc,Fail
kms_plane_alpha_blend@coverage-vs-premult-vs-constant,Fail
-kms_plane@pixel-format,Fail
-kms_plane@pixel-format-source-clamping,Fail
diff --git a/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-lazor-limozeen-fails.txt b/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-lazor-limozeen-fails.txt
index 2daf4ab879a9..ea3013faa37c 100644
--- a/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-lazor-limozeen-fails.txt
+++ b/drivers/gpu/drm/ci/xfails/msm-sc7180-trogdor-lazor-limozeen-fails.txt
@@ -18,5 +18,3 @@ kms_pipe_crc_basic@compare-crc-sanitycheck-nv12,Fail
kms_plane_alpha_blend@alpha-7efc,Fail
kms_plane_alpha_blend@coverage-7efc,Fail
kms_plane_alpha_blend@coverage-vs-premult-vs-constant,Fail
-kms_plane@pixel-format,Fail
-kms_plane@pixel-format-source-clamping,Fail
--
2.47.3
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v2 1/6] drm/msm: serialise framebuffer pin state
2026-09-08 15:24 ` [PATCH v2 1/6] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
@ 2026-09-08 15:38 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-08 15:38 UTC (permalink / raw)
To: Dmitry Baryshkov; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Initialization order vulnerability: mutex and refcount initialized after object is globally visible.
--
commit 43edb96a02a3e020c9ba63b15f602a74d5eb759f
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
drm/msm: serialise framebuffer pin state
This commit introduces a mutex to serialize framebuffer pin states,
resolving an issue where parallel prepare and cleanup callbacks could
leave a plane programmed with a NULL base address.
> diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
> --- a/drivers/gpu/drm/msm/msm_fb.c
> +++ b/drivers/gpu/drm/msm/msm_fb.c
[ ... ]
> @@ -206,6 +227,7 @@ msm_framebuffer_init(struct drm_device *dev, const struct drm_format_info *info,
> }
>
> refcount_set(&msm_fb->dirtyfb, 1);
> + mutex_init(&msm_fb->lock);
[Severity: High]
Since drm_framebuffer_init() is called just before this block and registers
the object in the DRM core IDR, the framebuffer becomes immediately visible
to userspace.
If userspace guesses the assigned framebuffer ID and concurrently issues an
atomic commit ioctl, could msm_framebuffer_prepare() execute and call
mutex_lock(&msm_fb->lock) before mutex_init() finishes here?
Could this race lead to memory corruption or deadlocks by clobbering the
mutex state? Would it be safer to move the mutex initialization to before
the drm_framebuffer_init() call?
>
> drm_dbg_state(dev, "create: FB ID: %d (%p)\n", fb->base.id, fb);
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-fd-kms-fix-smmu-v2-0-9391815742a8@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 3/6] drm/msm: release scanout framebuffers only after a vblank
2026-09-08 15:24 ` [PATCH v2 3/6] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
@ 2026-09-08 15:42 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-08 15:42 UTC (permalink / raw)
To: Dmitry Baryshkov; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Fatal NULL pointer dereference in msm_kms_fini_fb_unpin() when handling initialization errors if msm_kms_init_fb_unpin() was not completed.
- [High] Deferred framebuffer unpin logic creates a TOCTOU race for shared framebuffers, leading to premature synchronous unpin and IOMMU faults.
--
commit ce24ef75bb4418725e7dde4f8dcb994be7c35638
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
drm/msm: release scanout framebuffers only after a vblank
This commit defers the release of scanout framebuffers until after a vblank
has occurred by queuing the unpin work via a vblank worker. It prevents
hardware from fetching unmapped memory by ensuring the new configuration is
fully latched before detaching the VMA.
> diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
> index dfe040d5260bc..2e317bec9fbbb 100644
> --- a/drivers/gpu/drm/msm/msm_fb.c
> +++ b/drivers/gpu/drm/msm/msm_fb.c
[ ... ]
> @@ -144,6 +145,9 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
>
> memset(msm_fb->iova, 0, sizeof(msm_fb->iova));
>
> + if (crtc && msm_crtc_queue_fb_unpin(crtc, fb))
> + goto out;
> +
> for (i = 0; i < n; i++) {
> msm_gem_unpin_iova(fb->obj[i], vm);
> msm_gem_vma_put(fb->obj[i]);
[Severity: High]
Does this deferred unpin logic handle shared framebuffers safely?
If a framebuffer is shared across multiple CRTCs and an atomic commit removes
it from all of them simultaneously, msm_framebuffer_cleanup() is called
sequentially for each plane. The last plane processed drops prepare_count to
zero and funnels the unpin to a single CRTC.
If that final CRTC happens to be disabled (where crtc->state->active is false
in msm_crtc_queue_fb_unpin()), it returns false, causing an immediate
synchronous unpin here. This could fault any other active CRTCs that haven't
reached their vblank and are still actively scanning out the old frame.
Even if active, could the last processed CRTC reach its vblank and unpin the
buffer before slower CRTCs finish their frames?
[ ... ]
> diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
> index e5d0ea6294484..6a857ad3464c4 100644
> --- a/drivers/gpu/drm/msm/msm_kms.c
> +++ b/drivers/gpu/drm/msm/msm_kms.c
[ ... ]
> +void msm_kms_fini_fb_unpin(struct drm_device *dev)
> +{
> + struct msm_drm_private *priv = dev->dev_private;
> + struct msm_kms *kms = priv->kms;
> + struct drm_crtc *crtc;
> +
> + drm_for_each_crtc(crtc, dev) {
> + unsigned int idx = drm_crtc_index(crtc);
> +
> + if (idx >= ARRAY_SIZE(kms->fb_unpin))
> + continue;
> +
> + drm_vblank_work_cancel_sync(&kms->fb_unpin[idx].vblank_work);
[Severity: High]
Can this cause a NULL pointer dereference on initialization error paths?
If msm_drm_kms_init() fails after CRTCs are created but before
msm_kms_init_fb_unpin() succeeds (for example, if drm_vblank_init() fails),
the error path calls msm_drm_kms_uninit(), which unconditionally calls
msm_kms_fini_fb_unpin().
Because the vblank_work structs were zero-allocated and never initialized,
drm_vblank_work_cancel_sync() will dereference a NULL work->vblank:
drivers/gpu/drm/drm_vblank_work.c:drm_vblank_work_cancel_sync() {
struct drm_vblank_crtc *vblank = work->vblank;
struct drm_device *dev = vblank->dev;
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-fd-kms-fix-smmu-v2-0-9391815742a8@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2 4/6] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
2026-09-08 15:24 ` [PATCH v2 4/6] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
@ 2026-09-08 15:42 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-08 15:42 UTC (permalink / raw)
To: Dmitry Baryshkov; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Stale pointers for hardware blocks (hw_dsc, hw_cwb) in persistent encoder state are not cleared when resource counts shrink, leading to incorrect hardware configuration and kernel panics.
--
commit 6954c50ce10cab5ddfb3ee7cbf148810fc5b6d78
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
This commit fixes a bug where stale DSPP pointers are retained in the CRTC
state across modesets. It assigns NULL to the DSPP pointer when there is no
DSPP for the mixer.
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
> index bf593020e8e45..5dc0c9aeaa5d7 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
> @@ -1480,8 +1480,8 @@ static int dpu_crtc_assign_resources(struct drm_crtc *crtc,
>
> cstate->mixers[i].hw_lm = to_dpu_hw_mixer(hw_lm[i]);
> cstate->mixers[i].lm_ctl = to_dpu_hw_ctl(hw_ctl[ctl_idx]);
> - if (i < num_dspp)
> - cstate->mixers[i].hw_dspp = to_dpu_hw_dspp(hw_dspp[i]);
> + cstate->mixers[i].hw_dspp = i < num_dspp ?
> + to_dpu_hw_dspp(hw_dspp[i]) : NULL;
> }
[Severity: High]
This is a pre-existing issue, but does this same stale pointer bug also
happen with hw_cwb and hw_dsc in dpu_encoder_virt_atomic_mode_set()?
Looking at drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c, if a modeset drops
CWB or DSC features, the loops only iterate up to the new smaller num_cwb
or num_dsc:
for (i = 0; i < num_cwb; i++) {
dpu_enc->hw_cwb[i] = to_dpu_hw_cwb(hw_cwb[i]);
cwb_mask |= BIT(dpu_enc->hw_cwb[i]->idx - CWB_0);
}
Since trailing elements aren't cleared, dpu_enc->hw_cwb[i] and
dpu_enc->hw_dsc[i] retain stale pointers.
When dpu_encoder_helper_phys_setup_cwb() later iterates over
MAX_CWB_PER_ENC, it finds the stale pointer and attempts to read from
the uninitialized stack array rt_pp_list[i]:
for (int i = 0; i < MAX_CWB_PER_ENC; i++) {
hw_cwb = dpu_enc->hw_cwb[i];
if (!hw_cwb)
continue;
if (enable) {
struct dpu_hw_pingpong *hw_pp =
to_dpu_hw_pingpong(rt_pp_list[i]);
cwb_cfg.pp_idx = hw_pp->idx;
...
This dereferences hw_pp->idx from stack garbage, causing a kernel panic.
Similarly, in dpu_encoder_use_dsc_merge(), stale hw_dsc pointers artificially
inflate num_dsc, which breaks topology decisions like 3D blend mode
selection:
for (i = 0; i < MAX_CHANNELS_PER_ENC; i++)
if (dpu_enc->hw_dsc[i])
num_dsc++;
Should similar clearing logic be added to dpu_encoder_virt_atomic_mode_set()
to clear trailing hw_cwb and hw_dsc pointers?
>
> cstate->num_mixers = num_lm;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-fd-kms-fix-smmu-v2-0-9391815742a8@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-08 15:42 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 15:24 [PATCH v2 0/6] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 1/6] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
2026-09-08 15:38 ` sashiko-bot
2026-09-08 15:24 ` [PATCH v2 2/6] drm/msm: fix framebuffer pin refcount leak on prepare failure Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 3/6] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
2026-09-08 15:42 ` sashiko-bot
2026-09-08 15:24 ` [PATCH v2 4/6] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
2026-09-08 15:42 ` sashiko-bot
2026-09-08 15:24 ` [PATCH v2 5/6] drm/msm/dpu: only reassign resources when the encoder is reprogrammed Dmitry Baryshkov
2026-09-08 15:24 ` [PATCH v2 6/6] drm/ci: mark pixel-format tests as passing on SC7180 Dmitry Baryshkov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox