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>
This commit is contained in:
Kuen-Han Tsai 2025-12-30 18:13:14 +08:00 committed by Greg Kroah-Hartman
parent c5177144b5
commit e065c6a7e4
3 changed files with 234 additions and 0 deletions

View 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;

View file

@ -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);

View file

@ -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 */