162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <net/xdp_sock_drv.h> 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include "netlink.h" 662306a36Sopenharmony_ci#include "common.h" 762306a36Sopenharmony_ci 862306a36Sopenharmony_cistruct channels_req_info { 962306a36Sopenharmony_ci struct ethnl_req_info base; 1062306a36Sopenharmony_ci}; 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistruct channels_reply_data { 1362306a36Sopenharmony_ci struct ethnl_reply_data base; 1462306a36Sopenharmony_ci struct ethtool_channels channels; 1562306a36Sopenharmony_ci}; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define CHANNELS_REPDATA(__reply_base) \ 1862306a36Sopenharmony_ci container_of(__reply_base, struct channels_reply_data, base) 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ciconst struct nla_policy ethnl_channels_get_policy[] = { 2162306a36Sopenharmony_ci [ETHTOOL_A_CHANNELS_HEADER] = 2262306a36Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic int channels_prepare_data(const struct ethnl_req_info *req_base, 2662306a36Sopenharmony_ci struct ethnl_reply_data *reply_base, 2762306a36Sopenharmony_ci const struct genl_info *info) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci struct channels_reply_data *data = CHANNELS_REPDATA(reply_base); 3062306a36Sopenharmony_ci struct net_device *dev = reply_base->dev; 3162306a36Sopenharmony_ci int ret; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci if (!dev->ethtool_ops->get_channels) 3462306a36Sopenharmony_ci return -EOPNOTSUPP; 3562306a36Sopenharmony_ci ret = ethnl_ops_begin(dev); 3662306a36Sopenharmony_ci if (ret < 0) 3762306a36Sopenharmony_ci return ret; 3862306a36Sopenharmony_ci dev->ethtool_ops->get_channels(dev, &data->channels); 3962306a36Sopenharmony_ci ethnl_ops_complete(dev); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci return 0; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int channels_reply_size(const struct ethnl_req_info *req_base, 4562306a36Sopenharmony_ci const struct ethnl_reply_data *reply_base) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci return nla_total_size(sizeof(u32)) + /* _CHANNELS_RX_MAX */ 4862306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _CHANNELS_TX_MAX */ 4962306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _CHANNELS_OTHER_MAX */ 5062306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _CHANNELS_COMBINED_MAX */ 5162306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _CHANNELS_RX_COUNT */ 5262306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _CHANNELS_TX_COUNT */ 5362306a36Sopenharmony_ci nla_total_size(sizeof(u32)) + /* _CHANNELS_OTHER_COUNT */ 5462306a36Sopenharmony_ci nla_total_size(sizeof(u32)); /* _CHANNELS_COMBINED_COUNT */ 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic int channels_fill_reply(struct sk_buff *skb, 5862306a36Sopenharmony_ci const struct ethnl_req_info *req_base, 5962306a36Sopenharmony_ci const struct ethnl_reply_data *reply_base) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci const struct channels_reply_data *data = CHANNELS_REPDATA(reply_base); 6262306a36Sopenharmony_ci const struct ethtool_channels *channels = &data->channels; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci if ((channels->max_rx && 6562306a36Sopenharmony_ci (nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_MAX, 6662306a36Sopenharmony_ci channels->max_rx) || 6762306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_COUNT, 6862306a36Sopenharmony_ci channels->rx_count))) || 6962306a36Sopenharmony_ci (channels->max_tx && 7062306a36Sopenharmony_ci (nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_MAX, 7162306a36Sopenharmony_ci channels->max_tx) || 7262306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_COUNT, 7362306a36Sopenharmony_ci channels->tx_count))) || 7462306a36Sopenharmony_ci (channels->max_other && 7562306a36Sopenharmony_ci (nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_MAX, 7662306a36Sopenharmony_ci channels->max_other) || 7762306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_COUNT, 7862306a36Sopenharmony_ci channels->other_count))) || 7962306a36Sopenharmony_ci (channels->max_combined && 8062306a36Sopenharmony_ci (nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_MAX, 8162306a36Sopenharmony_ci channels->max_combined) || 8262306a36Sopenharmony_ci nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_COUNT, 8362306a36Sopenharmony_ci channels->combined_count)))) 8462306a36Sopenharmony_ci return -EMSGSIZE; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci/* CHANNELS_SET */ 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ciconst struct nla_policy ethnl_channels_set_policy[] = { 9262306a36Sopenharmony_ci [ETHTOOL_A_CHANNELS_HEADER] = 9362306a36Sopenharmony_ci NLA_POLICY_NESTED(ethnl_header_policy), 9462306a36Sopenharmony_ci [ETHTOOL_A_CHANNELS_RX_COUNT] = { .type = NLA_U32 }, 9562306a36Sopenharmony_ci [ETHTOOL_A_CHANNELS_TX_COUNT] = { .type = NLA_U32 }, 9662306a36Sopenharmony_ci [ETHTOOL_A_CHANNELS_OTHER_COUNT] = { .type = NLA_U32 }, 9762306a36Sopenharmony_ci [ETHTOOL_A_CHANNELS_COMBINED_COUNT] = { .type = NLA_U32 }, 9862306a36Sopenharmony_ci}; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int 10162306a36Sopenharmony_ciethnl_set_channels_validate(struct ethnl_req_info *req_info, 10262306a36Sopenharmony_ci struct genl_info *info) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci const struct ethtool_ops *ops = req_info->dev->ethtool_ops; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return ops->get_channels && ops->set_channels ? 1 : -EOPNOTSUPP; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int 11062306a36Sopenharmony_ciethnl_set_channels(struct ethnl_req_info *req_info, struct genl_info *info) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci unsigned int from_channel, old_total, i; 11362306a36Sopenharmony_ci bool mod = false, mod_combined = false; 11462306a36Sopenharmony_ci struct net_device *dev = req_info->dev; 11562306a36Sopenharmony_ci struct ethtool_channels channels = {}; 11662306a36Sopenharmony_ci struct nlattr **tb = info->attrs; 11762306a36Sopenharmony_ci u32 err_attr, max_rxfh_in_use; 11862306a36Sopenharmony_ci u64 max_rxnfc_in_use; 11962306a36Sopenharmony_ci int ret; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci dev->ethtool_ops->get_channels(dev, &channels); 12262306a36Sopenharmony_ci old_total = channels.combined_count + 12362306a36Sopenharmony_ci max(channels.rx_count, channels.tx_count); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci ethnl_update_u32(&channels.rx_count, tb[ETHTOOL_A_CHANNELS_RX_COUNT], 12662306a36Sopenharmony_ci &mod); 12762306a36Sopenharmony_ci ethnl_update_u32(&channels.tx_count, tb[ETHTOOL_A_CHANNELS_TX_COUNT], 12862306a36Sopenharmony_ci &mod); 12962306a36Sopenharmony_ci ethnl_update_u32(&channels.other_count, 13062306a36Sopenharmony_ci tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], &mod); 13162306a36Sopenharmony_ci ethnl_update_u32(&channels.combined_count, 13262306a36Sopenharmony_ci tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod_combined); 13362306a36Sopenharmony_ci mod |= mod_combined; 13462306a36Sopenharmony_ci if (!mod) 13562306a36Sopenharmony_ci return 0; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* ensure new channel counts are within limits */ 13862306a36Sopenharmony_ci if (channels.rx_count > channels.max_rx) 13962306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_RX_COUNT; 14062306a36Sopenharmony_ci else if (channels.tx_count > channels.max_tx) 14162306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_TX_COUNT; 14262306a36Sopenharmony_ci else if (channels.other_count > channels.max_other) 14362306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_OTHER_COUNT; 14462306a36Sopenharmony_ci else if (channels.combined_count > channels.max_combined) 14562306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT; 14662306a36Sopenharmony_ci else 14762306a36Sopenharmony_ci err_attr = 0; 14862306a36Sopenharmony_ci if (err_attr) { 14962306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr], 15062306a36Sopenharmony_ci "requested channel count exceeds maximum"); 15162306a36Sopenharmony_ci return -EINVAL; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci /* ensure there is at least one RX and one TX channel */ 15562306a36Sopenharmony_ci if (!channels.combined_count && !channels.rx_count) 15662306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_RX_COUNT; 15762306a36Sopenharmony_ci else if (!channels.combined_count && !channels.tx_count) 15862306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_TX_COUNT; 15962306a36Sopenharmony_ci else 16062306a36Sopenharmony_ci err_attr = 0; 16162306a36Sopenharmony_ci if (err_attr) { 16262306a36Sopenharmony_ci if (mod_combined) 16362306a36Sopenharmony_ci err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT; 16462306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr], 16562306a36Sopenharmony_ci "requested channel counts would result in no RX or TX channel being configured"); 16662306a36Sopenharmony_ci return -EINVAL; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* ensure the new Rx count fits within the configured Rx flow 17062306a36Sopenharmony_ci * indirection table/rxnfc settings 17162306a36Sopenharmony_ci */ 17262306a36Sopenharmony_ci if (ethtool_get_max_rxnfc_channel(dev, &max_rxnfc_in_use)) 17362306a36Sopenharmony_ci max_rxnfc_in_use = 0; 17462306a36Sopenharmony_ci if (!netif_is_rxfh_configured(dev) || 17562306a36Sopenharmony_ci ethtool_get_max_rxfh_channel(dev, &max_rxfh_in_use)) 17662306a36Sopenharmony_ci max_rxfh_in_use = 0; 17762306a36Sopenharmony_ci if (channels.combined_count + channels.rx_count <= max_rxfh_in_use) { 17862306a36Sopenharmony_ci GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings"); 17962306a36Sopenharmony_ci return -EINVAL; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci if (channels.combined_count + channels.rx_count <= max_rxnfc_in_use) { 18262306a36Sopenharmony_ci GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing ntuple filter settings"); 18362306a36Sopenharmony_ci return -EINVAL; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* Disabling channels, query zero-copy AF_XDP sockets */ 18762306a36Sopenharmony_ci from_channel = channels.combined_count + 18862306a36Sopenharmony_ci min(channels.rx_count, channels.tx_count); 18962306a36Sopenharmony_ci for (i = from_channel; i < old_total; i++) 19062306a36Sopenharmony_ci if (xsk_get_pool_from_qid(dev, i)) { 19162306a36Sopenharmony_ci GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets"); 19262306a36Sopenharmony_ci return -EINVAL; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci ret = dev->ethtool_ops->set_channels(dev, &channels); 19662306a36Sopenharmony_ci return ret < 0 ? ret : 1; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ciconst struct ethnl_request_ops ethnl_channels_request_ops = { 20062306a36Sopenharmony_ci .request_cmd = ETHTOOL_MSG_CHANNELS_GET, 20162306a36Sopenharmony_ci .reply_cmd = ETHTOOL_MSG_CHANNELS_GET_REPLY, 20262306a36Sopenharmony_ci .hdr_attr = ETHTOOL_A_CHANNELS_HEADER, 20362306a36Sopenharmony_ci .req_info_size = sizeof(struct channels_req_info), 20462306a36Sopenharmony_ci .reply_data_size = sizeof(struct channels_reply_data), 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci .prepare_data = channels_prepare_data, 20762306a36Sopenharmony_ci .reply_size = channels_reply_size, 20862306a36Sopenharmony_ci .fill_reply = channels_fill_reply, 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci .set_validate = ethnl_set_channels_validate, 21162306a36Sopenharmony_ci .set = ethnl_set_channels, 21262306a36Sopenharmony_ci .set_ntf_cmd = ETHTOOL_MSG_CHANNELS_NTF, 21362306a36Sopenharmony_ci}; 214