18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * general timer device for using in ISDN stacks 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Author Karsten Keil <kkeil@novell.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright 2008 by Karsten Keil <kkeil@novell.com> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/poll.h> 128c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/timer.h> 158c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/mISDNif.h> 188c2ecf20Sopenharmony_ci#include <linux/mutex.h> 198c2ecf20Sopenharmony_ci#include <linux/sched/signal.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include "core.h" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(mISDN_mutex); 248c2ecf20Sopenharmony_cistatic u_int *debug; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistruct mISDNtimerdev { 288c2ecf20Sopenharmony_ci int next_id; 298c2ecf20Sopenharmony_ci struct list_head pending; 308c2ecf20Sopenharmony_ci struct list_head expired; 318c2ecf20Sopenharmony_ci wait_queue_head_t wait; 328c2ecf20Sopenharmony_ci u_int work; 338c2ecf20Sopenharmony_ci spinlock_t lock; /* protect lists */ 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistruct mISDNtimer { 378c2ecf20Sopenharmony_ci struct list_head list; 388c2ecf20Sopenharmony_ci struct mISDNtimerdev *dev; 398c2ecf20Sopenharmony_ci struct timer_list tl; 408c2ecf20Sopenharmony_ci int id; 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic int 448c2ecf20Sopenharmony_cimISDN_open(struct inode *ino, struct file *filep) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci struct mISDNtimerdev *dev; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 498c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); 508c2ecf20Sopenharmony_ci dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL); 518c2ecf20Sopenharmony_ci if (!dev) 528c2ecf20Sopenharmony_ci return -ENOMEM; 538c2ecf20Sopenharmony_ci dev->next_id = 1; 548c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dev->pending); 558c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dev->expired); 568c2ecf20Sopenharmony_ci spin_lock_init(&dev->lock); 578c2ecf20Sopenharmony_ci dev->work = 0; 588c2ecf20Sopenharmony_ci init_waitqueue_head(&dev->wait); 598c2ecf20Sopenharmony_ci filep->private_data = dev; 608c2ecf20Sopenharmony_ci return nonseekable_open(ino, filep); 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic int 648c2ecf20Sopenharmony_cimISDN_close(struct inode *ino, struct file *filep) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct mISDNtimerdev *dev = filep->private_data; 678c2ecf20Sopenharmony_ci struct list_head *list = &dev->pending; 688c2ecf20Sopenharmony_ci struct mISDNtimer *timer, *next; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 718c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci spin_lock_irq(&dev->lock); 748c2ecf20Sopenharmony_ci while (!list_empty(list)) { 758c2ecf20Sopenharmony_ci timer = list_first_entry(list, struct mISDNtimer, list); 768c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 778c2ecf20Sopenharmony_ci del_timer_sync(&timer->tl); 788c2ecf20Sopenharmony_ci spin_lock_irq(&dev->lock); 798c2ecf20Sopenharmony_ci /* it might have been moved to ->expired */ 808c2ecf20Sopenharmony_ci list_del(&timer->list); 818c2ecf20Sopenharmony_ci kfree(timer); 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci list_for_each_entry_safe(timer, next, &dev->expired, list) { 868c2ecf20Sopenharmony_ci kfree(timer); 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci kfree(dev); 898c2ecf20Sopenharmony_ci return 0; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic ssize_t 938c2ecf20Sopenharmony_cimISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct mISDNtimerdev *dev = filep->private_data; 968c2ecf20Sopenharmony_ci struct list_head *list = &dev->expired; 978c2ecf20Sopenharmony_ci struct mISDNtimer *timer; 988c2ecf20Sopenharmony_ci int ret = 0; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 1018c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__, 1028c2ecf20Sopenharmony_ci filep, buf, (int)count, off); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (count < sizeof(int)) 1058c2ecf20Sopenharmony_ci return -ENOSPC; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci spin_lock_irq(&dev->lock); 1088c2ecf20Sopenharmony_ci while (list_empty(list) && (dev->work == 0)) { 1098c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 1108c2ecf20Sopenharmony_ci if (filep->f_flags & O_NONBLOCK) 1118c2ecf20Sopenharmony_ci return -EAGAIN; 1128c2ecf20Sopenharmony_ci wait_event_interruptible(dev->wait, (dev->work || 1138c2ecf20Sopenharmony_ci !list_empty(list))); 1148c2ecf20Sopenharmony_ci if (signal_pending(current)) 1158c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1168c2ecf20Sopenharmony_ci spin_lock_irq(&dev->lock); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci if (dev->work) 1198c2ecf20Sopenharmony_ci dev->work = 0; 1208c2ecf20Sopenharmony_ci if (!list_empty(list)) { 1218c2ecf20Sopenharmony_ci timer = list_first_entry(list, struct mISDNtimer, list); 1228c2ecf20Sopenharmony_ci list_del(&timer->list); 1238c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 1248c2ecf20Sopenharmony_ci if (put_user(timer->id, (int __user *)buf)) 1258c2ecf20Sopenharmony_ci ret = -EFAULT; 1268c2ecf20Sopenharmony_ci else 1278c2ecf20Sopenharmony_ci ret = sizeof(int); 1288c2ecf20Sopenharmony_ci kfree(timer); 1298c2ecf20Sopenharmony_ci } else { 1308c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci return ret; 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic __poll_t 1368c2ecf20Sopenharmony_cimISDN_poll(struct file *filep, poll_table *wait) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci struct mISDNtimerdev *dev = filep->private_data; 1398c2ecf20Sopenharmony_ci __poll_t mask = EPOLLERR; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 1428c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait); 1438c2ecf20Sopenharmony_ci if (dev) { 1448c2ecf20Sopenharmony_ci poll_wait(filep, &dev->wait, wait); 1458c2ecf20Sopenharmony_ci mask = 0; 1468c2ecf20Sopenharmony_ci if (dev->work || !list_empty(&dev->expired)) 1478c2ecf20Sopenharmony_ci mask |= (EPOLLIN | EPOLLRDNORM); 1488c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 1498c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__, 1508c2ecf20Sopenharmony_ci dev->work, list_empty(&dev->expired)); 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci return mask; 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic void 1568c2ecf20Sopenharmony_cidev_expire_timer(struct timer_list *t) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct mISDNtimer *timer = from_timer(timer, t, tl); 1598c2ecf20Sopenharmony_ci u_long flags; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci spin_lock_irqsave(&timer->dev->lock, flags); 1628c2ecf20Sopenharmony_ci if (timer->id >= 0) 1638c2ecf20Sopenharmony_ci list_move_tail(&timer->list, &timer->dev->expired); 1648c2ecf20Sopenharmony_ci wake_up_interruptible(&timer->dev->wait); 1658c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&timer->dev->lock, flags); 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic int 1698c2ecf20Sopenharmony_cimisdn_add_timer(struct mISDNtimerdev *dev, int timeout) 1708c2ecf20Sopenharmony_ci{ 1718c2ecf20Sopenharmony_ci int id; 1728c2ecf20Sopenharmony_ci struct mISDNtimer *timer; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci if (!timeout) { 1758c2ecf20Sopenharmony_ci dev->work = 1; 1768c2ecf20Sopenharmony_ci wake_up_interruptible(&dev->wait); 1778c2ecf20Sopenharmony_ci id = 0; 1788c2ecf20Sopenharmony_ci } else { 1798c2ecf20Sopenharmony_ci timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL); 1808c2ecf20Sopenharmony_ci if (!timer) 1818c2ecf20Sopenharmony_ci return -ENOMEM; 1828c2ecf20Sopenharmony_ci timer->dev = dev; 1838c2ecf20Sopenharmony_ci timer_setup(&timer->tl, dev_expire_timer, 0); 1848c2ecf20Sopenharmony_ci spin_lock_irq(&dev->lock); 1858c2ecf20Sopenharmony_ci id = timer->id = dev->next_id++; 1868c2ecf20Sopenharmony_ci if (dev->next_id < 0) 1878c2ecf20Sopenharmony_ci dev->next_id = 1; 1888c2ecf20Sopenharmony_ci list_add_tail(&timer->list, &dev->pending); 1898c2ecf20Sopenharmony_ci timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000); 1908c2ecf20Sopenharmony_ci add_timer(&timer->tl); 1918c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci return id; 1948c2ecf20Sopenharmony_ci} 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cistatic int 1978c2ecf20Sopenharmony_cimisdn_del_timer(struct mISDNtimerdev *dev, int id) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci struct mISDNtimer *timer; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci spin_lock_irq(&dev->lock); 2028c2ecf20Sopenharmony_ci list_for_each_entry(timer, &dev->pending, list) { 2038c2ecf20Sopenharmony_ci if (timer->id == id) { 2048c2ecf20Sopenharmony_ci list_del_init(&timer->list); 2058c2ecf20Sopenharmony_ci timer->id = -1; 2068c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 2078c2ecf20Sopenharmony_ci del_timer_sync(&timer->tl); 2088c2ecf20Sopenharmony_ci kfree(timer); 2098c2ecf20Sopenharmony_ci return id; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci spin_unlock_irq(&dev->lock); 2138c2ecf20Sopenharmony_ci return 0; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic long 2178c2ecf20Sopenharmony_cimISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci struct mISDNtimerdev *dev = filep->private_data; 2208c2ecf20Sopenharmony_ci int id, tout, ret = 0; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 2248c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__, 2258c2ecf20Sopenharmony_ci filep, cmd, arg); 2268c2ecf20Sopenharmony_ci mutex_lock(&mISDN_mutex); 2278c2ecf20Sopenharmony_ci switch (cmd) { 2288c2ecf20Sopenharmony_ci case IMADDTIMER: 2298c2ecf20Sopenharmony_ci if (get_user(tout, (int __user *)arg)) { 2308c2ecf20Sopenharmony_ci ret = -EFAULT; 2318c2ecf20Sopenharmony_ci break; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci id = misdn_add_timer(dev, tout); 2348c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 2358c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s add %d id %d\n", __func__, 2368c2ecf20Sopenharmony_ci tout, id); 2378c2ecf20Sopenharmony_ci if (id < 0) { 2388c2ecf20Sopenharmony_ci ret = id; 2398c2ecf20Sopenharmony_ci break; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci if (put_user(id, (int __user *)arg)) 2428c2ecf20Sopenharmony_ci ret = -EFAULT; 2438c2ecf20Sopenharmony_ci break; 2448c2ecf20Sopenharmony_ci case IMDELTIMER: 2458c2ecf20Sopenharmony_ci if (get_user(id, (int __user *)arg)) { 2468c2ecf20Sopenharmony_ci ret = -EFAULT; 2478c2ecf20Sopenharmony_ci break; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci if (*debug & DEBUG_TIMER) 2508c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s del id %d\n", __func__, id); 2518c2ecf20Sopenharmony_ci id = misdn_del_timer(dev, id); 2528c2ecf20Sopenharmony_ci if (put_user(id, (int __user *)arg)) 2538c2ecf20Sopenharmony_ci ret = -EFAULT; 2548c2ecf20Sopenharmony_ci break; 2558c2ecf20Sopenharmony_ci default: 2568c2ecf20Sopenharmony_ci ret = -EINVAL; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci mutex_unlock(&mISDN_mutex); 2598c2ecf20Sopenharmony_ci return ret; 2608c2ecf20Sopenharmony_ci} 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic const struct file_operations mISDN_fops = { 2638c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2648c2ecf20Sopenharmony_ci .read = mISDN_read, 2658c2ecf20Sopenharmony_ci .poll = mISDN_poll, 2668c2ecf20Sopenharmony_ci .unlocked_ioctl = mISDN_ioctl, 2678c2ecf20Sopenharmony_ci .open = mISDN_open, 2688c2ecf20Sopenharmony_ci .release = mISDN_close, 2698c2ecf20Sopenharmony_ci .llseek = no_llseek, 2708c2ecf20Sopenharmony_ci}; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic struct miscdevice mISDNtimer = { 2738c2ecf20Sopenharmony_ci .minor = MISC_DYNAMIC_MINOR, 2748c2ecf20Sopenharmony_ci .name = "mISDNtimer", 2758c2ecf20Sopenharmony_ci .fops = &mISDN_fops, 2768c2ecf20Sopenharmony_ci}; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ciint 2798c2ecf20Sopenharmony_cimISDN_inittimer(u_int *deb) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci int err; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci debug = deb; 2848c2ecf20Sopenharmony_ci err = misc_register(&mISDNtimer); 2858c2ecf20Sopenharmony_ci if (err) 2868c2ecf20Sopenharmony_ci printk(KERN_WARNING "mISDN: Could not register timer device\n"); 2878c2ecf20Sopenharmony_ci return err; 2888c2ecf20Sopenharmony_ci} 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_civoid mISDN_timer_cleanup(void) 2918c2ecf20Sopenharmony_ci{ 2928c2ecf20Sopenharmony_ci misc_deregister(&mISDNtimer); 2938c2ecf20Sopenharmony_ci} 294