18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci#include <net/xdp_sock_drv.h>
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include "netlink.h"
68c2ecf20Sopenharmony_ci#include "common.h"
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_cistruct channels_req_info {
98c2ecf20Sopenharmony_ci	struct ethnl_req_info		base;
108c2ecf20Sopenharmony_ci};
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistruct channels_reply_data {
138c2ecf20Sopenharmony_ci	struct ethnl_reply_data		base;
148c2ecf20Sopenharmony_ci	struct ethtool_channels		channels;
158c2ecf20Sopenharmony_ci};
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define CHANNELS_REPDATA(__reply_base) \
188c2ecf20Sopenharmony_ci	container_of(__reply_base, struct channels_reply_data, base)
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ciconst struct nla_policy ethnl_channels_get_policy[] = {
218c2ecf20Sopenharmony_ci	[ETHTOOL_A_CHANNELS_HEADER]		=
228c2ecf20Sopenharmony_ci		NLA_POLICY_NESTED(ethnl_header_policy),
238c2ecf20Sopenharmony_ci};
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic int channels_prepare_data(const struct ethnl_req_info *req_base,
268c2ecf20Sopenharmony_ci				 struct ethnl_reply_data *reply_base,
278c2ecf20Sopenharmony_ci				 struct genl_info *info)
288c2ecf20Sopenharmony_ci{
298c2ecf20Sopenharmony_ci	struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
308c2ecf20Sopenharmony_ci	struct net_device *dev = reply_base->dev;
318c2ecf20Sopenharmony_ci	int ret;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	if (!dev->ethtool_ops->get_channels)
348c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
358c2ecf20Sopenharmony_ci	ret = ethnl_ops_begin(dev);
368c2ecf20Sopenharmony_ci	if (ret < 0)
378c2ecf20Sopenharmony_ci		return ret;
388c2ecf20Sopenharmony_ci	dev->ethtool_ops->get_channels(dev, &data->channels);
398c2ecf20Sopenharmony_ci	ethnl_ops_complete(dev);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	return 0;
428c2ecf20Sopenharmony_ci}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic int channels_reply_size(const struct ethnl_req_info *req_base,
458c2ecf20Sopenharmony_ci			       const struct ethnl_reply_data *reply_base)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	return nla_total_size(sizeof(u32)) +	/* _CHANNELS_RX_MAX */
488c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32)) +	/* _CHANNELS_TX_MAX */
498c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32)) +	/* _CHANNELS_OTHER_MAX */
508c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32)) +	/* _CHANNELS_COMBINED_MAX */
518c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32)) +	/* _CHANNELS_RX_COUNT */
528c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32)) +	/* _CHANNELS_TX_COUNT */
538c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32)) +	/* _CHANNELS_OTHER_COUNT */
548c2ecf20Sopenharmony_ci	       nla_total_size(sizeof(u32));	/* _CHANNELS_COMBINED_COUNT */
558c2ecf20Sopenharmony_ci}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_cistatic int channels_fill_reply(struct sk_buff *skb,
588c2ecf20Sopenharmony_ci			       const struct ethnl_req_info *req_base,
598c2ecf20Sopenharmony_ci			       const struct ethnl_reply_data *reply_base)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	const struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
628c2ecf20Sopenharmony_ci	const struct ethtool_channels *channels = &data->channels;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	if ((channels->max_rx &&
658c2ecf20Sopenharmony_ci	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_MAX,
668c2ecf20Sopenharmony_ci			  channels->max_rx) ||
678c2ecf20Sopenharmony_ci	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_COUNT,
688c2ecf20Sopenharmony_ci			  channels->rx_count))) ||
698c2ecf20Sopenharmony_ci	    (channels->max_tx &&
708c2ecf20Sopenharmony_ci	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_MAX,
718c2ecf20Sopenharmony_ci			  channels->max_tx) ||
728c2ecf20Sopenharmony_ci	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_COUNT,
738c2ecf20Sopenharmony_ci			  channels->tx_count))) ||
748c2ecf20Sopenharmony_ci	    (channels->max_other &&
758c2ecf20Sopenharmony_ci	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_MAX,
768c2ecf20Sopenharmony_ci			  channels->max_other) ||
778c2ecf20Sopenharmony_ci	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_COUNT,
788c2ecf20Sopenharmony_ci			  channels->other_count))) ||
798c2ecf20Sopenharmony_ci	    (channels->max_combined &&
808c2ecf20Sopenharmony_ci	     (nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_MAX,
818c2ecf20Sopenharmony_ci			  channels->max_combined) ||
828c2ecf20Sopenharmony_ci	      nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_COUNT,
838c2ecf20Sopenharmony_ci			  channels->combined_count))))
848c2ecf20Sopenharmony_ci		return -EMSGSIZE;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return 0;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ciconst struct ethnl_request_ops ethnl_channels_request_ops = {
908c2ecf20Sopenharmony_ci	.request_cmd		= ETHTOOL_MSG_CHANNELS_GET,
918c2ecf20Sopenharmony_ci	.reply_cmd		= ETHTOOL_MSG_CHANNELS_GET_REPLY,
928c2ecf20Sopenharmony_ci	.hdr_attr		= ETHTOOL_A_CHANNELS_HEADER,
938c2ecf20Sopenharmony_ci	.req_info_size		= sizeof(struct channels_req_info),
948c2ecf20Sopenharmony_ci	.reply_data_size	= sizeof(struct channels_reply_data),
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	.prepare_data		= channels_prepare_data,
978c2ecf20Sopenharmony_ci	.reply_size		= channels_reply_size,
988c2ecf20Sopenharmony_ci	.fill_reply		= channels_fill_reply,
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci/* CHANNELS_SET */
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ciconst struct nla_policy ethnl_channels_set_policy[] = {
1048c2ecf20Sopenharmony_ci	[ETHTOOL_A_CHANNELS_HEADER]		=
1058c2ecf20Sopenharmony_ci		NLA_POLICY_NESTED(ethnl_header_policy),
1068c2ecf20Sopenharmony_ci	[ETHTOOL_A_CHANNELS_RX_COUNT]		= { .type = NLA_U32 },
1078c2ecf20Sopenharmony_ci	[ETHTOOL_A_CHANNELS_TX_COUNT]		= { .type = NLA_U32 },
1088c2ecf20Sopenharmony_ci	[ETHTOOL_A_CHANNELS_OTHER_COUNT]	= { .type = NLA_U32 },
1098c2ecf20Sopenharmony_ci	[ETHTOOL_A_CHANNELS_COMBINED_COUNT]	= { .type = NLA_U32 },
1108c2ecf20Sopenharmony_ci};
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ciint ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	unsigned int from_channel, old_total, i;
1158c2ecf20Sopenharmony_ci	bool mod = false, mod_combined = false;
1168c2ecf20Sopenharmony_ci	struct ethtool_channels channels = {};
1178c2ecf20Sopenharmony_ci	struct ethnl_req_info req_info = {};
1188c2ecf20Sopenharmony_ci	struct nlattr **tb = info->attrs;
1198c2ecf20Sopenharmony_ci	u32 err_attr, max_rx_in_use = 0;
1208c2ecf20Sopenharmony_ci	const struct ethtool_ops *ops;
1218c2ecf20Sopenharmony_ci	struct net_device *dev;
1228c2ecf20Sopenharmony_ci	int ret;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	ret = ethnl_parse_header_dev_get(&req_info,
1258c2ecf20Sopenharmony_ci					 tb[ETHTOOL_A_CHANNELS_HEADER],
1268c2ecf20Sopenharmony_ci					 genl_info_net(info), info->extack,
1278c2ecf20Sopenharmony_ci					 true);
1288c2ecf20Sopenharmony_ci	if (ret < 0)
1298c2ecf20Sopenharmony_ci		return ret;
1308c2ecf20Sopenharmony_ci	dev = req_info.dev;
1318c2ecf20Sopenharmony_ci	ops = dev->ethtool_ops;
1328c2ecf20Sopenharmony_ci	ret = -EOPNOTSUPP;
1338c2ecf20Sopenharmony_ci	if (!ops->get_channels || !ops->set_channels)
1348c2ecf20Sopenharmony_ci		goto out_dev;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	rtnl_lock();
1378c2ecf20Sopenharmony_ci	ret = ethnl_ops_begin(dev);
1388c2ecf20Sopenharmony_ci	if (ret < 0)
1398c2ecf20Sopenharmony_ci		goto out_rtnl;
1408c2ecf20Sopenharmony_ci	ops->get_channels(dev, &channels);
1418c2ecf20Sopenharmony_ci	old_total = channels.combined_count +
1428c2ecf20Sopenharmony_ci		    max(channels.rx_count, channels.tx_count);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	ethnl_update_u32(&channels.rx_count, tb[ETHTOOL_A_CHANNELS_RX_COUNT],
1458c2ecf20Sopenharmony_ci			 &mod);
1468c2ecf20Sopenharmony_ci	ethnl_update_u32(&channels.tx_count, tb[ETHTOOL_A_CHANNELS_TX_COUNT],
1478c2ecf20Sopenharmony_ci			 &mod);
1488c2ecf20Sopenharmony_ci	ethnl_update_u32(&channels.other_count,
1498c2ecf20Sopenharmony_ci			 tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], &mod);
1508c2ecf20Sopenharmony_ci	ethnl_update_u32(&channels.combined_count,
1518c2ecf20Sopenharmony_ci			 tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod_combined);
1528c2ecf20Sopenharmony_ci	mod |= mod_combined;
1538c2ecf20Sopenharmony_ci	ret = 0;
1548c2ecf20Sopenharmony_ci	if (!mod)
1558c2ecf20Sopenharmony_ci		goto out_ops;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	/* ensure new channel counts are within limits */
1588c2ecf20Sopenharmony_ci	if (channels.rx_count > channels.max_rx)
1598c2ecf20Sopenharmony_ci		err_attr = ETHTOOL_A_CHANNELS_RX_COUNT;
1608c2ecf20Sopenharmony_ci	else if (channels.tx_count > channels.max_tx)
1618c2ecf20Sopenharmony_ci		err_attr = ETHTOOL_A_CHANNELS_TX_COUNT;
1628c2ecf20Sopenharmony_ci	else if (channels.other_count > channels.max_other)
1638c2ecf20Sopenharmony_ci		err_attr = ETHTOOL_A_CHANNELS_OTHER_COUNT;
1648c2ecf20Sopenharmony_ci	else if (channels.combined_count > channels.max_combined)
1658c2ecf20Sopenharmony_ci		err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT;
1668c2ecf20Sopenharmony_ci	else
1678c2ecf20Sopenharmony_ci		err_attr = 0;
1688c2ecf20Sopenharmony_ci	if (err_attr) {
1698c2ecf20Sopenharmony_ci		ret = -EINVAL;
1708c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr],
1718c2ecf20Sopenharmony_ci				    "requested channel count exceeds maximum");
1728c2ecf20Sopenharmony_ci		goto out_ops;
1738c2ecf20Sopenharmony_ci	}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	/* ensure there is at least one RX and one TX channel */
1768c2ecf20Sopenharmony_ci	if (!channels.combined_count && !channels.rx_count)
1778c2ecf20Sopenharmony_ci		err_attr = ETHTOOL_A_CHANNELS_RX_COUNT;
1788c2ecf20Sopenharmony_ci	else if (!channels.combined_count && !channels.tx_count)
1798c2ecf20Sopenharmony_ci		err_attr = ETHTOOL_A_CHANNELS_TX_COUNT;
1808c2ecf20Sopenharmony_ci	else
1818c2ecf20Sopenharmony_ci		err_attr = 0;
1828c2ecf20Sopenharmony_ci	if (err_attr) {
1838c2ecf20Sopenharmony_ci		if (mod_combined)
1848c2ecf20Sopenharmony_ci			err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT;
1858c2ecf20Sopenharmony_ci		ret = -EINVAL;
1868c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr],
1878c2ecf20Sopenharmony_ci				    "requested channel counts would result in no RX or TX channel being configured");
1888c2ecf20Sopenharmony_ci		goto out_ops;
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	/* ensure the new Rx count fits within the configured Rx flow
1928c2ecf20Sopenharmony_ci	 * indirection table settings
1938c2ecf20Sopenharmony_ci	 */
1948c2ecf20Sopenharmony_ci	if (netif_is_rxfh_configured(dev) &&
1958c2ecf20Sopenharmony_ci	    !ethtool_get_max_rxfh_channel(dev, &max_rx_in_use) &&
1968c2ecf20Sopenharmony_ci	    (channels.combined_count + channels.rx_count) <= max_rx_in_use) {
1978c2ecf20Sopenharmony_ci		ret = -EINVAL;
1988c2ecf20Sopenharmony_ci		GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings");
1998c2ecf20Sopenharmony_ci		goto out_ops;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	/* Disabling channels, query zero-copy AF_XDP sockets */
2038c2ecf20Sopenharmony_ci	from_channel = channels.combined_count +
2048c2ecf20Sopenharmony_ci		       min(channels.rx_count, channels.tx_count);
2058c2ecf20Sopenharmony_ci	for (i = from_channel; i < old_total; i++)
2068c2ecf20Sopenharmony_ci		if (xsk_get_pool_from_qid(dev, i)) {
2078c2ecf20Sopenharmony_ci			ret = -EINVAL;
2088c2ecf20Sopenharmony_ci			GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets");
2098c2ecf20Sopenharmony_ci			goto out_ops;
2108c2ecf20Sopenharmony_ci		}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	ret = dev->ethtool_ops->set_channels(dev, &channels);
2138c2ecf20Sopenharmony_ci	if (ret < 0)
2148c2ecf20Sopenharmony_ci		goto out_ops;
2158c2ecf20Sopenharmony_ci	ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF, NULL);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ciout_ops:
2188c2ecf20Sopenharmony_ci	ethnl_ops_complete(dev);
2198c2ecf20Sopenharmony_ciout_rtnl:
2208c2ecf20Sopenharmony_ci	rtnl_unlock();
2218c2ecf20Sopenharmony_ciout_dev:
2228c2ecf20Sopenharmony_ci	dev_put(dev);
2238c2ecf20Sopenharmony_ci	return ret;
2248c2ecf20Sopenharmony_ci}
225