Skip to content

Commit e367599

Browse files
Kuen-Han Tsaigregkh
authored andcommitted
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: f466c63 ("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>
1 parent 06524cd commit e367599

2 files changed

Lines changed: 48 additions & 25 deletions

File tree

drivers/usb/gadget/function/f_rndis.c

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
666666

667667
struct f_rndis_opts *rndis_opts;
668668
struct usb_os_desc_table *os_desc_table __free(kfree) = NULL;
669+
struct net_device *net __free(detach_gadget) = NULL;
669670
struct usb_request *request __free(free_usb_request) = NULL;
670671

671672
if (!can_support_rndis(c))
@@ -683,21 +684,18 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
683684
rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
684685
rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass;
685686
rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol;
686-
}
687687

688-
/*
689-
* in drivers/usb/gadget/configfs.c:configfs_composite_bind()
690-
* configurations are bound in sequence with list_for_each_entry,
691-
* in each configuration its functions are bound in sequence
692-
* with list_for_each_entry, so we assume no race condition
693-
* with regard to rndis_opts->bound access
694-
*/
695-
if (!rndis_opts->bound) {
696-
gether_set_gadget(rndis_opts->net, cdev->gadget);
697-
status = gether_register_netdev(rndis_opts->net);
698-
if (status)
699-
return status;
700-
rndis_opts->bound = true;
688+
if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) {
689+
if (!device_is_registered(&rndis_opts->net->dev)) {
690+
gether_set_gadget(rndis_opts->net, cdev->gadget);
691+
status = gether_register_netdev(rndis_opts->net);
692+
} else
693+
status = gether_attach_gadget(rndis_opts->net, cdev->gadget);
694+
695+
if (status)
696+
return status;
697+
net = rndis_opts->net;
698+
}
701699
}
702700

703701
us = usb_gstrings_attach(cdev, rndis_strings,
@@ -796,6 +794,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
796794
}
797795
rndis->notify_req = no_free_ptr(request);
798796

797+
rndis_opts->bind_count++;
798+
retain_and_null_ptr(net);
799+
799800
/* NOTE: all that is done without knowing or caring about
800801
* the network link ... which is unavailable to this code
801802
* until we're activated via set_alt().
@@ -812,11 +813,11 @@ void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net)
812813
struct f_rndis_opts *opts;
813814

814815
opts = container_of(f, struct f_rndis_opts, func_inst);
815-
if (opts->bound)
816+
if (device_is_registered(&opts->net->dev))
816817
gether_cleanup(netdev_priv(opts->net));
817818
else
818819
free_netdev(opts->net);
819-
opts->borrowed_net = opts->bound = true;
820+
opts->borrowed_net = true;
820821
opts->net = net;
821822
}
822823
EXPORT_SYMBOL_GPL(rndis_borrow_net);
@@ -874,7 +875,7 @@ static void rndis_free_inst(struct usb_function_instance *f)
874875

875876
opts = container_of(f, struct f_rndis_opts, func_inst);
876877
if (!opts->borrowed_net) {
877-
if (opts->bound)
878+
if (device_is_registered(&opts->net->dev))
878879
gether_cleanup(netdev_priv(opts->net));
879880
else
880881
free_netdev(opts->net);
@@ -943,13 +944,20 @@ static void rndis_free(struct usb_function *f)
943944
static void rndis_unbind(struct usb_configuration *c, struct usb_function *f)
944945
{
945946
struct f_rndis *rndis = func_to_rndis(f);
947+
struct f_rndis_opts *rndis_opts;
948+
949+
rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst);
946950

947951
kfree(f->os_desc_table);
948952
f->os_desc_n = 0;
949953
usb_free_all_descriptors(f);
950954

951955
kfree(rndis->notify_req->buf);
952956
usb_ep_free_request(rndis->notify, rndis->notify_req);
957+
958+
rndis_opts->bind_count--;
959+
if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net)
960+
gether_detach_gadget(rndis_opts->net);
953961
}
954962

955963
static struct usb_function *rndis_alloc(struct usb_function_instance *fi)

drivers/usb/gadget/function/u_rndis.h

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,34 @@
1515

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

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+
*/
1840
struct f_rndis_opts {
1941
struct usb_function_instance func_inst;
2042
u32 vendor_id;
2143
const char *manufacturer;
2244
struct net_device *net;
23-
bool bound;
45+
int bind_count;
2446
bool borrowed_net;
2547

2648
struct config_group *rndis_interf_group;
@@ -30,13 +52,6 @@ struct f_rndis_opts {
3052
u8 class;
3153
u8 subclass;
3254
u8 protocol;
33-
34-
/*
35-
* Read/write access to configfs attributes is handled by configfs.
36-
*
37-
* This is to protect the data from concurrent access by read/write
38-
* and create symlink/remove symlink.
39-
*/
4055
struct mutex lock;
4156
int refcnt;
4257
};

0 commit comments

Comments
 (0)