diff mbox

[RFC,1/7] drm/atomic: initial support for asynchronous plane update

Message ID 20170410002448.12460-2-gustavo@padovan.org (mailing list archive)
State New, archived
Headers show

Commit Message

Gustavo Padovan April 10, 2017, 12:24 a.m. UTC
From: Gustavo Padovan <gustavo.padovan@collabora.com>

In some cases, like cursor updates, it is interesting to update the
plane in an asynchronous fashion to avoid big delays.

The current queued update could be still waiting for a fence to signal and
thus block and subsequent update until its scan out. In cases like this if
we update the cursor synchronously it will cause significant delays on
some platforms that would be noticed by the final user.

This patch creates a fast path to jump ahead the current queued state and
do single planes updates without going through all atomic step in
drm_atomic_helper_commit().

For now only single plane updates are supported and only if there is no
update to that plane in the queued state.

We plan to support async PageFlips through this interface as well in the
near future.

Cc: Daniel Vetter <daniel.vetter@intel.com>
Cc: Rob Clark <robdclark@gmail.com>
Cc: Eric Anholt <eric@anholt.net>
Signed-off-by: Gustavo Padovan <gustavo.padovan@collabora.com>
---
 drivers/gpu/drm/drm_atomic.c             | 75 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_atomic_helper.c      | 41 +++++++++++++++++
 include/drm/drm_atomic.h                 |  4 ++
 include/drm/drm_atomic_helper.h          |  2 +
 include/drm/drm_modeset_helper_vtables.h | 47 ++++++++++++++++++++
 5 files changed, 169 insertions(+)

Comments

Emil Velikov April 10, 2017, 7:13 a.m. UTC | #1
Hi Gustavo,

Mostly some documentation suggestions below. Hope that I've not lost
the plot and they seem ok :-)

On 10 April 2017 at 01:24, Gustavo Padovan <gustavo@padovan.org> wrote:

