diff options
Diffstat (limited to 'drivers/isdn/mISDN/timerdev.c')
-rw-r--r-- | drivers/isdn/mISDN/timerdev.c | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/drivers/isdn/mISDN/timerdev.c b/drivers/isdn/mISDN/timerdev.c new file mode 100644 index 000000000000..b5fabc7019d8 --- /dev/null +++ b/drivers/isdn/mISDN/timerdev.c | |||
@@ -0,0 +1,301 @@ | |||
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/timer.h> | ||
23 | #include <linux/miscdevice.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/mISDNif.h> | ||
26 | |||
27 | static int *debug; | ||
28 | |||
29 | |||
30 | struct mISDNtimerdev { | ||
31 | int next_id; | ||
32 | struct list_head pending; | ||
33 | struct list_head expired; | ||
34 | wait_queue_head_t wait; | ||
35 | u_int work; | ||
36 | spinlock_t lock; /* protect lists */ | ||
37 | }; | ||
38 | |||
39 | struct mISDNtimer { | ||
40 | struct list_head list; | ||
41 | struct mISDNtimerdev *dev; | ||
42 | struct timer_list tl; | ||
43 | int id; | ||
44 | }; | ||
45 | |||
46 | static int | ||
47 | mISDN_open(struct inode *ino, struct file *filep) | ||
48 | { | ||
49 | struct mISDNtimerdev *dev; | ||
50 | |||
51 | if (*debug & DEBUG_TIMER) | ||
52 | printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); | ||
53 | dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL); | ||
54 | if (!dev) | ||
55 | return -ENOMEM; | ||
56 | dev->next_id = 1; | ||
57 | INIT_LIST_HEAD(&dev->pending); | ||
58 | INIT_LIST_HEAD(&dev->expired); | ||
59 | spin_lock_init(&dev->lock); | ||
60 | dev->work = 0; | ||
61 | init_waitqueue_head(&dev->wait); | ||
62 | filep->private_data = dev; | ||
63 | __module_get(THIS_MODULE); | ||
64 | return 0; | ||
65 | } | ||
66 | |||
67 | static int | ||
68 | mISDN_close(struct inode *ino, struct file *filep) | ||
69 | { | ||
70 | struct mISDNtimerdev *dev = filep->private_data; | ||
71 | struct mISDNtimer *timer, *next; | ||
72 | |||
73 | if (*debug & DEBUG_TIMER) | ||
74 | printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); | ||
75 | list_for_each_entry_safe(timer, next, &dev->pending, list) { | ||
76 | del_timer(&timer->tl); | ||
77 | kfree(timer); | ||
78 | } | ||
79 | list_for_each_entry_safe(timer, next, &dev->expired, list) { | ||
80 | kfree(timer); | ||
81 | } | ||
82 | kfree(dev); | ||
83 | module_put(THIS_MODULE); | ||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | static ssize_t | ||
88 | mISDN_read(struct file *filep, char *buf, size_t count, loff_t *off) | ||
89 | { | ||
90 | struct mISDNtimerdev *dev = filep->private_data; | ||
91 | struct mISDNtimer *timer; | ||
92 | u_long flags; | ||
93 | int ret = 0; | ||
94 | |||
95 | if (*debug & DEBUG_TIMER) | ||
96 | printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__, | ||
97 | filep, buf, (int)count, off); | ||
98 | if (*off != filep->f_pos) | ||
99 | return -ESPIPE; | ||
100 | |||
101 | if (list_empty(&dev->expired) && (dev->work == 0)) { | ||
102 | if (filep->f_flags & O_NONBLOCK) | ||
103 | return -EAGAIN; | ||
104 | wait_event_interruptible(dev->wait, (dev->work || | ||
105 | !list_empty(&dev->expired))); | ||
106 | if (signal_pending(current)) | ||
107 | return -ERESTARTSYS; | ||
108 | } | ||
109 | if (count < sizeof(int)) | ||
110 | return -ENOSPC; | ||
111 | if (dev->work) | ||
112 | dev->work = 0; | ||
113 | if (!list_empty(&dev->expired)) { | ||
114 | spin_lock_irqsave(&dev->lock, flags); | ||
115 | timer = (struct mISDNtimer *)dev->expired.next; | ||
116 | list_del(&timer->list); | ||
117 | spin_unlock_irqrestore(&dev->lock, flags); | ||
118 | if (put_user(timer->id, (int *)buf)) | ||
119 | ret = -EFAULT; | ||
120 | else | ||
121 | ret = sizeof(int); | ||
122 | kfree(timer); | ||
123 | } | ||
124 | return ret; | ||
125 | } | ||
126 | |||
127 | static loff_t | ||
128 | mISDN_llseek(struct file *filep, loff_t offset, int orig) | ||
129 | { | ||
130 | return -ESPIPE; | ||
131 | } | ||
132 | |||
133 | static ssize_t | ||
134 | mISDN_write(struct file *filep, const char *buf, size_t count, loff_t *off) | ||
135 | { | ||
136 | return -EOPNOTSUPP; | ||
137 | } | ||
138 | |||
139 | static unsigned int | ||
140 | mISDN_poll(struct file *filep, poll_table *wait) | ||
141 | { | ||
142 | struct mISDNtimerdev *dev = filep->private_data; | ||
143 | unsigned int mask = POLLERR; | ||
144 | |||
145 | if (*debug & DEBUG_TIMER) | ||
146 | printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait); | ||
147 | if (dev) { | ||
148 | poll_wait(filep, &dev->wait, wait); | ||
149 | mask = 0; | ||
150 | if (dev->work || !list_empty(&dev->expired)) | ||
151 | mask |= (POLLIN | POLLRDNORM); | ||
152 | if (*debug & DEBUG_TIMER) | ||
153 | printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__, | ||
154 | dev->work, list_empty(&dev->expired)); | ||
155 | } | ||
156 | return mask; | ||
157 | } | ||
158 | |||
159 | static void | ||
160 | dev_expire_timer(struct mISDNtimer *timer) | ||
161 | { | ||
162 | u_long flags; | ||
163 | |||
164 | spin_lock_irqsave(&timer->dev->lock, flags); | ||
165 | list_del(&timer->list); | ||
166 | list_add_tail(&timer->list, &timer->dev->expired); | ||
167 | spin_unlock_irqrestore(&timer->dev->lock, flags); | ||
168 | wake_up_interruptible(&timer->dev->wait); | ||
169 | } | ||
170 | |||
171 | static int | ||
172 | misdn_add_timer(struct mISDNtimerdev *dev, int timeout) | ||
173 | { | ||
174 | int id; | ||
175 | u_long flags; | ||
176 | struct mISDNtimer *timer; | ||
177 | |||
178 | if (!timeout) { | ||
179 | dev->work = 1; | ||
180 | wake_up_interruptible(&dev->wait); | ||
181 | id = 0; | ||
182 | } else { | ||
183 | timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL); | ||
184 | if (!timer) | ||
185 | return -ENOMEM; | ||
186 | spin_lock_irqsave(&dev->lock, flags); | ||
187 | timer->id = dev->next_id++; | ||
188 | if (dev->next_id < 0) | ||
189 | dev->next_id = 1; | ||
190 | list_add_tail(&timer->list, &dev->pending); | ||
191 | spin_unlock_irqrestore(&dev->lock, flags); | ||
192 | timer->dev = dev; | ||
193 | timer->tl.data = (long)timer; | ||
194 | timer->tl.function = (void *) dev_expire_timer; | ||
195 | init_timer(&timer->tl); | ||
196 | timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000); | ||
197 | add_timer(&timer->tl); | ||
198 | id = timer->id; | ||
199 | } | ||
200 | return id; | ||
201 | } | ||
202 | |||
203 | static int | ||
204 | misdn_del_timer(struct mISDNtimerdev *dev, int id) | ||
205 | { | ||
206 | u_long flags; | ||
207 | struct mISDNtimer *timer; | ||
208 | int ret = 0; | ||
209 | |||
210 | spin_lock_irqsave(&dev->lock, flags); | ||
211 | list_for_each_entry(timer, &dev->pending, list) { | ||
212 | if (timer->id == id) { | ||
213 | list_del_init(&timer->list); | ||
214 | del_timer(&timer->tl); | ||
215 | ret = timer->id; | ||
216 | kfree(timer); | ||
217 | goto unlock; | ||
218 | } | ||
219 | } | ||
220 | unlock: | ||
221 | spin_unlock_irqrestore(&dev->lock, flags); | ||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | static int | ||
226 | mISDN_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, | ||
227 | unsigned long arg) | ||
228 | { | ||
229 | struct mISDNtimerdev *dev = filep->private_data; | ||
230 | int id, tout, ret = 0; | ||
231 | |||
232 | |||
233 | if (*debug & DEBUG_TIMER) | ||
234 | printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__, | ||
235 | filep, cmd, arg); | ||
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 | return ret; | ||
268 | } | ||
269 | |||
270 | static struct file_operations mISDN_fops = { | ||
271 | .llseek = mISDN_llseek, | ||
272 | .read = mISDN_read, | ||
273 | .write = mISDN_write, | ||
274 | .poll = mISDN_poll, | ||
275 | .ioctl = mISDN_ioctl, | ||
276 | .open = mISDN_open, | ||
277 | .release = mISDN_close, | ||
278 | }; | ||
279 | |||
280 | static struct miscdevice mISDNtimer = { | ||
281 | .minor = MISC_DYNAMIC_MINOR, | ||
282 | .name = "mISDNtimer", | ||
283 | .fops = &mISDN_fops, | ||
284 | }; | ||
285 | |||
286 | int | ||
287 | mISDN_inittimer(int *deb) | ||
288 | { | ||
289 | int err; | ||
290 | |||
291 | debug = deb; | ||
292 | err = misc_register(&mISDNtimer); | ||
293 | if (err) | ||
294 | printk(KERN_WARNING "mISDN: Could not register timer device\n"); | ||
295 | return err; | ||
296 | } | ||
297 | |||
298 | void mISDN_timer_cleanup(void) | ||
299 | { | ||
300 | misc_deregister(&mISDNtimer); | ||
301 | } | ||