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