xref: /openbmc/linux/drivers/isdn/mISDN/timerdev.c (revision ebb06be1)
11b2b03f8SKarsten Keil /*
21b2b03f8SKarsten Keil  *
31b2b03f8SKarsten Keil  * general timer device for using in ISDN stacks
41b2b03f8SKarsten Keil  *
51b2b03f8SKarsten Keil  * Author	Karsten Keil <kkeil@novell.com>
61b2b03f8SKarsten Keil  *
71b2b03f8SKarsten Keil  * Copyright 2008  by Karsten Keil <kkeil@novell.com>
81b2b03f8SKarsten Keil  *
91b2b03f8SKarsten Keil  * This program is free software; you can redistribute it and/or modify
101b2b03f8SKarsten Keil  * it under the terms of the GNU General Public License version 2 as
111b2b03f8SKarsten Keil  * published by the Free Software Foundation.
121b2b03f8SKarsten Keil  *
131b2b03f8SKarsten Keil  * This program is distributed in the hope that it will be useful,
141b2b03f8SKarsten Keil  * but WITHOUT ANY WARRANTY; without even the implied warranty of
151b2b03f8SKarsten Keil  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
161b2b03f8SKarsten Keil  * GNU General Public License for more details.
171b2b03f8SKarsten Keil  *
181b2b03f8SKarsten Keil  */
191b2b03f8SKarsten Keil 
201b2b03f8SKarsten Keil #include <linux/poll.h>
211b2b03f8SKarsten Keil #include <linux/vmalloc.h>
225a0e3ad6STejun Heo #include <linux/slab.h>
231b2b03f8SKarsten Keil #include <linux/timer.h>
241b2b03f8SKarsten Keil #include <linux/miscdevice.h>
251b2b03f8SKarsten Keil #include <linux/module.h>
261b2b03f8SKarsten Keil #include <linux/mISDNif.h>
2776a64921SArnd Bergmann #include <linux/mutex.h>
285b834354SHannes Eder #include "core.h"
291b2b03f8SKarsten Keil 
3076a64921SArnd Bergmann static DEFINE_MUTEX(mISDN_mutex);
31dfa96ec1SHannes Eder static u_int	*debug;
321b2b03f8SKarsten Keil 
331b2b03f8SKarsten Keil 
341b2b03f8SKarsten Keil struct mISDNtimerdev {
351b2b03f8SKarsten Keil 	int			next_id;
361b2b03f8SKarsten Keil 	struct list_head	pending;
371b2b03f8SKarsten Keil 	struct list_head	expired;
381b2b03f8SKarsten Keil 	wait_queue_head_t	wait;
391b2b03f8SKarsten Keil 	u_int			work;
401b2b03f8SKarsten Keil 	spinlock_t		lock; /* protect lists */
411b2b03f8SKarsten Keil };
421b2b03f8SKarsten Keil 
431b2b03f8SKarsten Keil struct mISDNtimer {
441b2b03f8SKarsten Keil 	struct list_head	list;
451b2b03f8SKarsten Keil 	struct  mISDNtimerdev	*dev;
461b2b03f8SKarsten Keil 	struct timer_list	tl;
471b2b03f8SKarsten Keil 	int			id;
481b2b03f8SKarsten Keil };
491b2b03f8SKarsten Keil 
501b2b03f8SKarsten Keil static int
511b2b03f8SKarsten Keil mISDN_open(struct inode *ino, struct file *filep)
521b2b03f8SKarsten Keil {
531b2b03f8SKarsten Keil 	struct mISDNtimerdev	*dev;
541b2b03f8SKarsten Keil 
551b2b03f8SKarsten Keil 	if (*debug & DEBUG_TIMER)
561b2b03f8SKarsten Keil 		printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
571b2b03f8SKarsten Keil 	dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL);
581b2b03f8SKarsten Keil 	if (!dev)
591b2b03f8SKarsten Keil 		return -ENOMEM;
601b2b03f8SKarsten Keil 	dev->next_id = 1;
611b2b03f8SKarsten Keil 	INIT_LIST_HEAD(&dev->pending);
621b2b03f8SKarsten Keil 	INIT_LIST_HEAD(&dev->expired);
631b2b03f8SKarsten Keil 	spin_lock_init(&dev->lock);
641b2b03f8SKarsten Keil 	dev->work = 0;
651b2b03f8SKarsten Keil 	init_waitqueue_head(&dev->wait);
661b2b03f8SKarsten Keil 	filep->private_data = dev;
671b2b03f8SKarsten Keil 	__module_get(THIS_MODULE);
686bff338bSAndrew Morton 	return nonseekable_open(ino, filep);
691b2b03f8SKarsten Keil }
701b2b03f8SKarsten Keil 
711b2b03f8SKarsten Keil static int
721b2b03f8SKarsten Keil mISDN_close(struct inode *ino, struct file *filep)
731b2b03f8SKarsten Keil {
741b2b03f8SKarsten Keil 	struct mISDNtimerdev	*dev = filep->private_data;
75c08c464dSAl Viro 	struct list_head	*list = &dev->pending;
761b2b03f8SKarsten Keil 	struct mISDNtimer	*timer, *next;
771b2b03f8SKarsten Keil 
781b2b03f8SKarsten Keil 	if (*debug & DEBUG_TIMER)
791b2b03f8SKarsten Keil 		printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
80c08c464dSAl Viro 
81c08c464dSAl Viro 	spin_lock_irq(&dev->lock);
82c08c464dSAl Viro 	while (!list_empty(list)) {
83c08c464dSAl Viro 		timer = list_first_entry(list, struct mISDNtimer, list);
84c08c464dSAl Viro 		spin_unlock_irq(&dev->lock);
85c08c464dSAl Viro 		del_timer_sync(&timer->tl);
86c08c464dSAl Viro 		spin_lock_irq(&dev->lock);
87c08c464dSAl Viro 		/* it might have been moved to ->expired */
88c08c464dSAl Viro 		list_del(&timer->list);
891b2b03f8SKarsten Keil 		kfree(timer);
901b2b03f8SKarsten Keil 	}
91c08c464dSAl Viro 	spin_unlock_irq(&dev->lock);
92c08c464dSAl Viro 
931b2b03f8SKarsten Keil 	list_for_each_entry_safe(timer, next, &dev->expired, list) {
941b2b03f8SKarsten Keil 		kfree(timer);
951b2b03f8SKarsten Keil 	}
961b2b03f8SKarsten Keil 	kfree(dev);
971b2b03f8SKarsten Keil 	module_put(THIS_MODULE);
981b2b03f8SKarsten Keil 	return 0;
991b2b03f8SKarsten Keil }
1001b2b03f8SKarsten Keil 
1011b2b03f8SKarsten Keil static ssize_t
102c46f0a2dSHannes Eder mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
1031b2b03f8SKarsten Keil {
1041b2b03f8SKarsten Keil 	struct mISDNtimerdev	*dev = filep->private_data;
105ebb06be1SAl Viro 	struct list_head *list = &dev->expired;
1061b2b03f8SKarsten Keil 	struct mISDNtimer	*timer;
1071b2b03f8SKarsten Keil 	int	ret = 0;
1081b2b03f8SKarsten Keil 
1091b2b03f8SKarsten Keil 	if (*debug & DEBUG_TIMER)
1101b2b03f8SKarsten Keil 		printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
1111b2b03f8SKarsten Keil 		       filep, buf, (int)count, off);
1121b2b03f8SKarsten Keil 
113ebb06be1SAl Viro 	if (count < sizeof(int))
114ebb06be1SAl Viro 		return -ENOSPC;
115ebb06be1SAl Viro 
116ebb06be1SAl Viro 	spin_lock_irq(&dev->lock);
117ebb06be1SAl Viro 	while (list_empty(list) && (dev->work == 0)) {
118ebb06be1SAl Viro 		spin_unlock_irq(&dev->lock);
1191b2b03f8SKarsten Keil 		if (filep->f_flags & O_NONBLOCK)
1201b2b03f8SKarsten Keil 			return -EAGAIN;
1211b2b03f8SKarsten Keil 		wait_event_interruptible(dev->wait, (dev->work ||
122ebb06be1SAl Viro 						     !list_empty(list)));
1231b2b03f8SKarsten Keil 		if (signal_pending(current))
1241b2b03f8SKarsten Keil 			return -ERESTARTSYS;
125ebb06be1SAl Viro 		spin_lock_irq(&dev->lock);
1261b2b03f8SKarsten Keil 	}
1271b2b03f8SKarsten Keil 	if (dev->work)
1281b2b03f8SKarsten Keil 		dev->work = 0;
129ebb06be1SAl Viro 	if (!list_empty(list)) {
130ebb06be1SAl Viro 		timer = list_first_entry(list, struct mISDNtimer, list);
1311b2b03f8SKarsten Keil 		list_del(&timer->list);
132ebb06be1SAl Viro 		spin_unlock_irq(&dev->lock);
133c46f0a2dSHannes Eder 		if (put_user(timer->id, (int __user *)buf))
1341b2b03f8SKarsten Keil 			ret = -EFAULT;
1351b2b03f8SKarsten Keil 		else
1361b2b03f8SKarsten Keil 			ret = sizeof(int);
1371b2b03f8SKarsten Keil 		kfree(timer);
138ebb06be1SAl Viro 	} else {
139ebb06be1SAl Viro 		spin_unlock_irq(&dev->lock);
1401b2b03f8SKarsten Keil 	}
1411b2b03f8SKarsten Keil 	return ret;
1421b2b03f8SKarsten Keil }
1431b2b03f8SKarsten Keil 
1441b2b03f8SKarsten Keil static unsigned int
1451b2b03f8SKarsten Keil mISDN_poll(struct file *filep, poll_table *wait)
1461b2b03f8SKarsten Keil {
1471b2b03f8SKarsten Keil 	struct mISDNtimerdev	*dev = filep->private_data;
1481b2b03f8SKarsten Keil 	unsigned int		mask = POLLERR;
1491b2b03f8SKarsten Keil 
1501b2b03f8SKarsten Keil 	if (*debug & DEBUG_TIMER)
1511b2b03f8SKarsten Keil 		printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait);
1521b2b03f8SKarsten Keil 	if (dev) {
1531b2b03f8SKarsten Keil 		poll_wait(filep, &dev->wait, wait);
1541b2b03f8SKarsten Keil 		mask = 0;
1551b2b03f8SKarsten Keil 		if (dev->work || !list_empty(&dev->expired))
1561b2b03f8SKarsten Keil 			mask |= (POLLIN | POLLRDNORM);
1571b2b03f8SKarsten Keil 		if (*debug & DEBUG_TIMER)
1581b2b03f8SKarsten Keil 			printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__,
1591b2b03f8SKarsten Keil 			       dev->work, list_empty(&dev->expired));
1601b2b03f8SKarsten Keil 	}
1611b2b03f8SKarsten Keil 	return mask;
1621b2b03f8SKarsten Keil }
1631b2b03f8SKarsten Keil 
1641b2b03f8SKarsten Keil static void
165ce425a9fSAndi Kleen dev_expire_timer(unsigned long data)
1661b2b03f8SKarsten Keil {
167ce425a9fSAndi Kleen 	struct mISDNtimer *timer = (void *)data;
1681b2b03f8SKarsten Keil 	u_long			flags;
1691b2b03f8SKarsten Keil 
1701b2b03f8SKarsten Keil 	spin_lock_irqsave(&timer->dev->lock, flags);
1711b108956SAl Viro 	if (timer->id >= 0)
172211174eaSEric Sesterhenn 		list_move_tail(&timer->list, &timer->dev->expired);
1731b2b03f8SKarsten Keil 	spin_unlock_irqrestore(&timer->dev->lock, flags);
1741b2b03f8SKarsten Keil 	wake_up_interruptible(&timer->dev->wait);
1751b2b03f8SKarsten Keil }
1761b2b03f8SKarsten Keil 
1771b2b03f8SKarsten Keil static int
1781b2b03f8SKarsten Keil misdn_add_timer(struct mISDNtimerdev *dev, int timeout)
1791b2b03f8SKarsten Keil {
1801b2b03f8SKarsten Keil 	int			id;
1811b2b03f8SKarsten Keil 	struct mISDNtimer	*timer;
1821b2b03f8SKarsten Keil 
1831b2b03f8SKarsten Keil 	if (!timeout) {
1841b2b03f8SKarsten Keil 		dev->work = 1;
1851b2b03f8SKarsten Keil 		wake_up_interruptible(&dev->wait);
1861b2b03f8SKarsten Keil 		id = 0;
1871b2b03f8SKarsten Keil 	} else {
1881b2b03f8SKarsten Keil 		timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL);
1891b2b03f8SKarsten Keil 		if (!timer)
1901b2b03f8SKarsten Keil 			return -ENOMEM;
1911678ec00SAl Viro 		timer->dev = dev;
1921678ec00SAl Viro 		setup_timer(&timer->tl, dev_expire_timer, (long)timer);
1931678ec00SAl Viro 		spin_lock_irq(&dev->lock);
1941678ec00SAl Viro 		id = timer->id = dev->next_id++;
1951b2b03f8SKarsten Keil 		if (dev->next_id < 0)
1961b2b03f8SKarsten Keil 			dev->next_id = 1;
1971b2b03f8SKarsten Keil 		list_add_tail(&timer->list, &dev->pending);
1981b2b03f8SKarsten Keil 		timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000);
1991b2b03f8SKarsten Keil 		add_timer(&timer->tl);
2001678ec00SAl Viro 		spin_unlock_irq(&dev->lock);
2011b2b03f8SKarsten Keil 	}
2021b2b03f8SKarsten Keil 	return id;
2031b2b03f8SKarsten Keil }
2041b2b03f8SKarsten Keil 
2051b2b03f8SKarsten Keil static int
2061b2b03f8SKarsten Keil misdn_del_timer(struct mISDNtimerdev *dev, int id)
2071b2b03f8SKarsten Keil {
2081b2b03f8SKarsten Keil 	struct mISDNtimer	*timer;
2091b2b03f8SKarsten Keil 
2101b108956SAl Viro 	spin_lock_irq(&dev->lock);
2111b2b03f8SKarsten Keil 	list_for_each_entry(timer, &dev->pending, list) {
2121b2b03f8SKarsten Keil 		if (timer->id == id) {
2131b2b03f8SKarsten Keil 			list_del_init(&timer->list);
2141b108956SAl Viro 			timer->id = -1;
2151b108956SAl Viro 			spin_unlock_irq(&dev->lock);
2161b108956SAl Viro 			del_timer_sync(&timer->tl);
2171b2b03f8SKarsten Keil 			kfree(timer);
2181b108956SAl Viro 			return id;
2191b2b03f8SKarsten Keil 		}
2201b2b03f8SKarsten Keil 	}
2211b108956SAl Viro 	spin_unlock_irq(&dev->lock);
2221b108956SAl Viro 	return 0;
2231b2b03f8SKarsten Keil }
2241b2b03f8SKarsten Keil 
225703c631eSArnd Bergmann static long
226703c631eSArnd Bergmann mISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
2271b2b03f8SKarsten Keil {
2281b2b03f8SKarsten Keil 	struct mISDNtimerdev	*dev = filep->private_data;
2291b2b03f8SKarsten Keil 	int			id, tout, ret = 0;
2301b2b03f8SKarsten Keil 
2311b2b03f8SKarsten Keil 
2321b2b03f8SKarsten Keil 	if (*debug & DEBUG_TIMER)
2331b2b03f8SKarsten Keil 		printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__,
2341b2b03f8SKarsten Keil 		       filep, cmd, arg);
23576a64921SArnd Bergmann 	mutex_lock(&mISDN_mutex);
2361b2b03f8SKarsten Keil 	switch (cmd) {
2371b2b03f8SKarsten Keil 	case IMADDTIMER:
2381b2b03f8SKarsten Keil 		if (get_user(tout, (int __user *)arg)) {
2391b2b03f8SKarsten Keil 			ret = -EFAULT;
2401b2b03f8SKarsten Keil 			break;
2411b2b03f8SKarsten Keil 		}
2421b2b03f8SKarsten Keil 		id = misdn_add_timer(dev, tout);
2431b2b03f8SKarsten Keil 		if (*debug & DEBUG_TIMER)
2441b2b03f8SKarsten Keil 			printk(KERN_DEBUG "%s add %d id %d\n", __func__,
2451b2b03f8SKarsten Keil 			       tout, id);
2461b2b03f8SKarsten Keil 		if (id < 0) {
2471b2b03f8SKarsten Keil 			ret = id;
2481b2b03f8SKarsten Keil 			break;
2491b2b03f8SKarsten Keil 		}
2501b2b03f8SKarsten Keil 		if (put_user(id, (int __user *)arg))
2511b2b03f8SKarsten Keil 			ret = -EFAULT;
2521b2b03f8SKarsten Keil 		break;
2531b2b03f8SKarsten Keil 	case IMDELTIMER:
2541b2b03f8SKarsten Keil 		if (get_user(id, (int __user *)arg)) {
2551b2b03f8SKarsten Keil 			ret = -EFAULT;
2561b2b03f8SKarsten Keil 			break;
2571b2b03f8SKarsten Keil 		}
2581b2b03f8SKarsten Keil 		if (*debug & DEBUG_TIMER)
2591b2b03f8SKarsten Keil 			printk(KERN_DEBUG "%s del id %d\n", __func__, id);
2601b2b03f8SKarsten Keil 		id = misdn_del_timer(dev, id);
2611b2b03f8SKarsten Keil 		if (put_user(id, (int __user *)arg))
2621b2b03f8SKarsten Keil 			ret = -EFAULT;
2631b2b03f8SKarsten Keil 		break;
2641b2b03f8SKarsten Keil 	default:
2651b2b03f8SKarsten Keil 		ret = -EINVAL;
2661b2b03f8SKarsten Keil 	}
26776a64921SArnd Bergmann 	mutex_unlock(&mISDN_mutex);
2681b2b03f8SKarsten Keil 	return ret;
2691b2b03f8SKarsten Keil }
2701b2b03f8SKarsten Keil 
271eac74af9SKarsten Keil static const struct file_operations mISDN_fops = {
2721b2b03f8SKarsten Keil 	.read		= mISDN_read,
2731b2b03f8SKarsten Keil 	.poll		= mISDN_poll,
274703c631eSArnd Bergmann 	.unlocked_ioctl	= mISDN_ioctl,
2751b2b03f8SKarsten Keil 	.open		= mISDN_open,
2761b2b03f8SKarsten Keil 	.release	= mISDN_close,
2776038f373SArnd Bergmann 	.llseek		= no_llseek,
2781b2b03f8SKarsten Keil };
2791b2b03f8SKarsten Keil 
2801b2b03f8SKarsten Keil static struct miscdevice mISDNtimer = {
2811b2b03f8SKarsten Keil 	.minor	= MISC_DYNAMIC_MINOR,
2821b2b03f8SKarsten Keil 	.name	= "mISDNtimer",
2831b2b03f8SKarsten Keil 	.fops	= &mISDN_fops,
2841b2b03f8SKarsten Keil };
2851b2b03f8SKarsten Keil 
2861b2b03f8SKarsten Keil int
287dfa96ec1SHannes Eder mISDN_inittimer(u_int *deb)
2881b2b03f8SKarsten Keil {
2891b2b03f8SKarsten Keil 	int	err;
2901b2b03f8SKarsten Keil 
2911b2b03f8SKarsten Keil 	debug = deb;
2921b2b03f8SKarsten Keil 	err = misc_register(&mISDNtimer);
2931b2b03f8SKarsten Keil 	if (err)
2941b2b03f8SKarsten Keil 		printk(KERN_WARNING "mISDN: Could not register timer device\n");
2951b2b03f8SKarsten Keil 	return err;
2961b2b03f8SKarsten Keil }
2971b2b03f8SKarsten Keil 
2981b2b03f8SKarsten Keil void mISDN_timer_cleanup(void)
2991b2b03f8SKarsten Keil {
3001b2b03f8SKarsten Keil 	misc_deregister(&mISDNtimer);
3011b2b03f8SKarsten Keil }
302