Commit 27641c3f authored by Mario Kleiner's avatar Mario Kleiner Committed by Dave Airlie

drm/vblank: Add support for precise vblank timestamping.

The DRI2 swap & sync implementation needs precise
vblank counts and precise timestamps corresponding
to those vblank counts. For conformance to the OpenML
OML_sync_control extension specification the DRM
timestamp associated with a vblank count should
correspond to the start of video scanout of the first
scanline of the video frame following the vblank
interval for that vblank count.

Therefore we need to carry around precise timestamps
for vblanks. Currently the DRM and KMS drivers generate
timestamps ad-hoc via do_gettimeofday() in some
places. The resulting timestamps are sometimes not
very precise due to interrupt handling delays, they
don't conform to OML_sync_control and some are wrong,
as they aren't taken synchronized to the vblank.

This patch implements support inside the drm core
for precise and robust timestamping. It consists
of the following interrelated pieces.

1. Vblank timestamp caching:

A per-crtc ringbuffer stores the most recent vblank
timestamps corresponding to vblank counts.

The ringbuffer can be read out lock-free via the
accessor function:

struct timeval timestamp;
vblankcount = drm_vblank_count_and_time(dev, crtcid, &timestamp).

The function returns the current vblank count and
the corresponding timestamp for start of video
scanout following the vblank interval. It can be
used anywhere between enclosing drm_vblank_get(dev, crtcid)
and drm_vblank_put(dev,crtcid) statements. It is used
inside the drmWaitVblank ioctl and in the vblank event
queueing and handling. It should be used by kms drivers for
timestamping of bufferswap completion.

The timestamp ringbuffer is reinitialized each time
vblank irq's get reenabled in drm_vblank_get()/
drm_update_vblank_count(). It is invalidated when
vblank irq's get disabled.

The ringbuffer is updated inside drm_handle_vblank()
at each vblank irq.

2. Calculation of precise vblank timestamps:

drm_get_last_vbltimestamp() is used to compute the
timestamp for the end of the most recent vblank (if
inside active scanout), or the expected end of the
current vblank interval (if called inside a vblank
interval). The function calls into a new optional kms
driver entry point dev->driver->get_vblank_timestamp()
which is supposed to provide the precise timestamp.
If a kms driver doesn't implement the entry point or
if the call fails, a simple do_gettimeofday() timestamp
is returned as crude approximation of the true vblank time.

A new drm module parameter drm.timestamp_precision_usec
allows to disable high precision timestamps (if set to
zero) or to specify the maximum acceptable error in
the timestamps in microseconds.

Kms drivers could implement their get_vblank_timestamp()
function in a gpu specific way, as long as returned
timestamps conform to OML_sync_control, e.g., by use
of gpu specific hardware timestamps.

Optionally, kms drivers can simply wrap and use the new
utility function drm_calc_vbltimestamp_from_scanoutpos().
This function calls a new optional kms driver function
dev->driver->get_scanout_position() which returns the
current horizontal and vertical video scanout position
of the crtc. The scanout position together with the
drm_display_timing of the current video mode is used
to calculate elapsed time relative to start of active scanout
for the current video frame. This elapsed time is subtracted
from the current do_gettimeofday() time to get the timestamp
corresponding to start of video scanout. Currently
non-interlaced, non-doublescan video modes, with or
without panel scaling are handled correctly. Interlaced/
doublescan modes are tbd in a future patch.

3. Filtering of redundant vblank irq's and removal of
some race-conditions in the vblank irq enable/disable path:

Some gpu's (e.g., Radeon R500/R600) send spurious vblank
irq's outside the vblank if vblank irq's get reenabled.
These get detected by use of the vblank timestamps and
filtered out to avoid miscounting of vblanks.

Some race-conditions between the vblank irq enable/disable
functions, the vblank irq handler and the gpu itself (updating
its hardware vblank counter in the "wrong" moment) are
fixed inside vblank_disable_and_save() and
drm_update_vblank_count() by use of the vblank timestamps and
a new spinlock dev->vblank_time_lock.

The time until vblank irq disable is now configurable via
a new drm module parameter drm.vblankoffdelay to allow
experimentation with timeouts that are much shorter than
the current 5 seconds and should allow longer vblank off
periods for better power savings.

