xref: /OK3568_Linux_fs/kernel/drivers/pps/pps.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * PPS core file
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (C) 2005-2009   Rodolfo Giometti <giometti@linux.it>
6*4882a593Smuzhiyun  */
7*4882a593Smuzhiyun 
8*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9*4882a593Smuzhiyun 
10*4882a593Smuzhiyun #include <linux/kernel.h>
11*4882a593Smuzhiyun #include <linux/module.h>
12*4882a593Smuzhiyun #include <linux/init.h>
13*4882a593Smuzhiyun #include <linux/sched.h>
14*4882a593Smuzhiyun #include <linux/uaccess.h>
15*4882a593Smuzhiyun #include <linux/idr.h>
16*4882a593Smuzhiyun #include <linux/mutex.h>
17*4882a593Smuzhiyun #include <linux/cdev.h>
18*4882a593Smuzhiyun #include <linux/poll.h>
19*4882a593Smuzhiyun #include <linux/pps_kernel.h>
20*4882a593Smuzhiyun #include <linux/slab.h>
21*4882a593Smuzhiyun 
22*4882a593Smuzhiyun #include "kc.h"
23*4882a593Smuzhiyun 
24*4882a593Smuzhiyun /*
25*4882a593Smuzhiyun  * Local variables
26*4882a593Smuzhiyun  */
27*4882a593Smuzhiyun 
28*4882a593Smuzhiyun static dev_t pps_devt;
29*4882a593Smuzhiyun static struct class *pps_class;
30*4882a593Smuzhiyun 
31*4882a593Smuzhiyun static DEFINE_MUTEX(pps_idr_lock);
32*4882a593Smuzhiyun static DEFINE_IDR(pps_idr);
33*4882a593Smuzhiyun 
34*4882a593Smuzhiyun /*
35*4882a593Smuzhiyun  * Char device methods
36*4882a593Smuzhiyun  */
37*4882a593Smuzhiyun 
pps_cdev_poll(struct file * file,poll_table * wait)38*4882a593Smuzhiyun static __poll_t pps_cdev_poll(struct file *file, poll_table *wait)
39*4882a593Smuzhiyun {
40*4882a593Smuzhiyun 	struct pps_device *pps = file->private_data;
41*4882a593Smuzhiyun 
42*4882a593Smuzhiyun 	poll_wait(file, &pps->queue, wait);
43*4882a593Smuzhiyun 
44*4882a593Smuzhiyun 	return EPOLLIN | EPOLLRDNORM;
45*4882a593Smuzhiyun }
46*4882a593Smuzhiyun 
pps_cdev_fasync(int fd,struct file * file,int on)47*4882a593Smuzhiyun static int pps_cdev_fasync(int fd, struct file *file, int on)
48*4882a593Smuzhiyun {
49*4882a593Smuzhiyun 	struct pps_device *pps = file->private_data;
50*4882a593Smuzhiyun 	return fasync_helper(fd, file, on, &pps->async_queue);
51*4882a593Smuzhiyun }
52*4882a593Smuzhiyun 
pps_cdev_pps_fetch(struct pps_device * pps,struct pps_fdata * fdata)53*4882a593Smuzhiyun static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata)
54*4882a593Smuzhiyun {
55*4882a593Smuzhiyun 	unsigned int ev = pps->last_ev;
56*4882a593Smuzhiyun 	int err = 0;
57*4882a593Smuzhiyun 
58*4882a593Smuzhiyun 	/* Manage the timeout */
59*4882a593Smuzhiyun 	if (fdata->timeout.flags & PPS_TIME_INVALID)
60*4882a593Smuzhiyun 		err = wait_event_interruptible(pps->queue,
61*4882a593Smuzhiyun 				ev != pps->last_ev);
62*4882a593Smuzhiyun 	else {
63*4882a593Smuzhiyun 		unsigned long ticks;
64*4882a593Smuzhiyun 
65*4882a593Smuzhiyun 		dev_dbg(pps->dev, "timeout %lld.%09d\n",
66*4882a593Smuzhiyun 				(long long) fdata->timeout.sec,
67*4882a593Smuzhiyun 				fdata->timeout.nsec);
68*4882a593Smuzhiyun 		ticks = fdata->timeout.sec * HZ;
69*4882a593Smuzhiyun 		ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ);
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun 		if (ticks != 0) {
72*4882a593Smuzhiyun 			err = wait_event_interruptible_timeout(
73*4882a593Smuzhiyun 					pps->queue,
74*4882a593Smuzhiyun 					ev != pps->last_ev,
75*4882a593Smuzhiyun 					ticks);
76*4882a593Smuzhiyun 			if (err == 0)
77*4882a593Smuzhiyun 				return -ETIMEDOUT;
78*4882a593Smuzhiyun 		}
79*4882a593Smuzhiyun 	}
80*4882a593Smuzhiyun 
81*4882a593Smuzhiyun 	/* Check for pending signals */
82*4882a593Smuzhiyun 	if (err == -ERESTARTSYS) {
83*4882a593Smuzhiyun 		dev_dbg(pps->dev, "pending signal caught\n");
84*4882a593Smuzhiyun 		return -EINTR;
85*4882a593Smuzhiyun 	}
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	return 0;
88*4882a593Smuzhiyun }
89*4882a593Smuzhiyun 
pps_cdev_ioctl(struct file * file,unsigned int cmd,unsigned long arg)90*4882a593Smuzhiyun static long pps_cdev_ioctl(struct file *file,
91*4882a593Smuzhiyun 		unsigned int cmd, unsigned long arg)
92*4882a593Smuzhiyun {
93*4882a593Smuzhiyun 	struct pps_device *pps = file->private_data;
94*4882a593Smuzhiyun 	struct pps_kparams params;
95*4882a593Smuzhiyun 	void __user *uarg = (void __user *) arg;
96*4882a593Smuzhiyun 	int __user *iuarg = (int __user *) arg;
97*4882a593Smuzhiyun 	int err;
98*4882a593Smuzhiyun 
99*4882a593Smuzhiyun 	switch (cmd) {
100*4882a593Smuzhiyun 	case PPS_GETPARAMS:
101*4882a593Smuzhiyun 		dev_dbg(pps->dev, "PPS_GETPARAMS\n");
102*4882a593Smuzhiyun 
103*4882a593Smuzhiyun 		spin_lock_irq(&pps->lock);
104*4882a593Smuzhiyun 
105*4882a593Smuzhiyun 		/* Get the current parameters */
106*4882a593Smuzhiyun 		params = pps->params;
107*4882a593Smuzhiyun 
108*4882a593Smuzhiyun 		spin_unlock_irq(&pps->lock);
109*4882a593Smuzhiyun 
110*4882a593Smuzhiyun 		err = copy_to_user(uarg, &params, sizeof(struct pps_kparams));
111*4882a593Smuzhiyun 		if (err)
112*4882a593Smuzhiyun 			return -EFAULT;
113*4882a593Smuzhiyun 
114*4882a593Smuzhiyun 		break;
115*4882a593Smuzhiyun 
116*4882a593Smuzhiyun 	case PPS_SETPARAMS:
117*4882a593Smuzhiyun 		dev_dbg(pps->dev, "PPS_SETPARAMS\n");
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun 		/* Check the capabilities */
120*4882a593Smuzhiyun 		if (!capable(CAP_SYS_TIME))
121*4882a593Smuzhiyun 			return -EPERM;
122*4882a593Smuzhiyun 
123*4882a593Smuzhiyun 		err = copy_from_user(&params, uarg, sizeof(struct pps_kparams));
124*4882a593Smuzhiyun 		if (err)
125*4882a593Smuzhiyun 			return -EFAULT;
126*4882a593Smuzhiyun 		if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) {
127*4882a593Smuzhiyun 			dev_dbg(pps->dev, "capture mode unspecified (%x)\n",
128*4882a593Smuzhiyun 								params.mode);
129*4882a593Smuzhiyun 			return -EINVAL;
130*4882a593Smuzhiyun 		}
131*4882a593Smuzhiyun 
132*4882a593Smuzhiyun 		/* Check for supported capabilities */
133*4882a593Smuzhiyun 		if ((params.mode & ~pps->info.mode) != 0) {
134*4882a593Smuzhiyun 			dev_dbg(pps->dev, "unsupported capabilities (%x)\n",
135*4882a593Smuzhiyun 								params.mode);
136*4882a593Smuzhiyun 			return -EINVAL;
137*4882a593Smuzhiyun 		}
138*4882a593Smuzhiyun 
139*4882a593Smuzhiyun 		spin_lock_irq(&pps->lock);
140*4882a593Smuzhiyun 
141*4882a593Smuzhiyun 		/* Save the new parameters */
142*4882a593Smuzhiyun 		pps->params = params;
143*4882a593Smuzhiyun 
144*4882a593Smuzhiyun 		/* Restore the read only parameters */
145*4882a593Smuzhiyun 		if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
146*4882a593Smuzhiyun 			/* section 3.3 of RFC 2783 interpreted */
147*4882a593Smuzhiyun 			dev_dbg(pps->dev, "time format unspecified (%x)\n",
148*4882a593Smuzhiyun 								params.mode);
149*4882a593Smuzhiyun 			pps->params.mode |= PPS_TSFMT_TSPEC;
150*4882a593Smuzhiyun 		}
151*4882a593Smuzhiyun 		if (pps->info.mode & PPS_CANWAIT)
152*4882a593Smuzhiyun 			pps->params.mode |= PPS_CANWAIT;
153*4882a593Smuzhiyun 		pps->params.api_version = PPS_API_VERS;
154*4882a593Smuzhiyun 
155*4882a593Smuzhiyun 		/*
156*4882a593Smuzhiyun 		 * Clear unused fields of pps_kparams to avoid leaking
157*4882a593Smuzhiyun 		 * uninitialized data of the PPS_SETPARAMS caller via
158*4882a593Smuzhiyun 		 * PPS_GETPARAMS
159*4882a593Smuzhiyun 		 */
160*4882a593Smuzhiyun 		pps->params.assert_off_tu.flags = 0;
161*4882a593Smuzhiyun 		pps->params.clear_off_tu.flags = 0;
162*4882a593Smuzhiyun 
163*4882a593Smuzhiyun 		spin_unlock_irq(&pps->lock);
164*4882a593Smuzhiyun 
165*4882a593Smuzhiyun 		break;
166*4882a593Smuzhiyun 
167*4882a593Smuzhiyun 	case PPS_GETCAP:
168*4882a593Smuzhiyun 		dev_dbg(pps->dev, "PPS_GETCAP\n");
169*4882a593Smuzhiyun 
170*4882a593Smuzhiyun 		err = put_user(pps->info.mode, iuarg);
171*4882a593Smuzhiyun 		if (err)
172*4882a593Smuzhiyun 			return -EFAULT;
173*4882a593Smuzhiyun 
174*4882a593Smuzhiyun 		break;
175*4882a593Smuzhiyun 
176*4882a593Smuzhiyun 	case PPS_FETCH: {
177*4882a593Smuzhiyun 		struct pps_fdata fdata;
178*4882a593Smuzhiyun 
179*4882a593Smuzhiyun 		dev_dbg(pps->dev, "PPS_FETCH\n");
180*4882a593Smuzhiyun 
181*4882a593Smuzhiyun 		err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));
182*4882a593Smuzhiyun 		if (err)
183*4882a593Smuzhiyun 			return -EFAULT;
184*4882a593Smuzhiyun 
185*4882a593Smuzhiyun 		err = pps_cdev_pps_fetch(pps, &fdata);
186*4882a593Smuzhiyun 		if (err)
187*4882a593Smuzhiyun 			return err;
188*4882a593Smuzhiyun 
189*4882a593Smuzhiyun 		/* Return the fetched timestamp */
190*4882a593Smuzhiyun 		spin_lock_irq(&pps->lock);
191*4882a593Smuzhiyun 
192*4882a593Smuzhiyun 		fdata.info.assert_sequence = pps->assert_sequence;
193*4882a593Smuzhiyun 		fdata.info.clear_sequence = pps->clear_sequence;
194*4882a593Smuzhiyun 		fdata.info.assert_tu = pps->assert_tu;
195*4882a593Smuzhiyun 		fdata.info.clear_tu = pps->clear_tu;
196*4882a593Smuzhiyun 		fdata.info.current_mode = pps->current_mode;
197*4882a593Smuzhiyun 
198*4882a593Smuzhiyun 		spin_unlock_irq(&pps->lock);
199*4882a593Smuzhiyun 
200*4882a593Smuzhiyun 		err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata));
201*4882a593Smuzhiyun 		if (err)
202*4882a593Smuzhiyun 			return -EFAULT;
203*4882a593Smuzhiyun 
204*4882a593Smuzhiyun 		break;
205*4882a593Smuzhiyun 	}
206*4882a593Smuzhiyun 	case PPS_KC_BIND: {
207*4882a593Smuzhiyun 		struct pps_bind_args bind_args;
208*4882a593Smuzhiyun 
209*4882a593Smuzhiyun 		dev_dbg(pps->dev, "PPS_KC_BIND\n");
210*4882a593Smuzhiyun 
211*4882a593Smuzhiyun 		/* Check the capabilities */
212*4882a593Smuzhiyun 		if (!capable(CAP_SYS_TIME))
213*4882a593Smuzhiyun 			return -EPERM;
214*4882a593Smuzhiyun 
215*4882a593Smuzhiyun 		if (copy_from_user(&bind_args, uarg,
216*4882a593Smuzhiyun 					sizeof(struct pps_bind_args)))
217*4882a593Smuzhiyun 			return -EFAULT;
218*4882a593Smuzhiyun 
219*4882a593Smuzhiyun 		/* Check for supported capabilities */
220*4882a593Smuzhiyun 		if ((bind_args.edge & ~pps->info.mode) != 0) {
221*4882a593Smuzhiyun 			dev_err(pps->dev, "unsupported capabilities (%x)\n",
222*4882a593Smuzhiyun 					bind_args.edge);
223*4882a593Smuzhiyun 			return -EINVAL;
224*4882a593Smuzhiyun 		}
225*4882a593Smuzhiyun 
226*4882a593Smuzhiyun 		/* Validate parameters roughly */
227*4882a593Smuzhiyun 		if (bind_args.tsformat != PPS_TSFMT_TSPEC ||
228*4882a593Smuzhiyun 				(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 ||
229*4882a593Smuzhiyun 				bind_args.consumer != PPS_KC_HARDPPS) {
230*4882a593Smuzhiyun 			dev_err(pps->dev, "invalid kernel consumer bind"
231*4882a593Smuzhiyun 					" parameters (%x)\n", bind_args.edge);
232*4882a593Smuzhiyun 			return -EINVAL;
233*4882a593Smuzhiyun 		}
234*4882a593Smuzhiyun 
235*4882a593Smuzhiyun 		err = pps_kc_bind(pps, &bind_args);
236*4882a593Smuzhiyun 		if (err < 0)
237*4882a593Smuzhiyun 			return err;
238*4882a593Smuzhiyun 
239*4882a593Smuzhiyun 		break;
240*4882a593Smuzhiyun 	}
241*4882a593Smuzhiyun 	default:
242*4882a593Smuzhiyun 		return -ENOTTY;
243*4882a593Smuzhiyun 	}
244*4882a593Smuzhiyun 
245*4882a593Smuzhiyun 	return 0;
246*4882a593Smuzhiyun }
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun #ifdef CONFIG_COMPAT
pps_cdev_compat_ioctl(struct file * file,unsigned int cmd,unsigned long arg)249*4882a593Smuzhiyun static long pps_cdev_compat_ioctl(struct file *file,
250*4882a593Smuzhiyun 		unsigned int cmd, unsigned long arg)
251*4882a593Smuzhiyun {
252*4882a593Smuzhiyun 	struct pps_device *pps = file->private_data;
253*4882a593Smuzhiyun 	void __user *uarg = (void __user *) arg;
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun 	cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *));
256*4882a593Smuzhiyun 
257*4882a593Smuzhiyun 	if (cmd == PPS_FETCH) {
258*4882a593Smuzhiyun 		struct pps_fdata_compat compat;
259*4882a593Smuzhiyun 		struct pps_fdata fdata;
260*4882a593Smuzhiyun 		int err;
261*4882a593Smuzhiyun 
262*4882a593Smuzhiyun 		dev_dbg(pps->dev, "PPS_FETCH\n");
263*4882a593Smuzhiyun 
264*4882a593Smuzhiyun 		err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat));
265*4882a593Smuzhiyun 		if (err)
266*4882a593Smuzhiyun 			return -EFAULT;
267*4882a593Smuzhiyun 
268*4882a593Smuzhiyun 		memcpy(&fdata.timeout, &compat.timeout,
269*4882a593Smuzhiyun 					sizeof(struct pps_ktime_compat));
270*4882a593Smuzhiyun 
271*4882a593Smuzhiyun 		err = pps_cdev_pps_fetch(pps, &fdata);
272*4882a593Smuzhiyun 		if (err)
273*4882a593Smuzhiyun 			return err;
274*4882a593Smuzhiyun 
275*4882a593Smuzhiyun 		/* Return the fetched timestamp */
276*4882a593Smuzhiyun 		spin_lock_irq(&pps->lock);
277*4882a593Smuzhiyun 
278*4882a593Smuzhiyun 		compat.info.assert_sequence = pps->assert_sequence;
279*4882a593Smuzhiyun 		compat.info.clear_sequence = pps->clear_sequence;
280*4882a593Smuzhiyun 		compat.info.current_mode = pps->current_mode;
281*4882a593Smuzhiyun 
282*4882a593Smuzhiyun 		memcpy(&compat.info.assert_tu, &pps->assert_tu,
283*4882a593Smuzhiyun 				sizeof(struct pps_ktime_compat));
284*4882a593Smuzhiyun 		memcpy(&compat.info.clear_tu, &pps->clear_tu,
285*4882a593Smuzhiyun 				sizeof(struct pps_ktime_compat));
286*4882a593Smuzhiyun 
287*4882a593Smuzhiyun 		spin_unlock_irq(&pps->lock);
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun 		return copy_to_user(uarg, &compat,
290*4882a593Smuzhiyun 				sizeof(struct pps_fdata_compat)) ? -EFAULT : 0;
291*4882a593Smuzhiyun 	}
292*4882a593Smuzhiyun 
293*4882a593Smuzhiyun 	return pps_cdev_ioctl(file, cmd, arg);
294*4882a593Smuzhiyun }
295*4882a593Smuzhiyun #else
296*4882a593Smuzhiyun #define pps_cdev_compat_ioctl	NULL
297*4882a593Smuzhiyun #endif
298*4882a593Smuzhiyun 
pps_cdev_open(struct inode * inode,struct file * file)299*4882a593Smuzhiyun static int pps_cdev_open(struct inode *inode, struct file *file)
300*4882a593Smuzhiyun {
301*4882a593Smuzhiyun 	struct pps_device *pps = container_of(inode->i_cdev,
302*4882a593Smuzhiyun 						struct pps_device, cdev);
303*4882a593Smuzhiyun 	file->private_data = pps;
304*4882a593Smuzhiyun 	kobject_get(&pps->dev->kobj);
305*4882a593Smuzhiyun 	return 0;
306*4882a593Smuzhiyun }
307*4882a593Smuzhiyun 
pps_cdev_release(struct inode * inode,struct file * file)308*4882a593Smuzhiyun static int pps_cdev_release(struct inode *inode, struct file *file)
309*4882a593Smuzhiyun {
310*4882a593Smuzhiyun 	struct pps_device *pps = container_of(inode->i_cdev,
311*4882a593Smuzhiyun 						struct pps_device, cdev);
312*4882a593Smuzhiyun 	kobject_put(&pps->dev->kobj);
313*4882a593Smuzhiyun 	return 0;
314*4882a593Smuzhiyun }
315*4882a593Smuzhiyun 
316*4882a593Smuzhiyun /*
317*4882a593Smuzhiyun  * Char device stuff
318*4882a593Smuzhiyun  */
319*4882a593Smuzhiyun 
320*4882a593Smuzhiyun static const struct file_operations pps_cdev_fops = {
321*4882a593Smuzhiyun 	.owner		= THIS_MODULE,
322*4882a593Smuzhiyun 	.llseek		= no_llseek,
323*4882a593Smuzhiyun 	.poll		= pps_cdev_poll,
324*4882a593Smuzhiyun 	.fasync		= pps_cdev_fasync,
325*4882a593Smuzhiyun 	.compat_ioctl	= pps_cdev_compat_ioctl,
326*4882a593Smuzhiyun 	.unlocked_ioctl	= pps_cdev_ioctl,
327*4882a593Smuzhiyun 	.open		= pps_cdev_open,
328*4882a593Smuzhiyun 	.release	= pps_cdev_release,
329*4882a593Smuzhiyun };
330*4882a593Smuzhiyun 
pps_device_destruct(struct device * dev)331*4882a593Smuzhiyun static void pps_device_destruct(struct device *dev)
332*4882a593Smuzhiyun {
333*4882a593Smuzhiyun 	struct pps_device *pps = dev_get_drvdata(dev);
334*4882a593Smuzhiyun 
335*4882a593Smuzhiyun 	cdev_del(&pps->cdev);
336*4882a593Smuzhiyun 
337*4882a593Smuzhiyun 	/* Now we can release the ID for re-use */
338*4882a593Smuzhiyun 	pr_debug("deallocating pps%d\n", pps->id);
339*4882a593Smuzhiyun 	mutex_lock(&pps_idr_lock);
340*4882a593Smuzhiyun 	idr_remove(&pps_idr, pps->id);
341*4882a593Smuzhiyun 	mutex_unlock(&pps_idr_lock);
342*4882a593Smuzhiyun 
343*4882a593Smuzhiyun 	kfree(dev);
344*4882a593Smuzhiyun 	kfree(pps);
345*4882a593Smuzhiyun }
346*4882a593Smuzhiyun 
pps_register_cdev(struct pps_device * pps)347*4882a593Smuzhiyun int pps_register_cdev(struct pps_device *pps)
348*4882a593Smuzhiyun {
349*4882a593Smuzhiyun 	int err;
350*4882a593Smuzhiyun 	dev_t devt;
351*4882a593Smuzhiyun 
352*4882a593Smuzhiyun 	mutex_lock(&pps_idr_lock);
353*4882a593Smuzhiyun 	/*
354*4882a593Smuzhiyun 	 * Get new ID for the new PPS source.  After idr_alloc() calling
355*4882a593Smuzhiyun 	 * the new source will be freely available into the kernel.
356*4882a593Smuzhiyun 	 */
357*4882a593Smuzhiyun 	err = idr_alloc(&pps_idr, pps, 0, PPS_MAX_SOURCES, GFP_KERNEL);
358*4882a593Smuzhiyun 	if (err < 0) {
359*4882a593Smuzhiyun 		if (err == -ENOSPC) {
360*4882a593Smuzhiyun 			pr_err("%s: too many PPS sources in the system\n",
361*4882a593Smuzhiyun 			       pps->info.name);
362*4882a593Smuzhiyun 			err = -EBUSY;
363*4882a593Smuzhiyun 		}
364*4882a593Smuzhiyun 		goto out_unlock;
365*4882a593Smuzhiyun 	}
366*4882a593Smuzhiyun 	pps->id = err;
367*4882a593Smuzhiyun 	mutex_unlock(&pps_idr_lock);
368*4882a593Smuzhiyun 
369*4882a593Smuzhiyun 	devt = MKDEV(MAJOR(pps_devt), pps->id);
370*4882a593Smuzhiyun 
371*4882a593Smuzhiyun 	cdev_init(&pps->cdev, &pps_cdev_fops);
372*4882a593Smuzhiyun 	pps->cdev.owner = pps->info.owner;
373*4882a593Smuzhiyun 
374*4882a593Smuzhiyun 	err = cdev_add(&pps->cdev, devt, 1);
375*4882a593Smuzhiyun 	if (err) {
376*4882a593Smuzhiyun 		pr_err("%s: failed to add char device %d:%d\n",
377*4882a593Smuzhiyun 				pps->info.name, MAJOR(pps_devt), pps->id);
378*4882a593Smuzhiyun 		goto free_idr;
379*4882a593Smuzhiyun 	}
380*4882a593Smuzhiyun 	pps->dev = device_create(pps_class, pps->info.dev, devt, pps,
381*4882a593Smuzhiyun 							"pps%d", pps->id);
382*4882a593Smuzhiyun 	if (IS_ERR(pps->dev)) {
383*4882a593Smuzhiyun 		err = PTR_ERR(pps->dev);
384*4882a593Smuzhiyun 		goto del_cdev;
385*4882a593Smuzhiyun 	}
386*4882a593Smuzhiyun 
387*4882a593Smuzhiyun 	/* Override the release function with our own */
388*4882a593Smuzhiyun 	pps->dev->release = pps_device_destruct;
389*4882a593Smuzhiyun 
390*4882a593Smuzhiyun 	pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
391*4882a593Smuzhiyun 			MAJOR(pps_devt), pps->id);
392*4882a593Smuzhiyun 
393*4882a593Smuzhiyun 	return 0;
394*4882a593Smuzhiyun 
395*4882a593Smuzhiyun del_cdev:
396*4882a593Smuzhiyun 	cdev_del(&pps->cdev);
397*4882a593Smuzhiyun 
398*4882a593Smuzhiyun free_idr:
399*4882a593Smuzhiyun 	mutex_lock(&pps_idr_lock);
400*4882a593Smuzhiyun 	idr_remove(&pps_idr, pps->id);
401*4882a593Smuzhiyun out_unlock:
402*4882a593Smuzhiyun 	mutex_unlock(&pps_idr_lock);
403*4882a593Smuzhiyun 	return err;
404*4882a593Smuzhiyun }
405*4882a593Smuzhiyun 
pps_unregister_cdev(struct pps_device * pps)406*4882a593Smuzhiyun void pps_unregister_cdev(struct pps_device *pps)
407*4882a593Smuzhiyun {
408*4882a593Smuzhiyun 	pr_debug("unregistering pps%d\n", pps->id);
409*4882a593Smuzhiyun 	pps->lookup_cookie = NULL;
410*4882a593Smuzhiyun 	device_destroy(pps_class, pps->dev->devt);
411*4882a593Smuzhiyun }
412*4882a593Smuzhiyun 
413*4882a593Smuzhiyun /*
414*4882a593Smuzhiyun  * Look up a pps device by magic cookie.
415*4882a593Smuzhiyun  * The cookie is usually a pointer to some enclosing device, but this
416*4882a593Smuzhiyun  * code doesn't care; you should never be dereferencing it.
417*4882a593Smuzhiyun  *
418*4882a593Smuzhiyun  * This is a bit of a kludge that is currently used only by the PPS
419*4882a593Smuzhiyun  * serial line discipline.  It may need to be tweaked when a second user
420*4882a593Smuzhiyun  * is found.
421*4882a593Smuzhiyun  *
422*4882a593Smuzhiyun  * There is no function interface for setting the lookup_cookie field.
423*4882a593Smuzhiyun  * It's initialized to NULL when the pps device is created, and if a
424*4882a593Smuzhiyun  * client wants to use it, just fill it in afterward.
425*4882a593Smuzhiyun  *
426*4882a593Smuzhiyun  * The cookie is automatically set to NULL in pps_unregister_source()
427*4882a593Smuzhiyun  * so that it will not be used again, even if the pps device cannot
428*4882a593Smuzhiyun  * be removed from the idr due to pending references holding the minor
429*4882a593Smuzhiyun  * number in use.
430*4882a593Smuzhiyun  */
pps_lookup_dev(void const * cookie)431*4882a593Smuzhiyun struct pps_device *pps_lookup_dev(void const *cookie)
432*4882a593Smuzhiyun {
433*4882a593Smuzhiyun 	struct pps_device *pps;
434*4882a593Smuzhiyun 	unsigned id;
435*4882a593Smuzhiyun 
436*4882a593Smuzhiyun 	rcu_read_lock();
437*4882a593Smuzhiyun 	idr_for_each_entry(&pps_idr, pps, id)
438*4882a593Smuzhiyun 		if (cookie == pps->lookup_cookie)
439*4882a593Smuzhiyun 			break;
440*4882a593Smuzhiyun 	rcu_read_unlock();
441*4882a593Smuzhiyun 	return pps;
442*4882a593Smuzhiyun }
443*4882a593Smuzhiyun EXPORT_SYMBOL(pps_lookup_dev);
444*4882a593Smuzhiyun 
445*4882a593Smuzhiyun /*
446*4882a593Smuzhiyun  * Module stuff
447*4882a593Smuzhiyun  */
448*4882a593Smuzhiyun 
pps_exit(void)449*4882a593Smuzhiyun static void __exit pps_exit(void)
450*4882a593Smuzhiyun {
451*4882a593Smuzhiyun 	class_destroy(pps_class);
452*4882a593Smuzhiyun 	unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES);
453*4882a593Smuzhiyun }
454*4882a593Smuzhiyun 
pps_init(void)455*4882a593Smuzhiyun static int __init pps_init(void)
456*4882a593Smuzhiyun {
457*4882a593Smuzhiyun 	int err;
458*4882a593Smuzhiyun 
459*4882a593Smuzhiyun 	pps_class = class_create(THIS_MODULE, "pps");
460*4882a593Smuzhiyun 	if (IS_ERR(pps_class)) {
461*4882a593Smuzhiyun 		pr_err("failed to allocate class\n");
462*4882a593Smuzhiyun 		return PTR_ERR(pps_class);
463*4882a593Smuzhiyun 	}
464*4882a593Smuzhiyun 	pps_class->dev_groups = pps_groups;
465*4882a593Smuzhiyun 
466*4882a593Smuzhiyun 	err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");
467*4882a593Smuzhiyun 	if (err < 0) {
468*4882a593Smuzhiyun 		pr_err("failed to allocate char device region\n");
469*4882a593Smuzhiyun 		goto remove_class;
470*4882a593Smuzhiyun 	}
471*4882a593Smuzhiyun 
472*4882a593Smuzhiyun 	pr_info("LinuxPPS API ver. %d registered\n", PPS_API_VERS);
473*4882a593Smuzhiyun 	pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti "
474*4882a593Smuzhiyun 		"<giometti@linux.it>\n", PPS_VERSION);
475*4882a593Smuzhiyun 
476*4882a593Smuzhiyun 	return 0;
477*4882a593Smuzhiyun 
478*4882a593Smuzhiyun remove_class:
479*4882a593Smuzhiyun 	class_destroy(pps_class);
480*4882a593Smuzhiyun 
481*4882a593Smuzhiyun 	return err;
482*4882a593Smuzhiyun }
483*4882a593Smuzhiyun 
484*4882a593Smuzhiyun subsys_initcall(pps_init);
485*4882a593Smuzhiyun module_exit(pps_exit);
486*4882a593Smuzhiyun 
487*4882a593Smuzhiyun MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
488*4882a593Smuzhiyun MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION);
489*4882a593Smuzhiyun MODULE_LICENSE("GPL");
490