diff options
Diffstat (limited to 'drivers/isdn/mISDN/timerdev.c')
-rw-r--r-- | drivers/isdn/mISDN/timerdev.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/drivers/isdn/mISDN/timerdev.c b/drivers/isdn/mISDN/timerdev.c new file mode 100644 index 0000000000..83d6b484d3 --- /dev/null +++ b/drivers/isdn/mISDN/timerdev.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * general timer device for using in ISDN stacks + * + * Author Karsten Keil <kkeil@novell.com> + * + * Copyright 2008 by Karsten Keil <kkeil@novell.com> + */ + +#include <linux/poll.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mISDNif.h> +#include <linux/mutex.h> +#include <linux/sched/signal.h> + +#include "core.h" + +static DEFINE_MUTEX(mISDN_mutex); +static u_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; + return nonseekable_open(ino, filep); +} + +static int +mISDN_close(struct inode *ino, struct file *filep) +{ + struct mISDNtimerdev *dev = filep->private_data; + struct list_head *list = &dev->pending; + struct mISDNtimer *timer, *next; + + if (*debug & DEBUG_TIMER) + printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); + + spin_lock_irq(&dev->lock); + while (!list_empty(list)) { + timer = list_first_entry(list, struct mISDNtimer, list); + spin_unlock_irq(&dev->lock); + timer_shutdown_sync(&timer->tl); + spin_lock_irq(&dev->lock); + /* it might have been moved to ->expired */ + list_del(&timer->list); + kfree(timer); + } + spin_unlock_irq(&dev->lock); + + list_for_each_entry_safe(timer, next, &dev->expired, list) { + kfree(timer); + } + kfree(dev); + return 0; +} + +static ssize_t +mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off) +{ + struct mISDNtimerdev *dev = filep->private_data; + struct list_head *list = &dev->expired; + struct mISDNtimer *timer; + int ret = 0; + + if (*debug & DEBUG_TIMER) + printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__, + filep, buf, (int)count, off); + + if (count < sizeof(int)) + return -ENOSPC; + + spin_lock_irq(&dev->lock); + while (list_empty(list) && (dev->work == 0)) { + spin_unlock_irq(&dev->lock); + if (filep->f_flags & O_NONBLOCK) + return -EAGAIN; + wait_event_interruptible(dev->wait, (dev->work || + !list_empty(list))); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dev->lock); + } + if (dev->work) + dev->work = 0; + if (!list_empty(list)) { + timer = list_first_entry(list, struct mISDNtimer, list); + list_del(&timer->list); + spin_unlock_irq(&dev->lock); + if (put_user(timer->id, (int __user *)buf)) + ret = -EFAULT; + else + ret = sizeof(int); + kfree(timer); + } else { + spin_unlock_irq(&dev->lock); + } + return ret; +} + +static __poll_t +mISDN_poll(struct file *filep, poll_table *wait) +{ + struct mISDNtimerdev *dev = filep->private_data; + __poll_t mask = EPOLLERR; + + 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 |= (EPOLLIN | EPOLLRDNORM); + if (*debug & DEBUG_TIMER) + printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__, + dev->work, list_empty(&dev->expired)); + } + return mask; +} + +static void +dev_expire_timer(struct timer_list *t) +{ + struct mISDNtimer *timer = from_timer(timer, t, tl); + u_long flags; + + spin_lock_irqsave(&timer->dev->lock, flags); + if (timer->id >= 0) + list_move_tail(&timer->list, &timer->dev->expired); + wake_up_interruptible(&timer->dev->wait); + spin_unlock_irqrestore(&timer->dev->lock, flags); +} + +static int +misdn_add_timer(struct mISDNtimerdev *dev, int timeout) +{ + int id; + 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; + timer->dev = dev; + timer_setup(&timer->tl, dev_expire_timer, 0); + spin_lock_irq(&dev->lock); + id = timer->id = dev->next_id++; + if (dev->next_id < 0) + dev->next_id = 1; + list_add_tail(&timer->list, &dev->pending); + timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000); + add_timer(&timer->tl); + spin_unlock_irq(&dev->lock); + } + return id; +} + +static int +misdn_del_timer(struct mISDNtimerdev *dev, int id) +{ + struct mISDNtimer *timer; + + spin_lock_irq(&dev->lock); + list_for_each_entry(timer, &dev->pending, list) { + if (timer->id == id) { + list_del_init(&timer->list); + timer->id = -1; + spin_unlock_irq(&dev->lock); + timer_shutdown_sync(&timer->tl); + kfree(timer); + return id; + } + } + spin_unlock_irq(&dev->lock); + return 0; +} + +static long +mISDN_ioctl(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); + mutex_lock(&mISDN_mutex); + 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; + } + mutex_unlock(&mISDN_mutex); + return ret; +} + +static const struct file_operations mISDN_fops = { + .owner = THIS_MODULE, + .read = mISDN_read, + .poll = mISDN_poll, + .unlocked_ioctl = mISDN_ioctl, + .open = mISDN_open, + .release = mISDN_close, + .llseek = no_llseek, +}; + +static struct miscdevice mISDNtimer = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mISDNtimer", + .fops = &mISDN_fops, +}; + +int +mISDN_inittimer(u_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); +} |