11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21b2b03f8SKarsten Keil /*
31b2b03f8SKarsten Keil *
41b2b03f8SKarsten Keil * general timer device for using in ISDN stacks
51b2b03f8SKarsten Keil *
61b2b03f8SKarsten Keil * Author Karsten Keil <kkeil@novell.com>
71b2b03f8SKarsten Keil *
81b2b03f8SKarsten Keil * Copyright 2008 by Karsten Keil <kkeil@novell.com>
91b2b03f8SKarsten Keil */
101b2b03f8SKarsten Keil
111b2b03f8SKarsten Keil #include <linux/poll.h>
121b2b03f8SKarsten Keil #include <linux/vmalloc.h>
135a0e3ad6STejun Heo #include <linux/slab.h>
141b2b03f8SKarsten Keil #include <linux/timer.h>
151b2b03f8SKarsten Keil #include <linux/miscdevice.h>
161b2b03f8SKarsten Keil #include <linux/module.h>
171b2b03f8SKarsten Keil #include <linux/mISDNif.h>
1876a64921SArnd Bergmann #include <linux/mutex.h>
19174cd4b1SIngo Molnar #include <linux/sched/signal.h>
20174cd4b1SIngo Molnar
215b834354SHannes Eder #include "core.h"
221b2b03f8SKarsten Keil
2376a64921SArnd Bergmann static DEFINE_MUTEX(mISDN_mutex);
24dfa96ec1SHannes Eder static u_int *debug;
251b2b03f8SKarsten Keil
261b2b03f8SKarsten Keil
271b2b03f8SKarsten Keil struct mISDNtimerdev {
281b2b03f8SKarsten Keil int next_id;
291b2b03f8SKarsten Keil struct list_head pending;
301b2b03f8SKarsten Keil struct list_head expired;
311b2b03f8SKarsten Keil wait_queue_head_t wait;
321b2b03f8SKarsten Keil u_int work;
331b2b03f8SKarsten Keil spinlock_t lock; /* protect lists */
341b2b03f8SKarsten Keil };
351b2b03f8SKarsten Keil
361b2b03f8SKarsten Keil struct mISDNtimer {
371b2b03f8SKarsten Keil struct list_head list;
381b2b03f8SKarsten Keil struct mISDNtimerdev *dev;
391b2b03f8SKarsten Keil struct timer_list tl;
401b2b03f8SKarsten Keil int id;
411b2b03f8SKarsten Keil };
421b2b03f8SKarsten Keil
431b2b03f8SKarsten Keil static int
mISDN_open(struct inode * ino,struct file * filep)441b2b03f8SKarsten Keil mISDN_open(struct inode *ino, struct file *filep)
451b2b03f8SKarsten Keil {
461b2b03f8SKarsten Keil struct mISDNtimerdev *dev;
471b2b03f8SKarsten Keil
481b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
491b2b03f8SKarsten Keil printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
501b2b03f8SKarsten Keil dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL);
511b2b03f8SKarsten Keil if (!dev)
521b2b03f8SKarsten Keil return -ENOMEM;
531b2b03f8SKarsten Keil dev->next_id = 1;
541b2b03f8SKarsten Keil INIT_LIST_HEAD(&dev->pending);
551b2b03f8SKarsten Keil INIT_LIST_HEAD(&dev->expired);
561b2b03f8SKarsten Keil spin_lock_init(&dev->lock);
571b2b03f8SKarsten Keil dev->work = 0;
581b2b03f8SKarsten Keil init_waitqueue_head(&dev->wait);
591b2b03f8SKarsten Keil filep->private_data = dev;
606bff338bSAndrew Morton return nonseekable_open(ino, filep);
611b2b03f8SKarsten Keil }
621b2b03f8SKarsten Keil
631b2b03f8SKarsten Keil static int
mISDN_close(struct inode * ino,struct file * filep)641b2b03f8SKarsten Keil mISDN_close(struct inode *ino, struct file *filep)
651b2b03f8SKarsten Keil {
661b2b03f8SKarsten Keil struct mISDNtimerdev *dev = filep->private_data;
67c08c464dSAl Viro struct list_head *list = &dev->pending;
681b2b03f8SKarsten Keil struct mISDNtimer *timer, *next;
691b2b03f8SKarsten Keil
701b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
711b2b03f8SKarsten Keil printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
72c08c464dSAl Viro
73c08c464dSAl Viro spin_lock_irq(&dev->lock);
74c08c464dSAl Viro while (!list_empty(list)) {
75c08c464dSAl Viro timer = list_first_entry(list, struct mISDNtimer, list);
76c08c464dSAl Viro spin_unlock_irq(&dev->lock);
77*292a089dSSteven Rostedt (Google) timer_shutdown_sync(&timer->tl);
78c08c464dSAl Viro spin_lock_irq(&dev->lock);
79c08c464dSAl Viro /* it might have been moved to ->expired */
80c08c464dSAl Viro list_del(&timer->list);
811b2b03f8SKarsten Keil kfree(timer);
821b2b03f8SKarsten Keil }
83c08c464dSAl Viro spin_unlock_irq(&dev->lock);
84c08c464dSAl Viro
851b2b03f8SKarsten Keil list_for_each_entry_safe(timer, next, &dev->expired, list) {
861b2b03f8SKarsten Keil kfree(timer);
871b2b03f8SKarsten Keil }
881b2b03f8SKarsten Keil kfree(dev);
891b2b03f8SKarsten Keil return 0;
901b2b03f8SKarsten Keil }
911b2b03f8SKarsten Keil
921b2b03f8SKarsten Keil static ssize_t
mISDN_read(struct file * filep,char __user * buf,size_t count,loff_t * off)93c46f0a2dSHannes Eder mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
941b2b03f8SKarsten Keil {
951b2b03f8SKarsten Keil struct mISDNtimerdev *dev = filep->private_data;
96ebb06be1SAl Viro struct list_head *list = &dev->expired;
971b2b03f8SKarsten Keil struct mISDNtimer *timer;
981b2b03f8SKarsten Keil int ret = 0;
991b2b03f8SKarsten Keil
1001b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
1011b2b03f8SKarsten Keil printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
1021b2b03f8SKarsten Keil filep, buf, (int)count, off);
1031b2b03f8SKarsten Keil
104ebb06be1SAl Viro if (count < sizeof(int))
105ebb06be1SAl Viro return -ENOSPC;
106ebb06be1SAl Viro
107ebb06be1SAl Viro spin_lock_irq(&dev->lock);
108ebb06be1SAl Viro while (list_empty(list) && (dev->work == 0)) {
109ebb06be1SAl Viro spin_unlock_irq(&dev->lock);
1101b2b03f8SKarsten Keil if (filep->f_flags & O_NONBLOCK)
1111b2b03f8SKarsten Keil return -EAGAIN;
1121b2b03f8SKarsten Keil wait_event_interruptible(dev->wait, (dev->work ||
113ebb06be1SAl Viro !list_empty(list)));
1141b2b03f8SKarsten Keil if (signal_pending(current))
1151b2b03f8SKarsten Keil return -ERESTARTSYS;
116ebb06be1SAl Viro spin_lock_irq(&dev->lock);
1171b2b03f8SKarsten Keil }
1181b2b03f8SKarsten Keil if (dev->work)
1191b2b03f8SKarsten Keil dev->work = 0;
120ebb06be1SAl Viro if (!list_empty(list)) {
121ebb06be1SAl Viro timer = list_first_entry(list, struct mISDNtimer, list);
1221b2b03f8SKarsten Keil list_del(&timer->list);
123ebb06be1SAl Viro spin_unlock_irq(&dev->lock);
124c46f0a2dSHannes Eder if (put_user(timer->id, (int __user *)buf))
1251b2b03f8SKarsten Keil ret = -EFAULT;
1261b2b03f8SKarsten Keil else
1271b2b03f8SKarsten Keil ret = sizeof(int);
1281b2b03f8SKarsten Keil kfree(timer);
129ebb06be1SAl Viro } else {
130ebb06be1SAl Viro spin_unlock_irq(&dev->lock);
1311b2b03f8SKarsten Keil }
1321b2b03f8SKarsten Keil return ret;
1331b2b03f8SKarsten Keil }
1341b2b03f8SKarsten Keil
135afc9a42bSAl Viro static __poll_t
mISDN_poll(struct file * filep,poll_table * wait)1361b2b03f8SKarsten Keil mISDN_poll(struct file *filep, poll_table *wait)
1371b2b03f8SKarsten Keil {
1381b2b03f8SKarsten Keil struct mISDNtimerdev *dev = filep->private_data;
139a9a08845SLinus Torvalds __poll_t mask = EPOLLERR;
1401b2b03f8SKarsten Keil
1411b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
1421b2b03f8SKarsten Keil printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait);
1431b2b03f8SKarsten Keil if (dev) {
1441b2b03f8SKarsten Keil poll_wait(filep, &dev->wait, wait);
1451b2b03f8SKarsten Keil mask = 0;
1461b2b03f8SKarsten Keil if (dev->work || !list_empty(&dev->expired))
147a9a08845SLinus Torvalds mask |= (EPOLLIN | EPOLLRDNORM);
1481b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
1491b2b03f8SKarsten Keil printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__,
1501b2b03f8SKarsten Keil dev->work, list_empty(&dev->expired));
1511b2b03f8SKarsten Keil }
1521b2b03f8SKarsten Keil return mask;
1531b2b03f8SKarsten Keil }
1541b2b03f8SKarsten Keil
1551b2b03f8SKarsten Keil static void
dev_expire_timer(struct timer_list * t)156e313ac12SKees Cook dev_expire_timer(struct timer_list *t)
1571b2b03f8SKarsten Keil {
158e313ac12SKees Cook struct mISDNtimer *timer = from_timer(timer, t, tl);
1591b2b03f8SKarsten Keil u_long flags;
1601b2b03f8SKarsten Keil
1611b2b03f8SKarsten Keil spin_lock_irqsave(&timer->dev->lock, flags);
1621b108956SAl Viro if (timer->id >= 0)
163211174eaSEric Sesterhenn list_move_tail(&timer->list, &timer->dev->expired);
1641b2b03f8SKarsten Keil wake_up_interruptible(&timer->dev->wait);
165bdcc5bc2SEric Dumazet spin_unlock_irqrestore(&timer->dev->lock, flags);
1661b2b03f8SKarsten Keil }
1671b2b03f8SKarsten Keil
1681b2b03f8SKarsten Keil static int
misdn_add_timer(struct mISDNtimerdev * dev,int timeout)1691b2b03f8SKarsten Keil misdn_add_timer(struct mISDNtimerdev *dev, int timeout)
1701b2b03f8SKarsten Keil {
1711b2b03f8SKarsten Keil int id;
1721b2b03f8SKarsten Keil struct mISDNtimer *timer;
1731b2b03f8SKarsten Keil
1741b2b03f8SKarsten Keil if (!timeout) {
1751b2b03f8SKarsten Keil dev->work = 1;
1761b2b03f8SKarsten Keil wake_up_interruptible(&dev->wait);
1771b2b03f8SKarsten Keil id = 0;
1781b2b03f8SKarsten Keil } else {
1791b2b03f8SKarsten Keil timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL);
1801b2b03f8SKarsten Keil if (!timer)
1811b2b03f8SKarsten Keil return -ENOMEM;
1821678ec00SAl Viro timer->dev = dev;
183e313ac12SKees Cook timer_setup(&timer->tl, dev_expire_timer, 0);
1841678ec00SAl Viro spin_lock_irq(&dev->lock);
1851678ec00SAl Viro id = timer->id = dev->next_id++;
1861b2b03f8SKarsten Keil if (dev->next_id < 0)
1871b2b03f8SKarsten Keil dev->next_id = 1;
1881b2b03f8SKarsten Keil list_add_tail(&timer->list, &dev->pending);
1891b2b03f8SKarsten Keil timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000);
1901b2b03f8SKarsten Keil add_timer(&timer->tl);
1911678ec00SAl Viro spin_unlock_irq(&dev->lock);
1921b2b03f8SKarsten Keil }
1931b2b03f8SKarsten Keil return id;
1941b2b03f8SKarsten Keil }
1951b2b03f8SKarsten Keil
1961b2b03f8SKarsten Keil static int
misdn_del_timer(struct mISDNtimerdev * dev,int id)1971b2b03f8SKarsten Keil misdn_del_timer(struct mISDNtimerdev *dev, int id)
1981b2b03f8SKarsten Keil {
1991b2b03f8SKarsten Keil struct mISDNtimer *timer;
2001b2b03f8SKarsten Keil
2011b108956SAl Viro spin_lock_irq(&dev->lock);
2021b2b03f8SKarsten Keil list_for_each_entry(timer, &dev->pending, list) {
2031b2b03f8SKarsten Keil if (timer->id == id) {
2041b2b03f8SKarsten Keil list_del_init(&timer->list);
2051b108956SAl Viro timer->id = -1;
2061b108956SAl Viro spin_unlock_irq(&dev->lock);
207*292a089dSSteven Rostedt (Google) timer_shutdown_sync(&timer->tl);
2081b2b03f8SKarsten Keil kfree(timer);
2091b108956SAl Viro return id;
2101b2b03f8SKarsten Keil }
2111b2b03f8SKarsten Keil }
2121b108956SAl Viro spin_unlock_irq(&dev->lock);
2131b108956SAl Viro return 0;
2141b2b03f8SKarsten Keil }
2151b2b03f8SKarsten Keil
216703c631eSArnd Bergmann static long
mISDN_ioctl(struct file * filep,unsigned int cmd,unsigned long arg)217703c631eSArnd Bergmann mISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
2181b2b03f8SKarsten Keil {
2191b2b03f8SKarsten Keil struct mISDNtimerdev *dev = filep->private_data;
2201b2b03f8SKarsten Keil int id, tout, ret = 0;
2211b2b03f8SKarsten Keil
2221b2b03f8SKarsten Keil
2231b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
2241b2b03f8SKarsten Keil printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__,
2251b2b03f8SKarsten Keil filep, cmd, arg);
22676a64921SArnd Bergmann mutex_lock(&mISDN_mutex);
2271b2b03f8SKarsten Keil switch (cmd) {
2281b2b03f8SKarsten Keil case IMADDTIMER:
2291b2b03f8SKarsten Keil if (get_user(tout, (int __user *)arg)) {
2301b2b03f8SKarsten Keil ret = -EFAULT;
2311b2b03f8SKarsten Keil break;
2321b2b03f8SKarsten Keil }
2331b2b03f8SKarsten Keil id = misdn_add_timer(dev, tout);
2341b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
2351b2b03f8SKarsten Keil printk(KERN_DEBUG "%s add %d id %d\n", __func__,
2361b2b03f8SKarsten Keil tout, id);
2371b2b03f8SKarsten Keil if (id < 0) {
2381b2b03f8SKarsten Keil ret = id;
2391b2b03f8SKarsten Keil break;
2401b2b03f8SKarsten Keil }
2411b2b03f8SKarsten Keil if (put_user(id, (int __user *)arg))
2421b2b03f8SKarsten Keil ret = -EFAULT;
2431b2b03f8SKarsten Keil break;
2441b2b03f8SKarsten Keil case IMDELTIMER:
2451b2b03f8SKarsten Keil if (get_user(id, (int __user *)arg)) {
2461b2b03f8SKarsten Keil ret = -EFAULT;
2471b2b03f8SKarsten Keil break;
2481b2b03f8SKarsten Keil }
2491b2b03f8SKarsten Keil if (*debug & DEBUG_TIMER)
2501b2b03f8SKarsten Keil printk(KERN_DEBUG "%s del id %d\n", __func__, id);
2511b2b03f8SKarsten Keil id = misdn_del_timer(dev, id);
2521b2b03f8SKarsten Keil if (put_user(id, (int __user *)arg))
2531b2b03f8SKarsten Keil ret = -EFAULT;
2541b2b03f8SKarsten Keil break;
2551b2b03f8SKarsten Keil default:
2561b2b03f8SKarsten Keil ret = -EINVAL;
2571b2b03f8SKarsten Keil }
25876a64921SArnd Bergmann mutex_unlock(&mISDN_mutex);
2591b2b03f8SKarsten Keil return ret;
2601b2b03f8SKarsten Keil }
2611b2b03f8SKarsten Keil
262eac74af9SKarsten Keil static const struct file_operations mISDN_fops = {
26389b107adSAl Viro .owner = THIS_MODULE,
2641b2b03f8SKarsten Keil .read = mISDN_read,
2651b2b03f8SKarsten Keil .poll = mISDN_poll,
266703c631eSArnd Bergmann .unlocked_ioctl = mISDN_ioctl,
2671b2b03f8SKarsten Keil .open = mISDN_open,
2681b2b03f8SKarsten Keil .release = mISDN_close,
2696038f373SArnd Bergmann .llseek = no_llseek,
2701b2b03f8SKarsten Keil };
2711b2b03f8SKarsten Keil
2721b2b03f8SKarsten Keil static struct miscdevice mISDNtimer = {
2731b2b03f8SKarsten Keil .minor = MISC_DYNAMIC_MINOR,
2741b2b03f8SKarsten Keil .name = "mISDNtimer",
2751b2b03f8SKarsten Keil .fops = &mISDN_fops,
2761b2b03f8SKarsten Keil };
2771b2b03f8SKarsten Keil
2781b2b03f8SKarsten Keil int
mISDN_inittimer(u_int * deb)279dfa96ec1SHannes Eder mISDN_inittimer(u_int *deb)
2801b2b03f8SKarsten Keil {
2811b2b03f8SKarsten Keil int err;
2821b2b03f8SKarsten Keil
2831b2b03f8SKarsten Keil debug = deb;
2841b2b03f8SKarsten Keil err = misc_register(&mISDNtimer);
2851b2b03f8SKarsten Keil if (err)
2861b2b03f8SKarsten Keil printk(KERN_WARNING "mISDN: Could not register timer device\n");
2871b2b03f8SKarsten Keil return err;
2881b2b03f8SKarsten Keil }
2891b2b03f8SKarsten Keil
mISDN_timer_cleanup(void)2901b2b03f8SKarsten Keil void mISDN_timer_cleanup(void)
2911b2b03f8SKarsten Keil {
2921b2b03f8SKarsten Keil misc_deregister(&mISDNtimer);
2931b2b03f8SKarsten Keil }
294