drm/msm: async commit support
authorRob Clark <robdclark@chromium.org>
Thu, 29 Aug 2019 16:45:16 +0000 (09:45 -0700)
committerRob Clark <robdclark@chromium.org>
Tue, 3 Sep 2019 23:17:01 +0000 (16:17 -0700)
Now that flush/wait/complete is decoupled from the "synchronous" part of
atomic commit_tail(), add support to defer flush to a timer that expires
shortly before vblank for async commits.  In this way, multiple atomic
commits (for example, cursor updates) can be coalesced into a single
flush at the end of the frame.

v2: don't hold lock over ->wait_flush(), to avoid locking interaction
    that was causing fps drop when combining page flips or non-async
    atomic commits and lots of legacy cursor updates

Signed-off-by: Rob Clark <robdclark@chromium.org>
Reviewed-by: Sean Paul <sean@poorly.run>
drivers/gpu/drm/msm/msm_atomic.c
drivers/gpu/drm/msm/msm_drv.c
drivers/gpu/drm/msm/msm_drv.h
drivers/gpu/drm/msm/msm_kms.h

index 517d3a74777805be4700b91bbdf48688babb5359..55c8d72f84011681643fafa3df448f2a6c4dc622 100644 (file)
@@ -26,6 +26,95 @@ int msm_atomic_prepare_fb(struct drm_plane *plane,
        return msm_framebuffer_prepare(new_state->fb, kms->aspace);
 }
 
