linux/drivers/isdn/mISDN/timerdev.c
<<
>>
Prefs
   1/*
   2 *
   3 * general timer device for using in ISDN stacks
   4 *
   5 * Author       Karsten Keil <kkeil@novell.com>
   6 *
   7 * Copyright 2008  by Karsten Keil <kkeil@novell.com>
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License version 2 as
  11 * published by the Free Software Foundation.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 */
  19
  20#include <linux/poll.h>
  21#include <linux/vmalloc.h>
  22#include <linux/slab.h>
  23#include <linux/timer.h>
  24#include <linux/miscdevice.h>
  25#include <linux/module.h>
  26#include <linux/mISDNif.h>
  27#include <linux/mutex.h>
  28#include "core.h"
  29
  30static DEFINE_MUTEX(mISDN_mutex);
  31static u_int    *debug;
  32
  33
  34struct mISDNtimerdev {
  35        int                     next_id;
  36        struct list_head        pending;
  37        struct list_head        expired;
  38        wait_queue_head_t       wait;
  39        u_int                   work;
  40        spinlock_t              lock; /* protect lists */
  41};
  42
  43struct mISDNtimer {
  44        struct list_head        list;
  45        struct  mISDNtimerdev   *dev;
  46        struct timer_list       tl;
  47        int                     id;
  48};
  49
  50static int
  51mISDN_open(struct inode *ino, struct file *filep)
  52{
  53        struct mISDNtimerdev    *dev;
  54
  55        if (*debug & DEBUG_TIMER)
  56                printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
  57        dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL);
  58        if (!dev)
  59                return -ENOMEM;
  60        dev->next_id = 1;
  61        INIT_LIST_HEAD(&dev->pending);
  62        INIT_LIST_HEAD(&dev->expired);
  63        spin_lock_init(&dev->lock);
  64        dev->work = 0;
  65        init_waitqueue_head(&dev->wait);
  66        filep->private_data = dev;
  67        return nonseekable_open(ino, filep);
  68}
  69
  70static int
  71mISDN_close(struct inode *ino, struct file *filep)
  72{
  73        struct mISDNtimerdev    *dev = filep->private_data;
  74        struct list_head        *list = &dev->pending;
  75        struct mISDNtimer       *timer, *next;
  76
  77        if (*debug & DEBUG_TIMER)
  78                printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
  79
  80        spin_lock_irq(&dev->lock);
  81        while (!list_empty(list)) {
  82                timer = list_first_entry(list, struct mISDNtimer, list);
  83                spin_unlock_irq(&dev->lock);
  84                del_timer_sync(&timer->tl);
  85                spin_lock_irq(&dev->lock);
  86                /* it might have been moved to ->expired */
  87                list_del(&timer->list);
  88                kfree(timer);
  89        }
  90        spin_unlock_irq(&dev->lock);
  91
  92        list_for_each_entry_safe(timer, next, &dev->expired, list) {
  93                kfree(timer);
  94        }
  95        kfree(dev);
  96        return 0;
  97}
  98
  99static ssize_t
 100mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
 101{
 102        struct mISDNtimerdev    *dev = filep->private_data;
 103        struct list_head *list = &dev->expired;
 104        struct mISDNtimer       *timer;
 105        int     ret = 0;
 106
 107        if (*debug & DEBUG_TIMER)
 108                printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
 109                       filep, buf, (int)count, off);
 110
 111        if (count < sizeof(int))
 112                return -ENOSPC;
 113
 114        spin_lock_irq(&dev->lock);
 115        while (list_empty(list) && (dev->work == 0)) {
 116                spin_unlock_irq(&dev->lock);
 117                if (filep->f_flags & O_NONBLOCK)
 118                        return -EAGAIN;
 119                wait_event_interruptible(dev->wait, (dev->work ||
 120                                                     !list_empty(list)));
 121                if (signal_pending(current))
 122                        return -ERESTARTSYS;
 123                spin_lock_irq(&dev->lock);
 124        }
 125        if (dev->work)
 126                dev->work = 0;
 127        if (!list_empty(list)) {
 128                timer = list_first_entry(list, struct mISDNtimer, list);
 129                list_del(&timer->list);
 130                spin_unlock_irq(&dev->lock);
 131                if (put_user(timer->id, (int __user *)buf))
 132                        ret = -EFAULT;
 133                else
 134                        ret = sizeof(int);
 135                kfree(timer);
 136        } else {
 137                spin_unlock_irq(&dev->lock);
 138        }
 139        return ret;
 140}
 141
 142static unsigned int
 143mISDN_poll(struct file *filep, poll_table *wait)
 144{
 145        struct mISDNtimerdev    *dev = filep->private_data;
 146        unsigned int            mask = POLLERR;
 147
 148        if (*debug & DEBUG_TIMER)
 149                printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait);
 150        if (dev) {
 151                poll_wait(filep, &dev->wait, wait);
 152                mask = 0;
 153                if (dev->work || !list_empty(&dev->expired))
 154                        mask |= (POLLIN | POLLRDNORM);
 155                if (*debug & DEBUG_TIMER)
 156                        printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__,
 157                               dev->work, list_empty(&dev->expired));
 158        }
 159        return mask;
 160}
 161
 162static void
 163dev_expire_timer(unsigned long data)
 164{
 165        struct mISDNtimer *timer = (void *)data;
 166        u_long                  flags;
 167
 168        spin_lock_irqsave(&timer->dev->lock, flags);
 169        if (timer->id >= 0)
 170                list_move_tail(&timer->list, &timer->dev->expired);
 171        spin_unlock_irqrestore(&timer->dev->lock, flags);
 172        wake_up_interruptible(&timer->dev->wait);
 173}
 174
 175static int
 176misdn_add_timer(struct mISDNtimerdev *dev, int timeout)
 177{
 178        int                     id;
 179        struct mISDNtimer       *timer;
 180
 181        if (!timeout) {
 182                dev->work = 1;
 183                wake_up_interruptible(&dev->wait);
 184                id = 0;
 185        } else {
 186                timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL);
 187                if (!timer)
 188                        return -ENOMEM;
 189                timer->dev = dev;
 190                setup_timer(&timer->tl, dev_expire_timer, (long)timer);
 191                spin_lock_irq(&dev->lock);
 192                id = timer->id = dev->next_id++;
 193                if (dev->next_id < 0)
 194                        dev->next_id = 1;
 195                list_add_tail(&timer->list, &dev->pending);
 196                timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000);
 197                add_timer(&timer->tl);
 198                spin_unlock_irq(&dev->lock);
 199        }
 200        return id;
 201}
 202
 203static int
 204misdn_del_timer(struct mISDNtimerdev *dev, int id)
 205{
 206        struct mISDNtimer       *timer;
 207
 208        spin_lock_irq(&dev->lock);
 209        list_for_each_entry(timer, &dev->pending, list) {
 210                if (timer->id == id) {
 211                        list_del_init(&timer->list);
 212                        timer->id = -1;
 213                        spin_unlock_irq(&dev->lock);
 214                        del_timer_sync(&timer->tl);
 215                        kfree(timer);
 216                        return id;
 217                }
 218        }
 219        spin_unlock_irq(&dev->lock);
 220        return 0;
 221}
 222
 223static long
 224mISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
 225{
 226        struct mISDNtimerdev    *dev = filep->private_data;
 227        int                     id, tout, ret = 0;
 228
 229
 230        if (*debug & DEBUG_TIMER)
 231                printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__,
 232                       filep, cmd, arg);
 233        mutex_lock(&mISDN_mutex);
 234        switch (cmd) {
 235        case IMADDTIMER:
 236                if (get_user(tout, (int __user *)arg)) {
 237                        ret = -EFAULT;
 238                        break;
 239                }
 240                id = misdn_add_timer(dev, tout);
 241                if (*debug & DEBUG_TIMER)
 242                        printk(KERN_DEBUG "%s add %d id %d\n", __func__,
 243                               tout, id);
 244                if (id < 0) {
 245                        ret = id;
 246                        break;
 247                }
 248                if (put_user(id, (int __user *)arg))
 249                        ret = -EFAULT;
 250                break;
 251        case IMDELTIMER:
 252                if (get_user(id, (int __user *)arg)) {
 253                        ret = -EFAULT;
 254                        break;
 255                }
 256                if (*debug & DEBUG_TIMER)
 257                        printk(KERN_DEBUG "%s del id %d\n", __func__, id);
 258                id = misdn_del_timer(dev, id);
 259                if (put_user(id, (int __user *)arg))
 260                        ret = -EFAULT;
 261                break;
 262        default:
 263                ret = -EINVAL;
 264        }
 265        mutex_unlock(&mISDN_mutex);
 266        return ret;
 267}
 268
 269static const struct file_operations mISDN_fops = {
 270        .owner          = THIS_MODULE,
 271        .read           = mISDN_read,
 272        .poll           = mISDN_poll,
 273        .unlocked_ioctl = mISDN_ioctl,
 274        .open           = mISDN_open,
 275        .release        = mISDN_close,
 276        .llseek         = no_llseek,
 277};
 278
 279static struct miscdevice mISDNtimer = {
 280        .minor  = MISC_DYNAMIC_MINOR,
 281        .name   = "mISDNtimer",
 282        .fops   = &mISDN_fops,
 283};
 284
 285int
 286mISDN_inittimer(u_int *deb)
 287{
 288        int     err;
 289
 290        debug = deb;
 291        err = misc_register(&mISDNtimer);
 292        if (err)
 293                printk(KERN_WARNING "mISDN: Could not register timer device\n");
 294        return err;
 295}
 296
 297void mISDN_timer_cleanup(void)
 298{
 299        misc_deregister(&mISDNtimer);
 300}
 301