From nobody Mon Apr 6 09:12:05 2026 Received: from mail-pj1-f73.google.com (mail-pj1-f73.google.com [209.85.216.73]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E1393378D8F for ; Fri, 20 Mar 2026 08:55:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773996933; cv=none; b=ttGOHB3I0hBBZjfJrQfP0Xmf7mLwJfcEqegsJ3JcPKto4XXrqK0vavYFfhQKmn9faw0aAio+i0uLlnsJUc8loyo4Lgx2KWJXjoiF4Gv2Y6Bs0wzJBCadfftNHQ5O0NhBLmq95lnIPP/aT1jClyqjgpMfnVcEoZ8buXmiRBKZ8rQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773996933; c=relaxed/simple; bh=WVxp0ZIk0xYVRKf3pptNJWqovoLfJ9UKaReWVwYXoRI=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=iFIGULv7D+VZeax051QABjHdwZaxeYw9C/UOZFsuPXylBu4YfM1koHSOEt3/kFPL8GUN9IViI3gV1HZeRRSEdjM7VPhAlE9KVAcoyh5l/F8XSm5PUiRh9m1/stXK3yGuZ1Vfjcly1W/TJPKxCWD11jTrAjHY+xIKMF+X5dVUuVA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--khtsai.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=bEVhC4aw; arc=none smtp.client-ip=209.85.216.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--khtsai.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="bEVhC4aw" Received: by mail-pj1-f73.google.com with SMTP id 98e67ed59e1d1-358df8fbd1cso2658647a91.0 for ; Fri, 20 Mar 2026 01:55:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1773996929; x=1774601729; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=+3atbIaOInnERa/VoAz+UbgXjD5q1aDENcAUOzWrVcA=; b=bEVhC4awlShF1WmqjoI6zSEJSx2dXkmLUCt+YJmASCgduC4kLe2sBcqW9zcqiiwkfZ MCSzwC9ngh+/LPsMBlfnxX+3g/F/ZHooL43I06cR4gnt3a0EHHFRtrq3rtHVNF6+7l76 LcWsFhGQNYBNpw7ij3oX7eczi13QaD1edVdU1wKIRvMq2bbm5UrwS278vvBbHSNjJVWE tYHfHBzQeZ77vAUl7YuZbYFAFUGK43KcDvDtptHTycHs4TN1IFsYNwW7XPHDTeO/nlZ2 /vHfM7/hXiphn8cgMr5Rgok0Xfcui5ekuBzKvO/IxTJ/1v8nVQj0buXZrbu167FY9htp zCiA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773996929; x=1774601729; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=+3atbIaOInnERa/VoAz+UbgXjD5q1aDENcAUOzWrVcA=; b=HCLq/QZi6kNCNTpilkQQE1sqXP5FeR9NPS7LxApeKP4WJqEjFtqBViVTSPD/SEBZR4 RbqCl7Ubk4JL1FZD3MGiL5R0ifCkFCO7MiT3pZ1qZCkCUNUO2cf0tWfaal/qlHnbsx6v iQ3h1PCbOlcrL7lm0Wnxx96kggDlA1jK38OMNwnwfCbtIYENJL7ebrpWsWcGGAjhI/gX w6x3Jrr8ikseBZUaYUJ5tyUeba9qGk918p6SrC+mpkJTaBP0uA+YG2ZHbKoqta7+u4v0 +htMwVN9lAwMxcbi9lgfefqT9UCWPHd8A7NHIOc82GVT8y9fJiKw6izZAhWslThTvXj5 dSBQ== X-Forwarded-Encrypted: i=1; AJvYcCUFMlRZuSjxF32T0mSbT5ZvJmz0IGHgJ575OTIlmx4yHQUSx8caISHfH75cAmyTag43+gyqrZ2nHlS+KEU=@vger.kernel.org X-Gm-Message-State: AOJu0Yz5+RXaNLSgkdxjAEEDbp18svX51bnTe+Q4gRjJh4X7AjWg185l 0x8c7/VUybRfiERjDHiiTCUL6wNlash44iOzzWw8dS1FE066lWHMPt3z4q3O+v9UHaiqR5tZju+ FMQ2RrQ== X-Received: from pjzg1.prod.google.com ([2002:a17:90a:e581:b0:359:fee7:eac2]) (user=khtsai job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:1dcb:b0:34c:c514:ee1f with SMTP id 98e67ed59e1d1-35bd2bf4d56mr1966801a91.11.1773996929139; Fri, 20 Mar 2026 01:55:29 -0700 (PDT) Date: Fri, 20 Mar 2026 16:54:49 +0800 In-Reply-To: <20260320-usb-net-lifecycle-v1-0-4886b578161b@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260320-usb-net-lifecycle-v1-0-4886b578161b@google.com> X-Developer-Key: i=khtsai@google.com; a=ed25519; pk=abA4Pw6dY2ZufSbSXW9mtp7xiv1AVPtgRhCFWJSEqLE= X-Developer-Signature: v=1; a=ed25519-sha256; t=1773996915; l=6962; i=khtsai@google.com; s=20250916; h=from:subject:message-id; bh=WVxp0ZIk0xYVRKf3pptNJWqovoLfJ9UKaReWVwYXoRI=; b=Gm9M57ASM4csFeNKTWED3B6hzlFROTR2MCxjFTnBBaRH7Cim2LsP4p6D8c4rZ8WLy7LIwR+EE QivIVHCuroPC8xdZHIGQRZnxB0eHFuqYdqbYpX0HkExsR5UpbnzzPNI X-Mailer: b4 0.14.3 Message-ID: <20260320-usb-net-lifecycle-v1-6-4886b578161b@google.com> Subject: [PATCH 6/7] usb: gadget: f_subset: Fix net_device lifecycle with device_move From: Kuen-Han Tsai To: Greg Kroah-Hartman , Kyungmin Park , Felipe Balbi , David Lechner Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Kuen-Han Tsai , stable@vger.kernel.org Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 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 interf= ace with backward compatibility") Cc: stable@vger.kernel.org Signed-off-by: Kuen-Han Tsai --- drivers/usb/gadget/function/f_subset.c | 57 +++++++++++++++++-------------= ---- drivers/usb/gadget/function/u_gether.h | 22 ++++++++----- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/drivers/usb/gadget/function/f_subset.c b/drivers/usb/gadget/fu= nction/f_subset.c index 74dc6da5c767..6e3265b8a3a0 100644 --- a/drivers/usb/gadget/function/f_subset.c +++ b/drivers/usb/gadget/function/f_subset.c @@ -299,25 +299,22 @@ geth_bind(struct usb_configuration *c, struct usb_fun= ction *f) struct usb_ep *ep; =20 struct f_gether_opts *gether_opts; + struct net_device *net __free(detach_gadget) =3D NULL; =20 gether_opts =3D container_of(f->fi, struct f_gether_opts, func_inst); =20 - /* - * in drivers/usb/gadget/configfs.c:configfs_composite_bind() - * configurations are bound in sequence with list_for_each_entry, - * in each configuration its functions are bound in sequence - * with list_for_each_entry, so we assume no race condition - * with regard to gether_opts->bound access - */ - if (!gether_opts->bound) { - mutex_lock(&gether_opts->lock); - gether_set_gadget(gether_opts->net, cdev->gadget); - status =3D gether_register_netdev(gether_opts->net); - mutex_unlock(&gether_opts->lock); - if (status) - return status; - gether_opts->bound =3D true; - } + scoped_guard(mutex, &gether_opts->lock) + if (gether_opts->bind_count =3D=3D 0 && !gether_opts->bound) { + if (!device_is_registered(&gether_opts->net->dev)) { + gether_set_gadget(gether_opts->net, cdev->gadget); + status =3D gether_register_netdev(gether_opts->net); + } else + status =3D gether_attach_gadget(gether_opts->net, cdev->gadget); + + if (status) + return status; + net =3D gether_opts->net; + } =20 us =3D usb_gstrings_attach(cdev, geth_strings, ARRAY_SIZE(geth_string_defs)); @@ -330,20 +327,18 @@ geth_bind(struct usb_configuration *c, struct usb_fun= ction *f) /* allocate instance-specific interface IDs */ status =3D usb_interface_id(c, f); if (status < 0) - goto fail; + return status; subset_data_intf.bInterfaceNumber =3D status; =20 - status =3D -ENODEV; - /* allocate instance-specific endpoints */ ep =3D usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc); if (!ep) - goto fail; + return -ENODEV; geth->port.in_ep =3D ep; =20 ep =3D usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc); if (!ep) - goto fail; + return -ENODEV; geth->port.out_ep =3D ep; =20 /* support all relevant hardware speeds... we expect that when @@ -361,21 +356,19 @@ geth_bind(struct usb_configuration *c, struct usb_fun= ction *f) status =3D usb_assign_descriptors(f, fs_eth_function, hs_eth_function, ss_eth_function, ss_eth_function); if (status) - goto fail; + return status; =20 /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code * until we're activated via set_alt(). */ =20 + gether_opts->bind_count++; + retain_and_null_ptr(net); + DBG(cdev, "CDC Subset: IN/%s OUT/%s\n", geth->port.in_ep->name, geth->port.out_ep->name); return 0; - -fail: - ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); - - return status; } =20 static inline struct f_gether_opts *to_f_gether_opts(struct config_item *i= tem) @@ -418,7 +411,7 @@ static void geth_free_inst(struct usb_function_instance= *f) struct f_gether_opts *opts; =20 opts =3D container_of(f, struct f_gether_opts, func_inst); - if (opts->bound) + if (device_is_registered(&opts->net->dev)) gether_cleanup(netdev_priv(opts->net)); else free_netdev(opts->net); @@ -462,8 +455,16 @@ static void geth_free(struct usb_function *f) =20 static void geth_unbind(struct usb_configuration *c, struct usb_function *= f) { + struct f_gether_opts *opts; + + opts =3D container_of(f->fi, struct f_gether_opts, func_inst); + geth_string_defs[0].id =3D 0; usb_free_all_descriptors(f); + + opts->bind_count--; + if (opts->bind_count =3D=3D 0 && !opts->bound) + gether_detach_gadget(opts->net); } =20 static struct usb_function *geth_alloc(struct usb_function_instance *fi) diff --git a/drivers/usb/gadget/function/u_gether.h b/drivers/usb/gadget/fu= nction/u_gether.h index 2f7a373ed449..e7b6b51f69c1 100644 --- a/drivers/usb/gadget/function/u_gether.h +++ b/drivers/usb/gadget/function/u_gether.h @@ -15,17 +15,25 @@ =20 #include =20 +/** + * struct f_gether_opts - subset function options + * @func_inst: USB function instance. + * @net: The net_device associated with the subset function. + * @bound: True if the net_device is shared and pre-registered during the + * legacy composite driver's bind phase (e.g., multi.c). If false, + * the subset function will register the net_device during its own + * bind phase. + * @bind_count: Tracks the number of configurations the subset function is + * bound to, preventing double-registration of the @net devic= e. + * @lock: Protects the data from concurrent access by configfs read/write + * and create symlink/remove symlink operations. + * @refcnt: Reference counter for the function instance. + */ struct f_gether_opts { struct usb_function_instance func_inst; struct net_device *net; bool bound; - - /* - * Read/write access to configfs attributes is handled by configfs. - * - * This is to protect the data from concurrent access by read/write - * and create symlink/remove symlink. - */ + int bind_count; struct mutex lock; int refcnt; }; --=20 2.53.0.959.g497ff81fa9-goog