Kernel panic when unbinding UVC gadget function

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hey all,

I recently ran into a kernel panic when testing the UVC Gadget Driver.
The device ramdumps with the following stack when removing the UVC config from
configfs:

KP: Oops - BUG: Fatal exception: comm:Thread-6 PC:__list_del_entry_valid+0xb0/0xc4 LR:__list_del_entry_valid+0xb0/0xc4
PC: __list_del_entry_valid+0xb0 <FFFFFFE685330294>
LR: __list_del_entry_valid+0xb0 <FFFFFFE685330294>

[<FFFFFFE685330294>] __list_del_entry_valid+0xb0
[<FFFFFFE6857E50AC>] v4l2_fh_del+0x78
[<FFFFFFE685769774>] uvc_v4l2_release+0xd0
[<FFFFFFE6857D9B10>] v4l2_release+0xcc
[<FFFFFFE684EE192C>] __fput+0xf8
[<FFFFFFE684EE17CC>] ____fput+0xc
[<FFFFFFE684B5C9E0>] task_work_run+0x138

This looks like a side effect of
https://lore.kernel.org/lkml/20230608204517.105396-1-badhri@xxxxxxxxxx/.
Effectively, UVC function tried to disconnect the gadget before 
cleaning up resources. However, usb_gadget_unregister_driver which is 
removing the function prevents the gadget from disconnecting until the 
function is unbound.

As of the patch mentioned above, gadget_unbind_driver holds
udc->connect_lock and calls both usb_gadget_disconnect_locked and
udc->driver->unbind one after the other.

usb_gadget_disconnect_locked calls into UVC Gadget driver as follows:

1. usb_gadget_disconnect_locked
2. configfs_composite_disconnect
3. __composite_disconnect
4. uvc_function_disable

udc->driver->unbind calls into UVC Gadget driver as follows:

1. udc->driver->unbind
2. configfs_composite_unbind
3. purge_configs_funcs
4. uvc_function_unbind

uvc_function_disable notifies the userspace application with
UVC_EVENT_DISCONNECTED which causes the V4L2 node to be released
(or unsubscribed to). Either on unsubscribe or on release, the UVC Gadget
Driver calls uvc_function_disconnect before cleaning up resources. Following
is the problematic stack from uvc_v4l2_disable.

1. uvc_v4l2_disable
2. uvc_function_disconnect
3. usb_function_deactivate
4. usb_gadget_deactivate

usb_gadget_deactivate attempts to lock udc->connect_lock as of the patch
mentioned above.

This means that attempting to unregister the UVC Gadget Driver results in the
V4L2 resource cleanup being stuck behind udc->connect_lock, which will only
be released after uvc_function_unbind finishes. This results in either the
gadget deactivating after the unbind process has finished, or in a Kernel Panic
as it tries to cleanup a V4L2 node that has been purged.

This leaves us with two options:
1. We "fix" the locking in core.c to restore old behavior, and let the
   usb_gadget_deactivate call go through without locking. However,
   I am not sure about the specifics of the patch were and what exact issue it
   was trying to fix. 

   Badhri, would you know if relaxing the constraints on 
   usb_gadget_deactivate is feasible? It is possible that other gadget drivers
   run into similar issues as UVC driver.

3. UVC Gadget Driver calls usb_function_deactivate to take down the gadget if
   the userspace application stops listening to the V4L2 node. However, AFAICT
   disable is called as a part of the gadget resetting. So, if the V4L2 node 
   is released because of UVC_EVENT_DISCONNECT, we can skip calling 
   usb_function_deactivate as the gadget will be reset anyway.

   usb_function documentation seems to agree that if 'disable' is called,
   the gadget will be reset/reconfigured shortly:

     @disable: (REQUIRED) Indicates the function should be disabled.  Reasons
      *	  include host resetting or reconfiguring the gadget, and disconnection.

A dirty Patch for option 2 is attached below which skips calling 
usb_function_deactivate if uvc_function_disable was called before. It seems 
to work okay in testing. Let me know if the analysis and solutions seems okay
and I can upload a formal patch.

Thank you!

---
 drivers/usb/gadget/function/f_uvc.c | 12 ++++++++++--
 drivers/usb/gadget/function/uvc.h   |  1 +
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index 5e919fb65833..cef92243f1f7 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -385,7 +385,7 @@ uvc_function_disable(struct usb_function *f)
 	v4l2_event.type = UVC_EVENT_DISCONNECT;
 	v4l2_event_queue(&uvc->vdev, &v4l2_event);
 
-	uvc->state = UVC_STATE_DISCONNECTED;
+	uvc->state = UVC_STATE_HOST_DISCONNECTED;
 
 	usb_ep_disable(uvc->video.ep);
 	if (uvc->enable_interrupt_ep)
@@ -410,8 +410,16 @@ uvc_function_disconnect(struct uvc_device *uvc)
 {
 	int ret;
 
-	if ((ret = usb_function_deactivate(&uvc->func)) < 0)
+	if (uvc->state == UVC_STATE_HOST_DISCONNECTED) {
+		/*
+		 * Don't deactivate gadget as this is being called in
+		 * response to the host resetting. Gadget will be deactivated
+		 * anyway. Just update to state as acknowledgement
+		 */
+		uvc->state = UVC_STATE_DISCONNECTED;
+	} else if ((ret = usb_function_deactivate(&uvc->func)) < 0) {
 		uvcg_info(&uvc->func, "UVC disconnect failed with %d\n", ret);
+	}
 }
 
 /* --------------------------------------------------------------------------
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index 100475b1363e..f1e2bc98dc61 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -120,6 +120,7 @@ struct uvc_video {
 };
 
 enum uvc_state {
+	UVC_STATE_HOST_DISCONNECTED,
 	UVC_STATE_DISCONNECTED,
 	UVC_STATE_CONNECTED,
 	UVC_STATE_STREAMING,
-- 



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux