2005-04-17 02:20:36 +04:00
/**
2005-09-25 08:28:13 +04:00
* \ file drm_irq . c
2005-04-17 02:20:36 +04:00
* IRQ support
*
* \ author Rickard E . ( Rik ) Faith < faith @ valinux . com >
* \ author Gareth Hughes < gareth @ valinux . com >
*/
/*
* Created : Fri Mar 19 14 : 30 : 16 1999 by faith @ valinux . com
*
* Copyright 1999 , 2000 Precision Insight , Inc . , Cedar Park , Texas .
* Copyright 2000 VA Linux Systems , Inc . , Sunnyvale , California .
* All Rights Reserved .
*
* Permission is hereby granted , free of charge , to any person obtaining a
* copy of this software and associated documentation files ( the " Software " ) ,
* to deal in the Software without restriction , including without limitation
* the rights to use , copy , modify , merge , publish , distribute , sublicense ,
* and / or sell copies of the Software , and to permit persons to whom the
* Software is furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice ( including the next
* paragraph ) shall be included in all copies or substantial portions of the
* Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
* VA LINUX SYSTEMS AND / OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM , DAMAGES OR
* OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE ,
* ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE .
*/
# include "drmP.h"
# include <linux/interrupt.h> /* For task queue support */
/**
* Get interrupt from bus id .
2005-09-25 08:28:13 +04:00
*
2005-04-17 02:20:36 +04:00
* \ param inode device inode .
* \ param filp file pointer .
* \ param cmd command .
* \ param arg user argument , pointing to a drm_irq_busid structure .
* \ return zero on success or a negative number on failure .
2005-09-25 08:28:13 +04:00
*
2005-04-17 02:20:36 +04:00
* Finds the PCI device with the specified bus id and gets its IRQ number .
* This IOCTL is deprecated , and will now return EINVAL for any busid not equal
* to that of the device that this DRM instance attached to .
*/
int drm_irq_by_busid ( struct inode * inode , struct file * filp ,
2005-09-25 08:28:13 +04:00
unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2007-07-11 09:53:27 +04:00
struct drm_file * priv = filp - > private_data ;
struct drm_device * dev = priv - > head - > dev ;
2007-07-11 09:27:12 +04:00
struct drm_irq_busid __user * argp = ( void __user * ) arg ;
struct drm_irq_busid p ;
2005-04-17 02:20:36 +04:00
if ( ! drm_core_check_feature ( dev , DRIVER_HAVE_IRQ ) )
return - EINVAL ;
if ( copy_from_user ( & p , argp , sizeof ( p ) ) )
return - EFAULT ;
2006-08-07 14:23:42 +04:00
if ( ( p . busnum > > 8 ) ! = drm_get_pci_domain ( dev ) | |
2006-07-17 22:01:01 +04:00
( p . busnum & 0xff ) ! = dev - > pdev - > bus - > number | |
p . devnum ! = PCI_SLOT ( dev - > pdev - > devfn ) | | p . funcnum ! = PCI_FUNC ( dev - > pdev - > devfn ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
p . irq = dev - > irq ;
2005-09-25 08:28:13 +04:00
DRM_DEBUG ( " %d:%d:%d => IRQ %d \n " , p . busnum , p . devnum , p . funcnum , p . irq ) ;
2005-04-17 02:20:36 +04:00
if ( copy_to_user ( argp , & p , sizeof ( p ) ) )
return - EFAULT ;
return 0 ;
}
/**
* Install IRQ handler .
*
* \ param dev DRM device .
* \ param irq IRQ number .
*
* Initializes the IRQ related data , and setups drm_device : : vbl_queue . Installs the handler , calling the driver
* \ c drm_driver_irq_preinstall ( ) and \ c drm_driver_irq_postinstall ( ) functions
* before and after the installation .
*/
2007-07-11 09:53:27 +04:00
static int drm_irq_install ( struct drm_device * dev )
2005-04-17 02:20:36 +04:00
{
int ret ;
2005-09-25 08:28:13 +04:00
unsigned long sh_flags = 0 ;
2005-04-17 02:20:36 +04:00
if ( ! drm_core_check_feature ( dev , DRIVER_HAVE_IRQ ) )
return - EINVAL ;
2005-09-25 08:28:13 +04:00
if ( dev - > irq = = 0 )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2006-02-02 11:37:46 +03:00
mutex_lock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
/* Driver must have been initialized */
2005-09-25 08:28:13 +04:00
if ( ! dev - > dev_private ) {
2006-02-02 11:37:46 +03:00
mutex_unlock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2005-09-25 08:28:13 +04:00
if ( dev - > irq_enabled ) {
2006-02-02 11:37:46 +03:00
mutex_unlock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
dev - > irq_enabled = 1 ;
2006-02-02 11:37:46 +03:00
mutex_unlock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
DRM_DEBUG ( " %s: irq=%d \n " , __FUNCTION__ , dev - > irq ) ;
2005-04-17 02:20:36 +04:00
if ( drm_core_check_feature ( dev , DRIVER_IRQ_VBL ) ) {
init_waitqueue_head ( & dev - > vbl_queue ) ;
2005-09-25 08:28:13 +04:00
spin_lock_init ( & dev - > vbl_lock ) ;
2007-05-25 23:01:51 +04:00
INIT_LIST_HEAD ( & dev - > vbl_sigs ) ;
INIT_LIST_HEAD ( & dev - > vbl_sigs2 ) ;
2005-09-25 08:28:13 +04:00
2005-04-17 02:20:36 +04:00
dev - > vbl_pending = 0 ;
}
2005-09-25 08:28:13 +04:00
/* Before installing handler */
2005-04-17 02:20:36 +04:00
dev - > driver - > irq_preinstall ( dev ) ;
2005-09-25 08:28:13 +04:00
/* Install handler */
2005-04-17 02:20:36 +04:00
if ( drm_core_check_feature ( dev , DRIVER_IRQ_SHARED ) )
2006-07-02 06:29:34 +04:00
sh_flags = IRQF_SHARED ;
2005-09-25 08:28:13 +04:00
ret = request_irq ( dev - > irq , dev - > driver - > irq_handler ,
sh_flags , dev - > devname , dev ) ;
if ( ret < 0 ) {
2006-02-02 11:37:46 +03:00
mutex_lock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
dev - > irq_enabled = 0 ;
2006-02-02 11:37:46 +03:00
mutex_unlock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
2005-09-25 08:28:13 +04:00
/* After installing handler */
2005-04-17 02:20:36 +04:00
dev - > driver - > irq_postinstall ( dev ) ;
return 0 ;
}
/**
* Uninstall the IRQ handler .
*
* \ param dev DRM device .
*
* Calls the driver ' s \ c drm_driver_irq_uninstall ( ) function , and stops the irq .
*/
2007-07-11 09:53:27 +04:00
int drm_irq_uninstall ( struct drm_device * dev )
2005-04-17 02:20:36 +04:00
{
int irq_enabled ;
if ( ! drm_core_check_feature ( dev , DRIVER_HAVE_IRQ ) )
return - EINVAL ;
2006-02-02 11:37:46 +03:00
mutex_lock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
irq_enabled = dev - > irq_enabled ;
dev - > irq_enabled = 0 ;
2006-02-02 11:37:46 +03:00
mutex_unlock ( & dev - > struct_mutex ) ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
if ( ! irq_enabled )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2005-09-25 08:28:13 +04:00
DRM_DEBUG ( " %s: irq=%d \n " , __FUNCTION__ , dev - > irq ) ;
2005-04-17 02:20:36 +04:00
dev - > driver - > irq_uninstall ( dev ) ;
2005-09-25 08:28:13 +04:00
free_irq ( dev - > irq , dev ) ;
2005-04-17 02:20:36 +04:00
2006-10-24 17:08:16 +04:00
dev - > locked_tasklet_func = NULL ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-09-25 08:28:13 +04:00
2005-04-17 02:20:36 +04:00
EXPORT_SYMBOL ( drm_irq_uninstall ) ;
/**
* IRQ control ioctl .
*
* \ param inode device inode .
* \ param filp file pointer .
* \ param cmd command .
* \ param arg user argument , pointing to a drm_control structure .
* \ return zero on success or a negative number on failure .
*
* Calls irq_install ( ) or irq_uninstall ( ) according to \ p arg .
*/
2005-09-25 08:28:13 +04:00
int drm_control ( struct inode * inode , struct file * filp ,
unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2007-07-11 09:53:27 +04:00
struct drm_file * priv = filp - > private_data ;
struct drm_device * dev = priv - > head - > dev ;
2007-07-11 09:27:12 +04:00
struct drm_control ctl ;
2005-09-25 08:28:13 +04:00
2005-04-17 02:20:36 +04:00
/* if we haven't irq we fallback for compatibility reasons - this used to be a separate function in drm_dma.h */
2007-07-11 09:27:12 +04:00
if ( copy_from_user ( & ctl , ( struct drm_control __user * ) arg , sizeof ( ctl ) ) )
2005-04-17 02:20:36 +04:00
return - EFAULT ;
2005-09-25 08:28:13 +04:00
switch ( ctl . func ) {
2005-04-17 02:20:36 +04:00
case DRM_INST_HANDLER :
if ( ! drm_core_check_feature ( dev , DRIVER_HAVE_IRQ ) )
return 0 ;
if ( dev - > if_version < DRM_IF_VERSION ( 1 , 2 ) & &
ctl . irq ! = dev - > irq )
return - EINVAL ;
2005-09-25 08:28:13 +04:00
return drm_irq_install ( dev ) ;
2005-04-17 02:20:36 +04:00
case DRM_UNINST_HANDLER :
if ( ! drm_core_check_feature ( dev , DRIVER_HAVE_IRQ ) )
return 0 ;
2005-09-25 08:28:13 +04:00
return drm_irq_uninstall ( dev ) ;
2005-04-17 02:20:36 +04:00
default :
return - EINVAL ;
}
}
/**
* Wait for VBLANK .
*
* \ param inode device inode .
* \ param filp file pointer .
* \ param cmd command .
* \ param data user argument , pointing to a drm_wait_vblank structure .
* \ return zero on success or a negative number on failure .
*
2005-09-25 08:28:13 +04:00
* Verifies the IRQ is installed .
2005-04-17 02:20:36 +04:00
*
* If a signal is requested checks if this task has already scheduled the same signal
* for the same vblank sequence number - nothing to be done in
* that case . If the number of tasks waiting for the interrupt exceeds 100 the
* function fails . Otherwise adds a new entry to drm_device : : vbl_sigs for this
* task .
*
* If a signal is not requested , then calls vblank_wait ( ) .
*/
2005-09-25 08:28:13 +04:00
int drm_wait_vblank ( DRM_IOCTL_ARGS )
2005-04-17 02:20:36 +04:00
{
2007-07-11 09:53:27 +04:00
struct drm_file * priv = filp - > private_data ;
struct drm_device * dev = priv - > head - > dev ;
2007-07-11 09:27:12 +04:00
union drm_wait_vblank __user * argp = ( void __user * ) data ;
union drm_wait_vblank vblwait ;
2005-04-17 02:20:36 +04:00
struct timeval now ;
int ret = 0 ;
2006-10-24 17:34:18 +04:00
unsigned int flags , seq ;
2005-04-17 02:20:36 +04:00
if ( ! dev - > irq )
return - EINVAL ;
2006-08-07 14:07:43 +04:00
if ( copy_from_user ( & vblwait , argp , sizeof ( vblwait ) ) )
return - EFAULT ;
2005-04-17 02:20:36 +04:00
2006-10-24 16:24:38 +04:00
if ( vblwait . request . type &
~ ( _DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK ) ) {
DRM_ERROR ( " Unsupported type value 0x%x, supported mask 0x%x \n " ,
vblwait . request . type ,
( _DRM_VBLANK_TYPES_MASK | _DRM_VBLANK_FLAGS_MASK ) ) ;
return - EINVAL ;
}
flags = vblwait . request . type & _DRM_VBLANK_FLAGS_MASK ;
if ( ! drm_core_check_feature ( dev , ( flags & _DRM_VBLANK_SECONDARY ) ?
DRIVER_IRQ_VBL2 : DRIVER_IRQ_VBL ) )
return - EINVAL ;
2006-10-24 17:34:18 +04:00
seq = atomic_read ( ( flags & _DRM_VBLANK_SECONDARY ) ? & dev - > vbl_received2
: & dev - > vbl_received ) ;
2006-10-24 16:24:38 +04:00
switch ( vblwait . request . type & _DRM_VBLANK_TYPES_MASK ) {
2005-04-17 02:20:36 +04:00
case _DRM_VBLANK_RELATIVE :
2006-10-24 17:34:18 +04:00
vblwait . request . sequence + = seq ;
2005-04-17 02:20:36 +04:00
vblwait . request . type & = ~ _DRM_VBLANK_RELATIVE ;
case _DRM_VBLANK_ABSOLUTE :
break ;
default :
return - EINVAL ;
}
2006-10-24 17:34:18 +04:00
if ( ( flags & _DRM_VBLANK_NEXTONMISS ) & &
( seq - vblwait . request . sequence ) < = ( 1 < < 23 ) ) {
vblwait . request . sequence = seq + 1 ;
}
2005-09-25 08:28:13 +04:00
if ( flags & _DRM_VBLANK_SIGNAL ) {
2005-04-17 02:20:36 +04:00
unsigned long irqflags ;
2007-05-25 23:01:51 +04:00
struct list_head * vbl_sigs = ( flags & _DRM_VBLANK_SECONDARY )
2006-10-24 16:24:38 +04:00
? & dev - > vbl_sigs2 : & dev - > vbl_sigs ;
2007-07-11 10:53:40 +04:00
struct drm_vbl_sig * vbl_sig ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
spin_lock_irqsave ( & dev - > vbl_lock , irqflags ) ;
2005-04-17 02:20:36 +04:00
/* Check if this task has already scheduled the same signal
* for the same vblank sequence number ; nothing to be done in
* that case
*/
2007-05-25 23:01:51 +04:00
list_for_each_entry ( vbl_sig , vbl_sigs , head ) {
2005-04-17 02:20:36 +04:00
if ( vbl_sig - > sequence = = vblwait . request . sequence
& & vbl_sig - > info . si_signo = = vblwait . request . signal
2005-09-25 08:28:13 +04:00
& & vbl_sig - > task = = current ) {
spin_unlock_irqrestore ( & dev - > vbl_lock ,
irqflags ) ;
2006-10-24 17:34:58 +04:00
vblwait . reply . sequence = seq ;
2005-04-17 02:20:36 +04:00
goto done ;
}
}
2005-09-25 08:28:13 +04:00
if ( dev - > vbl_pending > = 100 ) {
spin_unlock_irqrestore ( & dev - > vbl_lock , irqflags ) ;
2005-04-17 02:20:36 +04:00
return - EBUSY ;
}
dev - > vbl_pending + + ;
2005-09-25 08:28:13 +04:00
spin_unlock_irqrestore ( & dev - > vbl_lock , irqflags ) ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
if ( !
( vbl_sig =
2007-07-11 10:53:40 +04:00
drm_alloc ( sizeof ( struct drm_vbl_sig ) , DRM_MEM_DRIVER ) ) ) {
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
}
2005-09-25 08:28:13 +04:00
memset ( ( void * ) vbl_sig , 0 , sizeof ( * vbl_sig ) ) ;
2005-04-17 02:20:36 +04:00
vbl_sig - > sequence = vblwait . request . sequence ;
vbl_sig - > info . si_signo = vblwait . request . signal ;
vbl_sig - > task = current ;
2005-09-25 08:28:13 +04:00
spin_lock_irqsave ( & dev - > vbl_lock , irqflags ) ;
2005-04-17 02:20:36 +04:00
2007-05-25 23:01:51 +04:00
list_add_tail ( & vbl_sig - > head , vbl_sigs ) ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
spin_unlock_irqrestore ( & dev - > vbl_lock , irqflags ) ;
2006-10-24 17:34:58 +04:00
vblwait . reply . sequence = seq ;
2005-04-17 02:20:36 +04:00
} else {
2006-10-24 16:24:38 +04:00
if ( flags & _DRM_VBLANK_SECONDARY ) {
if ( dev - > driver - > vblank_wait2 )
ret = dev - > driver - > vblank_wait2 ( dev , & vblwait . request . sequence ) ;
} else if ( dev - > driver - > vblank_wait )
2005-09-25 08:28:13 +04:00
ret =
dev - > driver - > vblank_wait ( dev ,
& vblwait . request . sequence ) ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
do_gettimeofday ( & now ) ;
2005-04-17 02:20:36 +04:00
vblwait . reply . tval_sec = now . tv_sec ;
vblwait . reply . tval_usec = now . tv_usec ;
}
2005-09-25 08:28:13 +04:00
done :
2006-08-07 14:07:43 +04:00
if ( copy_to_user ( argp , & vblwait , sizeof ( vblwait ) ) )
return - EFAULT ;
2005-04-17 02:20:36 +04:00
return ret ;
}
/**
* Send the VBLANK signals .
*
* \ param dev DRM device .
*
* 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 ( ) .
*/
2007-07-11 09:53:27 +04:00
void drm_vbl_send_signals ( struct drm_device * dev )
2005-04-17 02:20:36 +04:00
{
unsigned long flags ;
2006-10-24 16:24:38 +04:00
int i ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
spin_lock_irqsave ( & dev - > vbl_lock , flags ) ;
2005-04-17 02:20:36 +04:00
2006-10-24 16:24:38 +04:00
for ( i = 0 ; i < 2 ; i + + ) {
2007-07-11 10:53:40 +04:00
struct drm_vbl_sig * vbl_sig , * tmp ;
2007-05-25 23:01:51 +04:00
struct list_head * vbl_sigs = i ? & dev - > vbl_sigs2 : & dev - > vbl_sigs ;
2006-10-24 16:24:38 +04:00
unsigned int vbl_seq = atomic_read ( i ? & dev - > vbl_received2 :
& dev - > vbl_received ) ;
2007-05-25 23:01:51 +04:00
list_for_each_entry_safe ( vbl_sig , tmp , vbl_sigs , head ) {
2006-10-24 16:24:38 +04:00
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 ) ;
2005-04-17 02:20:36 +04:00
2007-05-25 23:01:51 +04:00
list_del ( & vbl_sig - > head ) ;
2005-04-17 02:20:36 +04:00
2006-10-24 16:24:38 +04:00
drm_free ( vbl_sig , sizeof ( * vbl_sig ) ,
DRM_MEM_DRIVER ) ;
2005-04-17 02:20:36 +04:00
2006-10-24 16:24:38 +04:00
dev - > vbl_pending - - ;
}
2005-04-17 02:20:36 +04:00
}
}
2005-09-25 08:28:13 +04:00
spin_unlock_irqrestore ( & dev - > vbl_lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
2005-09-25 08:28:13 +04:00
EXPORT_SYMBOL ( drm_vbl_send_signals ) ;
2006-10-24 17:08:16 +04:00
/**
* 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 )
{
2007-07-11 09:53:27 +04:00
struct drm_device * dev = ( struct drm_device * ) data ;
2006-10-24 17:08:16 +04:00
unsigned long irqflags ;
spin_lock_irqsave ( & dev - > tasklet_lock , irqflags ) ;
if ( ! dev - > locked_tasklet_func | |
2007-03-23 05:28:33 +03:00
! drm_lock_take ( & dev - > lock ,
2006-10-24 17:08:16 +04:00
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 ) ;
2007-03-23 05:28:33 +03:00
drm_lock_free ( & dev - > lock ,
2006-10-24 17:08:16 +04:00
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 .
*/
2007-07-11 09:53:27 +04:00
void drm_locked_tasklet ( struct drm_device * dev , void ( * func ) ( struct drm_device * ) )
2006-10-24 17:08:16 +04:00
{
unsigned long irqflags ;
static DECLARE_TASKLET ( drm_tasklet , drm_locked_tasklet_func , 0 ) ;
2006-10-24 17:30:01 +04:00
if ( ! drm_core_check_feature ( dev , DRIVER_HAVE_IRQ ) | |
test_bit ( TASKLET_STATE_SCHED , & drm_tasklet . state ) )
2006-10-24 17:08:16 +04:00
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 ) ;