* [PATCH 0/2] net/tap: fix Rx queue interrupt support
@ 2026-07-17 9:58 Maxime Leroy
2026-07-17 9:58 ` [PATCH 1/2] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
` (2 more replies)
0 siblings, 3 replies; 11+ messages in thread
From: Maxime Leroy @ 2026-07-17 9:58 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable, Maxime Leroy
The tap PMD registers each queue file descriptor for Rx interrupts but the
feature is not actually usable through the generic API. This series fixes
two distinct issues, both against the original Rx interrupt support.
Patch 1 adds the missing rx_queue_intr_enable/disable ops. Without them
rte_eth_dev_rx_intr_enable() returns -ENOTSUP, so applications that arm
queues before sleeping (l3fwd-power and similar) treat tap as having no
interrupt support and fall back to polling.
Patch 2 fixes a traffic stall specific to interrupt mode: the Rx burst
skips reading the queue fd until the SIGIO trigger advances, but in
interrupt mode the application wakes through epoll, a distinct signal, so
the burst can return 0 right after a wakeup and the fd stays readable with
no further edge. The fd is now drained unconditionally in interrupt mode
and the SIGIO trigger is not armed on the data queue fds.
Both patches are candidates for stable.
Maxime Leroy (2):
net/tap: support Rx queue interrupt enable/disable
net/tap: drain queue FD in Rx interrupt mode
drivers/net/tap/rte_eth_tap.c | 23 +++++++++++++++++-
drivers/net/tap/rte_eth_tap.h | 5 ++++
drivers/net/tap/tap_intr.c | 44 +++++++++++++++++++++++++++++++++++
3 files changed, 71 insertions(+), 1 deletion(-)
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/2] net/tap: support Rx queue interrupt enable/disable
2026-07-17 9:58 [PATCH 0/2] net/tap: fix Rx queue interrupt support Maxime Leroy
@ 2026-07-17 9:58 ` Maxime Leroy
2026-07-17 9:58 ` [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
2 siblings, 0 replies; 11+ messages in thread
From: Maxime Leroy @ 2026-07-17 9:58 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable, Maxime Leroy
The driver registers each queue file descriptor for Rx interrupts but
never provided the rx_queue_intr_enable/disable ops, so
rte_eth_dev_rx_intr_enable() returned -ENOTSUP. Applications that arm
queues before sleeping (l3fwd-power and similar) treat that as "no
interrupt support" and fall back to polling, even though the epoll wait
on the queue fd works. The advertised Rx interrupt feature is therefore
unusable through the generic API.
The queue fd is kept readable by the kernel whenever data is available
and is drained by the Rx burst itself, so there is no interrupt source to
arm or mask. Record whether the port was configured for Rx interrupts and
implement both ops to succeed in that mode and return -ENOTSUP otherwise,
so the generic Rx interrupt API works and applications can block on the
queue fd.
Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
Cc: stable@dpdk.org
Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
drivers/net/tap/rte_eth_tap.c | 5 ++++
drivers/net/tap/rte_eth_tap.h | 3 +++
drivers/net/tap/tap_intr.c | 44 +++++++++++++++++++++++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index b93452f168..99ede19e49 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -935,6 +935,7 @@ static int
tap_dev_configure(struct rte_eth_dev *dev)
{
struct pmd_internals *pmd = dev->data->dev_private;
+ int intr_mode = !!dev->data->dev_conf.intr_conf.rxq;
if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
TAP_LOG(ERR,
@@ -945,6 +946,8 @@ tap_dev_configure(struct rte_eth_dev *dev)
return -1;
}
+ pmd->intr_mode = intr_mode;
+
TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
dev->device->name, pmd->name, dev->data->nb_tx_queues);
@@ -2098,6 +2101,8 @@ static const struct eth_dev_ops ops = {
.tx_queue_stop = tap_tx_queue_stop,
.rx_queue_release = tap_rx_queue_release,
.tx_queue_release = tap_tx_queue_release,
+ .rx_queue_intr_enable = tap_rx_queue_intr_enable,
+ .rx_queue_intr_disable = tap_rx_queue_intr_disable,
.flow_ctrl_get = tap_flow_ctrl_get,
.flow_ctrl_set = tap_flow_ctrl_set,
.link_update = tap_link_update,
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index f53a5ad077..3180719c34 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -93,6 +93,7 @@ struct pmd_internals {
struct rte_intr_handle *intr_handle; /* LSC interrupt handle. */
+ int intr_mode; /* Rx queue interrupt mode */
int ka_fd; /* keep-alive file descriptor */
struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
};
@@ -104,5 +105,7 @@ struct pmd_process_private {
/* tap_intr.c */
int tap_rx_intr_vec_set(struct rte_eth_dev *dev, int set);
+int tap_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id);
+int tap_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id);
#endif /* _RTE_ETH_TAP_H_ */
diff --git a/drivers/net/tap/tap_intr.c b/drivers/net/tap/tap_intr.c
index 1908f71f97..7db22b69f5 100644
--- a/drivers/net/tap/tap_intr.c
+++ b/drivers/net/tap/tap_intr.c
@@ -112,3 +112,47 @@ tap_rx_intr_vec_set(struct rte_eth_dev *dev, int set)
return tap_rx_intr_vec_install(dev);
return 0;
}
+
+/**
+ * Arm a queue for Rx interrupts.
+ *
+ * @param dev
+ * Pointer to the tap rte_eth_dev device structure.
+ * @param queue_id
+ * Rx queue index.
+ *
+ * @return
+ * 0 on success, -ENOTSUP if the port was not configured for Rx interrupts.
+ */
+int
+tap_rx_queue_intr_enable(struct rte_eth_dev *dev,
+ uint16_t queue_id __rte_unused)
+{
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (!pmd->intr_mode)
+ return -ENOTSUP;
+ return 0;
+}
+
+/**
+ * Disarm a queue from Rx interrupts.
+ *
+ * @param dev
+ * Pointer to the tap rte_eth_dev device structure.
+ * @param queue_id
+ * Rx queue index.
+ *
+ * @return
+ * 0 on success, -ENOTSUP if the port was not configured for Rx interrupts.
+ */
+int
+tap_rx_queue_intr_disable(struct rte_eth_dev *dev,
+ uint16_t queue_id __rte_unused)
+{
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (!pmd->intr_mode)
+ return -ENOTSUP;
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode
2026-07-17 9:58 [PATCH 0/2] net/tap: fix Rx queue interrupt support Maxime Leroy
2026-07-17 9:58 ` [PATCH 1/2] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
@ 2026-07-17 9:58 ` Maxime Leroy
2026-07-17 12:31 ` Stephen Hemminger
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
2 siblings, 1 reply; 11+ messages in thread
From: Maxime Leroy @ 2026-07-17 9:58 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable, Maxime Leroy
The tap Rx path is driven by a SIGIO trigger: pmd_rx_burst() returns
without reading the queue fd unless tap_trigger, bumped by the O_ASYNC
signal handler, has advanced. That avoids a readv() on every empty poll.
In Rx interrupt mode the application blocks on the queue fd through epoll
and polls only after a wakeup. The epoll wakeup and the trigger are
distinct signals, so the burst can return 0 right after a wakeup because
the trigger has not advanced, leaving the fd readable with no further
edge: traffic stalls.
When the port is configured for Rx interrupts, drain the fd
unconditionally in the burst and do not arm the SIGIO trigger on the data
queue fd. A data queue fd can be closed and recreated on a later setup,
so the mode must stay constant to keep every fd on the same SIGIO policy:
it is fixed at configure time and a later change is rejected. Close and
reopen the port to switch modes.
Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
Cc: stable@dpdk.org
Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
drivers/net/tap/rte_eth_tap.c | 18 +++++++++++++++++-
drivers/net/tap/rte_eth_tap.h | 2 ++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 99ede19e49..ea9ef76335 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -254,6 +254,10 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
goto error;
}
+ /* interrupt mode wakes through epoll on the data queue fd, not the SIGIO trigger */
+ if (pmd->intr_mode)
+ return fd;
+
/* Find a free realtime signal */
for (signo = SIGRTMIN + 1; signo < SIGRTMAX; signo++) {
struct sigaction sa;
@@ -477,7 +481,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
unsigned long num_rx_bytes = 0;
uint32_t trigger = tap_trigger;
- if (trigger == rxq->trigger_seen)
+ if (!rxq->intr_mode && trigger == rxq->trigger_seen)
return 0;
process_private = rte_eth_devices[rxq->in_port].process_private;
@@ -937,6 +941,16 @@ tap_dev_configure(struct rte_eth_dev *dev)
struct pmd_internals *pmd = dev->data->dev_private;
int intr_mode = !!dev->data->dev_conf.intr_conf.rxq;
+ /* The queue fd is created once and its SIGIO trigger is armed for poll
+ * mode only; the interrupt mode cannot be toggled on an existing port.
+ */
+ if (pmd->intr_mode_set && pmd->intr_mode != intr_mode) {
+ TAP_LOG(ERR,
+ "%s: Rx interrupt mode is fixed after configure, close and reopen the port to change it",
+ dev->device->name);
+ return -ENOTSUP;
+ }
+
if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
TAP_LOG(ERR,
"%s: number of rx queues %d must be equal to number of tx queues %d",
@@ -947,6 +961,7 @@ tap_dev_configure(struct rte_eth_dev *dev)
}
pmd->intr_mode = intr_mode;
+ pmd->intr_mode_set = 1;
TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
dev->device->name, pmd->name, dev->data->nb_tx_queues);
@@ -1620,6 +1635,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
rxq->queue_id = rx_queue_id;
rxq->max_rx_segs = max_rx_segs;
rxq->rxmode = &dev->data->dev_conf.rxmode;
+ rxq->intr_mode = internals->intr_mode;
dev->data->rx_queues[rx_queue_id] = rxq;
int fd = tap_setup_queue(dev, rx_queue_id, 1);
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 3180719c34..abe22aac9f 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -50,6 +50,7 @@ struct rx_queue {
uint16_t queue_id; /* queue ID*/
struct queue_stats stats; /* Stats for this RX queue */
uint16_t max_rx_segs; /* max scatter segments per packet */
+ uint16_t intr_mode; /* 1 when Rx queue interrupts are used */
struct rte_eth_rxmode *rxmode; /* RX features */
struct rte_mbuf *pool; /* mbufs pool for this queue */
struct tun_pi pi; /* packet info for iovecs */
@@ -94,6 +95,7 @@ struct pmd_internals {
struct rte_intr_handle *intr_handle; /* LSC interrupt handle. */
int intr_mode; /* Rx queue interrupt mode */
+ int intr_mode_set; /* intr_mode locked after configure */
int ka_fd; /* keep-alive file descriptor */
struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
};
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode
2026-07-17 9:58 ` [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
@ 2026-07-17 12:31 ` Stephen Hemminger
2026-07-20 8:33 ` Maxime Leroy
0 siblings, 1 reply; 11+ messages in thread
From: Stephen Hemminger @ 2026-07-17 12:31 UTC (permalink / raw)
To: Maxime Leroy; +Cc: dev, stable
[-- Attachment #1: Type: text/plain, Size: 5139 bytes --]
Don't use int as a boolean. Use bool. Ideally find unused pad hole for it
On Fri, Jul 17, 2026, 11:59 AM Maxime Leroy <maxime@leroys.fr> wrote:
> The tap Rx path is driven by a SIGIO trigger: pmd_rx_burst() returns
> without reading the queue fd unless tap_trigger, bumped by the O_ASYNC
> signal handler, has advanced. That avoids a readv() on every empty poll.
>
> In Rx interrupt mode the application blocks on the queue fd through epoll
> and polls only after a wakeup. The epoll wakeup and the trigger are
> distinct signals, so the burst can return 0 right after a wakeup because
> the trigger has not advanced, leaving the fd readable with no further
> edge: traffic stalls.
>
> When the port is configured for Rx interrupts, drain the fd
> unconditionally in the burst and do not arm the SIGIO trigger on the data
> queue fd. A data queue fd can be closed and recreated on a later setup,
> so the mode must stay constant to keep every fd on the same SIGIO policy:
> it is fixed at configure time and a later change is rejected. Close and
> reopen the port to switch modes.
>
> Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
> Cc: stable@dpdk.org
>
> Signed-off-by: Maxime Leroy <maxime@leroys.fr>
> ---
> drivers/net/tap/rte_eth_tap.c | 18 +++++++++++++++++-
> drivers/net/tap/rte_eth_tap.h | 2 ++
> 2 files changed, 19 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
> index 99ede19e49..ea9ef76335 100644
> --- a/drivers/net/tap/rte_eth_tap.c
> +++ b/drivers/net/tap/rte_eth_tap.c
> @@ -254,6 +254,10 @@ tun_alloc(struct pmd_internals *pmd, int
> is_keepalive, int persistent)
> goto error;
> }
>
> + /* interrupt mode wakes through epoll on the data queue fd, not
> the SIGIO trigger */
> + if (pmd->intr_mode)
> + return fd;
> +
> /* Find a free realtime signal */
> for (signo = SIGRTMIN + 1; signo < SIGRTMAX; signo++) {
> struct sigaction sa;
> @@ -477,7 +481,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs,
> uint16_t nb_pkts)
> unsigned long num_rx_bytes = 0;
> uint32_t trigger = tap_trigger;
>
> - if (trigger == rxq->trigger_seen)
> + if (!rxq->intr_mode && trigger == rxq->trigger_seen)
> return 0;
>
> process_private = rte_eth_devices[rxq->in_port].process_private;
> @@ -937,6 +941,16 @@ tap_dev_configure(struct rte_eth_dev *dev)
> struct pmd_internals *pmd = dev->data->dev_private;
> int intr_mode = !!dev->data->dev_conf.intr_conf.rxq;
>
> + /* The queue fd is created once and its SIGIO trigger is armed for
> poll
> + * mode only; the interrupt mode cannot be toggled on an existing
> port.
> + */
> + if (pmd->intr_mode_set && pmd->intr_mode != intr_mode) {
> + TAP_LOG(ERR,
> + "%s: Rx interrupt mode is fixed after configure,
> close and reopen the port to change it",
> + dev->device->name);
> + return -ENOTSUP;
> + }
> +
> if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
> TAP_LOG(ERR,
> "%s: number of rx queues %d must be equal to
> number of tx queues %d",
> @@ -947,6 +961,7 @@ tap_dev_configure(struct rte_eth_dev *dev)
> }
>
> pmd->intr_mode = intr_mode;
> + pmd->intr_mode_set = 1;
>
> TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
> dev->device->name, pmd->name, dev->data->nb_tx_queues);
> @@ -1620,6 +1635,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
> rxq->queue_id = rx_queue_id;
> rxq->max_rx_segs = max_rx_segs;
> rxq->rxmode = &dev->data->dev_conf.rxmode;
> + rxq->intr_mode = internals->intr_mode;
>
> dev->data->rx_queues[rx_queue_id] = rxq;
> int fd = tap_setup_queue(dev, rx_queue_id, 1);
> diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
> index 3180719c34..abe22aac9f 100644
> --- a/drivers/net/tap/rte_eth_tap.h
> +++ b/drivers/net/tap/rte_eth_tap.h
> @@ -50,6 +50,7 @@ struct rx_queue {
> uint16_t queue_id; /* queue ID*/
> struct queue_stats stats; /* Stats for this RX queue */
> uint16_t max_rx_segs; /* max scatter segments per packet
> */
> + uint16_t intr_mode; /* 1 when Rx queue interrupts are
> used */
> struct rte_eth_rxmode *rxmode; /* RX features */
> struct rte_mbuf *pool; /* mbufs pool for this queue */
> struct tun_pi pi; /* packet info for iovecs */
> @@ -94,6 +95,7 @@ struct pmd_internals {
>
> struct rte_intr_handle *intr_handle; /* LSC interrupt
> handle. */
> int intr_mode; /* Rx queue interrupt mode */
> + int intr_mode_set; /* intr_mode locked after
> configure */
> int ka_fd; /* keep-alive file descriptor */
> struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
> };
> --
> 2.43.0
>
>
[-- Attachment #2: Type: text/html, Size: 6401 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode
2026-07-17 12:31 ` Stephen Hemminger
@ 2026-07-20 8:33 ` Maxime Leroy
2026-07-20 11:16 ` Stephen Hemminger
0 siblings, 1 reply; 11+ messages in thread
From: Maxime Leroy @ 2026-07-20 8:33 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, stable
On Fri, Jul 17, 2026 at 2:34 PM Stephen Hemminger
<stephen@networkplumber.org> wrote:
>
> Don't use int as a boolean. Use bool. Ideally find unused pad hole for it
Agreed int-as-a-boolean is not great. But bool in a structure is
discouraged by coding_style.rst:
"Uses of bool in structures are not preferred as is wastes space and
it's also not clear as to what type size the bool is." (Ref: LKML)
and that very LKML reference is Linus recommending the opposite of
bool for struct members:
"please don't use bool in structures at all. [...] Use bool mainly as a
return type from functions [...]. [...] just make sure the base type is
unsigned [...] you can specify the base type as you wish [...] for
packing."
The current tap PMD follows that too: the existing flags (persist,
flow_init, flow_isolate) are all int, and stdbool.h is not even
included in the headers.
So for v2 I'll use uint8_t for intr_mode and intr_mode_set: fixed
1-byte size, unambiguous representation, and it fits the existing
padding so neither struct grows (pmd_internals stays 144 bytes,
rx_queue stays 80). That matches both the guide and the referenced
LKML guidance.
I can also add a third patch at the end of the series converting the
existing int flags (persist, flow_init, flow_isolate) to uint8_t, to
make the driver consistent
>
> On Fri, Jul 17, 2026, 11:59 AM Maxime Leroy <maxime@leroys.fr> wrote:
>>
>> The tap Rx path is driven by a SIGIO trigger: pmd_rx_burst() returns
>> without reading the queue fd unless tap_trigger, bumped by the O_ASYNC
>> signal handler, has advanced. That avoids a readv() on every empty poll.
>>
>> In Rx interrupt mode the application blocks on the queue fd through epoll
>> and polls only after a wakeup. The epoll wakeup and the trigger are
>> distinct signals, so the burst can return 0 right after a wakeup because
>> the trigger has not advanced, leaving the fd readable with no further
>> edge: traffic stalls.
>>
>> When the port is configured for Rx interrupts, drain the fd
>> unconditionally in the burst and do not arm the SIGIO trigger on the data
>> queue fd. A data queue fd can be closed and recreated on a later setup,
>> so the mode must stay constant to keep every fd on the same SIGIO policy:
>> it is fixed at configure time and a later change is rejected. Close and
>> reopen the port to switch modes.
>>
>> Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
>> Cc: stable@dpdk.org
>>
>> Signed-off-by: Maxime Leroy <maxime@leroys.fr>
>> ---
>> drivers/net/tap/rte_eth_tap.c | 18 +++++++++++++++++-
>> drivers/net/tap/rte_eth_tap.h | 2 ++
>> 2 files changed, 19 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
>> index 99ede19e49..ea9ef76335 100644
>> --- a/drivers/net/tap/rte_eth_tap.c
>> +++ b/drivers/net/tap/rte_eth_tap.c
>> @@ -254,6 +254,10 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
>> goto error;
>> }
>>
>> + /* interrupt mode wakes through epoll on the data queue fd, not the SIGIO trigger */
>> + if (pmd->intr_mode)
>> + return fd;
>> +
>> /* Find a free realtime signal */
>> for (signo = SIGRTMIN + 1; signo < SIGRTMAX; signo++) {
>> struct sigaction sa;
>> @@ -477,7 +481,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
>> unsigned long num_rx_bytes = 0;
>> uint32_t trigger = tap_trigger;
>>
>> - if (trigger == rxq->trigger_seen)
>> + if (!rxq->intr_mode && trigger == rxq->trigger_seen)
>> return 0;
>>
>> process_private = rte_eth_devices[rxq->in_port].process_private;
>> @@ -937,6 +941,16 @@ tap_dev_configure(struct rte_eth_dev *dev)
>> struct pmd_internals *pmd = dev->data->dev_private;
>> int intr_mode = !!dev->data->dev_conf.intr_conf.rxq;
>>
>> + /* The queue fd is created once and its SIGIO trigger is armed for poll
>> + * mode only; the interrupt mode cannot be toggled on an existing port.
>> + */
>> + if (pmd->intr_mode_set && pmd->intr_mode != intr_mode) {
>> + TAP_LOG(ERR,
>> + "%s: Rx interrupt mode is fixed after configure, close and reopen the port to change it",
>> + dev->device->name);
>> + return -ENOTSUP;
>> + }
>> +
>> if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
>> TAP_LOG(ERR,
>> "%s: number of rx queues %d must be equal to number of tx queues %d",
>> @@ -947,6 +961,7 @@ tap_dev_configure(struct rte_eth_dev *dev)
>> }
>>
>> pmd->intr_mode = intr_mode;
>> + pmd->intr_mode_set = 1;
>>
>> TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
>> dev->device->name, pmd->name, dev->data->nb_tx_queues);
>> @@ -1620,6 +1635,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
>> rxq->queue_id = rx_queue_id;
>> rxq->max_rx_segs = max_rx_segs;
>> rxq->rxmode = &dev->data->dev_conf.rxmode;
>> + rxq->intr_mode = internals->intr_mode;
>>
>> dev->data->rx_queues[rx_queue_id] = rxq;
>> int fd = tap_setup_queue(dev, rx_queue_id, 1);
>> diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
>> index 3180719c34..abe22aac9f 100644
>> --- a/drivers/net/tap/rte_eth_tap.h
>> +++ b/drivers/net/tap/rte_eth_tap.h
>> @@ -50,6 +50,7 @@ struct rx_queue {
>> uint16_t queue_id; /* queue ID*/
>> struct queue_stats stats; /* Stats for this RX queue */
>> uint16_t max_rx_segs; /* max scatter segments per packet */
>> + uint16_t intr_mode; /* 1 when Rx queue interrupts are used */
>> struct rte_eth_rxmode *rxmode; /* RX features */
>> struct rte_mbuf *pool; /* mbufs pool for this queue */
>> struct tun_pi pi; /* packet info for iovecs */
>> @@ -94,6 +95,7 @@ struct pmd_internals {
>>
>> struct rte_intr_handle *intr_handle; /* LSC interrupt handle. */
>> int intr_mode; /* Rx queue interrupt mode */
>> + int intr_mode_set; /* intr_mode locked after configure */
>> int ka_fd; /* keep-alive file descriptor */
>> struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
>> };
>> --
>> 2.43.0
>>
--
-------------------------------
Maxime Leroy
maxime@leroys.fr
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode
2026-07-20 8:33 ` Maxime Leroy
@ 2026-07-20 11:16 ` Stephen Hemminger
0 siblings, 0 replies; 11+ messages in thread
From: Stephen Hemminger @ 2026-07-20 11:16 UTC (permalink / raw)
To: Maxime Leroy; +Cc: dev, stable
[-- Attachment #1: Type: text/plain, Size: 7183 bytes --]
the coding style against bool is more of a keenel thing.
On Mon, Jul 20, 2026, 10:33 Maxime Leroy <maxime@leroys.fr> wrote:
> On Fri, Jul 17, 2026 at 2:34 PM Stephen Hemminger
> <stephen@networkplumber.org> wrote:
> >
> > Don't use int as a boolean. Use bool. Ideally find unused pad hole for it
>
> Agreed int-as-a-boolean is not great. But bool in a structure is
> discouraged by coding_style.rst:
>
> "Uses of bool in structures are not preferred as is wastes space and
> it's also not clear as to what type size the bool is." (Ref: LKML)
>
> and that very LKML reference is Linus recommending the opposite of
> bool for struct members:
>
> "please don't use bool in structures at all. [...] Use bool mainly as a
> return type from functions [...]. [...] just make sure the base type is
> unsigned [...] you can specify the base type as you wish [...] for
> packing."
>
> The current tap PMD follows that too: the existing flags (persist,
> flow_init, flow_isolate) are all int, and stdbool.h is not even
> included in the headers.
>
> So for v2 I'll use uint8_t for intr_mode and intr_mode_set: fixed
> 1-byte size, unambiguous representation, and it fits the existing
> padding so neither struct grows (pmd_internals stays 144 bytes,
> rx_queue stays 80). That matches both the guide and the referenced
> LKML guidance.
>
> I can also add a third patch at the end of the series converting the
> existing int flags (persist, flow_init, flow_isolate) to uint8_t, to
> make the driver consistent
>
> >
> > On Fri, Jul 17, 2026, 11:59 AM Maxime Leroy <maxime@leroys.fr> wrote:
> >>
> >> The tap Rx path is driven by a SIGIO trigger: pmd_rx_burst() returns
> >> without reading the queue fd unless tap_trigger, bumped by the O_ASYNC
> >> signal handler, has advanced. That avoids a readv() on every empty poll.
> >>
> >> In Rx interrupt mode the application blocks on the queue fd through
> epoll
> >> and polls only after a wakeup. The epoll wakeup and the trigger are
> >> distinct signals, so the burst can return 0 right after a wakeup because
> >> the trigger has not advanced, leaving the fd readable with no further
> >> edge: traffic stalls.
> >>
> >> When the port is configured for Rx interrupts, drain the fd
> >> unconditionally in the burst and do not arm the SIGIO trigger on the
> data
> >> queue fd. A data queue fd can be closed and recreated on a later setup,
> >> so the mode must stay constant to keep every fd on the same SIGIO
> policy:
> >> it is fixed at configure time and a later change is rejected. Close and
> >> reopen the port to switch modes.
> >>
> >> Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
> >> Cc: stable@dpdk.org
> >>
> >> Signed-off-by: Maxime Leroy <maxime@leroys.fr>
> >> ---
> >> drivers/net/tap/rte_eth_tap.c | 18 +++++++++++++++++-
> >> drivers/net/tap/rte_eth_tap.h | 2 ++
> >> 2 files changed, 19 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/drivers/net/tap/rte_eth_tap.c
> b/drivers/net/tap/rte_eth_tap.c
> >> index 99ede19e49..ea9ef76335 100644
> >> --- a/drivers/net/tap/rte_eth_tap.c
> >> +++ b/drivers/net/tap/rte_eth_tap.c
> >> @@ -254,6 +254,10 @@ tun_alloc(struct pmd_internals *pmd, int
> is_keepalive, int persistent)
> >> goto error;
> >> }
> >>
> >> + /* interrupt mode wakes through epoll on the data queue fd, not
> the SIGIO trigger */
> >> + if (pmd->intr_mode)
> >> + return fd;
> >> +
> >> /* Find a free realtime signal */
> >> for (signo = SIGRTMIN + 1; signo < SIGRTMAX; signo++) {
> >> struct sigaction sa;
> >> @@ -477,7 +481,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs,
> uint16_t nb_pkts)
> >> unsigned long num_rx_bytes = 0;
> >> uint32_t trigger = tap_trigger;
> >>
> >> - if (trigger == rxq->trigger_seen)
> >> + if (!rxq->intr_mode && trigger == rxq->trigger_seen)
> >> return 0;
> >>
> >> process_private = rte_eth_devices[rxq->in_port].process_private;
> >> @@ -937,6 +941,16 @@ tap_dev_configure(struct rte_eth_dev *dev)
> >> struct pmd_internals *pmd = dev->data->dev_private;
> >> int intr_mode = !!dev->data->dev_conf.intr_conf.rxq;
> >>
> >> + /* The queue fd is created once and its SIGIO trigger is armed
> for poll
> >> + * mode only; the interrupt mode cannot be toggled on an
> existing port.
> >> + */
> >> + if (pmd->intr_mode_set && pmd->intr_mode != intr_mode) {
> >> + TAP_LOG(ERR,
> >> + "%s: Rx interrupt mode is fixed after
> configure, close and reopen the port to change it",
> >> + dev->device->name);
> >> + return -ENOTSUP;
> >> + }
> >> +
> >> if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
> >> TAP_LOG(ERR,
> >> "%s: number of rx queues %d must be equal to
> number of tx queues %d",
> >> @@ -947,6 +961,7 @@ tap_dev_configure(struct rte_eth_dev *dev)
> >> }
> >>
> >> pmd->intr_mode = intr_mode;
> >> + pmd->intr_mode_set = 1;
> >>
> >> TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
> >> dev->device->name, pmd->name, dev->data->nb_tx_queues);
> >> @@ -1620,6 +1635,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
> >> rxq->queue_id = rx_queue_id;
> >> rxq->max_rx_segs = max_rx_segs;
> >> rxq->rxmode = &dev->data->dev_conf.rxmode;
> >> + rxq->intr_mode = internals->intr_mode;
> >>
> >> dev->data->rx_queues[rx_queue_id] = rxq;
> >> int fd = tap_setup_queue(dev, rx_queue_id, 1);
> >> diff --git a/drivers/net/tap/rte_eth_tap.h
> b/drivers/net/tap/rte_eth_tap.h
> >> index 3180719c34..abe22aac9f 100644
> >> --- a/drivers/net/tap/rte_eth_tap.h
> >> +++ b/drivers/net/tap/rte_eth_tap.h
> >> @@ -50,6 +50,7 @@ struct rx_queue {
> >> uint16_t queue_id; /* queue ID*/
> >> struct queue_stats stats; /* Stats for this RX queue */
> >> uint16_t max_rx_segs; /* max scatter segments per
> packet */
> >> + uint16_t intr_mode; /* 1 when Rx queue interrupts
> are used */
> >> struct rte_eth_rxmode *rxmode; /* RX features */
> >> struct rte_mbuf *pool; /* mbufs pool for this queue */
> >> struct tun_pi pi; /* packet info for iovecs */
> >> @@ -94,6 +95,7 @@ struct pmd_internals {
> >>
> >> struct rte_intr_handle *intr_handle; /* LSC interrupt
> handle. */
> >> int intr_mode; /* Rx queue interrupt mode */
> >> + int intr_mode_set; /* intr_mode locked after
> configure */
> >> int ka_fd; /* keep-alive file descriptor
> */
> >> struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets
> */
> >> };
> >> --
> >> 2.43.0
> >>
>
>
> --
> -------------------------------
> Maxime Leroy
> maxime@leroys.fr
>
[-- Attachment #2: Type: text/html, Size: 9351 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 0/3] net/tap: fix Rx queue interrupt support
2026-07-17 9:58 [PATCH 0/2] net/tap: fix Rx queue interrupt support Maxime Leroy
2026-07-17 9:58 ` [PATCH 1/2] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
2026-07-17 9:58 ` [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
@ 2026-07-20 12:06 ` Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 1/3] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
` (3 more replies)
2 siblings, 4 replies; 11+ messages in thread
From: Maxime Leroy @ 2026-07-20 12:06 UTC (permalink / raw)
To: dev; +Cc: Maxime Leroy
The tap PMD registers each queue file descriptor for Rx interrupts but the
feature is not usable through the generic API. This series fixes two
distinct issues against the original Rx interrupt support, plus a small
type cleanup.
Patch 1 adds the missing rx_queue_intr_enable/disable ops. Without them
rte_eth_dev_rx_intr_enable() returns -ENOTSUP, so applications that arm
queues before sleeping (l3fwd-power and similar) treat tap as having no
interrupt support and fall back to polling.
Patch 2 fixes a traffic stall specific to interrupt mode: the Rx burst
skips reading the queue fd until the SIGIO trigger advances, but in
interrupt mode the application wakes through epoll, a distinct signal, so
the burst can return 0 right after a wakeup and the fd stays readable with
no further edge. The fd is now drained unconditionally in interrupt mode
and the SIGIO trigger is not armed on the data queue fds.
Patch 3 converts the driver's int-as-boolean fields to bool.
Patches 1 and 2 are candidates for stable.
v2:
- use bool for the new interrupt-mode fields instead of int (Stephen);
both fit existing padding, so neither struct grows
- add patch 3 converting the existing int booleans to bool
The coding_style.rst note that discouraged bool in structures no longer
matches DPDK practice; it is dropped by a separate patch.
Maxime Leroy (3):
net/tap: support Rx queue interrupt enable/disable
net/tap: drain queue FD in Rx interrupt mode
net/tap: use bool for boolean flags
drivers/net/tap/rte_eth_tap.c | 25 +++++++++++++++++++-
drivers/net/tap/rte_eth_tap.h | 11 ++++++---
drivers/net/tap/tap_flow.c | 4 ++--
drivers/net/tap/tap_intr.c | 44 +++++++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 6 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/3] net/tap: support Rx queue interrupt enable/disable
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
@ 2026-07-20 12:06 ` Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 2/3] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
` (2 subsequent siblings)
3 siblings, 0 replies; 11+ messages in thread
From: Maxime Leroy @ 2026-07-20 12:06 UTC (permalink / raw)
To: dev; +Cc: Maxime Leroy, stable, Stephen Hemminger, Pascal Mazon,
Moti Haimovsky
The driver registers each queue file descriptor for Rx interrupts but
never provided the rx_queue_intr_enable/disable ops, so
rte_eth_dev_rx_intr_enable() returned -ENOTSUP. Applications that arm
queues before sleeping (l3fwd-power and similar) treat that as "no
interrupt support" and fall back to polling, even though the epoll wait
on the queue fd works. The advertised Rx interrupt feature is therefore
unusable through the generic API.
The queue fd is kept readable by the kernel whenever data is available
and is drained by the Rx burst itself, so there is no interrupt source to
arm or mask. Record whether the port was configured for Rx interrupts and
implement both ops to succeed in that mode and return -ENOTSUP otherwise,
so the generic Rx interrupt API works and applications can block on the
queue fd.
Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
Cc: stable@dpdk.org
Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
drivers/net/tap/rte_eth_tap.c | 5 ++++
drivers/net/tap/rte_eth_tap.h | 3 +++
drivers/net/tap/tap_intr.c | 44 +++++++++++++++++++++++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index b93452f168..dc56c974da 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -935,6 +935,7 @@ static int
tap_dev_configure(struct rte_eth_dev *dev)
{
struct pmd_internals *pmd = dev->data->dev_private;
+ bool intr_mode = dev->data->dev_conf.intr_conf.rxq;
if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
TAP_LOG(ERR,
@@ -945,6 +946,8 @@ tap_dev_configure(struct rte_eth_dev *dev)
return -1;
}
+ pmd->intr_mode = intr_mode;
+
TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
dev->device->name, pmd->name, dev->data->nb_tx_queues);
@@ -2098,6 +2101,8 @@ static const struct eth_dev_ops ops = {
.tx_queue_stop = tap_tx_queue_stop,
.rx_queue_release = tap_rx_queue_release,
.tx_queue_release = tap_tx_queue_release,
+ .rx_queue_intr_enable = tap_rx_queue_intr_enable,
+ .rx_queue_intr_disable = tap_rx_queue_intr_disable,
.flow_ctrl_get = tap_flow_ctrl_get,
.flow_ctrl_set = tap_flow_ctrl_set,
.link_update = tap_link_update,
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index f53a5ad077..51fd2339fc 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -93,6 +93,7 @@ struct pmd_internals {
struct rte_intr_handle *intr_handle; /* LSC interrupt handle. */
+ bool intr_mode; /* Rx queue interrupt mode */
int ka_fd; /* keep-alive file descriptor */
struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
};
@@ -104,5 +105,7 @@ struct pmd_process_private {
/* tap_intr.c */
int tap_rx_intr_vec_set(struct rte_eth_dev *dev, int set);
+int tap_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id);
+int tap_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id);
#endif /* _RTE_ETH_TAP_H_ */
diff --git a/drivers/net/tap/tap_intr.c b/drivers/net/tap/tap_intr.c
index 1908f71f97..7db22b69f5 100644
--- a/drivers/net/tap/tap_intr.c
+++ b/drivers/net/tap/tap_intr.c
@@ -112,3 +112,47 @@ tap_rx_intr_vec_set(struct rte_eth_dev *dev, int set)
return tap_rx_intr_vec_install(dev);
return 0;
}
+
+/**
+ * Arm a queue for Rx interrupts.
+ *
+ * @param dev
+ * Pointer to the tap rte_eth_dev device structure.
+ * @param queue_id
+ * Rx queue index.
+ *
+ * @return
+ * 0 on success, -ENOTSUP if the port was not configured for Rx interrupts.
+ */
+int
+tap_rx_queue_intr_enable(struct rte_eth_dev *dev,
+ uint16_t queue_id __rte_unused)
+{
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (!pmd->intr_mode)
+ return -ENOTSUP;
+ return 0;
+}
+
+/**
+ * Disarm a queue from Rx interrupts.
+ *
+ * @param dev
+ * Pointer to the tap rte_eth_dev device structure.
+ * @param queue_id
+ * Rx queue index.
+ *
+ * @return
+ * 0 on success, -ENOTSUP if the port was not configured for Rx interrupts.
+ */
+int
+tap_rx_queue_intr_disable(struct rte_eth_dev *dev,
+ uint16_t queue_id __rte_unused)
+{
+ struct pmd_internals *pmd = dev->data->dev_private;
+
+ if (!pmd->intr_mode)
+ return -ENOTSUP;
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 2/3] net/tap: drain queue FD in Rx interrupt mode
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 1/3] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
@ 2026-07-20 12:06 ` Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 3/3] net/tap: use bool for boolean flags Maxime Leroy
2026-07-26 17:27 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Stephen Hemminger
3 siblings, 0 replies; 11+ messages in thread
From: Maxime Leroy @ 2026-07-20 12:06 UTC (permalink / raw)
To: dev; +Cc: Maxime Leroy, stable, Stephen Hemminger, Moti Haimovsky,
Pascal Mazon
The tap Rx path is driven by a SIGIO trigger: pmd_rx_burst() returns
without reading the queue fd unless tap_trigger, bumped by the O_ASYNC
signal handler, has advanced. That avoids a readv() on every empty poll.
In Rx interrupt mode the application blocks on the queue fd through epoll
and polls only after a wakeup. The epoll wakeup and the trigger are
distinct signals, so the burst can return 0 right after a wakeup because
the trigger has not advanced, leaving the fd readable with no further
edge: traffic stalls.
When the port is configured for Rx interrupts, drain the fd
unconditionally in the burst and do not arm the SIGIO trigger on the data
queue fd. A data queue fd can be closed and recreated on a later setup,
so the mode must stay constant to keep every fd on the same SIGIO policy:
it is fixed at configure time and a later change is rejected. Close and
reopen the port to switch modes.
Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
Cc: stable@dpdk.org
Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
drivers/net/tap/rte_eth_tap.c | 20 +++++++++++++++++++-
drivers/net/tap/rte_eth_tap.h | 2 ++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index dc56c974da..882bb5aa71 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -254,6 +254,12 @@ tun_alloc(struct pmd_internals *pmd, int is_keepalive, int persistent)
goto error;
}
+ /* data queue fds in interrupt mode wake through epoll, not the SIGIO
+ * trigger; the keep-alive fd always keeps SIGIO
+ */
+ if (!is_keepalive && pmd->intr_mode)
+ return fd;
+
/* Find a free realtime signal */
for (signo = SIGRTMIN + 1; signo < SIGRTMAX; signo++) {
struct sigaction sa;
@@ -477,7 +483,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
unsigned long num_rx_bytes = 0;
uint32_t trigger = tap_trigger;
- if (trigger == rxq->trigger_seen)
+ if (!rxq->intr_mode && trigger == rxq->trigger_seen)
return 0;
process_private = rte_eth_devices[rxq->in_port].process_private;
@@ -937,6 +943,16 @@ tap_dev_configure(struct rte_eth_dev *dev)
struct pmd_internals *pmd = dev->data->dev_private;
bool intr_mode = dev->data->dev_conf.intr_conf.rxq;
+ /* The queue fd is created once and its SIGIO trigger is armed for poll
+ * mode only; the interrupt mode cannot be toggled on an existing port.
+ */
+ if (pmd->intr_mode_set && pmd->intr_mode != intr_mode) {
+ TAP_LOG(ERR,
+ "%s: Rx interrupt mode is fixed after configure, close and reopen the port to change it",
+ dev->device->name);
+ return -ENOTSUP;
+ }
+
if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
TAP_LOG(ERR,
"%s: number of rx queues %d must be equal to number of tx queues %d",
@@ -947,6 +963,7 @@ tap_dev_configure(struct rte_eth_dev *dev)
}
pmd->intr_mode = intr_mode;
+ pmd->intr_mode_set = true;
TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
dev->device->name, pmd->name, dev->data->nb_tx_queues);
@@ -1620,6 +1637,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
rxq->queue_id = rx_queue_id;
rxq->max_rx_segs = max_rx_segs;
rxq->rxmode = &dev->data->dev_conf.rxmode;
+ rxq->intr_mode = internals->intr_mode;
dev->data->rx_queues[rx_queue_id] = rxq;
int fd = tap_setup_queue(dev, rx_queue_id, 1);
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index 51fd2339fc..aae9eea510 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -50,6 +50,7 @@ struct rx_queue {
uint16_t queue_id; /* queue ID*/
struct queue_stats stats; /* Stats for this RX queue */
uint16_t max_rx_segs; /* max scatter segments per packet */
+ bool intr_mode; /* 1 when Rx queue interrupts are used */
struct rte_eth_rxmode *rxmode; /* RX features */
struct rte_mbuf *pool; /* mbufs pool for this queue */
struct tun_pi pi; /* packet info for iovecs */
@@ -94,6 +95,7 @@ struct pmd_internals {
struct rte_intr_handle *intr_handle; /* LSC interrupt handle. */
bool intr_mode; /* Rx queue interrupt mode */
+ bool intr_mode_set; /* intr_mode locked after configure */
int ka_fd; /* keep-alive file descriptor */
struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
};
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 3/3] net/tap: use bool for boolean flags
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 1/3] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 2/3] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
@ 2026-07-20 12:06 ` Maxime Leroy
2026-07-26 17:27 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Stephen Hemminger
3 siblings, 0 replies; 11+ messages in thread
From: Maxime Leroy @ 2026-07-20 12:06 UTC (permalink / raw)
To: dev; +Cc: Maxime Leroy, Stephen Hemminger
persist, flow_init and flow_isolate only ever hold true or false but were
declared as int. Use bool, consistent with the interrupt-mode flags added
in this series and with common DPDK practice.
Signed-off-by: Maxime Leroy <maxime@leroys.fr>
---
drivers/net/tap/rte_eth_tap.h | 6 +++---
drivers/net/tap/tap_flow.c | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index aae9eea510..80419aad8c 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -72,7 +72,7 @@ struct pmd_internals {
char remote_iface[IFNAMSIZ]; /* Remote netdevice name */
char name[IFNAMSIZ]; /* Internal Tap device name */
int type; /* Type field - TUN|TAP */
- int persist; /* 1 if keep link up, else 0 */
+ bool persist; /* 1 if keep link up, else 0 */
struct rte_ether_addr eth_addr; /* Mac address of the device port */
struct rte_ether_addr *mc_addrs; /* multicast address list */
uint32_t nb_mc_addrs; /* multicast address count */
@@ -82,8 +82,8 @@ struct pmd_internals {
int nlsk_fd; /* Netlink socket fd */
#ifdef HAVE_TCA_FLOWER
- int flow_init; /* 1 if qdiscs were created */
- int flow_isolate; /* 1 if flow isolation is enabled */
+ bool flow_init; /* 1 if qdiscs were created */
+ bool flow_isolate; /* 1 if flow isolation is enabled */
struct tap_rss *rss; /* BPF program */
diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index a9d8e09562..a0ffb35bdd 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -1521,7 +1521,7 @@ tap_flow_isolate(struct rte_eth_dev *dev,
}
return 0;
error:
- pmd->flow_isolate = 0;
+ pmd->flow_isolate = false;
return rte_flow_error_set(
error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
"TC rule creation failed");
@@ -1914,7 +1914,7 @@ tap_flow_init(struct pmd_internals *pmd)
return -1;
}
- pmd->flow_init = 1;
+ pmd->flow_init = true;
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2 0/3] net/tap: fix Rx queue interrupt support
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
` (2 preceding siblings ...)
2026-07-20 12:06 ` [PATCH v2 3/3] net/tap: use bool for boolean flags Maxime Leroy
@ 2026-07-26 17:27 ` Stephen Hemminger
3 siblings, 0 replies; 11+ messages in thread
From: Stephen Hemminger @ 2026-07-26 17:27 UTC (permalink / raw)
To: Maxime Leroy; +Cc: dev
On Mon, 20 Jul 2026 14:06:56 +0200
Maxime Leroy <maxime@leroys.fr> wrote:
> The tap PMD registers each queue file descriptor for Rx interrupts but the
> feature is not usable through the generic API. This series fixes two
> distinct issues against the original Rx interrupt support, plus a small
> type cleanup.
>
> Patch 1 adds the missing rx_queue_intr_enable/disable ops. Without them
> rte_eth_dev_rx_intr_enable() returns -ENOTSUP, so applications that arm
> queues before sleeping (l3fwd-power and similar) treat tap as having no
> interrupt support and fall back to polling.
>
> Patch 2 fixes a traffic stall specific to interrupt mode: the Rx burst
> skips reading the queue fd until the SIGIO trigger advances, but in
> interrupt mode the application wakes through epoll, a distinct signal, so
> the burst can return 0 right after a wakeup and the fd stays readable with
> no further edge. The fd is now drained unconditionally in interrupt mode
> and the SIGIO trigger is not armed on the data queue fds.
>
> Patch 3 converts the driver's int-as-boolean fields to bool.
>
> Patches 1 and 2 are candidates for stable.
>
> v2:
> - use bool for the new interrupt-mode fields instead of int (Stephen);
> both fit existing padding, so neither struct grows
> - add patch 3 converting the existing int booleans to bool
>
> The coding_style.rst note that discouraged bool in structures no longer
> matches DPDK practice; it is dropped by a separate patch.
>
> Maxime Leroy (3):
> net/tap: support Rx queue interrupt enable/disable
> net/tap: drain queue FD in Rx interrupt mode
> net/tap: use bool for boolean flags
>
> drivers/net/tap/rte_eth_tap.c | 25 +++++++++++++++++++-
> drivers/net/tap/rte_eth_tap.h | 11 ++++++---
> drivers/net/tap/tap_flow.c | 4 ++--
> drivers/net/tap/tap_intr.c | 44 +++++++++++++++++++++++++++++++++++
> 4 files changed, 78 insertions(+), 6 deletions(-)
>
Like this but detailed AI review found some things.
Review of [PATCH v2 0/3] net/tap: Rx interrupt fixes
Applied clean to 38f72e5. All three commits build independently under
-Dwerror=true, so the series is bisect-safe. No compiler diagnostics.
Verified against a running tap port (/dev/net/tun, kernel-side traffic),
not just by reading the diffs.
Patch 1/3: net/tap: support Rx queue interrupt enable/disable
------------------------------------------------------------
Confirmed working. rte_eth_dev_rx_intr_enable()/disable() return 0 in
interrupt mode where they previously returned -ENOTSUP, and an
epoll-driven receive loop delivers 100% of offered packets.
The -ENOTSUP branch is not dead code: rte_eth_dev_rx_intr_enable()
validates only the queue id and the op pointer, never intr_conf.rxq, so
the driver is the sole guard.
tap.ini already advertises "Rx interrupt = Y", so no features-matrix
change is needed -- that overclaim is precisely what this patch fixes.
Info: pmd->intr_mode duplicates dev->data->dev_conf.intr_conf.rxq, which
tap_rx_intr_vec_install() already reads directly. The rx_queue copy earns
its place on the fast path; the pmd_internals copy is redundant state
that can drift. Not blocking.
Error (pre-existing, but patch 2 makes it reachable)
---------------------------------------------------
tap_tx_queue_release() dereferences dev->data->rx_queues after ethdev has
already freed it:
/* lib/ethdev/rte_ethdev.c */
reset_queues:
eth_dev_rx_queue_config(dev, 0); /* rte_free(rx_queues); = NULL */
eth_dev_tx_queue_config(dev, 0); /* -> tap_tx_queue_release() */
/* drivers/net/tap/rte_eth_tap.c */
if (dev->data->rx_queues[qid] == NULL) /* rx_queues is NULL here */
Confirmed by three independent reproducers, all segfaulting in
tap_tx_queue_release():
1. reconfigure that trips patch 2's new intr_mode check
2. reconfigure that trips the pre-existing nb_rx != nb_tx check
(intr_conf.rxq never set -- so this is NOT introduced by the series)
3. rte_eth_dev_internal_reset() on a normally configured port, with no
dev_configure failure at all
Path 3 matters independently of this series: bonding's member_remove()
calls rte_eth_dev_internal_reset() unconditionally, so removing a tap
port from a bonding device crashes on current main.
Both ethdev call sites free rx before tx, so tap_rx_queue_release()'s
symmetric read of dev->data->tx_queues[qid] is always safe. Only the tx
side needs the guard, though guarding both is cheap.
This is not your bug. But patch 2 adds a second failure return to
tap_dev_configure() that fires on an ordinary reconfigure, and the commit
message points users straight at it -- an application trying to toggle
interrupt mode crashes instead of receiving -ENOTSUP. Suggest a
preceding patch in the series fixing the deref, with its own Fixes: and
Cc: stable@dpdk.org.
Patch 2/3: net/tap: drain queue FD in Rx interrupt mode
-------------------------------------------------------
The mechanism works as designed. Verified at the kernel fd-flag level
(/proc/PID/fdinfo) that in interrupt mode the data queue fd is created
without O_ASYNC while the keep-alive fd retains it:
patch 1 only : ka fd O_ASYNC=yes, data fd O_ASYNC=yes
full series : ka fd O_ASYNC=yes, data fd O_ASYNC=no
The early return in tun_alloc() sits after the O_NONBLOCK fcntl, so
nothing required is skipped and there is no fd leak. Reading dev_private
is safe: rte_eth_dev_release_port() frees it on close, so "close and
reopen the port to change it" is accurate.
Warning: I could not reproduce the stall this patch fixes. With patch 1
applied alone -- SIGIO still armed on the data fd -- three traffic
patterns all delivered every packet with zero empty bursts after a
wakeup:
- low-rate broadcast ARP : 117/117 rx, 0 empty after wake
- flood ping, static neighbour : 437/437 rx, 0 empty after wake
- strict l3fwd-power-style loop
(one burst per wakeup, no
re-poll) with bursty traffic : 100/100 rx, 0 empty after wake
The full series produced identical numbers in each case. Single queue,
single core, kernel-side tap traffic, so this does not disprove the race
-- it may need multiple queues, higher rates than the tap path gives, or
signal-queue pressure. But the justification is not demonstrated by any
reproducer I could build, and the patch has real cost: a permanent
restriction that interrupt mode cannot be toggled after configure, a new
error path that exposes the crash above, a fast-path branch, and a
readv() syscall on every idle burst (exactly what the SIGIO trigger
exists to avoid).
Could you share a reproducer, or describe the configuration where the
stall appears? Note that the Rx interrupt epoll registration is
edge-triggered (EPOLLIN|EPOLLPRI|EPOLLET in rte_intr_rx_ctl), which is
what would turn a single missed read into a permanent stall rather than
a spin -- that detail seems worth stating explicitly in the commit
message, since it is the part that makes the failure non-self-healing.
For what it is worth, the EAL side is safe: tap sets
RTE_INTR_HANDLE_EXT, so eal_intr_proc_rxtx_intr() returns early and never
consumes packet bytes from the queue fd.
Patch 3/3: net/tap: use bool for boolean flags
----------------------------------------------
Warning: the conversion is incomplete on its own terms.
- tap_flow.c:1247 still reads "if (pmd->flow_init == 0 && ...)";
on a bool this should be "!pmd->flow_init"
- the three converted fields keep "/* 1 if ... */" comments
- the local "int persist" in rte_pmd_tap_probe() and the "int persist"
parameter of pmd_init_internals() were not converted
Info: rte_eth_tap.h uses bool without including <stdbool.h>, relying on a
transitive include via ethdev_driver.h. It compiles; a direct include
would make the header self-contained.
I initially flagged the (set ^ pmd->flow_isolate) idiom in
tap_flow_isolate() as losing idempotency for non-0/1 values under the
bool conversion. There is a "if (set) set = 1;" normalization immediately
above it, so the XOR is correct. Dropped.
Summary
-------
Patch 1 is good and independently verified. Patch 3 needs the leftovers
cleaned up. Patch 2 is mechanically sound but its justification is
unreproduced here, and it should not go in ahead of a fix for the
tap_tx_queue_release() NULL dereference it makes easy to hit.
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-26 17:27 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 9:58 [PATCH 0/2] net/tap: fix Rx queue interrupt support Maxime Leroy
2026-07-17 9:58 ` [PATCH 1/2] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
2026-07-17 9:58 ` [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
2026-07-17 12:31 ` Stephen Hemminger
2026-07-20 8:33 ` Maxime Leroy
2026-07-20 11:16 ` Stephen Hemminger
2026-07-20 12:06 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 1/3] net/tap: support Rx queue interrupt enable/disable Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 2/3] net/tap: drain queue FD in Rx interrupt mode Maxime Leroy
2026-07-20 12:06 ` [PATCH v2 3/3] net/tap: use bool for boolean flags Maxime Leroy
2026-07-26 17:27 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Stephen Hemminger
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.