Add core support code for jobs API. This manages the life cycle of jobs
and creation of a jobs queue, as well as the interface for job states.

It also exposes the user-space jobs API.

Signed-off-by: Alexandre Courbot <acour...@chromium.org>
---
 drivers/media/v4l2-core/Makefile            |   3 +-
 drivers/media/v4l2-core/v4l2-dev.c          |   6 +
 drivers/media/v4l2-core/v4l2-jobqueue-dev.c | 173 +++++++
 drivers/media/v4l2-core/v4l2-jobqueue.c     | 764 ++++++++++++++++++++++++++++
 include/media/v4l2-dev.h                    |   4 +
 include/media/v4l2-fh.h                     |   4 +
 include/media/v4l2-job-state.h              |  75 +++
 include/media/v4l2-jobqueue-dev.h           |  24 +
 include/media/v4l2-jobqueue.h               |  54 ++
 include/uapi/linux/v4l2-jobs.h              |  40 ++
 include/uapi/linux/videodev2.h              |   2 +
 11 files changed, 1148 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/v4l2-core/v4l2-jobqueue-dev.c
 create mode 100644 drivers/media/v4l2-core/v4l2-jobqueue.c
 create mode 100644 include/media/v4l2-job-state.h
 create mode 100644 include/media/v4l2-jobqueue-dev.h
 create mode 100644 include/media/v4l2-jobqueue.h
 create mode 100644 include/uapi/linux/v4l2-jobs.h

diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 098ad5fd5231..a717bb8f1a25 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -6,7 +6,8 @@ tuner-objs      :=      tuner-core.o
 
 videodev-objs  :=      v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \
                        v4l2-event.o v4l2-ctrls.o v4l2-subdev.o v4l2-clk.o \
-                       v4l2-async.o
+                       v4l2-async.o v4l2-jobqueue.o v4l2-jobqueue-dev.o
+
 ifeq ($(CONFIG_COMPAT),y)
   videodev-objs += v4l2-compat-ioctl32.o
 endif
diff --git a/drivers/media/v4l2-core/v4l2-dev.c 
b/drivers/media/v4l2-core/v4l2-dev.c
index 5a7063886c93..fb229b671b9d 100644
--- a/drivers/media/v4l2-core/v4l2-dev.c
+++ b/drivers/media/v4l2-core/v4l2-dev.c
@@ -30,6 +30,7 @@
 #include <media/v4l2-common.h>
 #include <media/v4l2-device.h>
 #include <media/v4l2-ioctl.h>
+#include <media/v4l2-jobqueue-dev.h>
 
 #define VIDEO_NUM_DEVICES      256
 #define VIDEO_NAME              "video4linux"
@@ -1058,6 +1059,10 @@ static int __init videodev_init(void)
                return -EIO;
        }
 
+       ret = v4l2_jobqueue_device_init();
+       if (ret < 0)
+               printk(KERN_WARNING "video_dev: channel initialization 
failed\n");
+
        return 0;
 }
 
@@ -1065,6 +1070,7 @@ static void __exit videodev_exit(void)
 {
        dev_t dev = MKDEV(VIDEO_MAJOR, 0);
 
+       v4l2_jobqueue_device_exit();
        class_unregister(&video_class);
        unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
 }
