[PATCH] drm/vblank: Fixup and document timestamp update/read barriers

Peter Hurley peter at hurleysoftware.com
Wed Apr 15 06:00:04 PDT 2015


Hi Daniel,

On 04/15/2015 03:17 AM, Daniel Vetter wrote:
> This was a bit too much cargo-culted, so lets make it solid:
> - vblank->count doesn't need to be an atomic, writes are always done
>   under the protection of dev->vblank_time_lock. Switch to an unsigned
>   long instead and update comments. Note that atomic_read is just a
>   normal read of a volatile variable, so no need to audit all the
>   read-side access specifically.
> 
> - The barriers for the vblank counter seqlock weren't complete: The
>   read-side was missing the first barrier between the counter read and
>   the timestamp read, it only had a barrier between the ts and the
>   counter read. We need both.
> 
> - Barriers weren't properly documented. Since barriers only work if
>   you have them on boths sides of the transaction it's prudent to
>   reference where the other side is. To avoid duplicating the
>   write-side comment 3 times extract a little store_vblank() helper.
>   In that helper also assert that we do indeed hold
>   dev->vblank_time_lock, since in some cases the lock is acquired a
>   few functions up in the callchain.
> 
> Spotted while reviewing a patch from Chris Wilson to add a fastpath to
> the vblank_wait ioctl.
> 
> Cc: Chris Wilson <chris at chris-wilson.co.uk>
> Cc: Mario Kleiner <mario.kleiner.de at gmail.com>
> Cc: Ville Syrjälä <ville.syrjala at linux.intel.com>
> Cc: Michel Dänzer <michel at daenzer.net>
> Signed-off-by: Daniel Vetter <daniel.vetter at intel.com>
> ---
>  drivers/gpu/drm/drm_irq.c | 92 ++++++++++++++++++++++++-----------------------
>  include/drm/drmP.h        |  8 +++--
>  2 files changed, 54 insertions(+), 46 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c
> index c8a34476570a..23bfbc61a494 100644
> --- a/drivers/gpu/drm/drm_irq.c
> +++ b/drivers/gpu/drm/drm_irq.c
> @@ -74,6 +74,33 @@ module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600);
>  module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600);
>  module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600);
>  
> +static void store_vblank(struct drm_device *dev, int crtc,
> +			 unsigned vblank_count_inc,
> +			 struct timeval *t_vblank)
> +{
> +	struct drm_vblank_crtc *vblank = &dev->vblank[crtc];
> +	u32 tslot;
> +
> +	assert_spin_locked(&dev->vblank_time_lock);
> +
> +	if (t_vblank) {
> +		tslot = vblank->count + vblank_count_inc;
> +		vblanktimestamp(dev, crtc, tslot) = *t_vblank;
> +	}
> +
> +	/*
> +	 * vblank timestamp updates are protected on the write side with
> +	 * vblank_time_lock, but on the read side done locklessly using a
> +	 * sequence-lock on the vblank counter. Ensure correct ordering using
> +	 * memory barrriers. We need the barrier both before and also after the
> +	 * counter update to synchronize with the next timestamp write.
> +	 * The read-side barriers for this are in drm_vblank_count_and_time.
> +	 */
> +	smp_wmb();
> +	vblank->count += vblank_count_inc;
> +	smp_wmb();

The comment and the code are each self-contradictory.

If vblank->count writes are always protected by vblank_time_lock (something I
did not verify but that the comment above asserts), then the trailing write
barrier is not required (and the assertion that it is in the comment is incorrect).

A spin unlock operation is always a write barrier.

Regards,
Peter Hurley

> +}
> +
>  /**
>   * drm_update_vblank_count - update the master vblank counter
>   * @dev: DRM device
> @@ -93,7 +120,7 @@ module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600);
>  static void drm_update_vblank_count(struct drm_device *dev, int crtc)
>  {
>  	struct drm_vblank_crtc *vblank = &dev->vblank[crtc];
> -	u32 cur_vblank, diff, tslot;
> +	u32 cur_vblank, diff;
>  	bool rc;
>  	struct timeval t_vblank;
>  
> @@ -129,18 +156,12 @@ static void drm_update_vblank_count(struct drm_device *dev, int crtc)
>  	if (diff == 0)
>  		return;
>  
> -	/* Reinitialize corresponding vblank timestamp if high-precision query
> -	 * available. Skip this step if query unsupported or failed. Will
> -	 * reinitialize delayed at next vblank interrupt in that case.
> +	/*
> +	 * Only reinitialize corresponding vblank timestamp if high-precision query
> +	 * available and didn't fail. Will reinitialize delayed at next vblank
> +	 * interrupt in that case.
>  	 */
> -	if (rc) {
> -		tslot = atomic_read(&vblank->count) + diff;
> -		vblanktimestamp(dev, crtc, tslot) = t_vblank;
> -	}
> -
> -	smp_mb__before_atomic();
> -	atomic_add(diff, &vblank->count);
> -	smp_mb__after_atomic();
> +	store_vblank(dev, crtc, diff, rc ? &t_vblank : NULL);
>  }
>  
>  /*
> @@ -218,7 +239,7 @@ static void vblank_disable_and_save(struct drm_device *dev, int crtc)
>  	/* Compute time difference to stored timestamp of last vblank
>  	 * as updated by last invocation of drm_handle_vblank() in vblank irq.
>  	 */
> -	vblcount = atomic_read(&vblank->count);
> +	vblcount = vblank->count;
>  	diff_ns = timeval_to_ns(&tvblank) -
>  		  timeval_to_ns(&vblanktimestamp(dev, crtc, vblcount));
>  
> @@ -234,17 +255,8 @@ static void vblank_disable_and_save(struct drm_device *dev, int crtc)
>  	 * available. In that case we can't account for this and just
>  	 * hope for the best.
>  	 */
> -	if (vblrc && (abs64(diff_ns) > 1000000)) {
> -		/* Store new timestamp in ringbuffer. */
> -		vblanktimestamp(dev, crtc, vblcount + 1) = tvblank;
> -
> -		/* Increment cooked vblank count. This also atomically commits
> -		 * the timestamp computed above.
> -		 */
> -		smp_mb__before_atomic();
> -		atomic_inc(&vblank->count);
> -		smp_mb__after_atomic();
> -	}
> +	if (vblrc && (abs64(diff_ns) > 1000000))
> +		store_vblank(dev, crtc, 1, &tvblank);
>  
>  	spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags);
>  }
> @@ -852,7 +864,7 @@ u32 drm_vblank_count(struct drm_device *dev, int crtc)
>  
>  	if (WARN_ON(crtc >= dev->num_crtcs))
>  		return 0;
> -	return atomic_read(&vblank->count);
> +	return vblank->count;
>  }
>  EXPORT_SYMBOL(drm_vblank_count);
>  
> @@ -897,16 +909,17 @@ u32 drm_vblank_count_and_time(struct drm_device *dev, int crtc,
>  	if (WARN_ON(crtc >= dev->num_crtcs))
>  		return 0;
>  
> -	/* Read timestamp from slot of _vblank_time ringbuffer
> -	 * that corresponds to current vblank count. Retry if
> -	 * count has incremented during readout. This works like
> -	 * a seqlock.
> +	/*
> +	 * Vblank timestamps are read lockless. To ensure consistency the vblank
> +	 * counter is rechecked and ordering is ensured using memory barriers.
> +	 * This works like a seqlock. The write-side barriers are in store_vblank.
>  	 */
>  	do {
> -		cur_vblank = atomic_read(&vblank->count);
> +		cur_vblank = vblank->count;
> +		smp_rmb();
>  		*vblanktime = vblanktimestamp(dev, crtc, cur_vblank);
>  		smp_rmb();
> -	} while (cur_vblank != atomic_read(&vblank->count));
> +	} while (cur_vblank != vblank->count);
>  
>  	return cur_vblank;
>  }
> @@ -1715,7 +1728,7 @@ bool drm_handle_vblank(struct drm_device *dev, int crtc)
>  	 */
>  
>  	/* Get current timestamp and count. */
> -	vblcount = atomic_read(&vblank->count);
> +	vblcount = vblank->count;
>  	drm_get_last_vbltimestamp(dev, crtc, &tvblank, DRM_CALLED_FROM_VBLIRQ);
>  
>  	/* Compute time difference to timestamp of last vblank */
> @@ -1731,20 +1744,11 @@ bool drm_handle_vblank(struct drm_device *dev, int crtc)
>  	 * e.g., due to spurious vblank interrupts. We need to
>  	 * ignore those for accounting.
>  	 */
> -	if (abs64(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS) {
> -		/* Store new timestamp in ringbuffer. */
> -		vblanktimestamp(dev, crtc, vblcount + 1) = tvblank;
> -
> -		/* Increment cooked vblank count. This also atomically commits
> -		 * the timestamp computed above.
> -		 */
> -		smp_mb__before_atomic();
> -		atomic_inc(&vblank->count);
> -		smp_mb__after_atomic();
> -	} else {
> +	if (abs64(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS)
> +		store_vblank(dev, crtc, 1, &tvblank);
> +	else
>  		DRM_DEBUG("crtc %d: Redundant vblirq ignored. diff_ns = %d\n",
>  			  crtc, (int) diff_ns);
> -	}
>  
>  	spin_unlock(&dev->vblank_time_lock);
>  
> diff --git a/include/drm/drmP.h b/include/drm/drmP.h
> index 62c40777c009..4c31a2cc5a33 100644
> --- a/include/drm/drmP.h
> +++ b/include/drm/drmP.h
> @@ -686,9 +686,13 @@ struct drm_pending_vblank_event {
>  struct drm_vblank_crtc {
>  	struct drm_device *dev;		/* pointer to the drm_device */
>  	wait_queue_head_t queue;	/**< VBLANK wait queue */
> -	struct timeval time[DRM_VBLANKTIME_RBSIZE];	/**< timestamp of current count */
>  	struct timer_list disable_timer;		/* delayed disable timer */
> -	atomic_t count;			/**< number of VBLANK interrupts */
> +
> +	/* vblank counter, protected by dev->vblank_time_lock for writes */
> +	unsigned long count;
> +	/* vblank timestamps, protected by dev->vblank_time_lock for writes */
> +	struct timeval time[DRM_VBLANKTIME_RBSIZE];
> +
>  	atomic_t refcount;		/* number of users of vblank interruptsper crtc */
>  	u32 last;			/* protected by dev->vbl_lock, used */
>  					/* for wraparound handling */
> 



More information about the dri-devel mailing list