10c84979cSMichal Kubecek // SPDX-License-Identifier: GPL-2.0-only 20c84979cSMichal Kubecek 3a71506a4SMagnus Karlsson #include <net/xdp_sock_drv.h> 4e19c591eSMichal Kubecek 50c84979cSMichal Kubecek #include "netlink.h" 60c84979cSMichal Kubecek #include "common.h" 70c84979cSMichal Kubecek 80c84979cSMichal Kubecek struct channels_req_info { 90c84979cSMichal Kubecek struct ethnl_req_info base; 100c84979cSMichal Kubecek }; 110c84979cSMichal Kubecek 120c84979cSMichal Kubecek struct channels_reply_data { 130c84979cSMichal Kubecek struct ethnl_reply_data base; 140c84979cSMichal Kubecek struct ethtool_channels channels; 150c84979cSMichal Kubecek }; 160c84979cSMichal Kubecek 170c84979cSMichal Kubecek #define CHANNELS_REPDATA(__reply_base) \ 180c84979cSMichal Kubecek container_of(__reply_base, struct channels_reply_data, base) 190c84979cSMichal Kubecek 20ff419afaSJakub Kicinski const struct nla_policy ethnl_channels_get_policy[] = { 21329d9c33SJakub Kicinski [ETHTOOL_A_CHANNELS_HEADER] = 22329d9c33SJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy), 230c84979cSMichal Kubecek }; 240c84979cSMichal Kubecek 250c84979cSMichal Kubecek static int channels_prepare_data(const struct ethnl_req_info *req_base, 260c84979cSMichal Kubecek struct ethnl_reply_data *reply_base, 27f946270dSJakub Kicinski const struct genl_info *info) 280c84979cSMichal Kubecek { 290c84979cSMichal Kubecek struct channels_reply_data *data = CHANNELS_REPDATA(reply_base); 300c84979cSMichal Kubecek struct net_device *dev = reply_base->dev; 310c84979cSMichal Kubecek int ret; 320c84979cSMichal Kubecek 330c84979cSMichal Kubecek if (!dev->ethtool_ops->get_channels) 340c84979cSMichal Kubecek return -EOPNOTSUPP; 350c84979cSMichal Kubecek ret = ethnl_ops_begin(dev); 360c84979cSMichal Kubecek if (ret < 0) 370c84979cSMichal Kubecek return ret; 380c84979cSMichal Kubecek dev->ethtool_ops->get_channels(dev, &data->channels); 390c84979cSMichal Kubecek ethnl_ops_complete(dev); 400c84979cSMichal Kubecek 410c84979cSMichal Kubecek return 0; 420c84979cSMichal Kubecek } 430c84979cSMichal Kubecek 440c84979cSMichal Kubecek static int channels_reply_size(const struct ethnl_req_info *req_base, 450c84979cSMichal Kubecek const struct ethnl_reply_data *reply_base) 460c84979cSMichal Kubecek { 470c84979cSMichal Kubecek return nla_total_size(sizeof(u32)) + /* _CHANNELS_RX_MAX */ 480c84979cSMichal Kubecek nla_total_size(sizeof(u32)) + /* _CHANNELS_TX_MAX */ 490c84979cSMichal Kubecek nla_total_size(sizeof(u32)) + /* _CHANNELS_OTHER_MAX */ 500c84979cSMichal Kubecek nla_total_size(sizeof(u32)) + /* _CHANNELS_COMBINED_MAX */ 510c84979cSMichal Kubecek nla_total_size(sizeof(u32)) + /* _CHANNELS_RX_COUNT */ 520c84979cSMichal Kubecek nla_total_size(sizeof(u32)) + /* _CHANNELS_TX_COUNT */ 530c84979cSMichal Kubecek nla_total_size(sizeof(u32)) + /* _CHANNELS_OTHER_COUNT */ 540c84979cSMichal Kubecek nla_total_size(sizeof(u32)); /* _CHANNELS_COMBINED_COUNT */ 550c84979cSMichal Kubecek } 560c84979cSMichal Kubecek 570c84979cSMichal Kubecek static int channels_fill_reply(struct sk_buff *skb, 580c84979cSMichal Kubecek const struct ethnl_req_info *req_base, 590c84979cSMichal Kubecek const struct ethnl_reply_data *reply_base) 600c84979cSMichal Kubecek { 610c84979cSMichal Kubecek const struct channels_reply_data *data = CHANNELS_REPDATA(reply_base); 620c84979cSMichal Kubecek const struct ethtool_channels *channels = &data->channels; 630c84979cSMichal Kubecek 640c84979cSMichal Kubecek if ((channels->max_rx && 650c84979cSMichal Kubecek (nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_MAX, 660c84979cSMichal Kubecek channels->max_rx) || 670c84979cSMichal Kubecek nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_COUNT, 680c84979cSMichal Kubecek channels->rx_count))) || 690c84979cSMichal Kubecek (channels->max_tx && 700c84979cSMichal Kubecek (nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_MAX, 710c84979cSMichal Kubecek channels->max_tx) || 720c84979cSMichal Kubecek nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_COUNT, 730c84979cSMichal Kubecek channels->tx_count))) || 740c84979cSMichal Kubecek (channels->max_other && 750c84979cSMichal Kubecek (nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_MAX, 760c84979cSMichal Kubecek channels->max_other) || 770c84979cSMichal Kubecek nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_COUNT, 780c84979cSMichal Kubecek channels->other_count))) || 790c84979cSMichal Kubecek (channels->max_combined && 800c84979cSMichal Kubecek (nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_MAX, 810c84979cSMichal Kubecek channels->max_combined) || 820c84979cSMichal Kubecek nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_COUNT, 830c84979cSMichal Kubecek channels->combined_count)))) 840c84979cSMichal Kubecek return -EMSGSIZE; 850c84979cSMichal Kubecek 860c84979cSMichal Kubecek return 0; 870c84979cSMichal Kubecek } 880c84979cSMichal Kubecek 89e19c591eSMichal Kubecek /* CHANNELS_SET */ 90e19c591eSMichal Kubecek 91ff419afaSJakub Kicinski const struct nla_policy ethnl_channels_set_policy[] = { 92329d9c33SJakub Kicinski [ETHTOOL_A_CHANNELS_HEADER] = 93329d9c33SJakub Kicinski NLA_POLICY_NESTED(ethnl_header_policy), 94e19c591eSMichal Kubecek [ETHTOOL_A_CHANNELS_RX_COUNT] = { .type = NLA_U32 }, 95e19c591eSMichal Kubecek [ETHTOOL_A_CHANNELS_TX_COUNT] = { .type = NLA_U32 }, 96e19c591eSMichal Kubecek [ETHTOOL_A_CHANNELS_OTHER_COUNT] = { .type = NLA_U32 }, 97e19c591eSMichal Kubecek [ETHTOOL_A_CHANNELS_COMBINED_COUNT] = { .type = NLA_U32 }, 98e19c591eSMichal Kubecek }; 99e19c591eSMichal Kubecek 10004007961SJakub Kicinski static int 10104007961SJakub Kicinski ethnl_set_channels_validate(struct ethnl_req_info *req_info, 10204007961SJakub Kicinski struct genl_info *info) 10304007961SJakub Kicinski { 10404007961SJakub Kicinski const struct ethtool_ops *ops = req_info->dev->ethtool_ops; 10504007961SJakub Kicinski 10604007961SJakub Kicinski return ops->get_channels && ops->set_channels ? 1 : -EOPNOTSUPP; 10704007961SJakub Kicinski } 10804007961SJakub Kicinski 10904007961SJakub Kicinski static int 11004007961SJakub Kicinski ethnl_set_channels(struct ethnl_req_info *req_info, struct genl_info *info) 111e19c591eSMichal Kubecek { 112e19c591eSMichal Kubecek unsigned int from_channel, old_total, i; 1137be92514SJakub Kicinski bool mod = false, mod_combined = false; 11404007961SJakub Kicinski struct net_device *dev = req_info->dev; 115e19c591eSMichal Kubecek struct ethtool_channels channels = {}; 1165028588bSJakub Kicinski struct nlattr **tb = info->attrs; 117*916b7d31SMina Almasry u32 err_attr; 118e19c591eSMichal Kubecek int ret; 119e19c591eSMichal Kubecek 12004007961SJakub Kicinski dev->ethtool_ops->get_channels(dev, &channels); 121e19c591eSMichal Kubecek old_total = channels.combined_count + 122e19c591eSMichal Kubecek max(channels.rx_count, channels.tx_count); 123e19c591eSMichal Kubecek 124e19c591eSMichal Kubecek ethnl_update_u32(&channels.rx_count, tb[ETHTOOL_A_CHANNELS_RX_COUNT], 125e19c591eSMichal Kubecek &mod); 126e19c591eSMichal Kubecek ethnl_update_u32(&channels.tx_count, tb[ETHTOOL_A_CHANNELS_TX_COUNT], 127e19c591eSMichal Kubecek &mod); 128e19c591eSMichal Kubecek ethnl_update_u32(&channels.other_count, 129e19c591eSMichal Kubecek tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], &mod); 130e19c591eSMichal Kubecek ethnl_update_u32(&channels.combined_count, 1317be92514SJakub Kicinski tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod_combined); 1327be92514SJakub Kicinski mod |= mod_combined; 133e19c591eSMichal Kubecek if (!mod) 13404007961SJakub Kicinski return 0; 135e19c591eSMichal Kubecek 136e19c591eSMichal Kubecek /* ensure new channel counts are within limits */ 137e19c591eSMichal Kubecek if (channels.rx_count > channels.max_rx) 138a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_RX_COUNT; 139e19c591eSMichal Kubecek else if (channels.tx_count > channels.max_tx) 140a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_TX_COUNT; 141e19c591eSMichal Kubecek else if (channels.other_count > channels.max_other) 142a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_OTHER_COUNT; 143e19c591eSMichal Kubecek else if (channels.combined_count > channels.max_combined) 144a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT; 145e19c591eSMichal Kubecek else 146a4fc088aSYinjun Zhang err_attr = 0; 147e19c591eSMichal Kubecek if (err_attr) { 148a4fc088aSYinjun Zhang NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr], 1495ec82c49SColin Ian King "requested channel count exceeds maximum"); 15004007961SJakub Kicinski return -EINVAL; 151e19c591eSMichal Kubecek } 152e19c591eSMichal Kubecek 1537be92514SJakub Kicinski /* ensure there is at least one RX and one TX channel */ 1547be92514SJakub Kicinski if (!channels.combined_count && !channels.rx_count) 155a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_RX_COUNT; 1567be92514SJakub Kicinski else if (!channels.combined_count && !channels.tx_count) 157a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_TX_COUNT; 1587be92514SJakub Kicinski else 159a4fc088aSYinjun Zhang err_attr = 0; 1607be92514SJakub Kicinski if (err_attr) { 1617be92514SJakub Kicinski if (mod_combined) 162a4fc088aSYinjun Zhang err_attr = ETHTOOL_A_CHANNELS_COMBINED_COUNT; 163a4fc088aSYinjun Zhang NL_SET_ERR_MSG_ATTR(info->extack, tb[err_attr], 164a4fc088aSYinjun Zhang "requested channel counts would result in no RX or TX channel being configured"); 16504007961SJakub Kicinski return -EINVAL; 1667be92514SJakub Kicinski } 1677be92514SJakub Kicinski 168*916b7d31SMina Almasry ret = ethtool_check_max_channel(dev, channels, info); 169*916b7d31SMina Almasry if (ret) 170*916b7d31SMina Almasry return ret; 171e19c591eSMichal Kubecek 172e19c591eSMichal Kubecek /* Disabling channels, query zero-copy AF_XDP sockets */ 173e19c591eSMichal Kubecek from_channel = channels.combined_count + 174e19c591eSMichal Kubecek min(channels.rx_count, channels.tx_count); 175e19c591eSMichal Kubecek for (i = from_channel; i < old_total; i++) 176c4655761SMagnus Karlsson if (xsk_get_pool_from_qid(dev, i)) { 177e19c591eSMichal Kubecek GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets"); 17804007961SJakub Kicinski return -EINVAL; 179e19c591eSMichal Kubecek } 180e19c591eSMichal Kubecek 181e19c591eSMichal Kubecek ret = dev->ethtool_ops->set_channels(dev, &channels); 18204007961SJakub Kicinski return ret < 0 ? ret : 1; 183e19c591eSMichal Kubecek } 18404007961SJakub Kicinski 18504007961SJakub Kicinski const struct ethnl_request_ops ethnl_channels_request_ops = { 18604007961SJakub Kicinski .request_cmd = ETHTOOL_MSG_CHANNELS_GET, 18704007961SJakub Kicinski .reply_cmd = ETHTOOL_MSG_CHANNELS_GET_REPLY, 18804007961SJakub Kicinski .hdr_attr = ETHTOOL_A_CHANNELS_HEADER, 18904007961SJakub Kicinski .req_info_size = sizeof(struct channels_req_info), 19004007961SJakub Kicinski .reply_data_size = sizeof(struct channels_reply_data), 19104007961SJakub Kicinski 19204007961SJakub Kicinski .prepare_data = channels_prepare_data, 19304007961SJakub Kicinski .reply_size = channels_reply_size, 19404007961SJakub Kicinski .fill_reply = channels_fill_reply, 19504007961SJakub Kicinski 19604007961SJakub Kicinski .set_validate = ethnl_set_channels_validate, 19704007961SJakub Kicinski .set = ethnl_set_channels, 19804007961SJakub Kicinski .set_ntf_cmd = ETHTOOL_MSG_CHANNELS_NTF, 19904007961SJakub Kicinski }; 200