OSDN Git Service

Comment new vblank routines and fixup several issues:
authorJesse Barnes <jbarnes@jbarnes-mobile.amr.corp.intel.com>
Thu, 14 Jun 2007 18:32:31 +0000 (11:32 -0700)
committerJesse Barnes <jbarnes@jbarnes-mobile.amr.corp.intel.com>
Thu, 14 Jun 2007 18:32:31 +0000 (11:32 -0700)
  - use correct refcount variable in get/put routines
  - extract counter update from drm_vblank_get
  - make signal handling callback per-crtc
  - update interrupt handling logic, drivers should use drm_handle_vblank
  - move wakeup and counter update logic to new drm_handle_vblank routine
  - fixup usage of get/put in light of counter update extraction
  - fix longstanding bug in signal code, update pending counter only
    *after* we're sure we'll setup signal handling

linux-core/drmP.h
linux-core/drm_irq.c
shared-core/drm.h
shared-core/i915_drv.h
shared-core/i915_irq.c

index c8b7225..b6cc7cb 100644 (file)
@@ -627,8 +627,49 @@ struct drm_driver {
        int (*kernel_context_switch) (struct drm_device * dev, int old,
                                      int new);
        void (*kernel_context_switch_unlock) (struct drm_device * dev);
+       /**
+        * get_vblank_counter - get raw hardware vblank counter
+        * @dev: DRM device
+        * @crtc: counter to fetch
+        *
+        * Driver callback for fetching a raw hardware vblank counter
+        * for @crtc.  If a device doesn't have a hardware counter, the
+        * driver can simply return the value of drm_vblank_count and
+        * make the enable_vblank() and disable_vblank() hooks into no-ops,
+        * leaving interrupts enabled at all times.
+        *
+        * Wraparound handling and loss of events due to modesetting is dealt
+        * with in the DRM core code.
+        *
+        * RETURNS
+        * Raw vblank counter value.
+        */
        u32 (*get_vblank_counter) (struct drm_device *dev, int crtc);
-       void (*enable_vblank) (struct drm_device *dev, int crtc);
+
+       /**
+        * enable_vblank - enable vblank interrupt events
+        * @dev: DRM device
+        * @crtc: which irq to enable
+        *
+        * Enable vblank interrupts for @crtc.  If the device doesn't have
+        * a hardware vblank counter, this routine should be a no-op, since
+        * interrupts will have to stay on to keep the count accurate.
+        *
+        * RETURNS
+        * Zero on success, appropriate errno if the given @crtc's vblank
+        * interrupt cannot be enabled.
+        */
+       int (*enable_vblank) (struct drm_device *dev, int crtc);
+
+       /**
+        * disable_vblank - disable vblank interrupt events
+        * @dev: DRM device
+        * @crtc: which irq to enable
+        *
+        * Disable vblank interrupts for @crtc.  If the device doesn't have
+        * a hardware vblank counter, this routine should be a no-op, since
+        * interrupts will have to stay on to keep the count accurate.
+        */
        void (*disable_vblank) (struct drm_device *dev, int crtc);
        int (*dri_library_name) (struct drm_device * dev, char * buf);
 
@@ -784,11 +825,11 @@ typedef struct drm_device {
        /*@{ */
 
        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) */
        spinlock_t vbl_lock;
        struct list_head *vbl_sigs;             /**< signal list to send on VBLANK */
-       atomic_t vbl_pending;           /* number of signals pending on all crtcs*/
-       atomic_t *vblank_usage;         /* number of users of vblank interrupts per crtc */
+       atomic_t vbl_signal_pending;    /* number of signals pending on all crtcs*/
+       atomic_t *vblank_refcount;      /* number of users of vblank interrupts per crtc */
        u32 *last_vblank;               /* protected by dev->vbl_lock, used */
                                        /* for wraparound handling */
        u32 *vblank_offset;             /* used to track how many vblanks */
@@ -1083,9 +1124,11 @@ extern int drm_vblank_init(drm_device_t *dev, int num_crtcs);
 extern int drm_wait_vblank(struct inode *inode, struct file *filp,
                           unsigned int cmd, unsigned long arg);
 extern int drm_vblank_wait(drm_device_t * dev, unsigned int *vbl_seq);
-extern void drm_vbl_send_signals(drm_device_t * dev);
 extern void drm_locked_tasklet(drm_device_t *dev, void(*func)(drm_device_t*));
-extern void drm_vblank_get(drm_device_t *dev, int crtc);
+extern u32 drm_vblank_count(drm_device_t *dev, int crtc);
+extern void drm_update_vblank_count(drm_device_t *dev, int crtc);
+extern void drm_handle_vblank(drm_device_t *dev, int crtc);
+extern int drm_vblank_get(drm_device_t *dev, int crtc);
 extern void drm_vblank_put(drm_device_t *dev, int crtc);
 
                                /* Modesetting support */
index 8125b75..7bdb01b 100644 (file)
@@ -83,7 +83,7 @@ int drm_vblank_init(drm_device_t *dev, int num_crtcs)
 
        init_waitqueue_head(&dev->vbl_queue);
        spin_lock_init(&dev->vbl_lock);
-       atomic_set(&dev->vbl_pending, 0);
+       atomic_set(&dev->vbl_signal_pending, 0);
        dev->num_crtcs = num_crtcs;
 
        dev->vbl_sigs = drm_alloc(sizeof(struct list_head) * num_crtcs,
@@ -91,14 +91,14 @@ int drm_vblank_init(drm_device_t *dev, int num_crtcs)
        if (!dev->vbl_sigs)
                goto err;
 
-       dev->vblank_count = drm_alloc(sizeof(atomic_t) * num_crtcs,
+       dev->_vblank_count = drm_alloc(sizeof(atomic_t) * num_crtcs,
                                      DRM_MEM_DRIVER);
-       if (!dev->vblank_count)
+       if (!dev->_vblank_count)
                goto err;
 
-       dev->vblank_usage = drm_alloc(sizeof(atomic_t) * num_crtcs,
-                                     DRM_MEM_DRIVER);
-       if (!dev->vblank_count)
+       dev->vblank_refcount = drm_alloc(sizeof(atomic_t) * num_crtcs,
+                                        DRM_MEM_DRIVER);
+       if (!dev->vblank_refcount)
                goto err;
 
        dev->last_vblank = drm_alloc(sizeof(u32) * num_crtcs,
@@ -119,24 +119,28 @@ int drm_vblank_init(drm_device_t *dev, int num_crtcs)
        /* Zero per-crtc vblank stuff */
        for (i = 0; i < num_crtcs; i++) {
                INIT_LIST_HEAD(&dev->vbl_sigs[i]);
-               atomic_set(&dev->vblank_count[i], 0);
-               atomic_set(&dev->vblank_usage[i], 0);
+               atomic_set(&dev->_vblank_count[i], 0);
+               atomic_set(&dev->vblank_refcount[i], 0);
                dev->last_vblank[i] = 0;
                dev->vblank_premodeset[i] = 0;
                dev->vblank_offset[i] = 0;
        }
 
-       ret = 0;
-       goto out;
+       return 0;
 
 err:
-       kfree(dev->vbl_sigs);
-       kfree(dev->vblank_count);
-       kfree(dev->vblank_usage);
-       kfree(dev->last_vblank);
-       kfree(dev->vblank_premodeset);
-       kfree(dev->vblank_offset);
-out:
+       drm_free(dev->vbl_sigs, sizeof(*dev->vbl_sigs) * num_crtcs,
+                DRM_MEM_DRIVER);
+       drm_free(dev->_vblank_count, sizeof(*dev->_vblank_count) * num_crtcs,
+                DRM_MEM_DRIVER);
+       drm_free(dev->vblank_refcount, sizeof(*dev->vblank_refcount) *
+                num_crtcs, DRM_MEM_DRIVER);
+       drm_free(dev->last_vblank, sizeof(*dev->last_vblank) * num_crtcs,
+                DRM_MEM_DRIVER);
+       drm_free(dev->vblank_premodeset, sizeof(*dev->vblank_premodeset) *
+                num_crtcs, DRM_MEM_DRIVER);
+       drm_free(dev->vblank_offset, sizeof(*dev->vblank_offset) * num_crtcs,
+                DRM_MEM_DRIVER);
        return ret;
 }
 EXPORT_SYMBOL(drm_vblank_init);
@@ -274,14 +278,37 @@ int drm_control(struct inode *inode, struct file *filp,
        }
 }
 
-void drm_vblank_get(drm_device_t *dev, int crtc)
+/**
+ * drm_vblank_count - retrieve "cooked" vblank counter value
+ * @dev: DRM device
+ * @crtc: which counter to retrieve
+ *
+ * Fetches the "cooked" vblank count value that represents the number of
+ * vblank events since the system was booted, including lost events due to
+ * modesetting activity.
+ */
+u32 drm_vblank_count(drm_device_t *dev, int crtc)
+{
+       return atomic_read(&dev->_vblank_count[crtc]) +
+               dev->vblank_offset[crtc];
+}
+EXPORT_SYMBOL(drm_vblank_count);
+
+/**
+ * drm_update_vblank_count - update the master vblank counter
+ * @dev: DRM device
+ * @crtc: counter to update
+ *
+ * Call back into the driver to update the appropriate vblank counter
+ * (specified by @crtc).  Deal with wraparound, if it occurred, and
+ * update the last read value so we can deal with wraparound on the next
+ * call if necessary.
+ */
+void drm_update_vblank_count(drm_device_t *dev, int crtc)
 {
        unsigned long irqflags;
        u32 cur_vblank, diff;
 
-       if (atomic_add_return(1, &dev->vblank_count[crtc]) != 1)
-               return;
-
        /*
         * Interrupts were disabled prior to this call, so deal with counter
         * wrap if needed.
@@ -301,18 +328,61 @@ void drm_vblank_get(drm_device_t *dev, int crtc)
        dev->last_vblank[crtc] = cur_vblank;
        spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
 
-       atomic_add(diff, &dev->vblank_count[crtc]);
-       dev->driver->enable_vblank(dev, crtc);
+       atomic_add(diff, &dev->_vblank_count[crtc]);
+}
+EXPORT_SYMBOL(drm_update_vblank_count);
+
+/**
+ * drm_vblank_get - get a reference count on vblank events
+ * @dev: DRM device
+ * @crtc: which CRTC to own
+ *
+ * Acquire a reference count on vblank events to avoid having them disabled
+ * while in use.  Note callers will probably want to update the master counter
+ * using drm_update_vblank_count() above before calling this routine so that
+ * wakeups occur on the right vblank event.
+ *
+ * RETURNS
+ * Zero on success, nonzero on failure.
+ */
+int drm_vblank_get(drm_device_t *dev, int crtc)
+{
+       int ret = 0;
+
+       /* Going from 0->1 means we have to enable interrupts again */
+       if (atomic_add_return(1, &dev->vblank_refcount[crtc]) == 1) {
+               ret = dev->driver->enable_vblank(dev, crtc);
+               if (ret)
+                       atomic_dec(&dev->vblank_refcount[crtc]);
+       }
+
+       return ret;
 }
 EXPORT_SYMBOL(drm_vblank_get);
 
+/**
+ * drm_vblank_put - give up ownership of vblank events
+ * @dev: DRM device
+ * @crtc: which counter to give up
+ *
+ * Release ownership of a given vblank counter, turning off interrupts
+ * if possible.
+ */
 void drm_vblank_put(drm_device_t *dev, int crtc)
 {
-       if (atomic_dec_and_test(&dev->vblank_count[crtc]))
+       /* Last user can disable interrupts */
+       if (atomic_dec_and_test(&dev->vblank_refcount[crtc]))
                dev->driver->disable_vblank(dev, crtc);
 }
 EXPORT_SYMBOL(drm_vblank_put);
 
+/**
+ * drm_modeset_ctl - handle vblank event counter changes across mode switch
+ * @DRM_IOCTL_ARGS: standard ioctl arguments
+ *
+ * Applications should call the %_DRM_PRE_MODESET and %_DRM_POST_MODESET
+ * ioctls around modesetting so that any lost vblank events are accounted for.
+ */
 int drm_modeset_ctl(DRM_IOCTL_ARGS)
 {
        drm_file_t *priv = filp->private_data;
@@ -401,8 +471,8 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
                                    DRIVER_IRQ_VBL2 : DRIVER_IRQ_VBL))
                return -EINVAL;
 
-       drm_vblank_get(dev, crtc);
-       seq = atomic_read(&dev->vblank_count[crtc]);
+       drm_update_vblank_count(dev, crtc);
+       seq = drm_vblank_count(dev, crtc);
 
        switch (vblwait.request.type & _DRM_VBLANK_TYPES_MASK) {
        case _DRM_VBLANK_RELATIVE:
@@ -437,28 +507,28 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
                                spin_unlock_irqrestore(&dev->vbl_lock,
                                                       irqflags);
                                vblwait.reply.sequence = seq;
-                               drm_vblank_put(dev, crtc);
                                goto done;
                        }
                }
 
-               if (atomic_read(&dev->vbl_pending) >= 100) {
+               if (atomic_read(&dev->vbl_signal_pending) >= 100) {
                        spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
-                       drm_vblank_put(dev, crtc);
                        return -EBUSY;
                }
 
                spin_unlock_irqrestore(&dev->vbl_lock, irqflags);
 
-               atomic_inc(&dev->vbl_pending);
-
                if (!
                    (vbl_sig =
                     drm_alloc(sizeof(drm_vbl_sig_t), DRM_MEM_DRIVER))) {
-                       drm_vblank_put(dev, crtc);
                        return -ENOMEM;
                }
 
+               ret = drm_vblank_get(dev, crtc);
+               if (ret)
+                       return ret;
+               atomic_inc(&dev->vbl_signal_pending);
+
                memset((void *)vbl_sig, 0, sizeof(*vbl_sig));
 
                vbl_sig->sequence = vblwait.request.sequence;
@@ -475,8 +545,11 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
        } else {
                unsigned long cur_vblank;
 
+               ret = drm_vblank_get(dev, crtc);
+               if (ret)
+                       return ret;
                DRM_WAIT_ON(ret, dev->vbl_queue, 3 * DRM_HZ,
-                           (((cur_vblank = atomic_read(&dev->vblank_count[crtc]))
+                           (((cur_vblank = drm_vblank_count(dev, crtc))
                              - seq) <= (1 << 23)));
                drm_vblank_put(dev, crtc);
                do_gettimeofday(&now);
@@ -495,42 +568,56 @@ int drm_wait_vblank(DRM_IOCTL_ARGS)
  * Send the VBLANK signals.
  *
  * \param dev DRM device.
+ * \param crtc CRTC where the vblank event occurred
  *
  * Sends a signal for each task in drm_device::vbl_sigs and empties the list.
  *
  * If a signal is not requested, then calls vblank_wait().
  */
-void drm_vbl_send_signals(drm_device_t * dev)
+static void drm_vbl_send_signals(drm_device_t * dev, int crtc)
 {
+       drm_vbl_sig_t *vbl_sig, *tmp;
+       struct list_head *vbl_sigs;
+       unsigned int vbl_seq;
        unsigned long flags;
-       int i;
 
        spin_lock_irqsave(&dev->vbl_lock, flags);
 
-       for (i = 0; i < dev->num_crtcs; i++) {
-               drm_vbl_sig_t *vbl_sig, *tmp;
-               struct list_head *vbl_sigs = &dev->vbl_sigs[i];
-               unsigned int vbl_seq = atomic_read(&dev->vblank_count[i]);
+       vbl_sigs = &dev->vbl_sigs[crtc];
+       vbl_seq = drm_vblank_count(dev, crtc);
 
-               list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) {
-                       if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
-                               vbl_sig->info.si_code = vbl_seq;
-                               send_sig_info(vbl_sig->info.si_signo,
-                                             &vbl_sig->info, vbl_sig->task);
+       list_for_each_entry_safe(vbl_sig, tmp, vbl_sigs, head) {
+           if ((vbl_seq - vbl_sig->sequence) <= (1 << 23)) {
+               vbl_sig->info.si_code = vbl_seq;
+               send_sig_info(vbl_sig->info.si_signo,
+                             &vbl_sig->info, vbl_sig->task);
 
-                               list_del(&vbl_sig->head);
+               list_del(&vbl_sig->head);
 
-                               drm_free(vbl_sig, sizeof(*vbl_sig),
-                                        DRM_MEM_DRIVER);
-                               atomic_dec(&dev->vbl_pending);
-                               drm_vblank_put(dev, i);
-                       }
-               }
+               drm_free(vbl_sig, sizeof(*vbl_sig),
+                        DRM_MEM_DRIVER);
+               atomic_dec(&dev->vbl_signal_pending);
+               drm_vblank_put(dev, crtc);
+           }
        }
 
        spin_unlock_irqrestore(&dev->vbl_lock, flags);
 }
-EXPORT_SYMBOL(drm_vbl_send_signals);
+
+/**
+ * drm_handle_vblank - handle a vblank event
+ * @dev: DRM device
+ * @crtc: where this event occurred
+ *
+ * Drivers should call this routine in their vblank interrupt handlers to
+ * update the vblank counter and send any signals that may be pending.
+ */
+void drm_handle_vblank(drm_device_t *dev, int crtc)
+{
+       drm_update_vblank_count(dev, crtc);
+       drm_vbl_send_signals(dev, crtc);
+}
+EXPORT_SYMBOL(drm_handle_vblank);
 
 /**
  * Tasklet wrapper function.
index 15081f9..3cd6d50 100644 (file)
@@ -599,7 +599,7 @@ typedef enum {
  */
 typedef struct drm_modeset_ctl {
        drm_modeset_ctl_cmd_t cmd;
-       unsigned long arg;
+       u64 arg;
 } drm_modeset_ctl_t;
 
 /**
@@ -968,7 +968,7 @@ typedef union drm_mm_init_arg{
 
 #define DRM_IOCTL_UPDATE_DRAW           DRM_IOW(0x3f, drm_update_draw_t)
 
-#define DRM_IOCTL_MODESET_CTL           DRM_IOW(0x40, drm_modeset_ctl_t)
+#define DRM_IOCTL_MODESET_CTL           DRM_IOW(0xa0, drm_modeset_ctl_t)
 
 /*@}*/
 
index 079e901..213759a 100644 (file)
@@ -171,7 +171,7 @@ extern int i915_emit_irq(drm_device_t * dev);
 extern void i915_user_irq_on(drm_i915_private_t *dev_priv);
 extern void i915_user_irq_off(drm_i915_private_t *dev_priv);
 extern int i915_vblank_swap(DRM_IOCTL_ARGS);
-extern void i915_enable_vblank(drm_device_t *dev, int crtc);
+extern int i915_enable_vblank(drm_device_t *dev, int crtc);
 extern void i915_disable_vblank(drm_device_t *dev, int crtc);
 extern u32 i915_get_vblank_counter(drm_device_t *dev, int crtc);
 
index 46b97e3..e91add9 100644 (file)
@@ -92,8 +92,7 @@ static void i915_vblank_tasklet(drm_device_t *dev)
        unsigned long irqflags;
        struct list_head *list, *tmp, hits, *hit;
        int nhits, nrects, slice[2], upper[2], lower[2], i, num_pages;
-       unsigned counter[2] = { atomic_read(&dev->vblank_count[0]),
-                               atomic_read(&dev->vblank_count[1]) };
+       unsigned counter[2];
        drm_drawable_info_t *drw;
        drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
        u32 cpp = dev_priv->cpp,  offsets[3];
@@ -105,6 +104,9 @@ static void i915_vblank_tasklet(drm_device_t *dev)
                          (cpp << 23) | (1 << 24);
        RING_LOCALS;
 
+       counter[0] = drm_vblank_count(dev, 0);
+       counter[1] = drm_vblank_count(dev, 1);
+
        DRM_DEBUG("\n");
 
        INIT_LIST_HEAD(&hits);
@@ -333,16 +335,17 @@ irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
 #endif
        }
 
+       /*
+        * Use drm_update_vblank_counter here to deal with potential lost
+        * interrupts
+        */
        if (temp & VSYNC_PIPEA_FLAG)
-               atomic_add(i915_get_vblank_counter(dev, 0),
-                          &dev->vblank_count[0]);
+               drm_handle_vblank(dev, 0);
        if (temp & VSYNC_PIPEB_FLAG)
-               atomic_add(i915_get_vblank_counter(dev, 1),
-                          &dev->vblank_count[1]);
+               drm_handle_vblank(dev, 1);
 
        if (temp & (VSYNC_PIPEA_FLAG | VSYNC_PIPEB_FLAG)) {
                DRM_WAKEUP(&dev->vbl_queue);
-               drm_vbl_send_signals(dev);
 
                if (dev_priv->swaps_pending > 0)
                        drm_locked_tasklet(dev, i915_vblank_tasklet);
@@ -477,12 +480,12 @@ int i915_irq_wait(DRM_IOCTL_ARGS)
        return i915_wait_irq(dev, irqwait.irq_seq);
 }
 
-void i915_enable_vblank(drm_device_t *dev, int crtc)
+int i915_enable_vblank(drm_device_t *dev, int crtc)
 {
        drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
 
-       if (crtc > dev_priv->vblank_pipe)
-               return;
+       if (dev_priv->vblank_pipe != (1 << crtc))
+               return -EINVAL;
        
        switch (crtc) {
        case 0:
@@ -498,6 +501,8 @@ void i915_enable_vblank(drm_device_t *dev, int crtc)
        }
 
        I915_WRITE16(I915REG_INT_ENABLE_R, dev_priv->irq_enable_reg);
+
+       return 0;
 }
 
 void i915_disable_vblank(drm_device_t *dev, int crtc)
@@ -597,6 +602,7 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
        unsigned int pipe, seqtype, curseq;
        unsigned long irqflags;
        struct list_head *list;
+       int ret;
 
        if (!dev_priv) {
                DRM_ERROR("%s called with no initialization\n", __func__);
@@ -637,8 +643,8 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
 
        spin_unlock_irqrestore(&dev->drw_lock, irqflags);
 
-       drm_vblank_get(dev, pipe);
-       curseq = atomic_read(&dev->vblank_count[pipe]);
+       drm_update_vblank_count(dev, pipe);
+       curseq = drm_vblank_count(dev, pipe);
 
        if (seqtype == _DRM_VBLANK_RELATIVE)
                swap.sequence += curseq;
@@ -648,7 +654,6 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
                        swap.sequence = curseq + 1;
                } else {
                        DRM_DEBUG("Missed target sequence\n");
-                       drm_vblank_put(dev, pipe);
                        return DRM_ERR(EINVAL);
                }
        }
@@ -669,7 +674,6 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
                                spin_unlock_irqrestore(&dev->drw_lock, irqflags);
                                DRM_DEBUG("Invalid drawable ID %d\n",
                                          swap.drawable);
-                               drm_vblank_put(dev, pipe);
                                return DRM_ERR(EINVAL);
                        }
 
