112112 'virtio_net' : ['xdp' ],
113113}
114114
115+ # drivers that require changing channels (half the maximum number of RX/TX queues)
116+ ethtool_channels_change_drv : list [str ] = ['ena' , 'gve' ]
117+
115118
116119def _load_module (module_name : str ):
117120 """
@@ -171,6 +174,25 @@ def _normalize_buffers(config: dict):
171174 config ['settings' ]['buffers' ]['buffers_per_numa' ] = str (buffers )
172175
173176
177+ def _get_max_xdp_rx_queues (config : dict ):
178+ """
179+ Count max number of RX queues for XDP driver
180+ - If the interface driver is in `ethtool_channels_change_drv`
181+ only half of the available queues are used (to avoid NIC issues)
182+ - For other interface drivers the full 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 :
188+ if config ['original_driver' ] in ethtool_channels_change_drv :
189+ return max (1 , int (value ) // 2 )
190+ else :
191+ return int (value )
192+
193+ return 1
194+
195+
174196def get_config (config = None ):
175197 # use persistent config to store interfaces data between executions
176198 # this is required because some interfaces after they are connected
@@ -284,6 +306,20 @@ def get_config(config=None):
284306 _normalize_buffers (effective_config )
285307 config ['effective' ] = effective_config
286308
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+
287323 if 'settings' in config :
288324 if 'interface' in config ['settings' ]:
289325 for iface , iface_config in config ['settings' ]['interface' ].items ():
@@ -307,11 +343,11 @@ def get_config(config=None):
307343 iface_filter_eth (conf , iface )
308344 set_dependents ('ethernet' , conf , iface )
309345 # Interfaces with changed driver should be removed/readded
310- if old_driver and old_driver [ 0 ] == 'dpdk' :
346+ if old_driver :
311347 removed_ifaces .append (
312348 {
313349 'iface_name' : iface ,
314- 'driver' : 'dpdk' ,
350+ 'driver' : old_driver [ 0 ] ,
315351 }
316352 )
317353
@@ -344,7 +380,8 @@ def get_config(config=None):
344380 'txq_size' : int (iface_config ['xdp_options' ]['tx_queue_size' ]),
345381 }
346382 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
348385 else :
349386 xdp_api_params ['rxq_num' ] = int (
350387 iface_config ['xdp_options' ]['num_rx_queues' ]
@@ -374,26 +411,11 @@ def get_config(config=None):
374411 iface_filter_eth (conf , iface )
375412 set_dependents (dependency , conf , iface )
376413
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-
389414 # PPPoE dependency
390415 if pppoe_map_ifaces :
391416 config ['pppoe_ifaces' ] = pppoe_map_ifaces
392417 set_dependents ('pppoe_server' , conf )
393418
394- # Return to config dictionary
395- config ['persist_config' ] = eth_ifaces_persist
396-
397419 return config
398420
399421
@@ -485,6 +507,14 @@ def verify(config):
485507 )
486508 if iface_config ['driver' ] == 'xdp' and 'xdp_options' in iface_config :
487509 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+
488518 Warning (f'Not all RX queues will be connected to VPP for { iface } !' )
489519
490520 if iface_config ['driver' ] == 'xdp' and 'dpdk_options' in iface_config :
@@ -593,8 +623,10 @@ def initialize_interface(iface, driver, iface_config) -> None:
593623 iface_new_name : str = control_host .get_eth_name (iface_config ['dev_id' ])
594624 control_host .rename_iface (iface_new_name , iface )
595625
596- # XDP - rename an interface, disable promisc and XDP
626+ # XDP - rename an interface, set original channels, disable promisc and XDP
597627 if driver == 'xdp' :
628+ if iface_config ['original_driver' ] in ethtool_channels_change_drv :
629+ control_host .set_eth_channels (f'defunct_{ iface } ' , iface_config ['channels' ])
598630 control_host .set_promisc (f'defunct_{ iface } ' , 'off' )
599631 control_host .rename_iface (f'defunct_{ iface } ' , iface )
600632 control_host .xdp_remove (iface )
@@ -691,6 +723,25 @@ def apply(config):
691723 # add XDP interfaces
692724 if iface_config ['driver' ] == 'xdp' :
693725 control_host .rename_iface (iface , f'defunct_{ iface } ' )
726+
727+ # Some cloud 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 (
730+ config ['persist_config' ][iface ]['original_driver' ]
731+ in ethtool_channels_change_drv
732+ ):
733+ max_rx_queues = _get_max_xdp_rx_queues (
734+ config ['persist_config' ][iface ]
735+ )
736+ channels_orig = config ['persist_config' ][iface ]['channels' ]
737+ channels = {}
738+ if channels_orig .get ('rx' ):
739+ channels = {'rx' : max_rx_queues , 'tx' : max_rx_queues }
740+ if channels_orig .get ('combined' ):
741+ channels ['combined' ] = max_rx_queues
742+ if channels :
743+ control_host .set_eth_channels (f'defunct_{ iface } ' , channels )
744+
694745 vpp_control .xdp_iface_create (
695746 host_if = f'defunct_{ iface } ' ,
696747 name = iface ,
0 commit comments