diff options
Diffstat (limited to 'drivers/pps/pps.c')
| -rw-r--r-- | drivers/pps/pps.c | 201 | 
1 files changed, 159 insertions, 42 deletions
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index ca5183bdad8..2f07cd61566 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -19,6 +19,7 @@   *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt  #include <linux/kernel.h>  #include <linux/module.h> @@ -26,9 +27,13 @@  #include <linux/sched.h>  #include <linux/uaccess.h>  #include <linux/idr.h> +#include <linux/mutex.h>  #include <linux/cdev.h>  #include <linux/poll.h>  #include <linux/pps_kernel.h> +#include <linux/slab.h> + +#include "kc.h"  /*   * Local variables @@ -37,6 +42,9 @@  static dev_t pps_devt;  static struct class *pps_class; +static DEFINE_MUTEX(pps_idr_lock); +static DEFINE_IDR(pps_idr); +  /*   * Char device methods   */ @@ -61,15 +69,13 @@ static long pps_cdev_ioctl(struct file *file,  {  	struct pps_device *pps = file->private_data;  	struct pps_kparams params; -	struct pps_fdata fdata; -	unsigned long ticks;  	void __user *uarg = (void __user *) arg;  	int __user *iuarg = (int __user *) arg;  	int err;  	switch (cmd) {  	case PPS_GETPARAMS: -		pr_debug("PPS_GETPARAMS: source %d\n", pps->id); +		dev_dbg(pps->dev, "PPS_GETPARAMS\n");  		spin_lock_irq(&pps->lock); @@ -85,7 +91,7 @@ static long pps_cdev_ioctl(struct file *file,  		break;  	case PPS_SETPARAMS: -		pr_debug("PPS_SETPARAMS: source %d\n", pps->id); +		dev_dbg(pps->dev, "PPS_SETPARAMS\n");  		/* Check the capabilities */  		if (!capable(CAP_SYS_TIME)) @@ -95,14 +101,14 @@ static long pps_cdev_ioctl(struct file *file,  		if (err)  			return -EFAULT;  		if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { -			pr_debug("capture mode unspecified (%x)\n", +			dev_dbg(pps->dev, "capture mode unspecified (%x)\n",  								params.mode);  			return -EINVAL;  		}  		/* Check for supported capabilities */  		if ((params.mode & ~pps->info.mode) != 0) { -			pr_debug("unsupported capabilities (%x)\n", +			dev_dbg(pps->dev, "unsupported capabilities (%x)\n",  								params.mode);  			return -EINVAL;  		} @@ -115,7 +121,7 @@ static long pps_cdev_ioctl(struct file *file,  		/* Restore the read only parameters */  		if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {  			/* section 3.3 of RFC 2783 interpreted */ -			pr_debug("time format unspecified (%x)\n", +			dev_dbg(pps->dev, "time format unspecified (%x)\n",  								params.mode);  			pps->params.mode |= PPS_TSFMT_TSPEC;  		} @@ -128,7 +134,7 @@ static long pps_cdev_ioctl(struct file *file,  		break;  	case PPS_GETCAP: -		pr_debug("PPS_GETCAP: source %d\n", pps->id); +		dev_dbg(pps->dev, "PPS_GETCAP\n");  		err = put_user(pps->info.mode, iuarg);  		if (err) @@ -136,20 +142,26 @@ static long pps_cdev_ioctl(struct file *file,  		break; -	case PPS_FETCH: -		pr_debug("PPS_FETCH: source %d\n", pps->id); +	case PPS_FETCH: { +		struct pps_fdata fdata; +		unsigned int ev; + +		dev_dbg(pps->dev, "PPS_FETCH\n");  		err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata));  		if (err)  			return -EFAULT; -		pps->go = 0; +		ev = pps->last_ev;  		/* Manage the timeout */  		if (fdata.timeout.flags & PPS_TIME_INVALID) -			err = wait_event_interruptible(pps->queue, pps->go); +			err = wait_event_interruptible(pps->queue, +					ev != pps->last_ev);  		else { -			pr_debug("timeout %lld.%09d\n", +			unsigned long ticks; + +			dev_dbg(pps->dev, "timeout %lld.%09d\n",  					(long long) fdata.timeout.sec,  					fdata.timeout.nsec);  			ticks = fdata.timeout.sec * HZ; @@ -157,7 +169,9 @@ static long pps_cdev_ioctl(struct file *file,  			if (ticks != 0) {  				err = wait_event_interruptible_timeout( -						pps->queue, pps->go, ticks); +						pps->queue, +						ev != pps->last_ev, +						ticks);  				if (err == 0)  					return -ETIMEDOUT;  			} @@ -165,7 +179,7 @@ static long pps_cdev_ioctl(struct file *file,  		/* Check for pending signals */  		if (err == -ERESTARTSYS) { -			pr_debug("pending signal caught\n"); +			dev_dbg(pps->dev, "pending signal caught\n");  			return -EINTR;  		} @@ -185,10 +199,44 @@ static long pps_cdev_ioctl(struct file *file,  			return -EFAULT;  		break; +	} +	case PPS_KC_BIND: { +		struct pps_bind_args bind_args; + +		dev_dbg(pps->dev, "PPS_KC_BIND\n"); + +		/* Check the capabilities */ +		if (!capable(CAP_SYS_TIME)) +			return -EPERM; +		if (copy_from_user(&bind_args, uarg, +					sizeof(struct pps_bind_args))) +			return -EFAULT; + +		/* Check for supported capabilities */ +		if ((bind_args.edge & ~pps->info.mode) != 0) { +			dev_err(pps->dev, "unsupported capabilities (%x)\n", +					bind_args.edge); +			return -EINVAL; +		} + +		/* Validate parameters roughly */ +		if (bind_args.tsformat != PPS_TSFMT_TSPEC || +				(bind_args.edge & ~PPS_CAPTUREBOTH) != 0 || +				bind_args.consumer != PPS_KC_HARDPPS) { +			dev_err(pps->dev, "invalid kernel consumer bind" +					" parameters (%x)\n", bind_args.edge); +			return -EINVAL; +		} + +		err = pps_kc_bind(pps, &bind_args); +		if (err < 0) +			return err; + +		break; +	}  	default:  		return -ENOTTY; -		break;  	}  	return 0; @@ -198,24 +246,16 @@ static int pps_cdev_open(struct inode *inode, struct file *file)  {  	struct pps_device *pps = container_of(inode->i_cdev,  						struct pps_device, cdev); -	int found; - -	found = pps_get_source(pps->id) != 0; -	if (!found) -		return -ENODEV; -  	file->private_data = pps; - +	kobject_get(&pps->dev->kobj);  	return 0;  }  static int pps_cdev_release(struct inode *inode, struct file *file)  { -	struct pps_device *pps = file->private_data; - -	/* Free the PPS source and wake up (possible) deregistration */ -	pps_put_source(pps); - +	struct pps_device *pps = container_of(inode->i_cdev, +						struct pps_device, cdev); +	kobject_put(&pps->dev->kobj);  	return 0;  } @@ -233,25 +273,64 @@ static const struct file_operations pps_cdev_fops = {  	.release	= pps_cdev_release,  }; +static void pps_device_destruct(struct device *dev) +{ +	struct pps_device *pps = dev_get_drvdata(dev); + +	cdev_del(&pps->cdev); + +	/* Now we can release the ID for re-use */ +	pr_debug("deallocating pps%d\n", pps->id); +	mutex_lock(&pps_idr_lock); +	idr_remove(&pps_idr, pps->id); +	mutex_unlock(&pps_idr_lock); + +	kfree(dev); +	kfree(pps); +} +  int pps_register_cdev(struct pps_device *pps)  {  	int err; +	dev_t devt; + +	mutex_lock(&pps_idr_lock); +	/* +	 * Get new ID for the new PPS source.  After idr_alloc() calling +	 * the new source will be freely available into the kernel. +	 */ +	err = idr_alloc(&pps_idr, pps, 0, PPS_MAX_SOURCES, GFP_KERNEL); +	if (err < 0) { +		if (err == -ENOSPC) { +			pr_err("%s: too many PPS sources in the system\n", +			       pps->info.name); +			err = -EBUSY; +		} +		goto out_unlock; +	} +	pps->id = err; +	mutex_unlock(&pps_idr_lock); + +	devt = MKDEV(MAJOR(pps_devt), pps->id); -	pps->devno = MKDEV(MAJOR(pps_devt), pps->id);  	cdev_init(&pps->cdev, &pps_cdev_fops);  	pps->cdev.owner = pps->info.owner; -	err = cdev_add(&pps->cdev, pps->devno, 1); +	err = cdev_add(&pps->cdev, devt, 1);  	if (err) { -		printk(KERN_ERR "pps: %s: failed to add char device %d:%d\n", +		pr_err("%s: failed to add char device %d:%d\n",  				pps->info.name, MAJOR(pps_devt), pps->id); -		return err; +		goto free_idr;  	} -	pps->dev = device_create(pps_class, pps->info.dev, pps->devno, NULL, +	pps->dev = device_create(pps_class, pps->info.dev, devt, pps,  							"pps%d", pps->id); -	if (IS_ERR(pps->dev)) +	if (IS_ERR(pps->dev)) { +		err = PTR_ERR(pps->dev);  		goto del_cdev; -	dev_set_drvdata(pps->dev, pps); +	} + +	/* Override the release function with our own */ +	pps->dev->release = pps_device_destruct;  	pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,  			MAJOR(pps_devt), pps->id); @@ -261,14 +340,52 @@ int pps_register_cdev(struct pps_device *pps)  del_cdev:  	cdev_del(&pps->cdev); +free_idr: +	mutex_lock(&pps_idr_lock); +	idr_remove(&pps_idr, pps->id); +out_unlock: +	mutex_unlock(&pps_idr_lock);  	return err;  }  void pps_unregister_cdev(struct pps_device *pps)  { -	device_destroy(pps_class, pps->devno); -	cdev_del(&pps->cdev); +	pr_debug("unregistering pps%d\n", pps->id); +	pps->lookup_cookie = NULL; +	device_destroy(pps_class, pps->dev->devt); +} + +/* + * Look up a pps device by magic cookie. + * The cookie is usually a pointer to some enclosing device, but this + * code doesn't care; you should never be dereferencing it. + * + * This is a bit of a kludge that is currently used only by the PPS + * serial line discipline.  It may need to be tweaked when a second user + * is found. + * + * There is no function interface for setting the lookup_cookie field. + * It's initialized to NULL when the pps device is created, and if a + * client wants to use it, just fill it in afterward. + * + * The cookie is automatically set to NULL in pps_unregister_source() + * so that it will not be used again, even if the pps device cannot + * be removed from the idr due to pending references holding the minor + * number in use. + */ +struct pps_device *pps_lookup_dev(void const *cookie) +{ +	struct pps_device *pps; +	unsigned id; + +	rcu_read_lock(); +	idr_for_each_entry(&pps_idr, pps, id) +		if (cookie == pps->lookup_cookie) +			break; +	rcu_read_unlock(); +	return pps;  } +EXPORT_SYMBOL(pps_lookup_dev);  /*   * Module stuff @@ -285,15 +402,15 @@ static int __init pps_init(void)  	int err;  	pps_class = class_create(THIS_MODULE, "pps"); -	if (!pps_class) { -		printk(KERN_ERR "pps: failed to allocate class\n"); -		return -ENOMEM; +	if (IS_ERR(pps_class)) { +		pr_err("failed to allocate class\n"); +		return PTR_ERR(pps_class);  	} -	pps_class->dev_attrs = pps_attrs; +	pps_class->dev_groups = pps_groups;  	err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps");  	if (err < 0) { -		printk(KERN_ERR "pps: failed to allocate char device region\n"); +		pr_err("failed to allocate char device region\n");  		goto remove_class;  	}  | 
