Commit c9a9c5e0 authored by Kristian Høgsberg's avatar Kristian Høgsberg Committed by Dave Airlie

drm: Add async event synchronization for drmWaitVblank

This patch adds a new flag to the drmWaitVblank ioctl, which asks the drm
to return immediately and notify userspace when the specified vblank sequence
happens by sending an event back on the drm fd.

The event mechanism works with the other flags supported by the ioctls,
specifically, the vblank sequence can be specified relatively or absolutely,
and works for primary and seconday crtc.

The signal field of the vblank request is used to provide user data,
which will be sent back to user space in the vblank event.
Signed-off-by: default avatarKristian Høgsberg <krh@redhat.com>
Reviewed-by: default avatarJesse Barnes <jbarnes@virtuousgeek.org>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 799dd75b
...@@ -257,6 +257,9 @@ static int drm_open_helper(struct inode *inode, struct file *filp, ...@@ -257,6 +257,9 @@ static int drm_open_helper(struct inode *inode, struct file *filp,
INIT_LIST_HEAD(&priv->lhead); INIT_LIST_HEAD(&priv->lhead);
INIT_LIST_HEAD(&priv->fbs); INIT_LIST_HEAD(&priv->fbs);
INIT_LIST_HEAD(&priv->event_list);
init_waitqueue_head(&priv->event_wait);
priv->event_space = 4096; /* set aside 4k for event buffer */
if (dev->driver->driver_features & DRIVER_GEM) if (dev->driver->driver_features & DRIVER_GEM)
drm_gem_open(dev, priv); drm_gem_open(dev, priv);
...@@ -413,6 +416,30 @@ static void drm_master_release(struct drm_device *dev, struct file *filp) ...@@ -413,6 +416,30 @@ static void drm_master_release(struct drm_device *dev, struct file *filp)
} }
} }
static void drm_events_release(struct drm_file *file_priv)
{
struct drm_device *dev = file_priv->minor->dev;
struct drm_pending_event *e, *et;
struct drm_pending_vblank_event *v, *vt;
unsigned long flags;
spin_lock_irqsave(&dev->event_lock, flags);
/* Remove pending flips */
list_for_each_entry_safe(v, vt, &dev->vblank_event_list, base.link)
if (v->base.file_priv == file_priv) {
list_del(&v->base.link);
drm_vblank_put(dev, v->pipe);
v->base.destroy(&v->base);
}
/* Remove unconsumed events */
list_for_each_entry_safe(e, et, &file_priv->event_list, link)
e->destroy(e);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
/** /**
* Release file. * Release file.
* *
...@@ -451,6 +478,8 @@ int drm_release(struct inode *inode, struct file *filp) ...@@ -451,6 +478,8 @@ int drm_release(struct inode *inode, struct file *filp)
if (file_priv->minor->master) if (file_priv->minor->master)
drm_master_release(dev, filp); drm_master_release(dev, filp);
drm_events_release(file_priv);
if (dev->driver->driver_features & DRIVER_GEM) if (dev->driver->driver_features & DRIVER_GEM)
drm_gem_release(dev, file_priv); drm_gem_release(dev, file_priv);
...@@ -544,9 +573,74 @@ int drm_release(struct inode *inode, struct file *filp) ...@@ -544,9 +573,74 @@ int drm_release(struct inode *inode, struct file *filp)
} }
EXPORT_SYMBOL(drm_release); EXPORT_SYMBOL(drm_release);
/** No-op. */ static bool
drm_dequeue_event(struct drm_file *file_priv,
size_t total, size_t max, struct drm_pending_event **out)
{
struct drm_device *dev = file_priv->minor->dev;
struct drm_pending_event *e;
unsigned long flags;
bool ret = false;
spin_lock_irqsave(&dev->event_lock, flags);
*out = NULL;
if (list_empty(&file_priv->event_list))
goto out;
e = list_first_entry(&file_priv->event_list,
struct drm_pending_event, link);
if (e->event->length + total > max)
goto out;
file_priv->event_space += e->event->length;
list_del(&e->link);
*out = e;
ret = true;
out:
spin_unlock_irqrestore(&dev->event_lock, flags);
return ret;
}
ssize_t drm_read(struct file *filp, char __user *buffer,
size_t count, loff_t *offset)
{
struct drm_file *file_priv = filp->private_data;
struct drm_pending_event *e;
size_t total;
ssize_t ret;
ret = wait_event_interruptible(file_priv->event_wait,
!list_empty(&file_priv->event_list));
if (ret < 0)
return ret;
total = 0;
while (drm_dequeue_event(file_priv, total, count, &e)) {
if (copy_to_user(buffer + total,
e->event, e->event->length)) {
total = -EFAULT;
break;
}
total += e->event->length;
e->destroy(e);
}
return total;
}
EXPORT_SYMBOL(drm_read);
unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait) unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait)
{ {
return 0; struct drm_file *file_priv = filp->private_data;
unsigned int mask = 0;
poll_wait(filp, &file_priv->event_wait, wait);
if (!list_empty(&file_priv->event_list))
mask |= POLLIN | POLLRDNORM;
return mask;
} }
EXPORT_SYMBOL(drm_poll); EXPORT_SYMBOL(drm_poll);
...@@ -550,6 +550,62 @@ out: ...@@ -550,6 +550,62 @@ out:
return ret; return ret;
} }
static int drm_queue_vblank_event(struct drm_device *dev, int pipe,
union drm_wait_vblank *vblwait,
struct drm_file *file_priv)
{
struct drm_pending_vblank_event *e;
struct timeval now;
unsigned long flags;
unsigned int seq;
e = kzalloc(sizeof *e, GFP_KERNEL);
if (e == NULL)
return -ENOMEM;
e->pipe = pipe;
e->event.base.type = DRM_EVENT_VBLANK;
e->event.base.length = sizeof e->event;
e->event.user_data = vblwait->request.signal;
e->base.event = &e->event.base;
e->base.file_priv = file_priv;
e->base.destroy = (void (*) (struct drm_pending_event *)) kfree;
do_gettimeofday(&now);
spin_lock_irqsave(&dev->event_lock, flags);
if (file_priv->event_space < sizeof e->event) {
spin_unlock_irqrestore(&dev->event_lock, flags);
kfree(e);
return -ENOMEM;
}
file_priv->event_space -= sizeof e->event;
seq = drm_vblank_count(dev, pipe);
if ((vblwait->request.type & _DRM_VBLANK_NEXTONMISS) &&
(seq - vblwait->request.sequence) <= (1 << 23)) {
vblwait->request.sequence = seq + 1;
}
DRM_DEBUG("event on vblank count %d, current %d, crtc %d\n",
vblwait->request.sequence, seq, pipe);
e->event.sequence = vblwait->request.sequence;
if ((seq - vblwait->request.sequence) <= (1 << 23)) {
e->event.tv_sec = now.tv_sec;
e->event.tv_usec = now.tv_usec;
drm_vblank_put(dev, e->pipe);
list_add_tail(&e->base.link, &e->base.file_priv->event_list);
wake_up_interruptible(&e->base.file_priv->event_wait);
} else {
list_add_tail(&e->base.link, &dev->vblank_event_list);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
return 0;
}
/** /**
* Wait for VBLANK. * Wait for VBLANK.
* *
...@@ -609,6 +665,9 @@ int drm_wait_vblank(struct drm_device *dev, void *data, ...@@ -609,6 +665,9 @@ int drm_wait_vblank(struct drm_device *dev, void *data,
goto done; goto done;
} }
if (flags & _DRM_VBLANK_EVENT)
return drm_queue_vblank_event(dev, crtc, vblwait, file_priv);
if ((flags & _DRM_VBLANK_NEXTONMISS) && if ((flags & _DRM_VBLANK_NEXTONMISS) &&
(seq - vblwait->request.sequence) <= (1<<23)) { (seq - vblwait->request.sequence) <= (1<<23)) {
vblwait->request.sequence = seq + 1; vblwait->request.sequence = seq + 1;
...@@ -641,6 +700,38 @@ done: ...@@ -641,6 +700,38 @@ done:
return ret; return ret;
} }
void drm_handle_vblank_events(struct drm_device *dev, int crtc)
{
struct drm_pending_vblank_event *e, *t;
struct timeval now;
unsigned long flags;
unsigned int seq;
do_gettimeofday(&now);
seq = drm_vblank_count(dev, crtc);
spin_lock_irqsave(&dev->event_lock, flags);
list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) {
if (e->pipe != crtc)
continue;
if ((seq - e->event.sequence) > (1<<23))
continue;
DRM_DEBUG("vblank event on %d, current %d\n",
e->event.sequence, seq);
e->event.sequence = seq;
e->event.tv_sec = now.tv_sec;
e->event.tv_usec = now.tv_usec;
drm_vblank_put(dev, e->pipe);
list_move_tail(&e->base.link, &e->base.file_priv->event_list);
wake_up_interruptible(&e->base.file_priv->event_wait);
}
spin_unlock_irqrestore(&dev->event_lock, flags);
}
/** /**
* drm_handle_vblank - handle a vblank event * drm_handle_vblank - handle a vblank event
* @dev: DRM device * @dev: DRM device
...@@ -651,7 +742,11 @@ done: ...@@ -651,7 +742,11 @@ done:
*/ */
void drm_handle_vblank(struct drm_device *dev, int crtc) void drm_handle_vblank(struct drm_device *dev, int crtc)
{ {
if (!dev->num_crtcs)
return;
atomic_inc(&dev->_vblank_count[crtc]); atomic_inc(&dev->_vblank_count[crtc]);
DRM_WAKEUP(&dev->vbl_queue[crtc]); DRM_WAKEUP(&dev->vbl_queue[crtc]);
drm_handle_vblank_events(dev, crtc);
} }
EXPORT_SYMBOL(drm_handle_vblank); EXPORT_SYMBOL(drm_handle_vblank);
...@@ -220,9 +220,11 @@ static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev, ...@@ -220,9 +220,11 @@ static int drm_fill_in_dev(struct drm_device * dev, struct pci_dev *pdev,
INIT_LIST_HEAD(&dev->ctxlist); INIT_LIST_HEAD(&dev->ctxlist);
INIT_LIST_HEAD(&dev->vmalist); INIT_LIST_HEAD(&dev->vmalist);
INIT_LIST_HEAD(&dev->maplist); INIT_LIST_HEAD(&dev->maplist);
INIT_LIST_HEAD(&dev->vblank_event_list);
spin_lock_init(&dev->count_lock); spin_lock_init(&dev->count_lock);
spin_lock_init(&dev->drw_lock); spin_lock_init(&dev->drw_lock);
spin_lock_init(&dev->event_lock);
init_timer(&dev->timer); init_timer(&dev->timer);
mutex_init(&dev->struct_mutex); mutex_init(&dev->struct_mutex);
mutex_init(&dev->ctxlist_mutex); mutex_init(&dev->ctxlist_mutex);
......
...@@ -333,6 +333,7 @@ static struct drm_driver driver = { ...@@ -333,6 +333,7 @@ static struct drm_driver driver = {
.mmap = drm_gem_mmap, .mmap = drm_gem_mmap,
.poll = drm_poll, .poll = drm_poll,
.fasync = drm_fasync, .fasync = drm_fasync,
.read = drm_read,
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
.compat_ioctl = i915_compat_ioctl, .compat_ioctl = i915_compat_ioctl,
#endif #endif
......
...@@ -454,6 +454,7 @@ struct drm_irq_busid { ...@@ -454,6 +454,7 @@ struct drm_irq_busid {
enum drm_vblank_seq_type { enum drm_vblank_seq_type {
_DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */ _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */
_DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */ _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */
_DRM_VBLANK_EVENT = 0x4000000, /**< Send event instead of blocking */
_DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */ _DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */
_DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */ _DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */
_DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */ _DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */
...@@ -461,8 +462,8 @@ enum drm_vblank_seq_type { ...@@ -461,8 +462,8 @@ enum drm_vblank_seq_type {
}; };
#define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE) #define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE)
#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_SIGNAL | _DRM_VBLANK_SECONDARY | \ #define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_EVENT | _DRM_VBLANK_SIGNAL | \
_DRM_VBLANK_NEXTONMISS) _DRM_VBLANK_SECONDARY | _DRM_VBLANK_NEXTONMISS)
struct drm_wait_vblank_request { struct drm_wait_vblank_request {
enum drm_vblank_seq_type type; enum drm_vblank_seq_type type;
...@@ -698,6 +699,34 @@ struct drm_gem_open { ...@@ -698,6 +699,34 @@ struct drm_gem_open {
#define DRM_COMMAND_BASE 0x40 #define DRM_COMMAND_BASE 0x40
#define DRM_COMMAND_END 0xA0 #define DRM_COMMAND_END 0xA0
/**
* Header for events written back to userspace on the drm fd. The
* type defines the type of event, the length specifies the total
* length of the event (including the header), and user_data is
* typically a 64 bit value passed with the ioctl that triggered the
* event. A read on the drm fd will always only return complete
* events, that is, if for example the read buffer is 100 bytes, and
* there are two 64 byte events pending, only one will be returned.
*
* Event types 0 - 0x7fffffff are generic drm events, 0x80000000 and
* up are chipset specific.
*/
struct drm_event {
__u32 type;
__u32 length;
};
#define DRM_EVENT_VBLANK 0x01
struct drm_event_vblank {
struct drm_event base;
__u64 user_data;
__u32 tv_sec;
__u32 tv_usec;
__u32 sequence;
__u32 reserved;
};
/* typedef area */ /* typedef area */
#ifndef __KERNEL__ #ifndef __KERNEL__
typedef struct drm_clip_rect drm_clip_rect_t; typedef struct drm_clip_rect drm_clip_rect_t;
......
...@@ -426,6 +426,14 @@ struct drm_buf_entry { ...@@ -426,6 +426,14 @@ struct drm_buf_entry {
struct drm_freelist freelist; struct drm_freelist freelist;
}; };
/* Event queued up for userspace to read */
struct drm_pending_event {
struct drm_event *event;
struct list_head link;
struct drm_file *file_priv;
void (*destroy)(struct drm_pending_event *event);
};
/** File private data */ /** File private data */
struct drm_file { struct drm_file {
int authenticated; int authenticated;
...@@ -449,6 +457,10 @@ struct drm_file { ...@@ -449,6 +457,10 @@ struct drm_file {
struct drm_master *master; /* master this node is currently associated with struct drm_master *master; /* master this node is currently associated with
N.B. not always minor->master */ N.B. not always minor->master */
struct list_head fbs; struct list_head fbs;
wait_queue_head_t event_wait;
struct list_head event_list;
int event_space;
}; };
/** Wait queue */ /** Wait queue */
...@@ -900,6 +912,12 @@ struct drm_minor { ...@@ -900,6 +912,12 @@ struct drm_minor {
struct drm_mode_group mode_group; struct drm_mode_group mode_group;
}; };
struct drm_pending_vblank_event {
struct drm_pending_event base;
int pipe;
struct drm_event_vblank event;
};
/** /**
* DRM device structure. This structure represent a complete card that * DRM device structure. This structure represent a complete card that
* may contain multiple heads. * may contain multiple heads.
...@@ -999,6 +1017,12 @@ struct drm_device { ...@@ -999,6 +1017,12 @@ struct drm_device {
u32 max_vblank_count; /**< size of vblank counter register */ u32 max_vblank_count; /**< size of vblank counter register */
/**
* List of events
*/
struct list_head vblank_event_list;
spinlock_t event_lock;
/*@} */ /*@} */
cycles_t ctx_start; cycles_t ctx_start;
cycles_t lck_start; cycles_t lck_start;
...@@ -1135,6 +1159,8 @@ extern int drm_lastclose(struct drm_device *dev); ...@@ -1135,6 +1159,8 @@ extern int drm_lastclose(struct drm_device *dev);
extern int drm_open(struct inode *inode, struct file *filp); extern int drm_open(struct inode *inode, struct file *filp);
extern int drm_stub_open(struct inode *inode, struct file *filp); extern int drm_stub_open(struct inode *inode, struct file *filp);
extern int drm_fasync(int fd, struct file *filp, int on); extern int drm_fasync(int fd, struct file *filp, int on);
extern ssize_t drm_read(struct file *filp, char __user *buffer,
size_t count, loff_t *offset);
extern int drm_release(struct inode *inode, struct file *filp); extern int drm_release(struct inode *inode, struct file *filp);
/* Mapping support (drm_vm.h) */ /* Mapping support (drm_vm.h) */
......
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