+static void msm_atomic_async_commit(struct msm_kms *kms, int crtc_idx)
+{
+       unsigned crtc_mask = BIT(crtc_idx);
+
+       mutex_lock(&kms->commit_lock);
+
+       if (!(kms->pending_crtc_mask & crtc_mask)) {
+               mutex_unlock(&kms->commit_lock);
+               return;
+       }
+
+       kms->pending_crtc_mask &= ~crtc_mask;
+
+       kms->funcs->enable_commit(kms);
+
+       /*
+        * Flush hardware updates:
+        */
+       DRM_DEBUG_ATOMIC("triggering async commit\n");
+       kms->funcs->flush_commit(kms, crtc_mask);
+       mutex_unlock(&kms->commit_lock);
+
+       /*
+        * Wait for flush to complete:
+        */
+       kms->funcs->wait_flush(kms, crtc_mask);
+
+       mutex_lock(&kms->commit_lock);
+       kms->funcs->complete_commit(kms, crtc_mask);
+       mutex_unlock(&kms->commit_lock);
+       kms->funcs->disable_commit(kms);
+}
+
+static enum hrtimer_restart msm_atomic_pending_timer(struct hrtimer *t)
+{
+       struct msm_pending_timer *timer = container_of(t,
+                       struct msm_pending_timer, timer);
+       struct msm_drm_private *priv = timer->kms->dev->dev_private;
+
+       queue_work(priv->wq, &timer->work);
+
+       return HRTIMER_NORESTART;
+}
+
+static void msm_atomic_pending_work(struct work_struct *work)
+{
+       struct msm_pending_timer *timer = container_of(work,
+                       struct msm_pending_timer, work);
+
+       msm_atomic_async_commit(timer->kms, timer->crtc_idx);
+}
+
+void msm_atomic_init_pending_timer(struct msm_pending_timer *timer,
+               struct msm_kms *kms, int crtc_idx)
+{
+       timer->kms = kms;
+       timer->crtc_idx = crtc_idx;
+       hrtimer_init(&timer->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+       timer->timer.function = msm_atomic_pending_timer;
+       INIT_WORK(&timer->work, msm_atomic_pending_work);
+}
+
+static bool can_do_async(struct drm_atomic_state *state,
+               struct drm_crtc **async_crtc)
+{
+       struct drm_connector_state *connector_state;
+       struct drm_connector *connector;
+       struct drm_crtc_state *crtc_state;
+       struct drm_crtc *crtc;
+       int i, num_crtcs = 0;
+
+       if (!(state->legacy_cursor_update || state->async_update))
+               return false;
+
+       /* any connector change, means slow path: */
+       for_each_new_connector_in_state(state, connector, connector_state, i)
+               return false;
+
+       for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+               if (drm_atomic_crtc_needs_modeset(crtc_state))
+                       return false;
+               if (++num_crtcs > 1)
+                       return false;
+               *async_crtc = crtc;
+       }
+
+       return true;
+}
+
 /* Get bitmask of crtcs that will need to be flushed.  The bitmask
  * can be used with for_each_crtc_mask() iterator, to iterate
  * effected crtcs without needing to preserve the atomic state.
@@ -47,9 +136,25 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state)
        struct drm_device *dev = state->dev;
        struct msm_drm_private *priv = dev->dev_private;
        struct msm_kms *kms = priv->kms;
+       struct drm_crtc *async_crtc = NULL;
        unsigned crtc_mask = get_crtc_mask(state);
+       bool async = kms->funcs->vsync_time &&
+                       can_do_async(state, &async_crtc);
 
        kms->funcs->enable_commit(kms);
+
+       /*
+        * Ensure any previous (potentially async) commit has
+        * completed:
+        */
+       kms->funcs->wait_flush(kms, crtc_mask);
+
+       mutex_lock(&kms->commit_lock);
+
+       /*
+        * Now that there is no in-progress flush, prepare the
+        * current update:
+        */
        kms->funcs->prepare_commit(kms, state);
 
        /*
@@ -59,6 +164,49 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state)
        drm_atomic_helper_commit_planes(dev, state, 0);
        drm_atomic_helper_commit_modeset_enables(dev, state);
 
+       if (async) {
+               struct msm_pending_timer *timer =
+                       &kms->pending_timers[drm_crtc_index(async_crtc)];
+
+               /* async updates are limited to single-crtc updates: */
+               WARN_ON(crtc_mask != drm_crtc_mask(async_crtc));
+
+               /*
+                * Start timer if we don't already have an update pending
+                * on this crtc:
+                */
+               if (!(kms->pending_crtc_mask & crtc_mask)) {
+                       ktime_t vsync_time, wakeup_time;
+
+                       kms->pending_crtc_mask |= crtc_mask;
+
+                       vsync_time = kms->funcs->vsync_time(kms, async_crtc);
+                       wakeup_time = ktime_sub(vsync_time, ms_to_ktime(1));
+
+                       hrtimer_start(&timer->timer, wakeup_time,
+                                       HRTIMER_MODE_ABS);
+               }
+
+               kms->funcs->disable_commit(kms);
+               mutex_unlock(&kms->commit_lock);
+
+               /*
+                * At this point, from drm core's perspective, we
+                * are done with the atomic update, so we can just
+                * go ahead and signal that it is done:
+                */
+               drm_atomic_helper_commit_hw_done(state);
+               drm_atomic_helper_cleanup_planes(dev, state);
+
+               return;
+       }
+
+       /*
+        * If there is any async flush pending on updated crtcs, fold
+        * them into the current flush.
+        */
+       kms->pending_crtc_mask &= ~crtc_mask;
+
        /*
         * Flush hardware updates:
         */
@@ -67,12 +215,18 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state)
                kms->funcs->commit(kms, state);
        }
        kms->funcs->flush_commit(kms, crtc_mask);
+       mutex_unlock(&kms->commit_lock);
 
+       /*
+        * Wait for flush to complete:
+        */
        kms->funcs->wait_flush(kms, crtc_mask);
+
+       mutex_lock(&kms->commit_lock);
        kms->funcs->complete_commit(kms, crtc_mask);
+       mutex_unlock(&kms->commit_lock);
        kms->funcs->disable_commit(kms);
 
        drm_atomic_helper_commit_hw_done(state);
-
        drm_atomic_helper_cleanup_planes(dev, state);
 }
index 996a508f762c7755e3c91e63757fc41f729a8d40..c84f0a8b3f2cebf76f99a70c01e4377fde610d0e 100644 (file)
@@ -473,6 +473,7 @@ static int msm_drm_init(struct device *dev, struct drm_driver *drv)
        ddev->mode_config.normalize_zpos = true;
 
        if (kms) {
+               kms->dev = ddev;
                ret = kms->funcs->hw_init(kms);
                if (ret) {
                        DRM_DEV_ERROR(dev, "kms hw init failed: %d\n", ret);
index 1fa0974eb69fd86e0224523504cb15892eed7b93..71547e756e2914a0152853ac967e7dfc357786db 100644 (file)
@@ -221,8 +221,12 @@ struct msm_format {
        uint32_t pixel_format;
 };
 
+struct msm_pending_timer;
+
 int msm_atomic_prepare_fb(struct drm_plane *plane,
                          struct drm_plane_state *new_state);
+void msm_atomic_init_pending_timer(struct msm_pending_timer *timer,
+               struct msm_kms *kms, int crtc_idx);
 void msm_atomic_commit_tail(struct drm_atomic_state *state);
 struct drm_atomic_state *msm_atomic_state_alloc(struct drm_device *dev);
 void msm_atomic_state_clear(struct drm_atomic_state *state);
index 85264a2f1f6dc15b0878339446d226648ca48c34..ed8df60a32ad7c9891140e55909b5c99e8cebd6e 100644 (file)
@@ -33,6 +33,20 @@ struct msm_kms_funcs {
 
        /*
         * Atomic commit handling:
+        *
+        * Note that in the case of async commits, the funcs which take
+        * a crtc_mask (ie. ->flush_commit(), and ->complete_commit())
+        * might not be evenly balanced with ->prepare_commit(), however
+        * each crtc that effected by a ->prepare_commit() (potentially
+        * multiple times) will eventually (at end of vsync period) be
+        * flushed and completed.
+        *
+        * This has some implications about tracking of cleanup state,
+        * for example SMP blocks to release after commit completes.  Ie.
+        * cleanup state should be also duplicated in the various
+        * duplicate_state() methods, as the current cleanup state at
+        * ->complete_commit() time may have accumulated cleanup work
+        * from multiple commits.
         */
 
        /**
@@ -45,6 +59,14 @@ struct msm_kms_funcs {
        void (*enable_commit)(struct msm_kms *kms);
        void (*disable_commit)(struct msm_kms *kms);
 
+       /**
+        * If the kms backend supports async commit, it should implement
+        * this method to return the time of the next vsync.  This is
+        * used to determine a time slightly before vsync, for the async
+        * commit timer to run and complete an async commit.
+        */
+       ktime_t (*vsync_time)(struct msm_kms *kms, struct drm_crtc *crtc);
+
        /**
         * Prepare for atomic commit.  This is called after any previous
         * (async or otherwise) commit has completed.
@@ -109,20 +131,48 @@ struct msm_kms_funcs {
 #endif
 };
 
+struct msm_kms;
+
+/*
+ * A per-crtc timer for pending async atomic flushes.  Scheduled to expire
+ * shortly before vblank to flush pending async updates.
+ */
+struct msm_pending_timer {
+       struct hrtimer timer;
+       struct work_struct work;
+       struct msm_kms *kms;
+       unsigned crtc_idx;
+};
+
 struct msm_kms {
        const struct msm_kms_funcs *funcs;
+       struct drm_device *dev;
 
        /* irq number to be passed on to drm_irq_install */
        int irq;
 
        /* mapper-id used to request GEM buffer mapped for scanout: */
        struct msm_gem_address_space *aspace;
+
+       /*
+        * For async commit, where ->flush_commit() and later happens
+        * from the crtc's pending_timer close to end of the frame:
+        */
+       struct mutex commit_lock;
+       unsigned pending_crtc_mask;
+       struct msm_pending_timer pending_timers[MAX_CRTCS];
 };
 
 static inline void msm_kms_init(struct msm_kms *kms,
                const struct msm_kms_funcs *funcs)
 {
+       unsigned i;
+
+       mutex_init(&kms->commit_lock);
        kms->funcs = funcs;
+
+       for (i = 0; i < ARRAY_SIZE(kms->pending_timers); i++)
+               msm_atomic_init_pending_timer(&kms->pending_timers[i], kms, i);
 }
 
 struct msm_kms *mdp4_kms_init(struct drm_device *dev);