18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * PPS core file
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2005-2009   Rodolfo Giometti <giometti@linux.it>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/init.h>
138c2ecf20Sopenharmony_ci#include <linux/sched.h>
148c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
158c2ecf20Sopenharmony_ci#include <linux/idr.h>
168c2ecf20Sopenharmony_ci#include <linux/mutex.h>
178c2ecf20Sopenharmony_ci#include <linux/cdev.h>
188c2ecf20Sopenharmony_ci#include <linux/poll.h>
198c2ecf20Sopenharmony_ci#include <linux/pps_kernel.h>
208c2ecf20Sopenharmony_ci#include <linux/slab.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include "kc.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/*
258c2ecf20Sopenharmony_ci * Local variables
268c2ecf20Sopenharmony_ci */
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic dev_t pps_devt;
298c2ecf20Sopenharmony_cistatic struct class *pps_class;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(pps_idr_lock);
328c2ecf20Sopenharmony_cistatic DEFINE_IDR(pps_idr);
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/*
358c2ecf20Sopenharmony_ci * Char device methods
368c2ecf20Sopenharmony_ci */
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic __poll_t pps_cdev_poll(struct file *file, poll_table *wait)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct pps_device *pps = file->private_data;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	poll_wait(file, &pps->queue, wait);
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	return EPOLLIN | EPOLLRDNORM;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic int pps_cdev_fasync(int fd, struct file *file, int on)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	struct pps_device *pps = file->private_data;
508c2ecf20Sopenharmony_ci	return fasync_helper(fd, file, on, &pps->async_queue);
518c2ecf20Sopenharmony_ci}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	unsigned int ev = pps->last_ev;
568c2ecf20Sopenharmony_ci	int err = 0;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	/* Manage the timeout */
598c2ecf20Sopenharmony_ci	if (fdata->timeout.flags & PPS_TIME_INVALID)
608c2ecf20Sopenharmony_ci		err = wait_event_interruptible(pps->queue,
618c2ecf20Sopenharmony_ci				ev != pps->last_ev);
628c2ecf20Sopenharmony_ci	else {
638c2ecf20Sopenharmony_ci		unsigned long ticks;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "timeout %lld.%09d\n",
668c2ecf20Sopenharmony_ci				(long long) fdata->timeout.sec,
678c2ecf20Sopenharmony_ci				fdata->timeout.nsec);
688c2ecf20Sopenharmony_ci		ticks = fdata->timeout.sec * HZ;
698c2ecf20Sopenharmony_ci		ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci		if (ticks != 0) {
728c2ecf20Sopenharmony_ci			err = wait_event_interruptible_timeout(
738c2ecf20Sopenharmony_ci					pps->queue,
748c2ecf20Sopenharmony_ci					ev != pps->last_ev,
758c2ecf20Sopenharmony_ci					ticks);
768c2ecf20Sopenharmony_ci			if (err == 0)
778c2ecf20Sopenharmony_ci				return -ETIMEDOUT;
788c2ecf20Sopenharmony_ci		}
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	/* Check for pending signals */
828c2ecf20Sopenharmony_ci	if (err == -ERESTARTSYS) {
838c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "pending signal caught\n");
848c2ecf20Sopenharmony_ci		return -EINTR;
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	return 0;
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic long pps_cdev_ioctl(struct file *file,
918c2ecf20Sopenharmony_ci		unsigned int cmd, unsigned long arg)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	struct pps_device *pps = file->private_data;
948c2ecf20Sopenharmony_ci	struct pps_kparams params;
958c2ecf20Sopenharmony_ci	void __user *uarg = (void __user *) arg;
968c2ecf20Sopenharmony_ci	int __user *iuarg = (int __user *) arg;
978c2ecf20Sopenharmony_ci	int err;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	switch (cmd) {
1008c2ecf20Sopenharmony_ci	case PPS_GETPARAMS:
1018c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "PPS_GETPARAMS\n");
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci		spin_lock_irq(&pps->lock);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci		/* Get the current parameters */
1068c2ecf20Sopenharmony_ci		params = pps->params;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci		spin_unlock_irq(&pps->lock);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		err = copy_to_user(uarg, &params, sizeof(struct pps_kparams));
1118c2ecf20Sopenharmony_ci		if (err)
1128c2ecf20Sopenharmony_ci			return -EFAULT;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		break;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	case PPS_SETPARAMS:
1178c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "PPS_SETPARAMS\n");
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci		/* Check the capabilities */
1208c2ecf20Sopenharmony_ci		if (!capable(CAP_SYS_TIME))
1218c2ecf20Sopenharmony_ci			return -EPERM;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci		err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
1248c2ecf20Sopenharmony_ci		if (err)
1258c2ecf20Sopenharmony_ci			return -EFAULT;
1268c2ecf20Sopenharmony_ci		if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
1278c2ecf20Sopenharmony_ci			dev_dbg(pps->dev, "capture mode unspecified (%x)\n",
1288c2ecf20Sopenharmony_ci								params.mode);
1298c2ecf20Sopenharmony_ci			return -EINVAL;
1308c2ecf20Sopenharmony_ci		}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci		/* Check for supported capabilities */
1338c2ecf20Sopenharmony_ci		if ((params.mode & ~pps->info.mode) != 0) {
1348c2ecf20Sopenharmony_ci			dev_dbg(pps->dev, "unsupported capabilities (%x)\n",
1358c2ecf20Sopenharmony_ci								params.mode);
1368c2ecf20Sopenharmony_ci			return -EINVAL;
1378c2ecf20Sopenharmony_ci		}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		spin_lock_irq(&pps->lock);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		/* Save the new parameters */
1428c2ecf20Sopenharmony_ci		pps->params = params;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci		/* Restore the read only parameters */
1458c2ecf20Sopenharmony_ci		if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
1468c2ecf20Sopenharmony_ci			/* section 3.3 of RFC 2783 interpreted */
1478c2ecf20Sopenharmony_ci			dev_dbg(pps->dev, "time format unspecified (%x)\n",
1488c2ecf20Sopenharmony_ci								params.mode);
1498c2ecf20Sopenharmony_ci			pps->params.mode |= PPS_TSFMT_TSPEC;
1508c2ecf20Sopenharmony_ci		}
1518c2ecf20Sopenharmony_ci		if (pps->info.mode & PPS_CANWAIT)
1528c2ecf20Sopenharmony_ci			pps->params.mode |= PPS_CANWAIT;
1538c2ecf20Sopenharmony_ci		pps->params.api_version = PPS_API_VERS;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci		/*
1568c2ecf20Sopenharmony_ci		 * Clear unused fields of pps_kparams to avoid leaking
1578c2ecf20Sopenharmony_ci		 * uninitialized data of the PPS_SETPARAMS caller via
1588c2ecf20Sopenharmony_ci		 * PPS_GETPARAMS
1598c2ecf20Sopenharmony_ci		 */
1608c2ecf20Sopenharmony_ci		pps->params.assert_off_tu.flags = 0;
1618c2ecf20Sopenharmony_ci		pps->params.clear_off_tu.flags = 0;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci		spin_unlock_irq(&pps->lock);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	case PPS_GETCAP:
1688c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "PPS_GETCAP\n");
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci		err = put_user(pps->info.mode, iuarg);
1718c2ecf20Sopenharmony_ci		if (err)
1728c2ecf20Sopenharmony_ci			return -EFAULT;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		break;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	case PPS_FETCH: {
1778c2ecf20Sopenharmony_ci		struct pps_fdata fdata;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "PPS_FETCH\n");
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci		err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
1828c2ecf20Sopenharmony_ci		if (err)
1838c2ecf20Sopenharmony_ci			return -EFAULT;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci		err = pps_cdev_pps_fetch(pps, &fdata);
1868c2ecf20Sopenharmony_ci		if (err)
1878c2ecf20Sopenharmony_ci			return err;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci		/* Return the fetched timestamp */
1908c2ecf20Sopenharmony_ci		spin_lock_irq(&pps->lock);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		fdata.info.assert_sequence = pps->assert_sequence;
1938c2ecf20Sopenharmony_ci		fdata.info.clear_sequence = pps->clear_sequence;
1948c2ecf20Sopenharmony_ci		fdata.info.assert_tu = pps->assert_tu;
1958c2ecf20Sopenharmony_ci		fdata.info.clear_tu = pps->clear_tu;
1968c2ecf20Sopenharmony_ci		fdata.info.current_mode = pps->current_mode;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		spin_unlock_irq(&pps->lock);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
2018c2ecf20Sopenharmony_ci		if (err)
2028c2ecf20Sopenharmony_ci			return -EFAULT;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci		break;
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci	case PPS_KC_BIND: {
2078c2ecf20Sopenharmony_ci		struct pps_bind_args bind_args;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "PPS_KC_BIND\n");
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci		/* Check the capabilities */
2128c2ecf20Sopenharmony_ci		if (!capable(CAP_SYS_TIME))
2138c2ecf20Sopenharmony_ci			return -EPERM;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci		if (copy_from_user(&bind_args, uarg,
2168c2ecf20Sopenharmony_ci					sizeof(struct pps_bind_args)))
2178c2ecf20Sopenharmony_ci			return -EFAULT;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci		/* Check for supported capabilities */
2208c2ecf20Sopenharmony_ci		if ((bind_args.edge & ~pps->info.mode) != 0) {
2218c2ecf20Sopenharmony_ci			dev_err(pps->dev, "unsupported capabilities (%x)\n",
2228c2ecf20Sopenharmony_ci					bind_args.edge);
2238c2ecf20Sopenharmony_ci			return -EINVAL;
2248c2ecf20Sopenharmony_ci		}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci		/* Validate parameters roughly */
2278c2ecf20Sopenharmony_ci		if (bind_args.tsformat != PPS_TSFMT_TSPEC ||
2288c2ecf20Sopenharmony_ci				(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 ||
2298c2ecf20Sopenharmony_ci				bind_args.consumer != PPS_KC_HARDPPS) {
2308c2ecf20Sopenharmony_ci			dev_err(pps->dev, "invalid kernel consumer bind"
2318c2ecf20Sopenharmony_ci					" parameters (%x)\n", bind_args.edge);
2328c2ecf20Sopenharmony_ci			return -EINVAL;
2338c2ecf20Sopenharmony_ci		}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci		err = pps_kc_bind(pps, &bind_args);
2368c2ecf20Sopenharmony_ci		if (err < 0)
2378c2ecf20Sopenharmony_ci			return err;
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci		break;
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci	default:
2428c2ecf20Sopenharmony_ci		return -ENOTTY;
2438c2ecf20Sopenharmony_ci	}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	return 0;
2468c2ecf20Sopenharmony_ci}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci#ifdef CONFIG_COMPAT
2498c2ecf20Sopenharmony_cistatic long pps_cdev_compat_ioctl(struct file *file,
2508c2ecf20Sopenharmony_ci		unsigned int cmd, unsigned long arg)
2518c2ecf20Sopenharmony_ci{
2528c2ecf20Sopenharmony_ci	struct pps_device *pps = file->private_data;
2538c2ecf20Sopenharmony_ci	void __user *uarg = (void __user *) arg;
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *));
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	if (cmd == PPS_FETCH) {
2588c2ecf20Sopenharmony_ci		struct pps_fdata_compat compat;
2598c2ecf20Sopenharmony_ci		struct pps_fdata fdata;
2608c2ecf20Sopenharmony_ci		int err;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci		dev_dbg(pps->dev, "PPS_FETCH\n");
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci		err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat));
2658c2ecf20Sopenharmony_ci		if (err)
2668c2ecf20Sopenharmony_ci			return -EFAULT;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci		memcpy(&fdata.timeout, &compat.timeout,
2698c2ecf20Sopenharmony_ci					sizeof(struct pps_ktime_compat));
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci		err = pps_cdev_pps_fetch(pps, &fdata);
2728c2ecf20Sopenharmony_ci		if (err)
2738c2ecf20Sopenharmony_ci			return err;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci		/* Return the fetched timestamp */
2768c2ecf20Sopenharmony_ci		spin_lock_irq(&pps->lock);
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci		compat.info.assert_sequence = pps->assert_sequence;
2798c2ecf20Sopenharmony_ci		compat.info.clear_sequence = pps->clear_sequence;
2808c2ecf20Sopenharmony_ci		compat.info.current_mode = pps->current_mode;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci		memcpy(&compat.info.assert_tu, &pps->assert_tu,
2838c2ecf20Sopenharmony_ci				sizeof(struct pps_ktime_compat));
2848c2ecf20Sopenharmony_ci		memcpy(&compat.info.clear_tu, &pps->clear_tu,
2858c2ecf20Sopenharmony_ci				sizeof(struct pps_ktime_compat));
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci		spin_unlock_irq(&pps->lock);
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci		return copy_to_user(uarg, &compat,
2908c2ecf20Sopenharmony_ci				sizeof(struct pps_fdata_compat)) ? -EFAULT : 0;
2918c2ecf20Sopenharmony_ci	}
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	return pps_cdev_ioctl(file, cmd, arg);
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ci#else
2968c2ecf20Sopenharmony_ci#define pps_cdev_compat_ioctl	NULL
2978c2ecf20Sopenharmony_ci#endif
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_cistatic int pps_cdev_open(struct inode *inode, struct file *file)
3008c2ecf20Sopenharmony_ci{
3018c2ecf20Sopenharmony_ci	struct pps_device *pps = container_of(inode->i_cdev,
3028c2ecf20Sopenharmony_ci						struct pps_device, cdev);
3038c2ecf20Sopenharmony_ci	file->private_data = pps;
3048c2ecf20Sopenharmony_ci	kobject_get(&pps->dev->kobj);
3058c2ecf20Sopenharmony_ci	return 0;
3068c2ecf20Sopenharmony_ci}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_cistatic int pps_cdev_release(struct inode *inode, struct file *file)
3098c2ecf20Sopenharmony_ci{
3108c2ecf20Sopenharmony_ci	struct pps_device *pps = container_of(inode->i_cdev,
3118c2ecf20Sopenharmony_ci						struct pps_device, cdev);
3128c2ecf20Sopenharmony_ci	kobject_put(&pps->dev->kobj);
3138c2ecf20Sopenharmony_ci	return 0;
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci/*
3178c2ecf20Sopenharmony_ci * Char device stuff
3188c2ecf20Sopenharmony_ci */
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_cistatic const struct file_operations pps_cdev_fops = {
3218c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
3228c2ecf20Sopenharmony_ci	.llseek		= no_llseek,
3238c2ecf20Sopenharmony_ci	.poll		= pps_cdev_poll,
3248c2ecf20Sopenharmony_ci	.fasync		= pps_cdev_fasync,
3258c2ecf20Sopenharmony_ci	.compat_ioctl	= pps_cdev_compat_ioctl,
3268c2ecf20Sopenharmony_ci	.unlocked_ioctl	= pps_cdev_ioctl,
3278c2ecf20Sopenharmony_ci	.open		= pps_cdev_open,
3288c2ecf20Sopenharmony_ci	.release	= pps_cdev_release,
3298c2ecf20Sopenharmony_ci};
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_cistatic void pps_device_destruct(struct device *dev)
3328c2ecf20Sopenharmony_ci{
3338c2ecf20Sopenharmony_ci	struct pps_device *pps = dev_get_drvdata(dev);
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	cdev_del(&pps->cdev);
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	/* Now we can release the ID for re-use */
3388c2ecf20Sopenharmony_ci	pr_debug("deallocating pps%d\n", pps->id);
3398c2ecf20Sopenharmony_ci	mutex_lock(&pps_idr_lock);
3408c2ecf20Sopenharmony_ci	idr_remove(&pps_idr, pps->id);
3418c2ecf20Sopenharmony_ci	mutex_unlock(&pps_idr_lock);
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	kfree(dev);
3448c2ecf20Sopenharmony_ci	kfree(pps);
3458c2ecf20Sopenharmony_ci}
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ciint pps_register_cdev(struct pps_device *pps)
3488c2ecf20Sopenharmony_ci{
3498c2ecf20Sopenharmony_ci	int err;
3508c2ecf20Sopenharmony_ci	dev_t devt;
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	mutex_lock(&pps_idr_lock);
3538c2ecf20Sopenharmony_ci	/*
3548c2ecf20Sopenharmony_ci	 * Get new ID for the new PPS source.  After idr_alloc() calling
3558c2ecf20Sopenharmony_ci	 * the new source will be freely available into the kernel.
3568c2ecf20Sopenharmony_ci	 */
3578c2ecf20Sopenharmony_ci	err = idr_alloc(&pps_idr, pps, 0, PPS_MAX_SOURCES, GFP_KERNEL);
3588c2ecf20Sopenharmony_ci	if (err < 0) {
3598c2ecf20Sopenharmony_ci		if (err == -ENOSPC) {
3608c2ecf20Sopenharmony_ci			pr_err("%s: too many PPS sources in the system\n",
3618c2ecf20Sopenharmony_ci			       pps->info.name);
3628c2ecf20Sopenharmony_ci			err = -EBUSY;
3638c2ecf20Sopenharmony_ci		}
3648c2ecf20Sopenharmony_ci		goto out_unlock;
3658c2ecf20Sopenharmony_ci	}
3668c2ecf20Sopenharmony_ci	pps->id = err;
3678c2ecf20Sopenharmony_ci	mutex_unlock(&pps_idr_lock);
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	devt = MKDEV(MAJOR(pps_devt), pps->id);
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	cdev_init(&pps->cdev, &pps_cdev_fops);
3728c2ecf20Sopenharmony_ci	pps->cdev.owner = pps->info.owner;
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	err = cdev_add(&pps->cdev, devt, 1);
3758c2ecf20Sopenharmony_ci	if (err) {
3768c2ecf20Sopenharmony_ci		pr_err("%s: failed to add char device %d:%d\n",
3778c2ecf20Sopenharmony_ci				pps->info.name, MAJOR(pps_devt), pps->id);
3788c2ecf20Sopenharmony_ci		goto free_idr;
3798c2ecf20Sopenharmony_ci	}
3808c2ecf20Sopenharmony_ci	pps->dev = device_create(pps_class, pps->info.dev, devt, pps,
3818c2ecf20Sopenharmony_ci							"pps%d", pps->id);
3828c2ecf20Sopenharmony_ci	if (IS_ERR(pps->dev)) {
3838c2ecf20Sopenharmony_ci		err = PTR_ERR(pps->dev);
3848c2ecf20Sopenharmony_ci		goto del_cdev;
3858c2ecf20Sopenharmony_ci	}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	/* Override the release function with our own */
3888c2ecf20Sopenharmony_ci	pps->dev->release = pps_device_destruct;
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
3918c2ecf20Sopenharmony_ci			MAJOR(pps_devt), pps->id);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	return 0;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_cidel_cdev:
3968c2ecf20Sopenharmony_ci	cdev_del(&pps->cdev);
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_cifree_idr:
3998c2ecf20Sopenharmony_ci	mutex_lock(&pps_idr_lock);
4008c2ecf20Sopenharmony_ci	idr_remove(&pps_idr, pps->id);
4018c2ecf20Sopenharmony_ciout_unlock:
4028c2ecf20Sopenharmony_ci	mutex_unlock(&pps_idr_lock);
4038c2ecf20Sopenharmony_ci	return err;
4048c2ecf20Sopenharmony_ci}
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_civoid pps_unregister_cdev(struct pps_device *pps)
4078c2ecf20Sopenharmony_ci{
4088c2ecf20Sopenharmony_ci	pr_debug("unregistering pps%d\n", pps->id);
4098c2ecf20Sopenharmony_ci	pps->lookup_cookie = NULL;
4108c2ecf20Sopenharmony_ci	device_destroy(pps_class, pps->dev->devt);
4118c2ecf20Sopenharmony_ci}
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci/*
4148c2ecf20Sopenharmony_ci * Look up a pps device by magic cookie.
4158c2ecf20Sopenharmony_ci * The cookie is usually a pointer to some enclosing device, but this
4168c2ecf20Sopenharmony_ci * code doesn't care; you should never be dereferencing it.
4178c2ecf20Sopenharmony_ci *
4188c2ecf20Sopenharmony_ci * This is a bit of a kludge that is currently used only by the PPS
4198c2ecf20Sopenharmony_ci * serial line discipline.  It may need to be tweaked when a second user
4208c2ecf20Sopenharmony_ci * is found.
4218c2ecf20Sopenharmony_ci *
4228c2ecf20Sopenharmony_ci * There is no function interface for setting the lookup_cookie field.
4238c2ecf20Sopenharmony_ci * It's initialized to NULL when the pps device is created, and if a
4248c2ecf20Sopenharmony_ci * client wants to use it, just fill it in afterward.
4258c2ecf20Sopenharmony_ci *
4268c2ecf20Sopenharmony_ci * The cookie is automatically set to NULL in pps_unregister_source()
4278c2ecf20Sopenharmony_ci * so that it will not be used again, even if the pps device cannot
4288c2ecf20Sopenharmony_ci * be removed from the idr due to pending references holding the minor
4298c2ecf20Sopenharmony_ci * number in use.
4308c2ecf20Sopenharmony_ci */
4318c2ecf20Sopenharmony_cistruct pps_device *pps_lookup_dev(void const *cookie)
4328c2ecf20Sopenharmony_ci{
4338c2ecf20Sopenharmony_ci	struct pps_device *pps;
4348c2ecf20Sopenharmony_ci	unsigned id;
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci	rcu_read_lock();
4378c2ecf20Sopenharmony_ci	idr_for_each_entry(&pps_idr, pps, id)
4388c2ecf20Sopenharmony_ci		if (cookie == pps->lookup_cookie)
4398c2ecf20Sopenharmony_ci			break;
4408c2ecf20Sopenharmony_ci	rcu_read_unlock();
4418c2ecf20Sopenharmony_ci	return pps;
4428c2ecf20Sopenharmony_ci}
4438c2ecf20Sopenharmony_ciEXPORT_SYMBOL(pps_lookup_dev);
4448c2ecf20Sopenharmony_ci
4458c2ecf20Sopenharmony_ci/*
4468c2ecf20Sopenharmony_ci * Module stuff
4478c2ecf20Sopenharmony_ci */
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_cistatic void __exit pps_exit(void)
4508c2ecf20Sopenharmony_ci{
4518c2ecf20Sopenharmony_ci	class_destroy(pps_class);
4528c2ecf20Sopenharmony_ci	unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
4538c2ecf20Sopenharmony_ci}
4548c2ecf20Sopenharmony_ci
4558c2ecf20Sopenharmony_cistatic int __init pps_init(void)
4568c2ecf20Sopenharmony_ci{
4578c2ecf20Sopenharmony_ci	int err;
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	pps_class = class_create(THIS_MODULE, "pps");
4608c2ecf20Sopenharmony_ci	if (IS_ERR(pps_class)) {
4618c2ecf20Sopenharmony_ci		pr_err("failed to allocate class\n");
4628c2ecf20Sopenharmony_ci		return PTR_ERR(pps_class);
4638c2ecf20Sopenharmony_ci	}
4648c2ecf20Sopenharmony_ci	pps_class->dev_groups = pps_groups;
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_ci	err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
4678c2ecf20Sopenharmony_ci	if (err < 0) {
4688c2ecf20Sopenharmony_ci		pr_err("failed to allocate char device region\n");
4698c2ecf20Sopenharmony_ci		goto remove_class;
4708c2ecf20Sopenharmony_ci	}
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS);
4738c2ecf20Sopenharmony_ci	pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
4748c2ecf20Sopenharmony_ci		"<giometti@linux.it>\n", PPS_VERSION);
4758c2ecf20Sopenharmony_ci
4768c2ecf20Sopenharmony_ci	return 0;
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ciremove_class:
4798c2ecf20Sopenharmony_ci	class_destroy(pps_class);
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	return err;
4828c2ecf20Sopenharmony_ci}
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_cisubsys_initcall(pps_init);
4858c2ecf20Sopenharmony_cimodule_exit(pps_exit);
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ciMODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
4888c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
4898c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
490