diff options
Diffstat (limited to 'drivers/pps/pps.c')
-rw-r--r-- | drivers/pps/pps.c | 83 |
1 files changed, 57 insertions, 26 deletions
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 2420d5af0583..7173e3ad475d 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -247,12 +247,15 @@ static int pps_cdev_open(struct inode *inode, struct file *file) struct pps_device *pps = container_of(inode->i_cdev, struct pps_device, cdev); 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 = container_of(inode->i_cdev, + struct pps_device, cdev); + kobject_put(&pps->dev->kobj); return 0; } @@ -274,8 +277,10 @@ static void pps_device_destruct(struct device *dev) { struct pps_device *pps = dev_get_drvdata(dev); - /* release id here to protect others from using it while it's - * still in use */ + 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); @@ -290,29 +295,21 @@ int pps_register_cdev(struct pps_device *pps) dev_t devt; mutex_lock(&pps_idr_lock); - /* Get new ID for the new PPS source */ - if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) { - mutex_unlock(&pps_idr_lock); - return -ENOMEM; - } - - /* Now really allocate the PPS source. - * After idr_get_new() calling the new source will be freely available - * into the kernel. + /* + * Get new ID for the new PPS source. After idr_alloc() calling + * the new source will be freely available into the kernel. */ - err = idr_get_new(&pps_idr, pps, &pps->id); - mutex_unlock(&pps_idr_lock); - - if (err < 0) - return err; - - pps->id &= MAX_IDR_MASK; - if (pps->id >= PPS_MAX_SOURCES) { - pr_err("%s: too many PPS sources in the system\n", - pps->info.name); - err = -EBUSY; - goto free_idr; + 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); @@ -332,6 +329,7 @@ int pps_register_cdev(struct pps_device *pps) goto del_cdev; } + /* 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, @@ -345,18 +343,51 @@ del_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) { + pr_debug("unregistering pps%d\n", pps->id); + pps->lookup_cookie = NULL; device_destroy(pps_class, pps->dev->devt); - cdev_del(&pps->cdev); } /* + * 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 */ |