> +static bool drm_atomic_async_check(struct drm_atomic_state *state)
> +{

/* FIXME: we support single plane updates for now */
> +       if (!plane || n_planes != 1)
> +               return false;
> +

> +       if (!funcs->atomic_async_update)
> +               return false;


> +void drm_atomic_helper_async_commit(struct drm_device *dev,
> +                                   struct drm_atomic_state *state)
> +{
> +       struct drm_plane *plane;
> +       struct drm_plane_state *plane_state;
> +       const struct drm_plane_helper_funcs *funcs;
> +       int i;
> +
> +       for_each_new_plane_in_state(state, plane, plane_state, i) {
> +               funcs = plane->helper_private;
> +
> +               plane->state->src_x = plane_state->src_x;
> +               plane->state->src_y = plane_state->src_y;
> +               plane->state->crtc_x = plane_state->crtc_x;
> +               plane->state->crtc_y = plane_state->crtc_y;
> +
> +               if (funcs && funcs->atomic_async_update)
> +                       funcs->atomic_async_update(plane, plane_state);

This looks a bit strange. We don't want to change any state if the
hook is missing right?
Actually the if will always be true, since we've already validated
that in drm_atomic_async_check().

Should we unconditionally call funcs->atomic_async_update()?


> @@ -172,6 +174,8 @@ struct drm_atomic_state {
>         struct drm_device *dev;
>         bool allow_modeset : 1;
>         bool legacy_cursor_update : 1;
> +       bool legacy_set_config : 1;
Seems unused in this patch. Introduce at a later stage?


> --- a/include/drm/drm_modeset_helper_vtables.h
> +++ b/include/drm/drm_modeset_helper_vtables.h
> @@ -1048,6 +1048,53 @@ struct drm_plane_helper_funcs {
>          */
>         void (*atomic_disable)(struct drm_plane *plane,
>                                struct drm_plane_state *old_state);
> +
> +       /**
> +        * @atomic_async_check:
> +        *
> +        * Drivers should use this function check if the plane state
s/use this function check/set this function pointer/

> +        * can be updated in a async fashion.
> +        *
> +        * This hook is called by drm_atomic_async_check() in the process
> +        * to figure out if an given update can be committed asynchronously,
s/in the process to figure out if an/to establish if a/ s/,/./

> +        * that is, if it can jump ahead the state currently queued for
> +        * update.
> +        *
> +        * Check drm_atomic_async_check() to see what is already there
> +        * to discover potential async updates.
Remove these two sentences? They don't seem to bring much.

> +        *
> +        * RETURNS:
> +        *
> +        * Return 0 on success and -EINVAL if the update can't be async.
s/if the update can't be async/on error/

> +        * Error return doesn't mean that the update can't be applied,
> +        * it just mean that it can't be an async one.
> +        *
Any error returned indicates that the update can not be applied in
asynchronous manner.
Side-note: suggest who/how to retry synchronously ?

> +        * FIXME:
> +        *  - It only works for single plane updates
> +        *  - It can't do async update if the plane is already being update
> +        *  by the queued state
> +        *  - Async Pageflips are not supported yet
> +        */
> +       int (*atomic_async_check)(struct drm_plane *plane,
> +                                 struct drm_plane_state *state);
> +
> +       /**
> +        * @atomic_async_update:
> +        *
> +        * Drivers should use this functions to perform asynchronous
As above - drivers do not use this function. They only provide the
function pointer, right?

> +        * updates of planes, that is jump ahead the currently queued
> +        * state for and update the plane.
> +        *
> +        * This hook is called by drm_atomic_helper_async_commit().
> +        *
> +        * Note that differently from the &drm_plane_helper_funcs.atomic_update
s/differently from the/unlike/

> +        * this hook takes the new &drm_plane_state as parameter. When doing
> +        * async_update drivers shouldn't replace the &drm_plane_state but
> +        * just, update the current one with the new plane configurations in
s/just,// or s/just,/simply/

Regards,
Emil
Gustavo Padovan April 10, 2017, 9:39 a.m. UTC | #2
Hi Emil,

Thank you for your review!

2017-04-10 Emil Velikov <emil.l.velikov@gmail.com>:

> Hi Gustavo,
> 
> Mostly some documentation suggestions below. Hope that I've not lost
> the plot and they seem ok :-)
> 
> On 10 April 2017 at 01:24, Gustavo Padovan <gustavo@padovan.org> wrote:
> 
> > +static bool drm_atomic_async_check(struct drm_atomic_state *state)
> > +{
> 
> /* FIXME: we support single plane updates for now */
> > +       if (!plane || n_planes != 1)
> > +               return false;
> > +
> 
> > +       if (!funcs->atomic_async_update)
> > +               return false;
> 
> 
> > +void drm_atomic_helper_async_commit(struct drm_device *dev,
> > +                                   struct drm_atomic_state *state)
> > +{
> > +       struct drm_plane *plane;
> > +       struct drm_plane_state *plane_state;
> > +       const struct drm_plane_helper_funcs *funcs;
> > +       int i;
> > +
> > +       for_each_new_plane_in_state(state, plane, plane_state, i) {
> > +               funcs = plane->helper_private;
> > +
> > +               plane->state->src_x = plane_state->src_x;
> > +               plane->state->src_y = plane_state->src_y;
> > +               plane->state->crtc_x = plane_state->crtc_x;
> > +               plane->state->crtc_y = plane_state->crtc_y;
> > +
> > +               if (funcs && funcs->atomic_async_update)
> > +                       funcs->atomic_async_update(plane, plane_state);
> 
> This looks a bit strange. We don't want to change any state if the
> hook is missing right?
> Actually the if will always be true, since we've already validated
> that in drm_atomic_async_check().
> 
> Should we unconditionally call funcs->atomic_async_update()?

That would be much better. I'll do that.

> 
> 
> > @@ -172,6 +174,8 @@ struct drm_atomic_state {
> >         struct drm_device *dev;
> >         bool allow_modeset : 1;
> >         bool legacy_cursor_update : 1;
> > +       bool legacy_set_config : 1;
> Seems unused in this patch. Introduce at a later stage?

Hmm, rebase garbage. :(  Sorry about that.

> 
> 
> > --- a/include/drm/drm_modeset_helper_vtables.h
> > +++ b/include/drm/drm_modeset_helper_vtables.h
> > @@ -1048,6 +1048,53 @@ struct drm_plane_helper_funcs {
> >          */
> >         void (*atomic_disable)(struct drm_plane *plane,
> >                                struct drm_plane_state *old_state);
> > +
> > +       /**
> > +        * @atomic_async_check:
> > +        *
> > +        * Drivers should use this function check if the plane state
> s/use this function check/set this function pointer/
> 
> > +        * can be updated in a async fashion.
> > +        *
> > +        * This hook is called by drm_atomic_async_check() in the process
> > +        * to figure out if an given update can be committed asynchronously,
> s/in the process to figure out if an/to establish if a/ s/,/./
> 
> > +        * that is, if it can jump ahead the state currently queued for
> > +        * update.
> > +        *
> > +        * Check drm_atomic_async_check() to see what is already there
> > +        * to discover potential async updates.
> Remove these two sentences? They don't seem to bring much.
> 
> > +        *
> > +        * RETURNS:
> > +        *
> > +        * Return 0 on success and -EINVAL if the update can't be async.
> s/if the update can't be async/on error/

I don't think that is an error, it just can't be async so the regular 
atomic sync commit will be performed. For async page flip this would be
a show stopper error, but we are not there yet.

> 
> > +        * Error return doesn't mean that the update can't be applied,
> > +        * it just mean that it can't be an async one.
> > +        *
> Any error returned indicates that the update can not be applied in
> asynchronous manner.
> Side-note: suggest who/how to retry synchronously ?

Retry synchronously is the default fallback. Actually it is the
opposite, async is a special shortcut of sync.

Gustavo
Eric Anholt April 10, 2017, 7:55 p.m. UTC | #3
Gustavo Padovan <gustavo@padovan.org> writes:

> From: Gustavo Padovan <gustavo.padovan@collabora.com>
>
> In some cases, like cursor updates, it is interesting to update the
> plane in an asynchronous fashion to avoid big delays.
>
> The current queued update could be still waiting for a fence to signal and
> thus block and subsequent update until its scan out. In cases like this if
> we update the cursor synchronously it will cause significant delays on
> some platforms that would be noticed by the final user.
>
> This patch creates a fast path to jump ahead the current queued state and
> do single planes updates without going through all atomic step in
> drm_atomic_helper_commit().
>
> For now only single plane updates are supported and only if there is no
> update to that plane in the queued state.
>
> We plan to support async PageFlips through this interface as well in the
> near future.

This looks really nice -- the vc4 code for cursors was really gross to
write, and I got it wrong a couple of times.  Couple of comments inline.

> ---
>  drivers/gpu/drm/drm_atomic.c             | 75 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/drm_atomic_helper.c      | 41 +++++++++++++++++
>  include/drm/drm_atomic.h                 |  4 ++
>  include/drm/drm_atomic_helper.h          |  2 +
>  include/drm/drm_modeset_helper_vtables.h | 47 ++++++++++++++++++++
>  5 files changed, 169 insertions(+)
>
> diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> index f32506a..cae0122 100644
> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c
> @@ -631,6 +631,79 @@ static int drm_atomic_crtc_check(struct drm_crtc *crtc,
>  	return 0;
>  }
>  
> +static bool drm_atomic_async_check(struct drm_atomic_state *state)
> +{
> +	struct drm_crtc *crtc;
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_crtc_commit *commit;
> +	struct drm_plane *__plane, *plane = NULL;
> +	struct drm_plane_state *__plane_state, *plane_state = NULL;
> +	const struct drm_plane_helper_funcs *funcs;
> +	int i, n_planes = 0;
> +
> +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> +		if (drm_atomic_crtc_needs_modeset(crtc_state))
> +			return false;
> +	}
> +
> +	for_each_new_plane_in_state(state, __plane, __plane_state, i) {
> +		n_planes++;
> +		plane = __plane;
> +		plane_state = __plane_state;
> +	}
> +
> +	if (!plane || n_planes != 1)
> +		return false;
> +
> +	if (!plane->state->crtc)
> +		return false;
> +
> +	if (plane_state->fence)
> +		return false;
> +
> +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> +		if (plane->crtc != crtc)
> +			continue;
> +
> +		spin_lock(&crtc->commit_lock);
> +		commit = list_first_entry_or_null(&crtc->commit_list,
> +						  struct drm_crtc_commit,
> +						  commit_entry);
> +		if (!commit) {
> +			spin_unlock(&crtc->commit_lock);
> +			continue;
> +		}
> +		spin_unlock(&crtc->commit_lock);
> +
> +		for_each_plane_in_state(crtc_state->state, __plane,
> +					__plane_state, i) {
> +			if (__plane == plane) {
> +				return false;
> +			}
> +		}
> +	}
> +
> +	/* Not configuring new scaling in the async path. */

s/Not/No/

> +	if (plane->state->crtc_w != plane_state->crtc_w ||
> +	    plane->state->crtc_h != plane_state->crtc_h ||
> +	    plane->state->src_w != plane_state->src_w ||
> +	    plane->state->src_h != plane_state->src_h) {
> +		return false;
> +	}
> +
> +	funcs = plane->helper_private;
> +
> +	if (!funcs->atomic_async_update)
> +		return false;
> +
> +	if (funcs->atomic_async_check) {
> +		if (funcs->atomic_async_check(plane, plane_state) < 0)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
>  static void drm_atomic_crtc_print_state(struct drm_printer *p,
>  		const struct drm_crtc_state *state)
>  {
> @@ -1591,6 +1664,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
>  		}
>  	}
>  
> +	state->async_update = drm_atomic_async_check(state);
> +
>  	return ret;
>  }

The promotion of any modeset that passes the async_check test to async
seems weird -- shouldn't we only be doing that if userspace requested
async?  Right now it seems like we're just getting lucky because only
cursor planes have it set, and the cursor update ioctls imply async.

>  EXPORT_SYMBOL(drm_atomic_check_only);
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 8be9719..a79e06c 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
> @@ -1235,6 +1235,36 @@ static void commit_work(struct work_struct *work)
>  }
>  
>  /**
> + * drm_atomic_helper_async_commit - commit state asynchronously
> + *
> + * This function commits a state asynchronously. It should be used
> + * on a state only when drm_atomic_async_check() succeds. Async
> + * commits are not supposed to swap the states like normal sync commits,
> + * but just do in-place change on the current state.
> + */
> +void drm_atomic_helper_async_commit(struct drm_device *dev,
> +				    struct drm_atomic_state *state)
> +{
> +	struct drm_plane *plane;
> +	struct drm_plane_state *plane_state;
> +	const struct drm_plane_helper_funcs *funcs;
> +	int i;
> +
> +	for_each_new_plane_in_state(state, plane, plane_state, i) {
> +		funcs = plane->helper_private;
> +
> +		plane->state->src_x = plane_state->src_x;
> +		plane->state->src_y = plane_state->src_y;
> +		plane->state->crtc_x = plane_state->crtc_x;
> +		plane->state->crtc_y = plane_state->crtc_y;
> +
> +		if (funcs && funcs->atomic_async_update)
> +			funcs->atomic_async_update(plane, plane_state);
> +	}
> +}

It seems strange to me that the async_update() implementation in the
driver needs to update the state->fb but not the x/y.  Could we move the
core's x/y update after the call into the driver, and then update
plane->state->fb in the core instead of the driver?

> +EXPORT_SYMBOL(drm_atomic_helper_async_commit);
> +
> +/**
>   * drm_atomic_helper_commit - commit validated state object
>   * @dev: DRM device
>   * @state: the driver state object
> @@ -1258,6 +1288,17 @@ int drm_atomic_helper_commit(struct drm_device *dev,
>  {
>  	int ret;
>  
> +	if (state->async_update) {
> +		ret = drm_atomic_helper_prepare_planes(dev, state);
> +		if (ret)
> +			return ret;
> +
> +		drm_atomic_helper_async_commit(dev, state);
> +		drm_atomic_helper_cleanup_planes(dev, state);
> +
> +		return 0;
> +	}
> +
>  	ret = drm_atomic_helper_setup_commit(state, nonblock);
>  	if (ret)
>  		return ret;
> diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> index 788daf7..c00c565 100644
> --- a/include/drm/drm_atomic.h
> +++ b/include/drm/drm_atomic.h

> diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> index c01c328..3efa4cc 100644
> --- a/include/drm/drm_modeset_helper_vtables.h
> +++ b/include/drm/drm_modeset_helper_vtables.h

> +	 *
> +	 * Check drm_atomic_async_check() to see what is already there
> +	 * to discover potential async updates.
> +	 *
> +	 * RETURNS:
> +	 *
> +	 * Return 0 on success and -EINVAL if the update can't be async.
> +	 * Error return doesn't mean that the update can't be applied,
> +	 * it just mean that it can't be an async one.
> +	 *
> +	 * FIXME:
> +	 *  - It only works for single plane updates
> +	 *  - It can't do async update if the plane is already being update
> +	 *  by the queued state

"already being updated"

> +	 *  - Async Pageflips are not supported yet
> +	 */
> +	int (*atomic_async_check)(struct drm_plane *plane,
> +				  struct drm_plane_state *state);
> +
> +	/**
> +	 * @atomic_async_update:
> +	 *
> +	 * Drivers should use this functions to perform asynchronous

"this function"

Also, maybe clarify that "async" means "not vblank synchronized"?

> +	 * updates of planes, that is jump ahead the currently queued
> +	 * state for and update the plane.
> +	 *
> +	 * This hook is called by drm_atomic_helper_async_commit().
> +	 *
> +	 * Note that differently from the &drm_plane_helper_funcs.atomic_update
> +	 * this hook takes the new &drm_plane_state as parameter. When doing
> +	 * async_update drivers shouldn't replace the &drm_plane_state but
> +	 * just, update the current one with the new plane configurations in
> +	 * the new plane_state.
> +	 */
> +	void (*atomic_async_update)(struct drm_plane *plane,
> +				    struct drm_plane_state *new_state);
>  };
>  
>  /**
> -- 
> 2.9.3
Daniel Vetter April 11, 2017, 8:23 p.m. UTC | #4
On Mon, Apr 10, 2017 at 12:55:33PM -0700, Eric Anholt wrote:
> Gustavo Padovan <gustavo@padovan.org> writes:
> 
> > From: Gustavo Padovan <gustavo.padovan@collabora.com>
> >
> > In some cases, like cursor updates, it is interesting to update the
> > plane in an asynchronous fashion to avoid big delays.
> >
> > The current queued update could be still waiting for a fence to signal and
> > thus block and subsequent update until its scan out. In cases like this if
> > we update the cursor synchronously it will cause significant delays on
> > some platforms that would be noticed by the final user.
> >
> > This patch creates a fast path to jump ahead the current queued state and
> > do single planes updates without going through all atomic step in
> > drm_atomic_helper_commit().
> >
> > For now only single plane updates are supported and only if there is no
> > update to that plane in the queued state.
> >
> > We plan to support async PageFlips through this interface as well in the
> > near future.
> 
> This looks really nice -- the vc4 code for cursors was really gross to
> write, and I got it wrong a couple of times.  Couple of comments inline.

Some more comments below.
-Daniel

> 
> > ---
> >  drivers/gpu/drm/drm_atomic.c             | 75 ++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/drm_atomic_helper.c      | 41 +++++++++++++++++
> >  include/drm/drm_atomic.h                 |  4 ++
> >  include/drm/drm_atomic_helper.h          |  2 +
> >  include/drm/drm_modeset_helper_vtables.h | 47 ++++++++++++++++++++
> >  5 files changed, 169 insertions(+)
> >
> > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > index f32506a..cae0122 100644
> > --- a/drivers/gpu/drm/drm_atomic.c
> > +++ b/drivers/gpu/drm/drm_atomic.c
> > @@ -631,6 +631,79 @@ static int drm_atomic_crtc_check(struct drm_crtc *crtc,
> >  	return 0;
> >  }
> >  
> > +static bool drm_atomic_async_check(struct drm_atomic_state *state)
> > +{
> > +	struct drm_crtc *crtc;
> > +	struct drm_crtc_state *crtc_state;
> > +	struct drm_crtc_commit *commit;
> > +	struct drm_plane *__plane, *plane = NULL;
> > +	struct drm_plane_state *__plane_state, *plane_state = NULL;
> > +	const struct drm_plane_helper_funcs *funcs;
> > +	int i, n_planes = 0;
> > +
> > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > +		if (drm_atomic_crtc_needs_modeset(crtc_state))
> > +			return false;
> > +	}
> > +
> > +	for_each_new_plane_in_state(state, __plane, __plane_state, i) {
> > +		n_planes++;
> > +		plane = __plane;
> > +		plane_state = __plane_state;
> > +	}
> > +
> > +	if (!plane || n_planes != 1)
> > +		return false;
> > +
> > +	if (!plane->state->crtc)
> > +		return false;
> > +
> > +	if (plane_state->fence)
> > +		return false;
> > +
> > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > +		if (plane->crtc != crtc)
> > +			continue;
> > +
> > +		spin_lock(&crtc->commit_lock);
> > +		commit = list_first_entry_or_null(&crtc->commit_list,
> > +						  struct drm_crtc_commit,
> > +						  commit_entry);
> > +		if (!commit) {
> > +			spin_unlock(&crtc->commit_lock);
> > +			continue;
> > +		}
> > +		spin_unlock(&crtc->commit_lock);
> > +
> > +		for_each_plane_in_state(crtc_state->state, __plane,
> > +					__plane_state, i) {
> > +			if (__plane == plane) {
> > +				return false;
> > +			}
> > +		}
> > +	}
> > +
> > +	/* Not configuring new scaling in the async path. */
> 
> s/Not/No/
> 
> > +	if (plane->state->crtc_w != plane_state->crtc_w ||
> > +	    plane->state->crtc_h != plane_state->crtc_h ||
> > +	    plane->state->src_w != plane_state->src_w ||
> > +	    plane->state->src_h != plane_state->src_h) {
> > +		return false;
> > +	}
> > +
> > +	funcs = plane->helper_private;
> > +
> > +	if (!funcs->atomic_async_update)
> > +		return false;

I kinda feel like we should check this first, to bail out for all the
drivers that don't implement this before any of the more expensive checks.
> > +
> > +	if (funcs->atomic_async_check) {

This is redundant.

> > +		if (funcs->atomic_async_check(plane, plane_state) < 0)
> > +			return false;

Of course the callback should be still called at the end, so it doesn't
have to check stuff.

> > +	}
> > +
> > +	return true;
> > +}
> > +
> >  static void drm_atomic_crtc_print_state(struct drm_printer *p,
> >  		const struct drm_crtc_state *state)
> >  {
> > @@ -1591,6 +1664,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> >  		}
> >  	}
> >  
> > +	state->async_update = drm_atomic_async_check(state);
> > +
> >  	return ret;
> >  }
> 
> The promotion of any modeset that passes the async_check test to async
> seems weird -- shouldn't we only be doing that if userspace requested
> async?  Right now it seems like we're just getting lucky because only
> cursor planes have it set, and the cursor update ioctls imply async.

Yeah, we should only do this when the async flag is set, and set that both
in the page_flip helpers and the cursor helpers.

One thing I wonder is whether we need to differentiate between "pls do
async if you can" and "I want async or let the update fail". Cursors would
be the former, page_flips probably too, but for async through atomic we
might also need the later. But that's just an aside, we can easily wire
this up when we have userspace asking for it (it might come real handy for
VR).

> 
> >  EXPORT_SYMBOL(drm_atomic_check_only);
> > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > index 8be9719..a79e06c 100644
> > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > @@ -1235,6 +1235,36 @@ static void commit_work(struct work_struct *work)
> >  }
> >  
> >  /**
> > + * drm_atomic_helper_async_commit - commit state asynchronously
> > + *
> > + * This function commits a state asynchronously. It should be used
> > + * on a state only when drm_atomic_async_check() succeds. Async
> > + * commits are not supposed to swap the states like normal sync commits,
> > + * but just do in-place change on the current state.
> > + */
> > +void drm_atomic_helper_async_commit(struct drm_device *dev,
> > +				    struct drm_atomic_state *state)
> > +{
> > +	struct drm_plane *plane;
> > +	struct drm_plane_state *plane_state;
> > +	const struct drm_plane_helper_funcs *funcs;
> > +	int i;
> > +
> > +	for_each_new_plane_in_state(state, plane, plane_state, i) {
> > +		funcs = plane->helper_private;
> > +
> > +		plane->state->src_x = plane_state->src_x;
> > +		plane->state->src_y = plane_state->src_y;
> > +		plane->state->crtc_x = plane_state->crtc_x;
> > +		plane->state->crtc_y = plane_state->crtc_y;
> > +
> > +		if (funcs && funcs->atomic_async_update)
> > +			funcs->atomic_async_update(plane, plane_state);
> > +	}
> > +}
> 
> It seems strange to me that the async_update() implementation in the
> driver needs to update the state->fb but not the x/y.  Could we move the
> core's x/y update after the call into the driver, and then update
> plane->state->fb in the core instead of the driver?

Yeah, consistency would be real good here ...

> > +EXPORT_SYMBOL(drm_atomic_helper_async_commit);
> > +
> > +/**
> >   * drm_atomic_helper_commit - commit validated state object
> >   * @dev: DRM device
> >   * @state: the driver state object
> > @@ -1258,6 +1288,17 @@ int drm_atomic_helper_commit(struct drm_device *dev,
> >  {
> >  	int ret;
> >  
> > +	if (state->async_update) {
> > +		ret = drm_atomic_helper_prepare_planes(dev, state);
> > +		if (ret)
> > +			return ret;
> > +
> > +		drm_atomic_helper_async_commit(dev, state);
> > +		drm_atomic_helper_cleanup_planes(dev, state);

Some async cursor updates are not 100% async, as in the hw might still
scan out the old buffer until the next vblank. Could/should we handle this
in core? Or are we going to shrug this off with "meh, you asked for
tearing, you got tearing"?

> > +
> > +		return 0;
> > +	}
> > +
> >  	ret = drm_atomic_helper_setup_commit(state, nonblock);
> >  	if (ret)
> >  		return ret;
> > diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
> > index 788daf7..c00c565 100644
> > --- a/include/drm/drm_atomic.h
> > +++ b/include/drm/drm_atomic.h
> 
> > diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
> > index c01c328..3efa4cc 100644
> > --- a/include/drm/drm_modeset_helper_vtables.h
> > +++ b/include/drm/drm_modeset_helper_vtables.h
> 
> > +	 *
> > +	 * Check drm_atomic_async_check() to see what is already there
> > +	 * to discover potential async updates.
> > +	 *
> > +	 * RETURNS:
> > +	 *
> > +	 * Return 0 on success and -EINVAL if the update can't be async.
> > +	 * Error return doesn't mean that the update can't be applied,
> > +	 * it just mean that it can't be an async one.
> > +	 *
> > +	 * FIXME:
> > +	 *  - It only works for single plane updates
> > +	 *  - It can't do async update if the plane is already being update
> > +	 *  by the queued state
> 
> "already being updated"
> 
> > +	 *  - Async Pageflips are not supported yet
> > +	 */
> > +	int (*atomic_async_check)(struct drm_plane *plane,
> > +				  struct drm_plane_state *state);
> > +
> > +	/**
> > +	 * @atomic_async_update:
> > +	 *
> > +	 * Drivers should use this functions to perform asynchronous
> 
> "this function"
> 
> Also, maybe clarify that "async" means "not vblank synchronized"?
> 
> > +	 * updates of planes, that is jump ahead the currently queued
> > +	 * state for and update the plane.
> > +	 *
> > +	 * This hook is called by drm_atomic_helper_async_commit().
> > +	 *
> > +	 * Note that differently from the &drm_plane_helper_funcs.atomic_update
> > +	 * this hook takes the new &drm_plane_state as parameter. When doing
> > +	 * async_update drivers shouldn't replace the &drm_plane_state but
> > +	 * just, update the current one with the new plane configurations in
> > +	 * the new plane_state.
> > +	 */
> > +	void (*atomic_async_update)(struct drm_plane *plane,
> > +				    struct drm_plane_state *new_state);
> >  };
> >  
> >  /**
> > -- 
> > 2.9.3



> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Gustavo Padovan April 12, 2017, 6:17 p.m. UTC | #5
2017-04-11 Daniel Vetter <daniel@ffwll.ch>:

> On Mon, Apr 10, 2017 at 12:55:33PM -0700, Eric Anholt wrote:
> > Gustavo Padovan <gustavo@padovan.org> writes:
> > 
> > > From: Gustavo Padovan <gustavo.padovan@collabora.com>
> > >
> > > In some cases, like cursor updates, it is interesting to update the
> > > plane in an asynchronous fashion to avoid big delays.
> > >
> > > The current queued update could be still waiting for a fence to signal and
> > > thus block and subsequent update until its scan out. In cases like this if
> > > we update the cursor synchronously it will cause significant delays on
> > > some platforms that would be noticed by the final user.
> > >
> > > This patch creates a fast path to jump ahead the current queued state and
> > > do single planes updates without going through all atomic step in
> > > drm_atomic_helper_commit().
> > >
> > > For now only single plane updates are supported and only if there is no
> > > update to that plane in the queued state.
> > >
> > > We plan to support async PageFlips through this interface as well in the
> > > near future.
> > 
> > This looks really nice -- the vc4 code for cursors was really gross to
> > write, and I got it wrong a couple of times.  Couple of comments inline.
> 
> Some more comments below.
> -Daniel
> 
> > 
> > > ---
> > >  drivers/gpu/drm/drm_atomic.c             | 75 ++++++++++++++++++++++++++++++++
> > >  drivers/gpu/drm/drm_atomic_helper.c      | 41 +++++++++++++++++
> > >  include/drm/drm_atomic.h                 |  4 ++
> > >  include/drm/drm_atomic_helper.h          |  2 +
> > >  include/drm/drm_modeset_helper_vtables.h | 47 ++++++++++++++++++++
> > >  5 files changed, 169 insertions(+)
> > >
> > > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > > index f32506a..cae0122 100644
> > > --- a/drivers/gpu/drm/drm_atomic.c
> > > +++ b/drivers/gpu/drm/drm_atomic.c
> > > @@ -631,6 +631,79 @@ static int drm_atomic_crtc_check(struct drm_crtc *crtc,
> > >  	return 0;
> > >  }
> > >  
> > > +static bool drm_atomic_async_check(struct drm_atomic_state *state)
> > > +{
> > > +	struct drm_crtc *crtc;
> > > +	struct drm_crtc_state *crtc_state;
> > > +	struct drm_crtc_commit *commit;
> > > +	struct drm_plane *__plane, *plane = NULL;
> > > +	struct drm_plane_state *__plane_state, *plane_state = NULL;
> > > +	const struct drm_plane_helper_funcs *funcs;
> > > +	int i, n_planes = 0;
> > > +
> > > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > > +		if (drm_atomic_crtc_needs_modeset(crtc_state))
> > > +			return false;
> > > +	}
> > > +
> > > +	for_each_new_plane_in_state(state, __plane, __plane_state, i) {
> > > +		n_planes++;
> > > +		plane = __plane;
> > > +		plane_state = __plane_state;
> > > +	}
> > > +
> > > +	if (!plane || n_planes != 1)
> > > +		return false;
> > > +
> > > +	if (!plane->state->crtc)
> > > +		return false;
> > > +
> > > +	if (plane_state->fence)
> > > +		return false;
> > > +
> > > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > > +		if (plane->crtc != crtc)
> > > +			continue;
> > > +
> > > +		spin_lock(&crtc->commit_lock);
> > > +		commit = list_first_entry_or_null(&crtc->commit_list,
> > > +						  struct drm_crtc_commit,
> > > +						  commit_entry);
> > > +		if (!commit) {
> > > +			spin_unlock(&crtc->commit_lock);
> > > +			continue;
> > > +		}
> > > +		spin_unlock(&crtc->commit_lock);
> > > +
> > > +		for_each_plane_in_state(crtc_state->state, __plane,
> > > +					__plane_state, i) {
> > > +			if (__plane == plane) {
> > > +				return false;
> > > +			}
> > > +		}
> > > +	}
> > > +
> > > +	/* Not configuring new scaling in the async path. */
> > 
> > s/Not/No/
> > 
> > > +	if (plane->state->crtc_w != plane_state->crtc_w ||
> > > +	    plane->state->crtc_h != plane_state->crtc_h ||
> > > +	    plane->state->src_w != plane_state->src_w ||
> > > +	    plane->state->src_h != plane_state->src_h) {
> > > +		return false;
> > > +	}
> > > +
> > > +	funcs = plane->helper_private;
> > > +
> > > +	if (!funcs->atomic_async_update)
> > > +		return false;
> 
> I kinda feel like we should check this first, to bail out for all the
> drivers that don't implement this before any of the more expensive checks.
> > > +
> > > +	if (funcs->atomic_async_check) {
> 
> This is redundant.
> 
> > > +		if (funcs->atomic_async_check(plane, plane_state) < 0)
> > > +			return false;
> 
> Of course the callback should be still called at the end, so it doesn't
> have to check stuff.
> 
> > > +	}
> > > +
> > > +	return true;
> > > +}
> > > +
> > >  static void drm_atomic_crtc_print_state(struct drm_printer *p,
> > >  		const struct drm_crtc_state *state)
> > >  {
> > > @@ -1591,6 +1664,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > >  		}
> > >  	}
> > >  
> > > +	state->async_update = drm_atomic_async_check(state);
> > > +
> > >  	return ret;
> > >  }
> > 
> > The promotion of any modeset that passes the async_check test to async
> > seems weird -- shouldn't we only be doing that if userspace requested
> > async?  Right now it seems like we're just getting lucky because only
> > cursor planes have it set, and the cursor update ioctls imply async.
> 
> Yeah, we should only do this when the async flag is set, and set that both
> in the page_flip helpers and the cursor helpers.
> 
> One thing I wonder is whether we need to differentiate between "pls do
> async if you can" and "I want async or let the update fail". Cursors would
> be the former, page_flips probably too, but for async through atomic we
> might also need the later. But that's just an aside, we can easily wire
> this up when we have userspace asking for it (it might come real handy for
> VR).

