Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/roland...
[pandora-kernel.git] / drivers / spi / spidev.c
index 799337f..e5e0cfe 100644 (file)
@@ -30,6 +30,7 @@
 #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>
@@ -167,14 +168,14 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
 
        mutex_lock(&spidev->buf_lock);
        status = spidev_sync_read(spidev, count);
-       if (status == 0) {
+       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);
 
@@ -200,8 +201,6 @@ spidev_write(struct file *filp, const char __user *buf,
        missing = copy_from_user(spidev->buffer, buf, count);
        if (missing == 0) {
                status = spidev_sync_write(spidev, count);
-               if (status == 0)
-                       status = count;
        } else
                status = -EFAULT;
        mutex_unlock(&spidev->buf_lock);
@@ -229,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;
@@ -297,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;
@@ -342,6 +338,14 @@ spidev_ioctl(struct inode *inode, struct file *filp,
        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 */
        case SPI_IOC_RD_MODE:
@@ -457,6 +461,8 @@ spidev_ioctl(struct inode *inode, struct file *filp,
                kfree(ioc);
                break;
        }
+
+       mutex_unlock(&spidev->buf_lock);
        spi_dev_put(spi);
        return retval;
 }
@@ -466,6 +472,7 @@ 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) {
@@ -491,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;
 }
 
@@ -532,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,
 };
@@ -575,7 +583,8 @@ static int spidev_probe(struct spi_device *spi)
                struct device *dev;
 
                spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
-               dev = device_create(spidev_class, &spi->dev, spidev->devt,
+               dev = device_create_drvdata(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;
@@ -585,7 +594,6 @@ static int spidev_probe(struct spi_device *spi)
        }
        if (status == 0) {
                set_bit(minor, minors);
-               spi_set_drvdata(spi, spidev);
                list_add(&spidev->device_entry, &device_list);
        }
        mutex_unlock(&device_list_lock);