]>
Commit | Line | Data |
---|---|---|
1b2b03f8 KK |
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> | |
5a0e3ad6 | 22 | #include <linux/slab.h> |
1b2b03f8 KK |
23 | #include <linux/timer.h> |
24 | #include <linux/miscdevice.h> | |
25 | #include <linux/module.h> | |
26 | #include <linux/mISDNif.h> | |
76a64921 | 27 | #include <linux/mutex.h> |
174cd4b1 IM |
28 | #include <linux/sched/signal.h> |
29 | ||
5b834354 | 30 | #include "core.h" |
1b2b03f8 | 31 | |
76a64921 | 32 | static DEFINE_MUTEX(mISDN_mutex); |
dfa96ec1 | 33 | static u_int *debug; |
1b2b03f8 KK |
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; | |
6bff338b | 69 | return nonseekable_open(ino, filep); |
1b2b03f8 KK |
70 | } |
71 | ||
72 | static int | |
73 | mISDN_close(struct inode *ino, struct file *filep) | |
74 | { | |
75 | struct mISDNtimerdev *dev = filep->private_data; | |
c08c464d | 76 | struct list_head *list = &dev->pending; |
1b2b03f8 KK |
77 | struct mISDNtimer *timer, *next; |
78 | ||
79 | if (*debug & DEBUG_TIMER) | |
80 | printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); | |
c08c464d AV |
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); | |
1b2b03f8 KK |
90 | kfree(timer); |
91 | } | |
c08c464d AV |
92 | spin_unlock_irq(&dev->lock); |
93 | ||
1b2b03f8 KK |
94 | list_for_each_entry_safe(timer, next, &dev->expired, list) { |
95 | kfree(timer); | |
96 | } | |
97 | kfree(dev); | |
1b2b03f8 KK |
98 | return 0; |
99 | } | |
100 | ||
101 | static ssize_t | |
c46f0a2d | 102 | mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off) |
1b2b03f8 KK |
103 | { |
104 | struct mISDNtimerdev *dev = filep->private_data; | |
ebb06be1 | 105 | struct list_head *list = &dev->expired; |
1b2b03f8 | 106 | struct mISDNtimer *timer; |
1b2b03f8 KK |
107 | int ret = 0; |
108 | ||
109 | if (*debug & DEBUG_TIMER) | |
110 | printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__, | |
475be4d8 | 111 | filep, buf, (int)count, off); |
1b2b03f8 | 112 | |
ebb06be1 AV |
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); | |
1b2b03f8 KK |
119 | if (filep->f_flags & O_NONBLOCK) |
120 | return -EAGAIN; | |
121 | wait_event_interruptible(dev->wait, (dev->work || | |
ebb06be1 | 122 | !list_empty(list))); |
1b2b03f8 KK |
123 | if (signal_pending(current)) |
124 | return -ERESTARTSYS; | |
ebb06be1 | 125 | spin_lock_irq(&dev->lock); |
1b2b03f8 | 126 | } |
1b2b03f8 KK |
127 | if (dev->work) |
128 | dev->work = 0; | |
ebb06be1 AV |
129 | if (!list_empty(list)) { |
130 | timer = list_first_entry(list, struct mISDNtimer, list); | |
1b2b03f8 | 131 | list_del(&timer->list); |
ebb06be1 | 132 | spin_unlock_irq(&dev->lock); |
c46f0a2d | 133 | if (put_user(timer->id, (int __user *)buf)) |
1b2b03f8 KK |
134 | ret = -EFAULT; |
135 | else | |
136 | ret = sizeof(int); | |
137 | kfree(timer); | |
ebb06be1 AV |
138 | } else { |
139 | spin_unlock_irq(&dev->lock); | |
1b2b03f8 KK |
140 | } |
141 | return ret; | |
142 | } | |
143 | ||
1b2b03f8 KK |
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__, | |
475be4d8 | 159 | dev->work, list_empty(&dev->expired)); |
1b2b03f8 KK |
160 | } |
161 | return mask; | |
162 | } | |
163 | ||
164 | static void | |
e313ac12 | 165 | dev_expire_timer(struct timer_list *t) |
1b2b03f8 | 166 | { |
e313ac12 | 167 | struct mISDNtimer *timer = from_timer(timer, t, tl); |
1b2b03f8 KK |
168 | u_long flags; |
169 | ||
170 | spin_lock_irqsave(&timer->dev->lock, flags); | |
1b108956 AV |
171 | if (timer->id >= 0) |
172 | list_move_tail(&timer->list, &timer->dev->expired); | |
1b2b03f8 KK |
173 | spin_unlock_irqrestore(&timer->dev->lock, flags); |
174 | wake_up_interruptible(&timer->dev->wait); | |
175 | } | |
176 | ||
177 | static int | |
178 | misdn_add_timer(struct mISDNtimerdev *dev, int timeout) | |
179 | { | |
475be4d8 | 180 | int id; |
1b2b03f8 KK |
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; | |
1678ec00 | 191 | timer->dev = dev; |
e313ac12 | 192 | timer_setup(&timer->tl, dev_expire_timer, 0); |
1678ec00 AV |
193 | spin_lock_irq(&dev->lock); |
194 | id = timer->id = dev->next_id++; | |
1b2b03f8 KK |
195 | if (dev->next_id < 0) |
196 | dev->next_id = 1; | |
197 | list_add_tail(&timer->list, &dev->pending); | |
1b2b03f8 KK |
198 | timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000); |
199 | add_timer(&timer->tl); | |
1678ec00 | 200 | spin_unlock_irq(&dev->lock); |
1b2b03f8 KK |
201 | } |
202 | return id; | |
203 | } | |
204 | ||
205 | static int | |
206 | misdn_del_timer(struct mISDNtimerdev *dev, int id) | |
207 | { | |
1b2b03f8 | 208 | struct mISDNtimer *timer; |
1b2b03f8 | 209 | |
1b108956 | 210 | spin_lock_irq(&dev->lock); |
1b2b03f8 KK |
211 | list_for_each_entry(timer, &dev->pending, list) { |
212 | if (timer->id == id) { | |
213 | list_del_init(&timer->list); | |
1b108956 AV |
214 | timer->id = -1; |
215 | spin_unlock_irq(&dev->lock); | |
216 | del_timer_sync(&timer->tl); | |
1b2b03f8 | 217 | kfree(timer); |
1b108956 | 218 | return id; |
1b2b03f8 KK |
219 | } |
220 | } | |
1b108956 AV |
221 | spin_unlock_irq(&dev->lock); |
222 | return 0; | |
1b2b03f8 KK |
223 | } |
224 | ||
703c631e AB |
225 | static long |
226 | mISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) | |
1b2b03f8 KK |
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__, | |
475be4d8 | 234 | filep, cmd, arg); |
76a64921 | 235 | mutex_lock(&mISDN_mutex); |
1b2b03f8 KK |
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__, | |
475be4d8 | 245 | tout, id); |
1b2b03f8 KK |
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 | } | |
76a64921 | 267 | mutex_unlock(&mISDN_mutex); |
1b2b03f8 KK |
268 | return ret; |
269 | } | |
270 | ||
eac74af9 | 271 | static const struct file_operations mISDN_fops = { |
89b107ad | 272 | .owner = THIS_MODULE, |
1b2b03f8 | 273 | .read = mISDN_read, |
1b2b03f8 | 274 | .poll = mISDN_poll, |
703c631e | 275 | .unlocked_ioctl = mISDN_ioctl, |
1b2b03f8 KK |
276 | .open = mISDN_open, |
277 | .release = mISDN_close, | |
6038f373 | 278 | .llseek = no_llseek, |
1b2b03f8 KK |
279 | }; |
280 | ||
281 | static struct miscdevice mISDNtimer = { | |
282 | .minor = MISC_DYNAMIC_MINOR, | |
283 | .name = "mISDNtimer", | |
284 | .fops = &mISDN_fops, | |
285 | }; | |
286 | ||
287 | int | |
dfa96ec1 | 288 | mISDN_inittimer(u_int *deb) |
1b2b03f8 KK |
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 | } |