[RFC 1/2] drm/vc4: Handle async page flips in the atomic_commit() path

Boris Brezillon boris.brezillon at bootlin.com
Fri Mar 30 15:20:13 UTC 2018


On Fri, 30 Mar 2018 17:09:03 +0200
Boris Brezillon <boris.brezillon at bootlin.com> wrote:

> Rework the atomic commit logic to handle async page flip requests in
> the atomic update path.
> 
> Async page flips are a bit more complicated than async cursor updates
> (already handled in the vc4_atomic_commit() function) in that you need
> to wait for fences on the new framebuffers to be signaled before doing
> the flip. In order to ensure that, we schedule a commit_work work to do
> the async update (note that the existing implementation already uses a
> work to wait for fences to be signaled, so, the latency shouldn't
> change that much).
> 
> Of course, we have to modify a bit the atomic_complete_commit()
> implementation to avoid waiting for things we don't care about when
> doing an async page flip:
> 
> * drm_atomic_helper_wait_for_dependencies() waits for flip_done which
>   we don't care about when doing async page flips. Instead we replace
>   that call by a loop that waits for hw_done only
> * drm_atomic_helper_commit_modeset_disables() and
>   drm_atomic_helper_commit_modeset_enables() are not needed because
>   nothing except the planes' FBs are updated in async page flips
> * drm_atomic_helper_commit_planes() is replaced by
>   vc4_plane_async_set_fb() which is doing only the FB update and is
>   thus much simpler
> * drm_atomic_helper_wait_for_vblanks() is not needed because we don't
>   wait for the next VBLANK period to apply the new state, and flip
>   events are signaled manually just after the HW has been updated
> 
> Thanks to this rework, we can get rid of the custom vc4_page_flip()
> implementation and its dependencies and use
> drm_atomic_helper_page_flip() instead.

Another good reason for moving async page flip to the atomic commit
path is that is fixes a bug we had:
drm_atomic_helper_prepare/cleanup_planes() were not called in the async
page flip path, which can lead to unbalanced vc4_inc/dec_usecnt()
in some situations (when the plane is updated once with an async page
flip and then with a regular update) or trigger a use after free if the
BO passed to the plane is marked purgeable and the kernel decides to
purge its cache.

