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