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_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: 8cedba7c73af ("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>

authored by

Kuen-Han Tsai and committed by
Greg Kroah-Hartman
06524cd1 d9270c9a

+44 -35
+29 -28
drivers/usb/gadget/function/f_subset.c
··· 299 299 struct usb_ep *ep; 300 300 301 301 struct f_gether_opts *gether_opts; 302 + struct net_device *net __free(detach_gadget) = NULL; 302 303 303 304 gether_opts = container_of(f->fi, struct f_gether_opts, func_inst); 304 305 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 + } 321 318 322 319 us = usb_gstrings_attach(cdev, geth_strings, 323 320 ARRAY_SIZE(geth_string_defs)); ··· 327 330 /* allocate instance-specific interface IDs */ 328 331 status = usb_interface_id(c, f); 329 332 if (status < 0) 330 - goto fail; 333 + return status; 331 334 subset_data_intf.bInterfaceNumber = status; 332 - 333 - status = -ENODEV; 334 335 335 336 /* allocate instance-specific endpoints */ 336 337 ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc); 337 338 if (!ep) 338 - goto fail; 339 + return -ENODEV; 339 340 geth->port.in_ep = ep; 340 341 341 342 ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc); 342 343 if (!ep) 343 - goto fail; 344 + return -ENODEV; 344 345 geth->port.out_ep = ep; 345 346 346 347 /* support all relevant hardware speeds... we expect that when ··· 356 361 status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function, 357 362 ss_eth_function, ss_eth_function); 358 363 if (status) 359 - goto fail; 364 + return status; 360 365 361 366 /* NOTE: all that is done without knowing or caring about 362 367 * the network link ... which is unavailable to this code 363 368 * until we're activated via set_alt(). 364 369 */ 365 370 371 + gether_opts->bind_count++; 372 + retain_and_null_ptr(net); 373 + 366 374 DBG(cdev, "CDC Subset: IN/%s OUT/%s\n", 367 375 geth->port.in_ep->name, geth->port.out_ep->name); 368 376 return 0; 369 - 370 - fail: 371 - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); 372 - 373 - return status; 374 377 } 375 378 376 379 static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item) ··· 411 418 struct f_gether_opts *opts; 412 419 413 420 opts = container_of(f, struct f_gether_opts, func_inst); 414 - if (opts->bound) 421 + if (device_is_registered(&opts->net->dev)) 415 422 gether_cleanup(netdev_priv(opts->net)); 416 423 else 417 424 free_netdev(opts->net); ··· 455 462 456 463 static void geth_unbind(struct usb_configuration *c, struct usb_function *f) 457 464 { 465 + struct f_gether_opts *opts; 466 + 467 + opts = container_of(f->fi, struct f_gether_opts, func_inst); 468 + 458 469 geth_string_defs[0].id = 0; 459 470 usb_free_all_descriptors(f); 471 + 472 + opts->bind_count--; 473 + if (opts->bind_count == 0 && !opts->bound) 474 + gether_detach_gadget(opts->net); 460 475 } 461 476 462 477 static struct usb_function *geth_alloc(struct usb_function_instance *fi)
+15 -7
drivers/usb/gadget/function/u_gether.h
··· 15 15 16 16 #include <linux/usb/composite.h> 17 17 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 + */ 18 32 struct f_gether_opts { 19 33 struct usb_function_instance func_inst; 20 34 struct net_device *net; 21 35 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; 29 37 struct mutex lock; 30 38 int refcnt; 31 39 };