Add a generic state handler that records controls to be set for a given
job and applies them once the job is scheduled, while also allowing said
controls to be read back once the job is dequeued.

This implementation can be used as-is for most drivers, with only drivers
with specific needs needing to provide an alternative implementation.

Note: this is still very early, but should do the job to demonstrate the
jobs API feasibility. Amongst the current limitations:

- We use v4l2_ext_control to store controls, which expects user-space
pointers. As a consequence only integer controls are supported at the
moment.
- No support for try_ctrl yet.

Signed-off-by: Alexandre Courbot <acour...@chromium.org>
---
 drivers/media/v4l2-core/Makefile           |   3 +-
 drivers/media/v4l2-core/v4l2-job-generic.c | 394 +++++++++++++++++++++++++++++
 include/media/v4l2-job-generic.h           |  47 ++++
 3 files changed, 443 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/v4l2-core/v4l2-job-generic.c
 create mode 100644 include/media/v4l2-job-generic.h

diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index a717bb8f1a25..ee09e1f29129 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-jobqueue.o v4l2-jobqueue-dev.o
+                       v4l2-async.o v4l2-jobqueue.o v4l2-jobqueue-dev.o \
+                       v4l2-job-generic.o
 
 ifeq ($(CONFIG_COMPAT),y)
   videodev-objs += v4l2-compat-ioctl32.o
