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> | |
22 | #include <linux/timer.h> | |
23 | #include <linux/miscdevice.h> | |
24 | #include <linux/module.h> | |
25 | #include <linux/mISDNif.h> | |
5b834354 | 26 | #include "core.h" |
1b2b03f8 | 27 | |
dfa96ec1 | 28 | static u_int *debug; |
1b2b03f8 KK |
29 | |
30 | ||
31 | struct mISDNtimerdev { | |
32 | int next_id; | |
33 | struct list_head pending; | |
34 | struct list_head expired; | |
35 | wait_queue_head_t wait; | |
36 | u_int work; | |
37 | spinlock_t lock; /* protect lists */ | |
38 | }; | |
39 | ||
40 | struct mISDNtimer { | |
41 | struct list_head list; | |
42 | struct mISDNtimerdev *dev; | |
43 | struct timer_list tl; | |
44 | int id; | |
45 | }; | |
46 | ||
47 | static int | |
48 | mISDN_open(struct inode *ino, struct file *filep) | |
49 | { | |
50 | struct mISDNtimerdev *dev; | |
51 | ||
52 | if (*debug & DEBUG_TIMER) | |
53 | printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); | |
54 | dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL); | |
55 | if (!dev) | |
56 | return -ENOMEM; | |
57 | dev->next_id = 1; | |
58 | INIT_LIST_HEAD(&dev->pending); | |
59 | INIT_LIST_HEAD(&dev->expired); | |
60 | spin_lock_init(&dev->lock); | |
61 | dev->work = 0; | |
62 | init_waitqueue_head(&dev->wait); | |
63 | filep->private_data = dev; | |
64 | __module_get(THIS_MODULE); | |
6bff338b | 65 | return nonseekable_open(ino, filep); |
1b2b03f8 KK |
66 | } |
67 | ||
68 | static int | |
69 | mISDN_close(struct inode *ino, struct file *filep) | |
70 | { | |
71 | struct mISDNtimerdev *dev = filep->private_data; | |
72 | struct mISDNtimer *timer, *next; | |
73 | ||
74 | if (*debug & DEBUG_TIMER) | |
75 | printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); | |
76 | list_for_each_entry_safe(timer, next, &dev->pending, list) { | |
77 | del_timer(&timer->tl); | |
78 | kfree(timer); | |
79 | } | |
80 | list_for_each_entry_safe(timer, next, &dev->expired, list) { | |
81 | kfree(timer); | |
82 | } | |
83 | kfree(dev); | |
84 | module_put(THIS_MODULE); | |
85 | return 0; | |
86 | } | |
87 | ||
88 | static ssize_t | |
c46f0a2d | 89 | mISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off) |
1b2b03f8 KK |
90 | { |
91 | struct mISDNtimerdev *dev = filep->private_data; | |
92 | struct mISDNtimer *timer; | |
93 | u_long flags; | |
94 | int ret = 0; | |
95 | ||
96 | if (*debug & DEBUG_TIMER) | |
97 | printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__, | |
98 | filep, buf, (int)count, off); | |
99 | if (*off != filep->f_pos) | |
100 | return -ESPIPE; | |
101 | ||
102 | if (list_empty(&dev->expired) && (dev->work == 0)) { | |
103 | if (filep->f_flags & O_NONBLOCK) | |
104 | return -EAGAIN; | |
105 | wait_event_interruptible(dev->wait, (dev->work || | |
106 | !list_empty(&dev->expired))); | |
107 | if (signal_pending(current)) | |
108 | return -ERESTARTSYS; | |
109 | } | |
110 | if (count < sizeof(int)) | |
111 | return -ENOSPC; | |
112 | if (dev->work) | |
113 | dev->work = 0; | |
114 | if (!list_empty(&dev->expired)) { | |
115 | spin_lock_irqsave(&dev->lock, flags); | |
116 | timer = (struct mISDNtimer *)dev->expired.next; | |
117 | list_del(&timer->list); | |
118 | spin_unlock_irqrestore(&dev->lock, flags); | |
c46f0a2d | 119 | if (put_user(timer->id, (int __user *)buf)) |
1b2b03f8 KK |
120 | ret = -EFAULT; |
121 | else | |
122 | ret = sizeof(int); | |
123 | kfree(timer); | |
124 | } | |
125 | return ret; | |
126 | } | |
127 | ||
1b2b03f8 KK |
128 | static unsigned int |
129 | mISDN_poll(struct file *filep, poll_table *wait) | |
130 | { | |
131 | struct mISDNtimerdev *dev = filep->private_data; | |
132 | unsigned int mask = POLLERR; | |
133 | ||
134 | if (*debug & DEBUG_TIMER) | |
135 | printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait); | |
136 | if (dev) { | |
137 | poll_wait(filep, &dev->wait, wait); | |
138 | mask = 0; | |
139 | if (dev->work || !list_empty(&dev->expired)) | |
140 | mask |= (POLLIN | POLLRDNORM); | |
141 | if (*debug & DEBUG_TIMER) | |
142 | printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__, | |
143 | dev->work, list_empty(&dev->expired)); | |
144 | } | |
145 | return mask; | |
146 | } | |
147 | ||
148 | static void | |
ce425a9f | 149 | dev_expire_timer(unsigned long data) |
1b2b03f8 | 150 | { |
ce425a9f | 151 | struct mISDNtimer *timer = (void *)data; |
1b2b03f8 KK |
152 | u_long flags; |
153 | ||
154 | spin_lock_irqsave(&timer->dev->lock, flags); | |
211174ea | 155 | list_move_tail(&timer->list, &timer->dev->expired); |
1b2b03f8 KK |
156 | spin_unlock_irqrestore(&timer->dev->lock, flags); |
157 | wake_up_interruptible(&timer->dev->wait); | |
158 | } | |
159 | ||
160 | static int | |
161 | misdn_add_timer(struct mISDNtimerdev *dev, int timeout) | |
162 | { | |
163 | int id; | |
164 | u_long flags; | |
165 | struct mISDNtimer *timer; | |
166 | ||
167 | if (!timeout) { | |
168 | dev->work = 1; | |
169 | wake_up_interruptible(&dev->wait); | |
170 | id = 0; | |
171 | } else { | |
172 | timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL); | |
173 | if (!timer) | |
174 | return -ENOMEM; | |
175 | spin_lock_irqsave(&dev->lock, flags); | |
176 | timer->id = dev->next_id++; | |
177 | if (dev->next_id < 0) | |
178 | dev->next_id = 1; | |
179 | list_add_tail(&timer->list, &dev->pending); | |
180 | spin_unlock_irqrestore(&dev->lock, flags); | |
181 | timer->dev = dev; | |
182 | timer->tl.data = (long)timer; | |
ce425a9f | 183 | timer->tl.function = dev_expire_timer; |
1b2b03f8 KK |
184 | init_timer(&timer->tl); |
185 | timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000); | |
186 | add_timer(&timer->tl); | |
187 | id = timer->id; | |
188 | } | |
189 | return id; | |
190 | } | |
191 | ||
192 | static int | |
193 | misdn_del_timer(struct mISDNtimerdev *dev, int id) | |
194 | { | |
195 | u_long flags; | |
196 | struct mISDNtimer *timer; | |
197 | int ret = 0; | |
198 | ||
199 | spin_lock_irqsave(&dev->lock, flags); | |
200 | list_for_each_entry(timer, &dev->pending, list) { | |
201 | if (timer->id == id) { | |
202 | list_del_init(&timer->list); | |
ce425a9f AK |
203 | /* RED-PEN AK: race -- timer can be still running on |
204 | * other CPU. Needs reference count I think | |
205 | */ | |
1b2b03f8 KK |
206 | del_timer(&timer->tl); |
207 | ret = timer->id; | |
208 | kfree(timer); | |
209 | goto unlock; | |
210 | } | |
211 | } | |
212 | unlock: | |
213 | spin_unlock_irqrestore(&dev->lock, flags); | |
214 | return ret; | |
215 | } | |
216 | ||
217 | static int | |
218 | mISDN_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, | |
219 | unsigned long arg) | |
220 | { | |
221 | struct mISDNtimerdev *dev = filep->private_data; | |
222 | int id, tout, ret = 0; | |
223 | ||
224 | ||
225 | if (*debug & DEBUG_TIMER) | |
226 | printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__, | |
227 | filep, cmd, arg); | |
228 | switch (cmd) { | |
229 | case IMADDTIMER: | |
230 | if (get_user(tout, (int __user *)arg)) { | |
231 | ret = -EFAULT; | |
232 | break; | |
233 | } | |
234 | id = misdn_add_timer(dev, tout); | |
235 | if (*debug & DEBUG_TIMER) | |
236 | printk(KERN_DEBUG "%s add %d id %d\n", __func__, | |
237 | tout, id); | |
238 | if (id < 0) { | |
239 | ret = id; | |
240 | break; | |
241 | } | |
242 | if (put_user(id, (int __user *)arg)) | |
243 | ret = -EFAULT; | |
244 | break; | |
245 | case IMDELTIMER: | |
246 | if (get_user(id, (int __user *)arg)) { | |
247 | ret = -EFAULT; | |
248 | break; | |
249 | } | |
250 | if (*debug & DEBUG_TIMER) | |
251 | printk(KERN_DEBUG "%s del id %d\n", __func__, id); | |
252 | id = misdn_del_timer(dev, id); | |
253 | if (put_user(id, (int __user *)arg)) | |
254 | ret = -EFAULT; | |
255 | break; | |
256 | default: | |
257 | ret = -EINVAL; | |
258 | } | |
259 | return ret; | |
260 | } | |
261 | ||
eac74af9 | 262 | static const struct file_operations mISDN_fops = { |
1b2b03f8 | 263 | .read = mISDN_read, |
1b2b03f8 KK |
264 | .poll = mISDN_poll, |
265 | .ioctl = mISDN_ioctl, | |
266 | .open = mISDN_open, | |
267 | .release = mISDN_close, | |
268 | }; | |
269 | ||
270 | static struct miscdevice mISDNtimer = { | |
271 | .minor = MISC_DYNAMIC_MINOR, | |
272 | .name = "mISDNtimer", | |
273 | .fops = &mISDN_fops, | |
274 | }; | |
275 | ||
276 | int | |
dfa96ec1 | 277 | mISDN_inittimer(u_int *deb) |
1b2b03f8 KK |
278 | { |
279 | int err; | |
280 | ||
281 | debug = deb; | |
282 | err = misc_register(&mISDNtimer); | |
283 | if (err) | |
284 | printk(KERN_WARNING "mISDN: Could not register timer device\n"); | |
285 | return err; | |
286 | } | |
287 | ||
288 | void mISDN_timer_cleanup(void) | |
289 | { | |
290 | misc_deregister(&mISDNtimer); | |
291 | } |