2008-07-27 03:54:58 +04:00
/*
*
* general timer device for using in ISDN stacks
*
* Author Karsten Keil < kkeil @ novell . com >
*
* Copyright 2008 by Karsten Keil < kkeil @ novell . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/poll.h>
# include <linux/vmalloc.h>
# include <linux/timer.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/mISDNif.h>
static int * debug ;
struct mISDNtimerdev {
int next_id ;
struct list_head pending ;
struct list_head expired ;
wait_queue_head_t wait ;
u_int work ;
spinlock_t lock ; /* protect lists */
} ;
struct mISDNtimer {
struct list_head list ;
struct mISDNtimerdev * dev ;
struct timer_list tl ;
int id ;
} ;
static int
mISDN_open ( struct inode * ino , struct file * filep )
{
struct mISDNtimerdev * dev ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s(%p,%p) \n " , __func__ , ino , filep ) ;
dev = kmalloc ( sizeof ( struct mISDNtimerdev ) , GFP_KERNEL ) ;
if ( ! dev )
return - ENOMEM ;
dev - > next_id = 1 ;
INIT_LIST_HEAD ( & dev - > pending ) ;
INIT_LIST_HEAD ( & dev - > expired ) ;
spin_lock_init ( & dev - > lock ) ;
dev - > work = 0 ;
init_waitqueue_head ( & dev - > wait ) ;
filep - > private_data = dev ;
__module_get ( THIS_MODULE ) ;
2008-10-14 05:42:07 +04:00
return nonseekable_open ( ino , filep ) ;
2008-07-27 03:54:58 +04:00
}
static int
mISDN_close ( struct inode * ino , struct file * filep )
{
struct mISDNtimerdev * dev = filep - > private_data ;
struct mISDNtimer * timer , * next ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s(%p,%p) \n " , __func__ , ino , filep ) ;
list_for_each_entry_safe ( timer , next , & dev - > pending , list ) {
del_timer ( & timer - > tl ) ;
kfree ( timer ) ;
}
list_for_each_entry_safe ( timer , next , & dev - > expired , list ) {
kfree ( timer ) ;
}
kfree ( dev ) ;
module_put ( THIS_MODULE ) ;
return 0 ;
}
static ssize_t
mISDN_read ( struct file * filep , char * buf , size_t count , loff_t * off )
{
struct mISDNtimerdev * dev = filep - > private_data ;
struct mISDNtimer * timer ;
u_long flags ;
int ret = 0 ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s(%p, %p, %d, %p) \n " , __func__ ,
filep , buf , ( int ) count , off ) ;
if ( * off ! = filep - > f_pos )
return - ESPIPE ;
if ( list_empty ( & dev - > expired ) & & ( dev - > work = = 0 ) ) {
if ( filep - > f_flags & O_NONBLOCK )
return - EAGAIN ;
wait_event_interruptible ( dev - > wait , ( dev - > work | |
! list_empty ( & dev - > expired ) ) ) ;
if ( signal_pending ( current ) )
return - ERESTARTSYS ;
}
if ( count < sizeof ( int ) )
return - ENOSPC ;
if ( dev - > work )
dev - > work = 0 ;
if ( ! list_empty ( & dev - > expired ) ) {
spin_lock_irqsave ( & dev - > lock , flags ) ;
timer = ( struct mISDNtimer * ) dev - > expired . next ;
list_del ( & timer - > list ) ;
spin_unlock_irqrestore ( & dev - > lock , flags ) ;
if ( put_user ( timer - > id , ( int * ) buf ) )
ret = - EFAULT ;
else
ret = sizeof ( int ) ;
kfree ( timer ) ;
}
return ret ;
}
static unsigned int
mISDN_poll ( struct file * filep , poll_table * wait )
{
struct mISDNtimerdev * dev = filep - > private_data ;
unsigned int mask = POLLERR ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s(%p, %p) \n " , __func__ , filep , wait ) ;
if ( dev ) {
poll_wait ( filep , & dev - > wait , wait ) ;
mask = 0 ;
if ( dev - > work | | ! list_empty ( & dev - > expired ) )
mask | = ( POLLIN | POLLRDNORM ) ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s work(%d) empty(%d) \n " , __func__ ,
dev - > work , list_empty ( & dev - > expired ) ) ;
}
return mask ;
}
static void
2008-09-23 06:18:15 +04:00
dev_expire_timer ( unsigned long data )
2008-07-27 03:54:58 +04:00
{
2008-09-23 06:18:15 +04:00
struct mISDNtimer * timer = ( void * ) data ;
2008-07-27 03:54:58 +04:00
u_long flags ;
spin_lock_irqsave ( & timer - > dev - > lock , flags ) ;
list_del ( & timer - > list ) ;
list_add_tail ( & timer - > list , & timer - > dev - > expired ) ;
spin_unlock_irqrestore ( & timer - > dev - > lock , flags ) ;
wake_up_interruptible ( & timer - > dev - > wait ) ;
}
static int
misdn_add_timer ( struct mISDNtimerdev * dev , int timeout )
{
int id ;
u_long flags ;
struct mISDNtimer * timer ;
if ( ! timeout ) {
dev - > work = 1 ;
wake_up_interruptible ( & dev - > wait ) ;
id = 0 ;
} else {
timer = kzalloc ( sizeof ( struct mISDNtimer ) , GFP_KERNEL ) ;
if ( ! timer )
return - ENOMEM ;
spin_lock_irqsave ( & dev - > lock , flags ) ;
timer - > id = dev - > next_id + + ;
if ( dev - > next_id < 0 )
dev - > next_id = 1 ;
list_add_tail ( & timer - > list , & dev - > pending ) ;
spin_unlock_irqrestore ( & dev - > lock , flags ) ;
timer - > dev = dev ;
timer - > tl . data = ( long ) timer ;
2008-09-23 06:18:15 +04:00
timer - > tl . function = dev_expire_timer ;
2008-07-27 03:54:58 +04:00
init_timer ( & timer - > tl ) ;
timer - > tl . expires = jiffies + ( ( HZ * ( u_long ) timeout ) / 1000 ) ;
add_timer ( & timer - > tl ) ;
id = timer - > id ;
}
return id ;
}
static int
misdn_del_timer ( struct mISDNtimerdev * dev , int id )
{
u_long flags ;
struct mISDNtimer * timer ;
int ret = 0 ;
spin_lock_irqsave ( & dev - > lock , flags ) ;
list_for_each_entry ( timer , & dev - > pending , list ) {
if ( timer - > id = = id ) {
list_del_init ( & timer - > list ) ;
2008-09-23 06:18:15 +04:00
/* RED-PEN AK: race -- timer can be still running on
* other CPU . Needs reference count I think
*/
2008-07-27 03:54:58 +04:00
del_timer ( & timer - > tl ) ;
ret = timer - > id ;
kfree ( timer ) ;
goto unlock ;
}
}
unlock :
spin_unlock_irqrestore ( & dev - > lock , flags ) ;
return ret ;
}
static int
mISDN_ioctl ( struct inode * inode , struct file * filep , unsigned int cmd ,
unsigned long arg )
{
struct mISDNtimerdev * dev = filep - > private_data ;
int id , tout , ret = 0 ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s(%p, %x, %lx) \n " , __func__ ,
filep , cmd , arg ) ;
switch ( cmd ) {
case IMADDTIMER :
if ( get_user ( tout , ( int __user * ) arg ) ) {
ret = - EFAULT ;
break ;
}
id = misdn_add_timer ( dev , tout ) ;
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s add %d id %d \n " , __func__ ,
tout , id ) ;
if ( id < 0 ) {
ret = id ;
break ;
}
if ( put_user ( id , ( int __user * ) arg ) )
ret = - EFAULT ;
break ;
case IMDELTIMER :
if ( get_user ( id , ( int __user * ) arg ) ) {
ret = - EFAULT ;
break ;
}
if ( * debug & DEBUG_TIMER )
printk ( KERN_DEBUG " %s del id %d \n " , __func__ , id ) ;
id = misdn_del_timer ( dev , id ) ;
if ( put_user ( id , ( int __user * ) arg ) )
ret = - EFAULT ;
break ;
default :
ret = - EINVAL ;
}
return ret ;
}
static struct file_operations mISDN_fops = {
. read = mISDN_read ,
. poll = mISDN_poll ,
. ioctl = mISDN_ioctl ,
. open = mISDN_open ,
. release = mISDN_close ,
} ;
static struct miscdevice mISDNtimer = {
. minor = MISC_DYNAMIC_MINOR ,
. name = " mISDNtimer " ,
. fops = & mISDN_fops ,
} ;
int
mISDN_inittimer ( int * deb )
{
int err ;
debug = deb ;
err = misc_register ( & mISDNtimer ) ;
if ( err )
printk ( KERN_WARNING " mISDN: Could not register timer device \n " ) ;
return err ;
}
void mISDN_timer_cleanup ( void )
{
misc_deregister ( & mISDNtimer ) ;
}