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