[PATCH 3/3] usb: gadget: f_tcm: don't hold dep_lock across unregister_gadget_item()

From: Slawomir Stepien

Date: Thu Oct 22 2026 - 09:56:15 EST


tcm_unregister_callback() runs when a TPG is disabled and unbinds the
configfs gadget via unregister_gadget_item() while holding
opts->dep_lock. unregister_gadget_item() takes gi->lock.

The opposite order is taken when a UDC is assigned to the gadget:
gadget_dev_desc_UDC_store() holds gi->lock across
usb_gadget_register_driver(), which binds the configuration and ends
up in tcm_bind(), which takes opts->dep_lock:

CPU0: echo 0 > .../tpgt_N/enable CPU1: echo <udc> > .../UDC
tcm_unregister_callback() gadget_dev_desc_UDC_store()
mutex_lock(&opts->dep_lock) mutex_lock(&gi->lock)
...
tcm_bind()
mutex_lock(&opts->dep_lock)
unregister_gadget_item()
mutex_lock(&gi->lock)

Disabling a TPG while a UDC is being assigned can therefore deadlock.

Here dep_lock only needs to cover the can_attach update, so clear
can_attach under the lock and call unregister_gadget_item() after
dropping it. Once can_attach is false, any later tcm_bind() fails with
-ENODEV, and a bind that already passed the check is undone by the
unregister that follows. The function item, and with it the gadget
item reached via ci_parent, stays alive because the TPG still holds a
configfs dependency on it.

Fixes: 4bb8548df632 ("usb: gadget: f_tcm: add configfs support")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Slawomir Stepien <sst@xxxxxxxxx>
---
drivers/usb/gadget/function/f_tcm.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index bcf11f4405ae..d4bb7ad3f1ae 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -2648,10 +2648,11 @@ static void tcm_unregister_callback(struct usb_function_instance *f)
struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst);

mutex_lock(&opts->dep_lock);
- unregister_gadget_item(opts->
- func_inst.group.cg_item.ci_parent->ci_parent);
opts->can_attach = false;
mutex_unlock(&opts->dep_lock);
+
+ /* Takes gi->lock; UDC_store holds gi->lock while tcm_bind() takes dep_lock. */
+ unregister_gadget_item(opts->func_inst.group.cg_item.ci_parent->ci_parent);
}

static int usbg_attach(struct usbg_tpg *tpg)
--
2.56.0