GNU Linux-libre 4.14.290-gnu1
[releases.git] / drivers / isdn / mISDN / timerdev.c
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
32 static DEFINE_MUTEX(mISDN_mutex);
33 static u_int    *debug;
34
35
36 struct 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
45 struct mISDNtimer {
46         struct list_head        list;
47         struct  mISDNtimerdev   *dev;
48         struct timer_list       tl;
49         int                     id;
50 };
51
52 static int
53 mISDN_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
72 static int
73 mISDN_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
101 static ssize_t
102 mISDN_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
144 static unsigned int
145 mISDN_poll(struct file *filep, poll_table *wait)
146 {
147         struct mISDNtimerdev    *dev = filep->private_data;
148         unsigned int            mask = POLLERR;
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 |= (POLLIN | POLLRDNORM);
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
164 static void
165 dev_expire_timer(unsigned long data)
166 {
167         struct mISDNtimer *timer = (void *)data;
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         wake_up_interruptible(&timer->dev->wait);
174         spin_unlock_irqrestore(&timer->dev->lock, flags);
175 }
176
177 static int
178 misdn_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                 setup_timer(&timer->tl, dev_expire_timer, (long)timer);
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
205 static int
206 misdn_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
225 static long
226 mISDN_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
271 static 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
281 static struct miscdevice mISDNtimer = {
282         .minor  = MISC_DYNAMIC_MINOR,
283         .name   = "mISDNtimer",
284         .fops   = &mISDN_fops,
285 };
286
287 int
288 mISDN_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
299 void mISDN_timer_cleanup(void)
300 {
301         misc_deregister(&mISDNtimer);
302 }