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 <acourbot@xxxxxxxxxxxx> --- 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