3967 lines · 100.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * net/core/ethtool.c - Ethtool ioctl handler * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> * * This file is where we call all the ethtool_ops commands to get * the information ethtool needs. */ #include <linux/compat.h>#include <linux/etherdevice.h>#include <linux/module.h>#include <linux/types.h>#include <linux/capability.h>#include <linux/errno.h>#include <linux/ethtool.h>#include <linux/netdevice.h>#include <linux/net_tstamp.h>#include <linux/phy.h>#include <linux/bitops.h>#include <linux/uaccess.h>#include <linux/vmalloc.h>#include <linux/sfp.h>#include <linux/slab.h>#include <linux/rtnetlink.h>#include <linux/sched/signal.h>#include <linux/net.h>#include <linux/pm_runtime.h>#include <linux/utsname.h>#include <linux/ethtool_netlink.h>#include <net/devlink.h>#include <net/ipv6.h>#include <net/flow_offload.h>#include <net/netdev_lock.h>#include <net/netdev_queues.h> #include "common.h" /* State held across locks and calls for commands which have devlink fallback */struct ethtool_devlink_compat {	struct devlink *devlink;	union {		struct ethtool_flash efl;		struct ethtool_drvinfo info;	};}; static struct devlink *netdev_to_devlink_get(struct net_device *dev){	if (!dev->devlink_port)		return NULL;	return devlink_try_get(dev->devlink_port->devlink);} /* * Some useful ethtool_ops methods that're device independent. * If we find that all drivers want to do the same thing here, * we can turn these into dev_() function calls. */ u32 ethtool_op_get_link(struct net_device *dev){	/* Synchronize carrier state with link watch, see also rtnl_getlink() */	__linkwatch_sync_dev(dev); 	return netif_carrier_ok(dev) ? 1 : 0;}EXPORT_SYMBOL(ethtool_op_get_link); int ethtool_op_get_ts_info(struct net_device *dev,			   struct kernel_ethtool_ts_info *info){	info->so_timestamping =		SOF_TIMESTAMPING_TX_SOFTWARE |		SOF_TIMESTAMPING_RX_SOFTWARE |		SOF_TIMESTAMPING_SOFTWARE;	info->phc_index = -1;	return 0;}EXPORT_SYMBOL(ethtool_op_get_ts_info); /* Handlers for each ethtool command */ static int ethtool_get_features(struct net_device *dev, void __user *useraddr){	struct ethtool_gfeatures cmd = {		.cmd = ETHTOOL_GFEATURES,		.size = ETHTOOL_DEV_FEATURE_WORDS,	};	struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS];	u32 __user *sizeaddr;	u32 copy_size;	int i; 	/* in case feature bits run out again */	BUILD_BUG_ON(ETHTOOL_DEV_FEATURE_WORDS * sizeof(u32) > sizeof(netdev_features_t)); 	for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) {		features[i].available = (u32)(dev->hw_features >> (32 * i));		features[i].requested = (u32)(dev->wanted_features >> (32 * i));		features[i].active = (u32)(dev->features >> (32 * i));		features[i].never_changed =			(u32)(NETIF_F_NEVER_CHANGE >> (32 * i));	} 	sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size);	if (get_user(copy_size, sizeaddr))		return -EFAULT; 	if (copy_size > ETHTOOL_DEV_FEATURE_WORDS)		copy_size = ETHTOOL_DEV_FEATURE_WORDS; 	if (copy_to_user(useraddr, &cmd, sizeof(cmd)))		return -EFAULT;	useraddr += sizeof(cmd);	if (copy_to_user(useraddr, features,			 array_size(copy_size, sizeof(*features))))		return -EFAULT; 	return 0;} static int ethtool_set_features(struct net_device *dev, void __user *useraddr){	struct ethtool_sfeatures cmd;	struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS];	netdev_features_t wanted = 0, valid = 0;	int i, ret = 0; 	if (copy_from_user(&cmd, useraddr, sizeof(cmd)))		return -EFAULT;	useraddr += sizeof(cmd); 	if (cmd.size != ETHTOOL_DEV_FEATURE_WORDS)		return -EINVAL; 	if (copy_from_user(features, useraddr, sizeof(features)))		return -EFAULT; 	for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) {		valid |= (netdev_features_t)features[i].valid << (32 * i);		wanted |= (netdev_features_t)features[i].requested << (32 * i);	} 	if (valid & ~NETIF_F_ETHTOOL_BITS)		return -EINVAL; 	if (valid & ~dev->hw_features) {		valid &= dev->hw_features;		ret |= ETHTOOL_F_UNSUPPORTED;	} 	dev->wanted_features &= ~valid;	dev->wanted_features |= wanted & valid;	__netdev_update_features(dev); 	if ((dev->wanted_features ^ dev->features) & valid)		ret |= ETHTOOL_F_WISH; 	return ret;} static int __ethtool_get_sset_count(struct net_device *dev, int sset){	const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;	const struct ethtool_ops *ops = dev->ethtool_ops; 	if (sset == ETH_SS_FEATURES)		return ARRAY_SIZE(netdev_features_strings); 	if (sset == ETH_SS_RSS_HASH_FUNCS)		return ARRAY_SIZE(rss_hash_func_strings); 	if (sset == ETH_SS_TUNABLES)		return ARRAY_SIZE(tunable_strings); 	if (sset == ETH_SS_PHY_TUNABLES)		return ARRAY_SIZE(phy_tunable_strings); 	if (sset == ETH_SS_PHY_STATS && dev->phydev &&	    !ops->get_ethtool_phy_stats &&	    phy_ops && phy_ops->get_sset_count)		return phy_ops->get_sset_count(dev->phydev); 	if (sset == ETH_SS_LINK_MODES)		return __ETHTOOL_LINK_MODE_MASK_NBITS; 	if (ops->get_sset_count && ops->get_strings)		return ops->get_sset_count(dev, sset);	else		return -EOPNOTSUPP;} static void __ethtool_get_strings(struct net_device *dev,	u32 stringset, u8 *data){	const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;	const struct ethtool_ops *ops = dev->ethtool_ops; 	if (stringset == ETH_SS_FEATURES)		memcpy(data, netdev_features_strings,			sizeof(netdev_features_strings));	else if (stringset == ETH_SS_RSS_HASH_FUNCS)		memcpy(data, rss_hash_func_strings,		       sizeof(rss_hash_func_strings));	else if (stringset == ETH_SS_TUNABLES)		memcpy(data, tunable_strings, sizeof(tunable_strings));	else if (stringset == ETH_SS_PHY_TUNABLES)		memcpy(data, phy_tunable_strings, sizeof(phy_tunable_strings));	else if (stringset == ETH_SS_PHY_STATS && dev->phydev &&		 !ops->get_ethtool_phy_stats && phy_ops &&		 phy_ops->get_strings)		phy_ops->get_strings(dev->phydev, data);	else if (stringset == ETH_SS_LINK_MODES)		memcpy(data, link_mode_names,		       __ETHTOOL_LINK_MODE_MASK_NBITS * ETH_GSTRING_LEN);	else		/* ops->get_strings is valid because checked earlier */		ops->get_strings(dev, stringset, data);} static netdev_features_t ethtool_get_feature_mask(u32 eth_cmd){	/* feature masks of legacy discrete ethtool ops */ 	switch (eth_cmd) {	case ETHTOOL_GTXCSUM:	case ETHTOOL_STXCSUM:		return NETIF_F_CSUM_MASK | NETIF_F_FCOE_CRC |		       NETIF_F_SCTP_CRC;	case ETHTOOL_GRXCSUM:	case ETHTOOL_SRXCSUM:		return NETIF_F_RXCSUM;	case ETHTOOL_GSG:	case ETHTOOL_SSG:		return NETIF_F_SG | NETIF_F_FRAGLIST;	case ETHTOOL_GTSO:	case ETHTOOL_STSO:		return NETIF_F_ALL_TSO;	case ETHTOOL_GGSO:	case ETHTOOL_SGSO:		return NETIF_F_GSO;	case ETHTOOL_GGRO:	case ETHTOOL_SGRO:		return NETIF_F_GRO;	default:		BUG();	}} static int ethtool_get_one_feature(struct net_device *dev,	char __user *useraddr, u32 ethcmd){	netdev_features_t mask = ethtool_get_feature_mask(ethcmd);	struct ethtool_value edata = {		.cmd = ethcmd,		.data = !!(dev->features & mask),	}; 	if (copy_to_user(useraddr, &edata, sizeof(edata)))		return -EFAULT;	return 0;} static int ethtool_set_one_feature(struct net_device *dev,	void __user *useraddr, u32 ethcmd){	struct ethtool_value edata;	netdev_features_t mask; 	if (copy_from_user(&edata, useraddr, sizeof(edata)))		return -EFAULT; 	mask = ethtool_get_feature_mask(ethcmd);	mask &= dev->hw_features;	if (!mask)		return -EOPNOTSUPP; 	if (edata.data)		dev->wanted_features |= mask;	else		dev->wanted_features &= ~mask; 	__netdev_update_features(dev); 	return 0;} #define ETH_ALL_FLAGS    (ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | \			  ETH_FLAG_NTUPLE | ETH_FLAG_RXHASH)#define ETH_ALL_FEATURES (NETIF_F_LRO | NETIF_F_HW_VLAN_CTAG_RX | \			  NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_NTUPLE | \			  NETIF_F_RXHASH) static u32 __ethtool_get_flags(struct net_device *dev){	u32 flags = 0; 	if (dev->features & NETIF_F_LRO)		flags |= ETH_FLAG_LRO;	if (dev->features & NETIF_F_HW_VLAN_CTAG_RX)		flags |= ETH_FLAG_RXVLAN;	if (dev->features & NETIF_F_HW_VLAN_CTAG_TX)		flags |= ETH_FLAG_TXVLAN;	if (dev->features & NETIF_F_NTUPLE)		flags |= ETH_FLAG_NTUPLE;	if (dev->features & NETIF_F_RXHASH)		flags |= ETH_FLAG_RXHASH; 	return flags;} static int __ethtool_set_flags(struct net_device *dev, u32 data){	netdev_features_t features = 0, changed; 	if (data & ~ETH_ALL_FLAGS)		return -EINVAL; 	if (data & ETH_FLAG_LRO)		features |= NETIF_F_LRO;	if (data & ETH_FLAG_RXVLAN)		features |= NETIF_F_HW_VLAN_CTAG_RX;	if (data & ETH_FLAG_TXVLAN)		features |= NETIF_F_HW_VLAN_CTAG_TX;	if (data & ETH_FLAG_NTUPLE)		features |= NETIF_F_NTUPLE;	if (data & ETH_FLAG_RXHASH)		features |= NETIF_F_RXHASH; 	/* allow changing only bits set in hw_features */	changed = (features ^ dev->features) & ETH_ALL_FEATURES;	if (changed & ~dev->hw_features)		return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP; 	dev->wanted_features =		(dev->wanted_features & ~changed) | (features & changed); 	__netdev_update_features(dev); 	return 0;} /* Given two link masks, AND them together and save the result in dst. */void ethtool_intersect_link_masks(struct ethtool_link_ksettings *dst,				  struct ethtool_link_ksettings *src){	unsigned int size = BITS_TO_LONGS(__ETHTOOL_LINK_MODE_MASK_NBITS);	unsigned int idx = 0; 	for (; idx < size; idx++) {		dst->link_modes.supported[idx] &=			src->link_modes.supported[idx];		dst->link_modes.advertising[idx] &=			src->link_modes.advertising[idx];	}}EXPORT_SYMBOL(ethtool_intersect_link_masks); void ethtool_convert_legacy_u32_to_link_mode(unsigned long *dst,					     u32 legacy_u32){	linkmode_zero(dst);	dst[0] = legacy_u32;}EXPORT_SYMBOL(ethtool_convert_legacy_u32_to_link_mode); /* return false if src had higher bits set. lower bits always updated. */bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,					     const unsigned long *src){	*legacy_u32 = src[0];	return find_next_bit(src, __ETHTOOL_LINK_MODE_MASK_NBITS, 32) ==		__ETHTOOL_LINK_MODE_MASK_NBITS;}EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32); /* return false if ksettings link modes had higher bits * set. legacy_settings always updated (best effort) */static boolconvert_link_ksettings_to_legacy_settings(	struct ethtool_cmd *legacy_settings,	const struct ethtool_link_ksettings *link_ksettings){	bool retval = true; 	memset(legacy_settings, 0, sizeof(*legacy_settings));	/* this also clears the deprecated fields in legacy structure:	 * __u8		transceiver;	 * __u32	maxtxpkt;	 * __u32	maxrxpkt;	 */ 	retval &= ethtool_convert_link_mode_to_legacy_u32(		&legacy_settings->supported,		link_ksettings->link_modes.supported);	retval &= ethtool_convert_link_mode_to_legacy_u32(		&legacy_settings->advertising,		link_ksettings->link_modes.advertising);	retval &= ethtool_convert_link_mode_to_legacy_u32(		&legacy_settings->lp_advertising,		link_ksettings->link_modes.lp_advertising);	ethtool_cmd_speed_set(legacy_settings, link_ksettings->base.speed);	legacy_settings->duplex		= link_ksettings->base.duplex;	legacy_settings->port		= link_ksettings->base.port;	legacy_settings->phy_address		= link_ksettings->base.phy_address;	legacy_settings->autoneg		= link_ksettings->base.autoneg;	legacy_settings->mdio_support		= link_ksettings->base.mdio_support;	legacy_settings->eth_tp_mdix		= link_ksettings->base.eth_tp_mdix;	legacy_settings->eth_tp_mdix_ctrl		= link_ksettings->base.eth_tp_mdix_ctrl;	legacy_settings->transceiver		= link_ksettings->base.transceiver;	return retval;} /* number of 32-bit words to store the user's link mode bitmaps */#define __ETHTOOL_LINK_MODE_MASK_NU32			\	DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32) /* layout of the struct passed from/to userland */struct ethtool_link_usettings {	struct ethtool_link_settings base;	struct {		__u32 supported[__ETHTOOL_LINK_MODE_MASK_NU32];		__u32 advertising[__ETHTOOL_LINK_MODE_MASK_NU32];		__u32 lp_advertising[__ETHTOOL_LINK_MODE_MASK_NU32];	} link_modes;}; /* Internal kernel helper to query a device ethtool_link_settings. */int netif_get_link_ksettings(struct net_device *dev,			     struct ethtool_link_ksettings *link_ksettings){	netdev_assert_locked_ops_compat(dev); 	if (!dev->ethtool_ops->get_link_ksettings)		return -EOPNOTSUPP; 	if (!netif_device_present(dev))		return -ENODEV; 	memset(link_ksettings, 0, sizeof(*link_ksettings));	return dev->ethtool_ops->get_link_ksettings(dev, link_ksettings);}EXPORT_SYMBOL(netif_get_link_ksettings); /* Convenience helper for callers that hold only rtnl_lock(). */int __ethtool_get_link_ksettings(struct net_device *dev,				 struct ethtool_link_ksettings *link_ksettings){	int ret; 	ASSERT_RTNL(); 	netdev_lock_ops(dev);	ret = netif_get_link_ksettings(dev, link_ksettings);	netdev_unlock_ops(dev);	return ret;}EXPORT_SYMBOL(__ethtool_get_link_ksettings); /* convert ethtool_link_usettings in user space to a kernel internal * ethtool_link_ksettings. return 0 on success, errno on error. */static int load_link_ksettings_from_user(struct ethtool_link_ksettings *to,					 const void __user *from){	struct ethtool_link_usettings link_usettings; 	if (copy_from_user(&link_usettings, from, sizeof(link_usettings)))		return -EFAULT; 	memcpy(&to->base, &link_usettings.base, sizeof(to->base));	bitmap_from_arr32(to->link_modes.supported,			  link_usettings.link_modes.supported,			  __ETHTOOL_LINK_MODE_MASK_NBITS);	bitmap_from_arr32(to->link_modes.advertising,			  link_usettings.link_modes.advertising,			  __ETHTOOL_LINK_MODE_MASK_NBITS);	bitmap_from_arr32(to->link_modes.lp_advertising,			  link_usettings.link_modes.lp_advertising,			  __ETHTOOL_LINK_MODE_MASK_NBITS); 	return 0;} /* Check if the user is trying to change anything besides speed/duplex */bool ethtool_virtdev_validate_cmd(const struct ethtool_link_ksettings *cmd){	struct ethtool_link_settings base2 = {}; 	base2.speed = cmd->base.speed;

Lines 1–500 of 3967.