Adding a flag makes a lot of sense, I'll have that changed for v2.

> 
> > 
> > >  EXPORT_SYMBOL(drm_atomic_check_only);
> > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > index 8be9719..a79e06c 100644
> > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > @@ -1235,6 +1235,36 @@ static void commit_work(struct work_struct *work)
> > >  }
> > >  
> > >  /**
> > > + * drm_atomic_helper_async_commit - commit state asynchronously
> > > + *
> > > + * This function commits a state asynchronously. It should be used
> > > + * on a state only when drm_atomic_async_check() succeds. Async
> > > + * commits are not supposed to swap the states like normal sync commits,
> > > + * but just do in-place change on the current state.
> > > + */
> > > +void drm_atomic_helper_async_commit(struct drm_device *dev,
> > > +				    struct drm_atomic_state *state)
> > > +{
> > > +	struct drm_plane *plane;
> > > +	struct drm_plane_state *plane_state;
> > > +	const struct drm_plane_helper_funcs *funcs;
> > > +	int i;
> > > +
> > > +	for_each_new_plane_in_state(state, plane, plane_state, i) {
> > > +		funcs = plane->helper_private;
> > > +
> > > +		plane->state->src_x = plane_state->src_x;
> > > +		plane->state->src_y = plane_state->src_y;
> > > +		plane->state->crtc_x = plane_state->crtc_x;
> > > +		plane->state->crtc_y = plane_state->crtc_y;
> > > +
> > > +		if (funcs && funcs->atomic_async_update)
> > > +			funcs->atomic_async_update(plane, plane_state);
> > > +	}
> > > +}
> > 
> > It seems strange to me that the async_update() implementation in the
> > driver needs to update the state->fb but not the x/y.  Could we move the
> > core's x/y update after the call into the driver, and then update
> > plane->state->fb in the core instead of the driver?
> 
> Yeah, consistency would be real good here ...

