Merge tag 'staging-5.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[linux-2.6-microblaze.git] / drivers / staging / axis-fifo / axis-fifo.c
index 39e6c59..5801067 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <linux/kernel.h>
 #include <linux/wait.h>
-#include <linux/spinlock_types.h>
+#include <linux/mutex.h>
 #include <linux/device.h>
 #include <linux/cdev.h>
 #include <linux/init.h>
@@ -133,9 +133,9 @@ struct axis_fifo {
        int has_tx_fifo; /* whether the IP has the tx fifo enabled */
 
        wait_queue_head_t read_queue; /* wait queue for asynchronos read */
-       spinlock_t read_queue_lock; /* lock for reading waitqueue */
+       struct mutex read_lock; /* lock for reading */
        wait_queue_head_t write_queue; /* wait queue for asynchronos write */
-       spinlock_t write_queue_lock; /* lock for writing waitqueue */
+       struct mutex write_lock; /* lock for writing */
        unsigned int write_flags; /* write file flags */
        unsigned int read_flags; /* read file flags */
 
@@ -336,7 +336,21 @@ static void reset_ip_core(struct axis_fifo *fifo)
        iowrite32(XLLF_INT_ALL_MASK, fifo->base_addr + XLLF_ISR_OFFSET);
 }
 
-/* reads a single packet from the fifo as dictated by the tlast signal */
+/**
+ * axis_fifo_write() - Read a packet from AXIS-FIFO character device.
+ * @f Open file.
+ * @buf User space buffer to read to.
+ * @len User space buffer length.
+ * @off Buffer offset.
+ *
+ * As defined by the device's documentation, we need to check the device's
+ * occupancy before reading the length register and then the data. All these
+ * operations must be executed atomically, in order and one after the other
+ * without missing any.
+ *
+ * Returns the number of bytes read from the device or negative error code
+ *     on failure.
+ */
 static ssize_t axis_fifo_read(struct file *f, char __user *buf,
                              size_t len, loff_t *off)
 {
@@ -350,36 +364,37 @@ static ssize_t axis_fifo_read(struct file *f, char __user *buf,
        u32 tmp_buf[READ_BUF_SIZE];
 
        if (fifo->read_flags & O_NONBLOCK) {
-               /* opened in non-blocking mode
-                * return if there are no packets available
+               /*
+                * Device opened in non-blocking mode. Try to lock it and then
+                * check if any packet is available.
                 */
-               if (!ioread32(fifo->base_addr + XLLF_RDFO_OFFSET))
+               if (!mutex_trylock(&fifo->read_lock))
                        return -EAGAIN;
+
+               if (!ioread32(fifo->base_addr + XLLF_RDFO_OFFSET)) {
+                       ret = -EAGAIN;
+                       goto end_unlock;
+               }
        } else {
                /* opened in blocking mode
                 * wait for a packet available interrupt (or timeout)
                 * if nothing is currently available
                 */
-               spin_lock_irq(&fifo->read_queue_lock);
-               ret = wait_event_interruptible_lock_irq_timeout
-                       (fifo->read_queue,
-                        ioread32(fifo->base_addr + XLLF_RDFO_OFFSET),
-                        fifo->read_queue_lock,
-                        (read_timeout >= 0) ? msecs_to_jiffies(read_timeout) :
+               mutex_lock(&fifo->read_lock);
+               ret = wait_event_interruptible_timeout(fifo->read_queue,
+                       ioread32(fifo->base_addr + XLLF_RDFO_OFFSET),
+                       (read_timeout >= 0) ? msecs_to_jiffies(read_timeout) :
                                MAX_SCHEDULE_TIMEOUT);
-               spin_unlock_irq(&fifo->read_queue_lock);
 
-               if (ret == 0) {
-                       /* timeout occurred */
-                       dev_dbg(fifo->dt_device, "read timeout");
-                       return -EAGAIN;
-               } else if (ret == -ERESTARTSYS) {
-                       /* signal received */
-                       return -ERESTARTSYS;
-               } else if (ret < 0) {
-                       dev_err(fifo->dt_device, "wait_event_interruptible_timeout() error in read (ret=%i)\n",
-                               ret);
-                       return ret;
+               if (ret <= 0) {
+                       if (ret == 0) {
+                               ret = -EAGAIN;
+                       } else if (ret != -ERESTARTSYS) {
+                               dev_err(fifo->dt_device, "wait_event_interruptible_timeout() error in read (ret=%i)\n",
+                                       ret);
+                       }
+
+                       goto end_unlock;
                }
        }
 
@@ -387,14 +402,16 @@ static ssize_t axis_fifo_read(struct file *f, char __user *buf,
        if (!bytes_available) {
                dev_err(fifo->dt_device, "received a packet of length 0 - fifo core will be reset\n");
                reset_ip_core(fifo);
-               return -EIO;
+               ret = -EIO;
+               goto end_unlock;
        }
 
        if (bytes_available > len) {
                dev_err(fifo->dt_device, "user read buffer too small (available bytes=%zu user buffer bytes=%zu) - fifo core will be reset\n",
                        bytes_available, len);
                reset_ip_core(fifo);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto end_unlock;
        }
 
        if (bytes_available % sizeof(u32)) {
@@ -403,7 +420,8 @@ static ssize_t axis_fifo_read(struct file *f, char __user *buf,
                 */
                dev_err(fifo->dt_device, "received a packet that isn't word-aligned - fifo core will be reset\n");
                reset_ip_core(fifo);
-               return -EIO;
+               ret = -EIO;
+               goto end_unlock;
        }
 
        words_available = bytes_available / sizeof(u32);
@@ -423,16 +441,37 @@ static ssize_t axis_fifo_read(struct file *f, char __user *buf,
                if (copy_to_user(buf + copied * sizeof(u32), tmp_buf,
                                 copy * sizeof(u32))) {
                        reset_ip_core(fifo);
-                       return -EFAULT;
+                       ret = -EFAULT;
+                       goto end_unlock;
                }
 
                copied += copy;
                words_available -= copy;
        }
 
-       return bytes_available;
+       ret = bytes_available;
+
+end_unlock:
+       mutex_unlock(&fifo->read_lock);
+
+       return ret;
 }
 
+/**
+ * axis_fifo_write() - Write buffer to AXIS-FIFO character device.
+ * @f Open file.
+ * @buf User space buffer to write to the device.
+ * @len User space buffer length.
+ * @off Buffer offset.
+ *
+ * As defined by the device's documentation, we need to write to the device's
+ * data buffer then to the device's packet length register atomically. Also,
+ * we need to lock before checking if the device has available space to avoid
+ * any concurrency issue.
+ *
+ * Returns the number of bytes written to the device or negative error code
+ *     on failure.
+ */
 static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
                               size_t len, loff_t *off)
 {
@@ -465,12 +504,17 @@ static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
        }
 
        if (fifo->write_flags & O_NONBLOCK) {
-               /* opened in non-blocking mode
-                * return if there is not enough room available in the fifo
+               /*
+                * Device opened in non-blocking mode. Try to lock it and then
+                * check if there is any room to write the given buffer.
                 */
+               if (!mutex_trylock(&fifo->write_lock))
+                       return -EAGAIN;
+
                if (words_to_write > ioread32(fifo->base_addr +
                                              XLLF_TDFV_OFFSET)) {
-                       return -EAGAIN;
+                       ret = -EAGAIN;
+                       goto end_unlock;
                }
        } else {
                /* opened in blocking mode */
@@ -478,30 +522,22 @@ static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
                /* wait for an interrupt (or timeout) if there isn't
                 * currently enough room in the fifo
                 */
-               spin_lock_irq(&fifo->write_queue_lock);
-               ret = wait_event_interruptible_lock_irq_timeout
-                       (fifo->write_queue,
-                        ioread32(fifo->base_addr + XLLF_TDFV_OFFSET)
+               mutex_lock(&fifo->write_lock);
+               ret = wait_event_interruptible_timeout(fifo->write_queue,
+                       ioread32(fifo->base_addr + XLLF_TDFV_OFFSET)
                                >= words_to_write,
-                        fifo->write_queue_lock,
-                        (write_timeout >= 0) ?
-                               msecs_to_jiffies(write_timeout) :
+                       (write_timeout >= 0) ? msecs_to_jiffies(write_timeout) :
                                MAX_SCHEDULE_TIMEOUT);
-               spin_unlock_irq(&fifo->write_queue_lock);
 
-               if (ret == 0) {
-                       /* timeout occurred */
-                       dev_dbg(fifo->dt_device, "write timeout\n");
-                       return -EAGAIN;
-               } else if (ret == -ERESTARTSYS) {
-                       /* signal received */
-                       return -ERESTARTSYS;
-               } else if (ret < 0) {
-                       /* unknown error */
-                       dev_err(fifo->dt_device,
-                               "wait_event_interruptible_timeout() error in write (ret=%i)\n",
-                               ret);
-                       return ret;
+               if (ret <= 0) {
+                       if (ret == 0) {
+                               ret = -EAGAIN;
+                       } else if (ret != -ERESTARTSYS) {
+                               dev_err(fifo->dt_device, "wait_event_interruptible_timeout() error in write (ret=%i)\n",
+                                       ret);
+                       }
+
+                       goto end_unlock;
                }
        }
 
@@ -515,7 +551,8 @@ static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
                if (copy_from_user(tmp_buf, buf + copied * sizeof(u32),
                                   copy * sizeof(u32))) {
                        reset_ip_core(fifo);
-                       return -EFAULT;
+                       ret = -EFAULT;
+                       goto end_unlock;
                }
 
                for (i = 0; i < copy; i++)
@@ -526,10 +563,15 @@ static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
                words_to_write -= copy;
        }
 
+       ret = copied * sizeof(u32);
+
        /* write packet size to fifo */
-       iowrite32(copied * sizeof(u32), fifo->base_addr + XLLF_TLR_OFFSET);
+       iowrite32(ret, fifo->base_addr + XLLF_TLR_OFFSET);
+
+end_unlock:
+       mutex_unlock(&fifo->write_lock);
 
-       return (ssize_t)copied * sizeof(u32);
+       return ret;
 }
 
 static irqreturn_t axis_fifo_irq(int irq, void *dw)
@@ -789,8 +831,8 @@ static int axis_fifo_probe(struct platform_device *pdev)
        init_waitqueue_head(&fifo->read_queue);
        init_waitqueue_head(&fifo->write_queue);
 
-       spin_lock_init(&fifo->read_queue_lock);
-       spin_lock_init(&fifo->write_queue_lock);
+       mutex_init(&fifo->read_lock);
+       mutex_init(&fifo->write_lock);
 
        /* ----------------------------
         *   init device memory space