xref: /openbmc/linux/net/ethtool/features.c (revision 1188f7f111c61394ec56beb8e30322305a8220b6)
10524399dSMichal Kubecek // SPDX-License-Identifier: GPL-2.0-only
20524399dSMichal Kubecek 
30524399dSMichal Kubecek #include "netlink.h"
40524399dSMichal Kubecek #include "common.h"
50524399dSMichal Kubecek #include "bitset.h"
60524399dSMichal Kubecek 
70524399dSMichal Kubecek struct features_req_info {
80524399dSMichal Kubecek 	struct ethnl_req_info	base;
90524399dSMichal Kubecek };
100524399dSMichal Kubecek 
110524399dSMichal Kubecek struct features_reply_data {
120524399dSMichal Kubecek 	struct ethnl_reply_data	base;
130524399dSMichal Kubecek 	u32			hw[ETHTOOL_DEV_FEATURE_WORDS];
140524399dSMichal Kubecek 	u32			wanted[ETHTOOL_DEV_FEATURE_WORDS];
150524399dSMichal Kubecek 	u32			active[ETHTOOL_DEV_FEATURE_WORDS];
160524399dSMichal Kubecek 	u32			nochange[ETHTOOL_DEV_FEATURE_WORDS];
170524399dSMichal Kubecek 	u32			all[ETHTOOL_DEV_FEATURE_WORDS];
180524399dSMichal Kubecek };
190524399dSMichal Kubecek 
200524399dSMichal Kubecek #define FEATURES_REPDATA(__reply_base) \
210524399dSMichal Kubecek 	container_of(__reply_base, struct features_reply_data, base)
220524399dSMichal Kubecek 
23ff419afaSJakub Kicinski const struct nla_policy ethnl_features_get_policy[] = {
24329d9c33SJakub Kicinski 	[ETHTOOL_A_FEATURES_HEADER]	=
25329d9c33SJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy),
260524399dSMichal Kubecek };
270524399dSMichal Kubecek 
ethnl_features_to_bitmap32(u32 * dest,netdev_features_t src)280524399dSMichal Kubecek static void ethnl_features_to_bitmap32(u32 *dest, netdev_features_t src)
290524399dSMichal Kubecek {
300524399dSMichal Kubecek 	unsigned int i;
310524399dSMichal Kubecek 
320524399dSMichal Kubecek 	for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; i++)
330524399dSMichal Kubecek 		dest[i] = src >> (32 * i);
340524399dSMichal Kubecek }
350524399dSMichal Kubecek 
features_prepare_data(const struct ethnl_req_info * req_base,struct ethnl_reply_data * reply_base,const struct genl_info * info)360524399dSMichal Kubecek static int features_prepare_data(const struct ethnl_req_info *req_base,
370524399dSMichal Kubecek 				 struct ethnl_reply_data *reply_base,
38f946270dSJakub Kicinski 				 const struct genl_info *info)
390524399dSMichal Kubecek {
400524399dSMichal Kubecek 	struct features_reply_data *data = FEATURES_REPDATA(reply_base);
410524399dSMichal Kubecek 	struct net_device *dev = reply_base->dev;
420524399dSMichal Kubecek 	netdev_features_t all_features;
430524399dSMichal Kubecek 
440524399dSMichal Kubecek 	ethnl_features_to_bitmap32(data->hw, dev->hw_features);
450524399dSMichal Kubecek 	ethnl_features_to_bitmap32(data->wanted, dev->wanted_features);
460524399dSMichal Kubecek 	ethnl_features_to_bitmap32(data->active, dev->features);
470524399dSMichal Kubecek 	ethnl_features_to_bitmap32(data->nochange, NETIF_F_NEVER_CHANGE);
480524399dSMichal Kubecek 	all_features = GENMASK_ULL(NETDEV_FEATURE_COUNT - 1, 0);
490524399dSMichal Kubecek 	ethnl_features_to_bitmap32(data->all, all_features);
500524399dSMichal Kubecek 
510524399dSMichal Kubecek 	return 0;
520524399dSMichal Kubecek }
530524399dSMichal Kubecek 
features_reply_size(const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)540524399dSMichal Kubecek static int features_reply_size(const struct ethnl_req_info *req_base,
550524399dSMichal Kubecek 			       const struct ethnl_reply_data *reply_base)
560524399dSMichal Kubecek {
570524399dSMichal Kubecek 	const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
580524399dSMichal Kubecek 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
590524399dSMichal Kubecek 	unsigned int len = 0;
600524399dSMichal Kubecek 	int ret;
610524399dSMichal Kubecek 
620524399dSMichal Kubecek 	ret = ethnl_bitset32_size(data->hw, data->all, NETDEV_FEATURE_COUNT,
630524399dSMichal Kubecek 				  netdev_features_strings, compact);
640524399dSMichal Kubecek 	if (ret < 0)
650524399dSMichal Kubecek 		return ret;
660524399dSMichal Kubecek 	len += ret;
670524399dSMichal Kubecek 	ret = ethnl_bitset32_size(data->wanted, NULL, NETDEV_FEATURE_COUNT,
680524399dSMichal Kubecek 				  netdev_features_strings, compact);
690524399dSMichal Kubecek 	if (ret < 0)
700524399dSMichal Kubecek 		return ret;
710524399dSMichal Kubecek 	len += ret;
720524399dSMichal Kubecek 	ret = ethnl_bitset32_size(data->active, NULL, NETDEV_FEATURE_COUNT,
730524399dSMichal Kubecek 				  netdev_features_strings, compact);
740524399dSMichal Kubecek 	if (ret < 0)
750524399dSMichal Kubecek 		return ret;
760524399dSMichal Kubecek 	len += ret;
770524399dSMichal Kubecek 	ret = ethnl_bitset32_size(data->nochange, NULL, NETDEV_FEATURE_COUNT,
780524399dSMichal Kubecek 				  netdev_features_strings, compact);
790524399dSMichal Kubecek 	if (ret < 0)
800524399dSMichal Kubecek 		return ret;
810524399dSMichal Kubecek 	len += ret;
820524399dSMichal Kubecek 
830524399dSMichal Kubecek 	return len;
840524399dSMichal Kubecek }
850524399dSMichal Kubecek 
features_fill_reply(struct sk_buff * skb,const struct ethnl_req_info * req_base,const struct ethnl_reply_data * reply_base)860524399dSMichal Kubecek static int features_fill_reply(struct sk_buff *skb,
870524399dSMichal Kubecek 			       const struct ethnl_req_info *req_base,
880524399dSMichal Kubecek 			       const struct ethnl_reply_data *reply_base)
890524399dSMichal Kubecek {
900524399dSMichal Kubecek 	const struct features_reply_data *data = FEATURES_REPDATA(reply_base);
910524399dSMichal Kubecek 	bool compact = req_base->flags & ETHTOOL_FLAG_COMPACT_BITSETS;
920524399dSMichal Kubecek 	int ret;
930524399dSMichal Kubecek 
940524399dSMichal Kubecek 	ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_HW, data->hw,
950524399dSMichal Kubecek 				 data->all, NETDEV_FEATURE_COUNT,
960524399dSMichal Kubecek 				 netdev_features_strings, compact);
970524399dSMichal Kubecek 	if (ret < 0)
980524399dSMichal Kubecek 		return ret;
990524399dSMichal Kubecek 	ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_WANTED, data->wanted,
1000524399dSMichal Kubecek 				 NULL, NETDEV_FEATURE_COUNT,
1010524399dSMichal Kubecek 				 netdev_features_strings, compact);
1020524399dSMichal Kubecek 	if (ret < 0)
1030524399dSMichal Kubecek 		return ret;
1040524399dSMichal Kubecek 	ret = ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_ACTIVE, data->active,
1050524399dSMichal Kubecek 				 NULL, NETDEV_FEATURE_COUNT,
1060524399dSMichal Kubecek 				 netdev_features_strings, compact);
1070524399dSMichal Kubecek 	if (ret < 0)
1080524399dSMichal Kubecek 		return ret;
1090524399dSMichal Kubecek 	return ethnl_put_bitset32(skb, ETHTOOL_A_FEATURES_NOCHANGE,
1100524399dSMichal Kubecek 				  data->nochange, NULL, NETDEV_FEATURE_COUNT,
1110524399dSMichal Kubecek 				  netdev_features_strings, compact);
1120524399dSMichal Kubecek }
1130524399dSMichal Kubecek 
1140524399dSMichal Kubecek const struct ethnl_request_ops ethnl_features_request_ops = {
1150524399dSMichal Kubecek 	.request_cmd		= ETHTOOL_MSG_FEATURES_GET,
1160524399dSMichal Kubecek 	.reply_cmd		= ETHTOOL_MSG_FEATURES_GET_REPLY,
1170524399dSMichal Kubecek 	.hdr_attr		= ETHTOOL_A_FEATURES_HEADER,
1180524399dSMichal Kubecek 	.req_info_size		= sizeof(struct features_req_info),
1190524399dSMichal Kubecek 	.reply_data_size	= sizeof(struct features_reply_data),
1200524399dSMichal Kubecek 
1210524399dSMichal Kubecek 	.prepare_data		= features_prepare_data,
1220524399dSMichal Kubecek 	.reply_size		= features_reply_size,
1230524399dSMichal Kubecek 	.fill_reply		= features_fill_reply,
1240524399dSMichal Kubecek };
1250980bfcdSMichal Kubecek 
1260980bfcdSMichal Kubecek /* FEATURES_SET */
1270980bfcdSMichal Kubecek 
128ff419afaSJakub Kicinski const struct nla_policy ethnl_features_set_policy[] = {
129329d9c33SJakub Kicinski 	[ETHTOOL_A_FEATURES_HEADER]	=
130329d9c33SJakub Kicinski 		NLA_POLICY_NESTED(ethnl_header_policy),
1310980bfcdSMichal Kubecek 	[ETHTOOL_A_FEATURES_WANTED]	= { .type = NLA_NESTED },
1320980bfcdSMichal Kubecek };
1330980bfcdSMichal Kubecek 
ethnl_features_to_bitmap(unsigned long * dest,netdev_features_t val)1340980bfcdSMichal Kubecek static void ethnl_features_to_bitmap(unsigned long *dest, netdev_features_t val)
1350980bfcdSMichal Kubecek {
1360980bfcdSMichal Kubecek 	const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
1370980bfcdSMichal Kubecek 	unsigned int i;
1380980bfcdSMichal Kubecek 
1390980bfcdSMichal Kubecek 	for (i = 0; i < words; i++)
1400980bfcdSMichal Kubecek 		dest[i] = (unsigned long)(val >> (i * BITS_PER_LONG));
1410980bfcdSMichal Kubecek }
1420980bfcdSMichal Kubecek 
ethnl_bitmap_to_features(unsigned long * src)1430980bfcdSMichal Kubecek static netdev_features_t ethnl_bitmap_to_features(unsigned long *src)
1440980bfcdSMichal Kubecek {
1450980bfcdSMichal Kubecek 	const unsigned int nft_bits = sizeof(netdev_features_t) * BITS_PER_BYTE;
1460980bfcdSMichal Kubecek 	const unsigned int words = BITS_TO_LONGS(NETDEV_FEATURE_COUNT);
1470980bfcdSMichal Kubecek 	netdev_features_t ret = 0;
1480980bfcdSMichal Kubecek 	unsigned int i;
1490980bfcdSMichal Kubecek 
1500980bfcdSMichal Kubecek 	for (i = 0; i < words; i++)
1510980bfcdSMichal Kubecek 		ret |= (netdev_features_t)(src[i]) << (i * BITS_PER_LONG);
1520980bfcdSMichal Kubecek 	ret &= ~(netdev_features_t)0 >> (nft_bits - NETDEV_FEATURE_COUNT);
1530980bfcdSMichal Kubecek 	return ret;
1540980bfcdSMichal Kubecek }
1550980bfcdSMichal Kubecek 
features_send_reply(struct net_device * dev,struct genl_info * info,const unsigned long * wanted,const unsigned long * wanted_mask,const unsigned long * active,const unsigned long * active_mask,bool compact)1560980bfcdSMichal Kubecek static int features_send_reply(struct net_device *dev, struct genl_info *info,
1570980bfcdSMichal Kubecek 			       const unsigned long *wanted,
1580980bfcdSMichal Kubecek 			       const unsigned long *wanted_mask,
1590980bfcdSMichal Kubecek 			       const unsigned long *active,
1600980bfcdSMichal Kubecek 			       const unsigned long *active_mask, bool compact)
1610980bfcdSMichal Kubecek {
1620980bfcdSMichal Kubecek 	struct sk_buff *rskb;
1630980bfcdSMichal Kubecek 	void *reply_payload;
1640980bfcdSMichal Kubecek 	int reply_len = 0;
1650980bfcdSMichal Kubecek 	int ret;
1660980bfcdSMichal Kubecek 
1670980bfcdSMichal Kubecek 	reply_len = ethnl_reply_header_size();
1680980bfcdSMichal Kubecek 	ret = ethnl_bitset_size(wanted, wanted_mask, NETDEV_FEATURE_COUNT,
1690980bfcdSMichal Kubecek 				netdev_features_strings, compact);
1700980bfcdSMichal Kubecek 	if (ret < 0)
1710980bfcdSMichal Kubecek 		goto err;
1720980bfcdSMichal Kubecek 	reply_len += ret;
1730980bfcdSMichal Kubecek 	ret = ethnl_bitset_size(active, active_mask, NETDEV_FEATURE_COUNT,
1740980bfcdSMichal Kubecek 				netdev_features_strings, compact);
1750980bfcdSMichal Kubecek 	if (ret < 0)
1760980bfcdSMichal Kubecek 		goto err;
1770980bfcdSMichal Kubecek 	reply_len += ret;
1780980bfcdSMichal Kubecek 
1790980bfcdSMichal Kubecek 	ret = -ENOMEM;
1800980bfcdSMichal Kubecek 	rskb = ethnl_reply_init(reply_len, dev, ETHTOOL_MSG_FEATURES_SET_REPLY,
1810980bfcdSMichal Kubecek 				ETHTOOL_A_FEATURES_HEADER, info,
1820980bfcdSMichal Kubecek 				&reply_payload);
1830980bfcdSMichal Kubecek 	if (!rskb)
1840980bfcdSMichal Kubecek 		goto err;
1850980bfcdSMichal Kubecek 
1860980bfcdSMichal Kubecek 	ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_WANTED, wanted,
1870980bfcdSMichal Kubecek 			       wanted_mask, NETDEV_FEATURE_COUNT,
1880980bfcdSMichal Kubecek 			       netdev_features_strings, compact);
1890980bfcdSMichal Kubecek 	if (ret < 0)
1900980bfcdSMichal Kubecek 		goto nla_put_failure;
1910980bfcdSMichal Kubecek 	ret = ethnl_put_bitset(rskb, ETHTOOL_A_FEATURES_ACTIVE, active,
1920980bfcdSMichal Kubecek 			       active_mask, NETDEV_FEATURE_COUNT,
1930980bfcdSMichal Kubecek 			       netdev_features_strings, compact);
1940980bfcdSMichal Kubecek 	if (ret < 0)
1950980bfcdSMichal Kubecek 		goto nla_put_failure;
1960980bfcdSMichal Kubecek 
1970980bfcdSMichal Kubecek 	genlmsg_end(rskb, reply_payload);
1980980bfcdSMichal Kubecek 	ret = genlmsg_reply(rskb, info);
1990980bfcdSMichal Kubecek 	return ret;
2000980bfcdSMichal Kubecek 
2010980bfcdSMichal Kubecek nla_put_failure:
2020980bfcdSMichal Kubecek 	nlmsg_free(rskb);
2030980bfcdSMichal Kubecek 	WARN_ONCE(1, "calculated message payload length (%d) not sufficient\n",
2040980bfcdSMichal Kubecek 		  reply_len);
2050980bfcdSMichal Kubecek err:
2060980bfcdSMichal Kubecek 	GENL_SET_ERR_MSG(info, "failed to send reply message");
2070980bfcdSMichal Kubecek 	return ret;
2080980bfcdSMichal Kubecek }
2090980bfcdSMichal Kubecek 
ethnl_set_features(struct sk_buff * skb,struct genl_info * info)2100980bfcdSMichal Kubecek int ethnl_set_features(struct sk_buff *skb, struct genl_info *info)
2110980bfcdSMichal Kubecek {
2120980bfcdSMichal Kubecek 	DECLARE_BITMAP(wanted_diff_mask, NETDEV_FEATURE_COUNT);
2130980bfcdSMichal Kubecek 	DECLARE_BITMAP(active_diff_mask, NETDEV_FEATURE_COUNT);
2140980bfcdSMichal Kubecek 	DECLARE_BITMAP(old_active, NETDEV_FEATURE_COUNT);
215840110a4SMaxim Mikityanskiy 	DECLARE_BITMAP(old_wanted, NETDEV_FEATURE_COUNT);
2160980bfcdSMichal Kubecek 	DECLARE_BITMAP(new_active, NETDEV_FEATURE_COUNT);
217840110a4SMaxim Mikityanskiy 	DECLARE_BITMAP(new_wanted, NETDEV_FEATURE_COUNT);
2180980bfcdSMichal Kubecek 	DECLARE_BITMAP(req_wanted, NETDEV_FEATURE_COUNT);
2190980bfcdSMichal Kubecek 	DECLARE_BITMAP(req_mask, NETDEV_FEATURE_COUNT);
2200980bfcdSMichal Kubecek 	struct ethnl_req_info req_info = {};
2215028588bSJakub Kicinski 	struct nlattr **tb = info->attrs;
2220980bfcdSMichal Kubecek 	struct net_device *dev;
2239c6451efSMichal Kubecek 	bool mod;
2240980bfcdSMichal Kubecek 	int ret;
2250980bfcdSMichal Kubecek 
2260980bfcdSMichal Kubecek 	if (!tb[ETHTOOL_A_FEATURES_WANTED])
2270980bfcdSMichal Kubecek 		return -EINVAL;
2280980bfcdSMichal Kubecek 	ret = ethnl_parse_header_dev_get(&req_info,
2290980bfcdSMichal Kubecek 					 tb[ETHTOOL_A_FEATURES_HEADER],
2300980bfcdSMichal Kubecek 					 genl_info_net(info), info->extack,
2310980bfcdSMichal Kubecek 					 true);
2320980bfcdSMichal Kubecek 	if (ret < 0)
2330980bfcdSMichal Kubecek 		return ret;
2340980bfcdSMichal Kubecek 	dev = req_info.dev;
2350980bfcdSMichal Kubecek 
2360980bfcdSMichal Kubecek 	rtnl_lock();
237*2ae2e7cfSLudvig Pärsson 	ret = ethnl_ops_begin(dev);
238*2ae2e7cfSLudvig Pärsson 	if (ret < 0)
239*2ae2e7cfSLudvig Pärsson 		goto out_rtnl;
2400980bfcdSMichal Kubecek 	ethnl_features_to_bitmap(old_active, dev->features);
241840110a4SMaxim Mikityanskiy 	ethnl_features_to_bitmap(old_wanted, dev->wanted_features);
2420980bfcdSMichal Kubecek 	ret = ethnl_parse_bitset(req_wanted, req_mask, NETDEV_FEATURE_COUNT,
2430980bfcdSMichal Kubecek 				 tb[ETHTOOL_A_FEATURES_WANTED],
2440980bfcdSMichal Kubecek 				 netdev_features_strings, info->extack);
2450980bfcdSMichal Kubecek 	if (ret < 0)
246*2ae2e7cfSLudvig Pärsson 		goto out_ops;
2470980bfcdSMichal Kubecek 	if (ethnl_bitmap_to_features(req_mask) & ~NETIF_F_ETHTOOL_BITS) {
2480980bfcdSMichal Kubecek 		GENL_SET_ERR_MSG(info, "attempt to change non-ethtool features");
2490980bfcdSMichal Kubecek 		ret = -EINVAL;
250*2ae2e7cfSLudvig Pärsson 		goto out_ops;
2510980bfcdSMichal Kubecek 	}
2520980bfcdSMichal Kubecek 
253840110a4SMaxim Mikityanskiy 	/* set req_wanted bits not in req_mask from old_wanted */
2540980bfcdSMichal Kubecek 	bitmap_and(req_wanted, req_wanted, req_mask, NETDEV_FEATURE_COUNT);
255840110a4SMaxim Mikityanskiy 	bitmap_andnot(new_wanted, old_wanted, req_mask, NETDEV_FEATURE_COUNT);
256840110a4SMaxim Mikityanskiy 	bitmap_or(req_wanted, new_wanted, req_wanted, NETDEV_FEATURE_COUNT);
257f01204ecSMaxim Mikityanskiy 	if (!bitmap_equal(req_wanted, old_wanted, NETDEV_FEATURE_COUNT)) {
2582847bfedSMaxim Mikityanskiy 		dev->wanted_features &= ~dev->hw_features;
2592847bfedSMaxim Mikityanskiy 		dev->wanted_features |= ethnl_bitmap_to_features(req_wanted) & dev->hw_features;
2600980bfcdSMichal Kubecek 		__netdev_update_features(dev);
261f01204ecSMaxim Mikityanskiy 	}
2620980bfcdSMichal Kubecek 	ethnl_features_to_bitmap(new_active, dev->features);
2639c6451efSMichal Kubecek 	mod = !bitmap_equal(old_active, new_active, NETDEV_FEATURE_COUNT);
2640980bfcdSMichal Kubecek 
2650980bfcdSMichal Kubecek 	ret = 0;
2660980bfcdSMichal Kubecek 	if (!(req_info.flags & ETHTOOL_FLAG_OMIT_REPLY)) {
2670980bfcdSMichal Kubecek 		bool compact = req_info.flags & ETHTOOL_FLAG_COMPACT_BITSETS;
2680980bfcdSMichal Kubecek 
2690980bfcdSMichal Kubecek 		bitmap_xor(wanted_diff_mask, req_wanted, new_active,
2700980bfcdSMichal Kubecek 			   NETDEV_FEATURE_COUNT);
2710980bfcdSMichal Kubecek 		bitmap_xor(active_diff_mask, old_active, new_active,
2720980bfcdSMichal Kubecek 			   NETDEV_FEATURE_COUNT);
2730980bfcdSMichal Kubecek 		bitmap_and(wanted_diff_mask, wanted_diff_mask, req_mask,
2740980bfcdSMichal Kubecek 			   NETDEV_FEATURE_COUNT);
2750980bfcdSMichal Kubecek 		bitmap_and(req_wanted, req_wanted, wanted_diff_mask,
2760980bfcdSMichal Kubecek 			   NETDEV_FEATURE_COUNT);
2770980bfcdSMichal Kubecek 		bitmap_and(new_active, new_active, active_diff_mask,
2780980bfcdSMichal Kubecek 			   NETDEV_FEATURE_COUNT);
2790980bfcdSMichal Kubecek 
2800980bfcdSMichal Kubecek 		ret = features_send_reply(dev, info, req_wanted,
2810980bfcdSMichal Kubecek 					  wanted_diff_mask, new_active,
2820980bfcdSMichal Kubecek 					  active_diff_mask, compact);
2830980bfcdSMichal Kubecek 	}
2849c6451efSMichal Kubecek 	if (mod)
28541369138SAlexander Lobakin 		netdev_features_change(dev);
2860980bfcdSMichal Kubecek 
287*2ae2e7cfSLudvig Pärsson out_ops:
288*2ae2e7cfSLudvig Pärsson 	ethnl_ops_complete(dev);
2890980bfcdSMichal Kubecek out_rtnl:
2900980bfcdSMichal Kubecek 	rtnl_unlock();
29134ac17ecSEric Dumazet 	ethnl_parse_header_dev_put(&req_info);
2920980bfcdSMichal Kubecek 	return ret;
2930980bfcdSMichal Kubecek }
294