diff options
Diffstat (limited to 'net/ethtool/linkmodes.c')
| -rw-r--r-- | net/ethtool/linkmodes.c | 375 | 
1 files changed, 375 insertions, 0 deletions
diff --git a/net/ethtool/linkmodes.c b/net/ethtool/linkmodes.c new file mode 100644 index 000000000000..96f20be64553 --- /dev/null +++ b/net/ethtool/linkmodes.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "netlink.h" +#include "common.h" +#include "bitset.h" + +struct linkmodes_req_info { +	struct ethnl_req_info		base; +}; + +struct linkmodes_reply_data { +	struct ethnl_reply_data		base; +	struct ethtool_link_ksettings	ksettings; +	struct ethtool_link_settings	*lsettings; +	bool				peer_empty; +}; + +#define LINKMODES_REPDATA(__reply_base) \ +	container_of(__reply_base, struct linkmodes_reply_data, base) + +static const struct nla_policy +linkmodes_get_policy[ETHTOOL_A_LINKMODES_MAX + 1] = { +	[ETHTOOL_A_LINKMODES_UNSPEC]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_HEADER]		= { .type = NLA_NESTED }, +	[ETHTOOL_A_LINKMODES_AUTONEG]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_OURS]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_PEER]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_SPEED]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_DUPLEX]		= { .type = NLA_REJECT }, +}; + +static int linkmodes_prepare_data(const struct ethnl_req_info *req_base, +				  struct ethnl_reply_data *reply_base, +				  struct genl_info *info) +{ +	struct linkmodes_reply_data *data = LINKMODES_REPDATA(reply_base); +	struct net_device *dev = reply_base->dev; +	int ret; + +	data->lsettings = &data->ksettings.base; + +	ret = ethnl_ops_begin(dev); +	if (ret < 0) +		return ret; + +	ret = __ethtool_get_link_ksettings(dev, &data->ksettings); +	if (ret < 0 && info) { +		GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); +		goto out; +	} + +	data->peer_empty = +		bitmap_empty(data->ksettings.link_modes.lp_advertising, +			     __ETHTOOL_LINK_MODE_MASK_NBITS); + +out: +	ethnl_ops_complete(dev); +	return ret; +} + +static int linkmodes_reply_size(const struct ethnl_req_info *req_base, +				const struct ethnl_reply_data *reply_base) +{ +	const struct linkmodes_reply_data *data = LINKMODES_REPDATA(reply_base); +	const struct ethtool_link_ksettings *ksettings = &data->ksettings; +	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; +	int len, ret; + +	len = nla_total_size(sizeof(u8)) /* LINKMODES_AUTONEG */ +		+ nla_total_size(sizeof(u32)) /* LINKMODES_SPEED */ +		+ nla_total_size(sizeof(u8)) /* LINKMODES_DUPLEX */ +		+ 0; +	ret = ethnl_bitset_size(ksettings->link_modes.advertising, +				ksettings->link_modes.supported, +				__ETHTOOL_LINK_MODE_MASK_NBITS, +				link_mode_names, compact); +	if (ret < 0) +		return ret; +	len += ret; +	if (!data->peer_empty) { +		ret = ethnl_bitset_size(ksettings->link_modes.lp_advertising, +					NULL, __ETHTOOL_LINK_MODE_MASK_NBITS, +					link_mode_names, compact); +		if (ret < 0) +			return ret; +		len += ret; +	} + +	return len; +} + +static int linkmodes_fill_reply(struct sk_buff *skb, +				const struct ethnl_req_info *req_base, +				const struct ethnl_reply_data *reply_base) +{ +	const struct linkmodes_reply_data *data = LINKMODES_REPDATA(reply_base); +	const struct ethtool_link_ksettings *ksettings = &data->ksettings; +	const struct ethtool_link_settings *lsettings = &ksettings->base; +	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS; +	int ret; + +	if (nla_put_u8(skb, ETHTOOL_A_LINKMODES_AUTONEG, lsettings->autoneg)) +		return -EMSGSIZE; + +	ret = ethnl_put_bitset(skb, ETHTOOL_A_LINKMODES_OURS, +			       ksettings->link_modes.advertising, +			       ksettings->link_modes.supported, +			       __ETHTOOL_LINK_MODE_MASK_NBITS, link_mode_names, +			       compact); +	if (ret < 0) +		return -EMSGSIZE; +	if (!data->peer_empty) { +		ret = ethnl_put_bitset(skb, ETHTOOL_A_LINKMODES_PEER, +				       ksettings->link_modes.lp_advertising, +				       NULL, __ETHTOOL_LINK_MODE_MASK_NBITS, +				       link_mode_names, compact); +		if (ret < 0) +			return -EMSGSIZE; +	} + +	if (nla_put_u32(skb, ETHTOOL_A_LINKMODES_SPEED, lsettings->speed) || +	    nla_put_u8(skb, ETHTOOL_A_LINKMODES_DUPLEX, lsettings->duplex)) +		return -EMSGSIZE; + +	return 0; +} + +const struct ethnl_request_ops ethnl_linkmodes_request_ops = { +	.request_cmd		= ETHTOOL_MSG_LINKMODES_GET, +	.reply_cmd		= ETHTOOL_MSG_LINKMODES_GET_REPLY, +	.hdr_attr		= ETHTOOL_A_LINKMODES_HEADER, +	.max_attr		= ETHTOOL_A_LINKMODES_MAX, +	.req_info_size		= sizeof(struct linkmodes_req_info), +	.reply_data_size	= sizeof(struct linkmodes_reply_data), +	.request_policy		= linkmodes_get_policy, + +	.prepare_data		= linkmodes_prepare_data, +	.reply_size		= linkmodes_reply_size, +	.fill_reply		= linkmodes_fill_reply, +}; + +/* LINKMODES_SET */ + +struct link_mode_info { +	int				speed; +	u8				duplex; +}; + +#define __DEFINE_LINK_MODE_PARAMS(_speed, _type, _duplex) \ +	[ETHTOOL_LINK_MODE(_speed, _type, _duplex)] = { \ +		.speed	= SPEED_ ## _speed, \ +		.duplex	= __DUPLEX_ ## _duplex \ +	} +#define __DUPLEX_Half DUPLEX_HALF +#define __DUPLEX_Full DUPLEX_FULL +#define __DEFINE_SPECIAL_MODE_PARAMS(_mode) \ +	[ETHTOOL_LINK_MODE_ ## _mode ## _BIT] = { \ +		.speed	= SPEED_UNKNOWN, \ +		.duplex	= DUPLEX_UNKNOWN, \ +	} + +static const struct link_mode_info link_mode_params[] = { +	__DEFINE_LINK_MODE_PARAMS(10, T, Half), +	__DEFINE_LINK_MODE_PARAMS(10, T, Full), +	__DEFINE_LINK_MODE_PARAMS(100, T, Half), +	__DEFINE_LINK_MODE_PARAMS(100, T, Full), +	__DEFINE_LINK_MODE_PARAMS(1000, T, Half), +	__DEFINE_LINK_MODE_PARAMS(1000, T, Full), +	__DEFINE_SPECIAL_MODE_PARAMS(Autoneg), +	__DEFINE_SPECIAL_MODE_PARAMS(TP), +	__DEFINE_SPECIAL_MODE_PARAMS(AUI), +	__DEFINE_SPECIAL_MODE_PARAMS(MII), +	__DEFINE_SPECIAL_MODE_PARAMS(FIBRE), +	__DEFINE_SPECIAL_MODE_PARAMS(BNC), +	__DEFINE_LINK_MODE_PARAMS(10000, T, Full), +	__DEFINE_SPECIAL_MODE_PARAMS(Pause), +	__DEFINE_SPECIAL_MODE_PARAMS(Asym_Pause), +	__DEFINE_LINK_MODE_PARAMS(2500, X, Full), +	__DEFINE_SPECIAL_MODE_PARAMS(Backplane), +	__DEFINE_LINK_MODE_PARAMS(1000, KX, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, KX4, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, KR, Full), +	[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = { +		.speed	= SPEED_10000, +		.duplex = DUPLEX_FULL, +	}, +	__DEFINE_LINK_MODE_PARAMS(20000, MLD2, Full), +	__DEFINE_LINK_MODE_PARAMS(20000, KR2, Full), +	__DEFINE_LINK_MODE_PARAMS(40000, KR4, Full), +	__DEFINE_LINK_MODE_PARAMS(40000, CR4, Full), +	__DEFINE_LINK_MODE_PARAMS(40000, SR4, Full), +	__DEFINE_LINK_MODE_PARAMS(40000, LR4, Full), +	__DEFINE_LINK_MODE_PARAMS(56000, KR4, Full), +	__DEFINE_LINK_MODE_PARAMS(56000, CR4, Full), +	__DEFINE_LINK_MODE_PARAMS(56000, SR4, Full), +	__DEFINE_LINK_MODE_PARAMS(56000, LR4, Full), +	__DEFINE_LINK_MODE_PARAMS(25000, CR, Full), +	__DEFINE_LINK_MODE_PARAMS(25000, KR, Full), +	__DEFINE_LINK_MODE_PARAMS(25000, SR, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, CR2, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, KR2, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, KR4, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, SR4, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, CR4, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, LR4_ER4, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, SR2, Full), +	__DEFINE_LINK_MODE_PARAMS(1000, X, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, CR, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, SR, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, LR, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, LRM, Full), +	__DEFINE_LINK_MODE_PARAMS(10000, ER, Full), +	__DEFINE_LINK_MODE_PARAMS(2500, T, Full), +	__DEFINE_LINK_MODE_PARAMS(5000, T, Full), +	__DEFINE_SPECIAL_MODE_PARAMS(FEC_NONE), +	__DEFINE_SPECIAL_MODE_PARAMS(FEC_RS), +	__DEFINE_SPECIAL_MODE_PARAMS(FEC_BASER), +	__DEFINE_LINK_MODE_PARAMS(50000, KR, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, SR, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, CR, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, LR_ER_FR, Full), +	__DEFINE_LINK_MODE_PARAMS(50000, DR, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, KR2, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, SR2, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, CR2, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, LR2_ER2_FR2, Full), +	__DEFINE_LINK_MODE_PARAMS(100000, DR2, Full), +	__DEFINE_LINK_MODE_PARAMS(200000, KR4, Full), +	__DEFINE_LINK_MODE_PARAMS(200000, SR4, Full), +	__DEFINE_LINK_MODE_PARAMS(200000, LR4_ER4_FR4, Full), +	__DEFINE_LINK_MODE_PARAMS(200000, DR4, Full), +	__DEFINE_LINK_MODE_PARAMS(200000, CR4, Full), +	__DEFINE_LINK_MODE_PARAMS(100, T1, Full), +	__DEFINE_LINK_MODE_PARAMS(1000, T1, Full), +	__DEFINE_LINK_MODE_PARAMS(400000, KR8, Full), +	__DEFINE_LINK_MODE_PARAMS(400000, SR8, Full), +	__DEFINE_LINK_MODE_PARAMS(400000, LR8_ER8_FR8, Full), +	__DEFINE_LINK_MODE_PARAMS(400000, DR8, Full), +	__DEFINE_LINK_MODE_PARAMS(400000, CR8, Full), +}; + +static const struct nla_policy +linkmodes_set_policy[ETHTOOL_A_LINKMODES_MAX + 1] = { +	[ETHTOOL_A_LINKMODES_UNSPEC]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_HEADER]		= { .type = NLA_NESTED }, +	[ETHTOOL_A_LINKMODES_AUTONEG]		= { .type = NLA_U8 }, +	[ETHTOOL_A_LINKMODES_OURS]		= { .type = NLA_NESTED }, +	[ETHTOOL_A_LINKMODES_PEER]		= { .type = NLA_REJECT }, +	[ETHTOOL_A_LINKMODES_SPEED]		= { .type = NLA_U32 }, +	[ETHTOOL_A_LINKMODES_DUPLEX]		= { .type = NLA_U8 }, +}; + +/* Set advertised link modes to all supported modes matching requested speed + * and duplex values. Called when autonegotiation is on, speed or duplex is + * requested but no link mode change. This is done in userspace with ioctl() + * interface, move it into kernel for netlink. + * Returns true if advertised modes bitmap was modified. + */ +static bool ethnl_auto_linkmodes(struct ethtool_link_ksettings *ksettings, +				 bool req_speed, bool req_duplex) +{ +	unsigned long *advertising = ksettings->link_modes.advertising; +	unsigned long *supported = ksettings->link_modes.supported; +	DECLARE_BITMAP(old_adv, __ETHTOOL_LINK_MODE_MASK_NBITS); +	unsigned int i; + +	BUILD_BUG_ON(ARRAY_SIZE(link_mode_params) != +		     __ETHTOOL_LINK_MODE_MASK_NBITS); + +	bitmap_copy(old_adv, advertising, __ETHTOOL_LINK_MODE_MASK_NBITS); + +	for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) { +		const struct link_mode_info *info = &link_mode_params[i]; + +		if (info->speed == SPEED_UNKNOWN) +			continue; +		if (test_bit(i, supported) && +		    (!req_speed || info->speed == ksettings->base.speed) && +		    (!req_duplex || info->duplex == ksettings->base.duplex)) +			set_bit(i, advertising); +		else +			clear_bit(i, advertising); +	} + +	return !bitmap_equal(old_adv, advertising, +			     __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static int ethnl_update_linkmodes(struct genl_info *info, struct nlattr **tb, +				  struct ethtool_link_ksettings *ksettings, +				  bool *mod) +{ +	struct ethtool_link_settings *lsettings = &ksettings->base; +	bool req_speed, req_duplex; +	int ret; + +	*mod = false; +	req_speed = tb[ETHTOOL_A_LINKMODES_SPEED]; +	req_duplex = tb[ETHTOOL_A_LINKMODES_DUPLEX]; + +	ethnl_update_u8(&lsettings->autoneg, tb[ETHTOOL_A_LINKMODES_AUTONEG], +			mod); +	ret = ethnl_update_bitset(ksettings->link_modes.advertising, +				  __ETHTOOL_LINK_MODE_MASK_NBITS, +				  tb[ETHTOOL_A_LINKMODES_OURS], link_mode_names, +				  info->extack, mod); +	if (ret < 0) +		return ret; +	ethnl_update_u32(&lsettings->speed, tb[ETHTOOL_A_LINKMODES_SPEED], +			 mod); +	ethnl_update_u8(&lsettings->duplex, tb[ETHTOOL_A_LINKMODES_DUPLEX], +			mod); + +	if (!tb[ETHTOOL_A_LINKMODES_OURS] && lsettings->autoneg && +	    (req_speed || req_duplex) && +	    ethnl_auto_linkmodes(ksettings, req_speed, req_duplex)) +		*mod = true; + +	return 0; +} + +int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info) +{ +	struct nlattr *tb[ETHTOOL_A_LINKMODES_MAX + 1]; +	struct ethtool_link_ksettings ksettings = {}; +	struct ethnl_req_info req_info = {}; +	struct net_device *dev; +	bool mod = false; +	int ret; + +	ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb, +			  ETHTOOL_A_LINKMODES_MAX, linkmodes_set_policy, +			  info->extack); +	if (ret < 0) +		return ret; +	ret = ethnl_parse_header(&req_info, tb[ETHTOOL_A_LINKMODES_HEADER], +				 genl_info_net(info), info->extack, true); +	if (ret < 0) +		return ret; +	dev = req_info.dev; +	if (!dev->ethtool_ops->get_link_ksettings || +	    !dev->ethtool_ops->set_link_ksettings) +		return -EOPNOTSUPP; + +	rtnl_lock(); +	ret = ethnl_ops_begin(dev); +	if (ret < 0) +		goto out_rtnl; + +	ret = __ethtool_get_link_ksettings(dev, &ksettings); +	if (ret < 0) { +		if (info) +			GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); +		goto out_ops; +	} + +	ret = ethnl_update_linkmodes(info, tb, &ksettings, &mod); +	if (ret < 0) +		goto out_ops; + +	if (mod) { +		ret = dev->ethtool_ops->set_link_ksettings(dev, &ksettings); +		if (ret < 0) +			GENL_SET_ERR_MSG(info, "link settings update failed"); +		else +			ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL); +	} + +out_ops: +	ethnl_ops_complete(dev); +out_rtnl: +	rtnl_unlock(); +	dev_put(dev); +	return ret; +}  |