112
112
'virtio_net' : ['xdp' ],
113
113
}
114
114
115
+ # drivers that does not support changing channels
116
+ not_ethtool_channel_change_drv : list [str ] = ['vmxnet3' , 'virtio_net' , 'tun' , 'tap' ]
117
+
115
118
116
119
def _load_module (module_name : str ):
117
120
"""
@@ -171,6 +174,25 @@ def _normalize_buffers(config: dict):
171
174
config ['settings' ]['buffers' ]['buffers_per_numa' ] = str (buffers )
172
175
173
176
177
+ def _get_max_xdp_rx_queues (config : dict ):
178
+ """
179
+ Count max number of RX queues for XDP driver
180
+ - Only half of the available queues are used (to avoid NIC issues).
181
+ - If the interface driver is in `not_ethtool_channel_change_drv`, the full
182
+ number of queues is returned.
183
+ - If neither `rx` nor `combined` is set, return 1.
184
+ """
185
+ for key in ('rx' , 'combined' ):
186
+ value = config ['channels' ].get (key )
187
+ if value is not None :
188
+ if config ['original_driver' ] not in not_ethtool_channel_change_drv :
189
+ return max (1 , int (value ) // 2 )
190
+ else :
191
+ return int (value )
192
+
193
+ return 1
194
+
195
+
174
196
def get_config (config = None ):
175
197
# use persistent config to store interfaces data between executions
176
198
# this is required because some interfaces after they are connected
@@ -284,6 +306,20 @@ def get_config(config=None):
284
306
_normalize_buffers (effective_config )
285
307
config ['effective' ] = effective_config
286
308
309
+ # Save important info about all interfaces that cannot be retrieved later
310
+ # Add new interfaces (only if they are first time seen in a config)
311
+ for iface , iface_config in config .get ('settings' , {}).get ('interface' , {}).items ():
312
+ if iface not in effective_config .get ('settings' , {}).get ('interface' , {}):
313
+ eth_ifaces_persist [iface ] = {
314
+ 'original_driver' : EthtoolGDrvinfo (iface ).driver ,
315
+ }
316
+ eth_ifaces_persist [iface ]['bus_id' ] = control_host .get_bus_name (iface )
317
+ eth_ifaces_persist [iface ]['dev_id' ] = control_host .get_dev_id (iface )
318
+ eth_ifaces_persist [iface ]['channels' ] = control_host .get_eth_channels (iface )
319
+
320
+ # Return to config dictionary
321
+ config ['persist_config' ] = eth_ifaces_persist
322
+
287
323
if 'settings' in config :
288
324
if 'interface' in config ['settings' ]:
289
325
for iface , iface_config in config ['settings' ]['interface' ].items ():
@@ -307,11 +343,11 @@ def get_config(config=None):
307
343
iface_filter_eth (conf , iface )
308
344
set_dependents ('ethernet' , conf , iface )
309
345
# Interfaces with changed driver should be removed/readded
310
- if old_driver and old_driver [ 0 ] == 'dpdk' :
346
+ if old_driver :
311
347
removed_ifaces .append (
312
348
{
313
349
'iface_name' : iface ,
314
- 'driver' : 'dpdk' ,
350
+ 'driver' : old_driver [ 0 ] ,
315
351
}
316
352
)
317
353
@@ -344,7 +380,8 @@ def get_config(config=None):
344
380
'txq_size' : int (iface_config ['xdp_options' ]['tx_queue_size' ]),
345
381
}
346
382
if iface_config ['xdp_options' ]['num_rx_queues' ] == 'all' :
347
- xdp_api_params ['rxq_num' ] = 0
383
+ # 65535 is used as special value to request all available queues
384
+ xdp_api_params ['rxq_num' ] = 65535
348
385
else :
349
386
xdp_api_params ['rxq_num' ] = int (
350
387
iface_config ['xdp_options' ]['num_rx_queues' ]
@@ -374,26 +411,11 @@ def get_config(config=None):
374
411
iface_filter_eth (conf , iface )
375
412
set_dependents (dependency , conf , iface )
376
413
377
- # Save important info about all interfaces that cannot be retrieved later
378
- # Add new interfaces (only if they are first time seen in a config)
379
- for iface , iface_config in config .get ('settings' , {}).get ('interface' , {}).items ():
380
- if iface not in effective_config .get ('settings' , {}).get ('interface' , {}):
381
- eth_ifaces_persist [iface ] = {
382
- 'original_driver' : config ['settings' ]['interface' ][iface ][
383
- 'kernel_module'
384
- ],
385
- }
386
- eth_ifaces_persist [iface ]['bus_id' ] = control_host .get_bus_name (iface )
387
- eth_ifaces_persist [iface ]['dev_id' ] = control_host .get_dev_id (iface )
388
-
389
414
# PPPoE dependency
390
415
if pppoe_map_ifaces :
391
416
config ['pppoe_ifaces' ] = pppoe_map_ifaces
392
417
set_dependents ('pppoe_server' , conf )
393
418
394
- # Return to config dictionary
395
- config ['persist_config' ] = eth_ifaces_persist
396
-
397
419
return config
398
420
399
421
@@ -485,6 +507,14 @@ def verify(config):
485
507
)
486
508
if iface_config ['driver' ] == 'xdp' and 'xdp_options' in iface_config :
487
509
if iface_config ['xdp_options' ]['num_rx_queues' ] != 'all' :
510
+ rx_queues = iface_config ['xdp_api_params' ]['rxq_num' ]
511
+ max_rx_queues = _get_max_xdp_rx_queues (config ['persist_config' ][iface ])
512
+ if rx_queues > max_rx_queues :
513
+ raise ConfigError (
514
+ f'Maximum supported number of RX queues for interface { iface } is { max_rx_queues } . '
515
+ f'Please set "xdp-options num-rx-queues" to { max_rx_queues } or fewer'
516
+ )
517
+
488
518
Warning (f'Not all RX queues will be connected to VPP for { iface } !' )
489
519
490
520
if iface_config ['driver' ] == 'xdp' and 'dpdk_options' in iface_config :
@@ -593,8 +623,10 @@ def initialize_interface(iface, driver, iface_config) -> None:
593
623
iface_new_name : str = control_host .get_eth_name (iface_config ['dev_id' ])
594
624
control_host .rename_iface (iface_new_name , iface )
595
625
596
- # XDP - rename an interface, disable promisc and XDP
626
+ # XDP - rename an interface, set original channels, disable promisc and XDP
597
627
if driver == 'xdp' :
628
+ if iface_config ['original_driver' ] not in not_ethtool_channel_change_drv :
629
+ control_host .set_eth_channels (f'defunct_{ iface } ' , iface_config ['channels' ])
598
630
control_host .set_promisc (f'defunct_{ iface } ' , 'off' )
599
631
control_host .rename_iface (f'defunct_{ iface } ' , iface )
600
632
control_host .xdp_remove (iface )
@@ -691,6 +723,26 @@ def apply(config):
691
723
# add XDP interfaces
692
724
if iface_config ['driver' ] == 'xdp' :
693
725
control_host .rename_iface (iface , f'defunct_{ iface } ' )
726
+
727
+ # Some NICs fail to load XDP if all RX queues are configured. To avoid this,
728
+ # we limit the number of queues to half of the maximum supported by the driver.
729
+ # If the NIC driver does not support ethtool channel changes, no adjustment is made.
730
+ if (
731
+ config ['persist_config' ][iface ]['original_driver' ]
732
+ not in not_ethtool_channel_change_drv
733
+ ):
734
+ max_rx_queues = _get_max_xdp_rx_queues (
735
+ config ['persist_config' ][iface ]
736
+ )
737
+ channels_orig = config ['persist_config' ][iface ]['channels' ]
738
+ channels = {}
739
+ if channels_orig .get ('rx' ):
740
+ channels = {'rx' : max_rx_queues , 'tx' : max_rx_queues }
741
+ if channels_orig .get ('combined' ):
742
+ channels ['combined' ] = max_rx_queues
743
+ if channels :
744
+ control_host .set_eth_channels (f'defunct_{ iface } ' , channels )
745
+
694
746
vpp_control .xdp_iface_create (
695
747
host_if = f'defunct_{ iface } ' ,
696
748
name = iface ,
0 commit comments