dma-buf: add poll support, v3
[pandora-kernel.git] / drivers / dma-buf / dma-buf.c
index cd40ca2..25e8c41 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/export.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
+#include <linux/poll.h>
 #include <linux/reservation.h>
 
 static inline int is_dma_buf_file(struct file *);
@@ -52,6 +53,16 @@ static int dma_buf_release(struct inode *inode, struct file *file)
 
        BUG_ON(dmabuf->vmapping_counter);
 
+       /*
+        * Any fences that a dma-buf poll can wait on should be signaled
+        * before releasing dma-buf. This is the responsibility of each
+        * driver that uses the reservation objects.
+        *
+        * If you hit this BUG() it means someone dropped their ref to the
+        * dma-buf while still having pending operation to the buffer.
+        */
+       BUG_ON(dmabuf->cb_shared.active || dmabuf->cb_excl.active);
+
        dmabuf->ops->release(dmabuf);
 
        mutex_lock(&db_list.lock);
@@ -108,10 +119,103 @@ static loff_t dma_buf_llseek(struct file *file, loff_t offset, int whence)
        return base + offset;
 }
 
+static void dma_buf_poll_cb(struct fence *fence, struct fence_cb *cb)
+{
+       struct dma_buf_poll_cb_t *dcb = (struct dma_buf_poll_cb_t *)cb;
+       unsigned long flags;
+
+       spin_lock_irqsave(&dcb->poll->lock, flags);
+       wake_up_locked_poll(dcb->poll, dcb->active);
+       dcb->active = 0;
+       spin_unlock_irqrestore(&dcb->poll->lock, flags);
+}
+
+static unsigned int dma_buf_poll(struct file *file, poll_table *poll)
+{
+       struct dma_buf *dmabuf;
+       struct reservation_object *resv;
+       unsigned long events;
+
+       dmabuf = file->private_data;
+       if (!dmabuf || !dmabuf->resv)
+               return POLLERR;
+
+       resv = dmabuf->resv;
+
+       poll_wait(file, &dmabuf->poll, poll);
+
+       events = poll_requested_events(poll) & (POLLIN | POLLOUT);
+       if (!events)
+               return 0;
+
+       ww_mutex_lock(&resv->lock, NULL);
+
+       if (resv->fence_excl && (!(events & POLLOUT) ||
+                                resv->fence_shared_count == 0)) {
+               struct dma_buf_poll_cb_t *dcb = &dmabuf->cb_excl;
+               unsigned long pevents = POLLIN;
+
+               if (resv->fence_shared_count == 0)
+                       pevents |= POLLOUT;
+
+               spin_lock_irq(&dmabuf->poll.lock);
+               if (dcb->active) {
+                       dcb->active |= pevents;
+                       events &= ~pevents;
+               } else
+                       dcb->active = pevents;
+               spin_unlock_irq(&dmabuf->poll.lock);
+
+               if (events & pevents) {
+                       if (!fence_add_callback(resv->fence_excl,
+                                               &dcb->cb, dma_buf_poll_cb))
+                               events &= ~pevents;
+                       else
+                               /*
+                                * No callback queued, wake up any additional
+                                * waiters.
+                                */
+                               dma_buf_poll_cb(NULL, &dcb->cb);
+               }
+       }
+
+       if ((events & POLLOUT) && resv->fence_shared_count > 0) {
+               struct dma_buf_poll_cb_t *dcb = &dmabuf->cb_shared;
+               int i;
+
+               /* Only queue a new callback if no event has fired yet */
+               spin_lock_irq(&dmabuf->poll.lock);
+               if (dcb->active)
+                       events &= ~POLLOUT;
+               else
+                       dcb->active = POLLOUT;
+               spin_unlock_irq(&dmabuf->poll.lock);
+
+               if (!(events & POLLOUT))
+                       goto out;
+
+               for (i = 0; i < resv->fence_shared_count; ++i)
+                       if (!fence_add_callback(resv->fence_shared[i],
+                                               &dcb->cb, dma_buf_poll_cb)) {
+                               events &= ~POLLOUT;
+                               break;
+                       }
+
+               /* No callback queued, wake up any additional waiters. */
+               if (i == resv->fence_shared_count)
+                       dma_buf_poll_cb(NULL, &dcb->cb);
+       }
+
+out:
+       ww_mutex_unlock(&resv->lock);
+       return events;
+}
+
 static const struct file_operations dma_buf_fops = {
        .release        = dma_buf_release,
        .mmap           = dma_buf_mmap_internal,
        .llseek         = dma_buf_llseek,
+       .poll           = dma_buf_poll,
 };
 
 /*
@@ -171,6 +275,10 @@ struct dma_buf *dma_buf_export_named(void *priv, const struct dma_buf_ops *ops,
        dmabuf->ops = ops;
        dmabuf->size = size;
        dmabuf->exp_name = exp_name;
+       init_waitqueue_head(&dmabuf->poll);
+       dmabuf->cb_excl.poll = dmabuf->cb_shared.poll = &dmabuf->poll;
+       dmabuf->cb_excl.active = dmabuf->cb_shared.active = 0;
+
        if (!resv) {
                resv = (struct reservation_object *)&dmabuf[1];
                reservation_object_init(resv);