Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

usb: gadget: f_eem: 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: b29002a15794 ("usb: gadget: f_eem: 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-5-4886b578161b@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Kuen-Han Tsai and committed by
Greg Kroah-Hartman
d9270c9a b2cc4fae

+46 -34
+31 -28
drivers/usb/gadget/function/f_eem.c
··· 7 7 * Copyright (C) 2009 EF Johnson Technologies 8 8 */ 9 9 10 + #include <linux/cleanup.h> 10 11 #include <linux/kernel.h> 11 12 #include <linux/module.h> 12 13 #include <linux/device.h> ··· 252 251 struct usb_ep *ep; 253 252 254 253 struct f_eem_opts *eem_opts; 254 + struct net_device *net __free(detach_gadget) = NULL; 255 255 256 256 eem_opts = container_of(f->fi, struct f_eem_opts, func_inst); 257 - /* 258 - * in drivers/usb/gadget/configfs.c:configfs_composite_bind() 259 - * configurations are bound in sequence with list_for_each_entry, 260 - * in each configuration its functions are bound in sequence 261 - * with list_for_each_entry, so we assume no race condition 262 - * with regard to eem_opts->bound access 263 - */ 264 - if (!eem_opts->bound) { 265 - mutex_lock(&eem_opts->lock); 266 - gether_set_gadget(eem_opts->net, cdev->gadget); 267 - status = gether_register_netdev(eem_opts->net); 268 - mutex_unlock(&eem_opts->lock); 269 - if (status) 270 - return status; 271 - eem_opts->bound = true; 272 - } 257 + 258 + scoped_guard(mutex, &eem_opts->lock) 259 + if (eem_opts->bind_count == 0 && !eem_opts->bound) { 260 + if (!device_is_registered(&eem_opts->net->dev)) { 261 + gether_set_gadget(eem_opts->net, cdev->gadget); 262 + status = gether_register_netdev(eem_opts->net); 263 + } else 264 + status = gether_attach_gadget(eem_opts->net, cdev->gadget); 265 + 266 + if (status) 267 + return status; 268 + net = eem_opts->net; 269 + } 273 270 274 271 us = usb_gstrings_attach(cdev, eem_strings, 275 272 ARRAY_SIZE(eem_string_defs)); ··· 278 279 /* allocate instance-specific interface IDs */ 279 280 status = usb_interface_id(c, f); 280 281 if (status < 0) 281 - goto fail; 282 + return status; 282 283 eem->ctrl_id = status; 283 284 eem_intf.bInterfaceNumber = status; 284 - 285 - status = -ENODEV; 286 285 287 286 /* allocate instance-specific endpoints */ 288 287 ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc); 289 288 if (!ep) 290 - goto fail; 289 + return -ENODEV; 291 290 eem->port.in_ep = ep; 292 291 293 292 ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc); 294 293 if (!ep) 295 - goto fail; 294 + return -ENODEV; 296 295 eem->port.out_ep = ep; 297 296 298 297 /* support all relevant hardware speeds... we expect that when ··· 306 309 status = usb_assign_descriptors(f, eem_fs_function, eem_hs_function, 307 310 eem_ss_function, eem_ss_function); 308 311 if (status) 309 - goto fail; 312 + return status; 313 + 314 + eem_opts->bind_count++; 315 + retain_and_null_ptr(net); 310 316 311 317 DBG(cdev, "CDC Ethernet (EEM): IN/%s OUT/%s\n", 312 318 eem->port.in_ep->name, eem->port.out_ep->name); 313 319 return 0; 314 - 315 - fail: 316 - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); 317 - 318 - return status; 319 320 } 320 321 321 322 static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req) ··· 592 597 struct f_eem_opts *opts; 593 598 594 599 opts = container_of(f, struct f_eem_opts, func_inst); 595 - if (opts->bound) 600 + if (device_is_registered(&opts->net->dev)) 596 601 gether_cleanup(netdev_priv(opts->net)); 597 602 else 598 603 free_netdev(opts->net); ··· 635 640 636 641 static void eem_unbind(struct usb_configuration *c, struct usb_function *f) 637 642 { 643 + struct f_eem_opts *opts; 644 + 638 645 DBG(c->cdev, "eem unbind\n"); 639 646 647 + opts = container_of(f->fi, struct f_eem_opts, func_inst); 648 + 640 649 usb_free_all_descriptors(f); 650 + 651 + opts->bind_count--; 652 + if (opts->bind_count == 0 && !opts->bound) 653 + gether_detach_gadget(opts->net); 641 654 } 642 655 643 656 static struct usb_function *eem_alloc(struct usb_function_instance *fi)
+15 -6
drivers/usb/gadget/function/u_eem.h
··· 15 15 16 16 #include <linux/usb/composite.h> 17 17 18 + /** 19 + * struct f_eem_opts - EEM function options 20 + * @func_inst: USB function instance. 21 + * @net: The net_device associated with the EEM 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 EEM function will register the net_device during its own 25 + * bind phase. 26 + * @bind_count: Tracks the number of configurations the EEM 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 + */ 18 32 struct f_eem_opts { 19 33 struct usb_function_instance func_inst; 20 34 struct net_device *net; 21 35 bool bound; 36 + int bind_count; 22 37 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 - */ 29 38 struct mutex lock; 30 39 int refcnt; 31 40 };