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