Commit bfbcfe20 authored by Michal Kubecek's avatar Michal Kubecek Committed by David S. Miller
Browse files

ethtool: set link modes related data with LINKMODES_SET request



Implement LINKMODES_SET netlink request to set advertised linkmodes and
related attributes as ETHTOOL_SLINKSETTINGS and ETHTOOL_SSET commands do.

The request allows setting autonegotiation flag, speed, duplex and
advertised link modes.

Signed-off-by: default avatarMichal Kubecek <mkubecek@suse.cz>
Reviewed-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent f625aa9b
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -183,6 +183,7 @@ Userspace to kernel:
  ``ETHTOOL_MSG_LINKINFO_GET``          get link settings
  ``ETHTOOL_MSG_LINKINFO_SET``          set link settings
  ``ETHTOOL_MSG_LINKMODES_GET``         get link modes info
  ``ETHTOOL_MSG_LINKMODES_SET``         set link modes info
  ===================================== ================================

Kernel to userspace:
@@ -366,6 +367,30 @@ list.
devices supporting the request).


LINKMODES_SET
=============

Request contents:

  ====================================  ======  ==========================
  ``ETHTOOL_A_LINKMODES_HEADER``        nested  request header
  ``ETHTOOL_A_LINKMODES_AUTONEG``       u8      autonegotiation status
  ``ETHTOOL_A_LINKMODES_OURS``          bitset  advertised link modes
  ``ETHTOOL_A_LINKMODES_PEER``          bitset  partner link modes
  ``ETHTOOL_A_LINKMODES_SPEED``         u32     link speed (Mb/s)
  ``ETHTOOL_A_LINKMODES_DUPLEX``        u8      duplex mode
  ====================================  ======  ==========================

``ETHTOOL_A_LINKMODES_OURS`` bit set allows setting advertised link modes. If
autonegotiation is on (either set now or kept from before), advertised modes
are not changed (no ``ETHTOOL_A_LINKMODES_OURS`` attribute) and at least one
of speed and duplex is specified, kernel adjusts advertised modes to all
supported modes matching speed, duplex or both (whatever is specified). This
autoselection is done on ethtool side with ioctl interface, netlink interface
is supposed to allow requesting changes without knowing what exactly kernel
supports.


Request translation
===================

@@ -379,6 +404,7 @@ have their netlink replacement yet.
  ``ETHTOOL_GSET``                    ``ETHTOOL_MSG_LINKINFO_GET``
                                      ``ETHTOOL_MSG_LINKMODES_GET``
  ``ETHTOOL_SSET``                    ``ETHTOOL_MSG_LINKINFO_SET``
                                      ``ETHTOOL_MSG_LINKMODES_SET``
  ``ETHTOOL_GDRVINFO``                n/a
  ``ETHTOOL_GREGS``                   n/a
  ``ETHTOOL_GWOL``                    n/a
@@ -454,6 +480,7 @@ have their netlink replacement yet.
  ``ETHTOOL_GLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_GET``
                                      ``ETHTOOL_MSG_LINKMODES_GET``
  ``ETHTOOL_SLINKSETTINGS``           ``ETHTOOL_MSG_LINKINFO_SET``
                                      ``ETHTOOL_MSG_LINKMODES_SET``
  ``ETHTOOL_PHY_GTUNABLE``            n/a
  ``ETHTOOL_PHY_STUNABLE``            n/a
  ``ETHTOOL_GFECPARAM``               n/a
+1 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ enum {
	ETHTOOL_MSG_LINKINFO_GET,
	ETHTOOL_MSG_LINKINFO_SET,
	ETHTOOL_MSG_LINKMODES_GET,
	ETHTOOL_MSG_LINKMODES_SET,

	/* add new constants above here */
	__ETHTOOL_MSG_USER_CNT,
+235 −0
Original line number Diff line number Diff line
@@ -138,3 +138,238 @@ const struct ethnl_request_ops ethnl_linkmodes_request_ops = {
	.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 ethtool_link_settings *lsettings;
	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;
	}
	lsettings = &ksettings.base;

	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");
	}

out_ops:
	ethnl_ops_complete(dev);
out_rtnl:
	rtnl_unlock();
	dev_put(dev);
	return ret;
}
+5 −0
Original line number Diff line number Diff line
@@ -638,6 +638,11 @@ static const struct genl_ops ethtool_genl_ops[] = {
		.dumpit	= ethnl_default_dumpit,
		.done	= ethnl_default_done,
	},
	{
		.cmd	= ETHTOOL_MSG_LINKMODES_SET,
		.flags	= GENL_UNS_ADMIN_PERM,
		.doit	= ethnl_set_linkmodes,
	},
};

static const struct genl_multicast_group ethtool_nl_mcgrps[] = {
+1 −0
Original line number Diff line number Diff line
@@ -335,5 +335,6 @@ extern const struct ethnl_request_ops ethnl_linkinfo_request_ops;
extern const struct ethnl_request_ops ethnl_linkmodes_request_ops;

int ethnl_set_linkinfo(struct sk_buff *skb, struct genl_info *info);
int ethnl_set_linkmodes(struct sk_buff *skb, struct genl_info *info);

#endif /* _NET_ETHTOOL_NETLINK_H */