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        __module_get(THIS_MODULE);
  68        return nonseekable_open(ino, filep);
  69}
  70
  71static int
  72mISDN_close(struct inode *ino, struct file *filep)
  73{
  74        struct mISDNtimerdev    *dev = filep->private_data;
  75        struct mISDNtimer       *timer, *next;
  76
  77        if (*debug & DEBUG_TIMER)
  78                printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
  79        list_for_each_entry_safe(timer, next, &dev->pending, list) {
  80                del_timer(&timer->tl);
  81                kfree(timer);
  82        }
  83        list_for_each_entry_safe(timer, next, &dev->expired, list) {
  84                kfree(timer);
  85        }
  86        kfree(dev);
  87        module_put(THIS_MODULE);
  88        return 0;
  89}
  90
  91static ssize_t
  92mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
  93{
  94        struct mISDNtimerdev    *dev = filep->private_data;
  95        struct mISDNtimer       *timer;
  96        u_long  flags;
  97        int     ret = 0;
  98
  99        if (*debug & DEBUG_TIMER)
 100                printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
 101                       filep, buf, (int)count, off);
 102
 103        if (list_empty(&dev->expired) && (dev->work == 0)) {
 104                if (filep->f_flags & O_NONBLOCK)
 105                        return -EAGAIN;
 106                wait_event_interruptible(dev->wait, (dev->work ||
 107                                                     !list_empty(&dev->expired)));
 108                if (signal_pending(current))
 109                        return -ERESTARTSYS;
 110        }
 111        if (count < sizeof(int))
 112                return -ENOSPC;
 113        if (dev->work)
 114                dev->work = 0;
 115        if (!list_empty(&dev->expired)) {
 116                spin_lock_irqsave(&dev->lock, flags);
 117                timer = (struct mISDNtimer *)dev->expired.next;
 118                list_del(&timer->list);
 119                spin_unlock_irqrestore(&dev->lock, flags);
 120                if (put_user(timer->id, (int __user *)buf))
 121                        ret = -EFAULT;
 122                else
 123                        ret = sizeof(int);
 124                kfree(timer);
 125        }
 126        return ret;
 127}
 128
 129static unsigned int
 130mISDN_poll(struct file *filep, poll_table *wait)
 131{
 132        struct mISDNtimerdev    *dev = filep->private_data;
 133        unsigned int            mask = POLLERR;
 134
 135        if (*debug & DEBUG_TIMER)
 136                printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait);
 137        if (dev) {
 138                poll_wait(filep, &dev->wait, wait);
 139                mask = 0;
 140                if (dev->work || !list_empty(&dev->expired))
 141                        mask |= (POLLIN | POLLRDNORM);
 142                if (*debug & DEBUG_TIMER)
 143                        printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__,
 144                               dev->work, list_empty(&dev->expired));
 145        }
 146        return mask;
 147}
 148
 149static void
 150dev_expire_timer(unsigned long data)
 151{
 152        struct mISDNtimer *timer = (void *)data;
 153        u_long                  flags;
 154
 155        spin_lock_irqsave(&timer->dev->lock, flags);
 156        list_move_tail(&timer->list, &timer->dev->expired);
 157        spin_unlock_irqrestore(&timer->dev->lock, flags);
 158        wake_up_interruptible(&timer->dev->wait);
 159}
 160
 161static int
 162misdn_add_timer(struct mISDNtimerdev *dev, int timeout)
 163{
 164        int                     id;
 165        u_long                  flags;
 166        struct mISDNtimer       *timer;
 167
 168        if (!timeout) {
 169                dev->work = 1;
 170                wake_up_interruptible(&dev->wait);
 171                id = 0;
 172        } else {
 173                timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL);
 174                if (!timer)
 175                        return -ENOMEM;
 176                spin_lock_irqsave(&dev->lock, flags);
 177                timer->id = dev->next_id++;
 178                if (dev->next_id < 0)
 179                        dev->next_id = 1;
 180                list_add_tail(&timer->list, &dev->pending);
 181                spin_unlock_irqrestore(&dev->lock, flags);
 182                timer->dev = dev;
 183                timer->tl.data = (long)timer;
 184                timer->tl.function = dev_expire_timer;
 185                init_timer(&timer->tl);
 186                timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000);
 187                add_timer(&timer->tl);
 188                id = timer->id;
 189        }
 190        return id;
 191}
 192
 193static int
 194misdn_del_timer(struct mISDNtimerdev *dev, int id)
 195{
 196        u_long                  flags;
 197        struct mISDNtimer       *timer;
 198        int                     ret = 0;
 199
 200        spin_lock_irqsave(&dev->lock, flags);
 201        list_for_each_entry(timer, &dev->pending, list) {
 202                if (timer->id == id) {
 203                        list_del_init(&timer->list);
 204                        /* RED-PEN AK: race -- timer can be still running on
 205                         * other CPU. Needs reference count I think
 206                         */
 207                        del_timer(&timer->tl);
 208                        ret = timer->id;
 209                        kfree(timer);
 210                        goto unlock;
 211                }
 212        }
 213unlock:
 214        spin_unlock_irqrestore(&dev->lock, flags);
 215        return ret;
 216}
 217
 218static long
 219mISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
 220{
 221        struct mISDNtimerdev    *dev = filep->private_data;
 222        int                     id, tout, ret = 0;
 223
 224
 225        if (*debug & DEBUG_TIMER)
 226                printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__,
 227                       filep, cmd, arg);
 228        mutex_lock(&mISDN_mutex);
 229        switch (cmd) {
 230        case IMADDTIMER:
 231                if (get_user(tout, (int __user *)arg)) {
 232                        ret = -EFAULT;
 233                        break;
 234                }
 235                id = misdn_add_timer(dev, tout);
 236                if (*debug & DEBUG_TIMER)
 237                        printk(KERN_DEBUG "%s add %d id %d\n", __func__,
 238                               tout, id);
 239                if (id < 0) {
 240                        ret = id;
 241                        break;
 242                }
 243                if (put_user(id, (int __user *)arg))
 244                        ret = -EFAULT;
 245                break;
 246        case IMDELTIMER:
 247                if (get_user(id, (int __user *)arg)) {
 248                        ret = -EFAULT;
 249                        break;
 250                }
 251                if (*debug & DEBUG_TIMER)
 252                        printk(KERN_DEBUG "%s del id %d\n", __func__, id);
 253                id = misdn_del_timer(dev, id);
 254                if (put_user(id, (int __user *)arg))
 255                        ret = -EFAULT;
 256                break;
 257        default:
 258                ret = -EINVAL;
 259        }
 260        mutex_unlock(&mISDN_mutex);
 261        return ret;
 262}
 263
 264static const struct file_operations mISDN_fops = {
 265        .read           = mISDN_read,
 266        .poll           = mISDN_poll,
 267        .unlocked_ioctl = mISDN_ioctl,
 268        .open           = mISDN_open,
 269        .release        = mISDN_close,
 270        .llseek         = no_llseek,
 271};
 272
 273static struct miscdevice mISDNtimer = {
 274        .minor  = MISC_DYNAMIC_MINOR,
 275        .name   = "mISDNtimer",
 276        .fops   = &mISDN_fops,
 277};
 278
 279int
 280mISDN_inittimer(u_int *deb)
 281{
 282        int     err;
 283
 284        debug = deb;
 285        err = misc_register(&mISDNtimer);
 286        if (err)
 287                printk(KERN_WARNING "mISDN: Could not register timer device\n");
 288        return err;
 289}
 290
 291void mISDN_timer_cleanup(void)
 292{
 293        misc_deregister(&mISDNtimer);
 294}
 295