|
1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | 2 |
|
| 3 | +#include <net/xdp_sock.h> |
| 4 | + |
3 | 5 | #include "netlink.h" |
4 | 6 | #include "common.h" |
5 | 7 |
|
@@ -106,3 +108,117 @@ const struct ethnl_request_ops ethnl_channels_request_ops = { |
106 | 108 | .reply_size = channels_reply_size, |
107 | 109 | .fill_reply = channels_fill_reply, |
108 | 110 | }; |
| 111 | + |
| 112 | +/* CHANNELS_SET */ |
| 113 | + |
| 114 | +static const struct nla_policy |
| 115 | +channels_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 | + |
| 128 | +int 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 | + struct ethtool_channels channels = {}; |
| 133 | + struct ethnl_req_info req_info = {}; |
| 134 | + const struct nlattr *err_attr; |
| 135 | + const struct ethtool_ops *ops; |
| 136 | + struct net_device *dev; |
| 137 | + u32 max_rx_in_use = 0; |
| 138 | + bool mod = false; |
| 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); |
| 174 | + ret = 0; |
| 175 | + if (!mod) |
| 176 | + goto out_ops; |
| 177 | + |
| 178 | + /* ensure new channel counts are within limits */ |
| 179 | + if (channels.rx_count > channels.max_rx) |
| 180 | + err_attr = tb[ETHTOOL_A_CHANNELS_RX_COUNT]; |
| 181 | + else if (channels.tx_count > channels.max_tx) |
| 182 | + err_attr = tb[ETHTOOL_A_CHANNELS_TX_COUNT]; |
| 183 | + else if (channels.other_count > channels.max_other) |
| 184 | + err_attr = tb[ETHTOOL_A_CHANNELS_OTHER_COUNT]; |
| 185 | + else if (channels.combined_count > channels.max_combined) |
| 186 | + err_attr = tb[ETHTOOL_A_CHANNELS_COMBINED_COUNT]; |
| 187 | + else |
| 188 | + err_attr = NULL; |
| 189 | + if (err_attr) { |
| 190 | + ret = -EINVAL; |
| 191 | + NL_SET_ERR_MSG_ATTR(info->extack, err_attr, |
| 192 | + "requested channel count exceeeds maximum"); |
| 193 | + goto out_ops; |
| 194 | + } |
| 195 | + |
| 196 | + /* ensure the new Rx count fits within the configured Rx flow |
| 197 | + * indirection table settings |
| 198 | + */ |
| 199 | + if (netif_is_rxfh_configured(dev) && |
| 200 | + !ethtool_get_max_rxfh_channel(dev, &max_rx_in_use) && |
| 201 | + (channels.combined_count + channels.rx_count) <= max_rx_in_use) { |
| 202 | + GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing indirection table settings"); |
| 203 | + return -EINVAL; |
| 204 | + } |
| 205 | + |
| 206 | + /* Disabling channels, query zero-copy AF_XDP sockets */ |
| 207 | + from_channel = channels.combined_count + |
| 208 | + min(channels.rx_count, channels.tx_count); |
| 209 | + for (i = from_channel; i < old_total; i++) |
| 210 | + if (xdp_get_umem_from_qid(dev, i)) { |
| 211 | + GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets"); |
| 212 | + return -EINVAL; |
| 213 | + } |
| 214 | + |
| 215 | + ret = dev->ethtool_ops->set_channels(dev, &channels); |
| 216 | + |
| 217 | +out_ops: |
| 218 | + ethnl_ops_complete(dev); |
| 219 | +out_rtnl: |
| 220 | + rtnl_unlock(); |
| 221 | +out_dev: |
| 222 | + dev_put(dev); |
| 223 | + return ret; |
| 224 | +} |
0 commit comments