diff --git a/drivers/media/v4l2-core/v4l2-job-generic.c 
b/drivers/media/v4l2-core/v4l2-job-generic.c
new file mode 100644
index 000000000000..ded6464e723a
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-job-generic.c
@@ -0,0 +1,394 @@
+/*
+    V4L2 generic jobs 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/slab.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+
+#include <media/v4l2-job-generic.h>
+#include <media/v4l2-jobqueue.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-dev.h>
+#include <linux/videodev2.h>
+
+#define to_generic_state_handler(hdl) \
+       container_of(hdl, struct v4l2_generic_state_handler, base)
+
+struct v4l2_generic_job_state {
+       struct v4l2_job_state base;
+       struct list_head node;
+
+       int nr_ctrls;
+       struct v4l2_ext_control ctrls[0];
+};
+#define to_generic_job_state(job) \
+       container_of(job, struct v4l2_generic_job_state, base)
+
+/* TODO this is O(n). Find a better way to store/lookup controls */
+static struct v4l2_ext_control *
+v4l2_generic_job_find_control(struct v4l2_generic_job_state *job, u32 id)
+{
+       int i;
+
+       for (i = 0; i < job->nr_ctrls; i++)
+               if (job->ctrls[i].id == id)
+                       return &job->ctrls[i];
+
+       return NULL;
+}
+
+static struct v4l2_job_state *
+v4l2_job_generic_job_new(struct v4l2_job_state_handler *_hdl)
+{
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *ret;
+
+       ret = kzalloc(sizeof(*ret) + sizeof(ret->ctrls[0]) * hdl->nr_ctrls,
+                     GFP_KERNEL);
+       if (ret == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       return &ret->base;
+}
+
+static void v4l2_job_generic_job_free(struct v4l2_job_state_handler *hdl,
+                                       struct v4l2_job_state *_job)
+{
+       struct v4l2_generic_job_state *job = to_generic_job_state(_job);
+
+       kfree(job);
+}
+
+static int v4l2_job_generic_job_apply(struct v4l2_job_state_handler *_hdl)
+{
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *job;
+       struct v4l2_ext_controls ctrls;
+       int ret;
+
+       job = to_generic_job_state(_hdl->active_state);
+
+       if (job->nr_ctrls == 0)
+               return 0;
+
+       ctrls.which = V4L2_CTRL_WHICH_CUR_VAL;
+       ctrls.count = job->nr_ctrls;
+       ctrls.controls = job->ctrls;
+
+       ret = v4l2_s_ext_ctrls(hdl->fh, hdl->ctrl_hdl, &ctrls);
+       if (ret) {
+               pr_err("Cannot set job controls: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int v4l2_job_generic_s_ctrl(struct v4l2_job_state_handler *_hdl,
+                                  struct v4l2_ext_control *c)
+{
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *job;
+       struct v4l2_ext_control *ctrl = NULL;
+
+       job = to_generic_job_state(_hdl->current_state);
+
+       ctrl = v4l2_generic_job_find_control(job, c->id);
+       if (!ctrl)
+               ctrl = &job->ctrls[job->nr_ctrls++];
+
+       /* This should never happen as we allocate exactly enough space for
+        * all the controls of our device */
+       BUG_ON(job->nr_ctrls > hdl->nr_ctrls);
+
+       /* TODO manage pointers, do a try, etc */
+       ctrl->id = c->id;
+       ctrl->value = c->value;
+
+       return 0;
+}
+
+/*
+ * Try to read the control value from the passed job, then its parents, then
+ * the hardware if none has defined a state.
+ */
+static int
+v4l2_job_generic_g_ctrl_locked(struct v4l2_generic_state_handler *hdl,
+                              struct v4l2_generic_job_state *job, u32 ctrl_id,
+                              struct v4l2_ext_control *c, bool upward)
+{
+       struct v4l2_ext_control *ctrl;
+       struct list_head *node;
+
+       if (job == NULL || &job->base == hdl->base.active_state) {
+               struct v4l2_control ctrl = {
+                       .id = ctrl_id,
+               };
+               int ret;
+
+               /* TODO terrible! */
+               mutex_unlock(hdl->ctrl_hdl->lock);
+               ret = v4l2_g_ctrl(hdl->ctrl_hdl, &ctrl);
+               mutex_lock(hdl->ctrl_hdl->lock);
+               if (ret < 0)
+                       return ret;
+
+               c->value = ctrl.value;
+
+               return 0;
+       }
+
+       ctrl = v4l2_generic_job_find_control(job, ctrl_id);
+       if (ctrl) {
+               /* TODO handle pointers, etc. */
+               c->value = ctrl->value;
+               return 0;
+       }
+
+       if (upward)
+               node = job->node.prev;
+       else
+               node = job->node.next;
+
+       /* That was our last job, request hardware state */
+       if (node == &hdl->jobs)
+               job = NULL;
+       else
+               job = container_of(node, struct v4l2_generic_job_state, node);
+       return v4l2_job_generic_g_ctrl_locked(hdl, job, ctrl_id, c, upward);
+}
+
+static int
+v4l2_job_generic_g_ctrl(struct v4l2_job_state_handler *_hdl, u32 ctrl_id,
+                       struct v4l2_ext_control *c, u32 which)
+{
+       struct v4l2_jobqueue *jq = _hdl->jobqueue;
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_job_state *_job;
+       struct v4l2_generic_job_state *job;
+       bool upward;
+       int ret;
+
+       if (which != V4L2_CTRL_WHICH_CURJOB_VAL &&
+           which != V4L2_CTRL_WHICH_DEQJOB_VAL)
+               return -EINVAL;
+
+       v4l2_jobqueue_lock(jq);
+
+       if (which == V4L2_CTRL_WHICH_DEQJOB_VAL) {
+               _job = hdl->base.dequeued_state;
+               upward = true;
+       } else {
+               _job = hdl->base.current_state;
+               upward = false;
+       }
+       if (!_job) {
+               pr_err("No state to query controls from!\n");
+               return -EINVAL;
+       }
+
+       job = to_generic_job_state(_job);
+
+       ret = v4l2_job_generic_g_ctrl_locked(hdl, job, ctrl_id, c, upward);
+
+       v4l2_jobqueue_unlock(jq);
+
+       return ret;
+}
+
+static void v4l2_job_generic_job_complete(struct v4l2_job_state_handler *_hdl)
+{
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *job;
+       struct v4l2_ext_controls cs;
+       struct v4l2_ext_control ctrls[hdl->nr_vol_ctrls];
+       int ret;
+       int i;
+
+       /* We need to update all volatile controls, if any */
+       if (hdl->nr_vol_ctrls == 0)
+               return;
+
+       job = to_generic_job_state(_hdl->active_state);
+
+       memset(job->ctrls, 0, sizeof(job->ctrls[0]) * hdl->nr_ctrls);
+
+       for (i = 0; i < hdl->nr_vol_ctrls; i++)
+               job->ctrls[i].id = hdl->ctrls_ids[i];
+
+       cs.which = V4L2_CTRL_WHICH_CUR_VAL;
+       cs.count = hdl->nr_vol_ctrls;
+       cs.controls = ctrls;
+
+       ret = v4l2_g_ext_ctrls(hdl->ctrl_hdl, &cs);
+       if (ret < 0) {
+               pr_err("Cannot read output controls: %d\n", ret);
+               return;
+       }
+
+       for (i = 0; i < cs.count; i++) {
+               ret = v4l2_job_generic_s_ctrl(_hdl, &ctrls[i]);
+               if (ret < 0)
+                       pr_err("Cannot update volatile control value!\n");
+       }
+}
+
+static void v4l2_job_generic_state_changed(struct v4l2_job_state_handler *_hdl,
+                                          struct v4l2_job_state *_job,
+                                          enum v4l2_job_status status)
+{
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *job = to_generic_job_state(_job);
+
+       switch (status) {
+       case CURRENT:
+               list_add(&job->node, &hdl->jobs);
+               break;
+       case COMPLETED:
+               hdl->last_completed = job;
+               break;
+       case OUT_OF_QUEUE:
+               list_del(&job->node);
+               if (hdl->last_completed == job)
+                       hdl->last_completed = NULL;
+               break;
+       default:
+               break;
+       }
+}
+
+static void v4l2_job_generic_ctrl_changed(struct v4l2_job_state_handler *_hdl,
+                                         struct v4l2_ctrl *ctrl)
+{
+       struct v4l2_jobqueue *jq = _hdl->jobqueue;
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *job;
+       struct v4l2_ext_control *ext_ctrl;
+
+       /* prevent the job queue from changing state */
+       v4l2_jobqueue_lock(jq);
+
+       job = hdl->last_completed;
+
+       if (!job)
+               goto out;
+
+       /* store the current value of the control into the job so it reflects
+        * the state at the time it completed */
+       ext_ctrl = v4l2_generic_job_find_control(job, ctrl->id);
+       /* we already have a completion value stored, nothing to do */
+       if (ext_ctrl)
+               goto out;
+
+       ext_ctrl = &job->ctrls[job->nr_ctrls++];
+       ext_ctrl->id = ctrl->id;
+       ext_ctrl->value = ctrl->cur.val;
+
+out:
+       v4l2_jobqueue_unlock(jq);
+}
+
+static int v4l2_job_generic_job_export(struct v4l2_job_state_handler *_hdl)
+{
+       struct v4l2_generic_state_handler *hdl = to_generic_state_handler(_hdl);
+       struct v4l2_generic_job_state *job;
+       struct v4l2_ctrl_handler *ctrl_hdl = hdl->ctrl_hdl;
+       struct v4l2_ctrl *ctrl;
+
+       job = to_generic_job_state(_hdl->current_state);
+
+       /*
+        * Read and store all controls, so the full state can be reapplied
+        * when we reuse this state
+        */
+       mutex_lock(ctrl_hdl->lock);
+
+       list_for_each_entry(ctrl, &ctrl_hdl->ctrls, node) {
+               /* dummy */
+               struct v4l2_ext_control c;
+
+               if (ctrl->flags & V4L2_CTRL_FLAG_WRITE_ONLY)
+                       continue;
+
+               c.id = ctrl->id;
+               /* get the current value either from the HW or a parent state */
+               v4l2_job_generic_g_ctrl_locked(hdl, job, ctrl->id, &c, false);
+               /* ... and store it */
+               v4l2_job_generic_s_ctrl(&hdl->base, &c);
+       }
+
+       mutex_unlock(ctrl_hdl->lock);
+
+       return 0;
+}
+
+static const struct v4l2_job_state_handler_ops v4l2_generic_job_ops = {
+       .job_new = v4l2_job_generic_job_new,
+       .job_free = v4l2_job_generic_job_free,
+       .job_apply = v4l2_job_generic_job_apply,
+       .job_complete = v4l2_job_generic_job_complete,
+       .job_export = v4l2_job_generic_job_export,
+
+       .s_ctrl = v4l2_job_generic_s_ctrl,
+       .g_ctrl = v4l2_job_generic_g_ctrl,
+
+       .state_changed = v4l2_job_generic_state_changed,
+       .ctrl_changed = v4l2_job_generic_ctrl_changed,
+};
+
+int v4l2_job_generic_init(struct v4l2_generic_state_handler *hdl,
+                   void (*process_active_job)(struct v4l2_job_state_handler *),
+                   struct v4l2_fh *fh, struct video_device *vdev)
+{
+       struct v4l2_ctrl *ctrl;
+       struct v4l2_ctrl_handler *ctrl_hdl;
+
+       hdl->base.process_active_job = process_active_job;
+
+       ctrl_hdl = fh ? fh->ctrl_handler : vdev->ctrl_handler;
+       ctrl_hdl->state_handler = &hdl->base;
+
+       if (fh)
+               fh->state_handler = &hdl->base;
+       else
+               vdev->state_handler = &hdl->base;
+
+       hdl->ctrl_hdl = ctrl_hdl;
+       hdl->fh = fh;
+       INIT_LIST_HEAD(&hdl->jobs);
+       hdl->base.ops = &v4l2_generic_job_ops;
+
+       mutex_lock(ctrl_hdl->lock);
+
+       /* Count how many controls we have to manage */
+       list_for_each_entry(ctrl, &ctrl_hdl->ctrls, node) {
+               /* Reserve permanent space for volatile controls */
+               if (ctrl->flags & V4L2_CTRL_FLAG_VOLATILE) {
+                       if (hdl->nr_vol_ctrls >= V4L2_GENERIC_JOB_MAX_CTRLS)
+                               return -ENOSPC;
+                       hdl->ctrls_ids[hdl->nr_vol_ctrls++] = ctrl->id;
+               }
+
+               hdl->nr_ctrls++;
+       }
+
+       mutex_unlock(ctrl_hdl->lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(v4l2_job_generic_init);
diff --git a/include/media/v4l2-job-generic.h b/include/media/v4l2-job-generic.h
new file mode 100644
index 000000000000..5f6ee55d68e4
--- /dev/null
+++ b/include/media/v4l2-job-generic.h
@@ -0,0 +1,47 @@
+/*
+    V4L2 generic jobs 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_JOB_GENERIC_H
+#define _V4L2_JOB_GENERIC_H
+
+#include <media/v4l2-job-state.h>
+#include <linux/videodev2.h>
+#include <linux/list.h>
+
+struct video_device;
+struct v4l2_fh;
+struct v4l2_ctrl;
+struct v4l2_ctrl_handler;
+struct v4l2_generic_job_state;
+
+#define V4L2_GENERIC_JOB_MAX_CTRLS 32
+struct v4l2_generic_state_handler {
+       struct v4l2_job_state_handler base;
+       struct list_head jobs;
+       struct v4l2_ctrl_handler *ctrl_hdl;
+       struct v4l2_fh *fh;
+       struct v4l2_generic_job_state *last_completed;
+       unsigned int nr_ctrls;
+       unsigned int nr_vol_ctrls;
+       u32 ctrls_ids[V4L2_GENERIC_JOB_MAX_CTRLS];
+};
+
+int v4l2_job_generic_init(struct v4l2_generic_state_handler *handler,
+                   void (*process_active_job)(struct v4l2_job_state_handler *),
+                   struct v4l2_fh *fh, struct video_device *vdev);
+
+#endif
-- 
2.14.2.822.g60be5d43e6-goog

Reply via email to