* [PATCH v3 0/8] drm/msm: fix SMMU fault dumps
@ 2026-09-12 12:48 Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 1/8] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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 the cases which I stumbled upon, together with the issues
found while chasing them: hardware block pointers surviving the
reservation which handed them out, and the teardown of a KMS which was
never fully set up.
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
Changes in v3:
- Initialise the framebuffer's lock and dirtyfb count before
drm_framebuffer_init() publishes the framebuffer (Sashiko)
- New patch: unwind msm_drm_kms_init() on failure rather than handing a
half-initialised kms to msm_drm_kms_uninit() (prompted by Sashiko)
- Drop the pin count from the deferred unpin work rather than from
->cleanup_fb(), so a framebuffer scanned out by several crtcs stays
pinned until the last of them has passed a vblank (Sashiko)
- Defer each retired framebuffer with its own drm_vblank_work, as i915
does for cursor framebuffers, instead of a per-crtc drm_flip_work: the
release no longer takes a detour through kms->wq, and nothing needs
setting up or tearing down in the kms init path
- Release the framebuffers still pending on a crtc by hand when its vblank
is switched off; its interface is already disabled by then, so no
further vblank arrives to run the work
- New patch: clear the DSC blocks left by a previous reservation, the same
bug as the DSPP one (Sashiko)
- Link to v2: https://patch.msgid.link/20260908-fd-kms-fix-smmu-v2-0-9391815742a8@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 (8):
drm/msm: serialise framebuffer pin state
drm/msm: fix framebuffer pin refcount leak on prepare failure
drm/msm: unwind msm_drm_kms_init() on 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: clear the DSC blocks left by a previous reservation
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 ++++-
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 7 +-
.../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.c | 27 +++-
drivers/gpu/drm/msm/msm_drv.h | 4 +-
drivers/gpu/drm/msm/msm_fb.c | 78 +++++++++--
drivers/gpu/drm/msm/msm_kms.c | 151 +++++++++++++++++++--
drivers/gpu/drm/msm/msm_kms.h | 16 +++
16 files changed, 283 insertions(+), 68 deletions(-)
---
base-commit: 140b13475302601368c0cf4e193e66126a49feb3
change-id: 20260902-fd-kms-fix-smmu-2d4baaf460b0
Best regards,
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3 1/8] drm/msm: serialise framebuffer pin state
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 2/8] drm/msm: fix framebuffer pin refcount leak on prepare failure Dmitry Baryshkov
` (6 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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. drm_framebuffer_init()
adds the framebuffer to the object idr, from where userspace can reach it
before msm_framebuffer_init() returns, so take the private state out of
its way and initialise it first.
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 | 44 +++++++++++++++++++++++++++++++++-----------
1 file changed, 33 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
index 60c108d35d2a..77415302e6d8 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)
@@ -199,14 +220,15 @@ msm_framebuffer_init(struct drm_device *dev, const struct drm_format_info *info,
drm_helper_mode_fill_fb_struct(dev, fb, info, mode_cmd);
+ refcount_set(&msm_fb->dirtyfb, 1);
+ mutex_init(&msm_fb->lock);
+
ret = drm_framebuffer_init(dev, fb, &msm_framebuffer_funcs);
if (ret) {
DRM_DEV_ERROR(dev->dev, "framebuffer init failed: %d\n", ret);
goto fail;
}
- refcount_set(&msm_fb->dirtyfb, 1);
-
drm_dbg_state(dev, "create: FB ID: %d (%p)\n", fb->base.id, fb);
return fb;
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 2/8] drm/msm: fix framebuffer pin refcount leak on prepare failure
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 1/8] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 3/8] drm/msm: unwind msm_drm_kms_init() on failure Dmitry Baryshkov
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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 77415302e6d8..552ca5cf0745 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] 13+ messages in thread
* [PATCH v3 3/8] drm/msm: unwind msm_drm_kms_init() on failure
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 1/8] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 2/8] drm/msm: fix framebuffer pin refcount leak on prepare failure Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 13:33 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 4/8] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
` (4 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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_drm_kms_init() leaves behind whatever it has already set up when it
fails and lets the caller sort it out: msm_drm_init() jumps to
msm_drm_uninit(), which runs msm_drm_kms_uninit() for every device which
has a kms. That is the teardown of a fully initialised KMS -- it flushes
kms->wq, calls ->irq_uninstall() and frees the IRQ, none of which exist
yet when the failure happened early.
The kms driver's own init is the first step which can fail, and since
commit a409b78fcdf7 ("drm/msm: move wq handling to KMS code") the
workqueue is created by msm_kms_init() rather than by msm_drm_init(), so a
failure there leaves a NULL kms->wq for flush_workqueue().
Undo the steps which have completed instead, and let msm_drm_init() unwind
its own error paths rather than calling the full teardown.
Fixes: a409b78fcdf7 ("drm/msm: move wq handling to KMS code")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/msm_drv.c | 27 +++++++++++++++++++++------
drivers/gpu/drm/msm/msm_kms.c | 36 ++++++++++++++++++++++++------------
2 files changed, 45 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index db1b655dd055..c842321c9069 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -159,29 +159,44 @@ static int msm_drm_init(struct device *dev, const struct drm_driver *drv,
ret = msm_gem_shrinker_init(ddev);
if (ret)
- goto err_msm_uninit;
+ goto err_unbind;
if (priv->kms_init) {
ret = msm_drm_kms_init(dev, drv);
if (ret)
- goto err_msm_uninit;
+ goto err_shrinker_cleanup;
}
ret = drm_dev_register(ddev, 0);
if (ret)
- goto err_msm_uninit;
+ goto err_kms_uninit;
ret = msm_debugfs_late_init(ddev);
if (ret)
- goto err_msm_uninit;
+ goto err_unregister;
if (priv->kms_init)
msm_drm_kms_post_init(dev);
return 0;
-err_msm_uninit:
- msm_drm_uninit(dev, gpu_ops);
+err_unregister:
+ drm_dev_unregister(ddev);
+ if (priv->kms_init)
+ msm_drm_kms_unregister(dev);
+ msm_rd_debugfs_cleanup(priv);
+err_kms_uninit:
+ if (priv->kms_init)
+ msm_drm_kms_uninit(dev);
+err_shrinker_cleanup:
+ msm_gem_shrinker_cleanup(ddev);
+err_unbind:
+ if (gpu_ops)
+ gpu_ops->unbind(dev, dev, NULL);
+ else
+ component_unbind_all(dev, ddev);
+ ddev->dev_private = NULL;
+ drm_dev_put(ddev);
return ret;
diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
index e5d0ea629448..f3e39c3907a9 100644
--- a/drivers/gpu/drm/msm/msm_kms.c
+++ b/drivers/gpu/drm/msm/msm_kms.c
@@ -225,13 +225,23 @@ void msm_drm_kms_unregister(struct device *dev)
drm_atomic_helper_shutdown(ddev);
}
+static void msm_drm_kms_destroy_event_threads(struct msm_kms *kms)
+{
+ int i;
+
+ for (i = 0; i < MAX_CRTCS; i++) {
+ if (kms->event_thread[i].worker)
+ kthread_destroy_worker(kms->event_thread[i].worker);
+ kms->event_thread[i].worker = NULL;
+ }
+}
+
void msm_drm_kms_uninit(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct msm_drm_private *priv = platform_get_drvdata(pdev);
struct drm_device *ddev = priv->dev;
struct msm_kms *kms = priv->kms;
- int i;
BUG_ON(!kms);
@@ -242,11 +252,7 @@ void msm_drm_kms_uninit(struct device *dev)
flush_workqueue(kms->wq);
- /* clean up event worker threads */
- for (i = 0; i < MAX_CRTCS; i++) {
- if (kms->event_thread[i].worker)
- kthread_destroy_worker(kms->event_thread[i].worker);
- }
+ msm_drm_kms_destroy_event_threads(kms);
drm_kms_helper_poll_fini(ddev);
@@ -282,7 +288,7 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
ret = priv->kms_init(ddev);
if (ret) {
DRM_DEV_ERROR(dev, "failed to load kms\n");
- goto err_msm_uninit;
+ goto err_destroy_kms;
}
/* Enable normalization of plane zpos */
@@ -295,7 +301,7 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
ret = kms->funcs->hw_init(kms);
if (ret) {
DRM_DEV_ERROR(dev, "kms hw init failed: %d\n", ret);
- goto err_msm_uninit;
+ goto err_destroy_kms;
}
drm_helper_move_panel_connectors_to_head(ddev);
@@ -311,7 +317,7 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
ret = PTR_ERR(ev_thread->worker);
DRM_DEV_ERROR(dev, "failed to create crtc_event kthread\n");
ev_thread->worker = NULL;
- goto err_msm_uninit;
+ goto err_destroy_event_threads;
}
sched_set_fifo(ev_thread->worker->task);
@@ -320,7 +326,7 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
ret = drm_vblank_init(ddev, ddev->mode_config.num_crtc);
if (ret < 0) {
DRM_DEV_ERROR(dev, "failed to initialize vblank\n");
- goto err_msm_uninit;
+ goto err_destroy_event_threads;
}
pm_runtime_get_sync(dev);
@@ -328,14 +334,20 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
pm_runtime_put_sync(dev);
if (ret < 0) {
DRM_DEV_ERROR(dev, "failed to install IRQ handler\n");
- goto err_msm_uninit;
+ goto err_destroy_event_threads;
}
drm_mode_config_reset(ddev);
return 0;
-err_msm_uninit:
+err_destroy_event_threads:
+ msm_drm_kms_destroy_event_threads(kms);
+err_destroy_kms:
+ msm_disp_snapshot_destroy(ddev);
+ if (kms->funcs)
+ kms->funcs->destroy(kms);
+
return ret;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 4/8] drm/msm: release scanout framebuffers only after a vblank
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (2 preceding siblings ...)
2026-09-12 12:48 ` [PATCH v3 3/8] drm/msm: unwind msm_drm_kms_init() on failure Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 12:59 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 5/8] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
` (3 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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
Hand the retired framebuffer to the crtc instead and drop the pin and the
vma reference from a drm_vblank_work, as i915 does for its cursor
framebuffers. The work holds a reference on the framebuffer, and
drm_vblank_work_schedule() holds a vblank reference until it runs.
The pin count is dropped by the deferred work rather than by
->cleanup_fb(), so a framebuffer scanned out by several crtcs stays pinned
until the last of them has passed a vblank, not until the last one has
retired it. Deferring here also covers the async plane update path, which
has no commit tail at all: drm_atomic_helper_async_commit() programs the
hardware and drm_atomic_helper_unprepare_planes() releases the old
framebuffer straight away.
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.
A crtc which is being switched off stops fetching as well, and its
interface has already been disabled by the time the helpers get to it, so
no further vblank arrives to run the pending works: keep them on a list
per crtc and release them there by hand rather than waiting.
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 | 4 +-
drivers/gpu/drm/msm/msm_fb.c | 19 +++-
drivers/gpu/drm/msm/msm_kms.c | 115 +++++++++++++++++++++
drivers/gpu/drm/msm/msm_kms.h | 16 +++
11 files changed, 157 insertions(+), 12 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..dc279a99e257 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -254,7 +254,9 @@ 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);
+void msm_framebuffer_unpin(struct drm_framebuffer *fb);
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 552ca5cf0745..5ae8ce3bc370 100644
--- a/drivers/gpu/drm/msm/msm_fb.c
+++ b/drivers/gpu/drm/msm/msm_fb.c
@@ -127,16 +127,13 @@ 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_unpin(struct drm_framebuffer *fb)
{
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 i, n = fb->format->num_planes;
- if (needed_dirtyfb)
- refcount_dec(&msm_fb->dirtyfb);
-
mutex_lock(&msm_fb->lock);
if (--msm_fb->prepare_count)
@@ -153,6 +150,20 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_dirtyfb)
mutex_unlock(&msm_fb->lock);
}
+void msm_framebuffer_cleanup(struct drm_framebuffer *fb, struct drm_crtc *crtc,
+ bool needed_dirtyfb)
+{
+ struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
+
+ if (needed_dirtyfb)
+ refcount_dec(&msm_fb->dirtyfb);
+
+ if (crtc && msm_crtc_queue_fb_unpin(crtc, fb))
+ return;
+
+ msm_framebuffer_unpin(fb);
+}
+
uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane)
{
struct msm_framebuffer *msm_fb = to_msm_framebuffer(fb);
diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
index f3e39c3907a9..4582ba040f6f 100644
--- a/drivers/gpu/drm/msm/msm_kms.c
+++ b/drivers/gpu/drm/msm/msm_kms.c
@@ -11,8 +11,10 @@
#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/drm_vblank_work.h>
#include <drm/clients/drm_client_setup.h>
#include "disp/msm_disp_snapshot.h"
@@ -165,6 +167,119 @@ void msm_crtc_disable_vblank(struct drm_crtc *crtc)
vblank_ctrl_queue_work(priv, crtc, false);
}
+struct msm_fb_unpin_work {
+ struct drm_vblank_work base;
+ struct list_head node;
+ struct msm_kms_fb_unpin *pending;
+ struct drm_framebuffer *fb;
+};
+
+static void msm_kms_fb_unpin_release(struct msm_fb_unpin_work *unpin)
+{
+ msm_framebuffer_unpin(unpin->fb);
+ drm_framebuffer_put(unpin->fb);
+ kfree(unpin);
+}
+
+static void msm_kms_fb_unpin_work(struct kthread_work *work)
+{
+ struct msm_fb_unpin_work *unpin =
+ container_of(to_drm_vblank_work(work), struct msm_fb_unpin_work,
+ base);
+ struct msm_kms_fb_unpin *pending = unpin->pending;
+
+ spin_lock(&pending->lock);
+ if (list_empty(&unpin->node)) {
+ spin_unlock(&pending->lock);
+ return;
+ }
+ list_del_init(&unpin->node);
+ spin_unlock(&pending->lock);
+
+ msm_kms_fb_unpin_release(unpin);
+}
+
+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);
+ struct msm_kms_fb_unpin *pending;
+ struct msm_fb_unpin_work *unpin;
+
+ if (!kms || idx >= ARRAY_SIZE(kms->fb_unpin))
+ goto out;
+
+ pending = &kms->fb_unpin[idx];
+
+ /*
+ * The crtc stops fetching here, and with it the vblanks the pending
+ * works are waiting for, so release the framebuffers directly.
+ */
+ for (;;) {
+ spin_lock(&pending->lock);
+ unpin = list_first_entry_or_null(&pending->fbs, typeof(*unpin),
+ node);
+ if (unpin)
+ list_del_init(&unpin->node);
+ spin_unlock(&pending->lock);
+
+ if (!unpin)
+ break;
+
+ drm_vblank_work_cancel_sync(&unpin->base);
+ msm_kms_fb_unpin_release(unpin);
+ }
+
+out:
+ 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);
+ struct msm_kms_fb_unpin *pending;
+ struct msm_fb_unpin_work *unpin;
+
+ if (!kms || idx >= ARRAY_SIZE(kms->fb_unpin))
+ return false;
+
+ if (!crtc->state->active)
+ return false;
+
+ pending = &kms->fb_unpin[idx];
+
+ unpin = kzalloc_obj(*unpin);
+ if (!unpin)
+ return false;
+
+ unpin->fb = fb;
+ unpin->pending = pending;
+ drm_framebuffer_get(fb);
+
+ drm_vblank_work_init(&unpin->base, crtc, msm_kms_fb_unpin_work);
+
+ spin_lock(&pending->lock);
+ list_add_tail(&unpin->node, &pending->fbs);
+ spin_unlock(&pending->lock);
+
+ if (drm_vblank_work_schedule(&unpin->base,
+ drm_crtc_vblank_count(crtc) + 1, true) != 1) {
+ spin_lock(&pending->lock);
+ list_del_init(&unpin->node);
+ spin_unlock(&pending->lock);
+
+ drm_framebuffer_put(fb);
+ kfree(unpin);
+
+ return false;
+ }
+
+ return true;
+}
+
static int msm_kms_fault_handler(void *arg, unsigned long iova, int flags, void *data)
{
struct msm_kms *kms = arg;
diff --git a/drivers/gpu/drm/msm/msm_kms.h b/drivers/gpu/drm/msm/msm_kms.h
index f25b31e502d2..6f305b4409ea 100644
--- a/drivers/gpu/drm/msm/msm_kms.h
+++ b/drivers/gpu/drm/msm/msm_kms.h
@@ -135,6 +135,12 @@ struct msm_drm_thread {
struct kthread_worker *worker;
};
+struct msm_kms_fb_unpin {
+ /* protects the list of framebuffers waiting for a vblank: */
+ spinlock_t lock;
+ struct list_head fbs;
+};
+
struct msm_kms {
const struct msm_kms_funcs *funcs;
struct drm_device *dev;
@@ -170,8 +176,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)
{
@@ -180,6 +191,11 @@ static inline int msm_kms_init(struct msm_kms *kms,
for (i = 0; i < ARRAY_SIZE(kms->commit_lock); i++)
mutex_init(&kms->commit_lock[i]);
+ for (i = 0; i < ARRAY_SIZE(kms->fb_unpin); i++) {
+ spin_lock_init(&kms->fb_unpin[i].lock);
+ INIT_LIST_HEAD(&kms->fb_unpin[i].fbs);
+ }
+
kms->funcs = funcs;
kms->wq = alloc_ordered_workqueue("msm", 0);
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 5/8] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (3 preceding siblings ...)
2026-09-12 12:48 ` [PATCH v3 4/8] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 13:02 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 6/8] drm/msm/dpu: clear the DSC blocks left by a previous reservation Dmitry Baryshkov
` (2 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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] 13+ messages in thread
* [PATCH v3 6/8] drm/msm/dpu: clear the DSC blocks left by a previous reservation
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (4 preceding siblings ...)
2026-09-12 12:48 ` [PATCH v3 5/8] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 12:59 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 7/8] drm/msm/dpu: only reassign resources when the encoder is reprogrammed Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 8/8] drm/ci: mark pixel-format tests as passing on SC7180 Dmitry Baryshkov
7 siblings, 1 reply; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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_encoder_virt_atomic_mode_set() fills dpu_enc->hw_dsc[] with the DSC
blocks of the new reservation, but only the first num_dsc entries. The
array is never cleared, so an encoder which once drove two DSC blocks
keeps pointing at the second one after it has been given a topology with
a single DSC.
dpu_encoder_use_dsc_merge() counts the non-NULL entries of the whole
array and compares them against the number of interfaces, so the leftover
pointer makes it report DSC merge for a topology which has none, and
dpu_encoder_prep_dsc() then programs DSC_MODE_MULTIPLEX into the only DSC
block that is really used.
Assign NULL when the new reservation has no block for the slot.
Fixes: 58dca9810749 ("drm/msm/disp/dpu1: Add support for DSC in encoder")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
index 1f20695f81e3..50c49259a23e 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
@@ -1222,9 +1222,10 @@ static void dpu_encoder_virt_atomic_mode_set(struct drm_encoder *drm_enc,
num_dsc = dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state,
drm_enc->crtc, DPU_HW_BLK_DSC,
hw_dsc, ARRAY_SIZE(hw_dsc));
- for (i = 0; i < num_dsc; i++) {
- dpu_enc->hw_dsc[i] = to_dpu_hw_dsc(hw_dsc[i]);
- dsc_mask |= BIT(dpu_enc->hw_dsc[i]->idx - DSC_0);
+ for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) {
+ dpu_enc->hw_dsc[i] = i < num_dsc ? to_dpu_hw_dsc(hw_dsc[i]) : NULL;
+ if (dpu_enc->hw_dsc[i])
+ dsc_mask |= BIT(dpu_enc->hw_dsc[i]->idx - DSC_0);
}
dpu_enc->dsc_mask = dsc_mask;
--
2.47.3
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 7/8] drm/msm/dpu: only reassign resources when the encoder is reprogrammed
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (5 preceding siblings ...)
2026-09-12 12:48 ` [PATCH v3 6/8] drm/msm/dpu: clear the DSC blocks left by a previous reservation Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 8/8] drm/ci: mark pixel-format tests as passing on SC7180 Dmitry Baryshkov
7 siblings, 0 replies; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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] 13+ messages in thread
* [PATCH v3 8/8] drm/ci: mark pixel-format tests as passing on SC7180
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
` (6 preceding siblings ...)
2026-09-12 12:48 ` [PATCH v3 7/8] drm/msm/dpu: only reassign resources when the encoder is reprogrammed Dmitry Baryshkov
@ 2026-09-12 12:48 ` Dmitry Baryshkov
7 siblings, 0 replies; 13+ messages in thread
From: Dmitry Baryshkov @ 2026-09-12 12:48 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] 13+ messages in thread
* Re: [PATCH v3 4/8] drm/msm: release scanout framebuffers only after a vblank
2026-09-12 12:48 ` [PATCH v3 4/8] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
@ 2026-09-12 12:59 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-12 12:59 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] Concurrent execution of msm_crtc_queue_fb_unpin() and msm_crtc_vblank_off() causes a use-after-free of the unpin work item.
--
commit d546c6b2b93655071c146a3c50f9a3cb4baa8f1b
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Subject: drm/msm: release scanout framebuffers only after a vblank
This commit modifies the framebuffer cleanup process to defer unpinning
until after a vblank by scheduling a drm_vblank_work item. It also
provides a mechanism to release these works directly if the CRTC is
switched off and no further vblanks will arrive.
> diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
> index f3e39c3907a93..4582ba040f6ff 100644
> --- a/drivers/gpu/drm/msm/msm_kms.c
> +++ b/drivers/gpu/drm/msm/msm_kms.c
[ ... ]
> @@ -165,6 +167,119 @@ void msm_crtc_disable_vblank(struct drm_crtc *crtc)
> vblank_ctrl_queue_work(priv, crtc, false);
> }
>
> +struct msm_fb_unpin_work {
> + struct drm_vblank_work base;
> + struct list_head node;
> + struct msm_kms_fb_unpin *pending;
> + struct drm_framebuffer *fb;
> +};
> +
> +static void msm_kms_fb_unpin_release(struct msm_fb_unpin_work *unpin)
> +{
> + msm_framebuffer_unpin(unpin->fb);
> + drm_framebuffer_put(unpin->fb);
> + kfree(unpin);
> +}
[ ... ]
> +void msm_crtc_vblank_off(struct drm_crtc *crtc)
> +{
[ ... ]
> + for (;;) {
> + spin_lock(&pending->lock);
> + unpin = list_first_entry_or_null(&pending->fbs, typeof(*unpin),
> + node);
> + if (unpin)
> + list_del_init(&unpin->node);
> + spin_unlock(&pending->lock);
> +
> + if (!unpin)
> + break;
> +
> + drm_vblank_work_cancel_sync(&unpin->base);
> + msm_kms_fb_unpin_release(unpin);
[Severity: High]
Is it possible for a concurrent msm_crtc_queue_fb_unpin() call to still be
holding the unpin pointer when this releases it?
If an atomic commit calls msm_crtc_queue_fb_unpin(), and a concurrent CRTC
disable commit concurrently calls msm_crtc_vblank_off() here, could the newly
added unpin object be popped and freed before it has been scheduled?
> + }
> +
> +out:
> + drm_crtc_vblank_off(crtc);
> +}
> +
> +bool msm_crtc_queue_fb_unpin(struct drm_crtc *crtc, struct drm_framebuffer *fb)
> +{
[ ... ]
> + drm_vblank_work_init(&unpin->base, crtc, msm_kms_fb_unpin_work);
> +
> + spin_lock(&pending->lock);
> + list_add_tail(&unpin->node, &pending->fbs);
> + spin_unlock(&pending->lock);
> +
> + if (drm_vblank_work_schedule(&unpin->base,
> + drm_crtc_vblank_count(crtc) + 1, true) != 1) {
[Severity: High]
Can this sequence trigger a use-after-free or double-free?
Since the unpin object is published to pending->fbs before calling
drm_vblank_work_schedule(), and the lock is dropped, a concurrent
msm_crtc_vblank_off() could potentially grab the object from the list and
free it.
If that happens, does this code then attempt to access the freed unpin->base,
and potentially double-free it with the kfree(unpin) below if scheduling
fails?
> + spin_lock(&pending->lock);
> + list_del_init(&unpin->node);
> + spin_unlock(&pending->lock);
> +
> + drm_framebuffer_put(fb);
> + kfree(unpin);
> +
> + return false;
> + }
> +
> + return true;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-fd-kms-fix-smmu-v3-0-a7ddc6fe2032@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 6/8] drm/msm/dpu: clear the DSC blocks left by a previous reservation
2026-09-12 12:48 ` [PATCH v3 6/8] drm/msm/dpu: clear the DSC blocks left by a previous reservation Dmitry Baryshkov
@ 2026-09-12 12:59 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-12 12:59 UTC (permalink / raw)
To: Dmitry Baryshkov; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Uninitialized stack memory dereference due to stale `hw_cwb` pointers.
- [High] Stale `hw_cdm` pointer leads to hardware state corruption.
--
commit 21c6c86adbc8ec970742702720e70e53d2f08f5c
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
drm/msm/dpu: clear the DSC blocks left by a previous reservation
This commit fixes an issue in dpu_encoder_virt_atomic_mode_set() where stale
DSC blocks from previous reservations were not cleared by writing NULL to the
remaining elements in the hw_dsc array.
> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> index 1f20695f81e35..50c49259a23e9 100644
> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c
> @@ -1222,9 +1222,10 @@ static void dpu_encoder_virt_atomic_mode_set(struct drm_encoder *drm_enc,
> num_dsc = dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state,
> drm_enc->crtc, DPU_HW_BLK_DSC,
> hw_dsc, ARRAY_SIZE(hw_dsc));
> - for (i = 0; i < num_dsc; i++) {
> - dpu_enc->hw_dsc[i] = to_dpu_hw_dsc(hw_dsc[i]);
> - dsc_mask |= BIT(dpu_enc->hw_dsc[i]->idx - DSC_0);
> + for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) {
> + dpu_enc->hw_dsc[i] = i < num_dsc ? to_dpu_hw_dsc(hw_dsc[i]) : NULL;
> + if (dpu_enc->hw_dsc[i])
> + dsc_mask |= BIT(dpu_enc->hw_dsc[i]->idx - DSC_0);
> }
>
> dpu_enc->dsc_mask = dsc_mask;
[Severity: High]
This is a pre-existing issue, but does the hw_cwb array suffer from the same
problem just above this block?
In dpu_encoder_virt_atomic_mode_set(), hw_cwb is populated up to num_cwb:
for (i = 0; i < num_cwb; i++) {
dpu_enc->hw_cwb[i] = to_dpu_hw_cwb(hw_cwb[i]);
...
}
Elements beyond num_cwb are not cleared to NULL. Later, in
dpu_encoder_helper_phys_setup_cwb(), it iterates over the entire
MAX_CWB_PER_ENC array:
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;
Could a stale hw_cwb pointer bypass the !hw_cwb check and access
rt_pp_list[i]? If i >= num_pp, it seems this would lead to dereferencing
uninitialized stack memory.
[Severity: High]
This isn't a bug introduced by this patch, but does hw_cdm have a similar
lifecycle issue in the same function?
Just below the hw_dsc clearing, dpu_encoder_virt_atomic_mode_set() handles
hw_cdm:
if ((dpu_enc->disp_info.intf_type == INTF_WB && conn_state->writeback_job) ||
dpu_enc->disp_info.intf_type == INTF_DP) {
struct dpu_hw_blk *hw_cdm = NULL;
dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state,
drm_enc->crtc, DPU_HW_BLK_CDM,
&hw_cdm, 1);
dpu_enc->cur_master->hw_cdm = hw_cdm ? to_dpu_hw_cdm(hw_cdm) : NULL;
}
If a modeset drops the writeback_job, this block is skipped entirely, leaving
the old pointer in dpu_enc->cur_master->hw_cdm.
Later, dpu_encoder_helper_phys_cleanup() unconditionally uses this pointer:
if (phys_enc->hw_cdm) {
if (phys_enc->hw_cdm->ops.bind_pingpong_blk && phys_enc->hw_pp)
phys_enc->hw_cdm->ops.bind_pingpong_blk(phys_enc->hw_cdm,
PINGPONG_NONE);
...
}
Can this stale pointer cause the cleanup path to alter a hardware block that
might now be owned by a different CRTC?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-fd-kms-fix-smmu-v3-0-a7ddc6fe2032@oss.qualcomm.com?part=6
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 5/8] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
2026-09-12 12:48 ` [PATCH v3 5/8] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
@ 2026-09-12 13:02 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-12 13:02 UTC (permalink / raw)
To: Dmitry Baryshkov; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Stale pointers in dpu_enc->hw_cwb array causing uninitialized stack memory dereference
- [High] Stale pointer in dpu_enc->cur_master->hw_cdm causing hardware state corruption
--
commit 50e668cb47f95334a8bcced939d0916fe0531def
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned
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.
> 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 problem exist
for hw_cwb in dpu_encoder_virt_atomic_mode_set() when the number of CWB
blocks decreases during a modeset?
If userspace submits a DRM commit that assigns fewer CWB blocks than the
previous state, trailing elements in dpu_enc->hw_cwb are not cleared:
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c:dpu_encoder_virt_atomic_mode_set() {
...
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);
}
...
}
Later, when dpu_encoder_helper_phys_setup_cwb() is called, it iterates over
MAX_CWB_PER_ENC and processes any non-NULL pointer. Could this cause the
driver to read from rt_pp_list[i] (an uninitialized stack array beyond
num_pp), cast the garbage value to a pointer, and dereference it via
hw_pp->idx, leading to a kernel crash?
[Severity: High]
This isn't a bug introduced by this patch, but there appears to be another
stale pointer issue in dpu_encoder_virt_atomic_mode_set() with hw_cdm:
drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c:dpu_encoder_virt_atomic_mode_set() {
...
if ((dpu_enc->disp_info.intf_type == INTF_WB && conn_state->writeback_job) ||
dpu_enc->disp_info.intf_type == INTF_DP) {
struct dpu_hw_blk *hw_cdm = NULL;
dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state,
drm_enc->crtc, DPU_HW_BLK_CDM,
&hw_cdm, 1);
dpu_enc->cur_master->hw_cdm = hw_cdm ? to_dpu_hw_cdm(hw_cdm) : NULL;
}
...
}
If a modeset occurs where the encoder had a CDM block (e.g., from an active
writeback job), but the new state lacks a writeback job, the if-condition
evaluates to false and the pointer remains uncleared.
During dpu_encoder_helper_phys_cleanup(), the driver attempts to unbind the
stale CDM block by calling bind_pingpong_blk(..., PINGPONG_NONE) and
configures a flush. Can this corrupt the hardware state of a block that no
longer belongs to this reservation and may now be assigned to another display?
> }
>
> cstate->num_mixers = num_lm;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-fd-kms-fix-smmu-v3-0-a7ddc6fe2032@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 3/8] drm/msm: unwind msm_drm_kms_init() on failure
2026-09-12 12:48 ` [PATCH v3 3/8] drm/msm: unwind msm_drm_kms_init() on failure Dmitry Baryshkov
@ 2026-09-12 13:33 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-12 13:33 UTC (permalink / raw)
To: Dmitry Baryshkov; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] msm_disp_snapshot_destroy() unconditionally passes an ERR_PTR to kthread_destroy_worker() if kthread creation failed.
- [High] destroy_workqueue() is called on a potentially NULL kms->wq when msm_kms_init() fails.
- [High] Double destruction of DRM private object state if hw_init fails.
--
commit 972acdabb4a5813d8316cd0796e9b9df36ce8dff
Author: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Subject: drm/msm: unwind msm_drm_kms_init() on failure
This commit updates the error unwinding in msm_drm_kms_init() and
msm_drm_init() to avoid calling the full teardown process when early
initialization steps fail. It undoes only the steps which have successfully
completed.
> diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
> --- a/drivers/gpu/drm/msm/msm_kms.c
> +++ b/drivers/gpu/drm/msm/msm_kms.c
[ ... ]
> @@ -328,14 +334,20 @@ int msm_drm_kms_init(struct device *dev, const struct drm_driver *drv)
> pm_runtime_put_sync(dev);
> if (ret < 0) {
> DRM_DEV_ERROR(dev, "failed to install IRQ handler\n");
> - goto err_msm_uninit;
> + goto err_destroy_event_threads;
> }
>
> drm_mode_config_reset(ddev);
>
> return 0;
>
> -err_msm_uninit:
> +err_destroy_event_threads:
> + msm_drm_kms_destroy_event_threads(kms);
> +err_destroy_kms:
> + msm_disp_snapshot_destroy(ddev);
[Severity: High]
This is a pre-existing issue, but does this error path lead to a kernel
panic?
If kthread_run_worker fails in msm_disp_snapshot_init, it stores an ERR_PTR
in kms->dump_worker but returns success. When we hit this error path and call
msm_disp_snapshot_destroy, it evaluates if (kms->dump_worker) which is true
for an ERR_PTR, and passes the error pointer directly to
kthread_destroy_worker, leading to an invalid memory dereference.
> + if (kms->funcs)
> + kms->funcs->destroy(kms);
[Severity: High]
This is also a pre-existing issue, but could this cause a NULL pointer
dereference if the workqueue failed to allocate?
If alloc_ordered_workqueue fails earlier in msm_kms_init, kms->wq is NULL
but kms->funcs is already assigned. This error propagates up, jumping to
err_destroy_kms, and we call kms->funcs->destroy here. That delegates to
msm_kms_destroy, which unconditionally calls destroy_workqueue(kms->wq)
without checking if the workqueue was actually allocated.
[Severity: High]
Additionally, this is a pre-existing issue, but can this lead to a double
free and list corruption in the DPU driver?
If dpu_kms_hw_init fails, it internally cleans up by calling
_dpu_kms_hw_destroy. This invokes drm_atomic_private_obj_fini to remove
the private object from its list via list_del. The error propagates to
msm_drm_kms_init, we jump to err_destroy_kms, and execute this destroy
callback. This resolves to dpu_kms_destroy, which calls
_dpu_kms_hw_destroy a second time and executes list_del on a poisoned
list head.
> +
> return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260912-fd-kms-fix-smmu-v3-0-a7ddc6fe2032@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-09-12 13:33 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-12 12:48 [PATCH v3 0/8] drm/msm: fix SMMU fault dumps Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 1/8] drm/msm: serialise framebuffer pin state Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 2/8] drm/msm: fix framebuffer pin refcount leak on prepare failure Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 3/8] drm/msm: unwind msm_drm_kms_init() on failure Dmitry Baryshkov
2026-09-12 13:33 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 4/8] drm/msm: release scanout framebuffers only after a vblank Dmitry Baryshkov
2026-09-12 12:59 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 5/8] drm/msm/dpu: clear the DSPP pointer when no DSPP is assigned Dmitry Baryshkov
2026-09-12 13:02 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 6/8] drm/msm/dpu: clear the DSC blocks left by a previous reservation Dmitry Baryshkov
2026-09-12 12:59 ` sashiko-bot
2026-09-12 12:48 ` [PATCH v3 7/8] drm/msm/dpu: only reassign resources when the encoder is reprogrammed Dmitry Baryshkov
2026-09-12 12:48 ` [PATCH v3 8/8] drm/ci: mark pixel-format tests as passing on SC7180 Dmitry Baryshkov
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.