From 1a9445eb10d70f6fa720ed02f72abca53aa42457 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Sat, 13 Mar 2021 19:55:35 +0800 Subject: [PATCH 01/26] clang-format Signed-off-by: You-Sheng Yang --- utils/v4l2loopback-ctl.c | 3 +-- v4l2loopback_formats.h | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/utils/v4l2loopback-ctl.c b/utils/v4l2loopback-ctl.c index 29bf1ce1..f8c85821 100644 --- a/utils/v4l2loopback-ctl.c +++ b/utils/v4l2loopback-ctl.c @@ -313,8 +313,7 @@ static void help_setcaps(const char *program, int brief, int argc, char **argv) "\n------------------" "\n"); char fourcc[5]; - const size_t num_formats = - sizeof(formats) / sizeof(*formats); + const size_t num_formats = sizeof(formats) / sizeof(*formats); size_t i = 0; for (i = 0; i < num_formats; i++) { const struct v4l2l_format *fmt = formats + i; diff --git a/v4l2loopback_formats.h b/v4l2loopback_formats.h index 70861759..5a29b260 100644 --- a/v4l2loopback_formats.h +++ b/v4l2loopback_formats.h @@ -6,13 +6,13 @@ static const struct v4l2l_format formats[] = { #define V4L2_PIX_FMT_HEVC v4l2_fourcc('H', 'E', 'V', 'C') #endif -/* here come the packed formats */ -{ - .name = "32 bpp RGB, le", - .fourcc = V4L2_PIX_FMT_BGR32, - .depth = 32, - .flags = 0, -}, + /* here come the packed formats */ + { + .name = "32 bpp RGB, le", + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = 32, + .flags = 0, + }, { .name = "32 bpp RGB, be", .fourcc = V4L2_PIX_FMT_RGB32, From 1ca5cce7ef8c474a2f6fab056f902c8b49486941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Wed, 27 Jan 2021 22:47:10 +0100 Subject: [PATCH 02/26] drop hacks for linux<<3 --- v4l2loopback.c | 160 +------------------------------------------------ 1 file changed, 1 insertion(+), 159 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 31bde714..4750caa8 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -26,14 +26,8 @@ #include #include #include -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) -#define HAVE__V4L2_DEVICE #include -#endif -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) -#define HAVE__V4L2_CTRLS #include -#endif #include #include @@ -99,68 +93,6 @@ MODULE_LICENSE("GPL"); * compatibility hacks */ -#ifndef HAVE__V4L2_CTRLS -struct v4l2_ctrl_handler { - int error; -}; -struct v4l2_ctrl_config { - void *ops; - u32 id; - const char *name; - int type; - s32 min; - s32 max; - u32 step; - s32 def; -}; -int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, - unsigned nr_of_controls_hint) -{ - hdl->error = 0; - return 0; -} -void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) -{ -} -void *v4l2_ctrl_new_custom(struct v4l2_ctrl_handler *hdl, - const struct v4l2_ctrl_config *conf, void *priv) -{ - return NULL; -} -#endif /* HAVE__V4L2_CTRLS */ - -#ifndef HAVE__V4L2_DEVICE -/* dummy v4l2_device struct/functions */ -#define V4L2_DEVICE_NAME_SIZE (20 + 16) -struct v4l2_device { - char name[V4L2_DEVICE_NAME_SIZE]; - struct v4l2_ctrl_handler *ctrl_handler; -}; -static inline int v4l2_device_register(void *dev, void *v4l2_dev) -{ - return 0; -} -static inline void v4l2_device_unregister(struct v4l2_device *v4l2_dev) -{ - return; -} -#endif /* HAVE__V4L2_DEVICE */ - -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29) -#define v4l2_file_operations file_operations -#endif -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37) -void *v4l2l_vzalloc(unsigned long size) -{ - void *data = vmalloc(size); - - memset(data, 0, size); - return data; -} -#else -#define v4l2l_vzalloc vzalloc -#endif - static inline void v4l2l_get_timestamp(struct v4l2_buffer *b) { /* ktime_get_ts is considered deprecated, so use ktime_get_ts64 if possible */ @@ -284,11 +216,7 @@ static DEFINE_IDR(v4l2loopback_index_idr); static DEFINE_MUTEX(v4l2loopback_ctl_mutex); /* control IDs */ -#ifndef HAVE__V4L2_CTRLS -#define V4L2LOOPBACK_CID_BASE (V4L2_CID_PRIVATE_BASE) -#else #define V4L2LOOPBACK_CID_BASE (V4L2_CID_USER_BASE | 0xf000) -#endif #define CID_KEEP_FORMAT (V4L2LOOPBACK_CID_BASE + 0) #define CID_SUSTAIN_FRAMERATE (V4L2LOOPBACK_CID_BASE + 1) #define CID_TIMEOUT (V4L2LOOPBACK_CID_BASE + 2) @@ -1235,67 +1163,6 @@ static int vidioc_querystd(struct file *file, void *fh, v4l2_std_id *norm) } #endif /* V4L2LOOPBACK_WITH_STD */ -/* get ctrls info - * called on VIDIOC_QUERYCTRL - */ -static int vidioc_queryctrl(struct file *file, void *fh, - struct v4l2_queryctrl *q) -{ - const struct v4l2_ctrl_config *cnf = 0; - switch (q->id) { - case CID_KEEP_FORMAT: - cnf = &v4l2loopback_ctrl_keepformat; - break; - case CID_SUSTAIN_FRAMERATE: - cnf = &v4l2loopback_ctrl_sustainframerate; - break; - case CID_TIMEOUT: - cnf = &v4l2loopback_ctrl_timeout; - break; - case CID_TIMEOUT_IMAGE_IO: - cnf = &v4l2loopback_ctrl_timeoutimageio; - break; - default: - return -EINVAL; - } - if (!cnf) - BUG(); - - strcpy(q->name, cnf->name); - q->default_value = cnf->def; - q->type = cnf->type; - q->minimum = cnf->min; - q->maximum = cnf->max; - q->step = cnf->step; - - memset(q->reserved, 0, sizeof(q->reserved)); - return 0; -} - -static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *c) -{ - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); - - switch (c->id) { - case CID_KEEP_FORMAT: - c->value = dev->keep_format; - break; - case CID_SUSTAIN_FRAMERATE: - c->value = dev->sustain_framerate; - break; - case CID_TIMEOUT: - c->value = jiffies_to_msecs(dev->timeout_jiffies); - break; - case CID_TIMEOUT_IMAGE_IO: - c->value = dev->timeout_image_io; - break; - default: - return -EINVAL; - } - - return 0; -} - static int v4l2loopback_set_ctrl(struct v4l2_loopback_device *dev, u32 id, s64 val) { @@ -1341,11 +1208,6 @@ static int v4l2loopback_s_ctrl(struct v4l2_ctrl *ctrl) ctrl->handler, struct v4l2_loopback_device, ctrl_handler); return v4l2loopback_set_ctrl(dev, ctrl->id, ctrl->val); } -static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *c) -{ - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); - return v4l2loopback_set_ctrl(dev, c->id, c->value); -} /* returns set of device outputs, in our case there is only one * called on VIDIOC_ENUMOUTPUT @@ -2242,7 +2104,7 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) return -EINVAL; if (dev->timeout_image == NULL) { - dev->timeout_image = v4l2l_vzalloc(dev->buffer_size); + dev->timeout_image = vzalloc(dev->buffer_size); if (dev->timeout_image == NULL) return -ENOMEM; } @@ -2708,16 +2570,8 @@ static const struct v4l2_file_operations v4l2_loopback_fops = { static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { // clang-format off .vidioc_querycap = &vidioc_querycap, -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29) .vidioc_enum_framesizes = &vidioc_enum_framesizes, .vidioc_enum_frameintervals = &vidioc_enum_frameintervals, -#endif - -#ifndef HAVE__V4L2_CTRLS - .vidioc_queryctrl = &vidioc_queryctrl, - .vidioc_g_ctrl = &vidioc_g_ctrl, - .vidioc_s_ctrl = &vidioc_s_ctrl, -#endif /* HAVE__V4L2_CTRLS */ .vidioc_enum_output = &vidioc_enum_output, .vidioc_g_output = &vidioc_g_output, @@ -2888,15 +2742,3 @@ MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR); module_init(v4l2loopback_init_module); module_exit(v4l2loopback_cleanup_module); - -/* - * fake usage of unused functions - */ -#ifdef HAVE__V4L2_CTRLS -static int vidioc_queryctrl(struct file *file, void *fh, - struct v4l2_queryctrl *q) __attribute__((unused)); -static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *c) - __attribute__((unused)); -static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *c) - __attribute__((unused)); -#endif /* HAVE__V4L2_CTRLS */ From 7bace706284c7bc8ce92df8d0d94dcc5d71a58f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Wed, 27 Jan 2021 22:47:56 +0100 Subject: [PATCH 03/26] mention that linux<<3.0.0 is unsupported --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 80249ef5..27a9fbec 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,7 @@ Support: - >= 5.0.0 should work - >= 4.0.0 should work - >= 3.0.0 might work -- << 3.0.0 may work (has not been tested in ages) -- <= 2.6.27 will definitely NOT work +- << 3.0.0 unsupported # DISTRIBUTIONS v4l2loopack is now (since 2010-10-13) available as a Debian-package. From 89b476ba0aaf21274b175478437f32bed7e46116 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Wed, 17 Mar 2021 17:54:31 +0800 Subject: [PATCH 04/26] drop VIDIOCGMBUF support V4L1 has been completely removed from kernel since 2.6.39. --- v4l2loopback.c | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 4750caa8..6e95eebe 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -1680,21 +1680,6 @@ static int vidioc_streamoff(struct file *file, void *fh, return -EINVAL; } -#ifdef CONFIG_VIDEO_V4L1_COMPAT -static int vidiocgmbuf(struct file *file, void *fh, struct video_mbuf *p) -{ - struct v4l2_loopback_device *dev; - MARK(); - - dev = v4l2loopback_getdevice(file); - p->frames = dev->buffers_number; - p->offsets[0] = 0; - p->offsets[1] = 0; - p->size = dev->buffer_size; - return 0; -} -#endif - static int vidioc_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -2613,10 +2598,6 @@ static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { .vidioc_streamon = &vidioc_streamon, .vidioc_streamoff = &vidioc_streamoff, -#ifdef CONFIG_VIDEO_V4L1_COMPAT - .vidiocgmbuf = &vidiocgmbuf, -#endif - .vidioc_subscribe_event = &vidioc_subscribe_event, .vidioc_unsubscribe_event = &v4l2_event_unsubscribe, // clang-format on From 790a046c5783432d48dbe825fcade7b7d1ad264c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Thu, 28 Jan 2021 09:53:16 +0100 Subject: [PATCH 05/26] drop useless address-operator from function-tables --- v4l2loopback.c | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 6e95eebe..35add40c 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -2554,52 +2554,52 @@ static const struct v4l2_file_operations v4l2_loopback_fops = { static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { // clang-format off - .vidioc_querycap = &vidioc_querycap, - .vidioc_enum_framesizes = &vidioc_enum_framesizes, - .vidioc_enum_frameintervals = &vidioc_enum_frameintervals, + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, - .vidioc_enum_output = &vidioc_enum_output, - .vidioc_g_output = &vidioc_g_output, - .vidioc_s_output = &vidioc_s_output, + .vidioc_enum_output = vidioc_enum_output, + .vidioc_g_output = vidioc_g_output, + .vidioc_s_output = vidioc_s_output, - .vidioc_enum_input = &vidioc_enum_input, - .vidioc_g_input = &vidioc_g_input, - .vidioc_s_input = &vidioc_s_input, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, - .vidioc_enum_fmt_vid_cap = &vidioc_enum_fmt_cap, - .vidioc_g_fmt_vid_cap = &vidioc_g_fmt_cap, - .vidioc_s_fmt_vid_cap = &vidioc_s_fmt_cap, - .vidioc_try_fmt_vid_cap = &vidioc_try_fmt_cap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap, - .vidioc_enum_fmt_vid_out = &vidioc_enum_fmt_out, - .vidioc_s_fmt_vid_out = &vidioc_s_fmt_out, - .vidioc_g_fmt_vid_out = &vidioc_g_fmt_out, - .vidioc_try_fmt_vid_out = &vidioc_try_fmt_out, + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_out, #ifdef V4L2L_OVERLAY - .vidioc_s_fmt_vid_overlay = &vidioc_s_fmt_overlay, - .vidioc_g_fmt_vid_overlay = &vidioc_g_fmt_overlay, + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_overlay, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_overlay, #endif #ifdef V4L2LOOPBACK_WITH_STD - .vidioc_s_std = &vidioc_s_std, - .vidioc_g_std = &vidioc_g_std, - .vidioc_querystd = &vidioc_querystd, + .vidioc_s_std = vidioc_s_std, + .vidioc_g_std = vidioc_g_std, + .vidioc_querystd = vidioc_querystd, #endif /* V4L2LOOPBACK_WITH_STD */ - .vidioc_g_parm = &vidioc_g_parm, - .vidioc_s_parm = &vidioc_s_parm, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, - .vidioc_reqbufs = &vidioc_reqbufs, - .vidioc_querybuf = &vidioc_querybuf, - .vidioc_qbuf = &vidioc_qbuf, - .vidioc_dqbuf = &vidioc_dqbuf, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, - .vidioc_streamon = &vidioc_streamon, - .vidioc_streamoff = &vidioc_streamoff, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, - .vidioc_subscribe_event = &vidioc_subscribe_event, - .vidioc_unsubscribe_event = &v4l2_event_unsubscribe, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, // clang-format on }; From c256fb7ef92e045c6fd71d8bbb9da5fd4f77a090 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 16:40:01 +0800 Subject: [PATCH 06/26] mod: add output_nr module parameter in parallel with video_nr Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 35add40c..650b5433 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -175,6 +175,11 @@ module_param_array(video_nr, int, NULL, 0444); MODULE_PARM_DESC(video_nr, "video device numbers (-1=auto, 0=/dev/video0, etc.)"); +static int output_nr[MAX_DEVICES] = { [0 ...(MAX_DEVICES - 1)] = -1 }; +module_param_array(output_nr, int, NULL, 0444); +MODULE_PARM_DESC(output_nr, + "output device numbers (-1=auto, 0=/dev/video0, etc.)"); + static char *card_label[MAX_DEVICES]; module_param_array(card_label, charp, NULL, 0000); MODULE_PARM_DESC(card_label, "card labels for each device"); @@ -2628,9 +2633,10 @@ static int v4l2loopback_init_module(void) if (devices < 0) { devices = 1; - /* try guessing the devices from the "video_nr" parameter */ + /* try guessing the devices from the "video_nr" and "output_nr" + * parameters */ for (i = MAX_DEVICES - 1; i >= 0; i--) { - if (video_nr[i] >= 0) { + if (video_nr[i] >= 0 || output_nr[i] >= 0) { devices = i + 1; break; } @@ -2673,7 +2679,7 @@ static int v4l2loopback_init_module(void) for (i = 0; i < devices; i++) { struct v4l2_loopback_config cfg = { // clang-format off - .output_nr = video_nr[i], + .output_nr = output_nr[i], .capture_nr = video_nr[i], .max_width = max_width, .max_height = max_height, From 03366a7be04bf909e567a1e89e6b320608f20609 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 19:07:10 +0800 Subject: [PATCH 07/26] keep track of both output_nr and capture_nr in IDR With this change, output_nr will be allocated and deallocated as capture_nr is, but currently saved with no other reference to it. Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 121 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 42 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 650b5433..6ac58a96 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -220,6 +220,45 @@ MODULE_PARM_DESC(max_height, static DEFINE_IDR(v4l2loopback_index_idr); static DEFINE_MUTEX(v4l2loopback_ctl_mutex); +static int idr_alloc1(struct idr *idr, void *ptr, int *nr) +{ + int err; + + /* allocate id, if @id >= 0, we're requesting that specific id */ + if (*nr >= 0) { + err = idr_alloc(&v4l2loopback_index_idr, ptr, *nr, *nr + 1, + GFP_KERNEL); + if (err == -ENOSPC) + err = -EEXIST; + } else { + err = idr_alloc(&v4l2loopback_index_idr, ptr, 0, 0, GFP_KERNEL); + } + + if (err < 0) + return err; + + *nr = err; + return 0; +} + +static int idr_alloc2(struct idr *idr, void *ptr, int *nr1, int *nr2) +{ + int nr1_copy = *nr1; + int err; + + err = idr_alloc1(idr, ptr, &nr1_copy); + if (err) + return err; + + err = idr_alloc1(idr, ptr, nr2); + if (err) + idr_remove(idr, nr1_copy); + else + *nr1 = nr1_copy; + + return err; +} + /* control IDs */ #define V4L2LOOPBACK_CID_BASE (V4L2_CID_USER_BASE | 0xf000) #define CID_KEEP_FORMAT (V4L2LOOPBACK_CID_BASE + 0) @@ -298,6 +337,7 @@ struct v4l2l_buffer { struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; + int output_nr; struct video_device *vdev; /* pixel and stream format */ struct v4l2_pix_format pix_format; @@ -596,7 +636,8 @@ static int v4l2loopback_lookup_cb(int id, void *ptr, void *data) struct v4l2_loopback_device *device = ptr; struct v4l2loopback_lookup_cb_data *cbdata = data; if (cbdata && device && device->vdev) { - if (device->vdev->num == cbdata->device_nr) { + if (device->output_nr == cbdata->device_nr || + device->vdev->num == cbdata->device_nr) { cbdata->device = device; cbdata->device_nr = id; return 1; @@ -2102,7 +2143,7 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) } /* fills and register video device */ -static void init_vdev(struct video_device *vdev, int nr) +static void init_vdev(struct video_device *vdev) { MARK(); @@ -2229,18 +2270,10 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) int _max_buffers = DEFAULT_FROM_CONF(max_buffers, <= 0, max_buffers); int _max_openers = DEFAULT_FROM_CONF(max_openers, <= 0, max_openers); - int nr = -1; + int output_nr = -1, capture_nr = -1; if (conf) { - if (conf->capture_nr >= 0 && + if (conf->output_nr >= 0 && conf->capture_nr >= 0 && conf->output_nr == conf->capture_nr) { - nr = conf->capture_nr; - } else if (conf->capture_nr < 0 && conf->output_nr < 0) { - nr = -1; - } else if (conf->capture_nr < 0) { - nr = conf->output_nr; - } else if (conf->output_nr < 0) { - nr = conf->capture_nr; - } else { printk(KERN_ERR "split OUTPUT and CAPTURE devices not yet supported."); printk(KERN_INFO @@ -2248,39 +2281,33 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) conf->output_nr, conf->capture_nr); return -EINVAL; } - } - if (idr_find(&v4l2loopback_index_idr, nr)) - return -EEXIST; + output_nr = conf->output_nr; + capture_nr = conf->capture_nr; + } - dprintk("creating v4l2loopback-device #%d\n", nr); dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; - /* allocate id, if @id >= 0, we're requesting that specific id */ - if (nr >= 0) { - err = idr_alloc(&v4l2loopback_index_idr, dev, nr, nr + 1, - GFP_KERNEL); - if (err == -ENOSPC) - err = -EEXIST; - } else { - err = idr_alloc(&v4l2loopback_index_idr, dev, 0, 0, GFP_KERNEL); - } - if (err < 0) + err = idr_alloc2(&v4l2loopback_index_idr, dev, &output_nr, &capture_nr); + if (err) goto out_free_dev; - nr = err; - err = -ENOMEM; + + dprintk("creating v4l2loopback-device %d:%d\n", output_nr, capture_nr); + + /* Save output_nr somewhere before spliting device is supported. */ + dev->output_nr = output_nr; if (conf && conf->card_label && *(conf->card_label)) { snprintf(dev->card_label, sizeof(dev->card_label), "%s", conf->card_label); } else { snprintf(dev->card_label, sizeof(dev->card_label), - "Dummy video device (0x%04X)", nr); + "Dummy video device (0x%04X)", capture_nr); } snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), - "v4l2loopback-%03d", nr); + "v4l2loopback-%03d", capture_nr); err = v4l2_device_register(NULL, &dev->v4l2_dev); if (err) @@ -2304,9 +2331,9 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) snprintf(dev->vdev->name, sizeof(dev->vdev->name), dev->card_label); ((struct v4l2loopback_private *)video_get_drvdata(dev->vdev)) - ->device_nr = nr; + ->device_nr = capture_nr; - init_vdev(dev->vdev, nr); + init_vdev(dev->vdev); dev->vdev->v4l2_dev = &dev->v4l2_dev; init_capture_param(&dev->capture_param); set_timeperframe(dev, &dev->capture_param.timeperframe); @@ -2343,8 +2370,8 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) timer_setup(&dev->sustain_timer, sustain_timer_clb, 0); timer_setup(&dev->timeout_timer, timeout_timer_clb, 0); #else - setup_timer(&dev->sustain_timer, sustain_timer_clb, nr); - setup_timer(&dev->timeout_timer, timeout_timer_clb, nr); + setup_timer(&dev->sustain_timer, sustain_timer_clb, capture_nr); + setup_timer(&dev->timeout_timer, timeout_timer_clb, capture_nr); #endif dev->reread_count = 0; dev->timeout_jiffies = 0; @@ -2385,7 +2412,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) init_waitqueue_head(&dev->read_event); /* register the device -> it creates /dev/video* */ - if (video_register_device(dev->vdev, VFL_TYPE_VIDEO, nr) < 0) { + if (video_register_device(dev->vdev, VFL_TYPE_VIDEO, capture_nr) < 0) { printk(KERN_ERR "v4l2loopback: failed video_register_device()\n"); err = -EFAULT; @@ -2405,7 +2432,8 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) out_unregister: v4l2_device_unregister(&dev->v4l2_dev); out_free_idr: - idr_remove(&v4l2loopback_index_idr, nr); + idr_remove(&v4l2loopback_index_idr, output_nr); + idr_remove(&v4l2loopback_index_idr, capture_nr); out_free_dev: kfree(dev); return err; @@ -2456,11 +2484,12 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, case V4L2LOOPBACK_CTL_REMOVE: ret = v4l2loopback_lookup((int)parm, &dev); if (ret >= 0 && dev) { - int nr = ret; - ret = -EBUSY; - if (dev->open_count.counter > 0) + if (dev->open_count.counter > 0) { + ret = -EBUSY; break; - idr_remove(&v4l2loopback_index_idr, nr); + } + idr_remove(&v4l2loopback_index_idr, dev->output_nr); + idr_remove(&v4l2loopback_index_idr, dev->vdev->num); v4l2_loopback_remove(dev); ret = 0; }; @@ -2500,7 +2529,8 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, snprintf(conf.card_label, sizeof(conf.card_label), "%s", dev->card_label); MARK(); - conf.output_nr = conf.capture_nr = dev->vdev->num; + conf.output_nr = dev->output_nr; + conf.capture_nr = dev->vdev->num; conf.max_width = dev->max_width; conf.max_height = dev->max_height; conf.announce_all_caps = dev->announce_all_caps; @@ -2611,7 +2641,14 @@ static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { static int free_device_cb(int id, void *ptr, void *data) { struct v4l2_loopback_device *dev = ptr; - v4l2_loopback_remove(dev); + + /* Half-configured device instances are freed in v4l2_loopback_add(), + * so here we only have to deal with fully instanciated devices. In + * order to avoid double free, free only when id matches its output_nr. + */ + if (id == dev->output_nr) + v4l2_loopback_remove(dev); + return 0; } static void free_devices(void) From 727e6258566258f47f07608c14dec61f39ecf0f9 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 19:52:39 +0800 Subject: [PATCH 08/26] refactor v4l2loopback_lookup Since now both output_nr and capture_nr are meaningful, don't return either of them. Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 50 ++++++++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 6ac58a96..0238b1e5 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -639,14 +639,12 @@ static int v4l2loopback_lookup_cb(int id, void *ptr, void *data) if (device->output_nr == cbdata->device_nr || device->vdev->num == cbdata->device_nr) { cbdata->device = device; - cbdata->device_nr = id; return 1; } } return 0; } -static int v4l2loopback_lookup(int device_nr, - struct v4l2_loopback_device **device) +static struct v4l2_loopback_device *v4l2loopback_lookup(int device_nr) { struct v4l2loopback_lookup_cb_data data = { .device_nr = device_nr, @@ -654,12 +652,7 @@ static int v4l2loopback_lookup(int device_nr, }; int err = idr_for_each(&v4l2loopback_index_idr, &v4l2loopback_lookup_cb, &data); - if (1 == err) { - if (device) - *device = data.device; - return data.device_nr; - } - return -ENODEV; + return 1 == err ? data.device : NULL; } static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd) { @@ -2453,7 +2446,7 @@ static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, unsigned long parm) { - struct v4l2_loopback_device *dev; + struct v4l2_loopback_device *dev, *capture_dev, *output_dev; struct v4l2_loopback_config conf; struct v4l2_loopback_config *confptr = &conf; int device_nr; @@ -2482,12 +2475,12 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, break; /* remove a v4l2loopback device (both capture and output) */ case V4L2LOOPBACK_CTL_REMOVE: - ret = v4l2loopback_lookup((int)parm, &dev); - if (ret >= 0 && dev) { - if (dev->open_count.counter > 0) { - ret = -EBUSY; - break; - } + dev = v4l2loopback_lookup((int)parm); + if (dev == NULL) + ret = -ENODEV; + else if (dev->open_count.counter > 0) + ret = -EBUSY; + else { idr_remove(&v4l2loopback_index_idr, dev->output_nr); idr_remove(&v4l2loopback_index_idr, dev->vdev->num); v4l2_loopback_remove(dev); @@ -2503,25 +2496,18 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, if ((ret = copy_from_user(&conf, (void *)parm, sizeof(conf))) < 0) break; - device_nr = - (conf.output_nr < 0) ? conf.capture_nr : conf.output_nr; - MARK(); + + output_dev = v4l2loopback_lookup(conf.output_nr); + capture_dev = v4l2loopback_lookup(conf.capture_nr); /* get the device from either capture_nr or output_nr (whatever is valid) */ - if ((ret = v4l2loopback_lookup(device_nr, &dev)) < 0) + dev = output_dev ? output_dev : capture_dev; + if (dev == NULL) break; MARK(); - /* if we got the device from output_nr and there is a valid capture_nr, - * make sure that both refer to the same device (or bail out) - */ - if ((device_nr != conf.capture_nr) && (conf.capture_nr >= 0) && - (ret != v4l2loopback_lookup(conf.capture_nr, 0))) - break; - MARK(); - /* if otoh, we got the device from capture_nr and there is a valid output_nr, - * make sure that both refer to the same device (or bail out) - */ - if ((device_nr != conf.output_nr) && (conf.output_nr >= 0) && - (ret != v4l2loopback_lookup(conf.output_nr, 0))) + /* if we got two valid device pointers, make sure they refer to + * the same device (or bail out) + */ + if (output_dev && capture_dev && output_dev != capture_dev) break; MARK(); From 32cbef313a1e89423afc03e4196b02b09052d659 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 17:04:53 +0800 Subject: [PATCH 09/26] cache vdev for latter uses Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 0238b1e5..a5cab421 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -2249,6 +2249,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) { struct v4l2_loopback_device *dev; struct v4l2_ctrl_handler *hdl; + struct video_device *vdev; int err = -ENOMEM; @@ -2307,27 +2308,27 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) goto out_free_idr; MARK(); - dev->vdev = video_device_alloc(); - if (dev->vdev == NULL) { + vdev = video_device_alloc(); + if (vdev == NULL) { err = -ENOMEM; goto out_unregister; } - video_set_drvdata(dev->vdev, - kzalloc(sizeof(struct v4l2loopback_private), - GFP_KERNEL)); - if (video_get_drvdata(dev->vdev) == NULL) { + dev->vdev = vdev; + video_set_drvdata(vdev, kzalloc(sizeof(struct v4l2loopback_private), + GFP_KERNEL)); + if (video_get_drvdata(vdev) == NULL) { err = -ENOMEM; goto out_unregister; } MARK(); - snprintf(dev->vdev->name, sizeof(dev->vdev->name), dev->card_label); + snprintf(vdev->name, sizeof(vdev->name), dev->card_label); - ((struct v4l2loopback_private *)video_get_drvdata(dev->vdev)) - ->device_nr = capture_nr; + ((struct v4l2loopback_private *)video_get_drvdata(vdev))->device_nr = + capture_nr; - init_vdev(dev->vdev); - dev->vdev->v4l2_dev = &dev->v4l2_dev; + init_vdev(vdev); + vdev->v4l2_dev = &dev->v4l2_dev; init_capture_param(&dev->capture_param); set_timeperframe(dev, &dev->capture_param.timeperframe); dev->keep_format = 0; @@ -2405,21 +2406,21 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) init_waitqueue_head(&dev->read_event); /* register the device -> it creates /dev/video* */ - if (video_register_device(dev->vdev, VFL_TYPE_VIDEO, capture_nr) < 0) { + if (video_register_device(vdev, VFL_TYPE_VIDEO, capture_nr) < 0) { printk(KERN_ERR "v4l2loopback: failed video_register_device()\n"); err = -EFAULT; goto out_free_device; } - v4l2loopback_create_sysfs(dev->vdev); + v4l2loopback_create_sysfs(vdev); MARK(); if (ret_nr) - *ret_nr = dev->vdev->num; + *ret_nr = vdev->num; return 0; out_free_device: - video_device_release(dev->vdev); + video_device_release(vdev); out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); out_unregister: @@ -2434,10 +2435,12 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) { + struct video_device *vdev = dev->vdev; + free_buffers(dev); - v4l2loopback_remove_sysfs(dev->vdev); - kfree(video_get_drvdata(dev->vdev)); - video_unregister_device(dev->vdev); + v4l2loopback_remove_sysfs(vdev); + kfree(video_get_drvdata(vdev)); + video_unregister_device(vdev); v4l2_device_unregister(&dev->v4l2_dev); v4l2_ctrl_handler_free(&dev->ctrl_handler); kfree(dev); From 6c6a6c1fdd1790f47e93eb0edc1166190a3ebd3e Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 23:04:52 +0800 Subject: [PATCH 10/26] embed struct video_device into v4l2_loopback_device Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index a5cab421..9cc2176f 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -338,7 +338,7 @@ struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; int output_nr; - struct video_device *vdev; + struct video_device vdev; /* pixel and stream format */ struct v4l2_pix_format pix_format; struct v4l2_captureparm capture_param; @@ -635,9 +635,9 @@ static int v4l2loopback_lookup_cb(int id, void *ptr, void *data) { struct v4l2_loopback_device *device = ptr; struct v4l2loopback_lookup_cb_data *cbdata = data; - if (cbdata && device && device->vdev) { + if (cbdata && device) { if (device->output_nr == cbdata->device_nr || - device->vdev->num == cbdata->device_nr) { + device->vdev.num == cbdata->device_nr) { cbdata->device = device; return 1; } @@ -716,7 +716,7 @@ static int vidioc_querycap(struct file *file, void *priv, sizeof(cap->card) : sizeof(dev->card_label); int device_nr = - ((struct v4l2loopback_private *)video_get_drvdata(dev->vdev)) + ((struct v4l2loopback_private *)video_get_drvdata(&dev->vdev)) ->device_nr; __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; @@ -742,7 +742,7 @@ static int vidioc_querycap(struct file *file, void *priv, } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) - dev->vdev->device_caps = + dev->vdev.device_caps = #endif /* >=linux-4.7.0 */ cap->device_caps = cap->capabilities = capabilities; @@ -2147,7 +2147,7 @@ static void init_vdev(struct video_device *vdev) vdev->vfl_type = VFL_TYPE_VIDEO; vdev->fops = &v4l2_loopback_fops; vdev->ioctl_ops = &v4l2_loopback_ioctl_ops; - vdev->release = &video_device_release; + vdev->release = &video_device_release_empty; vdev->minor = -1; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | @@ -2308,12 +2308,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) goto out_free_idr; MARK(); - vdev = video_device_alloc(); - if (vdev == NULL) { - err = -ENOMEM; - goto out_unregister; - } - dev->vdev = vdev; + vdev = &dev->vdev; video_set_drvdata(vdev, kzalloc(sizeof(struct v4l2loopback_private), GFP_KERNEL)); if (video_get_drvdata(vdev) == NULL) { @@ -2375,7 +2370,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) hdl = &dev->ctrl_handler; err = v4l2_ctrl_handler_init(hdl, 4); if (err) - goto out_unregister; + goto out_free_device; v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_keepformat, NULL); v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_sustainframerate, NULL); v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_timeout, NULL); @@ -2410,7 +2405,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) printk(KERN_ERR "v4l2loopback: failed video_register_device()\n"); err = -EFAULT; - goto out_free_device; + goto out_free_handler; } v4l2loopback_create_sysfs(vdev); @@ -2419,10 +2414,11 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) *ret_nr = vdev->num; return 0; -out_free_device: - video_device_release(vdev); out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); +out_free_device: + kfree(video_get_drvdata(vdev)); + video_device_release_empty(vdev); out_unregister: v4l2_device_unregister(&dev->v4l2_dev); out_free_idr: @@ -2435,12 +2431,13 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) { - struct video_device *vdev = dev->vdev; + struct video_device *vdev = &dev->vdev; free_buffers(dev); v4l2loopback_remove_sysfs(vdev); kfree(video_get_drvdata(vdev)); video_unregister_device(vdev); + video_device_release_empty(vdev); v4l2_device_unregister(&dev->v4l2_dev); v4l2_ctrl_handler_free(&dev->ctrl_handler); kfree(dev); @@ -2485,7 +2482,7 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, ret = -EBUSY; else { idr_remove(&v4l2loopback_index_idr, dev->output_nr); - idr_remove(&v4l2loopback_index_idr, dev->vdev->num); + idr_remove(&v4l2loopback_index_idr, dev->vdev.num); v4l2_loopback_remove(dev); ret = 0; }; @@ -2519,7 +2516,7 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, dev->card_label); MARK(); conf.output_nr = dev->output_nr; - conf.capture_nr = dev->vdev->num; + conf.capture_nr = dev->vdev.num; conf.max_width = dev->max_width; conf.max_height = dev->max_height; conf.announce_all_caps = dev->announce_all_caps; From c5807374ed903474de48426f7a1a3d16e397a5a3 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 23:04:52 +0800 Subject: [PATCH 11/26] set v4l2_loopback_device addr as video_device drvdata instead Converting function v4l2loopback_cd2dev and v4l2loopback_getdevice searches v4l2loopback_index_idr with device number retrieved from video_device drvdata, but this could be time consuming and creates unecessary complexity. And since we may need to retrieve v4l2_loopback_device address from callbacks shared between capture and output devices, this patch reuses video_device drvdata instead of struct member offsets. Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 116 +++++++++++++++++-------------------------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 9cc2176f..c85d0b03 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -320,9 +320,6 @@ static const struct v4l2_ctrl_config v4l2loopback_ctrl_timeoutimageio = { }; /* module structures */ -struct v4l2loopback_private { - int device_nr; -}; /* TODO(vasaka) use typenames which are common to kernel, but first find out if * it is needed */ @@ -399,6 +396,9 @@ struct v4l2_loopback_device { spinlock_t lock; }; +#define cd_to_loopdev(ptr) video_get_drvdata(to_video_device((ptr))) +#define file_to_loopdev(ptr) video_get_drvdata(video_devdata((ptr))) + /* types of opener shows what opener wants to do with loopback */ enum opener_type { // clang-format off @@ -495,8 +495,6 @@ static int set_timeperframe(struct v4l2_loopback_device *dev, return 0; } -static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd); - /* device attributes */ /* available via sysfs: /sys/devices/virtual/video4linux/video* */ @@ -504,7 +502,7 @@ static ssize_t attr_show_format(struct device *cd, struct device_attribute *attr, char *buf) { /* gets the current format as "FOURCC:WxH@f/s", e.g. "YUYV:320x240@1000/30" */ - struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + struct v4l2_loopback_device *dev = cd_to_loopdev(cd); const struct v4l2_fract *tpf; char buf4cc[5], buf_fps[32]; @@ -527,7 +525,7 @@ static ssize_t attr_store_format(struct device *cd, struct device_attribute *attr, const char *buf, size_t len) { - struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + struct v4l2_loopback_device *dev = cd_to_loopdev(cd); int fps_num = 0, fps_den = 1; /* only fps changing is supported */ @@ -548,7 +546,7 @@ static DEVICE_ATTR(format, S_IRUGO | S_IWUSR, attr_show_format, static ssize_t attr_show_buffers(struct device *cd, struct device_attribute *attr, char *buf) { - struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + struct v4l2_loopback_device *dev = cd_to_loopdev(cd); return sprintf(buf, "%d\n", dev->used_buffers); } @@ -558,7 +556,7 @@ static DEVICE_ATTR(buffers, S_IRUGO, attr_show_buffers, NULL); static ssize_t attr_show_maxopeners(struct device *cd, struct device_attribute *attr, char *buf) { - struct v4l2_loopback_device *dev = v4l2loopback_cd2dev(cd); + struct v4l2_loopback_device *dev = cd_to_loopdev(cd); return sprintf(buf, "%d\n", dev->max_openers); } @@ -573,7 +571,7 @@ static ssize_t attr_store_maxopeners(struct device *cd, if (kstrtoul(buf, 0, &curr)) return -EINVAL; - dev = v4l2loopback_cd2dev(cd); + dev = cd_to_loopdev(cd); if (dev->max_openers == curr) return len; @@ -654,25 +652,6 @@ static struct v4l2_loopback_device *v4l2loopback_lookup(int device_nr) &data); return 1 == err ? data.device : NULL; } -static struct v4l2_loopback_device *v4l2loopback_cd2dev(struct device *cd) -{ - struct video_device *loopdev = to_video_device(cd); - struct v4l2loopback_private *ptr = - (struct v4l2loopback_private *)video_get_drvdata(loopdev); - int nr = ptr->device_nr; - - return idr_find(&v4l2loopback_index_idr, nr); -} - -static struct v4l2_loopback_device *v4l2loopback_getdevice(struct file *f) -{ - struct video_device *loopdev = video_devdata(f); - struct v4l2loopback_private *ptr = - (struct v4l2loopback_private *)video_get_drvdata(loopdev); - int nr = ptr->device_nr; - - return idr_find(&v4l2loopback_index_idr, nr); -} /* forward declarations */ static void init_buffers(struct v4l2_loopback_device *dev); @@ -711,19 +690,16 @@ static inline void unset_flags(struct v4l2l_buffer *buffer) static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); int labellen = (sizeof(cap->card) < sizeof(dev->card_label)) ? sizeof(cap->card) : sizeof(dev->card_label); - int device_nr = - ((struct v4l2loopback_private *)video_get_drvdata(&dev->vdev)) - ->device_nr; __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; strlcpy(cap->driver, "v4l2 loopback", sizeof(cap->driver)); snprintf(cap->card, labellen, dev->card_label); snprintf(cap->bus_info, sizeof(cap->bus_info), - "platform:v4l2loopback-%03d", device_nr); + "platform:v4l2loopback-%03d", dev->vdev.num); #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) /* since 3.1.0, the v4l2-core system is supposed to set the version */ @@ -763,7 +739,7 @@ static int vidioc_enum_framesizes(struct file *file, void *fh, if (argp->index) return -EINVAL; - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (dev->ready_for_capture) { /* format has already been negotiated * cannot change during runtime @@ -801,7 +777,7 @@ static int vidioc_enum_framesizes(struct file *file, void *fh, static int vidioc_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *argp) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); /* there can be only one... */ if (argp->index) @@ -846,7 +822,7 @@ static int vidioc_enum_fmt_cap(struct file *file, void *fh, struct v4l2_loopback_device *dev; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (f->index) return -EINVAL; @@ -875,7 +851,7 @@ static int vidioc_g_fmt_cap(struct file *file, void *priv, struct v4l2_loopback_device *dev; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (!dev->ready_for_capture) return -EINVAL; @@ -897,7 +873,7 @@ static int vidioc_try_fmt_cap(struct file *file, void *priv, struct v4l2_loopback_device *dev; char buf[5]; - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (0 == dev->ready_for_capture) { dprintk("setting fmt_cap not possible yet\n"); @@ -937,7 +913,7 @@ static int vidioc_enum_fmt_out(struct file *file, void *fh, struct v4l2_loopback_device *dev; const struct v4l2l_format *fmt; - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (dev->ready_for_capture) { const __u32 format = dev->pix_format.pixelformat; @@ -984,7 +960,7 @@ static int vidioc_g_fmt_out(struct file *file, void *priv, struct v4l2_loopback_device *dev; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); /* * LATER: this should return the currently valid format @@ -1008,7 +984,7 @@ static int vidioc_try_fmt_out(struct file *file, void *priv, struct v4l2_loopback_device *dev; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); /* TODO(vasaka) loopback does not care about formats writer want to set, * maybe it is a good idea to restrict format somehow */ @@ -1063,7 +1039,7 @@ static int vidioc_s_fmt_out(struct file *file, void *priv, int ret; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); ret = vidioc_try_fmt_out(file, priv, fmt); dprintk("s_fmt_out(%d) %d...%d\n", ret, dev->ready_for_capture, @@ -1119,7 +1095,7 @@ static int vidioc_g_parm(struct file *file, void *priv, struct v4l2_loopback_device *dev; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); parm->parm.capture = dev->capture_param; return 0; } @@ -1135,7 +1111,7 @@ static int vidioc_s_parm(struct file *file, void *priv, int err = 0; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); dprintk("vidioc_s_parm called frate=%d/%d\n", parm->parm.capture.timeperframe.numerator, parm->parm.capture.timeperframe.denominator); @@ -1255,7 +1231,7 @@ static int vidioc_enum_output(struct file *file, void *fh, struct v4l2_output *outp) { __u32 index = outp->index; - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); MARK(); if (!dev->announce_all_caps && !dev->ready_for_output) @@ -1287,7 +1263,7 @@ static int vidioc_enum_output(struct file *file, void *fh, */ static int vidioc_g_output(struct file *file, void *fh, unsigned int *i) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); if (!dev->announce_all_caps && !dev->ready_for_output) return -ENOTTY; if (i) @@ -1300,7 +1276,7 @@ static int vidioc_g_output(struct file *file, void *fh, unsigned int *i) */ static int vidioc_s_output(struct file *file, void *fh, unsigned int i) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); if (!dev->announce_all_caps && !dev->ready_for_output) return -ENOTTY; @@ -1348,7 +1324,7 @@ static int vidioc_enum_input(struct file *file, void *fh, */ static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); if (!dev->announce_all_caps && !dev->ready_for_capture) return -ENOTTY; if (i) @@ -1361,7 +1337,7 @@ static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) */ static int vidioc_s_input(struct file *file, void *fh, unsigned int i) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); if (!dev->announce_all_caps && !dev->ready_for_capture) return -ENOTTY; if (i == 0) @@ -1383,7 +1359,7 @@ static int vidioc_reqbufs(struct file *file, void *fh, int i; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); opener = fh_to_opener(fh); dprintk("reqbufs: %d\t%d=%d\n", b->memory, b->count, @@ -1461,7 +1437,7 @@ static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) type = b->type; index = b->index; - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); opener = fh_to_opener(fh); if ((b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && @@ -1516,7 +1492,7 @@ static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) struct v4l2l_buffer *b; int index; - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); opener = fh_to_opener(fh); if (buf->index > max_buffers) @@ -1570,7 +1546,7 @@ static int can_read(struct v4l2_loopback_device *dev, static int get_capture_buffer(struct file *file) { - struct v4l2_loopback_device *dev = v4l2loopback_getdevice(file); + struct v4l2_loopback_device *dev = file_to_loopdev(file); struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data); int pos, ret; int timeout_happened; @@ -1622,7 +1598,7 @@ static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) int index; struct v4l2l_buffer *b; - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); opener = fh_to_opener(fh); if (opener->timeout_image_io) { *buf = dev->timeout_image_buffer.buffer; @@ -1669,7 +1645,7 @@ static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type type) struct v4l2_loopback_opener *opener; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); opener = fh_to_opener(fh); switch (type) { @@ -1704,7 +1680,7 @@ static int vidioc_streamoff(struct file *file, void *fh, MARK(); dprintk("%d\n", type); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); switch (type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT: @@ -1767,7 +1743,7 @@ static int v4l2_loopback_mmap(struct file *file, struct vm_area_struct *vma) start = (unsigned long)vma->vm_start; size = (unsigned long)(vma->vm_end - vma->vm_start); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); opener = fh_to_opener(file->private_data); if (size > dev->buffer_size) { @@ -1844,7 +1820,7 @@ static unsigned int v4l2_loopback_poll(struct file *file, MARK(); opener = fh_to_opener(file->private_data); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (req_events & POLLPRI) { if (!v4l2_event_pending(&opener->fh)) @@ -1886,7 +1862,7 @@ static int v4l2_loopback_open(struct file *file) struct v4l2_loopback_device *dev; struct v4l2_loopback_opener *opener; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (dev->open_count.counter >= dev->max_openers) return -EBUSY; /* kfree on close */ @@ -1924,7 +1900,7 @@ static int v4l2_loopback_close(struct file *file) MARK(); opener = fh_to_opener(file->private_data); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); if (WRITER == opener->type) iswriter = 1; @@ -1955,7 +1931,7 @@ static ssize_t v4l2_loopback_read(struct file *file, char __user *buf, struct v4l2_buffer *b; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); read_index = get_capture_buffer(file); if (read_index < 0) @@ -1983,7 +1959,7 @@ static ssize_t v4l2_loopback_write(struct file *file, const char __user *buf, struct v4l2_buffer *b; MARK(); - dev = v4l2loopback_getdevice(file); + dev = file_to_loopdev(file); /* there's at least one writer, so don't stop announcing output capabilities */ dev->ready_for_output = 0; @@ -2309,19 +2285,9 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) MARK(); vdev = &dev->vdev; - video_set_drvdata(vdev, kzalloc(sizeof(struct v4l2loopback_private), - GFP_KERNEL)); - if (video_get_drvdata(vdev) == NULL) { - err = -ENOMEM; - goto out_unregister; - } - - MARK(); + video_set_drvdata(vdev, dev); snprintf(vdev->name, sizeof(vdev->name), dev->card_label); - ((struct v4l2loopback_private *)video_get_drvdata(vdev))->device_nr = - capture_nr; - init_vdev(vdev); vdev->v4l2_dev = &dev->v4l2_dev; init_capture_param(&dev->capture_param); @@ -2417,7 +2383,6 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); out_free_device: - kfree(video_get_drvdata(vdev)); video_device_release_empty(vdev); out_unregister: v4l2_device_unregister(&dev->v4l2_dev); @@ -2435,7 +2400,6 @@ static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) free_buffers(dev); v4l2loopback_remove_sysfs(vdev); - kfree(video_get_drvdata(vdev)); video_unregister_device(vdev); video_device_release_empty(vdev); v4l2_device_unregister(&dev->v4l2_dev); From fd056bde56a96f6695a9d7f43a37ed4330187066 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Thu, 15 Oct 2020 16:59:01 +0800 Subject: [PATCH 12/26] move v4l2_loopback_device->vdev into v4l2_loopback_device->capture Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index c85d0b03..aad294cc 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -335,7 +335,9 @@ struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; int output_nr; - struct video_device vdev; + struct { + struct video_device vdev; + } capture; /* pixel and stream format */ struct v4l2_pix_format pix_format; struct v4l2_captureparm capture_param; @@ -635,7 +637,7 @@ static int v4l2loopback_lookup_cb(int id, void *ptr, void *data) struct v4l2loopback_lookup_cb_data *cbdata = data; if (cbdata && device) { if (device->output_nr == cbdata->device_nr || - device->vdev.num == cbdata->device_nr) { + device->capture.vdev.num == cbdata->device_nr) { cbdata->device = device; return 1; } @@ -699,7 +701,7 @@ static int vidioc_querycap(struct file *file, void *priv, strlcpy(cap->driver, "v4l2 loopback", sizeof(cap->driver)); snprintf(cap->card, labellen, dev->card_label); snprintf(cap->bus_info, sizeof(cap->bus_info), - "platform:v4l2loopback-%03d", dev->vdev.num); + "platform:v4l2loopback-%03d", dev->capture.vdev.num); #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 1, 0) /* since 3.1.0, the v4l2-core system is supposed to set the version */ @@ -718,7 +720,7 @@ static int vidioc_querycap(struct file *file, void *priv, } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) - dev->vdev.device_caps = + dev->capture.vdev.device_caps = #endif /* >=linux-4.7.0 */ cap->device_caps = cap->capabilities = capabilities; @@ -2284,7 +2286,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) goto out_free_idr; MARK(); - vdev = &dev->vdev; + vdev = &dev->capture.vdev; video_set_drvdata(vdev, dev); snprintf(vdev->name, sizeof(vdev->name), dev->card_label); @@ -2384,7 +2386,6 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) v4l2_ctrl_handler_free(&dev->ctrl_handler); out_free_device: video_device_release_empty(vdev); -out_unregister: v4l2_device_unregister(&dev->v4l2_dev); out_free_idr: idr_remove(&v4l2loopback_index_idr, output_nr); @@ -2396,7 +2397,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) { - struct video_device *vdev = &dev->vdev; + struct video_device *vdev = &dev->capture.vdev; free_buffers(dev); v4l2loopback_remove_sysfs(vdev); @@ -2446,7 +2447,8 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, ret = -EBUSY; else { idr_remove(&v4l2loopback_index_idr, dev->output_nr); - idr_remove(&v4l2loopback_index_idr, dev->vdev.num); + idr_remove(&v4l2loopback_index_idr, + dev->capture.vdev.num); v4l2_loopback_remove(dev); ret = 0; }; @@ -2480,7 +2482,7 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, dev->card_label); MARK(); conf.output_nr = dev->output_nr; - conf.capture_nr = dev->vdev.num; + conf.capture_nr = dev->capture.vdev.num; conf.max_width = dev->max_width; conf.max_height = dev->max_height; conf.announce_all_caps = dev->announce_all_caps; From 26705099137c486c340350fdab32685e07bfb56f Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 11:57:51 +0800 Subject: [PATCH 13/26] move all vdev init procedures into init_vdev Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index aad294cc..449fa5b2 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -2114,9 +2114,12 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) } /* fills and register video device */ -static void init_vdev(struct video_device *vdev) +static int init_vdev(struct video_device *vdev, int nr, + struct v4l2_loopback_device *dev) { - MARK(); + snprintf(vdev->name, sizeof(vdev->name), dev->card_label); + vdev->v4l2_dev = &dev->v4l2_dev; + video_set_drvdata(vdev, dev); #ifdef V4L2LOOPBACK_WITH_STD vdev->tvnorms = V4L2_STD_ALL; @@ -2147,6 +2150,17 @@ static void init_vdev(struct video_device *vdev) #endif MARK(); + + /* register the device -> it creates /dev/video* */ + if (video_register_device(vdev, VFL_TYPE_VIDEO, nr) < 0) { + printk(KERN_ERR + "v4l2loopback: failed video_register_device()\n"); + return -EFAULT; + } + + v4l2loopback_create_sysfs(vdev); + + return 0; } /* init default capture parameters, only fps may be changed in future */ @@ -2286,12 +2300,6 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) goto out_free_idr; MARK(); - vdev = &dev->capture.vdev; - video_set_drvdata(vdev, dev); - snprintf(vdev->name, sizeof(vdev->name), dev->card_label); - - init_vdev(vdev); - vdev->v4l2_dev = &dev->v4l2_dev; init_capture_param(&dev->capture_param); set_timeperframe(dev, &dev->capture_param.timeperframe); dev->keep_format = 0; @@ -2338,7 +2346,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) hdl = &dev->ctrl_handler; err = v4l2_ctrl_handler_init(hdl, 4); if (err) - goto out_free_device; + goto out_free_idr; v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_keepformat, NULL); v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_sustainframerate, NULL); v4l2_ctrl_new_custom(hdl, &v4l2loopback_ctrl_timeout, NULL); @@ -2368,14 +2376,11 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) init_waitqueue_head(&dev->read_event); - /* register the device -> it creates /dev/video* */ - if (video_register_device(vdev, VFL_TYPE_VIDEO, capture_nr) < 0) { - printk(KERN_ERR - "v4l2loopback: failed video_register_device()\n"); - err = -EFAULT; + MARK(); + + vdev = &dev->capture.vdev; + if (init_vdev(vdev, capture_nr, dev)) goto out_free_handler; - } - v4l2loopback_create_sysfs(vdev); MARK(); if (ret_nr) @@ -2384,9 +2389,6 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); -out_free_device: - video_device_release_empty(vdev); - v4l2_device_unregister(&dev->v4l2_dev); out_free_idr: idr_remove(&v4l2loopback_index_idr, output_nr); idr_remove(&v4l2loopback_index_idr, capture_nr); From 68616dd9e3b9a1679884214e6a8d6e3b1eeb9fe9 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 18:07:43 +0800 Subject: [PATCH 14/26] fix cap->device_caps may not be defined before v3.3 Also use V4L2_CAP_DEVICE_CAPS to detect this instead of hardcoded kernel version. Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 449fa5b2..6ba5ccce 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -719,12 +719,12 @@ static int vidioc_querycap(struct file *file, void *priv, } } + cap->capabilities = capabilities; +#if defined(V4L2_CAP_DEVICE_CAPS) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) dev->capture.vdev.device_caps = #endif /* >=linux-4.7.0 */ - cap->device_caps = cap->capabilities = capabilities; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0) + cap->device_caps = capabilities; cap->capabilities |= V4L2_CAP_DEVICE_CAPS; #endif From eeb0625bb3a922f4e92e8fd82858797c22778da3 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 19:04:26 +0800 Subject: [PATCH 15/26] don't change device state in vidioc_s_fmt_out Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 6ba5ccce..86aa61bc 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -1021,9 +1021,6 @@ static int vidioc_try_fmt_out(struct file *file, void *priv, if (V4L2_FIELD_ANY == fmt->fmt.pix.field) fmt->fmt.pix.field = V4L2_FIELD_NONE; - - /* FIXXME: try_fmt should never modify the device-state */ - dev->pix_format = fmt->fmt.pix; } return 0; } @@ -1045,14 +1042,15 @@ static int vidioc_s_fmt_out(struct file *file, void *priv, ret = vidioc_try_fmt_out(file, priv, fmt); dprintk("s_fmt_out(%d) %d...%d\n", ret, dev->ready_for_capture, - dev->pix_format.sizeimage); + fmt->fmt.pix.sizeimage); buf[4] = 0; - dprintk("outFOURCC=%s\n", fourcc2str(dev->pix_format.pixelformat, buf)); + dprintk("outFOURCC=%s\n", fourcc2str(fmt->fmt.pix.pixelformat, buf)); if (ret < 0) return ret; + dev->pix_format = fmt->fmt.pix; if (!dev->ready_for_capture) { dev->buffer_size = PAGE_ALIGN(dev->pix_format.sizeimage); fmt->fmt.pix.sizeimage = dev->buffer_size; From 50e2fcf78f17e40517b17b49a3e01f72953ec383 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 23:12:22 +0800 Subject: [PATCH 16/26] create a draft output video_device Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 76 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 86aa61bc..84cdd898 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -337,7 +337,7 @@ struct v4l2_loopback_device { int output_nr; struct { struct video_device vdev; - } capture; + } capture, output; /* pixel and stream format */ struct v4l2_pix_format pix_format; struct v4l2_captureparm capture_param; @@ -662,6 +662,8 @@ static int free_buffers(struct v4l2_loopback_device *dev); static void try_free_buffers(struct v4l2_loopback_device *dev); static int allocate_timeout_image(struct v4l2_loopback_device *dev); static void check_timers(struct v4l2_loopback_device *dev); +static const struct v4l2_file_operations output_fops; +static const struct v4l2_ioctl_ops output_ioctl_ops; static const struct v4l2_file_operations v4l2_loopback_fops; static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops; @@ -2112,9 +2114,11 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) } /* fills and register video device */ -static int init_vdev(struct video_device *vdev, int nr, +static int init_vdev(struct video_device *vdev, int nr, int type, struct v4l2_loopback_device *dev) { + int is_output = (type == V4L2_CAP_VIDEO_OUTPUT) ? 1 : 0; + snprintf(vdev->name, sizeof(vdev->name), dev->card_label); vdev->v4l2_dev = &dev->v4l2_dev; video_set_drvdata(vdev, dev); @@ -2124,13 +2128,17 @@ static int init_vdev(struct video_device *vdev, int nr, #endif /* V4L2LOOPBACK_WITH_STD */ vdev->vfl_type = VFL_TYPE_VIDEO; - vdev->fops = &v4l2_loopback_fops; - vdev->ioctl_ops = &v4l2_loopback_ioctl_ops; + if (is_output) { + vdev->fops = &output_fops; + vdev->ioctl_ops = &output_ioctl_ops; + } else { + vdev->fops = &v4l2_loopback_fops; + vdev->ioctl_ops = &v4l2_loopback_ioctl_ops; + } vdev->release = &video_device_release_empty; vdev->minor = -1; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) - vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | - V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + vdev->device_caps = type | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; #endif /* >=linux-4.7.0 */ if (debug > 1) @@ -2144,7 +2152,10 @@ static int init_vdev(struct video_device *vdev, int nr, /* since kernel-3.7, there is a new field 'vfl_dir' that has to be * set to VFL_DIR_M2M for bidirectional devices */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) - vdev->vfl_dir = VFL_DIR_M2M; + if (is_output) + vdev->vfl_dir = VFL_DIR_TX; + else + vdev->vfl_dir = VFL_DIR_M2M; #endif MARK(); @@ -2161,6 +2172,18 @@ static int init_vdev(struct video_device *vdev, int nr, return 0; } +static int init_output_vdev(struct video_device *vdev, int output_nr, + struct v4l2_loopback_device *dev) +{ + return init_vdev(vdev, output_nr, V4L2_CAP_VIDEO_OUTPUT, dev); +} + +static int init_capture_vdev(struct video_device *vdev, int capture_nr, + struct v4l2_loopback_device *dev) +{ + return init_vdev(vdev, capture_nr, V4L2_CAP_VIDEO_CAPTURE, dev); +} + /* init default capture parameters, only fps may be changed in future */ static void init_capture_param(struct v4l2_captureparm *capture_param) { @@ -2239,7 +2262,7 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) { struct v4l2_loopback_device *dev; struct v4l2_ctrl_handler *hdl; - struct video_device *vdev; + struct video_device *capture_vdev, *output_vdev; int err = -ENOMEM; @@ -2376,15 +2399,23 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) MARK(); - vdev = &dev->capture.vdev; - if (init_vdev(vdev, capture_nr, dev)) + output_vdev = &dev->output.vdev; + if (init_output_vdev(output_vdev, output_nr, dev)) goto out_free_handler; + MARK(); + + capture_vdev = &dev->capture.vdev; + if (init_capture_vdev(capture_vdev, capture_nr, dev)) + goto out_unregister_output_vdev; + MARK(); if (ret_nr) - *ret_nr = vdev->num; + *ret_nr = capture_vdev->num; return 0; +out_unregister_output_vdev: + video_unregister_device(output_vdev); out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); out_free_idr: @@ -2397,12 +2428,19 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) static void v4l2_loopback_remove(struct v4l2_loopback_device *dev) { - struct video_device *vdev = &dev->capture.vdev; + struct video_device *output_vdev = &dev->output.vdev; + struct video_device *capture_vdev = &dev->capture.vdev; free_buffers(dev); - v4l2loopback_remove_sysfs(vdev); - video_unregister_device(vdev); - video_device_release_empty(vdev); + + v4l2loopback_remove_sysfs(output_vdev); + video_unregister_device(output_vdev); + video_device_release_empty(output_vdev); + + v4l2loopback_remove_sysfs(capture_vdev); + video_unregister_device(capture_vdev); + video_device_release_empty(capture_vdev); + v4l2_device_unregister(&dev->v4l2_dev); v4l2_ctrl_handler_free(&dev->ctrl_handler); kfree(dev); @@ -2526,6 +2564,14 @@ static struct miscdevice v4l2loopback_misc = { // clang-format on }; +static const struct v4l2_file_operations output_fops = { + // clang-format off + .owner = THIS_MODULE, + // clang-format on +}; + +static const struct v4l2_ioctl_ops output_ioctl_ops = {}; + static const struct v4l2_file_operations v4l2_loopback_fops = { // clang-format off .owner = THIS_MODULE, From 210e307ca4f80d65e9bd96327f66684d727c9469 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 20:53:23 +0800 Subject: [PATCH 17/26] remove dev->output_nr Since we have already instantiated an output video device, use its `num` field instead. Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 84cdd898..7077f424 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -334,7 +334,6 @@ struct v4l2l_buffer { struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; - int output_nr; struct { struct video_device vdev; } capture, output; @@ -636,7 +635,7 @@ static int v4l2loopback_lookup_cb(int id, void *ptr, void *data) struct v4l2_loopback_device *device = ptr; struct v4l2loopback_lookup_cb_data *cbdata = data; if (cbdata && device) { - if (device->output_nr == cbdata->device_nr || + if (device->output.vdev.num == cbdata->device_nr || device->capture.vdev.num == cbdata->device_nr) { cbdata->device = device; return 1; @@ -2303,9 +2302,6 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) dprintk("creating v4l2loopback-device %d:%d\n", output_nr, capture_nr); - /* Save output_nr somewhere before spliting device is supported. */ - dev->output_nr = output_nr; - if (conf && conf->card_label && *(conf->card_label)) { snprintf(dev->card_label, sizeof(dev->card_label), "%s", conf->card_label); @@ -2484,7 +2480,8 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, else if (dev->open_count.counter > 0) ret = -EBUSY; else { - idr_remove(&v4l2loopback_index_idr, dev->output_nr); + idr_remove(&v4l2loopback_index_idr, + dev->output.vdev.num); idr_remove(&v4l2loopback_index_idr, dev->capture.vdev.num); v4l2_loopback_remove(dev); @@ -2519,7 +2516,7 @@ static long v4l2loopback_control_ioctl(struct file *file, unsigned int cmd, snprintf(conf.card_label, sizeof(conf.card_label), "%s", dev->card_label); MARK(); - conf.output_nr = dev->output_nr; + conf.output_nr = dev->output.vdev.num; conf.capture_nr = dev->capture.vdev.num; conf.max_width = dev->max_width; conf.max_height = dev->max_height; @@ -2644,7 +2641,7 @@ static int free_device_cb(int id, void *ptr, void *data) * so here we only have to deal with fully instanciated devices. In * order to avoid double free, free only when id matches its output_nr. */ - if (id == dev->output_nr) + if (id == dev->output.vdev.num) v4l2_loopback_remove(dev); return 0; From babaffebf2a9beca06ba20f97cb82a77a55219c1 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 23:20:52 +0800 Subject: [PATCH 18/26] output: add ioctl handlers Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 197 ++++++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 94 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 7077f424..1222c807 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -693,11 +693,16 @@ static inline void unset_flags(struct v4l2l_buffer *buffer) static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { - struct v4l2_loopback_device *dev = file_to_loopdev(file); + struct video_device *vdev = video_devdata(file); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + int is_output = (vdev == &dev->output.vdev) ? 1 : 0; int labellen = (sizeof(cap->card) < sizeof(dev->card_label)) ? sizeof(cap->card) : sizeof(dev->card_label); __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; +#if defined(V4L2_CAP_DEVICE_CAPS) + __u32 device_caps; +#endif strlcpy(cap->driver, "v4l2 loopback", sizeof(cap->driver)); snprintf(cap->card, labellen, dev->card_label); @@ -709,23 +714,44 @@ static int vidioc_querycap(struct file *file, void *priv, cap->version = V4L2LOOPBACK_VERSION_CODE; #endif - if (dev->announce_all_caps) { + if (is_output) { + /* If this is that splited output device, it will always be an + * output device. No more trick here. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + capabilities = dev->output.vdev.device_caps | + dev->capture.vdev.device_caps; + device_caps = vdev->device_caps; +#else capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; +#if defined(V4L2_CAP_DEVICE_CAPS) + device_caps = capabilities & (~V4L2_CAP_VIDEO_CAPTURE); +#endif +#endif } else { - if (dev->ready_for_capture) { - capabilities |= V4L2_CAP_VIDEO_CAPTURE; - } - if (dev->ready_for_output) { - capabilities |= V4L2_CAP_VIDEO_OUTPUT; + /* So this is the capture device that currently serves both + * roles depending on ready_for_capture/ready_for_output and + * announce_all_caps. */ + if (dev->announce_all_caps) { + capabilities |= + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + } else { + if (dev->ready_for_capture) + capabilities |= V4L2_CAP_VIDEO_CAPTURE; + if (dev->ready_for_output) + capabilities |= V4L2_CAP_VIDEO_OUTPUT; } +#if defined(V4L2_CAP_DEVICE_CAPS) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + dev->capture.vdev.device_caps = +#endif + device_caps = capabilities; +#endif } cap->capabilities = capabilities; #if defined(V4L2_CAP_DEVICE_CAPS) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) - dev->capture.vdev.device_caps = -#endif /* >=linux-4.7.0 */ - cap->device_caps = capabilities; + cap->device_caps = device_caps; cap->capabilities |= V4L2_CAP_DEVICE_CAPS; #endif @@ -913,39 +939,14 @@ static int vidioc_s_fmt_cap(struct file *file, void *priv, static int vidioc_enum_fmt_out(struct file *file, void *fh, struct v4l2_fmtdesc *f) { - struct v4l2_loopback_device *dev; const struct v4l2l_format *fmt; - dev = file_to_loopdev(file); - - if (dev->ready_for_capture) { - const __u32 format = dev->pix_format.pixelformat; - - /* format has been fixed by the writer, so only one single format is supported */ - if (f->index) - return -EINVAL; - - fmt = format_by_fourcc(format); - if (NULL == fmt) - return -EINVAL; - - /* f->flags = ??; */ - snprintf(f->description, sizeof(f->description), "%s", - fmt->name); - - f->pixelformat = dev->pix_format.pixelformat; - } else { - /* fill in a dummy format */ - /* coverity[unsigned_compare] */ - if (f->index < 0 || f->index >= FORMATS) - return -EINVAL; - - fmt = &formats[f->index]; + if (f->index < 0 || f->index >= FORMATS) + return -EINVAL; - f->pixelformat = fmt->fourcc; - snprintf(f->description, sizeof(f->description), "%s", - fmt->name); - } + fmt = &formats[f->index]; + f->pixelformat = fmt->fourcc; + snprintf(f->description, sizeof(f->description), "%s", fmt->name); f->flags = 0; return 0; @@ -984,45 +985,39 @@ static int vidioc_g_fmt_out(struct file *file, void *priv, static int vidioc_try_fmt_out(struct file *file, void *priv, struct v4l2_format *fmt) { - struct v4l2_loopback_device *dev; + struct video_device *vdev = video_devdata(file); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + __u32 w = fmt->fmt.pix.width; + __u32 h = fmt->fmt.pix.height; + __u32 pixfmt = fmt->fmt.pix.pixelformat; + const struct v4l2l_format *format; MARK(); - dev = file_to_loopdev(file); + if (w > dev->max_width) + w = dev->max_width; + if (h > dev->max_height) + h = dev->max_height; - /* TODO(vasaka) loopback does not care about formats writer want to set, - * maybe it is a good idea to restrict format somehow */ - if (dev->ready_for_capture) { - fmt->fmt.pix = dev->pix_format; - } else { - __u32 w = fmt->fmt.pix.width; - __u32 h = fmt->fmt.pix.height; - __u32 pixfmt = fmt->fmt.pix.pixelformat; - const struct v4l2l_format *format = format_by_fourcc(pixfmt); - - if (w > dev->max_width) - w = dev->max_width; - if (h > dev->max_height) - h = dev->max_height; + dprintk("trying image %dx%d\n", w, h); - dprintk("trying image %dx%d\n", w, h); + if (w < 1) + w = V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; - if (w < 1) - w = V4L2LOOPBACK_SIZE_DEFAULT_WIDTH; + if (h < 1) + h = V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; - if (h < 1) - h = V4L2LOOPBACK_SIZE_DEFAULT_HEIGHT; + format = format_by_fourcc(pixfmt); + if (NULL == format) + format = &formats[0]; - if (NULL == format) - format = &formats[0]; + pix_format_set_size(&fmt->fmt.pix, format, w, h); - pix_format_set_size(&fmt->fmt.pix, format, w, h); + fmt->fmt.pix.pixelformat = format->fourcc; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; - fmt->fmt.pix.pixelformat = format->fourcc; - fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + if (V4L2_FIELD_ANY == fmt->fmt.pix.field) + fmt->fmt.pix.field = V4L2_FIELD_NONE; - if (V4L2_FIELD_ANY == fmt->fmt.pix.field) - fmt->fmt.pix.field = V4L2_FIELD_NONE; - } return 0; } @@ -1034,12 +1029,12 @@ static int vidioc_try_fmt_out(struct file *file, void *priv, static int vidioc_s_fmt_out(struct file *file, void *priv, struct v4l2_format *fmt) { - struct v4l2_loopback_device *dev; + struct video_device *vdev = video_devdata(file); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); char buf[5]; int ret; MARK(); - dev = file_to_loopdev(file); ret = vidioc_try_fmt_out(file, priv, fmt); dprintk("s_fmt_out(%d) %d...%d\n", ret, dev->ready_for_capture, @@ -1232,12 +1227,8 @@ static int vidioc_enum_output(struct file *file, void *fh, struct v4l2_output *outp) { __u32 index = outp->index; - struct v4l2_loopback_device *dev = file_to_loopdev(file); MARK(); - if (!dev->announce_all_caps && !dev->ready_for_output) - return -ENOTTY; - if (0 != index) return -EINVAL; @@ -1264,9 +1255,6 @@ static int vidioc_enum_output(struct file *file, void *fh, */ static int vidioc_g_output(struct file *file, void *fh, unsigned int *i) { - struct v4l2_loopback_device *dev = file_to_loopdev(file); - if (!dev->announce_all_caps && !dev->ready_for_output) - return -ENOTTY; if (i) *i = 0; return 0; @@ -1277,10 +1265,6 @@ static int vidioc_g_output(struct file *file, void *fh, unsigned int *i) */ static int vidioc_s_output(struct file *file, void *fh, unsigned int i) { - struct v4l2_loopback_device *dev = file_to_loopdev(file); - if (!dev->announce_all_caps && !dev->ready_for_output) - return -ENOTTY; - if (i) return -EINVAL; @@ -2563,11 +2547,45 @@ static struct miscdevice v4l2loopback_misc = { static const struct v4l2_file_operations output_fops = { // clang-format off - .owner = THIS_MODULE, + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .unlocked_ioctl = video_ioctl2, // clang-format on }; -static const struct v4l2_ioctl_ops output_ioctl_ops = {}; +static const struct v4l2_ioctl_ops output_ioctl_ops = { + // clang-format off + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + + .vidioc_enum_output = vidioc_enum_output, + .vidioc_g_output = vidioc_g_output, + .vidioc_s_output = vidioc_s_output, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_out, + +#ifdef V4L2L_OVERLAY + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_overlay, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_overlay, +#endif + +#ifdef V4L2LOOPBACK_WITH_STD + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_querystd = vidioc_querystd, +#endif /* V4L2LOOPBACK_WITH_STD */ + + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + // clang-format on +}; static const struct v4l2_file_operations v4l2_loopback_fops = { // clang-format off @@ -2588,10 +2606,6 @@ static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { .vidioc_enum_framesizes = vidioc_enum_framesizes, .vidioc_enum_frameintervals = vidioc_enum_frameintervals, - .vidioc_enum_output = vidioc_enum_output, - .vidioc_g_output = vidioc_g_output, - .vidioc_s_output = vidioc_s_output, - .vidioc_enum_input = vidioc_enum_input, .vidioc_g_input = vidioc_g_input, .vidioc_s_input = vidioc_s_input, @@ -2601,11 +2615,6 @@ static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap, .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap, - .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_out, - .vidioc_s_fmt_vid_out = vidioc_s_fmt_out, - .vidioc_g_fmt_vid_out = vidioc_g_fmt_out, - .vidioc_try_fmt_vid_out = vidioc_try_fmt_out, - #ifdef V4L2L_OVERLAY .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_overlay, .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_overlay, From 2f0fb5aff0086349200cde220a5c901cae096566 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Fri, 16 Oct 2020 11:34:03 +0800 Subject: [PATCH 19/26] re-implement a separate output device Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/v4l2loopback.c b/v4l2loopback.c index 1222c807..25146472 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -331,11 +333,24 @@ struct v4l2l_buffer { int use_count; }; +struct v4l2_loopback_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb2_v4l2_buf; + + struct list_head list; +}; +#define to_v4l2_loopback_buffer(buf) \ + container_of(buf, struct v4l2_loopback_buffer, vb2_v4l2_buf) + struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; struct { struct video_device vdev; + struct vb2_queue vidq; + struct list_head active_bufs; /* buffers in DQBUF order */ + struct mutex lock; + int streaming : 1; } capture, output; /* pixel and stream format */ struct v4l2_pix_format pix_format; @@ -2096,6 +2111,121 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) return 0; } +static int output_qops_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct video_device *vdev = vb2_get_drv_priv(q); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + unsigned int size; + + size = dev->buffer_size; + if (*num_buffers > max_buffers) + *num_buffers = max_buffers; + + /* When called with plane sizes, validate them. v4l2loopback supports + * single planar formats only, and requires buffers to be large enough + * to store a complete frame. + */ + if (*num_planes) + return *num_planes != 1 || sizes[0] < size ? -EINVAL : 0; + + *num_planes = 1; + sizes[0] = size; + return 0; +} + +static int output_qops_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vb_v4l2 = to_vb2_v4l2_buffer(vb); + struct v4l2_loopback_buffer *buf = to_v4l2_loopback_buffer(vb_v4l2); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int output_qops_buf_prepare(struct vb2_buffer *vb) +{ + struct video_device *vdev = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + unsigned long size; + + size = dev->buffer_size; + if (vb2_plane_size(vb, 0) < size) { + v4l2_err(&dev->v4l2_dev, + "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + return 0; +} + +static void output_qops_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vb_v4l2 = to_vb2_v4l2_buffer(vb); + struct v4l2_loopback_buffer *buf = to_v4l2_loopback_buffer(vb_v4l2); + + list_del(&buf->list); +} + +static void output_qops_buf_queue(struct vb2_buffer *vb) +{ + struct video_device *vdev = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + struct vb2_v4l2_buffer *vb_v4l2 = to_vb2_v4l2_buffer(vb); + struct v4l2_loopback_buffer *buf = to_v4l2_loopback_buffer(vb_v4l2); + + list_add_tail(&buf->list, &dev->output.active_bufs); + + if (!dev->output.streaming) + return; + /* TODO: wake readers */ +} + +static int output_qops_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct video_device *vdev = vb2_get_drv_priv(q); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + + dev->output.streaming = 1; + /* TODO: wake readers */ + + return 0; +} + +static void output_qops_stop_streaming(struct vb2_queue *q) +{ + struct video_device *vdev = vb2_get_drv_priv(q); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + struct v4l2_loopback_buffer *buf; + struct list_head *pos, *n; + + list_for_each_safe (pos, n, &dev->output.active_bufs) { + buf = list_entry(pos, struct v4l2_loopback_buffer, list); + if (buf->vb2_v4l2_buf.vb2_buf.state == VB2_BUF_STATE_ACTIVE) + vb2_buffer_done(&buf->vb2_v4l2_buf.vb2_buf, + VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops output_qops = { + // clang-format off + .queue_setup = output_qops_queue_setup, + .buf_init = output_qops_buf_init, + .buf_prepare = output_qops_buf_prepare, + .buf_finish = output_qops_buf_finish, + .buf_queue = output_qops_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = output_qops_start_streaming, + .stop_streaming = output_qops_stop_streaming, + // clang-format on +}; + /* fills and register video device */ static int init_vdev(struct video_device *vdev, int nr, int type, struct v4l2_loopback_device *dev) @@ -2141,6 +2271,30 @@ static int init_vdev(struct video_device *vdev, int nr, int type, vdev->vfl_dir = VFL_DIR_M2M; #endif + if (is_output) { + struct vb2_queue *q = &dev->output.vidq; + int err; + + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRITE; + q->gfp_flags = 0; + q->min_buffers_needed = 1; + q->drv_priv = vdev; + q->buf_struct_size = sizeof(struct v4l2_loopback_buffer); + q->ops = &output_qops; + q->mem_ops = &vb2_vmalloc_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &dev->output.lock; + + err = vb2_queue_init(q); + if (err < 0) + return err; + + INIT_LIST_HEAD(&dev->output.active_bufs); + vdev->lock = q->lock; + vdev->queue = q; + } + MARK(); /* register the device -> it creates /dev/video* */ @@ -2549,6 +2703,10 @@ static const struct v4l2_file_operations output_fops = { // clang-format off .owner = THIS_MODULE, .open = v4l2_fh_open, + .release = vb2_fop_release, + .write = vb2_fop_write, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, .unlocked_ioctl = video_ioctl2, // clang-format on }; @@ -2582,6 +2740,16 @@ static const struct v4l2_ioctl_ops output_ioctl_ops = { .vidioc_g_parm = vidioc_g_parm, .vidioc_s_parm = vidioc_s_parm, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_subscribe_event = vidioc_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, // clang-format on From 4e0ef85c455685de4376810320ee4e9809471650 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Wed, 17 Mar 2021 00:52:12 +0800 Subject: [PATCH 20/26] prepare to share output/capture entity init Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 25146472..a1185c00 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -345,7 +345,7 @@ struct v4l2_loopback_buffer { struct v4l2_loopback_device { struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; - struct { + struct v4l2_loopback_entity { struct video_device vdev; struct vb2_queue vidq; struct list_head active_bufs; /* buffers in DQBUF order */ @@ -2227,10 +2227,11 @@ static const struct vb2_ops output_qops = { }; /* fills and register video device */ -static int init_vdev(struct video_device *vdev, int nr, int type, - struct v4l2_loopback_device *dev) +static int init_entity(struct v4l2_loopback_entity *entity, int nr, int type, + struct v4l2_loopback_device *dev) { int is_output = (type == V4L2_CAP_VIDEO_OUTPUT) ? 1 : 0; + struct video_device *vdev = &entity->vdev; snprintf(vdev->name, sizeof(vdev->name), dev->card_label); vdev->v4l2_dev = &dev->v4l2_dev; @@ -2309,18 +2310,6 @@ static int init_vdev(struct video_device *vdev, int nr, int type, return 0; } -static int init_output_vdev(struct video_device *vdev, int output_nr, - struct v4l2_loopback_device *dev) -{ - return init_vdev(vdev, output_nr, V4L2_CAP_VIDEO_OUTPUT, dev); -} - -static int init_capture_vdev(struct video_device *vdev, int capture_nr, - struct v4l2_loopback_device *dev) -{ - return init_vdev(vdev, capture_nr, V4L2_CAP_VIDEO_CAPTURE, dev); -} - /* init default capture parameters, only fps may be changed in future */ static void init_capture_param(struct v4l2_captureparm *capture_param) { @@ -2399,7 +2388,6 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) { struct v4l2_loopback_device *dev; struct v4l2_ctrl_handler *hdl; - struct video_device *capture_vdev, *output_vdev; int err = -ENOMEM; @@ -2533,23 +2521,21 @@ static int v4l2_loopback_add(struct v4l2_loopback_config *conf, int *ret_nr) MARK(); - output_vdev = &dev->output.vdev; - if (init_output_vdev(output_vdev, output_nr, dev)) + if (init_entity(&dev->output, output_nr, V4L2_CAP_VIDEO_OUTPUT, dev)) goto out_free_handler; MARK(); - capture_vdev = &dev->capture.vdev; - if (init_capture_vdev(capture_vdev, capture_nr, dev)) + if (init_entity(&dev->capture, capture_nr, V4L2_CAP_VIDEO_CAPTURE, dev)) goto out_unregister_output_vdev; MARK(); if (ret_nr) - *ret_nr = capture_vdev->num; + *ret_nr = dev->capture.vdev.num; return 0; out_unregister_output_vdev: - video_unregister_device(output_vdev); + video_unregister_device(&dev->output.vdev); out_free_handler: v4l2_ctrl_handler_free(&dev->ctrl_handler); out_free_idr: From 6d4a482b9c704a20dea73681f5a48961e0965c3d Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Wed, 17 Mar 2021 01:46:22 +0800 Subject: [PATCH 21/26] capture: init vb2_queue Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 54 ++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index a1185c00..0d21b1f6 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -678,8 +678,8 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev); static void check_timers(struct v4l2_loopback_device *dev); static const struct v4l2_file_operations output_fops; static const struct v4l2_ioctl_ops output_ioctl_ops; -static const struct v4l2_file_operations v4l2_loopback_fops; -static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops; +static const struct v4l2_file_operations capture_fops; +static const struct v4l2_ioctl_ops capture_ioctl_ops; /* Queue helpers */ /* next functions sets buffer flags and adjusts counters accordingly */ @@ -2226,12 +2226,16 @@ static const struct vb2_ops output_qops = { // clang-format on }; +static const struct vb2_ops capture_qops = {}; + /* fills and register video device */ static int init_entity(struct v4l2_loopback_entity *entity, int nr, int type, struct v4l2_loopback_device *dev) { int is_output = (type == V4L2_CAP_VIDEO_OUTPUT) ? 1 : 0; struct video_device *vdev = &entity->vdev; + struct vb2_queue *q = &entity->vidq; + int err; snprintf(vdev->name, sizeof(vdev->name), dev->card_label); vdev->v4l2_dev = &dev->v4l2_dev; @@ -2246,8 +2250,8 @@ static int init_entity(struct v4l2_loopback_entity *entity, int nr, int type, vdev->fops = &output_fops; vdev->ioctl_ops = &output_ioctl_ops; } else { - vdev->fops = &v4l2_loopback_fops; - vdev->ioctl_ops = &v4l2_loopback_ioctl_ops; + vdev->fops = &capture_fops; + vdev->ioctl_ops = &capture_ioctl_ops; } vdev->release = &video_device_release_empty; vdev->minor = -1; @@ -2272,29 +2276,27 @@ static int init_entity(struct v4l2_loopback_entity *entity, int nr, int type, vdev->vfl_dir = VFL_DIR_M2M; #endif - if (is_output) { - struct vb2_queue *q = &dev->output.vidq; - int err; - - q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRITE; - q->gfp_flags = 0; - q->min_buffers_needed = 1; - q->drv_priv = vdev; - q->buf_struct_size = sizeof(struct v4l2_loopback_buffer); + q->type = type; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRITE; + q->gfp_flags = 0; + q->min_buffers_needed = 1; + q->drv_priv = vdev; + q->buf_struct_size = sizeof(struct v4l2_loopback_buffer); + q->mem_ops = &vb2_vmalloc_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &entity->lock; + if (is_output) q->ops = &output_qops; - q->mem_ops = &vb2_vmalloc_memops; - q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->lock = &dev->output.lock; + else + q->ops = &capture_qops; - err = vb2_queue_init(q); - if (err < 0) - return err; + err = vb2_queue_init(q); + if (err < 0) + return err; - INIT_LIST_HEAD(&dev->output.active_bufs); - vdev->lock = q->lock; - vdev->queue = q; - } + INIT_LIST_HEAD(&entity->active_bufs); + vdev->lock = q->lock; + vdev->queue = q; MARK(); @@ -2741,7 +2743,7 @@ static const struct v4l2_ioctl_ops output_ioctl_ops = { // clang-format on }; -static const struct v4l2_file_operations v4l2_loopback_fops = { +static const struct v4l2_file_operations capture_fops = { // clang-format off .owner = THIS_MODULE, .open = v4l2_loopback_open, @@ -2754,7 +2756,7 @@ static const struct v4l2_file_operations v4l2_loopback_fops = { // clang-format on }; -static const struct v4l2_ioctl_ops v4l2_loopback_ioctl_ops = { +static const struct v4l2_ioctl_ops capture_ioctl_ops = { // clang-format off .vidioc_querycap = vidioc_querycap, .vidioc_enum_framesizes = vidioc_enum_framesizes, From 37c700ea9593002ee7e3d16409dd169f063c6273 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Wed, 17 Mar 2021 17:48:31 +0800 Subject: [PATCH 22/26] capture: use fops callback from videobuf2 Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 298 +------------------------------------------------ 1 file changed, 5 insertions(+), 293 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 0d21b1f6..1930f5be 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -1706,293 +1706,6 @@ static int vidioc_subscribe_event(struct v4l2_fh *fh, return -EINVAL; } -/* file operations */ -static void vm_open(struct vm_area_struct *vma) -{ - struct v4l2l_buffer *buf; - MARK(); - - buf = vma->vm_private_data; - buf->use_count++; -} - -static void vm_close(struct vm_area_struct *vma) -{ - struct v4l2l_buffer *buf; - MARK(); - - buf = vma->vm_private_data; - buf->use_count--; -} - -static struct vm_operations_struct vm_ops = { - .open = vm_open, - .close = vm_close, -}; - -static int v4l2_loopback_mmap(struct file *file, struct vm_area_struct *vma) -{ - unsigned long addr; - unsigned long start; - unsigned long size; - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - struct v4l2l_buffer *buffer = NULL; - MARK(); - - start = (unsigned long)vma->vm_start; - size = (unsigned long)(vma->vm_end - vma->vm_start); - - dev = file_to_loopdev(file); - opener = fh_to_opener(file->private_data); - - if (size > dev->buffer_size) { - dprintk("userspace tries to mmap too much, fail\n"); - return -EINVAL; - } - if (opener->timeout_image_io) { - /* we are going to map the timeout_image_buffer */ - if ((vma->vm_pgoff << PAGE_SHIFT) != - dev->buffer_size * MAX_BUFFERS) { - dprintk("invalid mmap offset for timeout_image_io mode\n"); - return -EINVAL; - } - } else if ((vma->vm_pgoff << PAGE_SHIFT) > - dev->buffer_size * (dev->buffers_number - 1)) { - dprintk("userspace tries to mmap too far, fail\n"); - return -EINVAL; - } - - /* FIXXXXXME: allocation should not happen here! */ - if (NULL == dev->image) - if (allocate_buffers(dev) < 0) - return -EINVAL; - - if (opener->timeout_image_io) { - buffer = &dev->timeout_image_buffer; - addr = (unsigned long)dev->timeout_image; - } else { - int i; - for (i = 0; i < dev->buffers_number; ++i) { - buffer = &dev->buffers[i]; - if ((buffer->buffer.m.offset >> PAGE_SHIFT) == - vma->vm_pgoff) - break; - } - - if (NULL == buffer) - return -EINVAL; - - addr = (unsigned long)dev->image + - (vma->vm_pgoff << PAGE_SHIFT); - } - - while (size > 0) { - struct page *page; - - page = (void *)vmalloc_to_page((void *)addr); - - if (vm_insert_page(vma, start, page) < 0) - return -EAGAIN; - - start += PAGE_SIZE; - addr += PAGE_SIZE; - size -= PAGE_SIZE; - } - - vma->vm_ops = &vm_ops; - vma->vm_private_data = buffer; - buffer->buffer.flags |= V4L2_BUF_FLAG_MAPPED; - - vm_open(vma); - - MARK(); - return 0; -} - -static unsigned int v4l2_loopback_poll(struct file *file, - struct poll_table_struct *pts) -{ - struct v4l2_loopback_opener *opener; - struct v4l2_loopback_device *dev; - __poll_t req_events = poll_requested_events(pts); - int ret_mask = 0; - MARK(); - - opener = fh_to_opener(file->private_data); - dev = file_to_loopdev(file); - - if (req_events & POLLPRI) { - if (!v4l2_event_pending(&opener->fh)) - poll_wait(file, &opener->fh.wait, pts); - if (v4l2_event_pending(&opener->fh)) { - ret_mask |= POLLPRI; - if (!(req_events & DEFAULT_POLLMASK)) - return ret_mask; - } - } - - switch (opener->type) { - case WRITER: - ret_mask |= POLLOUT | POLLWRNORM; - break; - case READER: - if (!can_read(dev, opener)) { - if (ret_mask) - return ret_mask; - poll_wait(file, &dev->read_event, pts); - } - if (can_read(dev, opener)) - ret_mask |= POLLIN | POLLRDNORM; - if (v4l2_event_pending(&opener->fh)) - ret_mask |= POLLPRI; - break; - default: - break; - } - - MARK(); - return ret_mask; -} - -/* do not want to limit device opens, it can be as many readers as user want, - * writers are limited by means of setting writer field */ -static int v4l2_loopback_open(struct file *file) -{ - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - MARK(); - dev = file_to_loopdev(file); - if (dev->open_count.counter >= dev->max_openers) - return -EBUSY; - /* kfree on close */ - opener = kzalloc(sizeof(*opener), GFP_KERNEL); - if (opener == NULL) - return -ENOMEM; - - v4l2_fh_init(&opener->fh, video_devdata(file)); - file->private_data = &opener->fh; - atomic_inc(&dev->open_count); - - opener->timeout_image_io = dev->timeout_image_io; - dev->timeout_image_io = 0; - - if (opener->timeout_image_io) { - int r = allocate_timeout_image(dev); - - if (r < 0) { - dprintk("timeout image allocation failed\n"); - return r; - } - } - - v4l2_fh_add(&opener->fh); - dprintk("opened dev:%p with image:%p\n", dev, dev ? dev->image : NULL); - MARK(); - return 0; -} - -static int v4l2_loopback_close(struct file *file) -{ - struct v4l2_loopback_opener *opener; - struct v4l2_loopback_device *dev; - int iswriter = 0; - MARK(); - - opener = fh_to_opener(file->private_data); - dev = file_to_loopdev(file); - - if (WRITER == opener->type) - iswriter = 1; - - atomic_dec(&dev->open_count); - if (dev->open_count.counter == 0) { - del_timer_sync(&dev->sustain_timer); - del_timer_sync(&dev->timeout_timer); - } - try_free_buffers(dev); - - v4l2_fh_del(&opener->fh); - v4l2_fh_exit(&opener->fh); - - kfree(opener); - if (iswriter) { - dev->ready_for_output = 1; - } - MARK(); - return 0; -} - -static ssize_t v4l2_loopback_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) -{ - int read_index; - struct v4l2_loopback_device *dev; - struct v4l2_buffer *b; - MARK(); - - dev = file_to_loopdev(file); - - read_index = get_capture_buffer(file); - if (read_index < 0) - return read_index; - if (count > dev->buffer_size) - count = dev->buffer_size; - b = &dev->buffers[read_index].buffer; - if (count > b->bytesused) - count = b->bytesused; - if (copy_to_user((void *)buf, (void *)(dev->image + b->m.offset), - count)) { - printk(KERN_ERR - "v4l2-loopback: failed copy_to_user() in read buf\n"); - return -EFAULT; - } - dprintkrw("leave v4l2_loopback_read()\n"); - return count; -} - -static ssize_t v4l2_loopback_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - struct v4l2_loopback_device *dev; - int write_index; - struct v4l2_buffer *b; - MARK(); - - dev = file_to_loopdev(file); - - /* there's at least one writer, so don't stop announcing output capabilities */ - dev->ready_for_output = 0; - - if (!dev->ready_for_capture) { - int ret = allocate_buffers(dev); - if (ret < 0) - return ret; - dev->ready_for_capture = 1; - } - dprintkrw("v4l2_loopback_write() trying to write %zu bytes\n", count); - if (count > dev->buffer_size) - count = dev->buffer_size; - - write_index = dev->write_position % dev->used_buffers; - b = &dev->buffers[write_index].buffer; - - if (copy_from_user((void *)(dev->image + b->m.offset), (void *)buf, - count)) { - printk(KERN_ERR - "v4l2-loopback: failed copy_from_user() in write buf, could not write %zu\n", - count); - return -EFAULT; - } - v4l2l_get_timestamp(b); - b->bytesused = count; - b->sequence = dev->write_position; - buffer_written(dev, &dev->buffers[write_index]); - wake_up_all(&dev->read_event); - dprintkrw("leave v4l2_loopback_write()\n"); - return count; -} - /* init functions */ /* frees buffers, if already allocated */ static int free_buffers(struct v4l2_loopback_device *dev) @@ -2746,12 +2459,11 @@ static const struct v4l2_ioctl_ops output_ioctl_ops = { static const struct v4l2_file_operations capture_fops = { // clang-format off .owner = THIS_MODULE, - .open = v4l2_loopback_open, - .release = v4l2_loopback_close, - .read = v4l2_loopback_read, - .write = v4l2_loopback_write, - .poll = v4l2_loopback_poll, - .mmap = v4l2_loopback_mmap, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, .unlocked_ioctl = video_ioctl2, // clang-format on }; From ac6439be04f57731391fe228b2062a4bad42caa6 Mon Sep 17 00:00:00 2001 From: You-Sheng Yang Date: Wed, 17 Mar 2021 01:33:16 +0800 Subject: [PATCH 23/26] [wip] reimplement capture entity using videobuf2 Signed-off-by: You-Sheng Yang --- v4l2loopback.c | 443 +++++++++---------------------------------------- 1 file changed, 74 insertions(+), 369 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 1930f5be..5042a707 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -1345,355 +1345,6 @@ static int vidioc_s_input(struct file *file, void *fh, unsigned int i) return -EINVAL; } -/* --------------- V4L2 ioctl buffer related calls ----------------- */ - -/* negotiate buffer type - * only mmap streaming supported - * called on VIDIOC_REQBUFS - */ -static int vidioc_reqbufs(struct file *file, void *fh, - struct v4l2_requestbuffers *b) -{ - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - int i; - MARK(); - - dev = file_to_loopdev(file); - opener = fh_to_opener(fh); - - dprintk("reqbufs: %d\t%d=%d\n", b->memory, b->count, - dev->buffers_number); - if (opener->timeout_image_io) { - if (b->memory != V4L2_MEMORY_MMAP) - return -EINVAL; - b->count = 1; - return 0; - } - - init_buffers(dev); - switch (b->memory) { - case V4L2_MEMORY_MMAP: - /* do nothing here, buffers are always allocated */ - if (b->count < 1 || dev->buffers_number < 1) - return 0; - - if (b->count > dev->buffers_number) - b->count = dev->buffers_number; - - /* make sure that outbufs_list contains buffers from 0 to used_buffers-1 - * actually, it will have been already populated via v4l2_loopback_init() - * at this point */ - if (list_empty(&dev->outbufs_list)) { - for (i = 0; i < dev->used_buffers; ++i) - list_add_tail(&dev->buffers[i].list_head, - &dev->outbufs_list); - } - - /* also, if dev->used_buffers is going to be decreased, we should remove - * out-of-range buffers from outbufs_list, and fix bufpos2index mapping */ - if (b->count < dev->used_buffers) { - struct v4l2l_buffer *pos, *n; - - list_for_each_entry_safe (pos, n, &dev->outbufs_list, - list_head) { - if (pos->buffer.index >= b->count) - list_del(&pos->list_head); - } - - /* after we update dev->used_buffers, buffers in outbufs_list will - * correspond to dev->write_position + [0;b->count-1] range */ - i = dev->write_position; - list_for_each_entry (pos, &dev->outbufs_list, - list_head) { - dev->bufpos2index[i % b->count] = - pos->buffer.index; - ++i; - } - } - - opener->buffers_number = b->count; - if (opener->buffers_number < dev->used_buffers) - dev->used_buffers = opener->buffers_number; - return 0; - default: - return -EINVAL; - } -} - -/* returns buffer asked for; - * give app as many buffers as it wants, if it less than MAX, - * but map them in our inner buffers - * called on VIDIOC_QUERYBUF - */ -static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) -{ - enum v4l2_buf_type type; - int index; - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - - MARK(); - - type = b->type; - index = b->index; - dev = file_to_loopdev(file); - opener = fh_to_opener(fh); - - if ((b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && - (b->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) { - return -EINVAL; - } - if (b->index > max_buffers) - return -EINVAL; - - if (opener->timeout_image_io) - *b = dev->timeout_image_buffer.buffer; - else - *b = dev->buffers[b->index % dev->used_buffers].buffer; - - b->type = type; - b->index = index; - dprintkrw("buffer type: %d (of %d with size=%ld)\n", b->memory, - dev->buffers_number, dev->buffer_size); - - /* Hopefully fix 'DQBUF return bad index if queue bigger then 2 for capture' - https://github.com/umlaeute/v4l2loopback/issues/60 */ - b->flags &= ~V4L2_BUF_FLAG_DONE; - b->flags |= V4L2_BUF_FLAG_QUEUED; - - return 0; -} - -static void buffer_written(struct v4l2_loopback_device *dev, - struct v4l2l_buffer *buf) -{ - del_timer_sync(&dev->sustain_timer); - del_timer_sync(&dev->timeout_timer); - spin_lock_bh(&dev->lock); - - dev->bufpos2index[dev->write_position % dev->used_buffers] = - buf->buffer.index; - list_move_tail(&buf->list_head, &dev->outbufs_list); - ++dev->write_position; - dev->reread_count = 0; - - check_timers(dev); - spin_unlock_bh(&dev->lock); -} - -/* put buffer to queue - * called on VIDIOC_QBUF - */ -static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) -{ - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - struct v4l2l_buffer *b; - int index; - - dev = file_to_loopdev(file); - opener = fh_to_opener(fh); - - if (buf->index > max_buffers) - return -EINVAL; - if (opener->timeout_image_io) - return 0; - - index = buf->index % dev->used_buffers; - b = &dev->buffers[index]; - - switch (buf->type) { - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - dprintkrw("capture QBUF index: %d\n", index); - set_queued(b); - return 0; - case V4L2_BUF_TYPE_VIDEO_OUTPUT: - dprintkrw("output QBUF pos: %d index: %d\n", - dev->write_position, index); - if (buf->timestamp.tv_sec == 0 && buf->timestamp.tv_usec == 0) - v4l2l_get_timestamp(&b->buffer); - else - b->buffer.timestamp = buf->timestamp; - b->buffer.bytesused = buf->bytesused; - set_done(b); - buffer_written(dev, b); - - /* Hopefully fix 'DQBUF return bad index if queue bigger then 2 for capture' - https://github.com/umlaeute/v4l2loopback/issues/60 */ - buf->flags &= ~V4L2_BUF_FLAG_DONE; - buf->flags |= V4L2_BUF_FLAG_QUEUED; - - wake_up_all(&dev->read_event); - return 0; - default: - return -EINVAL; - } -} - -static int can_read(struct v4l2_loopback_device *dev, - struct v4l2_loopback_opener *opener) -{ - int ret; - - spin_lock_bh(&dev->lock); - check_timers(dev); - ret = dev->write_position > opener->read_position || - dev->reread_count > opener->reread_count || dev->timeout_happened; - spin_unlock_bh(&dev->lock); - return ret; -} - -static int get_capture_buffer(struct file *file) -{ - struct v4l2_loopback_device *dev = file_to_loopdev(file); - struct v4l2_loopback_opener *opener = fh_to_opener(file->private_data); - int pos, ret; - int timeout_happened; - - if ((file->f_flags & O_NONBLOCK) && - (dev->write_position <= opener->read_position && - dev->reread_count <= opener->reread_count && - !dev->timeout_happened)) - return -EAGAIN; - wait_event_interruptible(dev->read_event, can_read(dev, opener)); - - spin_lock_bh(&dev->lock); - if (dev->write_position == opener->read_position) { - if (dev->reread_count > opener->reread_count + 2) - opener->reread_count = dev->reread_count - 1; - ++opener->reread_count; - pos = (opener->read_position + dev->used_buffers - 1) % - dev->used_buffers; - } else { - opener->reread_count = 0; - if (dev->write_position > - opener->read_position + dev->used_buffers) - opener->read_position = dev->write_position - 1; - pos = opener->read_position % dev->used_buffers; - ++opener->read_position; - } - timeout_happened = dev->timeout_happened; - dev->timeout_happened = 0; - spin_unlock_bh(&dev->lock); - - ret = dev->bufpos2index[pos]; - if (timeout_happened) { - /* although allocated on-demand, timeout_image is freed only - * in free_buffers(), so we don't need to worry about it being - * deallocated suddenly */ - memcpy(dev->image + dev->buffers[ret].buffer.m.offset, - dev->timeout_image, dev->buffer_size); - } - return ret; -} - -/* put buffer to dequeue - * called on VIDIOC_DQBUF - */ -static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) -{ - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - int index; - struct v4l2l_buffer *b; - - dev = file_to_loopdev(file); - opener = fh_to_opener(fh); - if (opener->timeout_image_io) { - *buf = dev->timeout_image_buffer.buffer; - return 0; - } - - switch (buf->type) { - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - index = get_capture_buffer(file); - if (index < 0) - return index; - dprintkrw("capture DQBUF pos: %d index: %d\n", - opener->read_position - 1, index); - if (!(dev->buffers[index].buffer.flags & - V4L2_BUF_FLAG_MAPPED)) { - dprintk("trying to return not mapped buf[%d]\n", index); - return -EINVAL; - } - unset_flags(&dev->buffers[index]); - *buf = dev->buffers[index].buffer; - return 0; - case V4L2_BUF_TYPE_VIDEO_OUTPUT: - b = list_entry(dev->outbufs_list.prev, struct v4l2l_buffer, - list_head); - list_move_tail(&b->list_head, &dev->outbufs_list); - dprintkrw("output DQBUF index: %d\n", b->buffer.index); - unset_flags(b); - *buf = b->buffer; - buf->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - return 0; - default: - return -EINVAL; - } -} - -/* ------------- STREAMING ------------------- */ - -/* start streaming - * called on VIDIOC_STREAMON - */ -static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type type) -{ - struct v4l2_loopback_device *dev; - struct v4l2_loopback_opener *opener; - MARK(); - - dev = file_to_loopdev(file); - opener = fh_to_opener(fh); - - switch (type) { - case V4L2_BUF_TYPE_VIDEO_OUTPUT: - opener->type = WRITER; - dev->ready_for_output = 0; - if (!dev->ready_for_capture) { - int ret = allocate_buffers(dev); - if (ret < 0) - return ret; - } - dev->ready_for_capture++; - return 0; - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - opener->type = READER; - if (!dev->ready_for_capture) - return -EIO; - return 0; - default: - return -EINVAL; - } - return -EINVAL; -} - -/* stop streaming - * called on VIDIOC_STREAMOFF - */ -static int vidioc_streamoff(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct v4l2_loopback_device *dev; - MARK(); - dprintk("%d\n", type); - - dev = file_to_loopdev(file); - - switch (type) { - case V4L2_BUF_TYPE_VIDEO_OUTPUT: - if (dev->ready_for_capture > 0) - dev->ready_for_capture--; - return 0; - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - return 0; - default: - return -EINVAL; - } - return -EINVAL; -} static int vidioc_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) @@ -1824,11 +1475,11 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) return 0; } -static int output_qops_queue_setup(struct vb2_queue *q, - unsigned int *num_buffers, - unsigned int *num_planes, - unsigned int sizes[], - struct device *alloc_devs[]) +static int qops_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) { struct video_device *vdev = vb2_get_drv_priv(q); struct v4l2_loopback_device *dev = video_get_drvdata(vdev); @@ -1850,7 +1501,7 @@ static int output_qops_queue_setup(struct vb2_queue *q, return 0; } -static int output_qops_buf_init(struct vb2_buffer *vb) +static int qops_buf_init(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vb_v4l2 = to_vb2_v4l2_buffer(vb); struct v4l2_loopback_buffer *buf = to_v4l2_loopback_buffer(vb_v4l2); @@ -1860,7 +1511,7 @@ static int output_qops_buf_init(struct vb2_buffer *vb) return 0; } -static int output_qops_buf_prepare(struct vb2_buffer *vb) +static int qops_buf_prepare(struct vb2_buffer *vb) { struct video_device *vdev = vb2_get_drv_priv(vb->vb2_queue); struct v4l2_loopback_device *dev = video_get_drvdata(vdev); @@ -1877,7 +1528,7 @@ static int output_qops_buf_prepare(struct vb2_buffer *vb) return 0; } -static void output_qops_buf_finish(struct vb2_buffer *vb) +static void qops_buf_finish(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vb_v4l2 = to_vb2_v4l2_buffer(vb); struct v4l2_loopback_buffer *buf = to_v4l2_loopback_buffer(vb_v4l2); @@ -1927,10 +1578,10 @@ static void output_qops_stop_streaming(struct vb2_queue *q) static const struct vb2_ops output_qops = { // clang-format off - .queue_setup = output_qops_queue_setup, - .buf_init = output_qops_buf_init, - .buf_prepare = output_qops_buf_prepare, - .buf_finish = output_qops_buf_finish, + .queue_setup = qops_queue_setup, + .buf_init = qops_buf_init, + .buf_prepare = qops_buf_prepare, + .buf_finish = qops_buf_finish, .buf_queue = output_qops_buf_queue, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, @@ -1939,7 +1590,59 @@ static const struct vb2_ops output_qops = { // clang-format on }; -static const struct vb2_ops capture_qops = {}; +static void capture_qops_buf_queue(struct vb2_buffer *vb) +{ + struct video_device *vdev = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + struct vb2_v4l2_buffer *vb_v4l2 = to_vb2_v4l2_buffer(vb); + struct v4l2_loopback_buffer *buf = to_v4l2_loopback_buffer(vb_v4l2); + + list_add_tail(&buf->list, &dev->capture.active_bufs); + + if (!dev->capture.streaming) + return; + /* TODO: wake readers */ +} + +static int capture_qops_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct video_device *vdev = vb2_get_drv_priv(q); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + + dev->capture.streaming = 1; + /* TODO: wake readers */ + + return 0; +} + +static void capture_qops_stop_streaming(struct vb2_queue *q) +{ + struct video_device *vdev = vb2_get_drv_priv(q); + struct v4l2_loopback_device *dev = video_get_drvdata(vdev); + struct v4l2_loopback_buffer *buf; + struct list_head *pos, *n; + + list_for_each_safe (pos, n, &dev->capture.active_bufs) { + buf = list_entry(pos, struct v4l2_loopback_buffer, list); + if (buf->vb2_v4l2_buf.vb2_buf.state == VB2_BUF_STATE_ACTIVE) + vb2_buffer_done(&buf->vb2_v4l2_buf.vb2_buf, + VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops capture_qops = { + // clang-format off + .queue_setup = qops_queue_setup, + .buf_init = qops_buf_init, + .buf_prepare = qops_buf_prepare, + .buf_finish = qops_buf_finish, + .buf_queue = capture_qops_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = capture_qops_start_streaming, + .stop_streaming = capture_qops_stop_streaming, + // clang-format on +}; /* fills and register video device */ static int init_entity(struct v4l2_loopback_entity *entity, int nr, int type, @@ -2497,13 +2200,15 @@ static const struct v4l2_ioctl_ops capture_ioctl_ops = { .vidioc_g_parm = vidioc_g_parm, .vidioc_s_parm = vidioc_s_parm, - .vidioc_reqbufs = vidioc_reqbufs, - .vidioc_querybuf = vidioc_querybuf, - .vidioc_qbuf = vidioc_qbuf, - .vidioc_dqbuf = vidioc_dqbuf, - - .vidioc_streamon = vidioc_streamon, - .vidioc_streamoff = vidioc_streamoff, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_subscribe_event = vidioc_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, From 2350269d52f5f530615f83ecb5004ca744df1ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Fri, 19 Mar 2021 10:57:14 +0100 Subject: [PATCH 24/26] clang-format --- v4l2loopback.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index a4157588..29da8051 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -848,7 +848,6 @@ static int vidioc_enum_fmt_cap(struct file *file, void *fh, { struct v4l2_loopback_device *dev; MARK(); - dev = file_to_loopdev(file); if (f->index) @@ -1328,7 +1327,6 @@ static int vidioc_s_input(struct file *file, void *fh, unsigned int i) return -EINVAL; } - static int vidioc_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -1458,10 +1456,8 @@ static int allocate_timeout_image(struct v4l2_loopback_device *dev) return 0; } -static int qops_queue_setup(struct vb2_queue *q, - unsigned int *num_buffers, - unsigned int *num_planes, - unsigned int sizes[], +static int qops_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], struct device *alloc_devs[]) { struct video_device *vdev = vb2_get_drv_priv(q); @@ -2277,7 +2273,8 @@ static int v4l2loopback_init_module(void) if (card_label[i]) snprintf(cfg.card_label, sizeof(cfg.card_label), "%s", card_label[i]); - err = v4l2_loopback_add(&cfg, video_nr+i); + //TODO: also set output_nr + err = v4l2_loopback_add(&cfg, video_nr + i); if (err) { free_devices(); goto error; From 3a1ab9ab82359ae180857bf72a2eff4e13b8d087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Fri, 19 Mar 2021 10:58:00 +0100 Subject: [PATCH 25/26] simplified capabilities logic (with only split-devices), setting CAPTURE only for capture devices --- v4l2loopback.c | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/v4l2loopback.c b/v4l2loopback.c index 29da8051..d6768841 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -700,54 +700,29 @@ static int vidioc_querycap(struct file *file, void *priv, { struct video_device *vdev = video_devdata(file); struct v4l2_loopback_device *dev = video_get_drvdata(vdev); - int is_output = (vdev == &dev->output.vdev) ? 1 : 0; int labellen = (sizeof(cap->card) < sizeof(dev->card_label)) ? sizeof(cap->card) : sizeof(dev->card_label); __u32 capabilities = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; -#if defined(V4L2_CAP_DEVICE_CAPS) __u32 device_caps; -#endif strlcpy(cap->driver, "v4l2 loopback", sizeof(cap->driver)); snprintf(cap->card, labellen, dev->card_label); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:v4l2loopback-%03d", dev->capture.vdev.num); - if (is_output) { - /* If this is that splited output device, it will always be an - * output device. No more trick here. - */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) - capabilities = dev->output.vdev.device_caps | - dev->capture.vdev.device_caps; - device_caps = vdev->device_caps; + capabilities = + dev->output.vdev.device_caps | dev->capture.vdev.device_caps; + device_caps = vdev->device_caps; #else - capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; -#if defined(V4L2_CAP_DEVICE_CAPS) + capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + if (vdev == &dev->output.vdev) { device_caps = capabilities & (~V4L2_CAP_VIDEO_CAPTURE); -#endif -#endif } else { - /* So this is the capture device that currently serves both - * roles depending on ready_for_capture/ready_for_output and - * announce_all_caps. */ - if (dev->announce_all_caps) { - capabilities |= - V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; - } else { - if (dev->ready_for_capture) - capabilities |= V4L2_CAP_VIDEO_CAPTURE; - if (dev->ready_for_output) - capabilities |= V4L2_CAP_VIDEO_OUTPUT; - } -#if defined(V4L2_CAP_DEVICE_CAPS) -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) - dev->capture.vdev.device_caps = -#endif - device_caps = capabilities; -#endif + device_caps = capabilities & (~V4L2_CAP_VIDEO_OUTPUT); } +#endif cap->capabilities = capabilities; cap->device_caps = device_caps; From 2e76bec5bf237c7020b8f793c34d0aaf0f0953e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Fri, 19 Mar 2021 10:58:28 +0100 Subject: [PATCH 26/26] set device transfer direction --- v4l2loopback.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/v4l2loopback.c b/v4l2loopback.c index d6768841..614c06ef 100644 --- a/v4l2loopback.c +++ b/v4l2loopback.c @@ -1619,9 +1619,11 @@ static int init_entity(struct v4l2_loopback_entity *entity, int nr, int type, if (is_output) { vdev->fops = &output_fops; vdev->ioctl_ops = &output_ioctl_ops; + vdev->vfl_dir = VFL_DIR_TX; } else { vdev->fops = &capture_fops; vdev->ioctl_ops = &capture_ioctl_ops; + vdev->vfl_dir = VFL_DIR_RX; } vdev->release = &video_device_release_empty; vdev->minor = -1;