usb: gadget: uvc: synchronize streamon/off with uvc_function_set_alt
PATCH v2 1/3 usb: gadget: uvc: synchronize streamon/off with uvc_function_set_alt
Hi Paul,
Thank you for the patch.
Quoted 28 lines · On Tuesday, 24 April 2018 23:59:34 EEST Paul Elder wrote:
Down the call stack from the ioctl handler for
VIDIOC_STREAMON,uvc_video_alloc_requestscontains aBUG_ON, which in the high level, triggers whenVIDIOC_STREAMONioctl is issued withoutVIDIOC_STREAMOFFbeing issued previously.This could be triggered by
uvc_function_set_alt0 racing and winning againstuvc_v4l2_streamon, or by userspace neglecting to issue theVIDIOC_STREAMOFFioctl.To fix this, add two more uvc states: starting and stopping. Use these to prevent the racing, and to detect when
VIDIOC_STREAMONis issued without previously issuingVIDIOC_STREAMOFF.Signed-off-by Paul Elder <[email protected]>---
Changes in v2: Nothingdrivers/usb/gadget/function/f_uvc.c | 8 ++++++-- drivers/usb/gadget/function/uvc.h | 2 ++ drivers/usb/gadget/function/uvc_v4l2.c | 19 +++++++++++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-)(quoted diff) +0
-0
b/drivers/usb/gadget/function/f_uvc.cindex439eba660e95..9b63b28a1ee3100644
drivers/usb/gadget/function/f_uvc.c uvc_function_set_alt(struct usb_function *f, unsigned · line 330
Quoted 7 lines
drivers/usb/gadget/function/f_uvc.c +2
-1@@ -325,17 +325,19 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)switch (alt) {case 0 : -if (uvc->state !=UVC_STATE_STREAMING ) +if (uvc->state !=UVC_STATE_STREAMING && + uvc->state !=UVC_STATE_STARTING )
Indentation is weird here, uvc should be aligned on the two lines.
Quoted 29 lines
drivers/usb/gadget/function/f_uvc.c +2
-1return 0 ;if (uvc->video.ep)usb_ep_disable (uvc->video.ep); + uvc->state =UVC_STATE_STOPPING ; +memset (&v4l2_event,0 ,sizeof (v4l2_event)); v4l2_event.type =UVC_EVENT_STREAMOFF ;v4l2_event_queue (&uvc->vdev, &v4l2_event); - uvc->state =UVC_STATE_CONNECTED ;return 0 ;drivers/usb/gadget/function/f_uvc.c +2
-0case 1 :@@ -354,6 +356,8 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) return ret;usb_ep_enable (uvc->video.ep); + uvc->state =UVC_STATE_STARTING ; +memset (&v4l2_event,0 ,sizeof (v4l2_event)); v4l2_event.type =UVC_EVENT_STREAMON ;v4l2_event_queue (&uvc->vdev, &v4l2_event);(quoted diff) +0
-0
b/drivers/usb/gadget/function/uvc.hindexa64e07e61f8c..afb2eac1f337100644
drivers/usb/gadget/function/uvc.h enum uvc_state { · line 135
Quoted 5 lines
drivers/usb/gadget/function/uvc.h +2
-0@@ -131,6 +131,8 @@ enum uvc_state {UVC_STATE_DISCONNECTED ,UVC_STATE_CONNECTED ,UVC_STATE_STREAMING , +UVC_STATE_STARTING , + UVC_STATE_STOPPING,
Let's order the states as theyr should normally occur, STARTING should come before STREAMING.
Quoted 9 lines
drivers/usb/gadget/function/uvc.h +0
-0};struct uvc_device {(quoted diff) +0
-0
b/drivers/usb/gadget/function/uvc_v4l2.cindex9a9019625496..fdf02b6987c0100644
drivers/usb/gadget/function/uvc_v4l2.c uvc_v4l2_streamon(struct file *file, void *fh, enum · line 197
Quoted 5 lines
drivers/usb/gadget/function/uvc_v4l2.c +2
-0@@ -193,6 +193,9 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type) struct uvc_video *video = &uvc->video;int ret; +if (uvc->state !=UVC_STATE_STARTING ) +return 0 ;
I would move this check after the next one, as the VIDIOC_STREAMON ioctl should fail if the type isn't valid, even if we're already streaming.
Furthermore, shouldn't we silently ignore the UVC_STATE_STREAMING only ? For other states, I think we should return an error, as starting the stream isn't valid for instance when the state is UVC_STATE_DISCONNECTED.
Quoted 19 lines
drivers/usb/gadget/function/uvc_v4l2.c +2
-1if (type != video->queue.queue.type)return -EINVAL ;@@ -201,12 +204,13 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type) if (ret < 0)return ret; + uvc->state =UVC_STATE_STREAMING ; +/* * Complete the alternate setting selection setup phase now that * userspace is ready to provide video frames. */ uvc_function_setup_continue (uvc); - uvc->state =UVC_STATE_STREAMING ;return 0 ; }
drivers/usb/gadget/function/uvc_v4l2.c uvc_v4l2_streamoff(struct file *file, void *fh, enum · line 227
Quoted 7 lines
drivers/usb/gadget/function/uvc_v4l2.c +4
-0@@ -217,11 +221,22 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) struct video_device *vdev = video_devdata(file);struct uvc_device *uvc =video_get_drvdata (vdev);struct uvc_video *video = &uvc->video; +int ret; + +if (uvc->state !=UVC_STATE_STOPPING ) +return 0 ;
Same comment here, this should go after the next check.
While I think extending the state machine this way makes sense, I believe you're introducing race conditions. The VIDIOC_STREAMON and VIDIOC_STREAMOFF ioctls can be issued by userspace at any time, completely asynchronously to the reception of SET_INTERFACE requests. You will need a lock to protect this. I recommend trying to draw sequence diagrams to see how the different events can race each other.
Quoted 15 lines
drivers/usb/gadget/function/uvc_v4l2.c +8
-1if (type != video->queue.queue.type)return -EINVAL ; -return uvcg_video_enable (video,0 ); + ret =uvcg_video_enable (video,0 ); +if (ret <0 ) +return ret; + + uvc->state =UVC_STATE_CONNECTED ; + +return 0 ; + } static int