]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
usb: gadget: u_ether: add gether_opts for config caching
authorKuen-Han Tsai <khtsai@google.com>
Tue, 30 Dec 2025 10:13:14 +0000 (18:13 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 7 Jan 2026 15:17:11 +0000 (16:17 +0100)
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>
drivers/usb/gadget/function/u_ether.c
drivers/usb/gadget/function/u_ether.h
drivers/usb/gadget/function/u_ether_configfs.h

index f58590bf5e02f5f785cf5bdc287f5ce9c95e47c3..745ed2c212e3a706b0e6725731b42d34428f8b22 100644 (file)
@@ -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;
index 34be220cef77c49262b2098771c211326d038407..63a0240df4d749bd91c9dd6743406075093a3168 100644 (file)
 
 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);
 
index 51f0d79e5eca4b8690cbc6d1ffc0ec1479c76a8c..39d3a261496d9fa0703c4b01c076550ecc02cda1 100644 (file)
 #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 */