CRIS: gpio: don't call copy_to_user()/copy_from_user() while holding spinlocks
authorJesper Nilsson <jesper.nilsson@axis.com>
Mon, 2 Aug 2010 11:17:05 +0000 (13:17 +0200)
committerJesper Nilsson <jesper.nilsson@axis.com>
Wed, 4 Aug 2010 10:59:14 +0000 (12:59 +0200)
copy_to_user()/copy_from_user() must not be used with spinlocks held.
Move locks inside each case so we have better control of when the locks
are held.

Also, since we use spinlocks, we don't need to hold the BKL, so remove it.

Reported-by: Kulikov Vasiliy <segooon@gmail.com>
Signed-off-by: Jesper Nilsson <jesper.nilsson@axis.com>
arch/cris/arch-v10/drivers/gpio.c

index 080927f..a07b6d2 100644 (file)
@@ -16,7 +16,6 @@
 #include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/fs.h>
-#include <linux/smp_lock.h>
 #include <linux/string.h>
 #include <linux/poll.h>
 #include <linux/init.h>
@@ -46,7 +45,7 @@ static char gpio_name[] = "etrax gpio";
 static wait_queue_head_t *gpio_wq;
 #endif
 
-static int gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
 static ssize_t gpio_write(struct file *file, const char __user *buf,
        size_t count, loff_t *off);
 static int gpio_open(struct inode *inode, struct file *filp);
@@ -323,7 +322,6 @@ gpio_open(struct inode *inode, struct file *filp)
        if (!priv)
                return -ENOMEM;
 
-       lock_kernel();
        priv->minor = p;
 
        /* initialize the io/alarm struct */
@@ -358,7 +356,6 @@ gpio_open(struct inode *inode, struct file *filp)
        alarmlist = priv;
        spin_unlock_irqrestore(&gpio_lock, flags);
 
-       unlock_kernel();
        return 0;
 }
 
@@ -503,8 +500,7 @@ unsigned long inline setget_output(struct gpio_private *priv, unsigned long arg)
 static int
 gpio_leds_ioctl(unsigned int cmd, unsigned long arg);
 
-static int
-gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
+static long gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
        unsigned long flags;
        unsigned long val;
@@ -514,54 +510,65 @@ gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
        if (_IOC_TYPE(cmd) != ETRAXGPIO_IOCTYPE)
                return -EINVAL;
 
