Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'ethtool-dynamic-rss-context-indirection-table-resizing'

Björn Töpel says:

====================
ethtool: Dynamic RSS context indirection table resizing

Some NICs (e.g. bnxt) change their RSS indirection table size based on
the queue count, because the hardware table is a shared resource. The
ethtool core locks ctx->indir_size at context creation, so drivers
have to reject channel changes when RSS contexts exist.

This series adds resize helpers and wires them up in bnxt. It also
adds tracking of the user provided indirection table size to the
ethtool core.

Patch 1 tracks the user-provided indirection table size (user_size)
in ctx->indir_user_size for non-default RSS contexts and in
dev->ethtool->rss_indir_user_size for context 0. It is set when the
indirection table is configured via netlink or ioctl, and cleared to
zero on reset-to-default.

IFF_RXFH_CONFIGURED is removed, and replaced with rss_indir_user_size.
The flag is redundant now that user_size captures the same
information.

Patch 2 adds core resize helpers:
ethtool_rxfh_indir_can_resize() - read-only validation for context 0
ethtool_rxfh_indir_resize() - fold/unfold context 0 table in place
ethtool_rxfh_ctxs_can_resize() - validate all non-default contexts
ethtool_rxfh_ctxs_resize() - resize all non-default contexts,
with locking and RSS_NTF notifications

Patch 3 uses the resize helpers in bnxt_set_channels().

Patch 4 adds HW tests in rss_drv.py (devices without dynamic table
sizing are skipped):
resize_periodic - fold/unfold with a non-default [3,2,1,0]
sub-table (user_size=4), verifying exact content preservation
(main + ctx)
resize_below_user_size_reject - periodic sub-table with user_size
between big and small device table sizes; verifies that shrinking
below user_size is rejected even when the table is periodic
(main + ctx)
resize_nonperiodic_reject - non-periodic table blocks channel
reduction, with an extra periodic context to exercise
multi-context validation (main + ctx)
resize_nonperiodic_no_corruption - failed resize leaves table
contents and channel count unchanged (main + ctx)

Running the tests:

# On real hardware
sudo NETIF=eth0 ./rss_drv.py
====================