> 
> Signed-off-by: Boris Brezillon <boris.brezillon at bootlin.com>
> ---
>  drivers/gpu/drm/vc4/vc4_crtc.c | 103 +----------------------------------------
>  drivers/gpu/drm/vc4/vc4_kms.c  | 101 +++++++++++++++++++++++++++++++++-------
>  2 files changed, 86 insertions(+), 118 deletions(-)
> 
> diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c
> index bf4667481935..3843c39dce61 100644
> --- a/drivers/gpu/drm/vc4/vc4_crtc.c
> +++ b/drivers/gpu/drm/vc4/vc4_crtc.c
> @@ -757,107 +757,6 @@ static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
>  	return ret;
>  }
>  
> -struct vc4_async_flip_state {
> -	struct drm_crtc *crtc;
> -	struct drm_framebuffer *fb;
> -	struct drm_pending_vblank_event *event;
> -
> -	struct vc4_seqno_cb cb;
> -};
> -
> -/* Called when the V3D execution for the BO being flipped to is done, so that
> - * we can actually update the plane's address to point to it.
> - */
> -static void
> -vc4_async_page_flip_complete(struct vc4_seqno_cb *cb)
> -{
> -	struct vc4_async_flip_state *flip_state =
> -		container_of(cb, struct vc4_async_flip_state, cb);
> -	struct drm_crtc *crtc = flip_state->crtc;
> -	struct drm_device *dev = crtc->dev;
> -	struct vc4_dev *vc4 = to_vc4_dev(dev);
> -	struct drm_plane *plane = crtc->primary;
> -
> -	vc4_plane_async_set_fb(plane, flip_state->fb);
> -	if (flip_state->event) {
> -		unsigned long flags;
> -
> -		spin_lock_irqsave(&dev->event_lock, flags);
> -		drm_crtc_send_vblank_event(crtc, flip_state->event);
> -		spin_unlock_irqrestore(&dev->event_lock, flags);
> -	}
> -
> -	drm_crtc_vblank_put(crtc);
> -	drm_framebuffer_put(flip_state->fb);
> -	kfree(flip_state);
> -
> -	up(&vc4->async_modeset);
> -}
> -
> -/* Implements async (non-vblank-synced) page flips.
> - *
> - * The page flip ioctl needs to return immediately, so we grab the
> - * modeset semaphore on the pipe, and queue the address update for
> - * when V3D is done with the BO being flipped to.
> - */
> -static int vc4_async_page_flip(struct drm_crtc *crtc,
> -			       struct drm_framebuffer *fb,
> -			       struct drm_pending_vblank_event *event,
> -			       uint32_t flags)
> -{
> -	struct drm_device *dev = crtc->dev;
> -	struct vc4_dev *vc4 = to_vc4_dev(dev);
> -	struct drm_plane *plane = crtc->primary;
> -	int ret = 0;
> -	struct vc4_async_flip_state *flip_state;
> -	struct drm_gem_cma_object *cma_bo = drm_fb_cma_get_gem_obj(fb, 0);
> -	struct vc4_bo *bo = to_vc4_bo(&cma_bo->base);
> -
> -	flip_state = kzalloc(sizeof(*flip_state), GFP_KERNEL);
> -	if (!flip_state)
> -		return -ENOMEM;
> -
> -	drm_framebuffer_get(fb);
> -	flip_state->fb = fb;
> -	flip_state->crtc = crtc;
> -	flip_state->event = event;
> -
> -	/* Make sure all other async modesetes have landed. */
> -	ret = down_interruptible(&vc4->async_modeset);
> -	if (ret) {
> -		drm_framebuffer_put(fb);
> -		kfree(flip_state);
> -		return ret;
> -	}
> -
> -	WARN_ON(drm_crtc_vblank_get(crtc) != 0);
> -
> -	/* Immediately update the plane's legacy fb pointer, so that later
> -	 * modeset prep sees the state that will be present when the semaphore
> -	 * is released.
> -	 */
> -	drm_atomic_set_fb_for_plane(plane->state, fb);
> -	plane->fb = fb;
> -
> -	vc4_queue_seqno_cb(dev, &flip_state->cb, bo->seqno,
> -			   vc4_async_page_flip_complete);
> -
> -	/* Driver takes ownership of state on successful async commit. */
> -	return 0;
> -}
> -
> -static int vc4_page_flip(struct drm_crtc *crtc,
> -			 struct drm_framebuffer *fb,
> -			 struct drm_pending_vblank_event *event,
> -			 uint32_t flags,
> -			 struct drm_modeset_acquire_ctx *ctx)
> -{
> -	if (flags & DRM_MODE_PAGE_FLIP_ASYNC)
> -		return vc4_async_page_flip(crtc, fb, event, flags);
> -	else
> -		return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
> -}
> -
>  static struct drm_crtc_state *vc4_crtc_duplicate_state(struct drm_crtc *crtc)
>  {
>  	struct vc4_crtc_state *vc4_state;
> @@ -902,7 +801,7 @@ vc4_crtc_reset(struct drm_crtc *crtc)
>  static const struct drm_crtc_funcs vc4_crtc_funcs = {
>  	.set_config = drm_atomic_helper_set_config,
>  	.destroy = vc4_crtc_destroy,
> -	.page_flip = vc4_page_flip,
> +	.page_flip = drm_atomic_helper_page_flip,
>  	.set_property = NULL,
>  	.cursor_set = NULL, /* handled by drm_mode_cursor_universal */
>  	.cursor_move = NULL, /* handled by drm_mode_cursor_universal */
> diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
> index e791e498a3dd..faa2c26f1235 100644
> --- a/drivers/gpu/drm/vc4/vc4_kms.c
> +++ b/drivers/gpu/drm/vc4/vc4_kms.c
> @@ -25,33 +25,89 @@
>  #include "vc4_drv.h"
>  
>  static void
> -vc4_atomic_complete_commit(struct drm_atomic_state *state)
> +vc4_atomic_complete_commit(struct drm_atomic_state *state, bool async)
>  {
>  	struct drm_device *dev = state->dev;
>  	struct vc4_dev *vc4 = to_vc4_dev(dev);
>  
>  	drm_atomic_helper_wait_for_fences(dev, state, false);
>  
> -	drm_atomic_helper_wait_for_dependencies(state);
> +	if (async) {
> +		struct drm_plane_state *plane_state;
> +		struct drm_crtc_state *new_crtc_state;
> +		struct drm_plane *plane;
> +		struct drm_crtc *crtc;
> +		int i;
> +
> +		/* We need to wait for HW done before applying the new FBs
> +		 * otherwise our update might be overwritten by the previous
> +		 * commit.
> +		 */
> +		for_each_old_plane_in_state(state, plane, plane_state, i) {
> +			struct drm_crtc_commit *commit = plane_state->commit;
> +			int ret;
> +
> +			if (!commit)
> +				continue;
> +
> +			ret = wait_for_completion_timeout(&commit->hw_done,
> +							  10 * HZ);
> +			if (!ret)
> +				DRM_ERROR("[PLANE:%d:%s] hw_done timed out\n",
> +					  plane->base.id, plane->name);
> +		}
>  
> -	drm_atomic_helper_commit_modeset_disables(dev, state);
> +		/* FIXME:
> +		 * We could use drm_atomic_helper_async_commit() here, but
> +		 * VC4's ->atomic_async_update() implementation expects
> +		 * plane->state to point to the old_state and old/new states
> +		 * have already been swapped.
> +		 * Let's just call our custom function for now and see how we
> +		 * can make that more generic afterwards.
> +		 */
> +		for_each_new_plane_in_state(state, plane, plane_state, i)
> +			vc4_plane_async_set_fb(plane, plane_state->fb);
> +
> +		/* Now, send 'fake' VBLANK events to let the user now its
> +		 * pageflip is done. By fake I mean they are really VBLANK
> +		 * synchronized but it seems to be expected by the core.
> +		 */
> +		for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
> +			unsigned long flags;
> +
> +			if (!new_crtc_state->event)
> +				continue;
> +
> +			WARN_ON(drm_crtc_vblank_get(crtc));
> +			spin_lock_irqsave(&dev->event_lock, flags);
> +			drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
> +			spin_unlock_irqrestore(&dev->event_lock, flags);
> +			drm_crtc_vblank_put(crtc);
> +		}
> +	} else {
> +		drm_atomic_helper_wait_for_dependencies(state);
>  
> -	drm_atomic_helper_commit_planes(dev, state, 0);
> +		drm_atomic_helper_commit_modeset_disables(dev, state);
>  
> -	drm_atomic_helper_commit_modeset_enables(dev, state);
> +		drm_atomic_helper_commit_planes(dev, state, 0);
>  
> -	/* Make sure that drm_atomic_helper_wait_for_vblanks()
> -	 * actually waits for vblank.  If we're doing a full atomic
> -	 * modeset (as opposed to a vc4_update_plane() short circuit),
> -	 * then we need to wait for scanout to be done with our
> -	 * display lists before we free it and potentially reallocate
> -	 * and overwrite the dlist memory with a new modeset.
> -	 */
> -	state->legacy_cursor_update = false;
> +		drm_atomic_helper_commit_modeset_enables(dev, state);
> +	}
>  
>  	drm_atomic_helper_commit_hw_done(state);
>  
> -	drm_atomic_helper_wait_for_vblanks(dev, state);
> +	if (!async) {
> +		/* Make sure that drm_atomic_helper_wait_for_vblanks()
> +		 * actually waits for vblank.  If we're doing a full atomic
> +		 * modeset (as opposed to a vc4_update_plane() short circuit),
> +		 * then we need to wait for scanout to be done with our
> +		 * display lists before we free it and potentially reallocate
> +		 * and overwrite the dlist memory with a new modeset.
> +		 */
> +		state->legacy_cursor_update = false;
> +
> +		drm_atomic_helper_wait_for_vblanks(dev, state);
> +	}
>  
>  	drm_atomic_helper_cleanup_planes(dev, state);
>  
> @@ -67,7 +123,20 @@ static void commit_work(struct work_struct *work)
>  	struct drm_atomic_state *state = container_of(work,
>  						      struct drm_atomic_state,
>  						      commit_work);
> -	vc4_atomic_complete_commit(state);
> +	struct drm_crtc_state *new_crtc_state;
> +	bool async_commit = true;
> +	struct drm_crtc *crtc;
> +	int i;
> +
> +	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
> +		if (new_crtc_state->pageflip_flags & DRM_MODE_PAGE_FLIP_ASYNC)
> +			continue;
> +
> +		async_commit = false;
> +		break;
> +	}
> +
> +	vc4_atomic_complete_commit(state, async_commit);
>  }
>  
>  /**
> @@ -163,7 +232,7 @@ static int vc4_atomic_commit(struct drm_device *dev,
>  	if (nonblock)
>  		queue_work(system_unbound_wq, &state->commit_work);
>  	else
> -		vc4_atomic_complete_commit(state);
> +		vc4_atomic_complete_commit(state, false);
>  
>  	return 0;
>  }



-- 
Boris Brezillon, Bootlin (formerly Free Electrons)
Embedded Linux and Kernel engineering
https://bootlin.com


More information about the dri-devel mailing list