@@ -677,7 +681,6 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
 
                        spin_unlock_irqrestore(&dev->drw_lock, irqflags);
 
-                       drm_vblank_put(dev, pipe);
                        return 0;
                }
        }
@@ -693,7 +696,6 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
                        vbl_swap->flip = (swap.seqtype & _DRM_VBLANK_FLIP);
                        spin_unlock_irqrestore(&dev_priv->swaps_lock, irqflags);
                        DRM_DEBUG("Already scheduled\n");
-                       drm_vblank_put(dev, pipe);
                        return 0;
                }
        }
@@ -702,7 +704,6 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
 
        if (dev_priv->swaps_pending >= 100) {
                DRM_DEBUG("Too many swaps queued\n");
-               drm_vblank_put(dev, pipe);
                return DRM_ERR(EBUSY);
        }
 
@@ -710,12 +711,15 @@ int i915_vblank_swap(DRM_IOCTL_ARGS)
 
        if (!vbl_swap) {
                DRM_ERROR("Failed to allocate memory to queue swap\n");
-               drm_vblank_put(dev, pipe);
                return DRM_ERR(ENOMEM);
        }
 
        DRM_DEBUG("\n");
 
+       ret = drm_vblank_get(dev, pipe);
+       if (ret)
+               return ret;
+
        vbl_swap->drw_id = swap.drawable;
        vbl_swap->pipe = pipe;
        vbl_swap->sequence = swap.sequence;