Link: https://patch.msgid.link/20260320085826.1957255-1-bjorn@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+488 -40
+1 -2
drivers/net/ethernet/broadcom/bnxt/bnxt.c
··· 8118 8118 (bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings) != 8119 8119 bnxt_get_nr_rss_ctxs(bp, rx_rings) || 8120 8120 bnxt_get_max_rss_ring(bp) >= rx_rings)) { 8121 - netdev_warn(bp->dev, "RSS table entries reverting to default\n"); 8122 - bp->dev->priv_flags &= ~IFF_RXFH_CONFIGURED; 8121 + ethtool_rxfh_indir_lost(bp->dev); 8123 8122 } 8124 8123 } 8125 8124 bp->rx_nr_rings = rx_rings;
+28 -7
drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
··· 942 942 { 943 943 struct bnxt *bp = netdev_priv(dev); 944 944 int req_tx_rings, req_rx_rings, tcs; 945 + u32 new_tbl_size = 0, old_tbl_size; 945 946 bool sh = false; 946 947 int tx_xdp = 0; 947 948 int rc = 0; ··· 978 977 tx_xdp = req_rx_rings; 979 978 } 980 979 981 - if (bnxt_get_nr_rss_ctxs(bp, req_rx_rings) != 982 - bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings) && 983 - (netif_is_rxfh_configured(dev) || bp->num_rss_ctx)) { 984 - netdev_warn(dev, "RSS table size change required, RSS table entries must be default (with no additional RSS contexts present) to proceed\n"); 985 - return -EINVAL; 986 - } 987 - 988 980 rc = bnxt_check_rings(bp, req_tx_rings, req_rx_rings, sh, tcs, tx_xdp); 989 981 if (rc) { 990 982 netdev_warn(dev, "Unable to allocate the requested rings\n"); 991 983 return rc; 984 + } 985 + 986 + /* RSS table size only changes on P5 chips with older firmware; 987 + * newer firmware always uses the largest table size. 988 + */ 989 + if (bnxt_get_nr_rss_ctxs(bp, req_rx_rings) != 990 + bnxt_get_nr_rss_ctxs(bp, bp->rx_nr_rings)) { 991 + new_tbl_size = bnxt_get_nr_rss_ctxs(bp, req_rx_rings) * 992 + BNXT_RSS_TABLE_ENTRIES_P5; 993 + old_tbl_size = bnxt_get_rxfh_indir_size(dev); 994 + 995 + if (!ethtool_rxfh_indir_can_resize(dev, bp->rss_indir_tbl, 996 + old_tbl_size, 997 + new_tbl_size)) { 998 + netdev_warn(dev, "RSS table resize not possible\n"); 999 + return -EINVAL; 1000 + } 1001 + 1002 + rc = ethtool_rxfh_ctxs_can_resize(dev, new_tbl_size); 1003 + if (rc) 1004 + return rc; 992 1005 } 993 1006 994 1007 if (netif_running(dev)) { ··· 1012 997 */ 1013 998 } 1014 999 bnxt_close_nic(bp, true, false); 1000 + } 1001 + 1002 + if (new_tbl_size) { 1003 + ethtool_rxfh_indir_resize(dev, bp->rss_indir_tbl, 1004 + old_tbl_size, new_tbl_size); 1005 + ethtool_rxfh_ctxs_resize(dev, new_tbl_size); 1015 1006 } 1016 1007 1017 1008 if (sh) {
+12 -9
drivers/net/ethernet/mellanox/mlx5/core/en_main.c
··· 6480 6480 6481 6481 /* max number of channels may have changed */ 6482 6482 max_nch = mlx5e_calc_max_nch(priv->mdev, priv->netdev, profile); 6483 + 6484 + /* Locking is required by ethtool_rxfh_indir_lost() (sends 6485 + * ETHTOOL_MSG_RSS_NTF) and by netif_set_real_num_*_queues in case 6486 + * the netdev has been registered by this point (if this function 6487 + * was called in the reload or resume flow). 6488 + */ 6489 + if (need_lock) { 6490 + rtnl_lock(); 6491 + netdev_lock(priv->netdev); 6492 + } 6493 + 6483 6494 if (priv->channels.params.num_channels > max_nch) { 6484 6495 mlx5_core_warn(priv->mdev, "MLX5E: Reducing number of channels to %d\n", max_nch); 6485 6496 /* Reducing the number of channels - RXFH has to be reset, and 6486 6497 * mlx5e_num_channels_changed below will build the RQT. 6487 6498 */ 6488 - priv->netdev->priv_flags &= ~IFF_RXFH_CONFIGURED; 6499 + ethtool_rxfh_indir_lost(priv->netdev); 6489 6500 priv->channels.params.num_channels = max_nch; 6490 6501 if (priv->channels.params.mqprio.mode == TC_MQPRIO_MODE_CHANNEL) { 6491 6502 mlx5_core_warn(priv->mdev, "MLX5E: Disabling MQPRIO channel mode\n"); ··· 6513 6502 /* 1. Set the real number of queues in the kernel the first time. 6514 6503 * 2. Set our default XPS cpumask. 6515 6504 * 3. Build the RQT. 6516 - * 6517 - * Locking is required by netif_set_real_num_*_queues in case the 6518 - * netdev has been registered by this point (if this function was called 6519 - * in the reload or resume flow). 6520 6505 */ 6521 - if (need_lock) { 6522 - rtnl_lock(); 6523 - netdev_lock(priv->netdev); 6524 - } 6525 6506 err = mlx5e_num_channels_changed(priv); 6526 6507 if (need_lock) { 6527 6508 netdev_unlock(priv->netdev);
+13
include/linux/ethtool.h
··· 176 176 * struct ethtool_rxfh_context - a custom RSS context configuration 177 177 * @indir_size: Number of u32 entries in indirection table 178 178 * @key_size: Size of hash key, in bytes 179 + * @indir_user_size: number of user provided entries for the 180 + * indirection table 179 181 * @priv_size: Size of driver private data, in bytes 180 182 * @hfunc: RSS hash function identifier. One of the %ETH_RSS_HASH_* 181 183 * @input_xfrm: Defines how the input data is transformed. Valid values are one ··· 188 186 struct ethtool_rxfh_context { 189 187 u32 indir_size; 190 188 u32 key_size; 189 + u32 indir_user_size; 191 190 u16 priv_size; 192 191 u8 hfunc; 193 192 u8 input_xfrm; ··· 217 214 } 218 215 219 216 void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id); 217 + void ethtool_rxfh_indir_lost(struct net_device *dev); 218 + bool ethtool_rxfh_indir_can_resize(struct net_device *dev, const u32 *tbl, 219 + u32 old_size, u32 new_size); 220 + void ethtool_rxfh_indir_resize(struct net_device *dev, u32 *tbl, 221 + u32 old_size, u32 new_size); 222 + int ethtool_rxfh_ctxs_can_resize(struct net_device *dev, u32 new_indir_size); 223 + void ethtool_rxfh_ctxs_resize(struct net_device *dev, u32 new_indir_size); 220 224 221 225 struct link_mode_info { 222 226 int speed; ··· 1347 1337 * @rss_ctx: XArray of custom RSS contexts 1348 1338 * @rss_lock: Protects entries in @rss_ctx. May be taken from 1349 1339 * within RTNL. 1340 + * @rss_indir_user_size: Number of user provided entries for the default 1341 + * (context 0) indirection table. 1350 1342 * @wol_enabled: Wake-on-LAN is enabled 1351 1343 * @module_fw_flash_in_progress: Module firmware flashing is in progress. 1352 1344 */ 1353 1345 struct ethtool_netdev_state { 1354 1346 struct xarray rss_ctx; 1355 1347 struct mutex rss_lock; 1348 + u32 rss_indir_user_size; 1356 1349 unsigned wol_enabled:1; 1357 1350 unsigned module_fw_flash_in_progress:1; 1358 1351 };
+1 -6
include/linux/netdevice.h
··· 1716 1716 * @IFF_OPENVSWITCH: device is a Open vSwitch master 1717 1717 * @IFF_L3MDEV_SLAVE: device is enslaved to an L3 master device 1718 1718 * @IFF_TEAM: device is a team device 1719 - * @IFF_RXFH_CONFIGURED: device has had Rx Flow indirection table configured 1720 1719 * @IFF_PHONY_HEADROOM: the headroom value is controlled by an external 1721 1720 * entity (i.e. the master device for bridged veth) 1722 1721 * @IFF_MACSEC: device is a MACsec device ··· 1751 1752 IFF_OPENVSWITCH = 1<<20, 1752 1753 IFF_L3MDEV_SLAVE = 1<<21, 1753 1754 IFF_TEAM = 1<<22, 1754 - IFF_RXFH_CONFIGURED = 1<<23, 1755 1755 IFF_PHONY_HEADROOM = 1<<24, 1756 1756 IFF_MACSEC = 1<<25, 1757 1757 IFF_NO_RX_HANDLER = 1<<26, ··· 5578 5580 return netif_is_bond_slave(dev) || netif_is_team_port(dev); 5579 5581 } 5580 5582 5581 - static inline bool netif_is_rxfh_configured(const struct net_device *dev) 5582 - { 5583 - return dev->priv_flags & IFF_RXFH_CONFIGURED; 5584 - } 5583 + bool netif_is_rxfh_configured(const struct net_device *dev); 5585 5584 5586 5585 static inline bool netif_is_failover(const struct net_device *dev) 5587 5586 {
+183
net/ethtool/common.c
··· 1204 1204 } 1205 1205 EXPORT_SYMBOL(ethtool_rxfh_context_lost); 1206 1206 1207 + bool netif_is_rxfh_configured(const struct net_device *dev) 1208 + { 1209 + return dev->ethtool->rss_indir_user_size; 1210 + } 1211 + EXPORT_SYMBOL(netif_is_rxfh_configured); 1212 + 1213 + /** 1214 + * ethtool_rxfh_indir_lost - Notify core that the RSS indirection table was lost 1215 + * @dev: network device 1216 + * 1217 + * Drivers should call this when the device can no longer maintain the 1218 + * user-configured indirection table, typically after a HW fault recovery 1219 + * that reduced the maximum queue count. Marks the default RSS context 1220 + * indirection table as unconfigured and sends an %ETHTOOL_MSG_RSS_NTF 1221 + * notification. 1222 + */ 1223 + void ethtool_rxfh_indir_lost(struct net_device *dev) 1224 + { 1225 + WARN_ONCE(!rtnl_is_locked() && 1226 + !lockdep_is_held_type(&dev->ethtool->rss_lock, -1), 1227 + "RSS context lock assertion failed\n"); 1228 + 1229 + netdev_err(dev, "device error, RSS indirection table lost\n"); 1230 + dev->ethtool->rss_indir_user_size = 0; 1231 + ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, 0); 1232 + } 1233 + EXPORT_SYMBOL(ethtool_rxfh_indir_lost); 1234 + 1235 + static bool ethtool_rxfh_is_periodic(const u32 *tbl, u32 old_size, u32 new_size) 1236 + { 1237 + u32 i; 1238 + 1239 + for (i = new_size; i < old_size; i++) 1240 + if (tbl[i] != tbl[i % new_size]) 1241 + return false; 1242 + return true; 1243 + } 1244 + 1245 + static bool ethtool_rxfh_can_resize(const u32 *tbl, u32 old_size, u32 new_size, 1246 + u32 user_size) 1247 + { 1248 + if (new_size == old_size) 1249 + return true; 1250 + 1251 + if (!user_size) 1252 + return true; 1253 + 1254 + if (new_size < old_size) { 1255 + if (new_size < user_size) 1256 + return false; 1257 + if (old_size % new_size) 1258 + return false; 1259 + if (!ethtool_rxfh_is_periodic(tbl, old_size, new_size)) 1260 + return false; 1261 + return true; 1262 + } 1263 + 1264 + if (new_size % old_size) 1265 + return false; 1266 + return true; 1267 + } 1268 + 1269 + /* Resize without validation; caller must have called can_resize first */ 1270 + static void ethtool_rxfh_resize(u32 *tbl, u32 old_size, u32 new_size) 1271 + { 1272 + u32 i; 1273 + 1274 + /* Grow: replicate existing pattern; shrink is a no-op on the data */ 1275 + for (i = old_size; i < new_size; i++) 1276 + tbl[i] = tbl[i % old_size]; 1277 + } 1278 + 1279 + /** 1280 + * ethtool_rxfh_indir_can_resize - Check if context 0 indir table can resize 1281 + * @dev: network device 1282 + * @tbl: indirection table 1283 + * @old_size: current number of entries in the table 1284 + * @new_size: desired number of entries 1285 + * 1286 + * Validate that @tbl can be resized from @old_size to @new_size without 1287 + * data loss. Uses the user_size floor from context 0. When user_size is 1288 + * zero the table is not user-configured and resize always succeeds. 1289 + * Read-only; does not modify the table. 1290 + * 1291 + * Return: true if resize is possible, false otherwise. 1292 + */ 1293 + bool ethtool_rxfh_indir_can_resize(struct net_device *dev, const u32 *tbl, 1294 + u32 old_size, u32 new_size) 1295 + { 1296 + return ethtool_rxfh_can_resize(tbl, old_size, new_size, 1297 + dev->ethtool->rss_indir_user_size); 1298 + } 1299 + EXPORT_SYMBOL(ethtool_rxfh_indir_can_resize); 1300 + 1301 + /** 1302 + * ethtool_rxfh_indir_resize - Fold or unfold context 0 indirection table 1303 + * @dev: network device 1304 + * @tbl: indirection table (must have room for max(old_size, new_size) entries) 1305 + * @old_size: current number of entries in the table 1306 + * @new_size: desired number of entries 1307 + * 1308 + * Resize the default RSS context indirection table in place. Caller 1309 + * must have validated with ethtool_rxfh_indir_can_resize() first. 1310 + */ 1311 + void ethtool_rxfh_indir_resize(struct net_device *dev, u32 *tbl, 1312 + u32 old_size, u32 new_size) 1313 + { 1314 + if (!dev->ethtool->rss_indir_user_size) 1315 + return; 1316 + 1317 + ethtool_rxfh_resize(tbl, old_size, new_size); 1318 + } 1319 + EXPORT_SYMBOL(ethtool_rxfh_indir_resize); 1320 + 1321 + /** 1322 + * ethtool_rxfh_ctxs_can_resize - Validate resize for all RSS contexts 1323 + * @dev: network device 1324 + * @new_indir_size: new indirection table size 1325 + * 1326 + * Validate that the indirection tables of all non-default RSS contexts 1327 + * can be resized to @new_indir_size. Read-only; does not modify any 1328 + * context. Intended to be paired with ethtool_rxfh_ctxs_resize(). 1329 + * 1330 + * Return: 0 if all contexts can be resized, negative errno on failure. 1331 + */ 1332 + int ethtool_rxfh_ctxs_can_resize(struct net_device *dev, u32 new_indir_size) 1333 + { 1334 + struct ethtool_rxfh_context *ctx; 1335 + unsigned long context; 1336 + int ret = 0; 1337 + 1338 + if (!dev->ethtool_ops->rxfh_indir_space || 1339 + new_indir_size > dev->ethtool_ops->rxfh_indir_space) 1340 + return -EINVAL; 1341 + 1342 + mutex_lock(&dev->ethtool->rss_lock); 1343 + xa_for_each(&dev->ethtool->rss_ctx, context, ctx) { 1344 + u32 *indir = ethtool_rxfh_context_indir(ctx); 1345 + 1346 + if (!ethtool_rxfh_can_resize(indir, ctx->indir_size, 1347 + new_indir_size, 1348 + ctx->indir_user_size)) { 1349 + ret = -EINVAL; 1350 + goto unlock; 1351 + } 1352 + } 1353 + unlock: 1354 + mutex_unlock(&dev->ethtool->rss_lock); 1355 + return ret; 1356 + } 1357 + EXPORT_SYMBOL(ethtool_rxfh_ctxs_can_resize); 1358 + 1359 + /** 1360 + * ethtool_rxfh_ctxs_resize - Resize all RSS context indirection tables 1361 + * @dev: network device 1362 + * @new_indir_size: new indirection table size 1363 + * 1364 + * Resize the indirection table of every non-default RSS context to 1365 + * @new_indir_size. Caller must have validated with 1366 + * ethtool_rxfh_ctxs_can_resize() first. An %ETHTOOL_MSG_RSS_NTF is 1367 + * sent for each resized context. 1368 + * 1369 + * Notifications are sent outside the RSS lock to avoid holding the 1370 + * mutex during notification delivery. 1371 + */ 1372 + void ethtool_rxfh_ctxs_resize(struct net_device *dev, u32 new_indir_size) 1373 + { 1374 + struct ethtool_rxfh_context *ctx; 1375 + unsigned long context; 1376 + 1377 + mutex_lock(&dev->ethtool->rss_lock); 1378 + xa_for_each(&dev->ethtool->rss_ctx, context, ctx) { 1379 + ethtool_rxfh_resize(ethtool_rxfh_context_indir(ctx), 1380 + ctx->indir_size, new_indir_size); 1381 + ctx->indir_size = new_indir_size; 1382 + } 1383 + mutex_unlock(&dev->ethtool->rss_lock); 1384 + 1385 + xa_for_each(&dev->ethtool->rss_ctx, context, ctx) 1386 + ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, context); 1387 + } 1388 + EXPORT_SYMBOL(ethtool_rxfh_ctxs_resize); 1389 + 1207 1390 enum ethtool_link_medium ethtool_str_to_medium(const char *str) 1208 1391 { 1209 1392 int i;
+5 -4
net/ethtool/ioctl.c
··· 1405 1405 1406 1406 /* indicate whether rxfh was set to default */ 1407 1407 if (user_size == 0) 1408 - dev->priv_flags &= ~IFF_RXFH_CONFIGURED; 1408 + dev->ethtool->rss_indir_user_size = 0; 1409 1409 else 1410 - dev->priv_flags |= IFF_RXFH_CONFIGURED; 1410 + dev->ethtool->rss_indir_user_size = rxfh_dev.indir_size; 1411 1411 1412 1412 out_unlock: 1413 1413 mutex_unlock(&dev->ethtool->rss_lock); ··· 1722 1722 if (!rxfh_dev.rss_context) { 1723 1723 /* indicate whether rxfh was set to default */ 1724 1724 if (rxfh.indir_size == 0) 1725 - dev->priv_flags &= ~IFF_RXFH_CONFIGURED; 1725 + dev->ethtool->rss_indir_user_size = 0; 1726 1726 else if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) 1727 - dev->priv_flags |= IFF_RXFH_CONFIGURED; 1727 + dev->ethtool->rss_indir_user_size = dev_indir_size; 1728 1728 } 1729 1729 /* Update rss_ctx tracking */ 1730 1730 if (rxfh_dev.rss_delete) { ··· 1737 1737 ctx->indir_configured = 1738 1738 rxfh.indir_size && 1739 1739 rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE; 1740 + ctx->indir_user_size = dev_indir_size; 1740 1741 } 1741 1742 if (rxfh_dev.key) { 1742 1743 memcpy(ethtool_rxfh_context_key(ctx), rxfh_dev.key,
+16 -8
net/ethtool/rss.c
··· 686 686 687 687 *mod |= memcmp(rxfh->indir, data->indir_table, data->indir_size); 688 688 689 - return 0; 689 + return user_size; 690 690 691 691 err_free: 692 692 kfree(rxfh->indir); ··· 833 833 struct nlattr **tb = info->attrs; 834 834 struct rss_reply_data data = {}; 835 835 const struct ethtool_ops *ops; 836 + u32 indir_user_size; 836 837 int ret; 837 838 838 839 ops = dev->ethtool_ops; ··· 846 845 rxfh.rss_context = request->rss_context; 847 846 848 847 ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_reset, &mod); 849 - if (ret) 848 + if (ret < 0) 850 849 goto exit_clean_data; 850 + indir_user_size = ret; 851 851 indir_mod = !!tb[ETHTOOL_A_RSS_INDIR]; 852 852 853 853 rxfh.hfunc = data.hfunc; ··· 891 889 if (ret) 892 890 goto exit_unlock; 893 891 894 - if (ctx) 892 + if (ctx) { 895 893 rss_set_ctx_update(ctx, tb, &data, &rxfh); 896 - else if (indir_reset) 897 - dev->priv_flags &= ~IFF_RXFH_CONFIGURED; 898 - else if (indir_mod) 899 - dev->priv_flags |= IFF_RXFH_CONFIGURED; 894 + if (indir_user_size) 895 + ctx->indir_user_size = indir_user_size; 896 + } else if (indir_reset) { 897 + dev->ethtool->rss_indir_user_size = 0; 898 + } else if (indir_mod) { 899 + dev->ethtool->rss_indir_user_size = indir_user_size; 900 + } 900 901 901 902 exit_unlock: 902 903 mutex_unlock(&dev->ethtool->rss_lock); ··· 1004 999 const struct ethtool_ops *ops; 1005 1000 struct rss_req_info req = {}; 1006 1001 struct net_device *dev; 1002 + u32 indir_user_size; 1007 1003 struct sk_buff *rsp; 1008 1004 void *hdr; 1009 1005 u32 limit; ··· 1041 1035 goto exit_ops; 1042 1036 1043 1037 ret = rss_set_prep_indir(dev, info, &data, &rxfh, &indir_dflt, &mod); 1044 - if (ret) 1038 + if (ret < 0) 1045 1039 goto exit_clean_data; 1040 + indir_user_size = ret; 1046 1041 1047 1042 ethnl_update_u8(&rxfh.hfunc, tb[ETHTOOL_A_RSS_HFUNC], &mod); 1048 1043 ··· 1087 1080 1088 1081 /* Store the config from rxfh to Xarray.. */ 1089 1082 rss_set_ctx_update(ctx, tb, &data, &rxfh); 1083 + ctx->indir_user_size = indir_user_size; 1090 1084 /* .. copy from Xarray to data. */ 1091 1085 __rss_prepare_ctx(dev, &data, ctx); 1092 1086
+229 -4
tools/testing/selftests/drivers/net/hw/rss_drv.py
··· 5 5 Driver-related behavior tests for RSS. 6 6 """ 7 7 8 - from lib.py import ksft_run, ksft_exit, ksft_ge 9 - from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx 10 - from lib.py import defer, ethtool 8 + from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_ge 9 + from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, ksft_raises 10 + from lib.py import defer, ethtool, CmdExitFailure 11 11 from lib.py import EthtoolFamily, NlError 12 12 from lib.py import NetDrvEnv 13 13 ··· 45 45 return ctx_id 46 46 47 47 48 + def _require_dynamic_indir_size(cfg, ch_max): 49 + """Skip if the device does not dynamically size its indirection table.""" 50 + ethtool(f"-X {cfg.ifname} default") 51 + ethtool(f"-L {cfg.ifname} combined 2") 52 + small = len(_get_rss(cfg)['rss-indirection-table']) 53 + ethtool(f"-L {cfg.ifname} combined {ch_max}") 54 + large = len(_get_rss(cfg)['rss-indirection-table']) 55 + 56 + if small == large: 57 + raise KsftSkipEx("Device does not dynamically size indirection table") 58 + 59 + 48 60 @ksft_variants([ 49 61 KsftNamedVariant("main", False), 50 62 KsftNamedVariant("ctx", True), ··· 88 76 _test_rss_indir_size(cfg, test_max, context=ctx_id) 89 77 90 78 79 + @ksft_variants([ 80 + KsftNamedVariant("main", False), 81 + KsftNamedVariant("ctx", True), 82 + ]) 83 + def resize_periodic(cfg, create_context): 84 + """Test that a periodic indirection table survives channel changes. 85 + 86 + Set a non-default periodic table ([3, 2, 1, 0] x N) via netlink, 87 + reduce channels to trigger a fold, then increase to trigger an 88 + unfold. Using a reversed pattern (instead of [0, 1, 2, 3]) ensures 89 + the test can distinguish a correct fold from a driver that silently 90 + resets the table to defaults. Verify the exact pattern is preserved 91 + and the size tracks the channel count. 92 + """ 93 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 94 + ch_max = channels.get('combined-max', 0) 95 + qcnt = channels['combined-count'] 96 + 97 + if ch_max < 4: 98 + raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}") 99 + 100 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 101 + 102 + _require_dynamic_indir_size(cfg, ch_max) 103 + 104 + ctx_id = _maybe_create_context(cfg, create_context) 105 + 106 + # Set a non-default periodic pattern via netlink. 107 + # Send only 4 entries (user_size=4) so the kernel replicates it 108 + # to fill the device table. This allows folding down to 4 entries. 109 + rss = _get_rss(cfg, context=ctx_id) 110 + orig_size = len(rss['rss-indirection-table']) 111 + pattern = [3, 2, 1, 0] 112 + req = {'header': {'dev-index': cfg.ifindex}, 'indir': pattern} 113 + if ctx_id: 114 + req['context'] = ctx_id 115 + else: 116 + defer(ethtool, f"-X {cfg.ifname} default") 117 + cfg.ethnl.rss_set(req) 118 + 119 + # Shrink — should fold 120 + ethtool(f"-L {cfg.ifname} combined 4") 121 + rss = _get_rss(cfg, context=ctx_id) 122 + indir = rss['rss-indirection-table'] 123 + 124 + ksft_ge(orig_size, len(indir), "Table did not shrink") 125 + ksft_eq(indir, [3, 2, 1, 0] * (len(indir) // 4), 126 + "Folded table has wrong pattern") 127 + 128 + # Grow back — should unfold 129 + ethtool(f"-L {cfg.ifname} combined {ch_max}") 130 + rss = _get_rss(cfg, context=ctx_id) 131 + indir = rss['rss-indirection-table'] 132 + 133 + ksft_eq(len(indir), orig_size, "Table size not restored") 134 + ksft_eq(indir, [3, 2, 1, 0] * (len(indir) // 4), 135 + "Unfolded table has wrong pattern") 136 + 137 + 138 + @ksft_variants([ 139 + KsftNamedVariant("main", False), 140 + KsftNamedVariant("ctx", True), 141 + ]) 142 + def resize_below_user_size_reject(cfg, create_context): 143 + """Test that shrinking below user_size is rejected. 144 + 145 + Send a table via netlink whose size (user_size) sits between 146 + the small and large device table sizes. The table is periodic, 147 + so folding would normally succeed, but the user_size floor must 148 + prevent it. 149 + """ 150 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 151 + ch_max = channels.get('combined-max', 0) 152 + qcnt = channels['combined-count'] 153 + 154 + if ch_max < 4: 155 + raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}") 156 + 157 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 158 + 159 + _require_dynamic_indir_size(cfg, ch_max) 160 + 161 + ctx_id = _maybe_create_context(cfg, create_context) 162 + 163 + # Measure the table size at max channels 164 + rss = _get_rss(cfg, context=ctx_id) 165 + big_size = len(rss['rss-indirection-table']) 166 + 167 + # Measure the table size at reduced channels 168 + ethtool(f"-L {cfg.ifname} combined 4") 169 + rss = _get_rss(cfg, context=ctx_id) 170 + small_size = len(rss['rss-indirection-table']) 171 + ethtool(f"-L {cfg.ifname} combined {ch_max}") 172 + 173 + if small_size >= big_size: 174 + raise KsftSkipEx("Table did not shrink at reduced channels") 175 + 176 + # Find a user_size 177 + user_size = None 178 + for div in [2, 4]: 179 + candidate = big_size // div 180 + if candidate > small_size and big_size % candidate == 0: 181 + user_size = candidate 182 + break 183 + if user_size is None: 184 + raise KsftSkipEx("No suitable user_size between small and big table") 185 + 186 + # Send a periodic sub-table of exactly user_size entries. 187 + # Pattern safe for 4 channels. 188 + pattern = [0, 1, 2, 3] * (user_size // 4) 189 + if len(pattern) != user_size: 190 + raise KsftSkipEx(f"user_size ({user_size}) not divisible by 4") 191 + req = {'header': {'dev-index': cfg.ifindex}, 'indir': pattern} 192 + if ctx_id: 193 + req['context'] = ctx_id 194 + else: 195 + defer(ethtool, f"-X {cfg.ifname} default") 196 + cfg.ethnl.rss_set(req) 197 + 198 + # Shrink channels — table would go to small_size < user_size. 199 + # The table is periodic so folding would work, but user_size 200 + # floor must reject it. 201 + with ksft_raises(CmdExitFailure): 202 + ethtool(f"-L {cfg.ifname} combined 4") 203 + 204 + 205 + @ksft_variants([ 206 + KsftNamedVariant("main", False), 207 + KsftNamedVariant("ctx", True), 208 + ]) 209 + def resize_nonperiodic_reject(cfg, create_context): 210 + """Test that a non-periodic table blocks channel reduction. 211 + 212 + Set equal weight across all queues so the table is not periodic 213 + at any smaller size, then verify channel reduction is rejected. 214 + An additional context with a periodic table is created to verify 215 + that validation catches the non-periodic one even when others 216 + are fine. 217 + """ 218 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 219 + ch_max = channels.get('combined-max', 0) 220 + qcnt = channels['combined-count'] 221 + 222 + if ch_max < 4: 223 + raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}") 224 + 225 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 226 + 227 + _require_dynamic_indir_size(cfg, ch_max) 228 + 229 + ctx_id = _maybe_create_context(cfg, create_context) 230 + ctx_ref = f"context {ctx_id}" if ctx_id else "" 231 + 232 + # Create an extra context with a periodic (foldable) table so that 233 + # the validation must iterate all contexts to find the bad one. 234 + extra_ctx = _maybe_create_context(cfg, True) 235 + ethtool(f"-X {cfg.ifname} context {extra_ctx} equal 2") 236 + 237 + ethtool(f"-X {cfg.ifname} {ctx_ref} equal {ch_max}") 238 + if not create_context: 239 + defer(ethtool, f"-X {cfg.ifname} default") 240 + 241 + with ksft_raises(CmdExitFailure): 242 + ethtool(f"-L {cfg.ifname} combined 2") 243 + 244 + 245 + @ksft_variants([ 246 + KsftNamedVariant("main", False), 247 + KsftNamedVariant("ctx", True), 248 + ]) 249 + def resize_nonperiodic_no_corruption(cfg, create_context): 250 + """Test that a failed resize does not corrupt table or channel count. 251 + 252 + Set a non-periodic table, attempt a channel reduction (which must 253 + fail), then verify both the indirection table contents and the 254 + channel count are unchanged. 255 + """ 256 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 257 + ch_max = channels.get('combined-max', 0) 258 + qcnt = channels['combined-count'] 259 + 260 + if ch_max < 4: 261 + raise KsftSkipEx(f"Not enough queues for the test: max={ch_max}") 262 + 263 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 264 + 265 + _require_dynamic_indir_size(cfg, ch_max) 266 + 267 + ctx_id = _maybe_create_context(cfg, create_context) 268 + ctx_ref = f"context {ctx_id}" if ctx_id else "" 269 + 270 + ethtool(f"-X {cfg.ifname} {ctx_ref} equal {ch_max}") 271 + if not create_context: 272 + defer(ethtool, f"-X {cfg.ifname} default") 273 + 274 + rss_before = _get_rss(cfg, context=ctx_id) 275 + 276 + with ksft_raises(CmdExitFailure): 277 + ethtool(f"-L {cfg.ifname} combined 2") 278 + 279 + rss_after = _get_rss(cfg, context=ctx_id) 280 + ksft_eq(rss_after['rss-indirection-table'], 281 + rss_before['rss-indirection-table'], 282 + "Indirection table corrupted after failed resize") 283 + 284 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 285 + ksft_eq(channels['combined-count'], ch_max, 286 + "Channel count changed after failed resize") 287 + 288 + 91 289 def main() -> None: 92 290 """ Ksft boiler plate main """ 93 291 with NetDrvEnv(__file__) as cfg: 94 292 cfg.ethnl = EthtoolFamily() 95 - ksft_run([indir_size_4x], args=(cfg, )) 293 + ksft_run([indir_size_4x, resize_periodic, 294 + resize_below_user_size_reject, 295 + resize_nonperiodic_reject, 296 + resize_nonperiodic_no_corruption], args=(cfg, )) 96 297 ksft_exit() 97 298 98 299