INIT_LIST_HEAD(&priv->lhead);
        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)
                drm_gem_open(dev, priv);
        }
 }
 
+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.
  *
        if (file_priv->minor->master)
                drm_master_release(dev, filp);
 
+       drm_events_release(file_priv);
+
        if (dev->driver->driver_features & DRIVER_GEM)
                drm_gem_release(dev, file_priv);
 
 }
 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)
 {
-       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);
 
        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.
  *
                goto done;
        }
 
+       if (flags & _DRM_VBLANK_EVENT)
+               return drm_queue_vblank_event(dev, crtc, vblwait, file_priv);
+
        if ((flags & _DRM_VBLANK_NEXTONMISS) &&
            (seq - vblwait->request.sequence) <= (1<<23)) {
                vblwait->request.sequence = seq + 1;
        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
  * @dev: DRM device
  */
 void drm_handle_vblank(struct drm_device *dev, int crtc)
 {
+       if (!dev->num_crtcs)
+               return;
+
        atomic_inc(&dev->_vblank_count[crtc]);
        DRM_WAKEUP(&dev->vbl_queue[crtc]);
+       drm_handle_vblank_events(dev, crtc);
 }
 EXPORT_SYMBOL(drm_handle_vblank);
 
        INIT_LIST_HEAD(&dev->ctxlist);
        INIT_LIST_HEAD(&dev->vmalist);
        INIT_LIST_HEAD(&dev->maplist);
+       INIT_LIST_HEAD(&dev->vblank_event_list);
 
        spin_lock_init(&dev->count_lock);
        spin_lock_init(&dev->drw_lock);
+       spin_lock_init(&dev->event_lock);
        init_timer(&dev->timer);
        mutex_init(&dev->struct_mutex);
        mutex_init(&dev->ctxlist_mutex);
 
                 .mmap = drm_gem_mmap,
                 .poll = drm_poll,
                 .fasync = drm_fasync,
+                .read = drm_read,
 #ifdef CONFIG_COMPAT
                 .compat_ioctl = i915_compat_ioctl,
 #endif
 
 enum drm_vblank_seq_type {
        _DRM_VBLANK_ABSOLUTE = 0x0,     /**< Wait for specific vblank sequence number */
        _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_NEXTONMISS = 0x10000000,    /**< If missed, wait for next vblank */
        _DRM_VBLANK_SECONDARY = 0x20000000,     /**< Secondary display controller */
 };
 
 #define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE)
-#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_SIGNAL | _DRM_VBLANK_SECONDARY | \
-                               _DRM_VBLANK_NEXTONMISS)
+#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_EVENT | _DRM_VBLANK_SIGNAL | \
+                               _DRM_VBLANK_SECONDARY | _DRM_VBLANK_NEXTONMISS)
 
 struct drm_wait_vblank_request {
        enum drm_vblank_seq_type type;
 #define DRM_COMMAND_BASE                0x40
 #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 */
 #ifndef __KERNEL__
 typedef struct drm_clip_rect drm_clip_rect_t;
 
        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 */
 struct drm_file {
        int authenticated;
        struct drm_master *master; /* master this node is currently associated with
                                      N.B. not always minor->master */
        struct list_head fbs;
+
+       wait_queue_head_t event_wait;
+       struct list_head event_list;
+       int event_space;
 };
 
 /** Wait queue */
        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
  * may contain multiple heads.
 
        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 lck_start;
 extern int drm_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 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);
 
                                /* Mapping support (drm_vm.h) */