Rigth, I didn't think about this lack of consistency.

> 
> > > +EXPORT_SYMBOL(drm_atomic_helper_async_commit);
> > > +
> > > +/**
> > >   * drm_atomic_helper_commit - commit validated state object
> > >   * @dev: DRM device
> > >   * @state: the driver state object
> > > @@ -1258,6 +1288,17 @@ int drm_atomic_helper_commit(struct drm_device *dev,
> > >  {
> > >  	int ret;
> > >  
> > > +	if (state->async_update) {
> > > +		ret = drm_atomic_helper_prepare_planes(dev, state);
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		drm_atomic_helper_async_commit(dev, state);
> > > +		drm_atomic_helper_cleanup_planes(dev, state);
> 
> Some async cursor updates are not 100% async, as in the hw might still
> scan out the old buffer until the next vblank. Could/should we handle this
> in core? Or are we going to shrug this off with "meh, you asked for
> tearing, you got tearing"?

Okay. It will be good to avoid any sort of tearing. For that we may need
to move the cleanup phase to the driver I think or have some sort of
callback.

Gustavo
Gustavo Padovan April 21, 2017, 6:41 p.m. UTC | #6
2017-04-11 Daniel Vetter <daniel@ffwll.ch>:

> On Mon, Apr 10, 2017 at 12:55:33PM -0700, Eric Anholt wrote:
> > Gustavo Padovan <gustavo@padovan.org> writes:
> > 
> > > From: Gustavo Padovan <gustavo.padovan@collabora.com>
> > >
> > > In some cases, like cursor updates, it is interesting to update the
> > > plane in an asynchronous fashion to avoid big delays.
> > >
> > > The current queued update could be still waiting for a fence to signal and
> > > thus block and subsequent update until its scan out. In cases like this if
> > > we update the cursor synchronously it will cause significant delays on
> > > some platforms that would be noticed by the final user.
> > >
> > > This patch creates a fast path to jump ahead the current queued state and
> > > do single planes updates without going through all atomic step in
> > > drm_atomic_helper_commit().
> > >
> > > For now only single plane updates are supported and only if there is no
> > > update to that plane in the queued state.
> > >
> > > We plan to support async PageFlips through this interface as well in the
> > > near future.
> > 
> > This looks really nice -- the vc4 code for cursors was really gross to
> > write, and I got it wrong a couple of times.  Couple of comments inline.
> 
> Some more comments below.
> -Daniel
> 
> > 
> > > ---
> > >  drivers/gpu/drm/drm_atomic.c             | 75 ++++++++++++++++++++++++++++++++
> > >  drivers/gpu/drm/drm_atomic_helper.c      | 41 +++++++++++++++++
> > >  include/drm/drm_atomic.h                 |  4 ++
> > >  include/drm/drm_atomic_helper.h          |  2 +
> > >  include/drm/drm_modeset_helper_vtables.h | 47 ++++++++++++++++++++
> > >  5 files changed, 169 insertions(+)
> > >
> > > diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
> > > index f32506a..cae0122 100644
> > > --- a/drivers/gpu/drm/drm_atomic.c
> > > +++ b/drivers/gpu/drm/drm_atomic.c
> > > @@ -631,6 +631,79 @@ static int drm_atomic_crtc_check(struct drm_crtc *crtc,
> > >  	return 0;
> > >  }
> > >  
> > > +static bool drm_atomic_async_check(struct drm_atomic_state *state)
> > > +{
> > > +	struct drm_crtc *crtc;
> > > +	struct drm_crtc_state *crtc_state;
> > > +	struct drm_crtc_commit *commit;
> > > +	struct drm_plane *__plane, *plane = NULL;
> > > +	struct drm_plane_state *__plane_state, *plane_state = NULL;
> > > +	const struct drm_plane_helper_funcs *funcs;
> > > +	int i, n_planes = 0;
> > > +
> > > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > > +		if (drm_atomic_crtc_needs_modeset(crtc_state))
> > > +			return false;
> > > +	}
> > > +
> > > +	for_each_new_plane_in_state(state, __plane, __plane_state, i) {
> > > +		n_planes++;
> > > +		plane = __plane;
> > > +		plane_state = __plane_state;
> > > +	}
> > > +
> > > +	if (!plane || n_planes != 1)
> > > +		return false;
> > > +
> > > +	if (!plane->state->crtc)
> > > +		return false;
> > > +
> > > +	if (plane_state->fence)
> > > +		return false;
> > > +
> > > +	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
> > > +		if (plane->crtc != crtc)
> > > +			continue;
> > > +
> > > +		spin_lock(&crtc->commit_lock);
> > > +		commit = list_first_entry_or_null(&crtc->commit_list,
> > > +						  struct drm_crtc_commit,
> > > +						  commit_entry);
> > > +		if (!commit) {
> > > +			spin_unlock(&crtc->commit_lock);
> > > +			continue;
> > > +		}
> > > +		spin_unlock(&crtc->commit_lock);
> > > +
> > > +		for_each_plane_in_state(crtc_state->state, __plane,
> > > +					__plane_state, i) {
> > > +			if (__plane == plane) {
> > > +				return false;
> > > +			}
> > > +		}
> > > +	}
> > > +
> > > +	/* Not configuring new scaling in the async path. */
> > 
> > s/Not/No/
> > 
> > > +	if (plane->state->crtc_w != plane_state->crtc_w ||
> > > +	    plane->state->crtc_h != plane_state->crtc_h ||
> > > +	    plane->state->src_w != plane_state->src_w ||
> > > +	    plane->state->src_h != plane_state->src_h) {
> > > +		return false;
> > > +	}
> > > +
> > > +	funcs = plane->helper_private;
> > > +
> > > +	if (!funcs->atomic_async_update)
> > > +		return false;
> 
> I kinda feel like we should check this first, to bail out for all the
> drivers that don't implement this before any of the more expensive checks.
> > > +
> > > +	if (funcs->atomic_async_check) {
> 
> This is redundant.
> 
> > > +		if (funcs->atomic_async_check(plane, plane_state) < 0)
> > > +			return false;
> 
> Of course the callback should be still called at the end, so it doesn't
> have to check stuff.
> 
> > > +	}
> > > +
> > > +	return true;
> > > +}
> > > +
> > >  static void drm_atomic_crtc_print_state(struct drm_printer *p,
> > >  		const struct drm_crtc_state *state)
> > >  {
> > > @@ -1591,6 +1664,8 @@ int drm_atomic_check_only(struct drm_atomic_state *state)
> > >  		}
> > >  	}
> > >  
> > > +	state->async_update = drm_atomic_async_check(state);
> > > +
> > >  	return ret;
> > >  }
> > 
> > The promotion of any modeset that passes the async_check test to async
> > seems weird -- shouldn't we only be doing that if userspace requested
> > async?  Right now it seems like we're just getting lucky because only
> > cursor planes have it set, and the cursor update ioctls imply async.
> 
> Yeah, we should only do this when the async flag is set, and set that both
> in the page_flip helpers and the cursor helpers.
> 
> One thing I wonder is whether we need to differentiate between "pls do
> async if you can" and "I want async or let the update fail". Cursors would
> be the former, page_flips probably too, but for async through atomic we
> might also need the later. But that's just an aside, we can easily wire
> this up when we have userspace asking for it (it might come real handy for
> VR).
> 
> > 
> > >  EXPORT_SYMBOL(drm_atomic_check_only);
> > > diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> > > index 8be9719..a79e06c 100644
> > > --- a/drivers/gpu/drm/drm_atomic_helper.c
> > > +++ b/drivers/gpu/drm/drm_atomic_helper.c
> > > @@ -1235,6 +1235,36 @@ static void commit_work(struct work_struct *work)
> > >  }
> > >  
> > >  /**
> > > + * drm_atomic_helper_async_commit - commit state asynchronously
> > > + *
> > > + * This function commits a state asynchronously. It should be used
> > > + * on a state only when drm_atomic_async_check() succeds. Async
> > > + * commits are not supposed to swap the states like normal sync commits,
> > > + * but just do in-place change on the current state.
> > > + */
> > > +void drm_atomic_helper_async_commit(struct drm_device *dev,
> > > +				    struct drm_atomic_state *state)
> > > +{
> > > +	struct drm_plane *plane;
> > > +	struct drm_plane_state *plane_state;
> > > +	const struct drm_plane_helper_funcs *funcs;
> > > +	int i;
> > > +
> > > +	for_each_new_plane_in_state(state, plane, plane_state, i) {
> > > +		funcs = plane->helper_private;
> > > +
> > > +		plane->state->src_x = plane_state->src_x;
> > > +		plane->state->src_y = plane_state->src_y;
> > > +		plane->state->crtc_x = plane_state->crtc_x;
> > > +		plane->state->crtc_y = plane_state->crtc_y;
> > > +
> > > +		if (funcs && funcs->atomic_async_update)
> > > +			funcs->atomic_async_update(plane, plane_state);
> > > +	}
> > > +}
> > 
> > It seems strange to me that the async_update() implementation in the
> > driver needs to update the state->fb but not the x/y.  Could we move the
> > core's x/y update after the call into the driver, and then update
> > plane->state->fb in the core instead of the driver?
> 
> Yeah, consistency would be real good here ...
> 
> > > +EXPORT_SYMBOL(drm_atomic_helper_async_commit);
> > > +
> > > +/**
> > >   * drm_atomic_helper_commit - commit validated state object
> > >   * @dev: DRM device
> > >   * @state: the driver state object
> > > @@ -1258,6 +1288,17 @@ int drm_atomic_helper_commit(struct drm_device *dev,
> > >  {
> > >  	int ret;
> > >  
> > > +	if (state->async_update) {
> > > +		ret = drm_atomic_helper_prepare_planes(dev, state);
> > > +		if (ret)
> > > +			return ret;
> > > +
> > > +		drm_atomic_helper_async_commit(dev, state);
> > > +		drm_atomic_helper_cleanup_planes(dev, state);
> 
> Some async cursor updates are not 100% async, as in the hw might still
> scan out the old buffer until the next vblank. Could/should we handle this
> in core? Or are we going to shrug this off with "meh, you asked for
> tearing, you got tearing"?

Do you know which hw would that be? I don't know. Maybe we should just
don't care for now and see how drivers will solve this to then extract
helpers like we did for atomic nonblocking commits.

Gustavo
Daniel Vetter May 2, 2017, 8:10 a.m. UTC | #7
On Fri, Apr 21, 2017 at 03:41:10PM -0300, Gustavo Padovan wrote:
> 2017-04-11 Daniel Vetter <daniel@ffwll.ch>:
> 
> > On Mon, Apr 10, 2017 at 12:55:33PM -0700, Eric Anholt wrote:
> > > Gustavo Padovan <gustavo@padovan.org> writes:
> > > 
> > > > From: Gustavo Padovan <gustavo.padovan@collabora.com>
> > Some async cursor updates are not 100% async, as in the hw might still
> > scan out the old buffer until the next vblank. Could/should we handle this
> > in core? Or are we going to shrug this off with "meh, you asked for
> > tearing, you got tearing"?
> 
> Do you know which hw would that be? I don't know. Maybe we should just
> don't care for now and see how drivers will solve this to then extract
> helpers like we did for atomic nonblocking commits.

i915 is one. The only way to do true async updates is throught the CS flip
command with a special bit set, and that one only works for the primary
plane. All other updates are synced to vblank, i.e. the hw will keep
scanning out the old address.

But I checked the current code, it's no better than what you've done.

More special is iirc rockchip (or some other arm-soc display ip), which on top
also has an iommu which falls over if the mapping disappears right away.
Easy solution is to not allow fb changes (but that kinda sucks), more
involved is delaying the fb cleanup into a worker (but since we don't
rate-limit this is tricky too ...).

Maybe just go with a FIXME comment here?
-Daniel
Daniel Stone May 2, 2017, 10:58 a.m. UTC | #8
Hi,

On 2 May 2017 at 09:10, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Fri, Apr 21, 2017 at 03:41:10PM -0300, Gustavo Padovan wrote:
>> Do you know which hw would that be? I don't know. Maybe we should just
>> don't care for now and see how drivers will solve this to then extract
>> helpers like we did for atomic nonblocking commits.
>
> i915 is one. The only way to do true async updates is throught the CS flip
> command with a special bit set, and that one only works for the primary
> plane. All other updates are synced to vblank, i.e. the hw will keep
> scanning out the old address.
>
> But I checked the current code, it's no better than what you've done.
>
> More special is iirc rockchip (or some other arm-soc display ip), which on top
> also has an iommu which falls over if the mapping disappears right away.
> Easy solution is to not allow fb changes (but that kinda sucks), more
> involved is delaying the fb cleanup into a worker (but since we don't
> rate-limit this is tricky too ...).

Most ARM display IP these days has it (notable exception is VC4 and I
think also i.MX?), and will do that. Exynos also has no way to update
the base address during scanout, and will not like it if it starts
smashing into faults from its IOMMU.

Cheers,
Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index f32506a..cae0122 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -631,6 +631,79 @@  static int drm_atomic_crtc_check(struct drm_crtc *crtc,
 	return 0;
 }
 
+static bool drm_atomic_async_check(struct drm_atomic_state *state)
+{
+	struct drm_crtc *crtc;
+	struct drm_crtc_state *crtc_state;
+	struct drm_crtc_commit *commit;
+	struct drm_plane *__plane, *plane = NULL;
+	struct drm_plane_state *__plane_state, *plane_state = NULL;
+	const struct drm_plane_helper_funcs *funcs;
+	int i, n_planes = 0;
+
+	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+		if (drm_atomic_crtc_needs_modeset(crtc_state))
+			return false;
+	}
+
+	for_each_new_plane_in_state(state, __plane, __plane_state, i) {
+		n_planes++;
+		plane = __plane;
+		plane_state = __plane_state;
+	}
+
+	if (!plane || n_planes != 1)
+		return false;
+
+	if (!plane->state->crtc)
+		return false;
+
+	if (plane_state->fence)
+		return false;
+
+	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+		if (plane->crtc != crtc)
+			continue;
+
+		spin_lock(&crtc->commit_lock);
+		commit = list_first_entry_or_null(&crtc->commit_list,
+						  struct drm_crtc_commit,
+						  commit_entry);
+		if (!commit) {
+			spin_unlock(&crtc->commit_lock);
+			continue;
+		}
+		spin_unlock(&crtc->commit_lock);
+
+		for_each_plane_in_state(crtc_state->state, __plane,
+					__plane_state, i) {
+			if (__plane == plane) {
+				return false;
+			}
+		}
+	}
+
+	/* Not configuring new scaling in the async path. */
+	if (plane->state->crtc_w != plane_state->crtc_w ||
+	    plane->state->crtc_h != plane_state->crtc_h ||
+	    plane->state->src_w != plane_state->src_w ||
+	    plane->state->src_h != plane_state->src_h) {
+		return false;
+	}
+
+	funcs = plane->helper_private;
+
+	if (!funcs->atomic_async_update)
+		return false;
+
+	if (funcs->atomic_async_check) {
+		if (funcs->atomic_async_check(plane, plane_state) < 0)
+			return false;
+	}
+
+	return true;
+}
+
 static void drm_atomic_crtc_print_state(struct drm_printer *p,
 		const struct drm_crtc_state *state)
 {
@@ -1591,6 +1664,8 @@  int drm_atomic_check_only(struct drm_atomic_state *state)
 		}
 	}
 
+	state->async_update = drm_atomic_async_check(state);
+
 	return ret;
 }
 EXPORT_SYMBOL(drm_atomic_check_only);
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 8be9719..a79e06c 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -1235,6 +1235,36 @@  static void commit_work(struct work_struct *work)
 }
 
 /**
+ * drm_atomic_helper_async_commit - commit state asynchronously
+ *
+ * This function commits a state asynchronously. It should be used
+ * on a state only when drm_atomic_async_check() succeds. Async
+ * commits are not supposed to swap the states like normal sync commits,
+ * but just do in-place change on the current state.
+ */
+void drm_atomic_helper_async_commit(struct drm_device *dev,
+				    struct drm_atomic_state *state)
+{
+	struct drm_plane *plane;
+	struct drm_plane_state *plane_state;
+	const struct drm_plane_helper_funcs *funcs;
+	int i;
+
+	for_each_new_plane_in_state(state, plane, plane_state, i) {
+		funcs = plane->helper_private;
+
+		plane->state->src_x = plane_state->src_x;
+		plane->state->src_y = plane_state->src_y;
+		plane->state->crtc_x = plane_state->crtc_x;
+		plane->state->crtc_y = plane_state->crtc_y;
+
+		if (funcs && funcs->atomic_async_update)
+			funcs->atomic_async_update(plane, plane_state);
+	}
+}
+EXPORT_SYMBOL(drm_atomic_helper_async_commit);
+
+/**
  * drm_atomic_helper_commit - commit validated state object
  * @dev: DRM device
  * @state: the driver state object
@@ -1258,6 +1288,17 @@  int drm_atomic_helper_commit(struct drm_device *dev,
 {
 	int ret;
 
+	if (state->async_update) {
+		ret = drm_atomic_helper_prepare_planes(dev, state);
+		if (ret)
+			return ret;
+
+		drm_atomic_helper_async_commit(dev, state);
+		drm_atomic_helper_cleanup_planes(dev, state);
+
+		return 0;
+	}
+
 	ret = drm_atomic_helper_setup_commit(state, nonblock);
 	if (ret)
 		return ret;
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 788daf7..c00c565 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -160,6 +160,8 @@  struct __drm_connnectors_state {
  * @dev: parent DRM device
  * @allow_modeset: allow full modeset
  * @legacy_cursor_update: hint to enforce legacy cursor IOCTL semantics
+ * @legacy_set_config: Disable conflicting encoders instead of failing with -EINVAL.
+ * @async_update: hint for asynchronous plane update
  * @planes: pointer to array of structures with per-plane data
  * @crtcs: pointer to array of CRTC pointers
  * @num_connector: size of the @connectors and @connector_states arrays
@@ -172,6 +174,8 @@  struct drm_atomic_state {
 	struct drm_device *dev;
 	bool allow_modeset : 1;
 	bool legacy_cursor_update : 1;
+	bool legacy_set_config : 1;
+	bool async_update : 1;
 	struct __drm_planes_state *planes;
 	struct __drm_crtcs_state *crtcs;
 	int num_connector;
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index f0a8678..8571b51 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -44,6 +44,8 @@  void drm_atomic_helper_commit_tail(struct drm_atomic_state *state);
 int drm_atomic_helper_commit(struct drm_device *dev,
 			     struct drm_atomic_state *state,
 			     bool nonblock);
+void drm_atomic_helper_async_commit(struct drm_device *dev,
+				    struct drm_atomic_state *state);
 
 int drm_atomic_helper_wait_for_fences(struct drm_device *dev,
 					struct drm_atomic_state *state,
diff --git a/include/drm/drm_modeset_helper_vtables.h b/include/drm/drm_modeset_helper_vtables.h
index c01c328..3efa4cc 100644
--- a/include/drm/drm_modeset_helper_vtables.h
+++ b/include/drm/drm_modeset_helper_vtables.h
@@ -1048,6 +1048,53 @@  struct drm_plane_helper_funcs {
 	 */
 	void (*atomic_disable)(struct drm_plane *plane,
 			       struct drm_plane_state *old_state);
+
+	/**
+	 * @atomic_async_check:
+	 *
+	 * Drivers should use this function check if the plane state
+	 * can be updated in a async fashion.
+	 *
+	 * This hook is called by drm_atomic_async_check() in the process
+	 * to figure out if an given update can be committed asynchronously,
+	 * that is, if it can jump ahead the state currently queued for
+	 * update.
+	 *
+	 * Check drm_atomic_async_check() to see what is already there
+	 * to discover potential async updates.
+	 *
+	 * RETURNS:
+	 *
+	 * Return 0 on success and -EINVAL if the update can't be async.
+	 * Error return doesn't mean that the update can't be applied,
+	 * it just mean that it can't be an async one.
+	 *
+	 * FIXME:
+	 *  - It only works for single plane updates
+	 *  - It can't do async update if the plane is already being update
+	 *  by the queued state
+	 *  - Async Pageflips are not supported yet
+	 */
+	int (*atomic_async_check)(struct drm_plane *plane,
+				  struct drm_plane_state *state);
+
+	/**
+	 * @atomic_async_update:
+	 *
+	 * Drivers should use this functions to perform asynchronous
+	 * updates of planes, that is jump ahead the currently queued
+	 * state for and update the plane.
+	 *
+	 * This hook is called by drm_atomic_helper_async_commit().
+	 *
+	 * Note that differently from the &drm_plane_helper_funcs.atomic_update
+	 * this hook takes the new &drm_plane_state as parameter. When doing
+	 * async_update drivers shouldn't replace the &drm_plane_state but
+	 * just, update the current one with the new plane configurations in
+	 * the new plane_state.
+	 */
+	void (*atomic_async_update)(struct drm_plane *plane,
+				    struct drm_plane_state *new_state);
 };
 
 /**