162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * general timer device for using in ISDN stacks
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Author	Karsten Keil <kkeil@novell.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Copyright 2008  by Karsten Keil <kkeil@novell.com>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/poll.h>
1262306a36Sopenharmony_ci#include <linux/vmalloc.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/timer.h>
1562306a36Sopenharmony_ci#include <linux/miscdevice.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/mISDNif.h>
1862306a36Sopenharmony_ci#include <linux/mutex.h>
1962306a36Sopenharmony_ci#include <linux/sched/signal.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include "core.h"
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic DEFINE_MUTEX(mISDN_mutex);
2462306a36Sopenharmony_cistatic u_int	*debug;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct mISDNtimerdev {
2862306a36Sopenharmony_ci	int			next_id;
2962306a36Sopenharmony_ci	struct list_head	pending;
3062306a36Sopenharmony_ci	struct list_head	expired;
3162306a36Sopenharmony_ci	wait_queue_head_t	wait;
3262306a36Sopenharmony_ci	u_int			work;
3362306a36Sopenharmony_ci	spinlock_t		lock; /* protect lists */
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistruct mISDNtimer {
3762306a36Sopenharmony_ci	struct list_head	list;
3862306a36Sopenharmony_ci	struct  mISDNtimerdev	*dev;
3962306a36Sopenharmony_ci	struct timer_list	tl;
4062306a36Sopenharmony_ci	int			id;
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic int
4462306a36Sopenharmony_cimISDN_open(struct inode *ino, struct file *filep)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct mISDNtimerdev	*dev;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if (*debug & DEBUG_TIMER)
4962306a36Sopenharmony_ci		printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
5062306a36Sopenharmony_ci	dev = kmalloc(sizeof(struct mISDNtimerdev) , GFP_KERNEL);
5162306a36Sopenharmony_ci	if (!dev)
5262306a36Sopenharmony_ci		return -ENOMEM;
5362306a36Sopenharmony_ci	dev->next_id = 1;
5462306a36Sopenharmony_ci	INIT_LIST_HEAD(&dev->pending);
5562306a36Sopenharmony_ci	INIT_LIST_HEAD(&dev->expired);
5662306a36Sopenharmony_ci	spin_lock_init(&dev->lock);
5762306a36Sopenharmony_ci	dev->work = 0;
5862306a36Sopenharmony_ci	init_waitqueue_head(&dev->wait);
5962306a36Sopenharmony_ci	filep->private_data = dev;
6062306a36Sopenharmony_ci	return nonseekable_open(ino, filep);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic int
6462306a36Sopenharmony_cimISDN_close(struct inode *ino, struct file *filep)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct mISDNtimerdev	*dev = filep->private_data;
6762306a36Sopenharmony_ci	struct list_head	*list = &dev->pending;
6862306a36Sopenharmony_ci	struct mISDNtimer	*timer, *next;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (*debug & DEBUG_TIMER)
7162306a36Sopenharmony_ci		printk(KERN_DEBUG "%s(%p,%p)\n", __func__, ino, filep);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	spin_lock_irq(&dev->lock);
7462306a36Sopenharmony_ci	while (!list_empty(list)) {
7562306a36Sopenharmony_ci		timer = list_first_entry(list, struct mISDNtimer, list);
7662306a36Sopenharmony_ci		spin_unlock_irq(&dev->lock);
7762306a36Sopenharmony_ci		timer_shutdown_sync(&timer->tl);
7862306a36Sopenharmony_ci		spin_lock_irq(&dev->lock);
7962306a36Sopenharmony_ci		/* it might have been moved to ->expired */
8062306a36Sopenharmony_ci		list_del(&timer->list);
8162306a36Sopenharmony_ci		kfree(timer);
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci	spin_unlock_irq(&dev->lock);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	list_for_each_entry_safe(timer, next, &dev->expired, list) {
8662306a36Sopenharmony_ci		kfree(timer);
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci	kfree(dev);
8962306a36Sopenharmony_ci	return 0;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic ssize_t
9362306a36Sopenharmony_cimISDN_read(struct file *filep, char __user *buf, size_t count, loff_t *off)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct mISDNtimerdev	*dev = filep->private_data;
9662306a36Sopenharmony_ci	struct list_head *list = &dev->expired;
9762306a36Sopenharmony_ci	struct mISDNtimer	*timer;
9862306a36Sopenharmony_ci	int	ret = 0;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (*debug & DEBUG_TIMER)
10162306a36Sopenharmony_ci		printk(KERN_DEBUG "%s(%p, %p, %d, %p)\n", __func__,
10262306a36Sopenharmony_ci		       filep, buf, (int)count, off);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (count < sizeof(int))
10562306a36Sopenharmony_ci		return -ENOSPC;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	spin_lock_irq(&dev->lock);
10862306a36Sopenharmony_ci	while (list_empty(list) && (dev->work == 0)) {
10962306a36Sopenharmony_ci		spin_unlock_irq(&dev->lock);
11062306a36Sopenharmony_ci		if (filep->f_flags & O_NONBLOCK)
11162306a36Sopenharmony_ci			return -EAGAIN;
11262306a36Sopenharmony_ci		wait_event_interruptible(dev->wait, (dev->work ||
11362306a36Sopenharmony_ci						     !list_empty(list)));
11462306a36Sopenharmony_ci		if (signal_pending(current))
11562306a36Sopenharmony_ci			return -ERESTARTSYS;
11662306a36Sopenharmony_ci		spin_lock_irq(&dev->lock);
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci	if (dev->work)
11962306a36Sopenharmony_ci		dev->work = 0;
12062306a36Sopenharmony_ci	if (!list_empty(list)) {
12162306a36Sopenharmony_ci		timer = list_first_entry(list, struct mISDNtimer, list);
12262306a36Sopenharmony_ci		list_del(&timer->list);
12362306a36Sopenharmony_ci		spin_unlock_irq(&dev->lock);
12462306a36Sopenharmony_ci		if (put_user(timer->id, (int __user *)buf))
12562306a36Sopenharmony_ci			ret = -EFAULT;
12662306a36Sopenharmony_ci		else
12762306a36Sopenharmony_ci			ret = sizeof(int);
12862306a36Sopenharmony_ci		kfree(timer);
12962306a36Sopenharmony_ci	} else {
13062306a36Sopenharmony_ci		spin_unlock_irq(&dev->lock);
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci	return ret;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic __poll_t
13662306a36Sopenharmony_cimISDN_poll(struct file *filep, poll_table *wait)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct mISDNtimerdev	*dev = filep->private_data;
13962306a36Sopenharmony_ci	__poll_t		mask = EPOLLERR;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	if (*debug & DEBUG_TIMER)
14262306a36Sopenharmony_ci		printk(KERN_DEBUG "%s(%p, %p)\n", __func__, filep, wait);
14362306a36Sopenharmony_ci	if (dev) {
14462306a36Sopenharmony_ci		poll_wait(filep, &dev->wait, wait);
14562306a36Sopenharmony_ci		mask = 0;
14662306a36Sopenharmony_ci		if (dev->work || !list_empty(&dev->expired))
14762306a36Sopenharmony_ci			mask |= (EPOLLIN | EPOLLRDNORM);
14862306a36Sopenharmony_ci		if (*debug & DEBUG_TIMER)
14962306a36Sopenharmony_ci			printk(KERN_DEBUG "%s work(%d) empty(%d)\n", __func__,
15062306a36Sopenharmony_ci			       dev->work, list_empty(&dev->expired));
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci	return mask;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic void
15662306a36Sopenharmony_cidev_expire_timer(struct timer_list *t)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	struct mISDNtimer *timer = from_timer(timer, t, tl);
15962306a36Sopenharmony_ci	u_long			flags;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	spin_lock_irqsave(&timer->dev->lock, flags);
16262306a36Sopenharmony_ci	if (timer->id >= 0)
16362306a36Sopenharmony_ci		list_move_tail(&timer->list, &timer->dev->expired);
16462306a36Sopenharmony_ci	wake_up_interruptible(&timer->dev->wait);
16562306a36Sopenharmony_ci	spin_unlock_irqrestore(&timer->dev->lock, flags);
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int
16962306a36Sopenharmony_cimisdn_add_timer(struct mISDNtimerdev *dev, int timeout)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	int			id;
17262306a36Sopenharmony_ci	struct mISDNtimer	*timer;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	if (!timeout) {
17562306a36Sopenharmony_ci		dev->work = 1;
17662306a36Sopenharmony_ci		wake_up_interruptible(&dev->wait);
17762306a36Sopenharmony_ci		id = 0;
17862306a36Sopenharmony_ci	} else {
17962306a36Sopenharmony_ci		timer = kzalloc(sizeof(struct mISDNtimer), GFP_KERNEL);
18062306a36Sopenharmony_ci		if (!timer)
18162306a36Sopenharmony_ci			return -ENOMEM;
18262306a36Sopenharmony_ci		timer->dev = dev;
18362306a36Sopenharmony_ci		timer_setup(&timer->tl, dev_expire_timer, 0);
18462306a36Sopenharmony_ci		spin_lock_irq(&dev->lock);
18562306a36Sopenharmony_ci		id = timer->id = dev->next_id++;
18662306a36Sopenharmony_ci		if (dev->next_id < 0)
18762306a36Sopenharmony_ci			dev->next_id = 1;
18862306a36Sopenharmony_ci		list_add_tail(&timer->list, &dev->pending);
18962306a36Sopenharmony_ci		timer->tl.expires = jiffies + ((HZ * (u_long)timeout) / 1000);
19062306a36Sopenharmony_ci		add_timer(&timer->tl);
19162306a36Sopenharmony_ci		spin_unlock_irq(&dev->lock);
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci	return id;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic int
19762306a36Sopenharmony_cimisdn_del_timer(struct mISDNtimerdev *dev, int id)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	struct mISDNtimer	*timer;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	spin_lock_irq(&dev->lock);
20262306a36Sopenharmony_ci	list_for_each_entry(timer, &dev->pending, list) {
20362306a36Sopenharmony_ci		if (timer->id == id) {
20462306a36Sopenharmony_ci			list_del_init(&timer->list);
20562306a36Sopenharmony_ci			timer->id = -1;
20662306a36Sopenharmony_ci			spin_unlock_irq(&dev->lock);
20762306a36Sopenharmony_ci			timer_shutdown_sync(&timer->tl);
20862306a36Sopenharmony_ci			kfree(timer);
20962306a36Sopenharmony_ci			return id;
21062306a36Sopenharmony_ci		}
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci	spin_unlock_irq(&dev->lock);
21362306a36Sopenharmony_ci	return 0;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic long
21762306a36Sopenharmony_cimISDN_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct mISDNtimerdev	*dev = filep->private_data;
22062306a36Sopenharmony_ci	int			id, tout, ret = 0;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	if (*debug & DEBUG_TIMER)
22462306a36Sopenharmony_ci		printk(KERN_DEBUG "%s(%p, %x, %lx)\n", __func__,
22562306a36Sopenharmony_ci		       filep, cmd, arg);
22662306a36Sopenharmony_ci	mutex_lock(&mISDN_mutex);
22762306a36Sopenharmony_ci	switch (cmd) {
22862306a36Sopenharmony_ci	case IMADDTIMER:
22962306a36Sopenharmony_ci		if (get_user(tout, (int __user *)arg)) {
23062306a36Sopenharmony_ci			ret = -EFAULT;
23162306a36Sopenharmony_ci			break;
23262306a36Sopenharmony_ci		}
23362306a36Sopenharmony_ci		id = misdn_add_timer(dev, tout);
23462306a36Sopenharmony_ci		if (*debug & DEBUG_TIMER)
23562306a36Sopenharmony_ci			printk(KERN_DEBUG "%s add %d id %d\n", __func__,
23662306a36Sopenharmony_ci			       tout, id);
23762306a36Sopenharmony_ci		if (id < 0) {
23862306a36Sopenharmony_ci			ret = id;
23962306a36Sopenharmony_ci			break;
24062306a36Sopenharmony_ci		}
24162306a36Sopenharmony_ci		if (put_user(id, (int __user *)arg))
24262306a36Sopenharmony_ci			ret = -EFAULT;
24362306a36Sopenharmony_ci		break;
24462306a36Sopenharmony_ci	case IMDELTIMER:
24562306a36Sopenharmony_ci		if (get_user(id, (int __user *)arg)) {
24662306a36Sopenharmony_ci			ret = -EFAULT;
24762306a36Sopenharmony_ci			break;
24862306a36Sopenharmony_ci		}
24962306a36Sopenharmony_ci		if (*debug & DEBUG_TIMER)
25062306a36Sopenharmony_ci			printk(KERN_DEBUG "%s del id %d\n", __func__, id);
25162306a36Sopenharmony_ci		id = misdn_del_timer(dev, id);
25262306a36Sopenharmony_ci		if (put_user(id, (int __user *)arg))
25362306a36Sopenharmony_ci			ret = -EFAULT;
25462306a36Sopenharmony_ci		break;
25562306a36Sopenharmony_ci	default:
25662306a36Sopenharmony_ci		ret = -EINVAL;
25762306a36Sopenharmony_ci	}
25862306a36Sopenharmony_ci	mutex_unlock(&mISDN_mutex);
25962306a36Sopenharmony_ci	return ret;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic const struct file_operations mISDN_fops = {
26362306a36Sopenharmony_ci	.owner		= THIS_MODULE,
26462306a36Sopenharmony_ci	.read		= mISDN_read,
26562306a36Sopenharmony_ci	.poll		= mISDN_poll,
26662306a36Sopenharmony_ci	.unlocked_ioctl	= mISDN_ioctl,
26762306a36Sopenharmony_ci	.open		= mISDN_open,
26862306a36Sopenharmony_ci	.release	= mISDN_close,
26962306a36Sopenharmony_ci	.llseek		= no_llseek,
27062306a36Sopenharmony_ci};
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic struct miscdevice mISDNtimer = {
27362306a36Sopenharmony_ci	.minor	= MISC_DYNAMIC_MINOR,
27462306a36Sopenharmony_ci	.name	= "mISDNtimer",
27562306a36Sopenharmony_ci	.fops	= &mISDN_fops,
27662306a36Sopenharmony_ci};
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ciint
27962306a36Sopenharmony_cimISDN_inittimer(u_int *deb)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	int	err;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	debug = deb;
28462306a36Sopenharmony_ci	err = misc_register(&mISDNtimer);
28562306a36Sopenharmony_ci	if (err)
28662306a36Sopenharmony_ci		printk(KERN_WARNING "mISDN: Could not register timer device\n");
28762306a36Sopenharmony_ci	return err;
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_civoid mISDN_timer_cleanup(void)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	misc_deregister(&mISDNtimer);
29362306a36Sopenharmony_ci}
294