diff mbox series

[RFC] usb: gadget: uvc: sane shutdown on soft streamoff

Message ID 20230402200122.2919202-1-m.grzeschik@pengutronix.de (mailing list archive)
State New, archived
Headers show
Series [RFC] usb: gadget: uvc: sane shutdown on soft streamoff | expand

Commit Message

Michael Grzeschik April 2, 2023, 8:01 p.m. UTC
Pending requests in the gadget hardware get dequeued and returned with
ECONNRESET when the available endpoint is not available anymore. This
can be caused by an unplugged cable or the decision to shutdown the
stream, e.g. by switching the alt setting.

In both cases the returned completion handler is marking the gadget
with UVC_QUEUE_DISCONNECTED by calling uvcg_queue_cancel.

Since in userspace applications there might be two threads, one for the
bufferqueueing and one to handle the uvc events. It is likely that the
bufferqueueing thread did not receive the UVC_EVENT_STREAMOFF coming
from the alt_setting change early enough and still tries to queue a
buffer into the already disconnected marked device.

This leads buf_prepare to return ENODEV, which usually makes the
userspace application quit.

To fix the soft-shutdown case this patch is marking the alt setting
change before disabling the endpoint. This way the still completing
requests on the disabled endpoint can call uvcg_queue_cancel without
marking the device disconnected.

Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
---
Hi Laurent!

We are running into this issue in gstreamer when the host is stopping
the stream. In fact I am unsure if this is not also an issue when the
real unplug will appear.

Since the v4l2 device is available all the time, and the streamoff
callback is cleaning up all the pending buffers in uvc_video_enable(0),
also the ones that got queued in this short time window of:

 alt_setting(0) -> userspace event handling -> streamoff ioctl

Would it not be also possible to just drop the whole
UVC_QUEUE_DISCONNECTED mechanism?

Thanks,
Michael

 drivers/usb/gadget/function/f_uvc.c     | 3 ++-
 drivers/usb/gadget/function/uvc_video.c | 5 +++--
 2 files changed, 5 insertions(+), 3 deletions(-)

Comments

Laurent Pinchart April 5, 2023, 4:43 a.m. UTC | #1
Hi Michael,

(CC'ing Dan)

Thank you for the patch.

On Sun, Apr 02, 2023 at 10:01:22PM +0200, Michael Grzeschik wrote:
> Pending requests in the gadget hardware get dequeued and returned with
> ECONNRESET when the available endpoint is not available anymore. This
> can be caused by an unplugged cable or the decision to shutdown the
> stream, e.g. by switching the alt setting.
> 
> In both cases the returned completion handler is marking the gadget
> with UVC_QUEUE_DISCONNECTED by calling uvcg_queue_cancel.
> 
> Since in userspace applications there might be two threads, one for the
> bufferqueueing and one to handle the uvc events. It is likely that the
> bufferqueueing thread did not receive the UVC_EVENT_STREAMOFF coming
> from the alt_setting change early enough and still tries to queue a
> buffer into the already disconnected marked device.

Does this require two threads in userspace, or can it also happen in a
single-threaded application ? It seems like a typical race condition
between a userspace ioctl and a kernel-generated event.

> This leads buf_prepare to return ENODEV, which usually makes the
> userspace application quit.
> 
> To fix the soft-shutdown case this patch is marking the alt setting
> change before disabling the endpoint. This way the still completing
> requests on the disabled endpoint can call uvcg_queue_cancel without
> marking the device disconnected.
> 
> Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> ---
> Hi Laurent!
> 
> We are running into this issue in gstreamer when the host is stopping
> the stream. In fact I am unsure if this is not also an issue when the
> real unplug will appear.

Isn't this something that should be fixed in userspace (possibly in
addition to a kernel change to make userspace's life easier) ? Userspace
should be able to gracefully handle the device getting stopped. What
GStreamer element are you using, and is it an issue with the GStreamer
element, or with the application ?

> Since the v4l2 device is available all the time, and the streamoff
> callback is cleaning up all the pending buffers in uvc_video_enable(0),
> also the ones that got queued in this short time window of:
> 
>  alt_setting(0) -> userspace event handling -> streamoff ioctl
> 
> Would it not be also possible to just drop the whole
> UVC_QUEUE_DISCONNECTED mechanism?

Do you mean accepting the buffers queued by userspace, even if the
driver knows (or can know) they will never be sent out ?

>  drivers/usb/gadget/function/f_uvc.c     | 3 ++-
>  drivers/usb/gadget/function/uvc_video.c | 5 +++--
>  2 files changed, 5 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
> index 5e919fb6583301..01ab8c07d85be9 100644
> --- a/drivers/usb/gadget/function/f_uvc.c
> +++ b/drivers/usb/gadget/function/f_uvc.c
> @@ -337,6 +337,8 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
>  		if (uvc->state != UVC_STATE_STREAMING)
>  			return 0;
>  
> +		uvc->state = UVC_STATE_CONNECTED;
> +
>  		if (uvc->video.ep)
>  			usb_ep_disable(uvc->video.ep);
>  
> @@ -344,7 +346,6 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
>  		v4l2_event.type = UVC_EVENT_STREAMOFF;
>  		v4l2_event_queue(&uvc->vdev, &v4l2_event);
>  
> -		uvc->state = UVC_STATE_CONNECTED;
>  		return 0;
>  
>  	case 1:
> diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
> index dd1c6b2ca7c6f3..2f36fef3824f8e 100644
> --- a/drivers/usb/gadget/function/uvc_video.c
> +++ b/drivers/usb/gadget/function/uvc_video.c
> @@ -265,9 +265,10 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
>  		queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
>  		break;
>  
> -	case -ESHUTDOWN:	/* disconnect from host. */
> +	case -ESHUTDOWN:	/* disconnect from host or streamoff pending */
>  		uvcg_dbg(&video->uvc->func, "VS request cancelled.\n");
> -		uvcg_queue_cancel(queue, 1);
> +		uvcg_queue_cancel(queue,
> +				  uvc->state != UVC_STATE_STREAMING ? 0 : 1);
>  		break;
>  
>  	default:
diff mbox series

Patch

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 5e919fb6583301..01ab8c07d85be9 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -337,6 +337,8 @@  uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		if (uvc->state != UVC_STATE_STREAMING)
 			return 0;
 
+		uvc->state = UVC_STATE_CONNECTED;
+
 		if (uvc->video.ep)
 			usb_ep_disable(uvc->video.ep);
 
@@ -344,7 +346,6 @@  uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
 		v4l2_event.type = UVC_EVENT_STREAMOFF;
 		v4l2_event_queue(&uvc->vdev, &v4l2_event);
 
-		uvc->state = UVC_STATE_CONNECTED;
 		return 0;
 
 	case 1:
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index dd1c6b2ca7c6f3..2f36fef3824f8e 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -265,9 +265,10 @@  uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 		queue->flags |= UVC_QUEUE_DROP_INCOMPLETE;
 		break;
 
-	case -ESHUTDOWN:	/* disconnect from host. */
+	case -ESHUTDOWN:	/* disconnect from host or streamoff pending */
 		uvcg_dbg(&video->uvc->func, "VS request cancelled.\n");
-		uvcg_queue_cancel(queue, 1);
+		uvcg_queue_cancel(queue,
+				  uvc->state != UVC_STATE_STREAMING ? 0 : 1);
 		break;
 
 	default: