Skip to content

Commit 06524cd

Browse files
Kuen-Han Tsaigregkh
authored andcommitted
usb: gadget: f_subset: Fix net_device lifecycle with device_move
The net_device is allocated during function instance creation and registered during the bind phase with the gadget device as its sysfs parent. When the function unbinds, the parent device is destroyed, but the net_device survives, resulting in dangling sysfs symlinks: console:/ # ls -l /sys/class/net/usb0 lrwxrwxrwx ... /sys/class/net/usb0 -> /sys/devices/platform/.../gadget.0/net/usb0 console:/ # ls -l /sys/devices/platform/.../gadget.0/net/usb0 ls: .../gadget.0/net/usb0: No such file or directory Use device_move() to reparent the net_device between the gadget device tree and /sys/devices/virtual across bind and unbind cycles. During the final unbind, calling device_move(NULL) moves the net_device to the virtual device tree before the gadget device is destroyed. On rebinding, device_move() reparents the device back under the new gadget, ensuring proper sysfs topology and power management ordering. To maintain compatibility with legacy composite drivers (e.g., multi.c), the bound flag is used to indicate whether the network device is shared and pre-registered during the legacy driver's bind phase. Fixes: 8cedba7 ("usb: gadget: f_subset: convert to new function interface with backward compatibility") Cc: stable@vger.kernel.org Signed-off-by: Kuen-Han Tsai <khtsai@google.com> Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-6-4886b578161b@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent d9270c9 commit 06524cd

2 files changed

Lines changed: 44 additions & 35 deletions

File tree

drivers/usb/gadget/function/f_subset.c

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -299,25 +299,22 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
299299
struct usb_ep *ep;
300300

301301
struct f_gether_opts *gether_opts;
302+
struct net_device *net __free(detach_gadget) = NULL;
302303

303304
gether_opts = container_of(f->fi, struct f_gether_opts, func_inst);
304305

305-
/*
306-
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
307-
* configurations are bound in sequence with list_for_each_entry,
308-
* in each configuration its functions are bound in sequence
309-
* with list_for_each_entry, so we assume no race condition
310-
* with regard to gether_opts->bound access
311-
*/
312-
if (!gether_opts->bound) {
313-
mutex_lock(&gether_opts->lock);
314-
gether_set_gadget(gether_opts->net, cdev->gadget);
315-
status = gether_register_netdev(gether_opts->net);
316-
mutex_unlock(&gether_opts->lock);
317-
if (status)
318-
return status;
319-
gether_opts->bound = true;
320-
}
306+
scoped_guard(mutex, &gether_opts->lock)
307+
if (gether_opts->bind_count == 0 && !gether_opts->bound) {
308+
if (!device_is_registered(&gether_opts->net->dev)) {
309+
gether_set_gadget(gether_opts->net, cdev->gadget);
310+
status = gether_register_netdev(gether_opts->net);
311+
} else
312+
status = gether_attach_gadget(gether_opts->net, cdev->gadget);
313+
314+
if (status)
315+
return status;
316+
net = gether_opts->net;
317+
}
321318

322319
us = usb_gstrings_attach(cdev, geth_strings,
323320
ARRAY_SIZE(geth_string_defs));
@@ -330,20 +327,18 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
330327
/* allocate instance-specific interface IDs */
331328
status = usb_interface_id(c, f);
332329
if (status < 0)
333-
goto fail;
330+
return status;
334331
subset_data_intf.bInterfaceNumber = status;
335332

336-
status = -ENODEV;
337-
338333
/* allocate instance-specific endpoints */
339334
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc);
340335
if (!ep)
341-
goto fail;
336+
return -ENODEV;
342337
geth->port.in_ep = ep;
343338

344339
ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc);
345340
if (!ep)
346-
goto fail;
341+
return -ENODEV;
347342
geth->port.out_ep = ep;
348343

349344
/* support all relevant hardware speeds... we expect that when
@@ -361,21 +356,19 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
361356
status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function,
362357
ss_eth_function, ss_eth_function);
363358
if (status)
364-
goto fail;
359+
return status;
365360

366361
/* NOTE: all that is done without knowing or caring about
367362
* the network link ... which is unavailable to this code
368363
* until we're activated via set_alt().
369364
*/
370365

366+
gether_opts->bind_count++;
367+
retain_and_null_ptr(net);
368+
371369
DBG(cdev, "CDC Subset: IN/%s OUT/%s\n",
372370
geth->port.in_ep->name, geth->port.out_ep->name);
373371
return 0;
374-
375-
fail:
376-
ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
377-
378-
return status;
379372
}
380373

381374
static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item)
@@ -418,7 +411,7 @@ static void geth_free_inst(struct usb_function_instance *f)
418411
struct f_gether_opts *opts;
419412

420413
opts = container_of(f, struct f_gether_opts, func_inst);
421-
if (opts->bound)
414+
if (device_is_registered(&opts->net->dev))
422415
gether_cleanup(netdev_priv(opts->net));
423416
else
424417
free_netdev(opts->net);
@@ -462,8 +455,16 @@ static void geth_free(struct usb_function *f)
462455

463456
static void geth_unbind(struct usb_configuration *c, struct usb_function *f)
464457
{
458+
struct f_gether_opts *opts;
459+
460+
opts = container_of(f->fi, struct f_gether_opts, func_inst);
461+
465462
geth_string_defs[0].id = 0;
466463
usb_free_all_descriptors(f);
464+
465+
opts->bind_count--;
466+
if (opts->bind_count == 0 && !opts->bound)
467+
gether_detach_gadget(opts->net);
467468
}
468469

469470
static struct usb_function *geth_alloc(struct usb_function_instance *fi)

drivers/usb/gadget/function/u_gether.h

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,25 @@
1515

1616
#include <linux/usb/composite.h>
1717

18+
/**
19+
* struct f_gether_opts - subset function options
20+
* @func_inst: USB function instance.
21+
* @net: The net_device associated with the subset function.
22+
* @bound: True if the net_device is shared and pre-registered during the
23+
* legacy composite driver's bind phase (e.g., multi.c). If false,
24+
* the subset function will register the net_device during its own
25+
* bind phase.
26+
* @bind_count: Tracks the number of configurations the subset function is
27+
* bound to, preventing double-registration of the @net device.
28+
* @lock: Protects the data from concurrent access by configfs read/write
29+
* and create symlink/remove symlink operations.
30+
* @refcnt: Reference counter for the function instance.
31+
*/
1832
struct f_gether_opts {
1933
struct usb_function_instance func_inst;
2034
struct net_device *net;
2135
bool bound;
22-
23-
/*
24-
* Read/write access to configfs attributes is handled by configfs.
25-
*
26-
* This is to protect the data from concurrent access by read/write
27-
* and create symlink/remove symlink.
28-
*/
36+
int bind_count;
2937
struct mutex lock;
3038
int refcnt;
3139
};

0 commit comments

Comments
 (0)