-       spin_lock_irqsave(&gpio_lock, flags);
-
        switch (_IOC_NR(cmd)) {
        case IO_READBITS: /* Use IO_READ_INBITS and IO_READ_OUTBITS instead */
                // read the port
+               spin_lock_irqsave(&gpio_lock, flags);
                if (USE_PORTS(priv)) {
                        ret =  *priv->port;
                } else if (priv->minor == GPIO_MINOR_G) {
                        ret =  (*R_PORT_G_DATA) & 0x7FFFFFFF;
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
+
                break;
        case IO_SETBITS:
                // set changeable bits with a 1 in arg
+               spin_lock_irqsave(&gpio_lock, flags);
+
                if (USE_PORTS(priv)) {
-                       *priv->port = *priv->shadow |= 
+                       *priv->port = *priv->shadow |=
                          ((unsigned char)arg & priv->changeable_bits);
                } else if (priv->minor == GPIO_MINOR_G) {
                        *R_PORT_G_DATA = port_g_data_shadow |= (arg & dir_g_out_bits);
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
+
                break;
        case IO_CLRBITS:
                // clear changeable bits with a 1 in arg
+               spin_lock_irqsave(&gpio_lock, flags);
                if (USE_PORTS(priv)) {
-                       *priv->port = *priv->shadow &= 
+                       *priv->port = *priv->shadow &=
                         ~((unsigned char)arg & priv->changeable_bits);
                } else if (priv->minor == GPIO_MINOR_G) {
                        *R_PORT_G_DATA = port_g_data_shadow &= ~((unsigned long)arg & dir_g_out_bits);
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_HIGHALARM:
                // set alarm when bits with 1 in arg go high
+               spin_lock_irqsave(&gpio_lock, flags);
                priv->highalarm |= arg;
                gpio_some_alarms = 1;
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_LOWALARM:
                // set alarm when bits with 1 in arg go low
+               spin_lock_irqsave(&gpio_lock, flags);
                priv->lowalarm |= arg;
                gpio_some_alarms = 1;
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_CLRALARM:
-               // clear alarm for bits with 1 in arg
+               /* clear alarm for bits with 1 in arg */
+               spin_lock_irqsave(&gpio_lock, flags);
                priv->highalarm &= ~arg;
                priv->lowalarm  &= ~arg;
                {
                        /* Must update gpio_some_alarms */
                        struct gpio_private *p = alarmlist;
                        int some_alarms;
-                       spin_lock_irq(&gpio_lock);
                        p = alarmlist;
                        some_alarms = 0;
                        while (p) {
@@ -572,11 +579,12 @@ gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
                                p = p->next;
                        }
                        gpio_some_alarms = some_alarms;
-                       spin_unlock_irq(&gpio_lock);
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_READDIR: /* Use IO_SETGET_INPUT/OUTPUT instead! */
                /* Read direction 0=input 1=output */
+               spin_lock_irqsave(&gpio_lock, flags);
                if (USE_PORTS(priv)) {
                        ret = *priv->dir_shadow;
                } else if (priv->minor == GPIO_MINOR_G) {
@@ -585,30 +593,40 @@ gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
                         */
                        ret = (dir_g_shadow | dir_g_out_bits) & 0x7FFFFFFF;
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_SETINPUT: /* Use IO_SETGET_INPUT instead! */
-               /* Set direction 0=unchanged 1=input, 
-                * return mask with 1=input 
+               /* Set direction 0=unchanged 1=input,
+                * return mask with 1=input
                 */
+               spin_lock_irqsave(&gpio_lock, flags);
                ret = setget_input(priv, arg) & 0x7FFFFFFF;
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_SETOUTPUT: /* Use IO_SETGET_OUTPUT instead! */
-               /* Set direction 0=unchanged 1=output, 
-                * return mask with 1=output 
+               /* Set direction 0=unchanged 1=output,
+                * return mask with 1=output
                 */
+               spin_lock_irqsave(&gpio_lock, flags);
                ret =  setget_output(priv, arg) & 0x7FFFFFFF;
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_SHUTDOWN:
+               spin_lock_irqsave(&gpio_lock, flags);
                SOFT_SHUTDOWN();
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_GET_PWR_BT:
+               spin_lock_irqsave(&gpio_lock, flags);
 #if defined (CONFIG_ETRAX_SOFT_SHUTDOWN)
                ret = (*R_PORT_G_DATA & ( 1 << CONFIG_ETRAX_POWERBUTTON_BIT));
 #else
                ret = 0;
 #endif
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
        case IO_CFG_WRITE_MODE:
+               spin_lock_irqsave(&gpio_lock, flags);
                priv->clk_mask = arg & 0xFF;
                priv->data_mask = (arg >> 8) & 0xFF;
                priv->write_msb = (arg >> 16) & 0x01;
@@ -624,28 +642,33 @@ gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
                        priv->data_mask = 0;
                        ret = -EPERM;
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
                break;
-       case IO_READ_INBITS: 
+       case IO_READ_INBITS:
                /* *arg is result of reading the input pins */
+               spin_lock_irqsave(&gpio_lock, flags);
                if (USE_PORTS(priv)) {
                        val = *priv->port;
                } else if (priv->minor == GPIO_MINOR_G) {
                        val = *R_PORT_G_DATA;
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
                if (copy_to_user((void __user *)arg, &val, sizeof(val)))
                        ret = -EFAULT;
                break;
        case IO_READ_OUTBITS:
                 /* *arg is result of reading the output shadow */
+               spin_lock_irqsave(&gpio_lock, flags);
                if (USE_PORTS(priv)) {
                        val = *priv->shadow;
                } else if (priv->minor == GPIO_MINOR_G) {
                        val = port_g_data_shadow;
                }
+               spin_unlock_irqrestore(&gpio_lock, flags);
                if (copy_to_user((void __user *)arg, &val, sizeof(val)))
                        ret = -EFAULT;
                break;
-       case IO_SETGET_INPUT: 
+       case IO_SETGET_INPUT:
                /* bits set in *arg is set to input,
                 * *arg updated with current input pins.
                 */
@@ -654,7 +677,9 @@ gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
                        ret = -EFAULT;
                        break;
                }
+               spin_lock_irqsave(&gpio_lock, flags);
                val = setget_input(priv, val);
+               spin_unlock_irqrestore(&gpio_lock, flags);
                if (copy_to_user((void __user *)arg, &val, sizeof(val)))
                        ret = -EFAULT;
                break;
@@ -666,33 +691,24 @@ gpio_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg)
                        ret = -EFAULT;
                        break;
                }
+               spin_lock_irqsave(&gpio_lock, flags);
                val = setget_output(priv, val);
+               spin_unlock_irqrestore(&gpio_lock, flags);
                if (copy_to_user((void __user *)arg, &val, sizeof(val)))
                        ret = -EFAULT;
                break;
        default:
+               spin_lock_irqsave(&gpio_lock, flags);
                if (priv->minor == GPIO_MINOR_LEDS)
                        ret = gpio_leds_ioctl(cmd, arg);
                else
                        ret = -EINVAL;
+               spin_unlock_irqrestore(&gpio_lock, flags);
        } /* switch */
 
-       spin_unlock_irqrestore(&gpio_lock, flags);
        return ret;
 }
 
-static int
-gpio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
-{
-       long ret;
-
-       lock_kernel();
-       ret = gpio_ioctl_unlocked(file, cmd, arg);
-       unlock_kernel();
-
-       return ret;
-}
-
 static int
 gpio_leds_ioctl(unsigned int cmd, unsigned long arg)
 {