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 <[email protected]>
---
 drivers/gpu/drm/msm/msm_fb.c | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c
index a38e5b3a1d1e..792d47b4acc4 100644
--- a/drivers/gpu/drm/msm/msm_fb.c
+++ b/drivers/gpu/drm/msm/msm_fb.c
@@ -80,6 +80,18 @@ void msm_framebuffer_describe(struct drm_framebuffer *fb, 
struct seq_file *m)
 }
 #endif
 
+static void msm_framebuffer_unpin_planes(struct drm_framebuffer *fb, int n)
+{
+       struct msm_drm_private *priv = fb->dev->dev_private;
+       struct drm_gpuvm *vm = priv->kms->vm;
+       int i;
+
+       for (i = 0; i < n; i++) {
+               msm_gem_unpin_iova(fb->obj[i], vm);
+               msm_gem_vma_put(fb->obj[i]);
+       }
+}
+
 /* prepare/pin all the fb's bo's for scanout.
  */
 int msm_framebuffer_prepare(struct drm_framebuffer *fb, bool needs_dirtyfb)
@@ -102,8 +114,17 @@ 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]);
+                       msm_framebuffer_unpin_planes(fb, i);
+                       memset(msm_fb->iova, 0, sizeof(msm_fb->iova));
+                       msm_fb->prepare_count--;
+
+                       if (needs_dirtyfb)
+                               refcount_dec(&msm_fb->dirtyfb);
+
                        return ret;
+               }
        }
 
        return 0;
@@ -111,10 +132,7 @@ int msm_framebuffer_prepare(struct drm_framebuffer *fb, 
bool needs_dirtyfb)
 
 void msm_framebuffer_cleanup(struct drm_framebuffer *fb, bool needed_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 i, n = fb->format->num_planes;
 
        if (needed_dirtyfb)
                refcount_dec(&msm_fb->dirtyfb);
@@ -125,11 +143,7 @@ void msm_framebuffer_cleanup(struct drm_framebuffer *fb, 
bool needed_dirtyfb)
                return;
 
        memset(msm_fb->iova, 0, sizeof(msm_fb->iova));
-
-       for (i = 0; i < n; i++) {
-               msm_gem_unpin_iova(fb->obj[i], vm);
-               msm_gem_vma_put(fb->obj[i]);
-       }
+       msm_framebuffer_unpin_planes(fb, fb->format->num_planes);
 }
 
 uint32_t msm_framebuffer_iova(struct drm_framebuffer *fb, int plane)

-- 
2.47.3

Reply via email to