Commit e065c6a7 authored by Kuen-Han Tsai's avatar Kuen-Han Tsai Committed by Greg Kroah-Hartman
Browse files

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: default avatarKuen-Han Tsai <khtsai@google.com>
Link: https://patch.msgid.link/20251230-ncm-refactor-v1-1-793e347bc7a7@google.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent c5177144
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -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;
+28 −0
Original line number Diff line number Diff line
@@ -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);

+176 −0
Original line number Diff line number Diff line
@@ -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 */