* [PATCH v2 1/7] Revert "usb: gadget: f_ncm: Fix atomic context locking issue"
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 2/7] Revert "usb: legacy: ncm: Fix NPE in gncm_bind" Kuen-Han Tsai
` (6 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai
This reverts commit 0d6c8144ca4d93253de952a5ea0028c19ed7ab68.
This commit is being reverted as part of a series-wide revert.
By deferring the net_device allocation to the bind() phase, a single
function instance will spawn multiple network devices if it is symlinked
to multiple USB configurations.
This causes regressions for userspace tools (like the postmarketOS DHCP
daemon) that rely on reading the interface name (e.g., "usb0") from
configfs. Currently, configfs returns the template "usb%d", causing the
userspace network setup to fail.
Crucially, because this patch breaks the 1:1 mapping between the
function instance and the network device, this naming issue cannot
simply be patched. Configfs only exposes a single 'ifname' attribute per
instance, making it impossible to accurately report the actual interface
name when multiple underlying network devices can exist for that single
instance.
All configurations tied to the same function instance are meant to share
a single network device. Revert this change to restore the 1:1 mapping
by allocating the network device at the instance level (alloc_inst).
Reported-by: David Heidelberg <david@ixit.cz>
Closes: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
Fixes: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/function/f_ncm.c | 29 +++++++++++++++-----------
drivers/usb/gadget/function/u_ether_configfs.h | 11 +++++++++-
drivers/usb/gadget/function/u_ncm.h | 1 +
3 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index 4da19864d70b..14fc7dce6f39 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -58,7 +58,6 @@ struct f_ncm {
u8 notify_state;
atomic_t notify_count;
bool is_open;
- bool is_connected;
const struct ndp_parser_opts *parser_opts;
bool is_crc;
@@ -865,6 +864,7 @@ static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
struct f_ncm *ncm = func_to_ncm(f);
+ struct f_ncm_opts *opts = func_to_ncm_opts(f);
struct usb_composite_dev *cdev = f->config->cdev;
/* Control interface has only altsetting 0 */
@@ -887,12 +887,13 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (alt > 1)
goto fail;
- if (ncm->is_connected) {
- DBG(cdev, "reset ncm\n");
- ncm->is_connected = false;
- gether_disconnect(&ncm->port);
- ncm_reset_values(ncm);
- }
+ scoped_guard(mutex, &opts->lock)
+ if (opts->net) {
+ DBG(cdev, "reset ncm\n");
+ opts->net = NULL;
+ gether_disconnect(&ncm->port);
+ ncm_reset_values(ncm);
+ }
/*
* CDC Network only sends data in non-default altsettings.
@@ -925,7 +926,8 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
net = gether_connect(&ncm->port);
if (IS_ERR(net))
return PTR_ERR(net);
- ncm->is_connected = true;
+ scoped_guard(mutex, &opts->lock)
+ opts->net = net;
}
spin_lock(&ncm->lock);
@@ -1372,14 +1374,16 @@ static int ncm_unwrap_ntb(struct gether *port,
static void ncm_disable(struct usb_function *f)
{
struct f_ncm *ncm = func_to_ncm(f);
+ struct f_ncm_opts *opts = func_to_ncm_opts(f);
struct usb_composite_dev *cdev = f->config->cdev;
DBG(cdev, "ncm deactivated\n");
- if (ncm->is_connected) {
- ncm->is_connected = false;
- gether_disconnect(&ncm->port);
- }
+ scoped_guard(mutex, &opts->lock)
+ if (opts->net) {
+ opts->net = NULL;
+ gether_disconnect(&ncm->port);
+ }
if (ncm->notify->enabled) {
usb_ep_disable(ncm->notify);
@@ -1683,6 +1687,7 @@ static struct usb_function_instance *ncm_alloc_inst(void)
if (!opts)
return ERR_PTR(-ENOMEM);
+ opts->net = NULL;
opts->ncm_os_desc.ext_compat_id = opts->ncm_ext_compat_id;
gether_setup_opts_default(&opts->net_opts, "usb");
diff --git a/drivers/usb/gadget/function/u_ether_configfs.h b/drivers/usb/gadget/function/u_ether_configfs.h
index 25d8fb05b598..217990a266b2 100644
--- a/drivers/usb/gadget/function/u_ether_configfs.h
+++ b/drivers/usb/gadget/function/u_ether_configfs.h
@@ -327,9 +327,18 @@ out: \
char *page) \
{ \
struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
+ const char *name; \
\
guard(mutex)(&opts->lock); \
- return sysfs_emit(page, "%s\n", opts->net_opts.name); \
+ rtnl_lock(); \
+ if (opts->net_opts.ifname_set) \
+ name = opts->net_opts.name; \
+ else if (opts->net) \
+ name = netdev_name(opts->net); \
+ else \
+ name = "(inactive net_device)"; \
+ rtnl_unlock(); \
+ return sysfs_emit(page, "%s\n", name); \
} \
\
static ssize_t _f_##_opts_ifname_store(struct config_item *item, \
diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/function/u_ncm.h
index 6d7538855744..d99330fe31e8 100644
--- a/drivers/usb/gadget/function/u_ncm.h
+++ b/drivers/usb/gadget/function/u_ncm.h
@@ -19,6 +19,7 @@
struct f_ncm_opts {
struct usb_function_instance func_inst;
+ struct net_device *net;
struct gether_opts net_opts;
struct config_group *ncm_interf_group;
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v2 2/7] Revert "usb: legacy: ncm: Fix NPE in gncm_bind"
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 1/7] Revert "usb: gadget: f_ncm: Fix atomic context locking issue" Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 3/7] Revert "usb: gadget: f_ncm: align net_device lifecycle with bind/unbind" Kuen-Han Tsai
` (5 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai
This reverts commit fde0634ad9856b3943a2d1a8cc8de174a63ac840.
This commit is being reverted as part of a series-wide revert.
By deferring the net_device allocation to the bind() phase, a single
function instance will spawn multiple network devices if it is symlinked
to multiple USB configurations.
This causes regressions for userspace tools (like the postmarketOS DHCP
daemon) that rely on reading the interface name (e.g., "usb0") from
configfs. Currently, configfs returns the template "usb%d", causing the
userspace network setup to fail.
Crucially, because this patch breaks the 1:1 mapping between the
function instance and the network device, this naming issue cannot
simply be patched. Configfs only exposes a single 'ifname' attribute per
instance, making it impossible to accurately report the actual interface
name when multiple underlying network devices can exist for that single
instance.
All configurations tied to the same function instance are meant to share
a single network device. Revert this change to restore the 1:1 mapping
by allocating the network device at the instance level (alloc_inst).
Reported-by: David Heidelberg <david@ixit.cz>
Closes: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
Fixes: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/legacy/ncm.c | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/drivers/usb/gadget/legacy/ncm.c b/drivers/usb/gadget/legacy/ncm.c
index e8d565534053..0f1b45e3abd1 100644
--- a/drivers/usb/gadget/legacy/ncm.c
+++ b/drivers/usb/gadget/legacy/ncm.c
@@ -15,10 +15,8 @@
/* #define DEBUG */
/* #define VERBOSE_DEBUG */
-#include <linux/hex.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/string.h>
#include <linux/usb/composite.h>
#include "u_ether.h"
@@ -131,7 +129,6 @@ static int gncm_bind(struct usb_composite_dev *cdev)
struct usb_gadget *gadget = cdev->gadget;
struct f_ncm_opts *ncm_opts;
int status;
- u8 mac[ETH_ALEN];
f_ncm_inst = usb_get_function_instance("ncm");
if (IS_ERR(f_ncm_inst))
@@ -139,15 +136,11 @@ static int gncm_bind(struct usb_composite_dev *cdev)
ncm_opts = container_of(f_ncm_inst, struct f_ncm_opts, func_inst);
- ncm_opts->net_opts.qmult = qmult;
- if (host_addr && mac_pton(host_addr, mac)) {
- memcpy(&ncm_opts->net_opts.host_mac, mac, ETH_ALEN);
+ gether_set_qmult(ncm_opts->net, qmult);
+ if (!gether_set_host_addr(ncm_opts->net, host_addr))
pr_info("using host ethernet address: %s", host_addr);
- }
- if (dev_addr && mac_pton(dev_addr, mac)) {
- memcpy(&ncm_opts->net_opts.dev_mac, mac, ETH_ALEN);
+ if (!gether_set_dev_addr(ncm_opts->net, dev_addr))
pr_info("using self ethernet address: %s", dev_addr);
- }
/* Allocate string descriptor numbers ... note that string
* contents can be overridden by the composite_dev glue.
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v2 3/7] Revert "usb: gadget: f_ncm: align net_device lifecycle with bind/unbind"
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 1/7] Revert "usb: gadget: f_ncm: Fix atomic context locking issue" Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 2/7] Revert "usb: legacy: ncm: Fix NPE in gncm_bind" Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 4/7] Revert "usb: gadget: u_ether: Add auto-cleanup helper for freeing net_device" Kuen-Han Tsai
` (4 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai, stable
This reverts commit 56a512a9b4107079f68701e7d55da8507eb963d9.
This commit is being reverted as part of a series-wide revert.
By deferring the net_device allocation to the bind() phase, a single
function instance will spawn multiple network devices if it is symlinked
to multiple USB configurations.
This causes regressions for userspace tools (like the postmarketOS DHCP
daemon) that rely on reading the interface name (e.g., "usb0") from
configfs. Currently, configfs returns the template "usb%d", causing the
userspace network setup to fail.
Crucially, because this patch breaks the 1:1 mapping between the
function instance and the network device, this naming issue cannot
simply be patched. Configfs only exposes a single 'ifname' attribute per
instance, making it impossible to accurately report the actual interface
name when multiple underlying network devices can exist for that single
instance.
All configurations tied to the same function instance are meant to share
a single network device. Revert this change to restore the 1:1 mapping
by allocating the network device at the instance level (alloc_inst).
Reported-by: David Heidelberg <david@ixit.cz>
Closes: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
Fixes: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Cc: stable@kernel.org
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/function/f_ncm.c | 128 ++++++++++++++++++------------------
drivers/usb/gadget/function/u_ncm.h | 4 +-
2 files changed, 66 insertions(+), 66 deletions(-)
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index 14fc7dce6f39..3d772c9beb91 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -83,11 +83,6 @@ static inline struct f_ncm *func_to_ncm(struct usb_function *f)
return container_of(f, struct f_ncm, port.func);
}
-static inline struct f_ncm_opts *func_to_ncm_opts(struct usb_function *f)
-{
- return container_of(f->fi, struct f_ncm_opts, func_inst);
-}
-
/*-------------------------------------------------------------------------*/
/*
@@ -864,7 +859,6 @@ static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
struct f_ncm *ncm = func_to_ncm(f);
- struct f_ncm_opts *opts = func_to_ncm_opts(f);
struct usb_composite_dev *cdev = f->config->cdev;
/* Control interface has only altsetting 0 */
@@ -887,13 +881,12 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (alt > 1)
goto fail;
- scoped_guard(mutex, &opts->lock)
- if (opts->net) {
- DBG(cdev, "reset ncm\n");
- opts->net = NULL;
- gether_disconnect(&ncm->port);
- ncm_reset_values(ncm);
- }
+ if (ncm->netdev) {
+ DBG(cdev, "reset ncm\n");
+ ncm->netdev = NULL;
+ gether_disconnect(&ncm->port);
+ ncm_reset_values(ncm);
+ }
/*
* CDC Network only sends data in non-default altsettings.
@@ -926,8 +919,7 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
net = gether_connect(&ncm->port);
if (IS_ERR(net))
return PTR_ERR(net);
- scoped_guard(mutex, &opts->lock)
- opts->net = net;
+ ncm->netdev = net;
}
spin_lock(&ncm->lock);
@@ -1374,16 +1366,14 @@ static int ncm_unwrap_ntb(struct gether *port,
static void ncm_disable(struct usb_function *f)
{
struct f_ncm *ncm = func_to_ncm(f);
- struct f_ncm_opts *opts = func_to_ncm_opts(f);
struct usb_composite_dev *cdev = f->config->cdev;
DBG(cdev, "ncm deactivated\n");
- scoped_guard(mutex, &opts->lock)
- if (opts->net) {
- opts->net = NULL;
- gether_disconnect(&ncm->port);
- }
+ if (ncm->netdev) {
+ ncm->netdev = NULL;
+ gether_disconnect(&ncm->port);
+ }
if (ncm->notify->enabled) {
usb_ep_disable(ncm->notify);
@@ -1443,44 +1433,39 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct f_ncm *ncm = func_to_ncm(f);
- struct f_ncm_opts *ncm_opts = func_to_ncm_opts(f);
struct usb_string *us;
int status = 0;
struct usb_ep *ep;
+ struct f_ncm_opts *ncm_opts;
struct usb_os_desc_table *os_desc_table __free(kfree) = NULL;
- struct net_device *netdev __free(free_gether_netdev) = NULL;
struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_ecm(cdev->gadget))
return -EINVAL;
+ ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst);
+
if (cdev->use_os_string) {
os_desc_table = kzalloc(sizeof(*os_desc_table), GFP_KERNEL);
if (!os_desc_table)
return -ENOMEM;
}
- netdev = gether_setup_default();
- if (IS_ERR(netdev))
- return -ENOMEM;
-
- scoped_guard(mutex, &ncm_opts->lock) {
- gether_apply_opts(netdev, &ncm_opts->net_opts);
- netdev->mtu = ncm_opts->max_segment_size - ETH_HLEN;
+ mutex_lock(&ncm_opts->lock);
+ gether_set_gadget(ncm_opts->net, cdev->gadget);
+ if (!ncm_opts->bound) {
+ ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN);
+ status = gether_register_netdev(ncm_opts->net);
}
+ mutex_unlock(&ncm_opts->lock);
- gether_set_gadget(netdev, cdev->gadget);
- status = gether_register_netdev(netdev);
if (status)
return status;
- /* export host's Ethernet address in CDC format */
- status = gether_get_host_addr_cdc(netdev, ncm->ethaddr,
- sizeof(ncm->ethaddr));
- if (status < 12)
- return -EINVAL;
- ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr;
+ ncm_opts->bound = true;
+
+ ncm_string_defs[1].s = ncm->ethaddr;
us = usb_gstrings_attach(cdev, ncm_strings,
ARRAY_SIZE(ncm_string_defs));
@@ -1578,8 +1563,6 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
f->os_desc_n = 1;
}
ncm->notify_req = no_free_ptr(request);
- ncm->netdev = no_free_ptr(netdev);
- ncm->port.ioport = netdev_priv(ncm->netdev);
DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n",
ncm->port.in_ep->name, ncm->port.out_ep->name,
@@ -1594,19 +1577,19 @@ static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item)
}
/* f_ncm_item_ops */
-USB_ETHER_OPTS_ITEM(ncm);
+USB_ETHERNET_CONFIGFS_ITEM(ncm);
/* f_ncm_opts_dev_addr */
-USB_ETHER_OPTS_ATTR_DEV_ADDR(ncm);
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ncm);
/* f_ncm_opts_host_addr */
-USB_ETHER_OPTS_ATTR_HOST_ADDR(ncm);
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ncm);
/* f_ncm_opts_qmult */
-USB_ETHER_OPTS_ATTR_QMULT(ncm);
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm);
/* f_ncm_opts_ifname */
-USB_ETHER_OPTS_ATTR_IFNAME(ncm);
+USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm);
static ssize_t ncm_opts_max_segment_size_show(struct config_item *item,
char *page)
@@ -1672,27 +1655,34 @@ static void ncm_free_inst(struct usb_function_instance *f)
struct f_ncm_opts *opts;
opts = container_of(f, struct f_ncm_opts, func_inst);
+ if (opts->bound)
+ gether_cleanup(netdev_priv(opts->net));
+ else
+ free_netdev(opts->net);
kfree(opts->ncm_interf_group);
kfree(opts);
}
static struct usb_function_instance *ncm_alloc_inst(void)
{
- struct usb_function_instance *ret;
+ struct f_ncm_opts *opts;
struct usb_os_desc *descs[1];
char *names[1];
struct config_group *ncm_interf_group;
- struct f_ncm_opts *opts __free(kfree) = kzalloc_obj(*opts);
+ opts = kzalloc_obj(*opts);
if (!opts)
return ERR_PTR(-ENOMEM);
-
- opts->net = NULL;
opts->ncm_os_desc.ext_compat_id = opts->ncm_ext_compat_id;
- gether_setup_opts_default(&opts->net_opts, "usb");
mutex_init(&opts->lock);
opts->func_inst.free_func_inst = ncm_free_inst;
+ opts->net = gether_setup_default();
+ if (IS_ERR(opts->net)) {
+ struct net_device *net = opts->net;
+ kfree(opts);
+ return ERR_CAST(net);
+ }
opts->max_segment_size = ETH_FRAME_LEN;
INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop);
@@ -1703,22 +1693,26 @@ static struct usb_function_instance *ncm_alloc_inst(void)
ncm_interf_group =
usb_os_desc_prepare_interf_dir(&opts->func_inst.group, 1, descs,
names, THIS_MODULE);
- if (IS_ERR(ncm_interf_group))
+ if (IS_ERR(ncm_interf_group)) {
+ ncm_free_inst(&opts->func_inst);
return ERR_CAST(ncm_interf_group);
+ }
opts->ncm_interf_group = ncm_interf_group;
- ret = &opts->func_inst;
- retain_and_null_ptr(opts);
- return ret;
+ return &opts->func_inst;
}
static void ncm_free(struct usb_function *f)
{
- struct f_ncm_opts *opts = func_to_ncm_opts(f);
+ struct f_ncm *ncm;
+ struct f_ncm_opts *opts;
- scoped_guard(mutex, &opts->lock)
- opts->refcnt--;
- kfree(func_to_ncm(f));
+ ncm = func_to_ncm(f);
+ opts = container_of(f->fi, struct f_ncm_opts, func_inst);
+ kfree(ncm);
+ mutex_lock(&opts->lock);
+ opts->refcnt--;
+ mutex_unlock(&opts->lock);
}
static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
@@ -1742,15 +1736,13 @@ static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
kfree(ncm->notify_req->buf);
usb_ep_free_request(ncm->notify, ncm->notify_req);
-
- ncm->port.ioport = NULL;
- gether_cleanup(netdev_priv(ncm->netdev));
}
static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
{
struct f_ncm *ncm;
struct f_ncm_opts *opts;
+ int status;
/* allocate and initialize one new instance */
ncm = kzalloc(sizeof(*ncm), GFP_KERNEL);
@@ -1758,12 +1750,22 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
return ERR_PTR(-ENOMEM);
opts = container_of(fi, struct f_ncm_opts, func_inst);
+ mutex_lock(&opts->lock);
+ opts->refcnt++;
- scoped_guard(mutex, &opts->lock)
- opts->refcnt++;
+ /* export host's Ethernet address in CDC format */
+ status = gether_get_host_addr_cdc(opts->net, ncm->ethaddr,
+ sizeof(ncm->ethaddr));
+ if (status < 12) { /* strlen("01234567890a") */
+ kfree(ncm);
+ mutex_unlock(&opts->lock);
+ return ERR_PTR(-EINVAL);
+ }
spin_lock_init(&ncm->lock);
ncm_reset_values(ncm);
+ ncm->port.ioport = netdev_priv(opts->net);
+ mutex_unlock(&opts->lock);
ncm->port.is_fixed = true;
ncm->port.supports_multi_frame = true;
diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/function/u_ncm.h
index d99330fe31e8..49ec095cdb4b 100644
--- a/drivers/usb/gadget/function/u_ncm.h
+++ b/drivers/usb/gadget/function/u_ncm.h
@@ -15,13 +15,11 @@
#include <linux/usb/composite.h>
-#include "u_ether.h"
-
struct f_ncm_opts {
struct usb_function_instance func_inst;
struct net_device *net;
+ bool bound;
- struct gether_opts net_opts;
struct config_group *ncm_interf_group;
struct usb_os_desc ncm_os_desc;
char ncm_ext_compat_id[16];
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v2 4/7] Revert "usb: gadget: u_ether: Add auto-cleanup helper for freeing net_device"
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
` (2 preceding siblings ...)
2026-03-09 12:04 ` [PATCH v2 3/7] Revert "usb: gadget: f_ncm: align net_device lifecycle with bind/unbind" Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 5/7] Revert "usb: gadget: u_ether: use <linux/hex.h> header file" Kuen-Han Tsai
` (3 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai
This reverts commit 0c0981126b99288ed354d3d414c8a5fd42ac9e25.
This commit is being reverted as part of a series-wide revert.
By deferring the net_device allocation to the bind() phase, a single
function instance will spawn multiple network devices if it is symlinked
to multiple USB configurations.
This causes regressions for userspace tools (like the postmarketOS DHCP
daemon) that rely on reading the interface name (e.g., "usb0") from
configfs. Currently, configfs returns the template "usb%d", causing the
userspace network setup to fail.
Crucially, because this patch breaks the 1:1 mapping between the
function instance and the network device, this naming issue cannot
simply be patched. Configfs only exposes a single 'ifname' attribute per
instance, making it impossible to accurately report the actual interface
name when multiple underlying network devices can exist for that single
instance.
All configurations tied to the same function instance are meant to share
a single network device. Revert this change to restore the 1:1 mapping
by allocating the network device at the instance level (alloc_inst).
Reported-by: David Heidelberg <david@ixit.cz>
Closes: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
Fixes: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/function/u_ether.c | 15 ---------------
drivers/usb/gadget/function/u_ether.h | 2 --
2 files changed, 17 deletions(-)
diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c
index 338f6e2a85a9..15685b2f8887 100644
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -1126,21 +1126,6 @@ void gether_cleanup(struct eth_dev *dev)
}
EXPORT_SYMBOL_GPL(gether_cleanup);
-void gether_unregister_free_netdev(struct net_device *net)
-{
- if (!net)
- return;
-
- struct eth_dev *dev = netdev_priv(net);
-
- if (net->reg_state == NETREG_REGISTERED) {
- unregister_netdev(net);
- flush_work(&dev->work);
- }
- free_netdev(net);
-}
-EXPORT_SYMBOL_GPL(gether_unregister_free_netdev);
-
/**
* gether_connect - notify network layer that USB link is active
* @link: the USB link, set up with endpoints, descriptors matching
diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h
index a212a8ec5eb1..63a0240df4d7 100644
--- a/drivers/usb/gadget/function/u_ether.h
+++ b/drivers/usb/gadget/function/u_ether.h
@@ -283,8 +283,6 @@ int gether_get_ifname(struct net_device *net, char *name, int len);
int gether_set_ifname(struct net_device *net, const char *name, int len);
void gether_cleanup(struct eth_dev *dev);
-void gether_unregister_free_netdev(struct net_device *net);
-DEFINE_FREE(free_gether_netdev, struct net_device *, gether_unregister_free_netdev(_T));
void gether_setup_opts_default(struct gether_opts *opts, const char *name);
void gether_apply_opts(struct net_device *net, struct gether_opts *opts);
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v2 5/7] Revert "usb: gadget: u_ether: use <linux/hex.h> header file"
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
` (3 preceding siblings ...)
2026-03-09 12:04 ` [PATCH v2 4/7] Revert "usb: gadget: u_ether: Add auto-cleanup helper for freeing net_device" Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 6/7] Revert "usb: gadget: u_ether: add gether_opts for config caching" Kuen-Han Tsai
` (2 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai
This reverts commit 7a7930c0f934fb0c46de6e7ca08e14e11df35dd6.
This commit is being reverted as part of a series-wide revert.
By deferring the net_device allocation to the bind() phase, a single
function instance will spawn multiple network devices if it is symlinked
to multiple USB configurations.
This causes regressions for userspace tools (like the postmarketOS DHCP
daemon) that rely on reading the interface name (e.g., "usb0") from
configfs. Currently, configfs returns the template "usb%d", causing the
userspace network setup to fail.
Crucially, because this patch breaks the 1:1 mapping between the
function instance and the network device, this naming issue cannot
simply be patched. Configfs only exposes a single 'ifname' attribute per
instance, making it impossible to accurately report the actual interface
name when multiple underlying network devices can exist for that single
instance.
All configurations tied to the same function instance are meant to share
a single network device. Revert this change to restore the 1:1 mapping
by allocating the network device at the instance level (alloc_inst).
Reported-by: David Heidelberg <david@ixit.cz>
Closes: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
Fixes: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/function/u_ether_configfs.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/usb/gadget/function/u_ether_configfs.h b/drivers/usb/gadget/function/u_ether_configfs.h
index 217990a266b2..39d3a261496d 100644
--- a/drivers/usb/gadget/function/u_ether_configfs.h
+++ b/drivers/usb/gadget/function/u_ether_configfs.h
@@ -14,7 +14,6 @@
#define __U_ETHER_CONFIGFS_H
#include <linux/cleanup.h>
-#include <linux/hex.h>
#include <linux/if_ether.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v2 6/7] Revert "usb: gadget: u_ether: add gether_opts for config caching"
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
` (4 preceding siblings ...)
2026-03-09 12:04 ` [PATCH v2 5/7] Revert "usb: gadget: u_ether: use <linux/hex.h> header file" Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-09 12:04 ` [PATCH v2 7/7] usb: gadget: f_ncm: Fix net_device lifecycle with device_move Kuen-Han Tsai
2026-03-13 12:40 ` [PATCH v2 0/7] usb: gadget: " Luca Weiss
7 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai
This reverts commit e065c6a7e46c2ee9c677fdbf50035323d2de1215.
This commit is being reverted as part of a series-wide revert.
By deferring the net_device allocation to the bind() phase, a single
function instance will spawn multiple network devices if it is symlinked
to multiple USB configurations.
This causes regressions for userspace tools (like the postmarketOS DHCP
daemon) that rely on reading the interface name (e.g., "usb0") from
configfs. Currently, configfs returns the template "usb%d", causing the
userspace network setup to fail.
Crucially, because this patch breaks the 1:1 mapping between the
function instance and the network device, this naming issue cannot
simply be patched. Configfs only exposes a single 'ifname' attribute per
instance, making it impossible to accurately report the actual interface
name when multiple underlying network devices can exist for that single
instance.
All configurations tied to the same function instance are meant to share
a single network device. Revert this change to restore the 1:1 mapping
by allocating the network device at the instance level (alloc_inst).
Reported-by: David Heidelberg <david@ixit.cz>
Closes: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
Fixes: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/function/u_ether.c | 30 -----
drivers/usb/gadget/function/u_ether.h | 28 ----
drivers/usb/gadget/function/u_ether_configfs.h | 176 -------------------------
3 files changed, 234 deletions(-)
diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c
index 15685b2f8887..c47965d850d4 100644
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -1040,36 +1040,6 @@ int gether_set_ifname(struct net_device *net, const char *name, int len)
}
EXPORT_SYMBOL_GPL(gether_set_ifname);
-void gether_setup_opts_default(struct gether_opts *opts, const char *name)
-{
- opts->qmult = QMULT_DEFAULT;
- snprintf(opts->name, sizeof(opts->name), "%s%%d", name);
- eth_random_addr(opts->dev_mac);
- opts->addr_assign_type = NET_ADDR_RANDOM;
- eth_random_addr(opts->host_mac);
-}
-EXPORT_SYMBOL_GPL(gether_setup_opts_default);
-
-void gether_apply_opts(struct net_device *net, struct gether_opts *opts)
-{
- struct eth_dev *dev = netdev_priv(net);
-
- dev->qmult = opts->qmult;
-
- if (opts->ifname_set) {
- strscpy(net->name, opts->name, sizeof(net->name));
- dev->ifname_set = true;
- }
-
- memcpy(dev->host_mac, opts->host_mac, sizeof(dev->host_mac));
-
- if (opts->addr_assign_type == NET_ADDR_SET) {
- memcpy(dev->dev_mac, opts->dev_mac, sizeof(dev->dev_mac));
- net->addr_assign_type = opts->addr_assign_type;
- }
-}
-EXPORT_SYMBOL_GPL(gether_apply_opts);
-
void gether_suspend(struct gether *link)
{
struct eth_dev *dev = link->ioport;
diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h
index 63a0240df4d7..34be220cef77 100644
--- a/drivers/usb/gadget/function/u_ether.h
+++ b/drivers/usb/gadget/function/u_ether.h
@@ -38,31 +38,6 @@
struct eth_dev;
-/**
- * struct gether_opts - Options for Ethernet gadget function instances
- * @name: Pattern for the network interface name (e.g., "usb%d").
- * Used to generate the net device name.
- * @qmult: Queue length multiplier for high/super speed.
- * @host_mac: The MAC address to be used by the host side.
- * @dev_mac: The MAC address to be used by the device side.
- * @ifname_set: True if the interface name pattern has been set by userspace.
- * @addr_assign_type: The method used for assigning the device MAC address
- * (e.g., NET_ADDR_RANDOM, NET_ADDR_SET).
- *
- * This structure caches network-related settings provided through configfs
- * before the net_device is fully instantiated. This allows for early
- * configuration while deferring net_device allocation until the function
- * is bound.
- */
-struct gether_opts {
- char name[IFNAMSIZ];
- unsigned int qmult;
- u8 host_mac[ETH_ALEN];
- u8 dev_mac[ETH_ALEN];
- bool ifname_set;
- unsigned char addr_assign_type;
-};
-
/*
* This represents the USB side of an "ethernet" link, managed by a USB
* function which provides control and (maybe) framing. Two functions
@@ -284,9 +259,6 @@ int gether_set_ifname(struct net_device *net, const char *name, int len);
void gether_cleanup(struct eth_dev *dev);
-void gether_setup_opts_default(struct gether_opts *opts, const char *name);
-void gether_apply_opts(struct net_device *net, struct gether_opts *opts);
-
void gether_suspend(struct gether *link);
void gether_resume(struct gether *link);
diff --git a/drivers/usb/gadget/function/u_ether_configfs.h b/drivers/usb/gadget/function/u_ether_configfs.h
index 39d3a261496d..51f0d79e5eca 100644
--- a/drivers/usb/gadget/function/u_ether_configfs.h
+++ b/drivers/usb/gadget/function/u_ether_configfs.h
@@ -13,12 +13,6 @@
#ifndef __U_ETHER_CONFIGFS_H
#define __U_ETHER_CONFIGFS_H
-#include <linux/cleanup.h>
-#include <linux/if_ether.h>
-#include <linux/mutex.h>
-#include <linux/netdevice.h>
-#include <linux/rtnetlink.h>
-
#define USB_ETHERNET_CONFIGFS_ITEM(_f_) \
static void _f_##_attr_release(struct config_item *item) \
{ \
@@ -203,174 +197,4 @@ out: \
\
CONFIGFS_ATTR(_f_##_opts_, _n_)
-#define USB_ETHER_OPTS_ITEM(_f_) \
- static void _f_##_attr_release(struct config_item *item) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- \
- usb_put_function_instance(&opts->func_inst); \
- } \
- \
- static struct configfs_item_operations _f_##_item_ops = { \
- .release = _f_##_attr_release, \
- }
-
-#define USB_ETHER_OPTS_ATTR_DEV_ADDR(_f_) \
- static ssize_t _f_##_opts_dev_addr_show(struct config_item *item, \
- char *page) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- \
- guard(mutex)(&opts->lock); \
- return sysfs_emit(page, "%pM\n", opts->net_opts.dev_mac); \
- } \
- \
- static ssize_t _f_##_opts_dev_addr_store(struct config_item *item, \
- const char *page, size_t len) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- u8 new_addr[ETH_ALEN]; \
- const char *p = page; \
- \
- guard(mutex)(&opts->lock); \
- if (opts->refcnt) \
- return -EBUSY; \
- \
- for (int i = 0; i < ETH_ALEN; i++) { \
- unsigned char num; \
- if ((*p == '.') || (*p == ':')) \
- p++; \
- num = hex_to_bin(*p++) << 4; \
- num |= hex_to_bin(*p++); \
- new_addr[i] = num; \
- } \
- if (!is_valid_ether_addr(new_addr)) \
- return -EINVAL; \
- memcpy(opts->net_opts.dev_mac, new_addr, ETH_ALEN); \
- opts->net_opts.addr_assign_type = NET_ADDR_SET; \
- return len; \
- } \
- \
- CONFIGFS_ATTR(_f_##_opts_, dev_addr)
-
-#define USB_ETHER_OPTS_ATTR_HOST_ADDR(_f_) \
- static ssize_t _f_##_opts_host_addr_show(struct config_item *item, \
- char *page) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- \
- guard(mutex)(&opts->lock); \
- return sysfs_emit(page, "%pM\n", opts->net_opts.host_mac); \
- } \
- \
- static ssize_t _f_##_opts_host_addr_store(struct config_item *item, \
- const char *page, size_t len) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- u8 new_addr[ETH_ALEN]; \
- const char *p = page; \
- \
- guard(mutex)(&opts->lock); \
- if (opts->refcnt) \
- return -EBUSY; \
- \
- for (int i = 0; i < ETH_ALEN; i++) { \
- unsigned char num; \
- if ((*p == '.') || (*p == ':')) \
- p++; \
- num = hex_to_bin(*p++) << 4; \
- num |= hex_to_bin(*p++); \
- new_addr[i] = num; \
- } \
- if (!is_valid_ether_addr(new_addr)) \
- return -EINVAL; \
- memcpy(opts->net_opts.host_mac, new_addr, ETH_ALEN); \
- return len; \
- } \
- \
- CONFIGFS_ATTR(_f_##_opts_, host_addr)
-
-#define USB_ETHER_OPTS_ATTR_QMULT(_f_) \
- static ssize_t _f_##_opts_qmult_show(struct config_item *item, \
- char *page) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- \
- guard(mutex)(&opts->lock); \
- return sysfs_emit(page, "%u\n", opts->net_opts.qmult); \
- } \
- \
- static ssize_t _f_##_opts_qmult_store(struct config_item *item, \
- const char *page, size_t len) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- u32 val; \
- int ret; \
- \
- guard(mutex)(&opts->lock); \
- if (opts->refcnt) \
- return -EBUSY; \
- \
- ret = kstrtou32(page, 0, &val); \
- if (ret) \
- return ret; \
- \
- opts->net_opts.qmult = val; \
- return len; \
- } \
- \
- CONFIGFS_ATTR(_f_##_opts_, qmult)
-
-#define USB_ETHER_OPTS_ATTR_IFNAME(_f_) \
- static ssize_t _f_##_opts_ifname_show(struct config_item *item, \
- char *page) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- const char *name; \
- \
- guard(mutex)(&opts->lock); \
- rtnl_lock(); \
- if (opts->net_opts.ifname_set) \
- name = opts->net_opts.name; \
- else if (opts->net) \
- name = netdev_name(opts->net); \
- else \
- name = "(inactive net_device)"; \
- rtnl_unlock(); \
- return sysfs_emit(page, "%s\n", name); \
- } \
- \
- static ssize_t _f_##_opts_ifname_store(struct config_item *item, \
- const char *page, size_t len) \
- { \
- struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \
- char tmp[IFNAMSIZ]; \
- const char *p; \
- size_t c_len = len; \
- \
- if (c_len > 0 && page[c_len - 1] == '\n') \
- c_len--; \
- \
- if (c_len >= sizeof(tmp)) \
- return -E2BIG; \
- \
- strscpy(tmp, page, c_len + 1); \
- if (!dev_valid_name(tmp)) \
- return -EINVAL; \
- \
- /* Require exactly one %d */ \
- p = strchr(tmp, '%'); \
- if (!p || p[1] != 'd' || strchr(p + 2, '%')) \
- return -EINVAL; \
- \
- guard(mutex)(&opts->lock); \
- if (opts->refcnt) \
- return -EBUSY; \
- strscpy(opts->net_opts.name, tmp, sizeof(opts->net_opts.name)); \
- opts->net_opts.ifname_set = true; \
- return len; \
- } \
- \
- CONFIGFS_ATTR(_f_##_opts_, ifname)
-
#endif /* __U_ETHER_CONFIGFS_H */
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v2 7/7] usb: gadget: f_ncm: Fix net_device lifecycle with device_move
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
` (5 preceding siblings ...)
2026-03-09 12:04 ` [PATCH v2 6/7] Revert "usb: gadget: u_ether: add gether_opts for config caching" Kuen-Han Tsai
@ 2026-03-09 12:04 ` Kuen-Han Tsai
2026-03-15 5:21 ` Val Packett
2026-03-13 12:40 ` [PATCH v2 0/7] usb: gadget: " Luca Weiss
7 siblings, 1 reply; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-09 12:04 UTC (permalink / raw)
To: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, Kuen-Han Tsai, stable
The network device outlived its parent gadget device during
disconnection, resulting in dangling sysfs links and null pointer
dereference problems.
A prior attempt to solve this by removing SET_NETDEV_DEV entirely [1]
was reverted due to power management ordering concerns and a NO-CARRIER
regression.
A subsequent attempt to defer net_device allocation to bind [2] broke
1:1 mapping between function instance and network device, making it
impossible for configfs to report the resolved interface name. This
results in a regression where the DHCP server fails on pmOS.
Use device_move to reparent the net_device between the gadget device and
/sys/devices/virtual/ across bind/unbind cycles. This preserves the
network interface across USB reconnection, allowing the DHCP server to
retain their binding.
Introduce gether_attach_gadget()/gether_detach_gadget() helpers and use
__free(detach_gadget) macro to undo attachment on bind failure. The
bind_count ensures device_move executes only on the first bind.
[1] https://lore.kernel.org/lkml/f2a4f9847617a0929d62025748384092e5f35cce.camel@crapouillou.net/
[2] https://lore.kernel.org/linux-usb/795ea759-7eaf-4f78-81f4-01ffbf2d7961@ixit.cz/
Fixes: 40d133d7f542 ("usb: gadget: f_ncm: convert to new function interface with backward compatibility")
Cc: stable@kernel.org
Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
---
drivers/usb/gadget/function/f_ncm.c | 38 +++++++++++++++++++++++------------
drivers/usb/gadget/function/u_ether.c | 22 ++++++++++++++++++++
drivers/usb/gadget/function/u_ether.h | 26 ++++++++++++++++++++++++
drivers/usb/gadget/function/u_ncm.h | 2 +-
4 files changed, 74 insertions(+), 14 deletions(-)
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
index 3d772c9beb91..a6fa5ed3d6cb 100644
--- a/drivers/usb/gadget/function/f_ncm.c
+++ b/drivers/usb/gadget/function/f_ncm.c
@@ -1439,6 +1439,7 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
struct f_ncm_opts *ncm_opts;
struct usb_os_desc_table *os_desc_table __free(kfree) = NULL;
+ struct net_device *net __free(detach_gadget) = NULL;
struct usb_request *request __free(free_usb_request) = NULL;
if (!can_support_ecm(cdev->gadget))
@@ -1452,18 +1453,19 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
return -ENOMEM;
}
- mutex_lock(&ncm_opts->lock);
- gether_set_gadget(ncm_opts->net, cdev->gadget);
- if (!ncm_opts->bound) {
- ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN);
- status = gether_register_netdev(ncm_opts->net);
- }
- mutex_unlock(&ncm_opts->lock);
-
- if (status)
- return status;
-
- ncm_opts->bound = true;
+ scoped_guard(mutex, &ncm_opts->lock)
+ if (ncm_opts->bind_count == 0) {
+ if (!device_is_registered(&ncm_opts->net->dev)) {
+ ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN);
+ gether_set_gadget(ncm_opts->net, cdev->gadget);
+ status = gether_register_netdev(ncm_opts->net);
+ } else
+ status = gether_attach_gadget(ncm_opts->net, cdev->gadget);
+
+ if (status)
+ return status;
+ net = ncm_opts->net;
+ }
ncm_string_defs[1].s = ncm->ethaddr;
@@ -1564,6 +1566,9 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
}
ncm->notify_req = no_free_ptr(request);
+ ncm_opts->bind_count++;
+ retain_and_null_ptr(net);
+
DBG(cdev, "CDC Network: IN/%s OUT/%s NOTIFY/%s\n",
ncm->port.in_ep->name, ncm->port.out_ep->name,
ncm->notify->name);
@@ -1655,7 +1660,7 @@ static void ncm_free_inst(struct usb_function_instance *f)
struct f_ncm_opts *opts;
opts = container_of(f, struct f_ncm_opts, func_inst);
- if (opts->bound)
+ if (device_is_registered(&opts->net->dev))
gether_cleanup(netdev_priv(opts->net));
else
free_netdev(opts->net);
@@ -1718,9 +1723,12 @@ static void ncm_free(struct usb_function *f)
static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct f_ncm *ncm = func_to_ncm(f);
+ struct f_ncm_opts *ncm_opts;
DBG(c->cdev, "ncm unbind\n");
+ ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst);
+
hrtimer_cancel(&ncm->task_timer);
kfree(f->os_desc_table);
@@ -1736,6 +1744,10 @@ static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
kfree(ncm->notify_req->buf);
usb_ep_free_request(ncm->notify, ncm->notify_req);
+
+ ncm_opts->bind_count--;
+ if (ncm_opts->bind_count == 0)
+ gether_detach_gadget(ncm_opts->net);
}
static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c
index c47965d850d4..1a9e7c495e2e 100644
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -897,6 +897,28 @@ void gether_set_gadget(struct net_device *net, struct usb_gadget *g)
}
EXPORT_SYMBOL_GPL(gether_set_gadget);
+int gether_attach_gadget(struct net_device *net, struct usb_gadget *g)
+{
+ int ret;
+
+ ret = device_move(&net->dev, &g->dev, DPM_ORDER_DEV_AFTER_PARENT);
+ if (ret)
+ return ret;
+
+ gether_set_gadget(net, g);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(gether_attach_gadget);
+
+void gether_detach_gadget(struct net_device *net)
+{
+ struct eth_dev *dev = netdev_priv(net);
+
+ device_move(&net->dev, NULL, DPM_ORDER_NONE);
+ dev->gadget = NULL;
+}
+EXPORT_SYMBOL_GPL(gether_detach_gadget);
+
int gether_set_dev_addr(struct net_device *net, const char *dev_addr)
{
struct eth_dev *dev;
diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h
index 34be220cef77..c85a1cf3c115 100644
--- a/drivers/usb/gadget/function/u_ether.h
+++ b/drivers/usb/gadget/function/u_ether.h
@@ -150,6 +150,32 @@ static inline struct net_device *gether_setup_default(void)
*/
void gether_set_gadget(struct net_device *net, struct usb_gadget *g);
+/**
+ * gether_attach_gadget - Reparent net_device to the gadget device.
+ * @net: The network device to reparent.
+ * @g: The target USB gadget device to parent to.
+ *
+ * This function moves the network device to be a child of the USB gadget
+ * device in the device hierarchy. This is typically done when the function
+ * is bound to a configuration.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int gether_attach_gadget(struct net_device *net, struct usb_gadget *g);
+
+/**
+ * gether_detach_gadget - Detach net_device from its gadget parent.
+ * @net: The network device to detach.
+ *
+ * This function moves the network device to be a child of the virtual
+ * devices parent, effectively detaching it from the USB gadget device
+ * hierarchy. This is typically done when the function is unbound
+ * from a configuration but the instance is not yet freed.
+ */
+void gether_detach_gadget(struct net_device *net);
+
+DEFINE_FREE(detach_gadget, struct net_device *, if (_T) gether_detach_gadget(_T))
+
/**
* gether_set_dev_addr - initialize an ethernet-over-usb link with eth address
* @net: device representing this link
diff --git a/drivers/usb/gadget/function/u_ncm.h b/drivers/usb/gadget/function/u_ncm.h
index 49ec095cdb4b..b1f3db8b68c1 100644
--- a/drivers/usb/gadget/function/u_ncm.h
+++ b/drivers/usb/gadget/function/u_ncm.h
@@ -18,7 +18,7 @@
struct f_ncm_opts {
struct usb_function_instance func_inst;
struct net_device *net;
- bool bound;
+ int bind_count;
struct config_group *ncm_interf_group;
struct usb_os_desc ncm_os_desc;
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v2 7/7] usb: gadget: f_ncm: Fix net_device lifecycle with device_move
2026-03-09 12:04 ` [PATCH v2 7/7] usb: gadget: f_ncm: Fix net_device lifecycle with device_move Kuen-Han Tsai
@ 2026-03-15 5:21 ` Val Packett
2026-03-16 6:03 ` Kuen-Han Tsai
0 siblings, 1 reply; 14+ messages in thread
From: Val Packett @ 2026-03-15 5:21 UTC (permalink / raw)
To: Kuen-Han Tsai, Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, stable
Hi,
On 3/9/26 9:04 AM, Kuen-Han Tsai wrote:
> The network device outlived its parent gadget device during
> disconnection, resulting in dangling sysfs links and null pointer
> dereference problems.
>
> A prior attempt to solve this by removing SET_NETDEV_DEV entirely [1]
> was reverted due to power management ordering concerns and a NO-CARRIER
> regression.
>
> A subsequent attempt to defer net_device allocation to bind [2] broke
> 1:1 mapping between function instance and network device, making it
> impossible for configfs to report the resolved interface name. This
> results in a regression where the DHCP server fails on pmOS.
>
> [..]
I just saw that this was the last commit touching u_ether while
debugging… the DHCP server failing on pmOS. (In the initrd, even).
Specifically, udev calling ethtool_get_drvinfo and eth_get_drvinfo
dereferencing an unset dev->gadget:
[ 7.528277] [pmOS-rd]: Setting up USB gadget through configfs
[ 7.539437] configfs-gadget.g1 gadget.0: HOST MAC 2a:a6:63:b7:92:23
[ 7.545914] configfs-gadget.g1 gadget.0: MAC 76:1d:2b:16:aa:25
[ 7.577888] [pmOS-rd]: Trying to start server with parameters: Server
IP addr: 172.16.42.1:67, client IP addr: 172.16.42.2, interface: usb0
[ 7.591522] [pmOS-rd]: Entering debug shell
[ 7.597590] Unable to handle kernel NULL pointer dereference at
virtual address 0000000000000080
[ 7.606670] Mem abort info:
[ 7.609571] ESR = 0x0000000096000004
[ 7.613462] EC = 0x25: DABT (current EL), IL = 32 bits
[ 7.618942] SET = 0, FnV = 0
[ 7.622105] EA = 0, S1PTW = 0
[ 7.625354] FSC = 0x04: level 0 translation fault
[ 7.630395] Data abort info:
[ 7.630398] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
[ 7.630401] CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[ 7.630404] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[ 7.630407] user pgtable: 4k pages, 48-bit VAs, pgdp=0000000107b18000
[ 7.630411] [0000000000000080] pgd=0000000000000000, p4d=0000000000000000
[ 7.630420] Internal error: Oops: 0000000096000004 [#1] SMP
[ 7.630425] Modules linked in: typec msm ubwc_config mdt_loader ocmem
rtc_pm8xxx drm_gpuvm drm_exec i2c_qcom_geni llcc_qcom gpi gpu_sched
drm_client_lib phy_qcom_snps_femto_v2 drm_display_helper cec
drm_dp_aux_bus icc_bwmon drm_kms_helper drm backlight ufs_qcom
phy_qcom_qmp_ufs icc_osm_l3 pmic_glink pdr_interface qcom_pdr_msg
qmi_helpers
[ 7.630486] CPU: 1 UID: 0 PID: 175 Comm: (udev-worker) Tainted: G
W 7.0.0-rc3-next-20260313-00118-gf4f287b6004a-dirty #59 PREEMPT(full)
[ 7.630493] Tainted: [W]=WARN
[ 7.630495] Hardware name: Motorola edge 30 (DT)
[ 7.630499] pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS
BTYPE=--)
[ 7.630503] pc : eth_get_drvinfo+0x50/0x90 <..snip..>
[ 7.630595] Call trace:
[ 7.630598] eth_get_drvinfo+0x50/0x90 (P)
[ 7.630608] ethtool_get_drvinfo+0x5c/0x1f0
[ 7.630617] __dev_ethtool+0xaec/0x1fe0
[ 7.630622] dev_ethtool+0x134/0x2e0
[ 7.630627] dev_ioctl+0x338/0x560
[ 7.630633] sock_do_ioctl+0xe0/0x128
[ 7.630642] sock_ioctl+0x2cc/0x3e0
[ 7.630647] __arm64_sys_ioctl+0xac/0x108
[ 7.630656] invoke_syscall.constprop.0+0x48/0x100
[ 7.630664] el0_svc_common.constprop.0+0x40/0xe8
[ 7.630670] do_el0_svc+0x24/0x38
[ 7.630676] el0_svc+0x34/0x180
[ 7.642931] [pmOS-rd]: /usr/bin/buffyboard
[ 7.644473] el0t_64_sync_handler+0xa0/0xe8
[ 7.644482] el0t_64_sync+0x17c/0x180
[ 7.644491] Code: 91094021 94134bd9 f9457680 d2800402 (f9404001)
[ 7.644495] ---[ end trace 0000000000000000 ]---
As a "workaround", this works:
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -113,8 +113,14 @@
strscpy(p->driver, "g_ether", sizeof(p->driver));
strscpy(p->version, UETH__VERSION, sizeof(p->version));
- strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
- strscpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
+ if (dev->gadget) {
+ strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
+ strscpy(p->bus_info, dev_name(&dev->gadget->dev),
sizeof(p->bus_info));
+ } else {
+ pr_warn("%s: called with no gadget set\n", __func__);
+ strscpy(p->fw_version, "N/A", sizeof(p->fw_version));
+ strscpy(p->bus_info, "platform", sizeof(p->bus_info));
+ }
}
/* REVISIT can also support:
..or would that not be a workaround? The lifecycle of gadget being set
seems kinda decoupled from the lifecycle of the registration (??) And as
long as it's registered, the dev info can be queried (?)
Thanks,
~val
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v2 7/7] usb: gadget: f_ncm: Fix net_device lifecycle with device_move
2026-03-15 5:21 ` Val Packett
@ 2026-03-16 6:03 ` Kuen-Han Tsai
0 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-16 6:03 UTC (permalink / raw)
To: Val Packett
Cc: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park, David Heidelberg,
Ernest Van Hoecke, Jon Hunter, LI Qingwu, linux-usb, linux-kernel,
stable
Hi Val,
On Sun, Mar 15, 2026 at 1:21 PM Val Packett <val@packett.cool> wrote:
>
> Hi,
>
> On 3/9/26 9:04 AM, Kuen-Han Tsai wrote:
> > The network device outlived its parent gadget device during
> > disconnection, resulting in dangling sysfs links and null pointer
> > dereference problems.
> >
> > A prior attempt to solve this by removing SET_NETDEV_DEV entirely [1]
> > was reverted due to power management ordering concerns and a NO-CARRIER
> > regression.
> >
> > A subsequent attempt to defer net_device allocation to bind [2] broke
> > 1:1 mapping between function instance and network device, making it
> > impossible for configfs to report the resolved interface name. This
> > results in a regression where the DHCP server fails on pmOS.
> >
> > [..]
>
> I just saw that this was the last commit touching u_ether while
> debugging… the DHCP server failing on pmOS. (In the initrd, even).
>
> Specifically, udev calling ethtool_get_drvinfo and eth_get_drvinfo
> dereferencing an unset dev->gadget:
Thanks for the report and testing.
I can reproduce the problem on a Pixel 3 by dropping into the pmOS
debug shell. When pmOS drops into the debug shell, it temporarily
unbinds the gadget to reconfigure the USB functions. Since my recent
patch intentionally reparented the net_device to /sys/devices/virtual
during unbind, dev>gadget became NULL. Meanwhile, the ethtool queries
on the surviving interface, leading to a null pointer dereference.
>
> [ 7.528277] [pmOS-rd]: Setting up USB gadget through configfs
> [ 7.539437] configfs-gadget.g1 gadget.0: HOST MAC 2a:a6:63:b7:92:23
> [ 7.545914] configfs-gadget.g1 gadget.0: MAC 76:1d:2b:16:aa:25
> [ 7.577888] [pmOS-rd]: Trying to start server with parameters: Server
> IP addr: 172.16.42.1:67, client IP addr: 172.16.42.2, interface: usb0
> [ 7.591522] [pmOS-rd]: Entering debug shell
> [ 7.597590] Unable to handle kernel NULL pointer dereference at
> virtual address 0000000000000080
> [ 7.606670] Mem abort info:
> [ 7.609571] ESR = 0x0000000096000004
> [ 7.613462] EC = 0x25: DABT (current EL), IL = 32 bits
> [ 7.618942] SET = 0, FnV = 0
> [ 7.622105] EA = 0, S1PTW = 0
> [ 7.625354] FSC = 0x04: level 0 translation fault
> [ 7.630395] Data abort info:
> [ 7.630398] ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
> [ 7.630401] CM = 0, WnR = 0, TnD = 0, TagAccess = 0
> [ 7.630404] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
> [ 7.630407] user pgtable: 4k pages, 48-bit VAs, pgdp=0000000107b18000
> [ 7.630411] [0000000000000080] pgd=0000000000000000, p4d=0000000000000000
> [ 7.630420] Internal error: Oops: 0000000096000004 [#1] SMP
> [ 7.630425] Modules linked in: typec msm ubwc_config mdt_loader ocmem
> rtc_pm8xxx drm_gpuvm drm_exec i2c_qcom_geni llcc_qcom gpi gpu_sched
> drm_client_lib phy_qcom_snps_femto_v2 drm_display_helper cec
> drm_dp_aux_bus icc_bwmon drm_kms_helper drm backlight ufs_qcom
> phy_qcom_qmp_ufs icc_osm_l3 pmic_glink pdr_interface qcom_pdr_msg
> qmi_helpers
> [ 7.630486] CPU: 1 UID: 0 PID: 175 Comm: (udev-worker) Tainted: G
> W 7.0.0-rc3-next-20260313-00118-gf4f287b6004a-dirty #59 PREEMPT(full)
> [ 7.630493] Tainted: [W]=WARN
> [ 7.630495] Hardware name: Motorola edge 30 (DT)
> [ 7.630499] pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS
> BTYPE=--)
> [ 7.630503] pc : eth_get_drvinfo+0x50/0x90 <..snip..>
> [ 7.630595] Call trace:
> [ 7.630598] eth_get_drvinfo+0x50/0x90 (P)
> [ 7.630608] ethtool_get_drvinfo+0x5c/0x1f0
> [ 7.630617] __dev_ethtool+0xaec/0x1fe0
> [ 7.630622] dev_ethtool+0x134/0x2e0
> [ 7.630627] dev_ioctl+0x338/0x560
> [ 7.630633] sock_do_ioctl+0xe0/0x128
> [ 7.630642] sock_ioctl+0x2cc/0x3e0
> [ 7.630647] __arm64_sys_ioctl+0xac/0x108
> [ 7.630656] invoke_syscall.constprop.0+0x48/0x100
> [ 7.630664] el0_svc_common.constprop.0+0x40/0xe8
> [ 7.630670] do_el0_svc+0x24/0x38
> [ 7.630676] el0_svc+0x34/0x180
> [ 7.642931] [pmOS-rd]: /usr/bin/buffyboard
> [ 7.644473] el0t_64_sync_handler+0xa0/0xe8
> [ 7.644482] el0t_64_sync+0x17c/0x180
> [ 7.644491] Code: 91094021 94134bd9 f9457680 d2800402 (f9404001)
> [ 7.644495] ---[ end trace 0000000000000000 ]---
>
> As a "workaround", this works:
>
>
> --- a/drivers/usb/gadget/function/u_ether.c
> +++ b/drivers/usb/gadget/function/u_ether.c
> @@ -113,8 +113,14 @@
>
> strscpy(p->driver, "g_ether", sizeof(p->driver));
> strscpy(p->version, UETH__VERSION, sizeof(p->version));
> - strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
> - strscpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
> + if (dev->gadget) {
> + strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
> + strscpy(p->bus_info, dev_name(&dev->gadget->dev),
> sizeof(p->bus_info));
> + } else {
> + pr_warn("%s: called with no gadget set\n", __func__);
> + strscpy(p->fw_version, "N/A", sizeof(p->fw_version));
> + strscpy(p->bus_info, "platform", sizeof(p->bus_info));
> + }
> }
>
> /* REVISIT can also support:
>
> ..or would that not be a workaround? The lifecycle of gadget being set
> seems kinda decoupled from the lifecycle of the registration (??) And as
> long as it's registered, the dev info can be queried (?)
>
>
> Thanks,
> ~val
>
I believe your suggested fix correctly handles this detached state.
One minor suggestion: looking at ethtool_get_drvinfo() in
net/ethtool/ioctl.c, we can simply skip the strscpy calls entirely
when dev->gadget is NULL. ethtool_get_drvinfo() checks if bus_info or
fw_version are empty strings and handles the fallback natively, so we
don't need to explicitly copy "N/A" or "platform".
I'll send out a standalone fix shortly and will include the
Suggested-by and Reported-by tags for you. Thanks again for catching
this!
Regards,
Kuen-Han
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move
2026-03-09 12:04 [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move Kuen-Han Tsai
` (6 preceding siblings ...)
2026-03-09 12:04 ` [PATCH v2 7/7] usb: gadget: f_ncm: Fix net_device lifecycle with device_move Kuen-Han Tsai
@ 2026-03-13 12:40 ` Luca Weiss
2026-03-16 6:17 ` Kuen-Han Tsai
7 siblings, 1 reply; 14+ messages in thread
From: Luca Weiss @ 2026-03-13 12:40 UTC (permalink / raw)
To: Kuen-Han Tsai, Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park
Cc: David Heidelberg, Ernest Van Hoecke, Jon Hunter, LI Qingwu,
linux-usb, linux-kernel, stable
Hi Kuen-Han,
On Mon Mar 9, 2026 at 1:04 PM CET, Kuen-Han Tsai wrote:
> PROBLEMS
> --------
> The net_device in f_ncm is allocated at function instance creation
> and registered at bind time with the gadget device as its sysfs parent.
> When the gadget unbinds, the parent device is destroyed but the
> net_device survives, leaving dangling sysfs symlinks and a NULL pointer
> dereference when userspace accesses the orphaned interface:
>
> Problem 1: Unable to handle kernel NULL pointer dereference
> Call trace:
> __pi_strlen+0x14/0x150
> rtnl_fill_ifinfo+0x6b4/0x708
> rtmsg_ifinfo_build_skb+0xd8/0x13c
> ...
> netlink_sendmsg+0x2e0/0x3d4
>
> Problem 2: Dangling sysfs symlinks
> console:/ # ls -l /sys/class/net/ncm0
> lrwxrwxrwx ... /sys/class/net/ncm0 ->
> /sys/devices/platform/.../gadget.0/net/ncm0
> console:/ # ls -l /sys/devices/platform/.../gadget.0/net/ncm0
> ls: .../gadget.0/net/ncm0: No such file or directory
>
> BACKGROUND & THE REVERTS
> ------------------------
> The deferred allocation causes a regression for userspace tools during
> network setup (such as the postmarketOS DHCP daemon). By moving the
> allocation out of alloc_inst, configfs returns the name pattern "usb%d"
> instead of the actual interface name (e.g., "usb0") when userspace reads
> the 'ifname' attribute.
>
> Investigating a fix for this naming issue revealed a deeper
> architectural flaw introduced by the series. Deferring the allocation to
> bind() means that a single function instance will spawn multiple network
> devices if it is symlinked to multiple USB configurations.
>
> Because all configurations tied to the same function instance are
> architecturally designed to share a single network device, and configfs
> only exposes a single 'ifname' attribute per instance, this 1-to-many
> bug cannot be safely patched.
>
> To restore the correct 1:1 mapping and resolve the userspace
> regressions, this series reverts the changes in reverse order, returning
> the net_device allocation back to the instance level (alloc_inst).
>
> THE NEW SOLUTION
> ----------------
> Use device_move() to reparent the net_device between the gadget device
> tree and /sys/devices/virtual across bind/unbind cycles. On the last
> unbind, device_move(NULL) moves the net_device to the virtual device
> tree before the gadget device is destroyed. On rebind, device_move()
> reparents it back under the new gadget, restoring proper sysfs topology
> and power management ordering.
>
> The 1:1 mapping between function instance and net_device is maintained,
> and configfs always reports the resolved interface name.
>
> A bind_count tracks how many configurations reference the function
> instance, ensuring device_move fires only on the first bind.
> __free(detach_gadget) ensures the net_device is moved back to virtual
> if bind fails after a successful device_move, preventing dangling
> sysfs on partial bind failure.
Applying this series on v7.0-rc3 fixes the reported issues for me on
Qualcomm-based Fairphone (Gen. 6). For v7.0-rc3 the first two commits
need to be skipped, looks like the original commits are only in -next
and not in v7.0-rc?
Tested-by: Luca Weiss <luca.weiss@fairphone.com> # milos-fairphone-fp6
Thanks for fixing this!
Regards
Luca
>
> Reported-by: David Heidelberg <david@ixit.cz>
> Link: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
> Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
> ---
> Changes in v2:
> - Introduce a new solution
> - Link to v1: https://lore.kernel.org/r/20260304-f-ncm-revert-v1-0-57c9157b58af@google.com
>
> ---
> Kuen-Han Tsai (7):
> Revert "usb: gadget: f_ncm: Fix atomic context locking issue"
> Revert "usb: legacy: ncm: Fix NPE in gncm_bind"
> Revert "usb: gadget: f_ncm: align net_device lifecycle with bind/unbind"
> Revert "usb: gadget: u_ether: Add auto-cleanup helper for freeing net_device"
> Revert "usb: gadget: u_ether: use <linux/hex.h> header file"
> Revert "usb: gadget: u_ether: add gether_opts for config caching"
> usb: gadget: f_ncm: Fix net_device lifecycle with device_move
>
> drivers/usb/gadget/function/f_ncm.c | 129 +++++++++++--------
> drivers/usb/gadget/function/u_ether.c | 67 ++++------
> drivers/usb/gadget/function/u_ether.h | 56 ++++-----
> drivers/usb/gadget/function/u_ether_configfs.h | 168 -------------------------
> drivers/usb/gadget/function/u_ncm.h | 5 +-
> drivers/usb/gadget/legacy/ncm.c | 13 +-
> 6 files changed, 127 insertions(+), 311 deletions(-)
> ---
> base-commit: 1be3b77de4eb89af8ae2fd6610546be778e25589
> change-id: 20260304-f-ncm-revert-490a66ae8da0
>
> Best regards,
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move
2026-03-13 12:40 ` [PATCH v2 0/7] usb: gadget: " Luca Weiss
@ 2026-03-16 6:17 ` Kuen-Han Tsai
2026-03-16 6:35 ` Greg Kroah-Hartman
0 siblings, 1 reply; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-16 6:17 UTC (permalink / raw)
To: Luca Weiss
Cc: Greg Kroah-Hartman, Felipe Balbi, Kyungmin Park, David Heidelberg,
Ernest Van Hoecke, Jon Hunter, LI Qingwu, linux-usb, linux-kernel,
stable
Hi Luca,
On Fri, Mar 13, 2026 at 8:40 PM Luca Weiss <luca.weiss@fairphone.com> wrote:
>
> Hi Kuen-Han,
>
> On Mon Mar 9, 2026 at 1:04 PM CET, Kuen-Han Tsai wrote:
> > PROBLEMS
> > --------
> > The net_device in f_ncm is allocated at function instance creation
> > and registered at bind time with the gadget device as its sysfs parent.
> > When the gadget unbinds, the parent device is destroyed but the
> > net_device survives, leaving dangling sysfs symlinks and a NULL pointer
> > dereference when userspace accesses the orphaned interface:
> >
> > Problem 1: Unable to handle kernel NULL pointer dereference
> > Call trace:
> > __pi_strlen+0x14/0x150
> > rtnl_fill_ifinfo+0x6b4/0x708
> > rtmsg_ifinfo_build_skb+0xd8/0x13c
> > ...
> > netlink_sendmsg+0x2e0/0x3d4
> >
> > Problem 2: Dangling sysfs symlinks
> > console:/ # ls -l /sys/class/net/ncm0
> > lrwxrwxrwx ... /sys/class/net/ncm0 ->
> > /sys/devices/platform/.../gadget.0/net/ncm0
> > console:/ # ls -l /sys/devices/platform/.../gadget.0/net/ncm0
> > ls: .../gadget.0/net/ncm0: No such file or directory
> >
> > BACKGROUND & THE REVERTS
> > ------------------------
> > The deferred allocation causes a regression for userspace tools during
> > network setup (such as the postmarketOS DHCP daemon). By moving the
> > allocation out of alloc_inst, configfs returns the name pattern "usb%d"
> > instead of the actual interface name (e.g., "usb0") when userspace reads
> > the 'ifname' attribute.
> >
> > Investigating a fix for this naming issue revealed a deeper
> > architectural flaw introduced by the series. Deferring the allocation to
> > bind() means that a single function instance will spawn multiple network
> > devices if it is symlinked to multiple USB configurations.
> >
> > Because all configurations tied to the same function instance are
> > architecturally designed to share a single network device, and configfs
> > only exposes a single 'ifname' attribute per instance, this 1-to-many
> > bug cannot be safely patched.
> >
> > To restore the correct 1:1 mapping and resolve the userspace
> > regressions, this series reverts the changes in reverse order, returning
> > the net_device allocation back to the instance level (alloc_inst).
> >
> > THE NEW SOLUTION
> > ----------------
> > Use device_move() to reparent the net_device between the gadget device
> > tree and /sys/devices/virtual across bind/unbind cycles. On the last
> > unbind, device_move(NULL) moves the net_device to the virtual device
> > tree before the gadget device is destroyed. On rebind, device_move()
> > reparents it back under the new gadget, restoring proper sysfs topology
> > and power management ordering.
> >
> > The 1:1 mapping between function instance and net_device is maintained,
> > and configfs always reports the resolved interface name.
> >
> > A bind_count tracks how many configurations reference the function
> > instance, ensuring device_move fires only on the first bind.
> > __free(detach_gadget) ensures the net_device is moved back to virtual
> > if bind fails after a successful device_move, preventing dangling
> > sysfs on partial bind failure.
>
> Applying this series on v7.0-rc3 fixes the reported issues for me on
> Qualcomm-based Fairphone (Gen. 6). For v7.0-rc3 the first two commits
> need to be skipped, looks like the original commits are only in -next
> and not in v7.0-rc?
>
> Tested-by: Luca Weiss <luca.weiss@fairphone.com> # milos-fairphone-fp6
>
> Thanks for fixing this!
>
> Regards
> Luca
Thanks for testing.
That is correct. The first two commits:
- [Patch v2 1/7] Revert "usb: gadget: f_ncm: Fix atomic context locking issue"
- [Patch v2 2/7] Revert "usb: legacy: ncm: Fix NPE in gncm_bind"
have not been merged into the mainline yet, so skipping them for your
test was the right move. This series is based on Greg's usb-linus
branch rather than the Linux's master branch.
Thanks again!
Regards,
Kuen-Han
>
> >
> > Reported-by: David Heidelberg <david@ixit.cz>
> > Link: https://lore.kernel.org/linux-usb/70b558ea-a12e-4170-9b8e-c951131249af@ixit.cz/
> > Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
> > ---
> > Changes in v2:
> > - Introduce a new solution
> > - Link to v1: https://lore.kernel.org/r/20260304-f-ncm-revert-v1-0-57c9157b58af@google.com
> >
> > ---
> > Kuen-Han Tsai (7):
> > Revert "usb: gadget: f_ncm: Fix atomic context locking issue"
> > Revert "usb: legacy: ncm: Fix NPE in gncm_bind"
> > Revert "usb: gadget: f_ncm: align net_device lifecycle with bind/unbind"
> > Revert "usb: gadget: u_ether: Add auto-cleanup helper for freeing net_device"
> > Revert "usb: gadget: u_ether: use <linux/hex.h> header file"
> > Revert "usb: gadget: u_ether: add gether_opts for config caching"
> > usb: gadget: f_ncm: Fix net_device lifecycle with device_move
> >
> > drivers/usb/gadget/function/f_ncm.c | 129 +++++++++++--------
> > drivers/usb/gadget/function/u_ether.c | 67 ++++------
> > drivers/usb/gadget/function/u_ether.h | 56 ++++-----
> > drivers/usb/gadget/function/u_ether_configfs.h | 168 -------------------------
> > drivers/usb/gadget/function/u_ncm.h | 5 +-
> > drivers/usb/gadget/legacy/ncm.c | 13 +-
> > 6 files changed, 127 insertions(+), 311 deletions(-)
> > ---
> > base-commit: 1be3b77de4eb89af8ae2fd6610546be778e25589
> > change-id: 20260304-f-ncm-revert-490a66ae8da0
> >
> > Best regards,
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move
2026-03-16 6:17 ` Kuen-Han Tsai
@ 2026-03-16 6:35 ` Greg Kroah-Hartman
2026-03-16 6:47 ` Kuen-Han Tsai
0 siblings, 1 reply; 14+ messages in thread
From: Greg Kroah-Hartman @ 2026-03-16 6:35 UTC (permalink / raw)
To: Kuen-Han Tsai
Cc: Luca Weiss, Felipe Balbi, Kyungmin Park, David Heidelberg,
Ernest Van Hoecke, Jon Hunter, LI Qingwu, linux-usb, linux-kernel,
stable
On Mon, Mar 16, 2026 at 02:17:09PM +0800, Kuen-Han Tsai wrote:
> Hi Luca,
>
> On Fri, Mar 13, 2026 at 8:40 PM Luca Weiss <luca.weiss@fairphone.com> wrote:
> >
> > Hi Kuen-Han,
> >
> > On Mon Mar 9, 2026 at 1:04 PM CET, Kuen-Han Tsai wrote:
> > > PROBLEMS
> > > --------
> > > The net_device in f_ncm is allocated at function instance creation
> > > and registered at bind time with the gadget device as its sysfs parent.
> > > When the gadget unbinds, the parent device is destroyed but the
> > > net_device survives, leaving dangling sysfs symlinks and a NULL pointer
> > > dereference when userspace accesses the orphaned interface:
> > >
> > > Problem 1: Unable to handle kernel NULL pointer dereference
> > > Call trace:
> > > __pi_strlen+0x14/0x150
> > > rtnl_fill_ifinfo+0x6b4/0x708
> > > rtmsg_ifinfo_build_skb+0xd8/0x13c
> > > ...
> > > netlink_sendmsg+0x2e0/0x3d4
> > >
> > > Problem 2: Dangling sysfs symlinks
> > > console:/ # ls -l /sys/class/net/ncm0
> > > lrwxrwxrwx ... /sys/class/net/ncm0 ->
> > > /sys/devices/platform/.../gadget.0/net/ncm0
> > > console:/ # ls -l /sys/devices/platform/.../gadget.0/net/ncm0
> > > ls: .../gadget.0/net/ncm0: No such file or directory
> > >
> > > BACKGROUND & THE REVERTS
> > > ------------------------
> > > The deferred allocation causes a regression for userspace tools during
> > > network setup (such as the postmarketOS DHCP daemon). By moving the
> > > allocation out of alloc_inst, configfs returns the name pattern "usb%d"
> > > instead of the actual interface name (e.g., "usb0") when userspace reads
> > > the 'ifname' attribute.
> > >
> > > Investigating a fix for this naming issue revealed a deeper
> > > architectural flaw introduced by the series. Deferring the allocation to
> > > bind() means that a single function instance will spawn multiple network
> > > devices if it is symlinked to multiple USB configurations.
> > >
> > > Because all configurations tied to the same function instance are
> > > architecturally designed to share a single network device, and configfs
> > > only exposes a single 'ifname' attribute per instance, this 1-to-many
> > > bug cannot be safely patched.
> > >
> > > To restore the correct 1:1 mapping and resolve the userspace
> > > regressions, this series reverts the changes in reverse order, returning
> > > the net_device allocation back to the instance level (alloc_inst).
> > >
> > > THE NEW SOLUTION
> > > ----------------
> > > Use device_move() to reparent the net_device between the gadget device
> > > tree and /sys/devices/virtual across bind/unbind cycles. On the last
> > > unbind, device_move(NULL) moves the net_device to the virtual device
> > > tree before the gadget device is destroyed. On rebind, device_move()
> > > reparents it back under the new gadget, restoring proper sysfs topology
> > > and power management ordering.
> > >
> > > The 1:1 mapping between function instance and net_device is maintained,
> > > and configfs always reports the resolved interface name.
> > >
> > > A bind_count tracks how many configurations reference the function
> > > instance, ensuring device_move fires only on the first bind.
> > > __free(detach_gadget) ensures the net_device is moved back to virtual
> > > if bind fails after a successful device_move, preventing dangling
> > > sysfs on partial bind failure.
> >
> > Applying this series on v7.0-rc3 fixes the reported issues for me on
> > Qualcomm-based Fairphone (Gen. 6). For v7.0-rc3 the first two commits
> > need to be skipped, looks like the original commits are only in -next
> > and not in v7.0-rc?
> >
> > Tested-by: Luca Weiss <luca.weiss@fairphone.com> # milos-fairphone-fp6
> >
> > Thanks for fixing this!
> >
> > Regards
> > Luca
>
> Thanks for testing.
>
> That is correct. The first two commits:
>
> - [Patch v2 1/7] Revert "usb: gadget: f_ncm: Fix atomic context locking issue"
> - [Patch v2 2/7] Revert "usb: legacy: ncm: Fix NPE in gncm_bind"
>
> have not been merged into the mainline yet, so skipping them for your
> test was the right move. This series is based on Greg's usb-linus
> branch rather than the Linux's master branch.
These should all now be in 7.0-rc4, right?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 0/7] usb: gadget: Fix net_device lifecycle with device_move
2026-03-16 6:35 ` Greg Kroah-Hartman
@ 2026-03-16 6:47 ` Kuen-Han Tsai
0 siblings, 0 replies; 14+ messages in thread
From: Kuen-Han Tsai @ 2026-03-16 6:47 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Luca Weiss, Felipe Balbi, Kyungmin Park, David Heidelberg,
Ernest Van Hoecke, Jon Hunter, LI Qingwu, linux-usb, linux-kernel,
stable
On Mon, Mar 16, 2026 at 2:36 PM Greg Kroah-Hartman
<gregkh@linuxfoundation.org> wrote:
>
> On Mon, Mar 16, 2026 at 02:17:09PM +0800, Kuen-Han Tsai wrote:
> > Hi Luca,
> >
> > On Fri, Mar 13, 2026 at 8:40 PM Luca Weiss <luca.weiss@fairphone.com> wrote:
> > >
> > > Hi Kuen-Han,
> > >
> > > On Mon Mar 9, 2026 at 1:04 PM CET, Kuen-Han Tsai wrote:
> > > > PROBLEMS
> > > > --------
> > > > The net_device in f_ncm is allocated at function instance creation
> > > > and registered at bind time with the gadget device as its sysfs parent.
> > > > When the gadget unbinds, the parent device is destroyed but the
> > > > net_device survives, leaving dangling sysfs symlinks and a NULL pointer
> > > > dereference when userspace accesses the orphaned interface:
> > > >
> > > > Problem 1: Unable to handle kernel NULL pointer dereference
> > > > Call trace:
> > > > __pi_strlen+0x14/0x150
> > > > rtnl_fill_ifinfo+0x6b4/0x708
> > > > rtmsg_ifinfo_build_skb+0xd8/0x13c
> > > > ...
> > > > netlink_sendmsg+0x2e0/0x3d4
> > > >
> > > > Problem 2: Dangling sysfs symlinks
> > > > console:/ # ls -l /sys/class/net/ncm0
> > > > lrwxrwxrwx ... /sys/class/net/ncm0 ->
> > > > /sys/devices/platform/.../gadget.0/net/ncm0
> > > > console:/ # ls -l /sys/devices/platform/.../gadget.0/net/ncm0
> > > > ls: .../gadget.0/net/ncm0: No such file or directory
> > > >
> > > > BACKGROUND & THE REVERTS
> > > > ------------------------
> > > > The deferred allocation causes a regression for userspace tools during
> > > > network setup (such as the postmarketOS DHCP daemon). By moving the
> > > > allocation out of alloc_inst, configfs returns the name pattern "usb%d"
> > > > instead of the actual interface name (e.g., "usb0") when userspace reads
> > > > the 'ifname' attribute.
> > > >
> > > > Investigating a fix for this naming issue revealed a deeper
> > > > architectural flaw introduced by the series. Deferring the allocation to
> > > > bind() means that a single function instance will spawn multiple network
> > > > devices if it is symlinked to multiple USB configurations.
> > > >
> > > > Because all configurations tied to the same function instance are
> > > > architecturally designed to share a single network device, and configfs
> > > > only exposes a single 'ifname' attribute per instance, this 1-to-many
> > > > bug cannot be safely patched.
> > > >
> > > > To restore the correct 1:1 mapping and resolve the userspace
> > > > regressions, this series reverts the changes in reverse order, returning
> > > > the net_device allocation back to the instance level (alloc_inst).
> > > >
> > > > THE NEW SOLUTION
> > > > ----------------
> > > > Use device_move() to reparent the net_device between the gadget device
> > > > tree and /sys/devices/virtual across bind/unbind cycles. On the last
> > > > unbind, device_move(NULL) moves the net_device to the virtual device
> > > > tree before the gadget device is destroyed. On rebind, device_move()
> > > > reparents it back under the new gadget, restoring proper sysfs topology
> > > > and power management ordering.
> > > >
> > > > The 1:1 mapping between function instance and net_device is maintained,
> > > > and configfs always reports the resolved interface name.
> > > >
> > > > A bind_count tracks how many configurations reference the function
> > > > instance, ensuring device_move fires only on the first bind.
> > > > __free(detach_gadget) ensures the net_device is moved back to virtual
> > > > if bind fails after a successful device_move, preventing dangling
> > > > sysfs on partial bind failure.
> > >
> > > Applying this series on v7.0-rc3 fixes the reported issues for me on
> > > Qualcomm-based Fairphone (Gen. 6). For v7.0-rc3 the first two commits
> > > need to be skipped, looks like the original commits are only in -next
> > > and not in v7.0-rc?
> > >
> > > Tested-by: Luca Weiss <luca.weiss@fairphone.com> # milos-fairphone-fp6
> > >
> > > Thanks for fixing this!
> > >
> > > Regards
> > > Luca
> >
> > Thanks for testing.
> >
> > That is correct. The first two commits:
> >
> > - [Patch v2 1/7] Revert "usb: gadget: f_ncm: Fix atomic context locking issue"
> > - [Patch v2 2/7] Revert "usb: legacy: ncm: Fix NPE in gncm_bind"
> >
> > have not been merged into the mainline yet, so skipping them for your
> > test was the right move. This series is based on Greg's usb-linus
> > branch rather than the Linux's master branch.
>
> These should all now be in 7.0-rc4, right?
>
Right, I saw all these patchsets land in 7.0-rc4 [1].
Thanks for the review and for merging these.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/drivers/usb/gadget?h=v7.0-rc4
Regards,
Kuen-Han
^ permalink raw reply [flat|nested] 14+ messages in thread