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

authored by

Kuen-Han Tsai and committed by
Greg Kroah-Hartman
b2cc4fae 57f531df

+38 -18
+23 -12
drivers/usb/gadget/function/f_ecm.c
··· 681 681 struct usb_ep *ep; 682 682 683 683 struct f_ecm_opts *ecm_opts; 684 + struct net_device *net __free(detach_gadget) = NULL; 684 685 struct usb_request *request __free(free_usb_request) = NULL; 685 686 686 687 if (!can_support_ecm(cdev->gadget)) ··· 689 688 690 689 ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst); 691 690 692 - mutex_lock(&ecm_opts->lock); 691 + scoped_guard(mutex, &ecm_opts->lock) 692 + if (ecm_opts->bind_count == 0 && !ecm_opts->bound) { 693 + if (!device_is_registered(&ecm_opts->net->dev)) { 694 + gether_set_gadget(ecm_opts->net, cdev->gadget); 695 + status = gether_register_netdev(ecm_opts->net); 696 + } else 697 + status = gether_attach_gadget(ecm_opts->net, cdev->gadget); 693 698 694 - gether_set_gadget(ecm_opts->net, cdev->gadget); 695 - 696 - if (!ecm_opts->bound) { 697 - status = gether_register_netdev(ecm_opts->net); 698 - ecm_opts->bound = true; 699 - } 700 - 701 - mutex_unlock(&ecm_opts->lock); 702 - if (status) 703 - return status; 699 + if (status) 700 + return status; 701 + net = ecm_opts->net; 702 + } 704 703 705 704 ecm_string_defs[1].s = ecm->ethaddr; 706 705 ··· 791 790 792 791 ecm->notify_req = no_free_ptr(request); 793 792 793 + ecm_opts->bind_count++; 794 + retain_and_null_ptr(net); 795 + 794 796 DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n", 795 797 ecm->port.in_ep->name, ecm->port.out_ep->name, 796 798 ecm->notify->name); ··· 840 836 struct f_ecm_opts *opts; 841 837 842 838 opts = container_of(f, struct f_ecm_opts, func_inst); 843 - if (opts->bound) 839 + if (device_is_registered(&opts->net->dev)) 844 840 gether_cleanup(netdev_priv(opts->net)); 845 841 else 846 842 free_netdev(opts->net); ··· 910 906 static void ecm_unbind(struct usb_configuration *c, struct usb_function *f) 911 907 { 912 908 struct f_ecm *ecm = func_to_ecm(f); 909 + struct f_ecm_opts *ecm_opts; 913 910 914 911 DBG(c->cdev, "ecm unbind\n"); 912 + 913 + ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst); 915 914 916 915 usb_free_all_descriptors(f); 917 916 ··· 925 918 926 919 kfree(ecm->notify_req->buf); 927 920 usb_ep_free_request(ecm->notify, ecm->notify_req); 921 + 922 + ecm_opts->bind_count--; 923 + if (ecm_opts->bind_count == 0 && !ecm_opts->bound) 924 + gether_detach_gadget(ecm_opts->net); 928 925 } 929 926 930 927 static struct usb_function *ecm_alloc(struct usb_function_instance *fi)
+15 -6
drivers/usb/gadget/function/u_ecm.h
··· 15 15 16 16 #include <linux/usb/composite.h> 17 17 18 + /** 19 + * struct f_ecm_opts - ECM function options 20 + * @func_inst: USB function instance. 21 + * @net: The net_device associated with the ECM 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 ECM function will register the net_device during its own 25 + * bind phase. 26 + * @bind_count: Tracks the number of configurations the ECM 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_ecm_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 };