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/timer.h>
  23#include <linux/miscdevice.h>
  24#include <linux/module.h>
  25#include <linux/mISDNif.h>
  26#include "core.h"
  27
  28static u_int    *debug;
  29
  30
  31struct mISDNtimerdev {
  32        int                     next_id;
  33        struct list_head        pending;
  34        struct list_head        expired;
  35        wait_queue_head_t       wait;
  36        u_int                   work;
  37        spinlock_t              lock; /* protect lists */
  38};
  39
  40struct mISDNtimer {
  41        struct list_head        list;
  42        struct  mISDNtimerdev   *dev;
  43        struct timer_list       tl;
  44        int                     id;
  45};
  46
  47static int
  48mISDN_open(struct inode *ino, struct file *filep)
  49{
  50        struct mISDNtimerdev    *dev;
  51
  52        if (*debug & DEBUG_TIMER)
  53                printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
  54        dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL);
  55        if (!dev)
  56                return -ENOMEM;
  57        dev->next_id = 1;
  58        INIT_LIST_HEAD(&dev->pending);
  59        INIT_LIST_HEAD(&dev->expired);
  60        spin_lock_init(&dev->lock);
  61        dev->work = 0;
  62        init_waitqueue_head(&dev->wait);
  63        filep->private_data = dev;
  64        __module_get(THIS_MODULE);
  65        return nonseekable_open(ino, filep);
  66}
  67
  68static int
  69mISDN_close(struct inode *ino, struct file *filep)
  70{
  71        struct mISDNtimerdev    *dev = filep->private_data;
  72        struct mISDNtimer       *timer, *next;
  73
  74        if (*debug & DEBUG_TIMER)
  75                printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
  76        list_for_each_entry_safe(timer, next, &dev->pending, list) {
  77                del_timer(&timer->tl);
  78                kfree(timer);
  79        }
  80        list_for_each_entry_safe(timer, next, &dev->expired, list) {
  81                kfree(timer);
  82        }
  83        kfree(dev);
  84        module_put(THIS_MODULE);
  85        return 0;
  86}
  87
  88static ssize_t
  89mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
  90{
  91        struct mISDNtimerdev    *dev = filep->private_data;
  92        struct mISDNtimer       *timer;
  93        u_long  flags;
  94        int     ret = 0;
  95
  96        if (*debug & DEBUG_TIMER)
  97                printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
  98                        filep, buf, (int)count, off);
  99        if (*off != filep->f_pos)
 100                return -ESPIPE;
 101
 102        if (list_empty(&dev->expired) && (dev->work == 0)) {
 103                if (filep->f_flags & O_NONBLOCK)
 104                        return -EAGAIN;
 105                wait_event_interruptible(dev->wait, (dev->work ||
 106                    !list_empty(&dev->expired)));
 107                if (signal_pending(current))
 108                        return -ERESTARTSYS;
 109        }
 110        if (count < sizeof(int))
 111                return -ENOSPC;
 112        if (dev->work)
 113                dev->work = 0;
 114        if (!list_empty(&dev->expired)) {
 115                spin_lock_irqsave(&dev->lock, flags);
 116                timer = (struct mISDNtimer *)dev->expired.next;
 117                list_del(&timer->list);
 118                spin_unlock_irqrestore(&dev->lock, flags);
 119                if (put_user(timer->id, (int __user *)buf))
 120                        ret = -EFAULT;
 121                else
 122                        ret = sizeof(int);
 123                kfree(timer);
 124        }
 125        return ret;
 126}
 127
 128static unsigned int
 129mISDN_poll(struct file *filep, poll_table *wait)
 130{
 131        struct mISDNtimerdev    *dev = filep->private_data;
 132        unsigned int            mask = POLLERR;
 133
 134        if (*debug & DEBUG_TIMER)
 135                printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait);
 136        if (dev) {
 137                poll_wait(filep, &dev->wait, wait);
 138                mask = 0;
 139                if (dev->work || !list_empty(&dev->expired))
 140                        mask |= (POLLIN | POLLRDNORM);
 141                if (*debug & DEBUG_TIMER)
 142                        printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__,
 143                                dev->work, list_empty(&dev->expired));
 144        }
 145        return mask;
 146}
 147
 148static void
 149dev_expire_timer(unsigned long data)
 150{
 151        struct mISDNtimer *timer = (void *)data;
 152        u_long                  flags;
 153
 154        spin_lock_irqsave(&timer->dev->lock, flags);
 155        list_move_tail(&timer->list, &timer->dev->expired);
 156        spin_unlock_irqrestore(&timer->dev->lock, flags);
 157        wake_up_interruptible(&timer->dev->wait);
 158}
 159
 160static int
 161misdn_add_timer(struct mISDNtimerdev *dev, int timeout)
 162{
 163        int                     id;
 164        u_long                  flags;
 165        struct mISDNtimer       *timer;
 166
 167        if (!timeout) {
 168                dev->work = 1;
 169                wake_up_interruptible(&dev->wait);
 170                id = 0;
 171        } else {
 172                timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL);
 173                if (!timer)
 174                        return -ENOMEM;
 175                spin_lock_irqsave(&dev->lock, flags);
 176                timer->id = dev->next_id++;
 177                if (dev->next_id < 0)
 178                        dev->next_id = 1;
 179                list_add_tail(&timer->list, &dev->pending);
 180                spin_unlock_irqrestore(&dev->lock, flags);
 181                timer->dev = dev;
 182                timer->tl.data = (long)timer;
 183                timer->tl.function = dev_expire_timer;
 184                init_timer(&timer->tl);
 185                timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000);
 186                add_timer(&timer->tl);
 187                id = timer->id;
 188        }
 189        return id;
 190}
 191
 192static int
 193misdn_del_timer(struct mISDNtimerdev *dev, int id)
 194{
 195        u_long                  flags;
 196        struct mISDNtimer       *timer;
 197        int                     ret = 0;
 198
 199        spin_lock_irqsave(&dev->lock, flags);
 200        list_for_each_entry(timer, &dev->pending, list) {
 201                if (timer->id == id) {
 202                        list_del_init(&timer->list);
 203                        /* RED-PEN AK: race -- timer can be still running on
 204                         * other CPU. Needs reference count I think
 205                         */
 206                        del_timer(&timer->tl);
 207                        ret = timer->id;
 208                        kfree(timer);
 209                        goto unlock;
 210                }
 211        }
 212unlock:
 213        spin_unlock_irqrestore(&dev->lock, flags);
 214        return ret;
 215}
 216
 217static int
 218mISDN_ioctl(struct inode *inode, struct file *filep, unsigned int cmd,
 219    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        switch (cmd) {
 229        case IMADDTIMER:
 230                if (get_user(tout, (int __user *)arg)) {
 231                        ret = -EFAULT;
 232                        break;
 233                }
 234                id = misdn_add_timer(dev, tout);
 235                if (*debug & DEBUG_TIMER)
 236                        printk(KERN_DEBUG "%s add %d id %d\n", __func__,
 237                            tout, id);
 238                if (id < 0) {
 239                        ret = id;
 240                        break;
 241                }
 242                if (put_user(id, (int __user *)arg))
 243                        ret = -EFAULT;
 244                break;
 245        case IMDELTIMER:
 246                if (get_user(id, (int __user *)arg)) {
 247                        ret = -EFAULT;
 248                        break;
 249                }
 250                if (*debug & DEBUG_TIMER)
 251                        printk(KERN_DEBUG "%s del id %d\n", __func__, id);
 252                id = misdn_del_timer(dev, id);
 253                if (put_user(id, (int __user *)arg))
 254                        ret = -EFAULT;
 255                break;
 256        default:
 257                ret = -EINVAL;
 258        }
 259        return ret;
 260}
 261
 262static const struct file_operations mISDN_fops = {
 263        .read           = mISDN_read,
 264        .poll           = mISDN_poll,
 265        .ioctl          = mISDN_ioctl,
 266        .open           = mISDN_open,
 267        .release        = mISDN_close,
 268};
 269
 270static struct miscdevice mISDNtimer = {
 271        .minor  = MISC_DYNAMIC_MINOR,
 272        .name   = "mISDNtimer",
 273        .fops   = &mISDN_fops,
 274};
 275
 276int
 277mISDN_inittimer(u_int *deb)
 278{
 279        int     err;
 280
 281        debug = deb;
 282        err = misc_register(&mISDNtimer);
 283        if (err)
 284                printk(KERN_WARNING "mISDN: Could not register timer device\n");
 285        return err;
 286}
 287
 288void mISDN_timer_cleanup(void)
 289{
 290        misc_deregister(&mISDNtimer);
 291}
 292
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.