162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * PPS core file
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2005-2009   Rodolfo Giometti <giometti@linux.it>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/sched.h>
1462306a36Sopenharmony_ci#include <linux/uaccess.h>
1562306a36Sopenharmony_ci#include <linux/idr.h>
1662306a36Sopenharmony_ci#include <linux/mutex.h>
1762306a36Sopenharmony_ci#include <linux/cdev.h>
1862306a36Sopenharmony_ci#include <linux/poll.h>
1962306a36Sopenharmony_ci#include <linux/pps_kernel.h>
2062306a36Sopenharmony_ci#include <linux/slab.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "kc.h"
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * Local variables
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic dev_t pps_devt;
2962306a36Sopenharmony_cistatic struct class *pps_class;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic DEFINE_MUTEX(pps_idr_lock);
3262306a36Sopenharmony_cistatic DEFINE_IDR(pps_idr);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/*
3562306a36Sopenharmony_ci * Char device methods
3662306a36Sopenharmony_ci */
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic __poll_t pps_cdev_poll(struct file *file, poll_table *wait)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct pps_device *pps = file->private_data;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	poll_wait(file, &pps->queue, wait);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	return EPOLLIN | EPOLLRDNORM;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int pps_cdev_fasync(int fd, struct file *file, int on)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct pps_device *pps = file->private_data;
5062306a36Sopenharmony_ci	return fasync_helper(fd, file, on, &pps->async_queue);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	unsigned int ev = pps->last_ev;
5662306a36Sopenharmony_ci	int err = 0;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	/* Manage the timeout */
5962306a36Sopenharmony_ci	if (fdata->timeout.flags & PPS_TIME_INVALID)
6062306a36Sopenharmony_ci		err = wait_event_interruptible(pps->queue,
6162306a36Sopenharmony_ci				ev != pps->last_ev);
6262306a36Sopenharmony_ci	else {
6362306a36Sopenharmony_ci		unsigned long ticks;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci		dev_dbg(pps->dev, "timeout %lld.%09d\n",
6662306a36Sopenharmony_ci				(long long) fdata->timeout.sec,
6762306a36Sopenharmony_ci				fdata->timeout.nsec);
6862306a36Sopenharmony_ci		ticks = fdata->timeout.sec * HZ;
6962306a36Sopenharmony_ci		ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		if (ticks != 0) {
7262306a36Sopenharmony_ci			err = wait_event_interruptible_timeout(
7362306a36Sopenharmony_ci					pps->queue,
7462306a36Sopenharmony_ci					ev != pps->last_ev,
7562306a36Sopenharmony_ci					ticks);
7662306a36Sopenharmony_ci			if (err == 0)
7762306a36Sopenharmony_ci				return -ETIMEDOUT;
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* Check for pending signals */
8262306a36Sopenharmony_ci	if (err == -ERESTARTSYS) {
8362306a36Sopenharmony_ci		dev_dbg(pps->dev, "pending signal caught\n");
8462306a36Sopenharmony_ci		return -EINTR;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return 0;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic long pps_cdev_ioctl(struct file *file,
9162306a36Sopenharmony_ci		unsigned int cmd, unsigned long arg)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct pps_device *pps = file->private_data;
9462306a36Sopenharmony_ci	struct pps_kparams params;
9562306a36Sopenharmony_ci	void __user *uarg = (void __user *) arg;
9662306a36Sopenharmony_ci	int __user *iuarg = (int __user *) arg;
9762306a36Sopenharmony_ci	int err;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	switch (cmd) {
10062306a36Sopenharmony_ci	case PPS_GETPARAMS:
10162306a36Sopenharmony_ci		dev_dbg(pps->dev, "PPS_GETPARAMS\n");
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		spin_lock_irq(&pps->lock);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		/* Get the current parameters */
10662306a36Sopenharmony_ci		params = pps->params;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		spin_unlock_irq(&pps->lock);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		err = copy_to_user(uarg, &params, sizeof(struct pps_kparams));
11162306a36Sopenharmony_ci		if (err)
11262306a36Sopenharmony_ci			return -EFAULT;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		break;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	case PPS_SETPARAMS:
11762306a36Sopenharmony_ci		dev_dbg(pps->dev, "PPS_SETPARAMS\n");
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		/* Check the capabilities */
12062306a36Sopenharmony_ci		if (!capable(CAP_SYS_TIME))
12162306a36Sopenharmony_ci			return -EPERM;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
12462306a36Sopenharmony_ci		if (err)
12562306a36Sopenharmony_ci			return -EFAULT;
12662306a36Sopenharmony_ci		if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
12762306a36Sopenharmony_ci			dev_dbg(pps->dev, "capture mode unspecified (%x)\n",
12862306a36Sopenharmony_ci								params.mode);
12962306a36Sopenharmony_ci			return -EINVAL;
13062306a36Sopenharmony_ci		}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		/* Check for supported capabilities */
13362306a36Sopenharmony_ci		if ((params.mode & ~pps->info.mode) != 0) {
13462306a36Sopenharmony_ci			dev_dbg(pps->dev, "unsupported capabilities (%x)\n",
13562306a36Sopenharmony_ci								params.mode);
13662306a36Sopenharmony_ci			return -EINVAL;
13762306a36Sopenharmony_ci		}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci		spin_lock_irq(&pps->lock);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		/* Save the new parameters */
14262306a36Sopenharmony_ci		pps->params = params;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		/* Restore the read only parameters */
14562306a36Sopenharmony_ci		if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
14662306a36Sopenharmony_ci			/* section 3.3 of RFC 2783 interpreted */
14762306a36Sopenharmony_ci			dev_dbg(pps->dev, "time format unspecified (%x)\n",
14862306a36Sopenharmony_ci								params.mode);
14962306a36Sopenharmony_ci			pps->params.mode |= PPS_TSFMT_TSPEC;
15062306a36Sopenharmony_ci		}
15162306a36Sopenharmony_ci		if (pps->info.mode & PPS_CANWAIT)
15262306a36Sopenharmony_ci			pps->params.mode |= PPS_CANWAIT;
15362306a36Sopenharmony_ci		pps->params.api_version = PPS_API_VERS;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		/*
15662306a36Sopenharmony_ci		 * Clear unused fields of pps_kparams to avoid leaking
15762306a36Sopenharmony_ci		 * uninitialized data of the PPS_SETPARAMS caller via
15862306a36Sopenharmony_ci		 * PPS_GETPARAMS
15962306a36Sopenharmony_ci		 */
16062306a36Sopenharmony_ci		pps->params.assert_off_tu.flags = 0;
16162306a36Sopenharmony_ci		pps->params.clear_off_tu.flags = 0;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci		spin_unlock_irq(&pps->lock);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		break;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	case PPS_GETCAP:
16862306a36Sopenharmony_ci		dev_dbg(pps->dev, "PPS_GETCAP\n");
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci		err = put_user(pps->info.mode, iuarg);
17162306a36Sopenharmony_ci		if (err)
17262306a36Sopenharmony_ci			return -EFAULT;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci		break;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	case PPS_FETCH: {
17762306a36Sopenharmony_ci		struct pps_fdata fdata;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		dev_dbg(pps->dev, "PPS_FETCH\n");
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
18262306a36Sopenharmony_ci		if (err)
18362306a36Sopenharmony_ci			return -EFAULT;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci		err = pps_cdev_pps_fetch(pps, &fdata);
18662306a36Sopenharmony_ci		if (err)
18762306a36Sopenharmony_ci			return err;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		/* Return the fetched timestamp */
19062306a36Sopenharmony_ci		spin_lock_irq(&pps->lock);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		fdata.info.assert_sequence = pps->assert_sequence;
19362306a36Sopenharmony_ci		fdata.info.clear_sequence = pps->clear_sequence;
19462306a36Sopenharmony_ci		fdata.info.assert_tu = pps->assert_tu;
19562306a36Sopenharmony_ci		fdata.info.clear_tu = pps->clear_tu;
19662306a36Sopenharmony_ci		fdata.info.current_mode = pps->current_mode;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		spin_unlock_irq(&pps->lock);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
20162306a36Sopenharmony_ci		if (err)
20262306a36Sopenharmony_ci			return -EFAULT;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		break;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci	case PPS_KC_BIND: {
20762306a36Sopenharmony_ci		struct pps_bind_args bind_args;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci		dev_dbg(pps->dev, "PPS_KC_BIND\n");
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci		/* Check the capabilities */
21262306a36Sopenharmony_ci		if (!capable(CAP_SYS_TIME))
21362306a36Sopenharmony_ci			return -EPERM;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci		if (copy_from_user(&bind_args, uarg,
21662306a36Sopenharmony_ci					sizeof(struct pps_bind_args)))
21762306a36Sopenharmony_ci			return -EFAULT;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		/* Check for supported capabilities */
22062306a36Sopenharmony_ci		if ((bind_args.edge & ~pps->info.mode) != 0) {
22162306a36Sopenharmony_ci			dev_err(pps->dev, "unsupported capabilities (%x)\n",
22262306a36Sopenharmony_ci					bind_args.edge);
22362306a36Sopenharmony_ci			return -EINVAL;
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci		/* Validate parameters roughly */
22762306a36Sopenharmony_ci		if (bind_args.tsformat != PPS_TSFMT_TSPEC ||
22862306a36Sopenharmony_ci				(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 ||
22962306a36Sopenharmony_ci				bind_args.consumer != PPS_KC_HARDPPS) {
23062306a36Sopenharmony_ci			dev_err(pps->dev, "invalid kernel consumer bind"
23162306a36Sopenharmony_ci					" parameters (%x)\n", bind_args.edge);
23262306a36Sopenharmony_ci			return -EINVAL;
23362306a36Sopenharmony_ci		}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci		err = pps_kc_bind(pps, &bind_args);
23662306a36Sopenharmony_ci		if (err < 0)
23762306a36Sopenharmony_ci			return err;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		break;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci	default:
24262306a36Sopenharmony_ci		return -ENOTTY;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return 0;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci#ifdef CONFIG_COMPAT
24962306a36Sopenharmony_cistatic long pps_cdev_compat_ioctl(struct file *file,
25062306a36Sopenharmony_ci		unsigned int cmd, unsigned long arg)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct pps_device *pps = file->private_data;
25362306a36Sopenharmony_ci	void __user *uarg = (void __user *) arg;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *));
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (cmd == PPS_FETCH) {
25862306a36Sopenharmony_ci		struct pps_fdata_compat compat;
25962306a36Sopenharmony_ci		struct pps_fdata fdata;
26062306a36Sopenharmony_ci		int err;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci		dev_dbg(pps->dev, "PPS_FETCH\n");
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci		err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat));
26562306a36Sopenharmony_ci		if (err)
26662306a36Sopenharmony_ci			return -EFAULT;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci		memcpy(&fdata.timeout, &compat.timeout,
26962306a36Sopenharmony_ci					sizeof(struct pps_ktime_compat));
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		err = pps_cdev_pps_fetch(pps, &fdata);
27262306a36Sopenharmony_ci		if (err)
27362306a36Sopenharmony_ci			return err;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci		/* Return the fetched timestamp */
27662306a36Sopenharmony_ci		spin_lock_irq(&pps->lock);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci		compat.info.assert_sequence = pps->assert_sequence;
27962306a36Sopenharmony_ci		compat.info.clear_sequence = pps->clear_sequence;
28062306a36Sopenharmony_ci		compat.info.current_mode = pps->current_mode;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci		memcpy(&compat.info.assert_tu, &pps->assert_tu,
28362306a36Sopenharmony_ci				sizeof(struct pps_ktime_compat));
28462306a36Sopenharmony_ci		memcpy(&compat.info.clear_tu, &pps->clear_tu,
28562306a36Sopenharmony_ci				sizeof(struct pps_ktime_compat));
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		spin_unlock_irq(&pps->lock);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci		return copy_to_user(uarg, &compat,
29062306a36Sopenharmony_ci				sizeof(struct pps_fdata_compat)) ? -EFAULT : 0;
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	return pps_cdev_ioctl(file, cmd, arg);
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci#else
29662306a36Sopenharmony_ci#define pps_cdev_compat_ioctl	NULL
29762306a36Sopenharmony_ci#endif
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic int pps_cdev_open(struct inode *inode, struct file *file)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	struct pps_device *pps = container_of(inode->i_cdev,
30262306a36Sopenharmony_ci						struct pps_device, cdev);
30362306a36Sopenharmony_ci	file->private_data = pps;
30462306a36Sopenharmony_ci	kobject_get(&pps->dev->kobj);
30562306a36Sopenharmony_ci	return 0;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int pps_cdev_release(struct inode *inode, struct file *file)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	struct pps_device *pps = container_of(inode->i_cdev,
31162306a36Sopenharmony_ci						struct pps_device, cdev);
31262306a36Sopenharmony_ci	kobject_put(&pps->dev->kobj);
31362306a36Sopenharmony_ci	return 0;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci/*
31762306a36Sopenharmony_ci * Char device stuff
31862306a36Sopenharmony_ci */
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cistatic const struct file_operations pps_cdev_fops = {
32162306a36Sopenharmony_ci	.owner		= THIS_MODULE,
32262306a36Sopenharmony_ci	.llseek		= no_llseek,
32362306a36Sopenharmony_ci	.poll		= pps_cdev_poll,
32462306a36Sopenharmony_ci	.fasync		= pps_cdev_fasync,
32562306a36Sopenharmony_ci	.compat_ioctl	= pps_cdev_compat_ioctl,
32662306a36Sopenharmony_ci	.unlocked_ioctl	= pps_cdev_ioctl,
32762306a36Sopenharmony_ci	.open		= pps_cdev_open,
32862306a36Sopenharmony_ci	.release	= pps_cdev_release,
32962306a36Sopenharmony_ci};
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_cistatic void pps_device_destruct(struct device *dev)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	struct pps_device *pps = dev_get_drvdata(dev);
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	cdev_del(&pps->cdev);
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	/* Now we can release the ID for re-use */
33862306a36Sopenharmony_ci	pr_debug("deallocating pps%d\n", pps->id);
33962306a36Sopenharmony_ci	mutex_lock(&pps_idr_lock);
34062306a36Sopenharmony_ci	idr_remove(&pps_idr, pps->id);
34162306a36Sopenharmony_ci	mutex_unlock(&pps_idr_lock);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	kfree(dev);
34462306a36Sopenharmony_ci	kfree(pps);
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ciint pps_register_cdev(struct pps_device *pps)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	int err;
35062306a36Sopenharmony_ci	dev_t devt;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	mutex_lock(&pps_idr_lock);
35362306a36Sopenharmony_ci	/*
35462306a36Sopenharmony_ci	 * Get new ID for the new PPS source.  After idr_alloc() calling
35562306a36Sopenharmony_ci	 * the new source will be freely available into the kernel.
35662306a36Sopenharmony_ci	 */
35762306a36Sopenharmony_ci	err = idr_alloc(&pps_idr, pps, 0, PPS_MAX_SOURCES, GFP_KERNEL);
35862306a36Sopenharmony_ci	if (err < 0) {
35962306a36Sopenharmony_ci		if (err == -ENOSPC) {
36062306a36Sopenharmony_ci			pr_err("%s: too many PPS sources in the system\n",
36162306a36Sopenharmony_ci			       pps->info.name);
36262306a36Sopenharmony_ci			err = -EBUSY;
36362306a36Sopenharmony_ci		}
36462306a36Sopenharmony_ci		goto out_unlock;
36562306a36Sopenharmony_ci	}
36662306a36Sopenharmony_ci	pps->id = err;
36762306a36Sopenharmony_ci	mutex_unlock(&pps_idr_lock);
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	devt = MKDEV(MAJOR(pps_devt), pps->id);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	cdev_init(&pps->cdev, &pps_cdev_fops);
37262306a36Sopenharmony_ci	pps->cdev.owner = pps->info.owner;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	err = cdev_add(&pps->cdev, devt, 1);
37562306a36Sopenharmony_ci	if (err) {
37662306a36Sopenharmony_ci		pr_err("%s: failed to add char device %d:%d\n",
37762306a36Sopenharmony_ci				pps->info.name, MAJOR(pps_devt), pps->id);
37862306a36Sopenharmony_ci		goto free_idr;
37962306a36Sopenharmony_ci	}
38062306a36Sopenharmony_ci	pps->dev = device_create(pps_class, pps->info.dev, devt, pps,
38162306a36Sopenharmony_ci							"pps%d", pps->id);
38262306a36Sopenharmony_ci	if (IS_ERR(pps->dev)) {
38362306a36Sopenharmony_ci		err = PTR_ERR(pps->dev);
38462306a36Sopenharmony_ci		goto del_cdev;
38562306a36Sopenharmony_ci	}
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	/* Override the release function with our own */
38862306a36Sopenharmony_ci	pps->dev->release = pps_device_destruct;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
39162306a36Sopenharmony_ci			MAJOR(pps_devt), pps->id);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	return 0;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cidel_cdev:
39662306a36Sopenharmony_ci	cdev_del(&pps->cdev);
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_cifree_idr:
39962306a36Sopenharmony_ci	mutex_lock(&pps_idr_lock);
40062306a36Sopenharmony_ci	idr_remove(&pps_idr, pps->id);
40162306a36Sopenharmony_ciout_unlock:
40262306a36Sopenharmony_ci	mutex_unlock(&pps_idr_lock);
40362306a36Sopenharmony_ci	return err;
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_civoid pps_unregister_cdev(struct pps_device *pps)
40762306a36Sopenharmony_ci{
40862306a36Sopenharmony_ci	pr_debug("unregistering pps%d\n", pps->id);
40962306a36Sopenharmony_ci	pps->lookup_cookie = NULL;
41062306a36Sopenharmony_ci	device_destroy(pps_class, pps->dev->devt);
41162306a36Sopenharmony_ci}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci/*
41462306a36Sopenharmony_ci * Look up a pps device by magic cookie.
41562306a36Sopenharmony_ci * The cookie is usually a pointer to some enclosing device, but this
41662306a36Sopenharmony_ci * code doesn't care; you should never be dereferencing it.
41762306a36Sopenharmony_ci *
41862306a36Sopenharmony_ci * This is a bit of a kludge that is currently used only by the PPS
41962306a36Sopenharmony_ci * serial line discipline.  It may need to be tweaked when a second user
42062306a36Sopenharmony_ci * is found.
42162306a36Sopenharmony_ci *
42262306a36Sopenharmony_ci * There is no function interface for setting the lookup_cookie field.
42362306a36Sopenharmony_ci * It's initialized to NULL when the pps device is created, and if a
42462306a36Sopenharmony_ci * client wants to use it, just fill it in afterward.
42562306a36Sopenharmony_ci *
42662306a36Sopenharmony_ci * The cookie is automatically set to NULL in pps_unregister_source()
42762306a36Sopenharmony_ci * so that it will not be used again, even if the pps device cannot
42862306a36Sopenharmony_ci * be removed from the idr due to pending references holding the minor
42962306a36Sopenharmony_ci * number in use.
43062306a36Sopenharmony_ci */
43162306a36Sopenharmony_cistruct pps_device *pps_lookup_dev(void const *cookie)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	struct pps_device *pps;
43462306a36Sopenharmony_ci	unsigned id;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	rcu_read_lock();
43762306a36Sopenharmony_ci	idr_for_each_entry(&pps_idr, pps, id)
43862306a36Sopenharmony_ci		if (cookie == pps->lookup_cookie)
43962306a36Sopenharmony_ci			break;
44062306a36Sopenharmony_ci	rcu_read_unlock();
44162306a36Sopenharmony_ci	return pps;
44262306a36Sopenharmony_ci}
44362306a36Sopenharmony_ciEXPORT_SYMBOL(pps_lookup_dev);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci/*
44662306a36Sopenharmony_ci * Module stuff
44762306a36Sopenharmony_ci */
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cistatic void __exit pps_exit(void)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	class_destroy(pps_class);
45262306a36Sopenharmony_ci	unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
45362306a36Sopenharmony_ci}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_cistatic int __init pps_init(void)
45662306a36Sopenharmony_ci{
45762306a36Sopenharmony_ci	int err;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	pps_class = class_create("pps");
46062306a36Sopenharmony_ci	if (IS_ERR(pps_class)) {
46162306a36Sopenharmony_ci		pr_err("failed to allocate class\n");
46262306a36Sopenharmony_ci		return PTR_ERR(pps_class);
46362306a36Sopenharmony_ci	}
46462306a36Sopenharmony_ci	pps_class->dev_groups = pps_groups;
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_ci	err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
46762306a36Sopenharmony_ci	if (err < 0) {
46862306a36Sopenharmony_ci		pr_err("failed to allocate char device region\n");
46962306a36Sopenharmony_ci		goto remove_class;
47062306a36Sopenharmony_ci	}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS);
47362306a36Sopenharmony_ci	pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
47462306a36Sopenharmony_ci		"<giometti@linux.it>\n", PPS_VERSION);
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	return 0;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ciremove_class:
47962306a36Sopenharmony_ci	class_destroy(pps_class);
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	return err;
48262306a36Sopenharmony_ci}
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_cisubsys_initcall(pps_init);
48562306a36Sopenharmony_cimodule_exit(pps_exit);
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ciMODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
48862306a36Sopenharmony_ciMODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
48962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
490