emac: Fix clock control for 405EX and 405EXr chips
[safe/jmp/linux-2.6] / drivers / spi / spidev.c
index bcfef04..5d869c4 100644 (file)
 #include <linux/ioctl.h>
 #include <linux/fs.h>
 #include <linux/device.h>
+#include <linux/err.h>
 #include <linux/list.h>
 #include <linux/errno.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
+#include <linux/smp_lock.h>
 
 #include <linux/spi/spi.h>
 #include <linux/spi/spidev.h>
 static unsigned long   minors[N_SPI_MINORS / BITS_PER_LONG];
 
 
-/* Bit masks for spi_device.mode management */
-#define SPI_MODE_MASK                  (SPI_CPHA | SPI_CPOL)
-
+/* Bit masks for spi_device.mode management.  Note that incorrect
+ * settings for CS_HIGH and 3WIRE can cause *lots* of trouble for other
+ * devices on a shared bus:  CS_HIGH, because this device will be
+ * active when it shouldn't be;  3WIRE, because when active it won't
+ * behave as it should.
+ *
+ * REVISIT should changing those two modes be privileged?
+ */
+#define SPI_MODE_MASK          (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \
+                               | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP)
 
 struct spidev_data {
-       struct device           dev;
+       dev_t                   devt;
+       spinlock_t              spi_lock;
        struct spi_device       *spi;
        struct list_head        device_entry;
 
+       /* buffer is NULL unless this device is open (users > 0) */
        struct mutex            buf_lock;
        unsigned                users;
        u8                      *buffer;
@@ -78,12 +89,75 @@ MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");
 
 /*-------------------------------------------------------------------------*/
 
+/*
+ * We can't use the standard synchronous wrappers for file I/O; we
+ * need to protect against async removal of the underlying spi_device.
+ */
+static void spidev_complete(void *arg)
+{
+       complete(arg);
+}
+
+static ssize_t
+spidev_sync(struct spidev_data *spidev, struct spi_message *message)
+{
+       DECLARE_COMPLETION_ONSTACK(done);
+       int status;
+
+       message->complete = spidev_complete;
+       message->context = &done;
+
+       spin_lock_irq(&spidev->spi_lock);
+       if (spidev->spi == NULL)
+               status = -ESHUTDOWN;
+       else
+               status = spi_async(spidev->spi, message);
+       spin_unlock_irq(&spidev->spi_lock);
+
+       if (status == 0) {
+               wait_for_completion(&done);
+               status = message->status;
+               if (status == 0)
+                       status = message->actual_length;
+       }
+       return status;
+}
+
+static inline ssize_t
+spidev_sync_write(struct spidev_data *spidev, size_t len)
+{
+       struct spi_transfer     t = {
+                       .tx_buf         = spidev->buffer,
+                       .len            = len,
+               };
+       struct spi_message      m;
+
+       spi_message_init(&m);
+       spi_message_add_tail(&t, &m);
+       return spidev_sync(spidev, &m);
+}
+
+static inline ssize_t
+spidev_sync_read(struct spidev_data *spidev, size_t len)
+{
+       struct spi_transfer     t = {
+                       .rx_buf         = spidev->buffer,
+                       .len            = len,
+               };
+       struct spi_message      m;
+
+       spi_message_init(&m);
+       spi_message_add_tail(&t, &m);
+       return spidev_sync(spidev, &m);
+}
+
+/*-------------------------------------------------------------------------*/
+
 /* Read-only message with current device setup */
 static ssize_t
 spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
 {
        struct spidev_data      *spidev;
-       struct spi_device       *spi;
        ssize_t                 status = 0;
 
        /* chipselect only toggles at start or end of operation */
@@ -91,18 +165,17 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
                return -EMSGSIZE;
 
        spidev = filp->private_data;
-       spi = spidev->spi;
 
        mutex_lock(&spidev->buf_lock);
-       status = spi_read(spi, spidev->buffer, count);
-       if (status == 0) {
+       status = spidev_sync_read(spidev, count);
+       if (status > 0) {
                unsigned long   missing;
 
-               missing = copy_to_user(buf, spidev->buffer, count);
-               if (count && missing == count)
+               missing = copy_to_user(buf, spidev->buffer, status);
+               if (missing == status)
                        status = -EFAULT;
                else
-                       status = count - missing;
+                       status = status - missing;
        }
        mutex_unlock(&spidev->buf_lock);
 
@@ -115,7 +188,6 @@ spidev_write(struct file *filp, const char __user *buf,
                size_t count, loff_t *f_pos)
 {
        struct spidev_data      *spidev;
-       struct spi_device       *spi;
        ssize_t                 status = 0;
        unsigned long           missing;
 
@@ -124,14 +196,11 @@ spidev_write(struct file *filp, const char __user *buf,
                return -EMSGSIZE;
 
        spidev = filp->private_data;
-       spi = spidev->spi;
 
        mutex_lock(&spidev->buf_lock);
        missing = copy_from_user(spidev->buffer, buf, count);
        if (missing == 0) {
-               status = spi_write(spi, spidev->buffer, count);
-               if (status == 0)
-                       status = count;
+               status = spidev_sync_write(spidev, count);
        } else
                status = -EFAULT;
        mutex_unlock(&spidev->buf_lock);
@@ -146,7 +215,6 @@ static int spidev_message(struct spidev_data *spidev,
        struct spi_transfer     *k_xfers;
        struct spi_transfer     *k_tmp;
        struct spi_ioc_transfer *u_tmp;
-       struct spi_device       *spi = spidev->spi;
        unsigned                n, total;
        u8                      *buf;
        int                     status = -EFAULT;
@@ -160,7 +228,6 @@ static int spidev_message(struct spidev_data *spidev,
         * We walk the array of user-provided transfers, using each one
         * to initialize a kernel version of the same transfer.
         */
-       mutex_lock(&spidev->buf_lock);
        buf = spidev->buffer;
        total = 0;
        for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
@@ -176,12 +243,15 @@ static int spidev_message(struct spidev_data *spidev,
 
                if (u_tmp->rx_buf) {
                        k_tmp->rx_buf = buf;
-                       if (!access_ok(VERIFY_WRITE, u_tmp->rx_buf, u_tmp->len))
+                       if (!access_ok(VERIFY_WRITE, (u8 __user *)
+                                               (uintptr_t) u_tmp->rx_buf,
+                                               u_tmp->len))
                                goto done;
                }
                if (u_tmp->tx_buf) {
                        k_tmp->tx_buf = buf;
-                       if (copy_from_user(buf, (const u8 __user *)u_tmp->tx_buf,
+                       if (copy_from_user(buf, (const u8 __user *)
+                                               (uintptr_t) u_tmp->tx_buf,
                                        u_tmp->len))
                                goto done;
                }
@@ -205,7 +275,7 @@ static int spidev_message(struct spidev_data *spidev,
                spi_message_add_tail(k_tmp, &msg);
        }
 
-       status = spi_sync(spi, &msg);
+       status = spidev_sync(spidev, &msg);
        if (status < 0)
                goto done;
 
@@ -213,7 +283,8 @@ static int spidev_message(struct spidev_data *spidev,
        buf = spidev->buffer;
        for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
                if (u_tmp->rx_buf) {
-                       if (__copy_to_user((u8 __user *)u_tmp->rx_buf, buf,
+                       if (__copy_to_user((u8 __user *)
+                                       (uintptr_t) u_tmp->rx_buf, buf,
                                        u_tmp->len)) {
                                status = -EFAULT;
                                goto done;
@@ -224,14 +295,12 @@ static int spidev_message(struct spidev_data *spidev,
        status = total;
 
 done:
-       mutex_unlock(&spidev->buf_lock);
        kfree(k_xfers);
        return status;
 }
 
-static int
-spidev_ioctl(struct inode *inode, struct file *filp,
-               unsigned int cmd, unsigned long arg)
+static long
+spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
        int                     err = 0;
        int                     retval = 0;
@@ -258,8 +327,24 @@ spidev_ioctl(struct inode *inode, struct file *filp,
        if (err)
                return -EFAULT;
 
+       /* guard against device removal before, or while,
+        * we issue this ioctl.
+        */
        spidev = filp->private_data;
-       spi = spidev->spi;
+       spin_lock_irq(&spidev->spi_lock);
+       spi = spi_dev_get(spidev->spi);
+       spin_unlock_irq(&spidev->spi_lock);
+
+       if (spi == NULL)
+               return -ESHUTDOWN;
+
+       /* use the buffer lock here for triple duty:
+        *  - prevent I/O (from us) so calling spi_setup() is safe;
+        *  - prevent concurrent SPI_IOC_WR_* from morphing
+        *    data fields while SPI_IOC_RD_* reads them;
+        *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
+        */
+       mutex_lock(&spidev->buf_lock);
 
        switch (cmd) {
        /* read requests */
@@ -345,8 +430,10 @@ spidev_ioctl(struct inode *inode, struct file *filp,
        default:
                /* segmented and/or full-duplex I/O request */
                if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
-                               || _IOC_DIR(cmd) != _IOC_WRITE)
-                       return -ENOTTY;
+                               || _IOC_DIR(cmd) != _IOC_WRITE) {
+                       retval = -ENOTTY;
+                       break;
+               }
 
                tmp = _IOC_SIZE(cmd);
                if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {
@@ -364,6 +451,7 @@ spidev_ioctl(struct inode *inode, struct file *filp,
                        break;
                }
                if (__copy_from_user(ioc, (void __user *)arg, tmp)) {
+                       kfree(ioc);
                        retval = -EFAULT;
                        break;
                }
@@ -373,6 +461,9 @@ spidev_ioctl(struct inode *inode, struct file *filp,
                kfree(ioc);
                break;
        }
+
+       mutex_unlock(&spidev->buf_lock);
+       spi_dev_put(spi);
        return retval;
 }
 
@@ -381,10 +472,11 @@ static int spidev_open(struct inode *inode, struct file *filp)
        struct spidev_data      *spidev;
        int                     status = -ENXIO;
 
+       lock_kernel();
        mutex_lock(&device_list_lock);
 
        list_for_each_entry(spidev, &device_list, device_entry) {
-               if (spidev->dev.devt == inode->i_rdev) {
+               if (spidev->devt == inode->i_rdev) {
                        status = 0;
                        break;
                }
@@ -406,6 +498,7 @@ static int spidev_open(struct inode *inode, struct file *filp)
                pr_debug("spidev: nothing for minor %d\n", iminor(inode));
 
        mutex_unlock(&device_list_lock);
+       unlock_kernel();
        return status;
 }
 
@@ -417,10 +510,22 @@ static int spidev_release(struct inode *inode, struct file *filp)
        mutex_lock(&device_list_lock);
        spidev = filp->private_data;
        filp->private_data = NULL;
+
+       /* last close? */
        spidev->users--;
        if (!spidev->users) {
+               int             dofree;
+
                kfree(spidev->buffer);
                spidev->buffer = NULL;
+
+               /* ... after we unbound from the underlying device? */
+               spin_lock_irq(&spidev->spi_lock);
+               dofree = (spidev->spi == NULL);
+               spin_unlock_irq(&spidev->spi_lock);
+
+               if (dofree)
+                       kfree(spidev);
        }
        mutex_unlock(&device_list_lock);
 
@@ -435,7 +540,7 @@ static struct file_operations spidev_fops = {
         */
        .write =        spidev_write,
        .read =         spidev_read,
-       .ioctl =        spidev_ioctl,
+       .unlocked_ioctl = spidev_ioctl,
        .open =         spidev_open,
        .release =      spidev_release,
 };
@@ -447,19 +552,7 @@ static struct file_operations spidev_fops = {
  * It also simplifies memory management.
  */
 
-static void spidev_classdev_release(struct device *dev)
-{
-       struct spidev_data      *spidev;
-
-       spidev = container_of(dev, struct spidev_data, dev);
-       kfree(spidev);
-}
-
-static struct class spidev_class = {
-       .name           = "spidev",
-       .owner          = THIS_MODULE,
-       .dev_release    = spidev_classdev_release,
-};
+static struct class *spidev_class;
 
 /*-------------------------------------------------------------------------*/
 
@@ -476,6 +569,7 @@ static int spidev_probe(struct spi_device *spi)
 
        /* Initialize the driver data */
        spidev->spi = spi;
+       spin_lock_init(&spidev->spi_lock);
        mutex_init(&spidev->buf_lock);
 
        INIT_LIST_HEAD(&spidev->device_entry);
@@ -486,25 +580,26 @@ static int spidev_probe(struct spi_device *spi)
        mutex_lock(&device_list_lock);
        minor = find_first_zero_bit(minors, N_SPI_MINORS);
        if (minor < N_SPI_MINORS) {
-               spidev->dev.parent = &spi->dev;
-               spidev->dev.class = &spidev_class;
-               spidev->dev.devt = MKDEV(SPIDEV_MAJOR, minor);
-               snprintf(spidev->dev.bus_id, sizeof spidev->dev.bus_id,
-                               "spidev%d.%d",
-                               spi->master->bus_num, spi->chip_select);
-               status = device_register(&spidev->dev);
+               struct device *dev;
+
+               spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
+               dev = device_create(spidev_class, &spi->dev, spidev->devt,
+                                   spidev, "spidev%d.%d",
+                                   spi->master->bus_num, spi->chip_select);
+               status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
        } else {
                dev_dbg(&spi->dev, "no minor number available!\n");
                status = -ENODEV;
        }
        if (status == 0) {
                set_bit(minor, minors);
-               dev_set_drvdata(&spi->dev, spidev);
                list_add(&spidev->device_entry, &device_list);
        }
        mutex_unlock(&device_list_lock);
 
-       if (status != 0)
+       if (status == 0)
+               spi_set_drvdata(spi, spidev);
+       else
                kfree(spidev);
 
        return status;
@@ -512,15 +607,21 @@ static int spidev_probe(struct spi_device *spi)
 
 static int spidev_remove(struct spi_device *spi)
 {
-       struct spidev_data      *spidev = dev_get_drvdata(&spi->dev);
+       struct spidev_data      *spidev = spi_get_drvdata(spi);
 
-       mutex_lock(&device_list_lock);
+       /* make sure ops on existing fds can abort cleanly */
+       spin_lock_irq(&spidev->spi_lock);
+       spidev->spi = NULL;
+       spi_set_drvdata(spi, NULL);
+       spin_unlock_irq(&spidev->spi_lock);
 
+       /* prevent new opens */
+       mutex_lock(&device_list_lock);
        list_del(&spidev->device_entry);
-       dev_set_drvdata(&spi->dev, NULL);
-       clear_bit(MINOR(spidev->dev.devt), minors);
-       device_unregister(&spidev->dev);
-
+       device_destroy(spidev_class, spidev->devt);
+       clear_bit(MINOR(spidev->devt), minors);
+       if (spidev->users == 0)
+               kfree(spidev);
        mutex_unlock(&device_list_lock);
 
        return 0;
@@ -556,15 +657,15 @@ static int __init spidev_init(void)
        if (status < 0)
                return status;
 
-       status = class_register(&spidev_class);
-       if (status < 0) {
+       spidev_class = class_create(THIS_MODULE, "spidev");
+       if (IS_ERR(spidev_class)) {
                unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
-               return status;
+               return PTR_ERR(spidev_class);
        }
 
        status = spi_register_driver(&spidev_spi);
        if (status < 0) {
-               class_unregister(&spidev_class);
+               class_destroy(spidev_class);
                unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
        }
        return status;
@@ -574,7 +675,7 @@ module_init(spidev_init);
 static void __exit spidev_exit(void)
 {
        spi_unregister_driver(&spidev_spi);
-       class_unregister(&spidev_class);
+       class_destroy(spidev_class);
        unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
 }
 module_exit(spidev_exit);