linux/net/ethtool/channels.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2
   3#include <net/xdp_sock_drv.h>
   4
   5#include "netlink.h"
   6#include "common.h"
   7
   8struct channels_req_info {
   9        struct ethnl_req_info           base;
  10};
  11
  12struct channels_reply_data {
  13        struct ethnl_reply_data         base;
  14        struct ethtool_channels         channels;
  15};
  16
  17#define CHANNELS_REPDATA(__reply_base) \
  18        container_of(__reply_base, struct channels_reply_data, base)
  19
  20static const struct nla_policy
  21channels_get_policy[ETHTOOL_A_CHANNELS_MAX + 1] = {
  22        [ETHTOOL_A_CHANNELS_UNSPEC]             = { .type = NLA_REJECT },
  23        [ETHTOOL_A_CHANNELS_HEADER]             = { .type = NLA_NESTED },
  24        [ETHTOOL_A_CHANNELS_RX_MAX]             = { .type = NLA_REJECT },
  25        [ETHTOOL_A_CHANNELS_TX_MAX]             = { .type = NLA_REJECT },
  26        [ETHTOOL_A_CHANNELS_OTHER_MAX]          = { .type = NLA_REJECT },
  27        [ETHTOOL_A_CHANNELS_COMBINED_MAX]       = { .type = NLA_REJECT },
  28        [ETHTOOL_A_CHANNELS_RX_COUNT]           = { .type = NLA_REJECT },
  29        [ETHTOOL_A_CHANNELS_TX_COUNT]           = { .type = NLA_REJECT },
  30        [ETHTOOL_A_CHANNELS_OTHER_COUNT]        = { .type = NLA_REJECT },
  31        [ETHTOOL_A_CHANNELS_COMBINED_COUNT]     = { .type = NLA_REJECT },
  32};
  33
  34static int channels_prepare_data(const struct ethnl_req_info *req_base,
  35                                 struct ethnl_reply_data *reply_base,
  36                                 struct genl_info *info)
  37{
  38        struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
  39        struct net_device *dev = reply_base->dev;
  40        int ret;
  41
  42        if (!dev->ethtool_ops->get_channels)
  43                return -EOPNOTSUPP;
  44        ret = ethnl_ops_begin(dev);
  45        if (ret < 0)
  46                return ret;
  47        dev->ethtool_ops->get_channels(dev, &data->channels);
  48        ethnl_ops_complete(dev);
  49
  50        return 0;
  51}
  52
  53static int channels_reply_size(const struct ethnl_req_info *req_base,
  54                               const struct ethnl_reply_data *reply_base)
  55{
  56        return nla_total_size(sizeof(u32)) +    /* _CHANNELS_RX_MAX */
  57               nla_total_size(sizeof(u32)) +    /* _CHANNELS_TX_MAX */
  58               nla_total_size(sizeof(u32)) +    /* _CHANNELS_OTHER_MAX */
  59               nla_total_size(sizeof(u32)) +    /* _CHANNELS_COMBINED_MAX */
  60               nla_total_size(sizeof(u32)) +    /* _CHANNELS_RX_COUNT */
  61               nla_total_size(sizeof(u32)) +    /* _CHANNELS_TX_COUNT */
  62               nla_total_size(sizeof(u32)) +    /* _CHANNELS_OTHER_COUNT */
  63               nla_total_size(sizeof(u32));     /* _CHANNELS_COMBINED_COUNT */
  64}
  65
  66static int channels_fill_reply(struct sk_buff *skb,
  67                               const struct ethnl_req_info *req_base,
  68                               const struct ethnl_reply_data *reply_base)
  69{
  70        const struct channels_reply_data *data = CHANNELS_REPDATA(reply_base);
  71        const struct ethtool_channels *channels = &data->channels;
  72
  73        if ((channels->max_rx &&
  74             (nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_MAX,
  75                          channels->max_rx) ||
  76              nla_put_u32(skb, ETHTOOL_A_CHANNELS_RX_COUNT,
  77                          channels->rx_count))) ||
  78            (channels->max_tx &&
  79             (nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_MAX,
  80                          channels->max_tx) ||
  81              nla_put_u32(skb, ETHTOOL_A_CHANNELS_TX_COUNT,
  82                          channels->tx_count))) ||
  83            (channels->max_other &&
  84             (nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_MAX,
  85                          channels->max_other) ||
  86              nla_put_u32(skb, ETHTOOL_A_CHANNELS_OTHER_COUNT,
  87                          channels->other_count))) ||
  88            (channels->max_combined &&
  89             (nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_MAX,
  90                          channels->max_combined) ||
  91              nla_put_u32(skb, ETHTOOL_A_CHANNELS_COMBINED_COUNT,
  92                          channels->combined_count))))
  93                return -EMSGSIZE;
  94
  95        return 0;
  96}
  97
  98const struct ethnl_request_ops ethnl_channels_request_ops = {
  99        .request_cmd            = ETHTOOL_MSG_CHANNELS_GET,
 100        .reply_cmd              = ETHTOOL_MSG_CHANNELS_GET_REPLY,
 101        .hdr_attr               = ETHTOOL_A_CHANNELS_HEADER,
 102        .max_attr               = ETHTOOL_A_CHANNELS_MAX,
 103        .req_info_size          = sizeof(struct channels_req_info),
 104        .reply_data_size        = sizeof(struct channels_reply_data),
 105        .request_policy         = channels_get_policy,
 106
 107        .prepare_data           = channels_prepare_data,
 108        .reply_size             = channels_reply_size,
 109        .fill_reply             = channels_fill_reply,
 110};
 111
 112/* CHANNELS_SET */
 113
 114static const struct nla_policy
 115channels_set_policy[ETHTOOL_A_CHANNELS_MAX + 1] = {
 116        [ETHTOOL_A_CHANNELS_UNSPEC]             = { .type = NLA_REJECT },
 117        [ETHTOOL_A_CHANNELS_HEADER]             = { .type = NLA_NESTED },
 118        [ETHTOOL_A_CHANNELS_RX_MAX]             = { .type = NLA_REJECT },
 119        [ETHTOOL_A_CHANNELS_TX_MAX]             = { .type = NLA_REJECT },
 120        [ETHTOOL_A_CHANNELS_OTHER_MAX]          = { .type = NLA_REJECT },
 121        [ETHTOOL_A_CHANNELS_COMBINED_MAX]       = { .type = NLA_REJECT },
 122        [ETHTOOL_A_CHANNELS_RX_COUNT]           = { .type = NLA_U32 },
 123        [ETHTOOL_A_CHANNELS_TX_COUNT]           = { .type = NLA_U32 },
 124        [ETHTOOL_A_CHANNELS_OTHER_COUNT]        = { .type = NLA_U32 },
 125        [ETHTOOL_A_CHANNELS_COMBINED_COUNT]     = { .type = NLA_U32 },
 126};
 127
 128int ethnl_set_channels(struct sk_buff *skb, struct genl_info *info)
 129{
 130        struct nlattr *tb[ETHTOOL_A_CHANNELS_MAX + 1];
 131        unsigned int from_channel, old_total, i;
 132        bool mod = false, mod_combined = false;
 133        struct ethtool_channels channels = {};
 134        struct ethnl_req_info req_info = {};
 135        const struct nlattr *err_attr;
 136        const struct ethtool_ops *ops;
 137        struct net_device *dev;
 138        u32 max_rx_in_use = 0;
 139        int ret;
 140
 141        ret = nlmsg_parse(info->nlhdr, GENL_HDRLEN, tb,
 142                          ETHTOOL_A_CHANNELS_MAX, channels_set_policy,
 143                          info->extack);
 144        if (ret < 0)
 145                return ret;
 146        ret = ethnl_parse_header_dev_get(&req_info,
 147                                         tb[ETHTOOL_A_CHANNELS_HEADER],
 148                                         genl_info_net(info), info->extack,
 149                                         true);
 150        if (ret < 0)
 151                return ret;
 152        dev = req_info.dev;
 153        ops = dev->ethtool_ops;
 154        ret = -EOPNOTSUPP;
 155        if (!ops->get_channels || !ops->set_channels)
 156                goto out_dev;
 157
 158        rtnl_lock();
 159        ret = ethnl_ops_begin(dev);
 160        if (ret < 0)
 161                goto out_rtnl;
 162        ops->get_channels(dev, &channels);
 163        old_total = channels.combined_count +
 164                    max(channels.rx_count, channels.tx_count);
 165
 166        ethnl_update_u32(&channels.rx_count, tb[ETHTOOL_A_CHANNELS_RX_COUNT],
 167                         &mod);
 168        ethnl_update_u32(&channels.tx_count, tb[ETHTOOL_A_CHANNELS_TX_COUNT],
 169                         &mod);
 170        ethnl_update_u32(&channels.other_count,
 171                         tb[ETHTOOL_A_CHANNELS_OTHER_COUNT], &mod);
 172        ethnl_update_u32(&channels.combined_count,
 173                         tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT], &mod_combined);
 174        mod |= mod_combined;
 175        ret = 0;
 176        if (!mod)
 177                goto out_ops;
 178
 179        /* ensure new channel counts are within limits */
 180        if (channels.rx_count > channels.max_rx)
 181                err_attr = tb[ETHTOOL_A_CHANNELS_RX_COUNT];
 182        else if (channels.tx_count > channels.max_tx)
 183                err_attr = tb[ETHTOOL_A_CHANNELS_TX_COUNT];
 184        else if (channels.other_count > channels.max_other)
 185                err_attr = tb[ETHTOOL_A_CHANNELS_OTHER_COUNT];
 186        else if (channels.combined_count > channels.max_combined)
 187                err_attr = tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT];
 188        else
 189                err_attr = NULL;
 190        if (err_attr) {
 191                ret = -EINVAL;
 192                NL_SET_ERR_MSG_ATTR(info->extack, err_attr,
 193                                    "requested channel count exceeds maximum");
 194                goto out_ops;
 195        }
 196
 197        /* ensure there is at least one RX and one TX channel */
 198        if (!channels.combined_count && !channels.rx_count)
 199                err_attr = tb[ETHTOOL_A_CHANNELS_RX_COUNT];
 200        else if (!channels.combined_count && !channels.tx_count)
 201                err_attr = tb[ETHTOOL_A_CHANNELS_TX_COUNT];
 202        else
 203                err_attr = NULL;
 204        if (err_attr) {
 205                if (mod_combined)
 206                        err_attr = tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT];
 207                ret = -EINVAL;
 208                NL_SET_ERR_MSG_ATTR(info->extack, err_attr, "requested channel counts would result in no RX or TX channel being configured");
 209                goto out_ops;
 210        }
 211
 212        /* ensure the new Rx count fits within the configured Rx flow
 213         * indirection table settings
 214         */
 215        if (netif_is_rxfh_configured(dev) &&
 216            !ethtool_get_max_rxfh_channel(dev, &max_rx_in_use) &&
 217            (channels.combined_count + channels.rx_count) <= max_rx_in_use) {
 218                GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings");
 219                return -EINVAL;
 220        }
 221
 222        /* Disabling channels, query zero-copy AF_XDP sockets */
 223        from_channel = channels.combined_count +
 224                       min(channels.rx_count, channels.tx_count);
 225        for (i = from_channel; i < old_total; i++)
 226                if (xdp_get_umem_from_qid(dev, i)) {
 227                        GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets");
 228                        return -EINVAL;
 229                }
 230
 231        ret = dev->ethtool_ops->set_channels(dev, &channels);
 232        if (ret < 0)
 233                goto out_ops;
 234        ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF, NULL);
 235
 236out_ops:
 237        ethnl_ops_complete(dev);
 238out_rtnl:
 239        rtnl_unlock();
 240out_dev:
 241        dev_put(dev);
 242        return ret;
 243}
 244