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(size: sizeof(struct mISDNtimerdev) , GFP_KERNEL); |
51 | if (!dev) |
52 | return -ENOMEM; |
53 | dev->next_id = 1; |
54 | INIT_LIST_HEAD(list: &dev->pending); |
55 | INIT_LIST_HEAD(list: &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(inode: ino, filp: 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(lock: &dev->lock); |
74 | while (!list_empty(head: list)) { |
75 | timer = list_first_entry(list, struct mISDNtimer, list); |
76 | spin_unlock_irq(lock: &dev->lock); |
77 | timer_shutdown_sync(timer: &timer->tl); |
78 | spin_lock_irq(lock: &dev->lock); |
79 | /* it might have been moved to ->expired */ |
80 | list_del(entry: &timer->list); |
81 | kfree(objp: timer); |
82 | } |
83 | spin_unlock_irq(lock: &dev->lock); |
84 | |
85 | list_for_each_entry_safe(timer, next, &dev->expired, list) { |
86 | kfree(objp: timer); |
87 | } |
88 | kfree(objp: 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(lock: &dev->lock); |
108 | while (list_empty(head: list) && (dev->work == 0)) { |
109 | spin_unlock_irq(lock: &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(lock: &dev->lock); |
117 | } |
118 | if (dev->work) |
119 | dev->work = 0; |
120 | if (!list_empty(head: list)) { |
121 | timer = list_first_entry(list, struct mISDNtimer, list); |
122 | list_del(entry: &timer->list); |
123 | spin_unlock_irq(lock: &dev->lock); |
124 | if (put_user(timer->id, (int __user *)buf)) |
125 | ret = -EFAULT; |
126 | else |
127 | ret = sizeof(int); |
128 | kfree(objp: timer); |
129 | } else { |
130 | spin_unlock_irq(lock: &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(filp: filep, wait_address: &dev->wait, p: wait); |
145 | mask = 0; |
146 | if (dev->work || !list_empty(head: &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(list: &timer->list, head: &timer->dev->expired); |
164 | wake_up_interruptible(&timer->dev->wait); |
165 | spin_unlock_irqrestore(lock: &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(size: 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(lock: &dev->lock); |
185 | id = timer->id = dev->next_id++; |
186 | if (dev->next_id < 0) |
187 | dev->next_id = 1; |
188 | list_add_tail(new: &timer->list, head: &dev->pending); |
189 | timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000); |
190 | add_timer(timer: &timer->tl); |
191 | spin_unlock_irq(lock: &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(lock: &dev->lock); |
202 | list_for_each_entry(timer, &dev->pending, list) { |
203 | if (timer->id == id) { |
204 | list_del_init(entry: &timer->list); |
205 | timer->id = -1; |
206 | spin_unlock_irq(lock: &dev->lock); |
207 | timer_shutdown_sync(timer: &timer->tl); |
208 | kfree(objp: timer); |
209 | return id; |
210 | } |
211 | } |
212 | spin_unlock_irq(lock: &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, timeout: 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(lock: &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(misc: &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(misc: &mISDNtimer); |
293 | } |
294 | |