Skip to content

Commit b2cc4fa

Browse files
Kuen-Han Tsaigregkh
authored andcommitted
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: fee562a ("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>
1 parent 57f531d commit b2cc4fa

2 files changed

Lines changed: 39 additions & 19 deletions

File tree

drivers/usb/gadget/function/f_ecm.c

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -681,25 +681,26 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
681681
struct usb_ep *ep;
682682

683683
struct f_ecm_opts *ecm_opts;
684+
struct net_device *net __free(detach_gadget) = NULL;
684685
struct usb_request *request __free(free_usb_request) = NULL;
685686

686687
if (!can_support_ecm(cdev->gadget))
687688
return -EINVAL;
688689

689690
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
690691

691-
mutex_lock(&ecm_opts->lock);
692-
693-
gether_set_gadget(ecm_opts->net, cdev->gadget);
694-
695-
if (!ecm_opts->bound) {
696-
status = gether_register_netdev(ecm_opts->net);
697-
ecm_opts->bound = true;
698-
}
699-
700-
mutex_unlock(&ecm_opts->lock);
701-
if (status)
702-
return status;
692+
scoped_guard(mutex, &ecm_opts->lock)
693+
if (ecm_opts->bind_count == 0 && !ecm_opts->bound) {
694+
if (!device_is_registered(&ecm_opts->net->dev)) {
695+
gether_set_gadget(ecm_opts->net, cdev->gadget);
696+
status = gether_register_netdev(ecm_opts->net);
697+
} else
698+
status = gether_attach_gadget(ecm_opts->net, cdev->gadget);
699+
700+
if (status)
701+
return status;
702+
net = ecm_opts->net;
703+
}
703704

704705
ecm_string_defs[1].s = ecm->ethaddr;
705706

@@ -790,6 +791,9 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
790791

791792
ecm->notify_req = no_free_ptr(request);
792793

794+
ecm_opts->bind_count++;
795+
retain_and_null_ptr(net);
796+
793797
DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n",
794798
ecm->port.in_ep->name, ecm->port.out_ep->name,
795799
ecm->notify->name);
@@ -836,7 +840,7 @@ static void ecm_free_inst(struct usb_function_instance *f)
836840
struct f_ecm_opts *opts;
837841

838842
opts = container_of(f, struct f_ecm_opts, func_inst);
839-
if (opts->bound)
843+
if (device_is_registered(&opts->net->dev))
840844
gether_cleanup(netdev_priv(opts->net));
841845
else
842846
free_netdev(opts->net);
@@ -906,9 +910,12 @@ static void ecm_free(struct usb_function *f)
906910
static void ecm_unbind(struct usb_configuration *c, struct usb_function *f)
907911
{
908912
struct f_ecm *ecm = func_to_ecm(f);
913+
struct f_ecm_opts *ecm_opts;
909914

910915
DBG(c->cdev, "ecm unbind\n");
911916

917+
ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
918+
912919
usb_free_all_descriptors(f);
913920

914921
if (atomic_read(&ecm->notify_count)) {
@@ -918,6 +925,10 @@ static void ecm_unbind(struct usb_configuration *c, struct usb_function *f)
918925

919926
kfree(ecm->notify_req->buf);
920927
usb_ep_free_request(ecm->notify, ecm->notify_req);
928+
929+
ecm_opts->bind_count--;
930+
if (ecm_opts->bind_count == 0 && !ecm_opts->bound)
931+
gether_detach_gadget(ecm_opts->net);
921932
}
922933

923934
static struct usb_function *ecm_alloc(struct usb_function_instance *fi)

drivers/usb/gadget/function/u_ecm.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,26 @@
1515

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

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+
*/
1832
struct f_ecm_opts {
1933
struct usb_function_instance func_inst;
2034
struct net_device *net;
2135
bool bound;
36+
int bind_count;
2237

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-
*/
2938
struct mutex lock;
3039
int refcnt;
3140
};

0 commit comments

Comments
 (0)