[media] vb2: add thread support
[firefly-linux-kernel-4.4.55.git] / drivers / media / v4l2-core / videobuf2-core.c
index 2075bac748c0a02917f7fcf5bc8d56c4bc89ce71..3f0cdb150dfd85a5b89a3b97f64f5e2dc63d4886 100644 (file)
@@ -6,6 +6,9 @@
  * Author: Pawel Osciak <pawel@osciak.com>
  *        Marek Szyprowski <m.szyprowski@samsung.com>
  *
+ * The vb2_thread implementation was based on code from videobuf-dvb.c:
+ *     (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs]
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation.
@@ -18,6 +21,8 @@
 #include <linux/poll.h>
 #include <linux/slab.h>
 #include <linux/sched.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
 
 #include <media/v4l2-dev.h>
 #include <media/v4l2-fh.h>
@@ -3005,6 +3010,147 @@ size_t vb2_write(struct vb2_queue *q, const char __user *data, size_t count,
 }
 EXPORT_SYMBOL_GPL(vb2_write);
 
+struct vb2_threadio_data {
+       struct task_struct *thread;
+       vb2_thread_fnc fnc;
+       void *priv;
+       bool stop;
+};
+
+static int vb2_thread(void *data)
+{
+       struct vb2_queue *q = data;
+       struct vb2_threadio_data *threadio = q->threadio;
+       struct vb2_fileio_data *fileio = q->fileio;
+       bool set_timestamp = false;
+       int prequeue = 0;
+       int index = 0;
+       int ret = 0;
+
+       if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+               prequeue = q->num_buffers;
+               set_timestamp =
+                       (q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
+                       V4L2_BUF_FLAG_TIMESTAMP_COPY;
+       }
+
+       set_freezable();
+
+       for (;;) {
+               struct vb2_buffer *vb;
+
+               /*
+                * Call vb2_dqbuf to get buffer back.
+                */
+               memset(&fileio->b, 0, sizeof(fileio->b));
+               fileio->b.type = q->type;
+               fileio->b.memory = q->memory;
+               if (prequeue) {
+                       fileio->b.index = index++;
+                       prequeue--;
+               } else {
+                       call_void_qop(q, wait_finish, q);
+                       ret = vb2_internal_dqbuf(q, &fileio->b, 0);
+                       call_void_qop(q, wait_prepare, q);
+                       dprintk(5, "file io: vb2_dqbuf result: %d\n", ret);
+               }
+               if (threadio->stop)
+                       break;
+               if (ret)
+                       break;
+               try_to_freeze();
+
+               vb = q->bufs[fileio->b.index];
+               if (!(fileio->b.flags & V4L2_BUF_FLAG_ERROR))
+                       ret = threadio->fnc(vb, threadio->priv);
+               if (ret)
+                       break;
+               call_void_qop(q, wait_finish, q);
+               if (set_timestamp)
+                       v4l2_get_timestamp(&fileio->b.timestamp);
+               ret = vb2_internal_qbuf(q, &fileio->b);
+               call_void_qop(q, wait_prepare, q);
+               if (ret)
+                       break;
+       }
+
+       /* Hmm, linux becomes *very* unhappy without this ... */
+       while (!kthread_should_stop()) {
+               set_current_state(TASK_INTERRUPTIBLE);
+               schedule();
+       }
+       return 0;
+}
+
+/*
+ * This function should not be used for anything else but the videobuf2-dvb
+ * support. If you think you have another good use-case for this, then please
+ * contact the linux-media mailinglist first.
+ */
+int vb2_thread_start(struct vb2_queue *q, vb2_thread_fnc fnc, void *priv,
+                    const char *thread_name)
+{
+       struct vb2_threadio_data *threadio;
+       int ret = 0;
+
+       if (q->threadio)
+               return -EBUSY;
+       if (vb2_is_busy(q))
+               return -EBUSY;
+       if (WARN_ON(q->fileio))
+               return -EBUSY;
+
+       threadio = kzalloc(sizeof(*threadio), GFP_KERNEL);
+       if (threadio == NULL)
+               return -ENOMEM;
+       threadio->fnc = fnc;
+       threadio->priv = priv;
+
+       ret = __vb2_init_fileio(q, !V4L2_TYPE_IS_OUTPUT(q->type));
+       dprintk(3, "file io: vb2_init_fileio result: %d\n", ret);
+       if (ret)
+               goto nomem;
+       q->threadio = threadio;
+       threadio->thread = kthread_run(vb2_thread, q, "vb2-%s", thread_name);
+       if (IS_ERR(threadio->thread)) {
+               ret = PTR_ERR(threadio->thread);
+               threadio->thread = NULL;
+               goto nothread;
+       }
+       return 0;
+
+nothread:
+       __vb2_cleanup_fileio(q);
+nomem:
+       kfree(threadio);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(vb2_thread_start);
+
+int vb2_thread_stop(struct vb2_queue *q)
+{
+       struct vb2_threadio_data *threadio = q->threadio;
+       struct vb2_fileio_data *fileio = q->fileio;
+       int err;
+
+       if (threadio == NULL)
+               return 0;
+       call_void_qop(q, wait_finish, q);
+       threadio->stop = true;
+       vb2_internal_streamoff(q, q->type);
+       call_void_qop(q, wait_prepare, q);
+       q->fileio = NULL;
+       fileio->req.count = 0;
+       vb2_reqbufs(q, &fileio->req);
+       kfree(fileio);
+       err = kthread_stop(threadio->thread);
+       threadio->thread = NULL;
+       kfree(threadio);
+       q->fileio = NULL;
+       q->threadio = NULL;
+       return err;
+}
+EXPORT_SYMBOL_GPL(vb2_thread_stop);
 
 /*
  * The following functions are not part of the vb2 core API, but are helper