Re: [RFC PATCH 2/9] [media] v4l2-core: add core jobs API support
From: Hans Verkuil
Date: Mon Oct 16 2017 - 06:01:15 EST
On 09/28/2017 11:50 AM, Alexandre Courbot wrote:
> 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 <acourbot@xxxxxxxxxxxx>
> ---
> 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);
There really is no need for these stub functions, just inline them for each
case.
> +
> + 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);
> +}
<snip>
> 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;
Shouldn't this be a list as well? I interpret 'active job' as being a
job that is passed to the various drivers that need to process it.
Just as with video buffers where the hardware may need a minimum of
buffers before it can start the DMA (min_buffers_needed), so the same
is true for jobs. E.g. a driver may have to look ahead by a few frames
to see what changes are requested. Some changes (esp. sensor related)
can take a few frames before they take effect and the hardware has to
be programmed ahead of time.
It would make more sense if this was a list of active jobs. It would
likely also solve the TODOs you have in the code w.r.t. min_buffers_needed:
after STREAMON you'd just queue the jobs to the active list, and also
queue any associated buffers. Once the minimum number of buffers has been
reached the DMA is started.
> +
> + /* 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;
> +};
<snip>
> 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
Why this offset?
> +
> +#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,
>
Regards,
Hans