* [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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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 2026-07-27 12:48 ` Maxime Leroy 3 siblings, 1 reply; 12+ 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] 12+ messages in thread
* Re: [PATCH v2 0/3] net/tap: fix Rx queue interrupt support 2026-07-26 17:27 ` [PATCH v2 0/3] net/tap: fix Rx queue interrupt support Stephen Hemminger @ 2026-07-27 12:48 ` Maxime Leroy 0 siblings, 0 replies; 12+ messages in thread From: Maxime Leroy @ 2026-07-27 12:48 UTC (permalink / raw) To: Stephen Hemminger; +Cc: dev On Sun, Jul 26, 2026 at 7:27 PM Stephen Hemminger <stephen@networkplumber.org> wrote: > > 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. > It is not a copy of the current value but the one locked by the first configure. rte_eth_dev_configure() has already written the new dev_conf by the time tap_dev_configure() runs, so comparing against dev->data->dev_conf.intr_conf.rxq would compare the new value with itself and the mode change could never be detected. > > 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. Agreed. I will fix it in the next version. > > 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. The drain patch, a reproducer https://github.com/DPDK/grout/actions/runs/30259855532/job/89956997416 grout has an opt-in mode where an idle dataplane worker stops busy polling and blocks on its Rx queue interrupts through rte_eth_dev_rx_intr_enable() and rte_epoll_wait(). Its smoke suite runs on net_tap ports, so that mode exercises this path in CI. At sha b2f775810a6a the vendored DPDK carries the intr enable/disable patch but not the drain one, which isolates it, and build-and-tests (x86_64, gcc-14, DPDK 25.11, FRR 10.5, adaptive-irq) fails on smoke/bgp_frr_test.sh: timeout: icmp_seq=1 [tx p0] ICMP echo request id=28908 seq=2 [rx p0] ICMP echo reply id=28908 seq=1 [rx p0] ICMP echo reply id=28908 seq=2 reply from 16.0.0.1: icmp_seq=2 ttl=64 time=0.083 ms error: command failed: Connection timed out (ETIMEDOUT) The reply to seq=1 sits readable in the queue fd. The burst that ran after the epoll wakeup returned 0, and being edge triggered there is no second wakeup for data already queued, so it comes out only when the request for seq=2 creates a new edge: both replies at once, and the first ping times out. The previous revision that morning, 08f1ccd941bb, carries both patches and the same job passes. What the trace establishes is that pmd_rx_burst() ran and returned 0 with the fd readable, so tap_trigger had not advanced at that point. It can lag because the trigger is process wide and the fd owner is the process: fcntl(fd, F_SETOWN, getpid()); The signal is therefore handled by some thread of the kernel's choosing, with no relation to the lcore that epoll woke for that queue, while the wakeup goes precisely to that lcore. A busy polling loop does not care, which is why the trigger has been fine since 2018: it re-reads the counter on the next iteration, so a late signal costs only latency. Interrupt mode bursts once per edge, so it is never caught up. On the cost: the mode restriction is there because tap_setup_queue() reuses queue fds across a reconfigure, so without it a port could mix fds with and without O_ASYNC. And the crash that made the new error path dangerous is fixed by the queue release patch, independently of this one. > > 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. Agreed. I will fix it in the next version. > > > 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. ------------------------------- Maxime Leroy maxime@leroys.fr ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-07-27 12:49 UTC | newest] Thread overview: 12+ 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 2026-07-27 12:48 ` Maxime Leroy
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox