[PATCH 6.18 086/314] usb: gadget: u_ether: add gether_opts for config caching

Sasha Levin
2026-03-10T11:26:02Z

From: Kuen-Han Tsai <khtsai@google.com>

[ Upstream commit e065c6a7e46c2ee9c677fdbf50035323d2de1215 ]

Currently, the net_device is allocated when the function instance is
created (e.g., in ncm_alloc_inst()). While this allows userspace to
configure the device early, it decouples the net_device lifecycle from
the actual USB connection state (bind/unbind). The goal is to defer
net_device creation to the bind callback to properly align the lifecycle
with its parent gadget device.

However, deferring net_device allocation would prevent userspace from
configuring parameters (like interface name or MAC address) before the
net_device exists.

Introduce a new structure, struct gether_opts, associated with the
usb_function_instance, to cache settings independently of the
net_device. These settings include the interface name pattern, MAC
addresses (device and host), queue multiplier, and address assignment
type.

New helper functions are added:
- gether_setup_opts_default(): Initializes struct gether_opts with
defaults, including random MAC addresses.
- gether_apply_opts(): Applies the cached options from a struct
gether_opts to a valid net_device.

To expose these options to userspace, new configfs macros
(USB_ETHER_OPTS_ITEM and USB_ETHER_OPTS_ATTR_*) are defined in
u_ether_configfs.h. These attributes are part of the function
instance's configfs group.

This refactoring is a preparatory step. It allows the subsequent patch
to safely move the net_device allocation from the instance creation
phase to the bind phase without losing the ability to pre-configure
the interface via configfs.

Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
Link: https://patch.msgid.link/20251230-ncm-refactor-v1-1-793e347bc7a7@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Stable-dep-of: 56a512a9b410 ("usb: gadget: f_ncm: align net_device lifecycle with bind/unbind")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
drivers/usb/gadget/function/u_ether.c | 30 +++
drivers/usb/gadget/function/u_ether.h | 28 +++
.../usb/gadget/function/u_ether_configfs.h | 176 ++++++++++++++++++
3 files changed, 234 insertions(+)

diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c
index f58590bf5e02f..745ed2c212e3a 100644
--- a/drivers/usb/gadget/function/u_ether.c
+++ b/drivers/usb/gadget/function/u_ether.c
@@ -1039,6 +1039,36 @@ 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 34be220cef77c..63a0240df4d74 100644
--- a/drivers/usb/gadget/function/u_ether.h
+++ b/drivers/usb/gadget/function/u_ether.h
@@ -38,6 +38,31 @@

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
@@ -259,6 +284,9 @@ 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 f558c3139ebe5..a3696797e074a 100644
--- a/drivers/usb/gadget/function/u_ether_configfs.h
+++ b/drivers/usb/gadget/function/u_ether_configfs.h
@@ -13,6 +13,12 @@
#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) \
{ \
@@ -197,4 +203,174 @@ 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.51.0