diff options
-rw-r--r-- | linux-core/drmP.h | 55 | ||||
-rw-r--r-- | linux-core/drm_irq.c | 191 | ||||
-rw-r--r-- | shared-core/drm.h | 4 | ||||
-rw-r--r-- | shared-core/i915_drv.h | 2 | ||||
-rw-r--r-- | shared-core/i915_irq.c | 40 |
5 files changed, 213 insertions, 79 deletions
diff --git a/linux-core/drmP.h b/linux-core/drmP.h index c8b72257..b6cc7cb1 100644 --- a/linux-core/drmP.h +++ b/linux-core/drmP.h @@ -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 */ diff --git a/linux-core/drm_irq.c b/linux-core/drm_irq.c index 8125b75c..7bdb01b2 100644 --- a/linux-core/drm_irq.c +++ b/linux-core/drm_irq.c @@ -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. diff --git a/shared-core/drm.h b/shared-core/drm.h index 15081f92..3cd6d500 100644 --- a/shared-core/drm.h +++ b/shared-core/drm.h @@ -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) /*@}*/ diff --git a/shared-core/i915_drv.h b/shared-core/i915_drv.h index 079e9017..213759a8 100644 --- a/shared-core/i915_drv.h +++ b/shared-core/i915_drv.h @@ -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); diff --git a/shared-core/i915_irq.c b/shared-core/i915_irq.c index 46b97e39..e91add9d 100644 --- a/shared-core/i915_irq.c +++ b/shared-core/i915_irq.c @@ -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; |