usb: gadget: uvc: fix multiple opens
authorThomas Haemmerle <thomas.haemmerle@wolfvision.net>
Sun, 3 Oct 2021 20:13:55 +0000 (22:13 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 5 Oct 2021 11:37:24 +0000 (13:37 +0200)
Currently, the UVC function is activated when open on the corresponding
v4l2 device is called.  On another open the activation of the function
fails since the deactivation counter in `usb_function_activate` equals
0. However the error is not returned to userspace since the open of the
v4l2 device is successful.

On a close the function is deactivated (since deactivation counter still
equals 0) and the video is disabled in `uvc_v4l2_release`, although the
UVC application potentially is streaming.

Move activation of UVC function to subscription on UVC_EVENT_SETUP
because there we can guarantee for a userspace application utilizing
UVC.  Block subscription on UVC_EVENT_SETUP while another application
already is subscribed to it, indicated by `bool func_connected` in
`struct uvc_device`.  Extend the `struct uvc_file_handle` with member
`bool is_uvc_app_handle` to tag it as the handle used by the userspace
UVC application.

With this a process is able to check capabilities of the v4l2 device
without deactivating the function for the actual UVC application.

Reviewed-By: Michael Tretter <m.tretter@pengutronix.de>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Thomas Haemmerle <thomas.haemmerle@wolfvision.net>
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Acked-by: Felipe Balbi <balbi@kernel.org>
Link: https://lore.kernel.org/r/20211003201355.24081-1-m.grzeschik@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/gadget/function/uvc.h
drivers/usb/gadget/function/uvc_v4l2.c

index 255a61bd6a6a8d58d552b0d32b9bba7b01d11489..9d5f17b551bbd1a7ffd61b46108e4fdc24e0ea42 100644 (file)
@@ -126,6 +126,7 @@ struct uvc_device {
        enum uvc_state state;
        struct usb_function func;
        struct uvc_video video;
+       bool func_connected;
 
        /* Descriptors */
        struct {
@@ -156,6 +157,7 @@ static inline struct uvc_device *to_uvc(struct usb_function *f)
 struct uvc_file_handle {
        struct v4l2_fh vfh;
        struct uvc_video *device;
+       bool is_uvc_app_handle;
 };
 
 #define to_uvc_file_handle(handle) \
index 4ca89eab61590d2722fb963c0d75cab5eea90308..197c26f7aec6382cf3019bf32d8254e09c1d4d1c 100644 (file)
@@ -227,17 +227,55 @@ static int
 uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
                         const struct v4l2_event_subscription *sub)
 {
+       struct uvc_device *uvc = video_get_drvdata(fh->vdev);
+       struct uvc_file_handle *handle = to_uvc_file_handle(fh);
+       int ret;
+
        if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
                return -EINVAL;
 
-       return v4l2_event_subscribe(fh, sub, 2, NULL);
+       if (sub->type == UVC_EVENT_SETUP && uvc->func_connected)
+               return -EBUSY;
+
+       ret = v4l2_event_subscribe(fh, sub, 2, NULL);
+       if (ret < 0)
+               return ret;
+
+       if (sub->type == UVC_EVENT_SETUP) {
+               uvc->func_connected = true;
+               handle->is_uvc_app_handle = true;
+               uvc_function_connect(uvc);
+       }
+
+       return 0;
+}
+
+static void uvc_v4l2_disable(struct uvc_device *uvc)
+{
+       uvc->func_connected = false;
+       uvc_function_disconnect(uvc);
+       uvcg_video_enable(&uvc->video, 0);
+       uvcg_free_buffers(&uvc->video.queue);
 }
 
 static int
 uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh,
                           const struct v4l2_event_subscription *sub)
 {
-       return v4l2_event_unsubscribe(fh, sub);
+       struct uvc_device *uvc = video_get_drvdata(fh->vdev);
+       struct uvc_file_handle *handle = to_uvc_file_handle(fh);
+       int ret;
+
+       ret = v4l2_event_unsubscribe(fh, sub);
+       if (ret < 0)
+               return ret;
+
+       if (sub->type == UVC_EVENT_SETUP && handle->is_uvc_app_handle) {
+               uvc_v4l2_disable(uvc);
+               handle->is_uvc_app_handle = false;
+       }
+
+       return 0;
 }
 
 static long
@@ -292,7 +330,6 @@ uvc_v4l2_open(struct file *file)
        handle->device = &uvc->video;
        file->private_data = &handle->vfh;
 
-       uvc_function_connect(uvc);
        return 0;
 }
 
@@ -304,11 +341,9 @@ uvc_v4l2_release(struct file *file)
        struct uvc_file_handle *handle = to_uvc_file_handle(file->private_data);
        struct uvc_video *video = handle->device;
 
-       uvc_function_disconnect(uvc);
-
        mutex_lock(&video->mutex);
-       uvcg_video_enable(video, 0);
-       uvcg_free_buffers(&video->queue);
+       if (handle->is_uvc_app_handle)
+               uvc_v4l2_disable(uvc);
        mutex_unlock(&video->mutex);
 
        file->private_data = NULL;