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