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_rndis: 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 borrowed_net flag is used to indicate whether the network device is
shared and pre-registered during the legacy driver's bind phase.

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

authored by

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

+48 -25
+25 -17
drivers/usb/gadget/function/f_rndis.c
··· 666 666 667 667 struct f_rndis_opts *rndis_opts; 668 668 struct usb_os_desc_table *os_desc_table __free(kfree) = NULL; 669 + struct net_device *net __free(detach_gadget) = NULL; 669 670 struct usb_request *request __free(free_usb_request) = NULL; 670 671 671 672 if (!can_support_rndis(c)) ··· 684 683 rndis_iad_descriptor.bFunctionClass = rndis_opts->class; 685 684 rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass; 686 685 rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol; 687 - } 688 686 689 - /* 690 - * in drivers/usb/gadget/configfs.c:configfs_composite_bind() 691 - * configurations are bound in sequence with list_for_each_entry, 692 - * in each configuration its functions are bound in sequence 693 - * with list_for_each_entry, so we assume no race condition 694 - * with regard to rndis_opts->bound access 695 - */ 696 - if (!rndis_opts->bound) { 697 - gether_set_gadget(rndis_opts->net, cdev->gadget); 698 - status = gether_register_netdev(rndis_opts->net); 699 - if (status) 700 - return status; 701 - rndis_opts->bound = true; 687 + if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) { 688 + if (!device_is_registered(&rndis_opts->net->dev)) { 689 + gether_set_gadget(rndis_opts->net, cdev->gadget); 690 + status = gether_register_netdev(rndis_opts->net); 691 + } else 692 + status = gether_attach_gadget(rndis_opts->net, cdev->gadget); 693 + 694 + if (status) 695 + return status; 696 + net = rndis_opts->net; 697 + } 702 698 } 703 699 704 700 us = usb_gstrings_attach(cdev, rndis_strings, ··· 794 796 } 795 797 rndis->notify_req = no_free_ptr(request); 796 798 799 + rndis_opts->bind_count++; 800 + retain_and_null_ptr(net); 801 + 797 802 /* NOTE: all that is done without knowing or caring about 798 803 * the network link ... which is unavailable to this code 799 804 * until we're activated via set_alt(). ··· 813 812 struct f_rndis_opts *opts; 814 813 815 814 opts = container_of(f, struct f_rndis_opts, func_inst); 816 - if (opts->bound) 815 + if (device_is_registered(&opts->net->dev)) 817 816 gether_cleanup(netdev_priv(opts->net)); 818 817 else 819 818 free_netdev(opts->net); 820 - opts->borrowed_net = opts->bound = true; 819 + opts->borrowed_net = true; 821 820 opts->net = net; 822 821 } 823 822 EXPORT_SYMBOL_GPL(rndis_borrow_net); ··· 875 874 876 875 opts = container_of(f, struct f_rndis_opts, func_inst); 877 876 if (!opts->borrowed_net) { 878 - if (opts->bound) 877 + if (device_is_registered(&opts->net->dev)) 879 878 gether_cleanup(netdev_priv(opts->net)); 880 879 else 881 880 free_netdev(opts->net); ··· 944 943 static void rndis_unbind(struct usb_configuration *c, struct usb_function *f) 945 944 { 946 945 struct f_rndis *rndis = func_to_rndis(f); 946 + struct f_rndis_opts *rndis_opts; 947 + 948 + rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); 947 949 948 950 kfree(f->os_desc_table); 949 951 f->os_desc_n = 0; ··· 954 950 955 951 kfree(rndis->notify_req->buf); 956 952 usb_ep_free_request(rndis->notify, rndis->notify_req); 953 + 954 + rndis_opts->bind_count--; 955 + if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) 956 + gether_detach_gadget(rndis_opts->net); 957 957 } 958 958 959 959 static struct usb_function *rndis_alloc(struct usb_function_instance *fi)
+23 -8
drivers/usb/gadget/function/u_rndis.h
··· 15 15 16 16 #include <linux/usb/composite.h> 17 17 18 + /** 19 + * struct f_rndis_opts - RNDIS function options 20 + * @func_inst: USB function instance. 21 + * @vendor_id: Vendor ID. 22 + * @manufacturer: Manufacturer string. 23 + * @net: The net_device associated with the RNDIS function. 24 + * @bind_count: Tracks the number of configurations the RNDIS function is 25 + * bound to, preventing double-registration of the @net device. 26 + * @borrowed_net: True if the net_device is shared and pre-registered during 27 + * the legacy composite driver's bind phase (e.g., multi.c). 28 + * If false, the RNDIS function will register the net_device 29 + * during its own bind phase. 30 + * @rndis_interf_group: ConfigFS group for RNDIS interface. 31 + * @rndis_os_desc: USB OS descriptor for RNDIS. 32 + * @rndis_ext_compat_id: Extended compatibility ID. 33 + * @class: USB class. 34 + * @subclass: USB subclass. 35 + * @protocol: USB protocol. 36 + * @lock: Protects the data from concurrent access by configfs read/write 37 + * and create symlink/remove symlink operations. 38 + * @refcnt: Reference counter for the function instance. 39 + */ 18 40 struct f_rndis_opts { 19 41 struct usb_function_instance func_inst; 20 42 u32 vendor_id; 21 43 const char *manufacturer; 22 44 struct net_device *net; 23 - bool bound; 45 + int bind_count; 24 46 bool borrowed_net; 25 47 26 48 struct config_group *rndis_interf_group; ··· 52 30 u8 class; 53 31 u8 subclass; 54 32 u8 protocol; 55 - 56 - /* 57 - * Read/write access to configfs attributes is handled by configfs. 58 - * 59 - * This is to protect the data from concurrent access by read/write 60 - * and create symlink/remove symlink. 61 - */ 62 33 struct mutex lock; 63 34 int refcnt; 64 35 };