diff --git a/drivers/media/v4l2-core/v4l2-jobqueue-dev.c 
b/drivers/media/v4l2-core/v4l2-jobqueue-dev.c
new file mode 100644
index 000000000000..688c4ba275a6
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-jobqueue-dev.c
@@ -0,0 +1,173 @@
+/*
+    V4L2 job queue device
+
+    Copyright (C) 2017  The Chromium project
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-jobqueue.h>
+#include <uapi/linux/v4l2-jobs.h>
+
+#define CLASS_NAME "v4l2_jobqueue"
+#define DEVICE_NAME "v4l2_jobqueue"
+
+static int major;
+static struct class *jobqueue_class;
+static struct device *jobqueue_device;
+
+static int v4l2_jobqueue_device_open(struct inode *inode, struct file *filp)
+{
+       struct v4l2_jobqueue *jq;
+
+       jq = v4l2_jobqueue_new();
+       if (IS_ERR(jq))
+               return PTR_ERR(jq);
+
+       filp->private_data = jq;
+
+       return 0;
+}
+
+static int v4l2_jobqueue_device_release(struct inode *inode, struct file *filp)
+{
+       struct v4l2_jobqueue *jq = filp->private_data;
+
+       return v4l2_jobqueue_del(jq);
+}
+
+static long v4l2_jobqueue_ioctl_init(struct file *filp, void *arg)
+{
+       struct v4l2_jobqueue *jq = filp->private_data;
+       struct v4l2_jobqueue_init *cinit = arg;
+
+       return v4l2_jobqueue_init(jq, cinit);
+}
+
+static long v4l2_jobqueue_device_ioctl_qjob(struct file *filp, void *arg)
+{
+       struct v4l2_jobqueue *jq = filp->private_data;
+
+       return v4l2_jobqueue_qjob(jq);
+}
+
+static long v4l2_jobqueue_device_ioctl_dqjob(struct file *filp, void *arg)
+{
+       struct v4l2_jobqueue *jq = filp->private_data;
+
+       return v4l2_jobqueue_dqjob(jq);
+}
+
+static long v4l2_jobqueue_device_ioctl_export_job(struct file *filp, void *arg)
+{
+       struct v4l2_jobqueue *jq = filp->private_data;
+       struct v4l2_jobqueue_job *job = arg;
+
+       return v4l2_jobqueue_export_job(jq, job);
+}
+
+static long v4l2_jobqueue_device_ioctl_import_job(struct file *filp, void *arg)
+{
+       struct v4l2_jobqueue *jq = filp->private_data;
+       struct v4l2_jobqueue_job *job = arg;
+
+       return v4l2_jobqueue_import_job(jq, job);
+}
+
+static long v4l2_jobqueue_device_do_ioctl(struct file *filp, unsigned int cmd,
+                                         void *arg)
+{
+       switch (cmd) {
+               case VIDIOC_JOBQUEUE_INIT:
+                       return v4l2_jobqueue_ioctl_init(filp, arg);
+
+               case VIDIOC_JOBQUEUE_QJOB:
+                       return v4l2_jobqueue_device_ioctl_qjob(filp, arg);
+
+               case VIDIOC_JOBQUEUE_DQJOB:
+                       return v4l2_jobqueue_device_ioctl_dqjob(filp, arg);
+
+               case VIDIOC_JOBQUEUE_EXPORT_JOB:
+                       return v4l2_jobqueue_device_ioctl_export_job(filp, arg);
+
+               case VIDIOC_JOBQUEUE_IMPORT_JOB:
+                       return v4l2_jobqueue_device_ioctl_import_job(filp, arg);
+
+               default:
+                       pr_err("Invalid ioctl!\n");
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static long v4l2_jobqueue_device_ioctl(struct file *filp, unsigned int cmd,
+                                      unsigned long arg)
+{
+       return video_usercopy(filp, cmd, arg, v4l2_jobqueue_device_do_ioctl);
+}
+
+static const struct file_operations v4l2_jobqueue_devnode_fops = {
+       .owner = THIS_MODULE,
+       .open = v4l2_jobqueue_device_open,
+       .unlocked_ioctl = v4l2_jobqueue_device_ioctl,
+#ifdef CONFIG_COMPAT
+       /* TODO */
+       /* .compat_ioctl = jobqueue_compat_ioctl, */
+#endif
+       .release = v4l2_jobqueue_device_release,
+};
+
+int __init v4l2_jobqueue_device_init(void)
+{
+       /* Set to error value so v4l2_jobqueue_device_exit does nothing if we
+        * don't initialize properly */
+       jobqueue_device = ERR_PTR(-EINVAL);
+
+       major = register_chrdev(0, DEVICE_NAME, &v4l2_jobqueue_devnode_fops);
+       if (major < 0) {
+               pr_err("unable to allocate major\n");
+               return major;
+       }
+
+       jobqueue_class = class_create(THIS_MODULE, CLASS_NAME);
+       if (IS_ERR(jobqueue_class)) {
+               pr_err("cannot create class\n");
+               unregister_chrdev(major, DEVICE_NAME);
+               return PTR_ERR(jobqueue_class);
+       }
+
+       jobqueue_device = device_create(jobqueue_class, NULL, MKDEV(major, 0),
+                                       NULL, DEVICE_NAME);
+       if (IS_ERR(jobqueue_device)) {
+               pr_err("cannot create device\n");
+               class_destroy(jobqueue_class);
+               unregister_chrdev(major, DEVICE_NAME);
+               return PTR_ERR(jobqueue_device);
+       }
+
+       return 0;
+}
+
+void __exit v4l2_jobqueue_device_exit(void)
+{
+       if (IS_ERR(jobqueue_device))
+               return;
+
+       device_destroy(jobqueue_class, MKDEV(major, 0));
+       class_destroy(jobqueue_class);
+       unregister_chrdev(major, DEVICE_NAME);
+}
diff --git a/drivers/media/v4l2-core/v4l2-jobqueue.c 
b/drivers/media/v4l2-core/v4l2-jobqueue.c
new file mode 100644
index 000000000000..36d2dd48b086
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-jobqueue.c
@@ -0,0 +1,764 @@
+/*
+    V4L2 job queue implementation
+
+    Copyright (C) 2017  The Chromium project
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+ */
+
+#include <linux/compat.h>
+#include <linux/export.h>
+#include <linux/string.h>
+#include <linux/file.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+#include <linux/anon_inodes.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-jobqueue.h>
+#include <media/v4l2-job-state.h>
+#include <uapi/linux/v4l2-jobs.h>
+
+/* Limited by the size of atomic_t to track devices that completed a job */
+#define V4L2_JOBQUEUE_MAX_DEVICES sizeof(atomic_t)
+
+/*
+ * State of all managed devices for a given job
+ */
+struct v4l2_job {
+       struct kref refcount;
+       struct v4l2_jobqueue *jq;
+       /* node in v4l2_jobqueue's queued_jobs or completed_jobs */
+       struct list_head node;
+       /* global list of existing jobs for this queue */
+       struct list_head jobs_list;
+       /* mask of devices that completed this job */
+       atomic_t completed;
+       /* fd exported to user-space */
+       int fd;
+       enum v4l2_job_status status;
+
+       /* per-device states */
+       struct v4l2_job_state *state[0];
+};
+
+/*
+ * A job queue manages the job flow for a given set of devices, applies their
+ * state, and activates them in lockstep.
+ *
+ * A job goes through the following stages through its life:
+ *
+ * * current_job: the job has been created and is waiting to be queued. S_CTRL
+ *   will apply to it. Once queued, it is pushed into
+ * * queued_jobs: a queue of jobs to be processed in sequential order. The head
+ *   of this list becomes the
+ * * active_job: the job currently being processed by the hardware. Once
+ *   completed, the next job in queued_job becomes active, and the previous
+ *   active job goes into
+ * * completed_jobs: a list of completed jobs waiting to be dequeued by
+ *   user-space. As user-space called the DQJOB ioctl, the head becomes the
+ * * dequeued_job: the job on which G_CTRL will be performed on. A job stays
+ *   in this state until another one is dequeued, at which point it is deleted.
+ */
+struct v4l2_jobqueue {
+       /* List of all jobs created for this queue, regardless of state */
+       struct list_head jobs_list;
+       /*
+        * Job that user-space is currently preparing, to be added to
+        * queued_jobs upon QJOB ioctl.
+        */
+       struct v4l2_job *current_job;
+
+       /* List of jobs that are ready to be processed */
+       struct list_head queued_jobs;
+
+       /* Job that is currently processed by the devices */
+       struct v4l2_job *active_job;
+
+       /* List of completed jobs, ready to be dequeued */
+       struct list_head completed_jobs;
+
+       /* Job that has last been dequeued and can be queried by user-space */
+       struct v4l2_job *dequeued_job;
+
+       /* Projects the *_job[s] lists/pointers above */
+       struct mutex lock;
+       struct work_struct job_complete_work;
+
+       wait_queue_head_t done_wq;
+
+       unsigned int nb_devs;
+       struct {
+               struct file *f;
+               struct v4l2_job_state_handler *state_handler;
+       } *devs;
+};
+
+static bool v4l2_jobqueue_is_locked(struct v4l2_jobqueue *jq)
+{
+       return mutex_is_locked(&jq->lock);
+}
+
+static void v4l2_jobqueue_free_job(struct v4l2_job *job)
+{
+       struct v4l2_jobqueue *jq = job->jq;
+       int i;
+
+       for (i = 0; i < jq->nb_devs; i++) {
+               struct v4l2_job_state_handler *hdl = jq->devs[i].state_handler;
+               if (job->state[i])
+                       hdl->ops->job_free(hdl, job->state[i]);
+       }
+       kfree(job);
+}
+
+/*
+ * Must be called with jobqueue lock held
+ */
+static void v4l2_jobqueue_delete_job(struct kref *ref)
+{
+       struct v4l2_job *job = container_of(ref, struct v4l2_job, refcount);
+
+       list_del(&job->jobs_list);
+
+          v4l2_jobqueue_free_job(job);
+}
+
+/*
+ * Must be called with the jobqueue lock acquired
+ */
+static void job_get(struct v4l2_job *job)
+{
+       kref_get(&job->refcount);
+}
+
+/*
+ * Must be called with the jobqueue lock acquired
+ */
+static int job_put(struct v4l2_job *job)
+{
+       return kref_put(&job->refcount, v4l2_jobqueue_delete_job);
+}
+
+/*
+ * Move a job from one state to another in the jobqueue state machine, making
+ * extensive sanity tests.
+ *
+ * jobqueue lock must be held when this function is called.
+ */
+static void jobqueue_set_job_state(struct v4l2_job *job,
+                                  enum v4l2_job_status status)
+{
+       struct v4l2_jobqueue *jq = job->jq;
+       int i;
+
+       BUG_ON(!v4l2_jobqueue_is_locked(jq));
+
+       /* Sanity checks & jobqueue state update */
+       switch (status) {
+       case CURRENT:
+               BUG_ON(job->status != OUT_OF_QUEUE);
+               BUG_ON(jq->current_job != NULL);
+               jq->current_job = job;
+               break;
+       case QUEUED:
+               BUG_ON(job->status != CURRENT);
+               BUG_ON(jq->current_job != job);
+               jq->current_job = NULL;
+               list_add_tail(&job->node, &jq->queued_jobs);
+               break;
+       case ACTIVE:
+               BUG_ON(job->status != QUEUED);
+               BUG_ON(jq->active_job != NULL);
+               BUG_ON(list_first_entry_or_null(&jq->queued_jobs,
+                                               struct v4l2_job, node) != job);
+               list_del(&job->node);
+               jq->active_job = job;
+               break;
+       case COMPLETED:
+               BUG_ON(job->status != ACTIVE);
+               BUG_ON(jq->active_job != job);
+               jq->active_job = NULL;
+               list_add_tail(&job->node, &jq->completed_jobs);
+               break;
+       case DEQUEUED:
+               BUG_ON(job->status != COMPLETED);
+               BUG_ON(jq->dequeued_job != NULL);
+               BUG_ON(list_first_entry_or_null(&jq->completed_jobs,
+                                               struct v4l2_job, node) != job);
+               list_del(&job->node);
+               jq->dequeued_job = job;
+               break;
+       case OUT_OF_QUEUE:
+               BUG_ON(job->status != DEQUEUED);
+               BUG_ON(jq->dequeued_job != job);
+               jq->dequeued_job = NULL;
+               break;
+       };
+
+       job->status = status;
+
+       for (i = 0; i < jq->nb_devs; i++) {
+               struct v4l2_job_state_handler *hdl = jq->devs[i].state_handler;
+               struct v4l2_job_state *state = job->state[i];
+
+               switch (status) {
+               case CURRENT:
+                       hdl->current_state = state;
+                       break;
+               case ACTIVE:
+                       hdl->active_state = state;
+                       break;
+               case DEQUEUED:
+                       hdl->dequeued_state = state;
+                       break;
+               default:
+                       break;
+               }
+
+               if (hdl->ops->state_changed)
+                       hdl->ops->state_changed(hdl, state, status);
+       }
+}
+
+/*
+ * jobqueue lock must be held by caller
+ */
+static int v4l2_jobqueue_new_job(struct v4l2_jobqueue *jq)
+{
+       struct v4l2_job *job;
+       int i;
+
+       BUG_ON(!v4l2_jobqueue_is_locked(jq));
+
+       if (jq->current_job)
+               return -EBUSY;
+
+       job = kzalloc(sizeof(*job) + sizeof(job->state[0]) * jq->nb_devs,
+                     GFP_KERNEL);
+       if (job == NULL)
+               return -ENOMEM;
+
+       list_add(&job->jobs_list, &jq->jobs_list);
+       kref_init(&job->refcount);
+       job->jq = jq;
+       job->status = OUT_OF_QUEUE;
+       job->fd = -1;
+       atomic_set(&job->completed, 0);
+       for (i = 0; i < jq->nb_devs; i++) {
+               struct v4l2_job_state_handler *hdl = jq->devs[i].state_handler;
+               struct v4l2_job_state *state;
+
+               state = hdl->ops->job_new(hdl);
+               job->state[i] = state;
+               if (IS_ERR(state)) {
+                                v4l2_jobqueue_free_job(job);
+                       return PTR_ERR(state);
+               }
+               state->job = job;
+       }
+
+       jobqueue_set_job_state(job, CURRENT);
+
+       return 0;
+}
+
+/*
+ * Prepare the next queued job for streaming.
+ *
+ * jobqueue lock must be held by the caller.
+ */
+static void v4l2_jobqueue_prepare_streaming(struct v4l2_jobqueue *jq)
+{
+       struct v4l2_job *job;
+
+       BUG_ON(!v4l2_jobqueue_is_locked(jq));
+
+       /* Already streaming */
+       if (jq->active_job)
+               return;
+
+       job = list_first_entry_or_null(&jq->queued_jobs, struct v4l2_job, node);
+       /* No job ready to be streamed */
+       if (!job)
+               return;
+       jobqueue_set_job_state(job, ACTIVE);
+}
+
+/*
+ * Asks all devices to start processing the prepared job
+ *
+ */
+static void v4l2_jobqueue_start_streaming(struct v4l2_jobqueue *jq)
+{
+       int ret;
+       int i;
+
+       /* No job ready to be streamed */
+       if (!jq->active_job)
+               return;
+
+       /*
+        * TODO move what follows into a worker?
+        */
+
+       /* Set the desired state on all devices */
+       for (i = 0; i < jq->nb_devs; i++) {
+               struct v4l2_job_state_handler *handler;
+
+               handler = jq->devs[i].state_handler;
+               ret = handler->ops->job_apply(handler);
+               /* TODO proper cleanup and reporting after error */
+               if (ret < 0) {
+                       pr_err("error while setting job queue parameters!\n");
+                       return;
+               }
+       }
+
+       /* Start streaming on all devices */
+       for (i = 0; i < jq->nb_devs; i++) {
+               struct v4l2_job_state_handler *handler;
+
+               handler = jq->devs[i].state_handler;
+
+               if (handler->process_active_job)
+                       handler->process_active_job(jq->devs[i].state_handler);
+       }
+}
+
+static void v4l2_jobqueue_job_complete(struct work_struct *work)
+{
+       struct v4l2_jobqueue *jq = container_of(work, struct v4l2_jobqueue,
+                                               job_complete_work);
+
+       v4l2_jobqueue_lock(jq);
+
+       jobqueue_set_job_state(jq->active_job, COMPLETED);
+       wake_up(&jq->done_wq);
+
+       v4l2_jobqueue_prepare_streaming(jq);
+
+       v4l2_jobqueue_unlock(jq);
+
+       /* see if we can perform the next job */
+       v4l2_jobqueue_start_streaming(jq);
+}
+
+struct v4l2_jobqueue *v4l2_jobqueue_new(void)
+{
+       struct v4l2_jobqueue *jq;
+
+       jq = kzalloc(sizeof(*jq), GFP_KERNEL);
+       if (jq == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       INIT_LIST_HEAD(&jq->jobs_list);
+       INIT_LIST_HEAD(&jq->queued_jobs);
+       INIT_LIST_HEAD(&jq->completed_jobs);
+       mutex_init(&jq->lock);
+       init_waitqueue_head(&jq->done_wq);
+       INIT_WORK(&jq->job_complete_work, v4l2_jobqueue_job_complete);
+
+       return jq;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_new);
+
+int v4l2_jobqueue_del(struct v4l2_jobqueue *jq)
+{
+       struct v4l2_job *job, *_job_t;
+       int i;
+
+       v4l2_jobqueue_lock(jq);
+
+       if (jq->current_job != NULL) {
+               job_put(jq->current_job);
+               jq->current_job = NULL;
+       }
+
+       /* Clean all pending jobs */
+       list_for_each_entry_safe(job, _job_t, &jq->queued_jobs, node) {
+               pr_warn("Deleting pending queued job\n");
+               list_del(&job->node);
+               job_put(job);
+       }
+
+       /* Wait for active job to complete, if any */
+       while (jq->active_job != NULL) {
+               v4l2_jobqueue_unlock(jq);
+               wait_event(jq->done_wq, jq->active_job == NULL);
+               cancel_work_sync(&jq->job_complete_work);
+               v4l2_jobqueue_lock(jq);
+       }
+
+       /* Clean all completed jobs */
+       list_for_each_entry_safe(job, _job_t, &jq->completed_jobs, node) {
+               pr_warn("Deleting pending completed job\n");
+               list_del(&job->node);
+               job_put(job);
+       }
+
+       /* Delete currently dequeued job, if any */
+       if (jq->dequeued_job != NULL) {
+               job = jq->dequeued_job;
+               jobqueue_set_job_state(job, OUT_OF_QUEUE);
+               job_put(job);
+       }
+
+       /* No job should exist anymore */
+       WARN_ON(jq->dequeued_job);
+       WARN_ON(!list_empty(&jq->completed_jobs));
+       WARN_ON(jq->active_job);
+       WARN_ON(!list_empty(&jq->queued_jobs));
+       WARN_ON(jq->current_job);
+
+       /*
+        * must invalidate all fds exported to user-space, otherwise users
+        * may do funny things with them, or they will be automatically closed
+        * when the process exits
+        *
+        * TODO improve this. We can still enter a race if jobqueue_release_job
+        * if called right before we set private_data to NULL. Unfortunately
+        * we also cannot use fput() here the call to release is asynchronous.
+        */
+       if (!list_empty(&jq->jobs_list)) {
+               pr_warn("Exported jobs still exist, removing them\n");
+               list_for_each_entry_safe(job, _job_t, &jq->jobs_list, jobs_list)
+                       v4l2_jobqueue_free_job(job);
+       }
+
+       v4l2_jobqueue_unlock(jq);
+
+       for (i = 0; i < jq->nb_devs; i++) {
+               jq->devs[i].state_handler->jobqueue = NULL;
+               fput(jq->devs[i].f);
+       }
+       kfree(jq->devs);
+       kfree(jq);
+
+       return 0;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_del);
+
+int v4l2_jobqueue_init(struct v4l2_jobqueue *jq,
+                      struct v4l2_jobqueue_init *cinit)
+{
+       int ret;
+       int i;
+
+       if (cinit->nb_devs > V4L2_JOBQUEUE_MAX_DEVICES)
+               return -ENOSPC;
+
+       jq->devs = kzalloc(sizeof(jq->devs[0]) * cinit->nb_devs, GFP_KERNEL);
+       if (!jq->devs) {
+               pr_err("error: out of memory\n");
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < cinit->nb_devs; i++) {
+               struct file *f;
+               struct video_device *vdev;
+               struct v4l2_fh *fh;
+               struct v4l2_job_state_handler *handler;
+               struct v4l2_ctrl_handler *ctrl_handler;
+
+               f = fget(cinit->fd[i]);
+               jq->devs[i].f = f;
+               if (!v4l2_is_v4l2_file(f)) {
+                       pr_err("error: passed fd is not v4l2 device!\n");
+                       ret = -EINVAL;
+                       goto error;
+               }
+
+               fh = f->private_data;
+               vdev = video_devdata(f);
+
+               ctrl_handler = fh ? fh->ctrl_handler : vdev->ctrl_handler;
+               if (!ctrl_handler) {
+                       pr_err("error: no control handler in device!\n");
+                       ret = -EINVAL;
+                       goto error;
+               }
+
+               if (fh)
+                       handler = fh->state_handler;
+
+               if (!handler && vdev)
+                       handler = vdev->state_handler;
+
+               if (!handler) {
+                       pr_err("error: no state handler in device!\n");
+                       ret = -EINVAL;
+                       goto error;
+               }
+               handler->jobqueue = jq;
+               jq->devs[0].state_handler = handler;
+       }
+
+       jq->nb_devs = cinit->nb_devs;
+
+       /* Create first job */
+       v4l2_jobqueue_lock(jq);
+       ret = v4l2_jobqueue_new_job(jq);
+       v4l2_jobqueue_unlock(jq);
+       if (ret < 0)
+               goto error;
+
+       return 0;
+
+error:
+       jq->nb_devs = 0;
+       for (i = 0; i < cinit->nb_devs; i++)
+               if (jq->devs[i].f)
+                       fput(jq->devs[i].f);
+       kfree(jq->devs);
+       jq->devs = NULL;
+       return ret;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_init);
+
+void v4l2_jobqueue_lock(struct v4l2_jobqueue *jq)
+{
+       mutex_lock(&jq->lock);
+}
+EXPORT_SYMBOL(v4l2_jobqueue_lock);
+
+void v4l2_jobqueue_unlock(struct v4l2_jobqueue *jq)
+{
+       mutex_unlock(&jq->lock);
+}
+EXPORT_SYMBOL(v4l2_jobqueue_unlock);
+
+void v4l2_jobqueue_job_finish(struct v4l2_job_state_handler *handler)
+{
+       struct v4l2_job_state *state = handler->active_state;
+       struct v4l2_job *job;
+       struct v4l2_jobqueue *jq;
+       unsigned int finished_mask;
+       int pos;
+
+       BUG_ON(!state);
+
+       job = state->job;
+       jq = job->jq;
+       pos = state - job->state[0];
+       finished_mask = BIT(jq->nb_devs) - 1;
+
+       handler->ops->job_complete(handler);
+       handler->active_state = NULL;
+
+       /* have all devices completed? */
+       if (!atomic_add_return(BIT(pos), &job->completed) != finished_mask)
+               schedule_work(&jq->job_complete_work);
+}
+EXPORT_SYMBOL(v4l2_jobqueue_job_finish);
+
+int v4l2_jobqueue_qjob(struct v4l2_jobqueue *jq)
+{
+       bool start_streaming;
+       int ret = 0;
+
+       v4l2_jobqueue_lock(jq);
+
+       if (jq->current_job == NULL) {
+               /* This should never happen */
+               pr_err("no current job at the moment!\n");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       jobqueue_set_job_state(jq->current_job, QUEUED);
+
+       start_streaming = (jq->active_job == NULL);
+
+       v4l2_jobqueue_prepare_streaming(jq);
+
+       v4l2_jobqueue_unlock(jq);
+
+       v4l2_jobqueue_lock(jq);
+
+       ret = v4l2_jobqueue_new_job(jq);
+       if (ret < 0)
+               goto out;
+
+out:
+       v4l2_jobqueue_unlock(jq);
+
+       if (ret == 0 && start_streaming)
+               v4l2_jobqueue_start_streaming(jq);
+
+       return ret;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_qjob);
+
+int v4l2_jobqueue_dqjob(struct v4l2_jobqueue *jq)
+{
+       struct v4l2_job *job = NULL;
+       struct v4l2_job *old_job;
+       int ret;
+
+       v4l2_jobqueue_lock(jq);
+
+       while (true) {
+               job = list_first_entry_or_null(&jq->completed_jobs,
+                                              struct v4l2_job, node);
+               if (job)
+                       break;
+
+               v4l2_jobqueue_unlock(jq);
+               ret = wait_event_interruptible(jq->done_wq,
+                                             !list_empty(&jq->completed_jobs));
+               if (ret)
+                       return ret;
+               v4l2_jobqueue_lock(jq);
+       }
+
+       old_job = jq->dequeued_job;
+       if (old_job)
+               jobqueue_set_job_state(old_job, OUT_OF_QUEUE);
+
+       jobqueue_set_job_state(job, DEQUEUED);
+
+       v4l2_jobqueue_unlock(jq);
+
+       /* dispose of reference to previous job */
+       if (old_job)
+               job_put(old_job);
+
+       return 0;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_dqjob);
+
+static int v4l2_jobqueue_release_job(struct inode *inode, struct file *filp)
+{
+       struct v4l2_job *job = filp->private_data;
+       struct v4l2_jobqueue *jq = job->jq;
+
+       /* This can happen if a job queue is closed before all its exported
+        * jobs. In that case the job becomes inactive until its fd finally
+        * gets closed */
+       if (job == NULL)
+               return 0;
+
+       v4l2_jobqueue_lock(jq);
+
+       job->fd = -1;
+       job_put(job);
+
+       v4l2_jobqueue_unlock(jq);
+
+       return 0;
+}
+
+static const struct file_operations v4l2_jobqueue_job_ops = {
+       .owner = THIS_MODULE,
+       .release = v4l2_jobqueue_release_job,
+};
+
+int v4l2_jobqueue_export_job(struct v4l2_jobqueue *jq,
+                            struct v4l2_jobqueue_job *export_job)
+{
+       struct v4l2_job *job = jq->current_job;
+       int fd;
+       int i;
+
+       v4l2_jobqueue_lock(jq);
+
+       if (job->fd >= 0) {
+               v4l2_jobqueue_unlock(jq);
+               pr_warn("job already exported!\n");
+               return -EBUSY;
+       }
+
+       /* Sanity check that all devices support export */
+       for (i = 0; i < jq->nb_devs; i++) {
+               if (!jq->devs[i].state_handler->ops->job_export) {
+                       v4l2_jobqueue_unlock(jq);
+                       pr_warn("some devices do not support job export\n");
+                       return -ENOTSUPP;
+               }
+       }
+
+       for (i = 0; i < jq->nb_devs; i++) {
+               struct v4l2_job_state_handler *hdl = jq->devs[i].state_handler;
+               int ret;
+
+               ret = hdl->ops->job_export(hdl);
+               if (ret < 0) {
+                       v4l2_jobqueue_unlock(jq);
+                       pr_warn("job export failed\n");
+                       return ret;
+               }
+       }
+
+       fd = anon_inode_getfd("v4l2", &v4l2_jobqueue_job_ops, job, O_CLOEXEC);
+       if (fd < 0) {
+               v4l2_jobqueue_unlock(jq);
+               return fd;
+       }
+
+       export_job->fd = job->fd = fd;
+       job_get(job);
+
+       v4l2_jobqueue_unlock(jq);
+
+       return 0;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_export_job);
+
+int v4l2_jobqueue_import_job(struct v4l2_jobqueue *jq,
+                            struct v4l2_jobqueue_job *import_job)
+{
+       struct v4l2_job *current_job = NULL;
+       struct v4l2_job *job;
+       struct file *f = fget(import_job->fd);
+
+       if (!f)
+               return -EINVAL;
+
+       /* Is this fd legit? */
+       if (f->f_op != &v4l2_jobqueue_job_ops)
+               return -EINVAL;
+
+       job = f->private_data;
+       fput(f);
+
+       /* Does the job actually belong to us? */
+       if (job->jq != jq) {
+               pr_err("job belongs to a different queue!\n");
+               return -EINVAL;
+       }
+
+       v4l2_jobqueue_lock(jq);
+
+       /* Cannot import a job that is not out of queue */
+       if (job->status != OUT_OF_QUEUE) {
+               pr_err("job is already in the queue!\n");
+               v4l2_jobqueue_unlock(jq);
+               return -EBUSY;
+       }
+
+       job_get(job);
+       current_job = jq->current_job;
+       jq->current_job = NULL;
+       jobqueue_set_job_state(job, CURRENT);
+
+       v4l2_jobqueue_unlock(jq);
+       if (current_job)
+               job_put(current_job);
+
+       return 0;
+}
+EXPORT_SYMBOL(v4l2_jobqueue_import_job);
diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h
index b73d646980da..a1558d9bf309 100644
--- a/include/media/v4l2-dev.h
+++ b/include/media/v4l2-dev.h
@@ -38,6 +38,7 @@ struct v4l2_ioctl_callbacks;
 struct video_device;
 struct v4l2_device;
 struct v4l2_ctrl_handler;
+struct v4l2_job_state_handler;
 
 /* Flag to mark the video_device struct as registered.
    Drivers can clear this flag if they want to block all future
@@ -184,6 +185,8 @@ struct v4l2_file_operations {
  * @dev_parent: pointer to &struct device parent
  * @ctrl_handler: Control handler associated with this device node.
  *      May be NULL.
+ * @state_handler: State handler associated with this device node.
+ *      May be NULL.
  * @queue: &struct vb2_queue associated with this device node. May be NULL.
  * @prio: pointer to &struct v4l2_prio_state with device's Priority state.
  *      If NULL, then v4l2_dev->prio will be used.
@@ -229,6 +232,7 @@ struct video_device
        struct device *dev_parent;
 
        struct v4l2_ctrl_handler *ctrl_handler;
+       struct v4l2_job_state_handler *state_handler;
 
        struct vb2_queue *queue;
 
diff --git a/include/media/v4l2-fh.h b/include/media/v4l2-fh.h
index 0b0757090c04..ea86d713a59a 100644
--- a/include/media/v4l2-fh.h
+++ b/include/media/v4l2-fh.h
@@ -28,6 +28,7 @@
 
 struct video_device;
 struct v4l2_ctrl_handler;
+struct v4l2_job_state_handler;
 
 /**
  * struct v4l2_fh - Describes a V4L2 file handler
@@ -35,6 +36,8 @@ struct v4l2_ctrl_handler;
  * @list: list of file handlers
  * @vdev: pointer to &struct video_device
  * @ctrl_handler: pointer to &struct v4l2_ctrl_handler
+ * @state_handler: pointer to &struct v4l2_job_state_handler, may be NULL if
+ *                 jobs API not supported by this device.
  * @prio: priority of the file handler, as defined by &enum v4l2_priority
  *
  * @wait: event' s wait queue
@@ -48,6 +51,7 @@ struct v4l2_fh {
        struct list_head        list;
        struct video_device     *vdev;
        struct v4l2_ctrl_handler *ctrl_handler;
+       struct v4l2_job_state_handler *state_handler;
        enum v4l2_priority      prio;
 
        /* Events */
diff --git a/include/media/v4l2-job-state.h b/include/media/v4l2-job-state.h
new file mode 100644
index 000000000000..42048e8d11a8
--- /dev/null
+++ b/include/media/v4l2-job-state.h
@@ -0,0 +1,75 @@
+/*
+    V4L2 job states interface
+
+    Copyright (C) 2017  The Chromium project
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+ */
+
+#ifndef _V4L2_JOB_STATE_H
+#define _V4L2_JOB_STATE_H
+
+struct v4l2_jobqueue;
+struct v4l2_job_state_handler;
+struct v4l2_ext_control;
+struct v4l2_job;
+struct v4l2_ctrl;
+
+enum v4l2_job_status;
+
+struct v4l2_job_state {
+       struct v4l2_job *job;
+};
+
+struct v4l2_job_state_handler_ops {
+       /* Allocate a new job state for this device */
+       struct v4l2_job_state *(*job_new)(struct v4l2_job_state_handler *hdl);
+       /* Free a previously allocated job state */
+       void (*job_free)(struct v4l2_job_state_handler *hdl,
+                        struct v4l2_job_state *state);
+       /* Apply current job state */
+       int (*job_apply)(struct v4l2_job_state_handler *hdl);
+       /* Signal that the device has completed its part of the job */
+       void (*job_complete)(struct v4l2_job_state_handler *hdl);
+       /* Prepare the current job for being exported */
+       int (*job_export)(struct v4l2_job_state_handler *hdl);
+
+       /* Set control the the current state */
+       int (*s_ctrl)(struct v4l2_job_state_handler *hdl,
+                     struct v4l2_ext_control *c);
+       /* Get control from the dequeued state */
+       int (*g_ctrl)(struct v4l2_job_state_handler *hdl, u32 ctrl_id,
+                     struct v4l2_ext_control *c, u32 which);
+
+       /* Called whenever the HW value of a non-volatile control is changed */
+       void (*ctrl_changed)(struct v4l2_job_state_handler *hdl,
+                            struct v4l2_ctrl *ctrl);
+       /* Called whenever a job has moved in the job queue */
+       void (*state_changed)(struct v4l2_job_state_handler *hdl,
+                             struct v4l2_job_state *state,
+                      enum v4l2_job_status status);
+};
+
+/*
+ * Manages the state of one particular device. Contains pointers to the
+ * current, active and dequeued state that are updated by the jobs framework
+ */
+struct v4l2_job_state_handler {
+       struct v4l2_jobqueue *jobqueue;
+       const struct v4l2_job_state_handler_ops *ops;
+       void (*process_active_job)(struct v4l2_job_state_handler *hdl);
+       struct v4l2_job_state *current_state;
+       struct v4l2_job_state *active_state;
+       struct v4l2_job_state *dequeued_state;
+};
+
+#endif
diff --git a/include/media/v4l2-jobqueue-dev.h 
b/include/media/v4l2-jobqueue-dev.h
new file mode 100644
index 000000000000..9c3dcff72563
--- /dev/null
+++ b/include/media/v4l2-jobqueue-dev.h
@@ -0,0 +1,24 @@
+/*
+    V4L2 jobqueue device
+
+    Copyright (C) 2017  The Chromium project
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+ */
+
+#ifndef _V4L2_JOBQUEUE_DEV_H_
+#define _V4L2_JOBQUEUE_DEV_H
+
+int v4l2_jobqueue_device_init(void);
+void v4l2_jobqueue_device_exit(void);
+
+#endif
diff --git a/include/media/v4l2-jobqueue.h b/include/media/v4l2-jobqueue.h
new file mode 100644
index 000000000000..a294042b7c13
--- /dev/null
+++ b/include/media/v4l2-jobqueue.h
@@ -0,0 +1,54 @@
+/*
+    V4L2 jobqueue support header.
+
+    Copyright (C) 2017  The Chromium project
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+ */
+
+#ifndef _V4L2_JOBQUEUE_H
+#define _V4L2_JOBQUEUE_H
+
+struct v4l2_jobqueue;
+struct v4l2_job_state_handler;
+
+#include <uapi/linux/v4l2-jobs.h>
+
+enum v4l2_job_status {
+       CURRENT,
+       QUEUED,
+       ACTIVE,
+       COMPLETED,
+       DEQUEUED,
+       OUT_OF_QUEUE,
+};
+
+/* Jobqueue device interface */
+struct v4l2_jobqueue *v4l2_jobqueue_new(void);
+int v4l2_jobqueue_del(struct v4l2_jobqueue *jq);
+
+/* Ioctls support */
+int v4l2_jobqueue_init(struct v4l2_jobqueue *jq,
+                      struct v4l2_jobqueue_init *init);
+int v4l2_jobqueue_qjob(struct v4l2_jobqueue *jq);
+int v4l2_jobqueue_dqjob(struct v4l2_jobqueue *jq);
+int v4l2_jobqueue_export_job(struct v4l2_jobqueue *jq,
+                            struct v4l2_jobqueue_job *job);
+int v4l2_jobqueue_import_job(struct v4l2_jobqueue *jq,
+                            struct v4l2_jobqueue_job *job);
+
+/* Driver/state handler interface */
+void v4l2_jobqueue_job_finish(struct v4l2_job_state_handler *hdl);
+void v4l2_jobqueue_lock(struct v4l2_jobqueue *jq);
+void v4l2_jobqueue_unlock(struct v4l2_jobqueue *jq);
+
+#endif
diff --git a/include/uapi/linux/v4l2-jobs.h b/include/uapi/linux/v4l2-jobs.h
new file mode 100644
index 000000000000..2cba4d20e62f
--- /dev/null
+++ b/include/uapi/linux/v4l2-jobs.h
@@ -0,0 +1,40 @@
+/*
+ * V4L2 jobs API
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_V4L2_JOBS_H
+#define __LINUX_V4L2_JOBS_H
+
+#ifndef __KERNEL__
+#include <stdint.h>
+#endif
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+struct v4l2_jobqueue_init {
+       __u32 nb_devs;
+       __s32 *fd;
+};
+
+struct v4l2_jobqueue_job {
+       __s32 fd;
+};
+
+#define VIDIOC_JOBQUEUE_IOCTL_START    0x80
+
+#define VIDIOC_JOBQUEUE_INIT           _IOW('|', VIDIOC_JOBQUEUE_IOCTL_START + 
0x00, struct v4l2_jobqueue_init)
+#define VIDIOC_JOBQUEUE_QJOB           _IO('|', VIDIOC_JOBQUEUE_IOCTL_START + 
0x01)
+#define VIDIOC_JOBQUEUE_DQJOB          _IO('|', VIDIOC_JOBQUEUE_IOCTL_START + 
0x02)
+#define VIDIOC_JOBQUEUE_EXPORT_JOB     _IOR('|', VIDIOC_JOBQUEUE_IOCTL_START + 
0x03, struct v4l2_jobqueue_job)
+#define VIDIOC_JOBQUEUE_IMPORT_JOB     _IOW('|', VIDIOC_JOBQUEUE_IOCTL_START + 
0x03, struct v4l2_jobqueue_job)
+
+#endif
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 45cf7359822c..7f43e97cf461 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -1591,6 +1591,8 @@ struct v4l2_ext_controls {
 #define V4L2_CTRL_MAX_DIMS       (4)
 #define V4L2_CTRL_WHICH_CUR_VAL   0
 #define V4L2_CTRL_WHICH_DEF_VAL   0x0f000000
+#define V4L2_CTRL_WHICH_CURJOB_VAL   0x0e000000
+#define V4L2_CTRL_WHICH_DEQJOB_VAL   0x0d000000
 
 enum v4l2_ctrl_type {
        V4L2_CTRL_TYPE_INTEGER       = 1,
-- 
2.14.2.822.g60be5d43e6-goog

Reply via email to