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