Followup patches will use these new functions to
implement precise timestamping for the intel and radeon
kms drivers.
Signed-off-by: default avatarMario Kleiner <mario.kleiner@tuebingen.mpg.de>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 3561d43f
...@@ -336,7 +336,7 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, ...@@ -336,7 +336,7 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
struct drm_framebuffer *old_fb) struct drm_framebuffer *old_fb)
{ {
struct drm_device *dev = crtc->dev; struct drm_device *dev = crtc->dev;
struct drm_display_mode *adjusted_mode, saved_mode; struct drm_display_mode *adjusted_mode, saved_mode, saved_hwmode;
struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
struct drm_encoder_helper_funcs *encoder_funcs; struct drm_encoder_helper_funcs *encoder_funcs;
int saved_x, saved_y; int saved_x, saved_y;
...@@ -350,6 +350,7 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, ...@@ -350,6 +350,7 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
if (!crtc->enabled) if (!crtc->enabled)
return true; return true;
saved_hwmode = crtc->hwmode;
saved_mode = crtc->mode; saved_mode = crtc->mode;
saved_x = crtc->x; saved_x = crtc->x;
saved_y = crtc->y; saved_y = crtc->y;
...@@ -427,11 +428,21 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, ...@@ -427,11 +428,21 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
} }
/* Store real post-adjustment hardware mode. */
crtc->hwmode = *adjusted_mode;
/* Calculate and store various constants which
* are later needed by vblank and swap-completion
* timestamping. They are derived from true hwmode.
*/
drm_calc_timestamping_constants(crtc);
/* XXX free adjustedmode */ /* XXX free adjustedmode */
drm_mode_destroy(dev, adjusted_mode); drm_mode_destroy(dev, adjusted_mode);
/* FIXME: add subpixel order */ /* FIXME: add subpixel order */
done: done:
if (!ret) { if (!ret) {
crtc->hwmode = saved_hwmode;
crtc->mode = saved_mode; crtc->mode = saved_mode;
crtc->x = saved_x; crtc->x = saved_x;
crtc->y = saved_y; crtc->y = saved_y;
......
This diff is collapsed.
...@@ -40,12 +40,22 @@ ...@@ -40,12 +40,22 @@
unsigned int drm_debug = 0; /* 1 to enable debug output */ unsigned int drm_debug = 0; /* 1 to enable debug output */
EXPORT_SYMBOL(drm_debug); EXPORT_SYMBOL(drm_debug);
unsigned int drm_vblank_offdelay = 5000; /* Default to 5000 msecs. */
EXPORT_SYMBOL(drm_vblank_offdelay);
unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */
EXPORT_SYMBOL(drm_timestamp_precision);
MODULE_AUTHOR(CORE_AUTHOR); MODULE_AUTHOR(CORE_AUTHOR);
MODULE_DESCRIPTION(CORE_DESC); MODULE_DESCRIPTION(CORE_DESC);
MODULE_LICENSE("GPL and additional rights"); MODULE_LICENSE("GPL and additional rights");
MODULE_PARM_DESC(debug, "Enable debug output"); MODULE_PARM_DESC(debug, "Enable debug output");
MODULE_PARM_DESC(vblankoffdelay, "Delay until vblank irq auto-disable [msecs]");
MODULE_PARM_DESC(timestamp_precision_usec, "Max. error on timestamps [usecs]");
module_param_named(debug, drm_debug, int, 0600); module_param_named(debug, drm_debug, int, 0600);
module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600);
module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600);
struct idr drm_minors_idr; struct idr drm_minors_idr;
......
...@@ -683,6 +683,21 @@ struct drm_master { ...@@ -683,6 +683,21 @@ struct drm_master {
void *driver_priv; /**< Private structure for driver to use */ void *driver_priv; /**< Private structure for driver to use */
}; };
/* Size of ringbuffer for vblank timestamps. Just double-buffer
* in initial implementation.
*/
#define DRM_VBLANKTIME_RBSIZE 2
/* Flags and return codes for get_vblank_timestamp() driver function. */
#define DRM_CALLED_FROM_VBLIRQ 1
#define DRM_VBLANKTIME_SCANOUTPOS_METHOD (1 << 0)
#define DRM_VBLANKTIME_INVBL (1 << 1)
/* get_scanout_position() return flags */
#define DRM_SCANOUTPOS_VALID (1 << 0)
#define DRM_SCANOUTPOS_INVBL (1 << 1)
#define DRM_SCANOUTPOS_ACCURATE (1 << 2)
/** /**
* DRM driver structure. This structure represent the common code for * DRM driver structure. This structure represent the common code for
* a family of cards. There will one drm_device for each card present * a family of cards. There will one drm_device for each card present
...@@ -760,6 +775,68 @@ struct drm_driver { ...@@ -760,6 +775,68 @@ struct drm_driver {
*/ */
int (*device_is_agp) (struct drm_device *dev); int (*device_is_agp) (struct drm_device *dev);
/**
* Called by vblank timestamping code.
*
* Return the current display scanout position from a crtc.
*
* \param dev DRM device.
* \param crtc Id of the crtc to query.
* \param *vpos Target location for current vertical scanout position.
* \param *hpos Target location for current horizontal scanout position.
*
* Returns vpos as a positive number while in active scanout area.
* Returns vpos as a negative number inside vblank, counting the number
* of scanlines to go until end of vblank, e.g., -1 means "one scanline
* until start of active scanout / end of vblank."
*
* \return Flags, or'ed together as follows:
*
* DRM_SCANOUTPOS_VALID = Query successfull.
* DRM_SCANOUTPOS_INVBL = Inside vblank.
* DRM_SCANOUTPOS_ACCURATE = Returned position is accurate. A lack of
* this flag means that returned position may be offset by a constant
* but unknown small number of scanlines wrt. real scanout position.
*
*/
int (*get_scanout_position) (struct drm_device *dev, int crtc,
int *vpos, int *hpos);
/**
* Called by \c drm_get_last_vbltimestamp. Should return a precise
* timestamp when the most recent VBLANK interval ended or will end.
*
* Specifically, the timestamp in @vblank_time should correspond as
* closely as possible to the time when the first video scanline of
* the video frame after the end of VBLANK will start scanning out,
* the time immmediately after end of the VBLANK interval. If the
* @crtc is currently inside VBLANK, this will be a time in the future.
* If the @crtc is currently scanning out a frame, this will be the
* past start time of the current scanout. This is meant to adhere
* to the OpenML OML_sync_control extension specification.
*
* \param dev dev DRM device handle.
* \param crtc crtc for which timestamp should be returned.
* \param *max_error Maximum allowable timestamp error in nanoseconds.
* Implementation should strive to provide timestamp
* with an error of at most *max_error nanoseconds.
* Returns true upper bound on error for timestamp.
* \param *vblank_time Target location for returned vblank timestamp.
* \param flags 0 = Defaults, no special treatment needed.
* \param DRM_CALLED_FROM_VBLIRQ = Function is called from vblank
* irq handler. Some drivers need to apply some workarounds
* for gpu-specific vblank irq quirks if flag is set.
*
* \returns
* Zero if timestamping isn't supported in current display mode or a
* negative number on failure. A positive status code on success,
* which describes how the vblank_time timestamp was computed.
*/
int (*get_vblank_timestamp) (struct drm_device *dev, int crtc,
int *max_error,
struct timeval *vblank_time,
unsigned flags);
/* these have to be filled in */ /* these have to be filled in */
irqreturn_t(*irq_handler) (DRM_IRQ_ARGS); irqreturn_t(*irq_handler) (DRM_IRQ_ARGS);
...@@ -983,6 +1060,8 @@ struct drm_device { ...@@ -983,6 +1060,8 @@ struct drm_device {
wait_queue_head_t *vbl_queue; /**< VBLANK wait queue */ wait_queue_head_t *vbl_queue; /**< VBLANK wait queue */
atomic_t *_vblank_count; /**< number of VBLANK interrupts (driver must alloc the right number of counters) */ atomic_t *_vblank_count; /**< number of VBLANK interrupts (driver must alloc the right number of counters) */
struct timeval *_vblank_time; /**< timestamp of current vblank_count (drivers must alloc right number of fields) */
spinlock_t vblank_time_lock; /**< Protects vblank count and time updates during vblank enable/disable */
spinlock_t vbl_lock; spinlock_t vbl_lock;
atomic_t *vblank_refcount; /* number of users of vblank interruptsper crtc */ atomic_t *vblank_refcount; /* number of users of vblank interruptsper crtc */
u32 *last_vblank; /* protected by dev->vbl_lock, used */ u32 *last_vblank; /* protected by dev->vbl_lock, used */
...@@ -1284,11 +1363,22 @@ extern int drm_wait_vblank(struct drm_device *dev, void *data, ...@@ -1284,11 +1363,22 @@ extern int drm_wait_vblank(struct drm_device *dev, void *data,
struct drm_file *filp); struct drm_file *filp);
extern int drm_vblank_wait(struct drm_device *dev, unsigned int *vbl_seq); extern int drm_vblank_wait(struct drm_device *dev, unsigned int *vbl_seq);
extern u32 drm_vblank_count(struct drm_device *dev, int crtc); extern u32 drm_vblank_count(struct drm_device *dev, int crtc);
extern u32 drm_vblank_count_and_time(struct drm_device *dev, int crtc,
struct timeval *vblanktime);
extern void drm_handle_vblank(struct drm_device *dev, int crtc); extern void drm_handle_vblank(struct drm_device *dev, int crtc);
extern int drm_vblank_get(struct drm_device *dev, int crtc); extern int drm_vblank_get(struct drm_device *dev, int crtc);
extern void drm_vblank_put(struct drm_device *dev, int crtc); extern void drm_vblank_put(struct drm_device *dev, int crtc);
extern void drm_vblank_off(struct drm_device *dev, int crtc); extern void drm_vblank_off(struct drm_device *dev, int crtc);
extern void drm_vblank_cleanup(struct drm_device *dev); extern void drm_vblank_cleanup(struct drm_device *dev);
extern u32 drm_get_last_vbltimestamp(struct drm_device *dev, int crtc,
struct timeval *tvblank, unsigned flags);
extern int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev,
int crtc, int *max_error,
struct timeval *vblank_time,
unsigned flags,
struct drm_crtc *refcrtc);
extern void drm_calc_timestamping_constants(struct drm_crtc *crtc);
/* Modesetting support */ /* Modesetting support */
extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_pre_modeset(struct drm_device *dev, int crtc);
extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc); extern void drm_vblank_post_modeset(struct drm_device *dev, int crtc);
...@@ -1340,6 +1430,9 @@ extern void drm_put_dev(struct drm_device *dev); ...@@ -1340,6 +1430,9 @@ extern void drm_put_dev(struct drm_device *dev);
extern int drm_put_minor(struct drm_minor **minor); extern int drm_put_minor(struct drm_minor **minor);
extern unsigned int drm_debug; extern unsigned int drm_debug;
extern unsigned int drm_vblank_offdelay;
extern unsigned int drm_timestamp_precision;
extern struct class *drm_class; extern struct class *drm_class;
extern struct proc_dir_entry *drm_proc_root; extern struct proc_dir_entry *drm_proc_root;
extern struct dentry *drm_debugfs_root; extern struct dentry *drm_debugfs_root;
......
...@@ -351,8 +351,14 @@ struct drm_crtc { ...@@ -351,8 +351,14 @@ struct drm_crtc {
bool enabled; bool enabled;
/* Requested mode from modesetting. */
struct drm_display_mode mode; struct drm_display_mode mode;
/* Programmed mode in hw, after adjustments for encoders,
* crtc, panel scaling etc. Needed for timestamping etc.
*/
struct drm_display_mode hwmode;
int x, y; int x, y;
const struct drm_crtc_funcs *funcs; const struct drm_crtc_funcs *funcs;
...@@ -360,6 +366,9 @@ struct drm_crtc { ...@@ -360,6 +366,9 @@ struct drm_crtc {
uint32_t gamma_size; uint32_t gamma_size;
uint16_t *gamma_store; uint16_t *gamma_store;
/* Constants needed for precise vblank and swap timestamping. */
s64 framedur_ns, linedur_ns, pixeldur_ns;
/* if you are using the helper */ /* if you are using the helper */
void *helper_private; void *helper_private;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment