]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/pps/pps.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / pps / pps.c
index ca5183bdad85f1384845bc8a525652edaadd534d..2baadd21b7a664dea71cce5a237d66b0b8e36a25 100644 (file)
@@ -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>
 #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,12 +246,6 @@ 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;
 
        return 0;
@@ -211,11 +253,6 @@ static int pps_cdev_open(struct inode *inode, struct file *file)
 
 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);
-
        return 0;
 }
 
@@ -233,25 +270,67 @@ 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);
+
+       /* release id here to protect others from using it while it's
+        * still in use */
+       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 */
+       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.
+        */
+       err = idr_get_new(&pps_idr, pps, &pps->id);
+       mutex_unlock(&pps_idr_lock);
+
+       if (err < 0)
+               return err;
+
+       pps->id &= MAX_ID_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;
+       }
+
+       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))
                goto del_cdev;
-       dev_set_drvdata(pps->dev, pps);
+
+       pps->dev->release = pps_device_destruct;
 
        pr_debug("source %s got cdev (%d:%d)\n", pps->info.name,
                        MAJOR(pps_devt), pps->id);
@@ -261,12 +340,17 @@ 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);
+       mutex_unlock(&pps_idr_lock);
+
        return err;
 }
 
 void pps_unregister_cdev(struct pps_device *pps)
 {
-       device_destroy(pps_class, pps->devno);
+       device_destroy(pps_class, pps->dev->devt);
        cdev_del(&pps->cdev);
 }
 
@@ -286,14 +370,14 @@ static int __init pps_init(void)
 
        pps_class = class_create(THIS_MODULE, "pps");
        if (!pps_class) {
-               printk(KERN_ERR "pps: failed to allocate class\n");
+               pr_err("failed to allocate class\n");
                return -ENOMEM;
        }
        pps_class->dev_attrs = pps_attrs;
 
        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;
        }