summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/function
diff options
context:
space:
mode:
authorKuen-Han Tsai <khtsai@google.com>2025-12-30 18:13:14 +0800
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-01-07 16:17:11 +0100
commite065c6a7e46c2ee9c677fdbf50035323d2de1215 (patch)
treeb3a9a41d55e1fa5f2168c3db79105117a277b244 /drivers/usb/gadget/function
parentc5177144b561dd4037a6a225d444b3604afbfbf2 (diff)
usb: gadget: u_ether: add gether_opts for config caching
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>
Diffstat (limited to 'drivers/usb/gadget/function')
-rw-r--r--drivers/usb/gadget/function/u_ether.c30
-rw-r--r--drivers/usb/gadget/function/u_ether.h28
-rw-r--r--drivers/usb/gadget/function/u_ether_configfs.h176
3 files changed, 234 insertions, 0 deletions
diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c
index f58590bf5e02..745ed2c212e3 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 34be220cef77..63a0240df4d7 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 51f0d79e5eca..39d3a261496d 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 */