summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2026-02-03 15:34:10 +0000
committerGitHub <noreply@github.com>2026-02-03 15:34:10 +0000
commitad3b7eddb696727c80cbf711f4e0567b38ea922c (patch)
treee9659ed57941a6ddd4b5f1f3cb5bb0391d6e1dce
parenta12905c3010520db3c1d7e652c90853940be5e75 (diff)
parentf01ff15b7530053e18bb79cd529a84e3f15b294a (diff)
downloadvyos-1x-ad3b7eddb696727c80cbf711f4e0567b38ea922c.tar.gz
vyos-1x-ad3b7eddb696727c80cbf711f4e0567b38ea922c.zip
Merge pull request #4919 from alexandr-san4ez/T7730-current
interfaces: T7730: Add interrupt coalescing settings for Ethernet interfaces
-rw-r--r--interface-definitions/interfaces_ethernet.xml.in307
-rw-r--r--python/vyos/ethtool.py20
-rw-r--r--python/vyos/ifconfig/ethernet.py56
-rw-r--r--python/vyos/netlink/__init__.py14
-rw-r--r--python/vyos/netlink/coalesce.py363
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py81
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py22
7 files changed, 863 insertions, 0 deletions
diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in
index 819ceb2cb..e9814817f 100644
--- a/interface-definitions/interfaces_ethernet.xml.in
+++ b/interface-definitions/interfaces_ethernet.xml.in
@@ -221,6 +221,313 @@
</leafNode>
</children>
</node>
+ <node name="interrupt-coalescing">
+ <properties>
+ <help>Interrupt coalescing options for the interface</help>
+ </properties>
+ <children>
+ <leafNode name="adaptive-rx">
+ <properties>
+ <help>Enable adaptive receive interrupt coalescing</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="adaptive-tx">
+ <properties>
+ <help>Enable adaptive transmit interrupt coalescing</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-usecs">
+ <properties>
+ <help>Delay in microseconds before generating RX interrupt</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>RX interrupt coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-frames">
+ <properties>
+ <help>Number of RX frames before generating interrupt</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>RX interrupt coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-usecs-irq">
+ <properties>
+ <help>Delay in microseconds before generating RX interrupt while servicing IRQ</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>RX IRQ interrupt coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-frames-irq">
+ <properties>
+ <help>Number of RX frames before generating interrupt while servicing IRQ</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>RX IRQ interrupt coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-usecs">
+ <properties>
+ <help>Delay in microseconds before generating TX interrupt</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>TX interrupt coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-frames">
+ <properties>
+ <help>Number of TX frames before generating interrupt</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>TX interrupt coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-usecs-irq">
+ <properties>
+ <help>Delay in microseconds before generating TX interrupt while servicing IRQ</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>TX IRQ interrupt coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-frames-irq">
+ <properties>
+ <help>Number of TX frames before generating interrupt while servicing IRQ</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>TX IRQ interrupt coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="stats-block-usecs">
+ <properties>
+ <help>Time in microseconds between updating coalescing statistics</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Statistics block interval</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="pkt-rate-low">
+ <properties>
+ <help>Lower packet rate threshold for adaptive coalescing</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Low packet rate threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-usecs-low">
+ <properties>
+ <help>RX coalescing delay (usecs) for low packet rate</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>Low-rate RX coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-frame-low">
+ <properties>
+ <help>RX coalescing frames threshold for low packet rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Low-rate RX coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-usecs-low">
+ <properties>
+ <help>TX coalescing delay (usecs) for low packet rate</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>Low-rate TX coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-frame-low">
+ <properties>
+ <help>TX coalescing frames threshold for low packet rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Low-rate TX coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="pkt-rate-high">
+ <properties>
+ <help>Upper packet rate threshold for adaptive coalescing</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>High packet rate threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-usecs-high">
+ <properties>
+ <help>RX coalescing delay (usecs) for high packet rate</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>High-rate RX coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="rx-frame-high">
+ <properties>
+ <help>RX coalescing frames threshold for high packet rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>High-rate RX coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-usecs-high">
+ <properties>
+ <help>TX coalescing delay (usecs) for high packet rate</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>High-rate TX coalescing delay</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-frame-high">
+ <properties>
+ <help>TX coalescing frames threshold for high packet rate</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>High-rate TX coalescing frames threshold</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="sample-interval">
+ <properties>
+ <help>Sampling interval for adaptive coalescing (in seconds)</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>Adaptive sampling interval</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="cqe-mode-rx">
+ <properties>
+ <help>Enable RX CQE (Completion Queue Entry) mode</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="cqe-mode-tx">
+ <properties>
+ <help>Enable TX CQE (Completion Queue Entry) mode</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-aggr-max-bytes">
+ <properties>
+ <help>Maximum number of bytes to aggregate before transmitting</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>TX aggregation maximum bytes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-aggr-max-frames">
+ <properties>
+ <help>Maximum number of frames to aggregate before transmitting</help>
+ <valueHelp>
+ <format>u32:0-4294967295</format>
+ <description>TX aggregation maximum frames</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tx-aggr-time-usecs">
+ <properties>
+ <help>Maximum time in microseconds to wait before transmitting aggregated frames</help>
+ <valueHelp>
+ <format>u32:0-16384</format>
+ <description>TX aggregation timeout</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16384"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
#include <include/interface/redirect.xml.i>
#include <include/interface/vif-s.xml.i>
#include <include/interface/vif.xml.i>
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index d0495df43..ea3ed0b7c 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -14,10 +14,12 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
import re
+import contextlib
from json import loads
from vyos.utils.network import interface_exists
from vyos.utils.process import popen
+from vyos.netlink import coalesce
# These drivers do not support using ethtool to change the speed, duplex, or
# flow control settings
@@ -65,6 +67,7 @@ class Ethtool:
_driver_name = None
_flow_control = None
_channels = ''
+ _coalesce = None
def __init__(self, ifname):
# Get driver used for interface
@@ -122,6 +125,10 @@ class Ethtool:
if not bool(err):
self._channels = out.lower()
+ # Get information about NIC coalesce settings
+ with contextlib.suppress(coalesce.CoalesceError, coalesce.GeneralNetlinkError):
+ self._coalesce = coalesce.get_coalesce(ifname)
+
def check_auto_negotiation_supported(self):
""" Check if the NIC supports changing auto-negotiation """
return self._base_settings['supports-auto-negotiation']
@@ -235,3 +242,16 @@ class Ethtool:
def check_bonding(self) -> bool:
""" Check if ethernet drivers supports bonding """
return bool(self.get_driver_name() not in _drivers_without_bonding_support)
+
+ def check_coalesce(self, setting_name=None):
+ """Check if the NIC supports 'coalesce' parameter(s)"""
+
+ if not self._coalesce:
+ return False
+
+ return self._coalesce.get(setting_name) is not None if setting_name else True
+
+ def get_coalesce(self):
+ """Get all 'coalesce' parameters for the interface"""
+
+ return self._coalesce.copy() if self._coalesce else {}
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 70da837b9..fb29c018c 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -19,6 +19,7 @@ from glob import glob
from vyos.base import Warning
from vyos.ethtool import Ethtool
+from vyos.netlink import coalesce
from vyos.ifconfig import Section
from vyos.ifconfig.interface import Interface
from vyos.utils.dict import dict_search
@@ -455,6 +456,57 @@ class EthernetIf(Interface):
print(f'could not set "{rx_tx}" ring-buffer for {ifname}')
return output
+ def set_interrupt_coalescing(self, params: dict):
+ """
+ Apply ethtool coalesce settings to an interface.
+
+ This method configures interrupt coalescing parameters for the interface
+ using the netlink API.
+
+ Args:
+ params: dict containing any of the supported keys, e.g.:
+ - adaptive_rx, adaptive_tx,
+ - rx_usecs, rx_frames, rx_usecs_irq, rx_frames_irq,
+ - tx_usecs, tx_frames, tx_usecs_irq, tx_frames_irq,
+ - stats_block_usecs,
+ - pkt_rate_low, pkt_rate_high,
+ - rx_usecs_low, rx_frames_low,
+ - tx_usecs_low, tx_frames_low,
+ - rx_usecs_high, rx_frames_high,
+ - tx_usecs_high, tx_frames_high,
+ - sample_interval,
+ - cqe_mode_rx, cqe_mode_tx,
+ - tx_aggr_max_bytes, tx_aggr_max_frames, tx_aggr_time_usecs.
+
+ Example:
+ >>> from vyos.ifconfig import EthernetIf
+ >>> i = EthernetIf('eth0')
+ >>> i.set_interrupt_coalescing({'rx_usecs': 8, 'tx_usecs': 16})
+ """
+
+ ifname = self.config['ifname']
+ output = ''
+
+ # Nothing to apply
+ if not params:
+ return None
+
+ # Override boolean parameters to true if they exist and supported by NIC driver
+ for boolean_param in coalesce.get_all_params(boolean=True):
+ supported = self.ethtool.check_coalesce(boolean_param)
+ if supported:
+ params[boolean_param] = boolean_param in params
+
+ # Update interrupt coalescing parameters
+ try:
+ coalesce.set_coalesce(ifname, **params)
+ except coalesce.CoalesceError as e:
+ print(f'interrupt coalescing error: {e}')
+ except coalesce.GeneralNetlinkError as e:
+ print(f'netlink error: {e}')
+
+ return output
+
def set_channels(self, rx_tx_comb, queues):
"""
Example:
@@ -543,6 +595,10 @@ class EthernetIf(Interface):
for rx_tx, size in config['ring_buffer'].items():
self.set_ring_buffer(rx_tx, size)
+ # Set coalesce settings for the interface
+ if 'interrupt_coalescing' in config:
+ self.set_interrupt_coalescing(config['interrupt_coalescing'])
+
self.set_switchdev('switchdev' in config)
# call base class last
diff --git a/python/vyos/netlink/__init__.py b/python/vyos/netlink/__init__.py
new file mode 100644
index 000000000..db07bae58
--- /dev/null
+++ b/python/vyos/netlink/__init__.py
@@ -0,0 +1,14 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
diff --git a/python/vyos/netlink/coalesce.py b/python/vyos/netlink/coalesce.py
new file mode 100644
index 000000000..d54a4dd07
--- /dev/null
+++ b/python/vyos/netlink/coalesce.py
@@ -0,0 +1,363 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+
+from pyroute2.netlink import genlmsg
+from pyroute2.netlink import NLM_F_ACK
+from pyroute2.netlink import NLM_F_REQUEST
+from pyroute2.netlink.exceptions import NetlinkError
+from pyroute2.netlink.generic import GenericNetlinkSocket
+from pyroute2.netlink.generic.ethtool import ETHTOOL_GENL_NAME
+from pyroute2.netlink.generic.ethtool import ETHTOOL_GENL_VERSION
+from pyroute2.netlink.generic.ethtool import ethtoolheader
+
+# Netlink message types for coalesce (from Linux kernel)
+ETHTOOL_MSG_COALESCE_GET = 0x13
+ETHTOOL_MSG_COALESCE_SET = 0x14
+
+# Error codes from the kernel
+EINVAL = 0x16 # Invalid argument
+EOPNOTSUPP = 0x5F # Operation not supported
+
+# Mapping between Python attribute names and netlink attribute names, types
+# https://docs.kernel.org/networking/ethtool-netlink.html#coalesce-get
+# https://git.kernel.org/pub/scm/network/ethtool/ethtool.git/tree/netlink/coalesce.c
+COALESCE_NL_ATTRS = {
+ 'adaptive_rx': 'ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX',
+ 'adaptive_tx': 'ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX',
+ 'cqe_mode_rx': 'ETHTOOL_A_COALESCE_USE_CQE_MODE_RX',
+ 'cqe_mode_tx': 'ETHTOOL_A_COALESCE_USE_CQE_MODE_TX',
+ 'pkt_rate_high': 'ETHTOOL_A_COALESCE_PKT_RATE_HIGH',
+ 'pkt_rate_low': 'ETHTOOL_A_COALESCE_PKT_RATE_LOW',
+ 'rx_frame_high': 'ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH',
+ 'rx_frame_low': 'ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW',
+ 'rx_frames': 'ETHTOOL_A_COALESCE_RX_MAX_FRAMES',
+ 'rx_frames_irq': 'ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ',
+ 'rx_usecs': 'ETHTOOL_A_COALESCE_RX_USECS',
+ 'rx_usecs_high': 'ETHTOOL_A_COALESCE_RX_USECS_HIGH',
+ 'rx_usecs_irq': 'ETHTOOL_A_COALESCE_RX_USECS_IRQ',
+ 'rx_usecs_low': 'ETHTOOL_A_COALESCE_RX_USECS_LOW',
+ 'sample_interval': 'ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL',
+ 'stats_block_usecs': 'ETHTOOL_A_COALESCE_STATS_BLOCK_USECS',
+ 'tx_aggr_max_bytes': 'ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES',
+ 'tx_aggr_max_frames': 'ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES',
+ 'tx_aggr_time_usecs': 'ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS',
+ 'tx_frame_high': 'ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH',
+ 'tx_frame_low': 'ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW',
+ 'tx_frames': 'ETHTOOL_A_COALESCE_TX_MAX_FRAMES',
+ 'tx_frames_irq': 'ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ',
+ 'tx_usecs': 'ETHTOOL_A_COALESCE_TX_USECS',
+ 'tx_usecs_high': 'ETHTOOL_A_COALESCE_TX_USECS_HIGH',
+ 'tx_usecs_irq': 'ETHTOOL_A_COALESCE_TX_USECS_IRQ',
+ 'tx_usecs_low': 'ETHTOOL_A_COALESCE_TX_USECS_LOW',
+}
+
+
+class ethtool_coalesce_msg(genlmsg):
+ """Netlink message structure for coalesce parameters."""
+
+ ethtoolheader = ethtoolheader
+
+ # https://docs.kernel.org/networking/ethtool-netlink.html#coalesce-get
+ nla_map = (
+ ('ETHTOOL_A_COALESCE_UNSPEC', 'none'),
+ ('ETHTOOL_A_COALESCE_HEADER', 'ethtoolheader'),
+ ('ETHTOOL_A_COALESCE_RX_USECS', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_MAX_FRAMES', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_USECS_IRQ', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_MAX_FRAMES_IRQ', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_USECS', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_MAX_FRAMES', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_USECS_IRQ', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_MAX_FRAMES_IRQ', 'uint32'),
+ ('ETHTOOL_A_COALESCE_STATS_BLOCK_USECS', 'uint32'),
+ ('ETHTOOL_A_COALESCE_USE_ADAPTIVE_RX', 'uint8'),
+ ('ETHTOOL_A_COALESCE_USE_ADAPTIVE_TX', 'uint8'),
+ ('ETHTOOL_A_COALESCE_PKT_RATE_LOW', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_USECS_LOW', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_MAX_FRAMES_LOW', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_USECS_LOW', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_MAX_FRAMES_LOW', 'uint32'),
+ ('ETHTOOL_A_COALESCE_PKT_RATE_HIGH', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_USECS_HIGH', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RX_MAX_FRAMES_HIGH', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_USECS_HIGH', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_MAX_FRAMES_HIGH', 'uint32'),
+ ('ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL', 'uint32'),
+ ('ETHTOOL_A_COALESCE_USE_CQE_MODE_TX', 'uint8'),
+ ('ETHTOOL_A_COALESCE_USE_CQE_MODE_RX', 'uint8'),
+ ('ETHTOOL_A_COALESCE_TX_AGGR_MAX_BYTES', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_AGGR_MAX_FRAMES', 'uint32'),
+ ('ETHTOOL_A_COALESCE_TX_AGGR_TIME_USECS', 'uint32'),
+ )
+
+ @classmethod
+ def get_nl_attr_type(cls, nl_attr: str) -> str:
+ """Returns type of attribute using declared 'nla_map' field"""
+
+ for nla_name, nla_type in cls.nla_map:
+ if nla_name == nl_attr:
+ return nla_type
+
+ raise ValueError(f'Unknown netlink attribute name: {nl_attr}')
+
+
+GeneralNetlinkError = NetlinkError
+
+
+class CoalesceError(Exception):
+ """Base exception for coalesce operations"""
+
+ pass
+
+
+class CoalesceNotSupportedParam(CoalesceError):
+ """Raised when a coalesce parameter is not supported for modification"""
+
+ pass
+
+
+class CoalesceNotSupportedOperation(CoalesceError):
+ """Raised when a coalesce operation is not supported by NIC driver"""
+
+ pass
+
+
+class CoalesceInvalidValue(CoalesceError):
+ """Raised when a coalesce parameter value is invalid"""
+
+ pass
+
+
+class CoalesceNetlink(GenericNetlinkSocket):
+ """
+ Interface coalesce management using `pyroute2` with netlink support.
+
+ This class provides functions to read and set interface coalesce parameters
+ using the ethtool netlink interface, which properly handles "unsupported" values
+ (shown as `n/a` in `ethtool --show-coalesce` output).
+
+ IMPORTANT: The kernel's ethtool netlink interface returns coalesce parameters
+ that have non-zero values, but this does NOT mean they are modifiable. The
+ driver may impose additional constraints:
+
+ 1. Some parameters are read-only (e.g., `rx_usecs` may be reported but not settable)
+ 2. Some parameters only accept specific values (e.g., `rx_frames` may only accept 1)
+ 3. The `supported_coalesce_params` bitmask in the driver determines what's truly
+ supported, but this is not directly queryable via netlink.
+
+ When setting parameters fails with EOPNOTSUPP (0x5F) or EINVAL (0x16), it typically
+ means the parameter is not modifiable or the value is not accepted by the driver.
+ """
+
+ def __init__(self, ifname: str):
+ super().__init__()
+ self._bound = False
+ self._ifname = ifname
+
+ def _ensure_bound(self):
+ """Ensure the socket is bound to the ethtool generic netlink family"""
+
+ if not self._bound:
+ self.bind(ETHTOOL_GENL_NAME, ethtool_coalesce_msg)
+ self._bound = True
+
+ def _get_dev_header(self):
+ """Create device header for netlink message"""
+
+ return {'attrs': [['ETHTOOL_A_HEADER_DEV_NAME', self._ifname]]}
+
+ def get_coalesce(self):
+ """
+ Get coalesce parameters for an interface using netlink.
+
+ Example:
+ >>> cn = CoalesceNetlink('eth0')
+ >>> coalesce = cn.get_coalesce()
+ >>> print(coalesce)
+ {'rx_usecs': 3, 'rx_frames': None, 'tx_usecs': None, ... }
+ """
+ self._ensure_bound()
+
+ msg = ethtool_coalesce_msg()
+ msg['cmd'] = ETHTOOL_MSG_COALESCE_GET
+ msg['version'] = ETHTOOL_GENL_VERSION
+ msg['attrs'].append(('ETHTOOL_A_COALESCE_HEADER', self._get_dev_header()))
+
+ try:
+ response = self.nlm_request(
+ msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST
+ )
+ except NetlinkError as e:
+ if e.code == EOPNOTSUPP:
+ raise CoalesceNotSupportedOperation(
+ f'Coalesce operation for {self._ifname} not supported by driver'
+ ) from e
+ raise
+
+ if not response:
+ raise CoalesceError(f'No response for coalesce get on {self._ifname}')
+
+ nl_msg = response[0]
+
+ # Parse response - only include attributes that were returned
+ # (unsupported attributes won't be in the response)
+ result = {}
+ for py_attr, nl_attr in COALESCE_NL_ATTRS.items():
+ value = nl_msg.get_attr(nl_attr) # Will be None if not present/supported
+
+ if value is not None:
+ nl_type = ethtool_coalesce_msg.get_nl_attr_type(nl_attr)
+
+ # Convert to boolean value if it is 'uint8' type
+ if nl_type == 'uint8':
+ value = bool(value)
+
+ result[py_attr] = value
+
+ return result
+
+ def set_coalesce(self, **kwargs):
+ """
+ Set coalesce parameters for an interface using netlink.
+
+ Only the parameters that are explicitly passed will be set.
+ Unsupported parameters will raise an error from the kernel.
+
+ Args:
+ **kwargs: Coalesce parameters to set. Valid keys are:
+ - rx_usecs, rx_frames, rx_usecs_irq, rx_frames_irq
+ - tx_usecs, tx_frames, tx_usecs_irq, tx_frames_irq
+ - stats_block_usecs
+ - adaptive_rx, adaptive_tx
+ - pkt_rate_low, pkt_rate_high
+ - rx_usecs_low, rx_frame_low, tx_usecs_low, tx_frame_low
+ - rx_usecs_high, rx_frame_high, tx_usecs_high, tx_frame_high
+ - sample_interval
+ - cqe_mode_tx, cqe_mode_rx
+ - tx_aggr_max_bytes, tx_aggr_max_frames, tx_aggr_time_usecs
+
+ Example:
+ >>> cn = CoalesceNetlink('eth0')
+ >>> cn.set_coalesce(rx_usecs=10, tx_usecs=10)
+ """
+ self._ensure_bound()
+
+ msg = ethtool_coalesce_msg()
+ msg['cmd'] = ETHTOOL_MSG_COALESCE_SET
+ msg['version'] = ETHTOOL_GENL_VERSION
+ msg['attrs'].append(('ETHTOOL_A_COALESCE_HEADER', self._get_dev_header()))
+
+ # Add only the parameters that were explicitly provided
+ for py_attr, value in kwargs.items():
+ if py_attr not in COALESCE_NL_ATTRS:
+ raise CoalesceInvalidValue(f'Unknown coalesce parameter: {py_attr}')
+
+ if value is not None:
+ nl_attr = COALESCE_NL_ATTRS[py_attr]
+ nl_type = ethtool_coalesce_msg.get_nl_attr_type(nl_attr)
+
+ # Convert values to 'uint' type
+ if nl_type.startswith('uint'):
+ value = int(value)
+
+ msg['attrs'].append((nl_attr, value))
+
+ try:
+ self.nlm_request(
+ msg, msg_type=self.prid, msg_flags=NLM_F_REQUEST | NLM_F_ACK
+ )
+ except NetlinkError as e:
+ params_str = ', '.join(
+ '='.join([k.replace('_', '-'), str(v)]) for k, v in kwargs.items()
+ )
+
+ if e.code == EOPNOTSUPP:
+ raise CoalesceNotSupportedParam(
+ f'Parameter(s) not supported for modification on '
+ f'{self._ifname}: {params_str}'
+ ) from e
+ elif e.code == EINVAL:
+ raise CoalesceInvalidValue(
+ f'Invalid value for coalesce parameter(s) on '
+ f'{self._ifname}: {params_str}'
+ ) from e
+ raise
+
+
+def get_coalesce(ifname) -> dict:
+ """
+ Get coalesce parameters for an interface.
+
+ This is a convenience function that creates a CoalesceNetlink instance,
+ gets the coalesce parameters, and closes the socket.
+
+ Args:
+ ifname: Interface name (e.g., 'eth0')
+
+ Returns:
+ dict: Coalesce parameters with None for unsupported values.
+ """
+ with CoalesceNetlink(ifname) as cn:
+ return cn.get_coalesce()
+
+
+def set_coalesce(ifname, **kwargs):
+ """
+ Set coalesce parameters for an interface.
+
+ This is a convenience function that creates a CoalesceNetlink instance,
+ sets the coalesce parameters, and closes the socket.
+
+ Args:
+ ifname: Interface name (e.g., 'eth0')
+ **kwargs: Coalesce parameters to set.
+ """
+ with CoalesceNetlink(ifname) as cn:
+ cn.set_coalesce(**kwargs)
+
+
+def get_all_params(boolean: bool = None) -> tuple:
+ """
+ Get all available parameters by the Linux kernel.
+
+ This is a function that gets the coalesce parameters
+ which are available by implementation and the Linux kernel.
+
+ Args:
+ boolean: Filter parameters and return only/without boolean types.
+
+ Returns:
+ tuple: All coalesce parameters
+ """
+
+ params = list(COALESCE_NL_ATTRS.keys())
+
+ def _param_is_bool(p):
+ nl_type = ethtool_coalesce_msg.get_nl_attr_type(COALESCE_NL_ATTRS[p])
+ return nl_type == 'uint8'
+
+ if boolean is not None:
+ boolean_params = list(filter(_param_is_bool, params))
+
+ if boolean:
+ # Use only boolean parameters
+ params = boolean_params
+ else:
+ # Use other parameters except booleans
+ for boolean_param in boolean_params:
+ params.remove(boolean_param)
+
+ return tuple(params)
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index 5b87bd9d1..7c8106521 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -28,6 +28,8 @@ from base_interfaces_test import BasicInterfaceTest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
+from vyos.ethtool import Ethtool
+from vyos.netlink import coalesce
from vyos.frrender import mgmt_daemon
from vyos.ifconfig import Section
from vyos.utils.file import read_file
@@ -205,6 +207,85 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.assertEqual(max_rx, rx)
self.assertEqual(max_tx, tx)
+ def test_ethtool_coalesce(self):
+ """
+ Verify that coalesce configuration is correctly applied to the interface using netlink
+ """
+
+ for interface in self._interfaces:
+ base_path = self._base_path + [interface, 'interrupt-coalescing']
+ ethtool = Ethtool(interface)
+ is_virtio = ethtool.get_driver_name() == 'virtio_net'
+
+ with self.subTest(interface=interface):
+ # Verify coalesce support on the NIC before check
+ supported = ethtool.check_coalesce()
+
+ # If ethtool reports completely unsupported feature, then the CLI commit
+ # should correctly raise a ConfigSessionError during commit
+ if not supported:
+ self.cli_set(base_path + ['rx-usecs', '32'])
+ self.cli_set(base_path + ['tx-usecs', '32'])
+
+ msg = 'Driver does not fully support coalesce configuration'
+ with self.assertRaisesRegex(ConfigSessionError, msg):
+ self.cli_commit()
+ continue
+
+ # To find out the supported features
+ supported_rx_usecs = ethtool.check_coalesce('rx_usecs')
+ supported_tx_usecs = ethtool.check_coalesce('tx_usecs')
+ supported_adaptive_rx = ethtool.check_coalesce('adaptive_rx')
+ supported_adaptive_tx = ethtool.check_coalesce('adaptive_tx')
+
+ # Disabled adaptive modes and set custom values
+ if supported_rx_usecs:
+ self.cli_set(base_path + ['rx-usecs', '64'])
+ if supported_tx_usecs:
+ self.cli_set(base_path + ['tx-usecs', '64'])
+
+ # Force adaptive to be disabled if it is already enabled
+ params = coalesce.get_coalesce(interface)
+ if supported_rx_usecs and params['adaptive_rx']:
+ cmd(f'sudo ethtool --coalesce {interface} adaptive-rx off')
+ if supported_tx_usecs and params['adaptive_tx']:
+ cmd(f'sudo ethtool --coalesce {interface} adaptive-tx off')
+
+ # Commit CLI configuration to apply coalescing
+ self.cli_commit()
+
+ # Query coalesce parameters after applying
+ params = coalesce.get_coalesce(interface)
+
+ # Assertions: all should reflect configured values
+ if supported_rx_usecs:
+ # `virtio-net` doesn't correctly work with this parameter
+ self.assertEqual(params['rx_usecs'], 0 if is_virtio else 64)
+ if supported_tx_usecs:
+ # `virtio-net` doesn't correctly work with this parameter
+ self.assertEqual(params['tx_usecs'], 0 if is_virtio else 64)
+
+ # Not all parameters are adjustable for some of NIC (`virtio-net`)
+ if supported_adaptive_rx:
+ # Now test enabling RX adaptive coalescing modes
+ self.cli_delete(base_path + ['rx-usecs'])
+ self.cli_set(base_path + ['adaptive-rx'])
+
+ if supported_adaptive_tx:
+ # Now test enabling TX adaptive coalescing modes
+ self.cli_delete(base_path + ['tx-usecs'])
+ self.cli_set(base_path + ['adaptive-tx'])
+
+ self.cli_commit()
+
+ # Verify that adaptive modes turned on correctly
+ params = coalesce.get_coalesce(interface)
+ if supported_adaptive_rx:
+ self.assertTrue(params['adaptive_rx'])
+
+ if supported_adaptive_tx:
+ self.assertTrue(params['adaptive_tx'])
+
def test_ethtool_flow_control(self):
for interface in self._interfaces:
# Disable flow-control
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index 554f0fa41..2a65855a4 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -37,6 +37,7 @@ from vyos.configverify import verify_vrf
from vyos.configverify import verify_bond_bridge_member
from vyos.configverify import verify_eapol
from vyos.ethtool import Ethtool
+from vyos.netlink import coalesce
from vyos.frrender import FRRender
from vyos.frrender import get_frrender_dict
from vyos.ifconfig import EthernetIf
@@ -256,6 +257,26 @@ def verify_ring_buffer(ethernet: dict, ethtool: Ethtool):
f'size of "{max_tx}" bytes!')
+def verify_coalesce(ethernet: dict, ethtool: Ethtool):
+ """
+ Verify coalesce settings
+ :param ethernet: dictionary which is received from get_interface_dict
+ :type ethernet: dict
+ :param ethtool: Ethernet object
+ :type ethtool: Ethtool
+ """
+ if 'interrupt_coalescing' in ethernet:
+ if not ethtool.check_coalesce():
+ raise ConfigError('Driver does not fully support coalesce configuration!')
+
+ for param in coalesce.get_all_params():
+ if param in ethernet['interrupt_coalescing']:
+ if not ethtool.check_coalesce(param):
+ param_name = param.replace('_', '-')
+ msg = f'Driver does not support "{param_name}" coalesce setting!'
+ raise ConfigError(msg)
+
+
def verify_offload(ethernet: dict, ethtool: Ethtool):
"""
Verify offloading capabilities
@@ -398,6 +419,7 @@ def verify(ethernet):
verify_ring_buffer(ethernet, ethtool)
verify_offload(ethernet, ethtool)
verify_mac_change(ethernet, ethtool)
+ verify_coalesce(ethernet, ethtool)
if 'is_bond_member' in ethernet:
verify_bond_member(ethernet, ethtool)