Commit 2e54a007 authored by =?utf-8?q?Michel_D=C3=A4nzer?='s avatar =?utf-8?q?Michel_D=C3=A4nzer?= Committed by airlied

drm: Add support for interrupt triggered driver callback with lock held to DRM core.

Signed-off-by: default avatarDave Airlie <airlied@linux.ie>
parent bea5679f
...@@ -715,6 +715,8 @@ typedef struct drm_device { ...@@ -715,6 +715,8 @@ typedef struct drm_device {
drm_vbl_sig_t vbl_sigs; /**< signal list to send on VBLANK */ drm_vbl_sig_t vbl_sigs; /**< signal list to send on VBLANK */
drm_vbl_sig_t vbl_sigs2; /**< signals to send on secondary VBLANK */ drm_vbl_sig_t vbl_sigs2; /**< signals to send on secondary VBLANK */
unsigned int vbl_pending; unsigned int vbl_pending;
spinlock_t tasklet_lock; /**< For drm_locked_tasklet */
void (*locked_tasklet_func)(struct drm_device *dev);
/*@} */ /*@} */
cycles_t ctx_start; cycles_t ctx_start;
...@@ -966,6 +968,7 @@ extern int drm_wait_vblank(struct inode *inode, struct file *filp, ...@@ -966,6 +968,7 @@ extern int drm_wait_vblank(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg); unsigned int cmd, unsigned long arg);
extern int drm_vblank_wait(drm_device_t * dev, unsigned int *vbl_seq); 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_vbl_send_signals(drm_device_t * dev);
extern void drm_locked_tasklet(drm_device_t *dev, void(*func)(drm_device_t*));
/* AGP/GART support (drm_agpsupport.h) */ /* AGP/GART support (drm_agpsupport.h) */
extern drm_agp_head_t *drm_agp_init(drm_device_t * dev); extern drm_agp_head_t *drm_agp_init(drm_device_t * dev);
......
...@@ -119,6 +119,7 @@ static int drm_irq_install(drm_device_t * dev) ...@@ -119,6 +119,7 @@ static int drm_irq_install(drm_device_t * dev)
init_waitqueue_head(&dev->vbl_queue); init_waitqueue_head(&dev->vbl_queue);
spin_lock_init(&dev->vbl_lock); spin_lock_init(&dev->vbl_lock);
spin_lock_init(&dev->tasklet_lock);
INIT_LIST_HEAD(&dev->vbl_sigs.head); INIT_LIST_HEAD(&dev->vbl_sigs.head);
INIT_LIST_HEAD(&dev->vbl_sigs2.head); INIT_LIST_HEAD(&dev->vbl_sigs2.head);
...@@ -176,6 +177,8 @@ int drm_irq_uninstall(drm_device_t * dev) ...@@ -176,6 +177,8 @@ int drm_irq_uninstall(drm_device_t * dev)
free_irq(dev->irq, dev); free_irq(dev->irq, dev);
dev->locked_tasklet_func = NULL;
return 0; return 0;
} }
...@@ -399,3 +402,76 @@ void drm_vbl_send_signals(drm_device_t * dev) ...@@ -399,3 +402,76 @@ void drm_vbl_send_signals(drm_device_t * dev)
} }
EXPORT_SYMBOL(drm_vbl_send_signals); EXPORT_SYMBOL(drm_vbl_send_signals);
/**
* Tasklet wrapper function.
*
* \param data DRM device in disguise.
*
* Attempts to grab the HW lock and calls the driver callback on success. On
* failure, leave the lock marked as contended so the callback can be called
* from drm_unlock().
*/
static void drm_locked_tasklet_func(unsigned long data)
{
drm_device_t *dev = (drm_device_t*)data;
unsigned long irqflags;
spin_lock_irqsave(&dev->tasklet_lock, irqflags);
if (!dev->locked_tasklet_func ||
!drm_lock_take(&dev->lock.hw_lock->lock,
DRM_KERNEL_CONTEXT)) {
spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
return;
}
dev->lock.lock_time = jiffies;
atomic_inc(&dev->counts[_DRM_STAT_LOCKS]);
dev->locked_tasklet_func(dev);
drm_lock_free(dev, &dev->lock.hw_lock->lock,
DRM_KERNEL_CONTEXT);
dev->locked_tasklet_func = NULL;
spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
}
/**
* Schedule a tasklet to call back a driver hook with the HW lock held.
*
* \param dev DRM device.
* \param func Driver callback.
*
* This is intended for triggering actions that require the HW lock from an
* interrupt handler. The lock will be grabbed ASAP after the interrupt handler
* completes. Note that the callback may be called from interrupt or process
* context, it must not make any assumptions about this. Also, the HW lock will
* be held with the kernel context or any client context.
*/
void drm_locked_tasklet(drm_device_t *dev, void (*func)(drm_device_t*))
{
unsigned long irqflags;
static DECLARE_TASKLET(drm_tasklet, drm_locked_tasklet_func, 0);
if (test_bit(TASKLET_STATE_SCHED, &drm_tasklet.state))
return;
spin_lock_irqsave(&dev->tasklet_lock, irqflags);
if (dev->locked_tasklet_func) {
spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
return;
}
dev->locked_tasklet_func = func;
spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
drm_tasklet.data = (unsigned long)dev;
tasklet_hi_schedule(&drm_tasklet);
}
EXPORT_SYMBOL(drm_locked_tasklet);
...@@ -155,6 +155,7 @@ int drm_unlock(struct inode *inode, struct file *filp, ...@@ -155,6 +155,7 @@ int drm_unlock(struct inode *inode, struct file *filp,
drm_file_t *priv = filp->private_data; drm_file_t *priv = filp->private_data;
drm_device_t *dev = priv->head->dev; drm_device_t *dev = priv->head->dev;
drm_lock_t lock; drm_lock_t lock;
unsigned int irqflags;
if (copy_from_user(&lock, (drm_lock_t __user *) arg, sizeof(lock))) if (copy_from_user(&lock, (drm_lock_t __user *) arg, sizeof(lock)))
return -EFAULT; return -EFAULT;
...@@ -165,6 +166,16 @@ int drm_unlock(struct inode *inode, struct file *filp, ...@@ -165,6 +166,16 @@ int drm_unlock(struct inode *inode, struct file *filp,
return -EINVAL; return -EINVAL;
} }
spin_lock_irqsave(&dev->tasklet_lock, irqflags);
if (dev->locked_tasklet_func) {
dev->locked_tasklet_func(dev);
dev->locked_tasklet_func = NULL;
}
spin_unlock_irqrestore(&dev->tasklet_lock, irqflags);
atomic_inc(&dev->counts[_DRM_STAT_UNLOCKS]); atomic_inc(&dev->counts[_DRM_STAT_UNLOCKS]);
/* kernel_context_switch isn't used by any of the x86 drm /* kernel_context_switch isn't used by any of the x86 drm
......
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