* [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts
@ 2026-06-11 15:49 Maxime Leroy
2026-06-11 15:49 ` [PATCH 1/9] net/dpaa2: implement RSS RETA query and update Maxime Leroy
` (9 more replies)
0 siblings, 10 replies; 51+ messages in thread
From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw)
To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy
This series lets a dpaa2 worker sleep on a queue's data-availability
notification instead of busy-polling, exposed through the generic
rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming,
arm the interrupt and sleep when the queue runs dry).
Why it is not a trivial .rx_queue_intr_enable
----------------------------------------------
A worker wakes on its software portal's DQRI, which fires when the
portal's DQRR holds frames. The default dpaa2 Rx burst pulls frames
from the FQ with a volatile dequeue and cannot be interrupt-driven; to
wake on the DQRI the FQ must instead be pushed to the portal's DQRR.
The natural dpni_set_queue with a notification destination would have to
target the worker's portal, but that portal is only known once a worker
affines, after dev_start, and that MC command holds the global MC lock
long enough to wedge the firmware while traffic runs. So the bind cannot
be done late, against the polling lcore.
Design
------
Each Rx FQ is bound to its own DPCON channel, statically, at dev_start
while the dpni is still disabled (no knowledge of the polling lcore). A
worker later subscribes its own ethrx portal to the channel and arms the
DQRI in rx_queue_intr_enable, a one-shot per-portal op, never the wedging
set_queue. One portal serves every queue a worker owns, so the DQRR
burst demuxes frames to their FQ by fqd_ctx; foreign frames are parked in
the target queue's stash, so the application polls all its queues after a
wakeup, the same scheduling contract as plain DPDK polling. A queue can
be re-homed to another lcore at runtime with no set_queue and no port
stop.
This reuses the event PMD's pushed/DQRR model but with one DPCON per FQ
and static affinity (no QBMan scheduling), so the DPCON allocator is
moved from the event driver to the fslmc bus and shared.
Patches 3 to 6 build the interrupt support proper, on top of three bug
fixes the path depends on and which it uncovered: patch 2 (eal, the
shared portal eventfd must not fail with -EEXIST), patch 7 (rx_queue_count
NULL on the primary process) and patch 8 (fast-path ops NULL after port
stop). They are real fixes, tagged for stable and backportable on their
own. Patches 1 (RSS RETA) and 9 (drop the software VLAN strip) are
independent net/dpaa2 changes the interrupt path does not require.
Tested on LX2160A (lx2160acex7).
Maxime Leroy (9):
net/dpaa2: implement RSS RETA query and update
eal/interrupts: keep real errno on epoll error
bus/fslmc: move DPCON management from event driver to bus
bus/fslmc/dpio: make the portal DQRI epoll optional
net/dpaa2: support Rx queue interrupts
bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff
net/dpaa2: fix Rx queue count for primary process
ethdev: keep fast-path ops valid after port stop
net/dpaa2: drop the fake software VLAN strip offload
doc/guides/nics/dpaa2.rst | 10 +
doc/guides/nics/features/dpaa2.ini | 2 +
doc/guides/rel_notes/release_26_07.rst | 8 +
drivers/bus/fslmc/meson.build | 1 +
.../fslmc/portal}/dpaa2_hw_dpcon.c | 16 +-
drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 113 +++-
drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 +
drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 35 +-
.../fslmc/qbman/include/fsl_qbman_portal.h | 9 +
drivers/bus/fslmc/qbman/qbman_portal.c | 7 +
drivers/event/dpaa2/dpaa2_eventdev.h | 5 +-
drivers/event/dpaa2/meson.build | 1 -
drivers/net/dpaa2/base/dpaa2_hw_dpni.c | 34 +-
drivers/net/dpaa2/dpaa2_ethdev.c | 556 +++++++++++++++++-
drivers/net/dpaa2/dpaa2_ethdev.h | 19 +
drivers/net/dpaa2/dpaa2_rxtx.c | 123 +++-
lib/eal/include/rte_epoll.h | 3 +-
lib/eal/linux/eal_interrupts.c | 18 +-
lib/ethdev/ethdev_private.c | 7 +
19 files changed, 908 insertions(+), 71 deletions(-)
rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (90%)
--
2.43.0
^ permalink raw reply [flat|nested] 51+ messages in thread* [PATCH 1/9] net/dpaa2: implement RSS RETA query and update 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 15:49 ` [PATCH 2/9] eal/interrupts: keep real errno on epoll error Maxime Leroy ` (8 subsequent siblings) 9 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy DPAA2 dispatches RX frames to FQs using 'queue_id = hash % dist_size', where dist_size is set per-TC via the dpni_set_rx_hash_dist MC command. There is no software-visible indirection table, so the standard DPDK RETA API has never been exposed by this PMD. Implement reta_update / reta_query as an emulation on top of dpni_set_rx_hash_dist. The emulation accepts only the uniform pattern 'reta[i] = i % N' for some N in the HW-allowed set (1, 2, 3, 4, 6, 7, 8, 12, 14, 16, 24, ...). Non-uniform or weighted patterns are rejected with -ENOTSUP, as the HW has no arbitrary indirection table. Changing N sets the size of the contiguous queue subset that RSS spreads traffic over; the queues above N are left out of the hash distribution. This covers the patterns that matter here, e.g. growing or shrinking the active subset to scale CPU cores with load, or reserving the upper queues for specific traffic that rte_flow steers there for dedicated polling or QoS handling on its own core. Refactor the existing dpaa2_setup_flow_dist() to delegate to a new helper dpaa2_setup_flow_dist_size() that takes the dist_size explicitly and caches it in priv->dist_size_cur[tc] so reta_query() can report it. reta_query() returns reta[i] = i % N: this is representative, not bit-exact, as the HW maps the hash to a queue through its distribution size encoding rather than a plain modulo. reta_update() takes the RSS hash set from dev_conf (rx_adv_conf.rss_conf.rss_hf); a prior rss_hash_update() with a different hf is not re-read. The advertised reta_size is 64 (one rte_eth_rss_reta_entry64 group), the smallest legal value and enough for all HW-permitted N values up to 64. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/nics/features/dpaa2.ini | 1 + doc/guides/rel_notes/release_26_07.rst | 4 + drivers/net/dpaa2/base/dpaa2_hw_dpni.c | 34 ++-- drivers/net/dpaa2/dpaa2_ethdev.c | 205 +++++++++++++++++++++++++ drivers/net/dpaa2/dpaa2_ethdev.h | 9 ++ 5 files changed, 244 insertions(+), 9 deletions(-) diff --git a/doc/guides/nics/features/dpaa2.ini b/doc/guides/nics/features/dpaa2.ini index 5f9c587847..5def653d1d 100644 --- a/doc/guides/nics/features/dpaa2.ini +++ b/doc/guides/nics/features/dpaa2.ini @@ -15,6 +15,7 @@ Promiscuous mode = Y Allmulticast mode = Y Unicast MAC filter = Y RSS hash = Y +RSS reta update = Y VLAN filter = Y Flow control = Y Traffic manager = Y diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index b5285af5fe..103c4034ca 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -126,6 +126,10 @@ New Features * Added support for selective Rx in scalar SPRQ Rx path. +* **Updated NXP dpaa2 driver.** + + * Added RSS RETA query and update support. + * **Updated PCAP ethernet driver.** * Added support for VLAN insertion and stripping. diff --git a/drivers/net/dpaa2/base/dpaa2_hw_dpni.c b/drivers/net/dpaa2/base/dpaa2_hw_dpni.c index 13825046d8..4cbc890cee 100644 --- a/drivers/net/dpaa2/base/dpaa2_hw_dpni.c +++ b/drivers/net/dpaa2/base/dpaa2_hw_dpni.c @@ -103,15 +103,10 @@ dpaa2_setup_flow_dist(struct rte_eth_dev *eth_dev, uint64_t req_dist_set, int tc_index) { struct dpaa2_dev_priv *priv = eth_dev->data->dev_private; - struct fsl_mc_io *dpni = eth_dev->process_private; - struct dpni_rx_dist_cfg tc_cfg; - struct dpkg_profile_cfg kg_cfg; - void *p_params; - int ret, tc_dist_queues; + int tc_dist_queues; - /*TC distribution size is set with dist_queues or - * nb_rx_queues % dist_queues in order of TC priority index. - * Calculating dist size for this tc_index:- + /* TC distribution size is set with dist_queues or + * (nb_rx_queues - tc_index*dist_queues) in order of TC priority index. */ tc_dist_queues = eth_dev->data->nb_rx_queues - tc_index * priv->dist_queues; @@ -123,6 +118,24 @@ dpaa2_setup_flow_dist(struct rte_eth_dev *eth_dev, if (tc_dist_queues > priv->dist_queues) tc_dist_queues = priv->dist_queues; + return dpaa2_setup_flow_dist_size(eth_dev, req_dist_set, + tc_index, tc_dist_queues); +} + +int +dpaa2_setup_flow_dist_size(struct rte_eth_dev *eth_dev, + uint64_t req_dist_set, int tc_index, uint16_t dist_size) +{ + struct dpaa2_dev_priv *priv = eth_dev->data->dev_private; + struct fsl_mc_io *dpni = eth_dev->process_private; + struct dpni_rx_dist_cfg tc_cfg; + struct dpkg_profile_cfg kg_cfg; + void *p_params; + int ret; + + if (dist_size == 0) + return 0; + p_params = rte_malloc(NULL, DIST_PARAM_IOVA_SIZE, RTE_CACHE_LINE_SIZE); if (!p_params) { @@ -150,7 +163,7 @@ dpaa2_setup_flow_dist(struct rte_eth_dev *eth_dev, return -ENOBUFS; } - tc_cfg.dist_size = tc_dist_queues; + tc_cfg.dist_size = dist_size; tc_cfg.enable = true; tc_cfg.tc = tc_index; @@ -168,6 +181,9 @@ dpaa2_setup_flow_dist(struct rte_eth_dev *eth_dev, return ret; } + if (tc_index < MAX_TCS) + priv->dist_size_cur[tc_index] = dist_size; + return 0; } diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 803a8321e0..8589398324 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -80,6 +80,33 @@ bool dpaa2_print_parser_result; #define MAX_NB_RX_DESC_IN_PEB 11264 static int total_nb_rx_desc; +/* Size of the RETA (Redirection Table) we expose to the standard DPDK API. + * Must be a multiple of RTE_ETH_RETA_GROUP_SIZE (64). DPAA2 has no actual + * indirection table in HW; this is the granularity at which uniform RSS + * patterns are inspected by dpaa2_dev_rss_reta_update(). + */ +#define DPAA2_RETA_SIZE 64 + +/* Values of dist_size accepted by the DPNI 'dpni_set_rx_hash_dist' MC command. + * Source: fsl_dpni.h, "struct dpni_rx_dist_cfg::dist_size" documentation. + * Used by dpaa2_dev_rss_reta_update() to validate user-requested patterns. + */ +static const uint16_t dpaa2_dist_size_allowed[] = { + 1, 2, 3, 4, 6, 7, 8, 12, 14, 16, 24, 28, 32, 48, 56, 64, + 96, 112, 128, 192, 224, 256, 384, 448, 512, 768, 896, 1024, +}; + +static bool +dpaa2_dist_size_is_supported(uint16_t n) +{ + size_t i; + for (i = 0; i < RTE_DIM(dpaa2_dist_size_allowed); i++) { + if (dpaa2_dist_size_allowed[i] == n) + return true; + } + return false; +} + int dpaa2_valid_dev; struct rte_mempool *dpaa2_tx_sg_pool; @@ -425,6 +452,14 @@ dpaa2_dev_info_get(struct rte_eth_dev *dev, dev_info->max_vfs = 0; dev_info->max_vmdq_pools = RTE_ETH_16_POOLS; dev_info->flow_type_rss_offloads = DPAA2_RSS_OFFLOAD_ALL; + /* DPAA2 has no software-visible indirection table: incoming packets are + * dispatched to FQs via 'queue_id = hash % dist_size'. We expose the + * standard RETA API as an emulation that only accepts uniform patterns + * 'reta[i] = i % N' and translates them into a dpni_set_rx_hash_dist + * command with dist_size=N. See dpaa2_dev_rss_reta_update(). + */ + dev_info->reta_size = DPAA2_RETA_SIZE; + dev_info->hash_key_size = 0; dev_info->default_rxportconf.burst_size = dpaa2_dqrr_size; /* same is rx size for best perf */ @@ -2508,6 +2543,174 @@ dpaa2_dev_rss_hash_conf_get(struct rte_eth_dev *dev, return 0; } +/* Emulation of the standard DPDK RETA API on top of DPAA2's + * dpni_set_rx_hash_dist MC command. + * + * DPAA2 hardware dispatches incoming frames using 'queue_id = hash % dist_size' + * (no software-visible indirection table). To expose the standard + * rte_eth_dev_rss_reta_update() interface, we accept ONLY uniform patterns of + * the form 'reta[i] = i % N' where N is in the HW-allowed dist_size list. Any + * other pattern (weighted RSS, non-contiguous queue IDs, gaps) is rejected + * with -ENOTSUP. This is enough to support dynamic RSS scale-up/down across + * a contiguous queue subset, which is the main use case for adaptive + * dataplane CPU usage. + * + * Applies the new dist_size on every configured RX TC, mirroring the + * behavior of dpaa2_dev_rss_hash_update(). + */ +static int +dpaa2_dev_rss_reta_update(struct rte_eth_dev *dev, + struct rte_eth_rss_reta_entry64 *reta_conf, + uint16_t reta_size) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct rte_eth_conf *eth_conf = &dev->data->dev_conf; + uint16_t i, max_q = 0, n; + int tc_index, ret; + bool any_set = false; + + PMD_INIT_FUNC_TRACE(); + + if (reta_size != DPAA2_RETA_SIZE) { + DPAA2_PMD_ERR("Invalid reta_size %u (expected %u)", + reta_size, DPAA2_RETA_SIZE); + return -EINVAL; + } + + /* dpaa2 cannot merge a partial RETA into the live table, so only a + * full update (every entry of every group) is accepted. + */ + for (i = 0; i < reta_size / RTE_ETH_RETA_GROUP_SIZE; i++) { + if (reta_conf[i].mask != UINT64_MAX) { + DPAA2_PMD_ERR("partial RETA update not supported; set all %u entries", + DPAA2_RETA_SIZE); + return -ENOTSUP; + } + } + + /* First pass: validate queue IDs, find max, and require at least + * one slot to be selected via the per-group mask. + */ + for (i = 0; i < reta_size; i++) { + uint16_t grp = i / RTE_ETH_RETA_GROUP_SIZE; + uint16_t pos = i % RTE_ETH_RETA_GROUP_SIZE; + uint16_t q; + + if (!(reta_conf[grp].mask & (1ULL << pos))) + continue; + any_set = true; + + q = reta_conf[grp].reta[pos]; + if (q >= dev->data->nb_rx_queues) { + DPAA2_PMD_ERR( + "reta[%u] = %u out of range (max %u)", + i, q, dev->data->nb_rx_queues - 1); + return -EINVAL; + } + if (q > max_q) + max_q = q; + } + + if (!any_set) { + DPAA2_PMD_WARN("reta_update called with empty mask, no-op"); + return 0; + } + + n = max_q + 1; + + /* Second pass: enforce the uniform pattern reta[i] = i % n on every + * slot the user has selected. dpaa2 HW cannot honor any other layout. + */ + for (i = 0; i < reta_size; i++) { + uint16_t grp = i / RTE_ETH_RETA_GROUP_SIZE; + uint16_t pos = i % RTE_ETH_RETA_GROUP_SIZE; + uint16_t expected = i % n; + uint16_t q; + + if (!(reta_conf[grp].mask & (1ULL << pos))) + continue; + + q = reta_conf[grp].reta[pos]; + if (q != expected) { + DPAA2_PMD_ERR( + "Non-uniform RETA pattern at slot %u " + "(got queue %u, expected %u). dpaa2 HW " + "only supports queue_id = hash mod N with " + "contiguous queues 0..N-1.", + i, q, expected); + return -ENOTSUP; + } + } + + if (!dpaa2_dist_size_is_supported(n)) { + DPAA2_PMD_ERR( + "dist_size %u not supported by HW. Allowed: " + "1,2,3,4,6,7,8,12,14,16,24,28,32,48,56,64,...", + n); + return -ENOTSUP; + } + + /* Apply on every configured RX TC, matching rss_hash_update behavior. */ + for (tc_index = 0; tc_index < priv->num_rx_tc; tc_index++) { + ret = dpaa2_setup_flow_dist_size(dev, + eth_conf->rx_adv_conf.rss_conf.rss_hf, + tc_index, n); + if (ret) { + DPAA2_PMD_ERR( + "Failed to apply dist_size=%u on tc%d (err=%d)", + n, tc_index, ret); + return ret; + } + } + + DPAA2_PMD_DEBUG("RETA updated: dist_size now %u on %u TC(s)", + n, priv->num_rx_tc); + return 0; +} + +/* Synthesizes a RETA snapshot from the currently-active dist_size on TC 0. + * Since DPAA2 always uses uniform 'hash mod N' distribution, the returned + * RETA is reta[i] = i % dist_size_cur[0]. + */ +static int +dpaa2_dev_rss_reta_query(struct rte_eth_dev *dev, + struct rte_eth_rss_reta_entry64 *reta_conf, + uint16_t reta_size) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + uint16_t i, n; + + PMD_INIT_FUNC_TRACE(); + + if (reta_size != DPAA2_RETA_SIZE) { + DPAA2_PMD_ERR("Invalid reta_size %u (expected %u)", + reta_size, DPAA2_RETA_SIZE); + return -EINVAL; + } + + /* Use the cached dist_size on TC 0 (representative). Fall back to the + * default (nb_rx_queues clamped to dist_queues) when never programmed. + */ + n = priv->dist_size_cur[0]; + if (n == 0) { + n = priv->dist_queues; + if (n > dev->data->nb_rx_queues) + n = dev->data->nb_rx_queues; + } + if (n == 0) + return -EINVAL; + + for (i = 0; i < reta_size; i++) { + uint16_t grp = i / RTE_ETH_RETA_GROUP_SIZE; + uint16_t pos = i % RTE_ETH_RETA_GROUP_SIZE; + + if (reta_conf[grp].mask & (1ULL << pos)) + reta_conf[grp].reta[pos] = i % n; + } + + return 0; +} + RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_eth_eventq_attach) int dpaa2_eth_eventq_attach(const struct rte_eth_dev *dev, int eth_rx_queue_id, @@ -2736,6 +2939,8 @@ static struct eth_dev_ops dpaa2_ethdev_ops = { .mac_addr_set = dpaa2_dev_set_mac_addr, .rss_hash_update = dpaa2_dev_rss_hash_update, .rss_hash_conf_get = dpaa2_dev_rss_hash_conf_get, + .reta_update = dpaa2_dev_rss_reta_update, + .reta_query = dpaa2_dev_rss_reta_query, .flow_ops_get = dpaa2_dev_flow_ops_get, .rxq_info_get = dpaa2_rxq_info_get, .txq_info_get = dpaa2_txq_info_get, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.h b/drivers/net/dpaa2/dpaa2_ethdev.h index 4da47a543a..3f224c654e 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.h +++ b/drivers/net/dpaa2/dpaa2_ethdev.h @@ -412,6 +412,12 @@ struct dpaa2_dev_priv { uint8_t max_cgs; uint8_t cgid_in_use[MAX_RX_QUEUES]; + /* Current hash distribution size per RX TC, written by + * dpaa2_setup_flow_dist_size() and read by reta_query / reta_update. + * Zero means "use default" (= nb_rx_queues clamped to dist_queues). + */ + uint16_t dist_size_cur[MAX_TCS]; + uint16_t dpni_ver_major; uint16_t dpni_ver_minor; uint32_t speed_capa; @@ -468,6 +474,9 @@ int dpaa2_distset_to_dpkg_profile_cfg(uint64_t req_dist_set, int dpaa2_setup_flow_dist(struct rte_eth_dev *eth_dev, uint64_t req_dist_set, int tc_index); +int dpaa2_setup_flow_dist_size(struct rte_eth_dev *eth_dev, + uint64_t req_dist_set, int tc_index, uint16_t dist_size); + int dpaa2_remove_flow_dist(struct rte_eth_dev *eth_dev, uint8_t tc_index); -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 2/9] eal/interrupts: keep real errno on epoll error 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 2026-06-11 15:49 ` [PATCH 1/9] net/dpaa2: implement RSS RETA query and update Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-16 8:02 ` David Marchand 2026-06-11 15:49 ` [PATCH 3/9] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy ` (7 subsequent siblings) 9 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena Cc: dev, Maxime Leroy, stable, Harman Kalra, Cunming Liang Some interrupt users have several vectors backed by the same eventfd (e.g. several Rx queues behind one DPAA2 portal eventfd). Adding the second vector to the same epoll instance then fails with EEXIST. Upper layers such as ethdev and bbdev already treat -EEXIST as a non-fatal duplicate registration (if (ret && ret != -EEXIST)), but rte_intr_rx_ctl() lost that information: rte_epoll_ctl() returned -1 and rte_intr_rx_ctl() flattened every failure to -EPERM. Return the negative errno from rte_epoll_ctl() (its documented contract is already "a negative value") and stop rte_intr_rx_ctl() from flattening errors to -EPERM, so EEXIST reaches the upper layers that already handle it; other failures carry their real errno. Fixes: 9efe9c6cdcac ("eal/linux: add epoll wrappers") Fixes: c9f3ec1a0f3f ("eal/linux: add Rx interrupt control function") Cc: stable@dpdk.org Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- lib/eal/include/rte_epoll.h | 3 ++- lib/eal/linux/eal_interrupts.c | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/eal/include/rte_epoll.h b/lib/eal/include/rte_epoll.h index ae0cf20853..0c7b510563 100644 --- a/lib/eal/include/rte_epoll.h +++ b/lib/eal/include/rte_epoll.h @@ -104,7 +104,8 @@ rte_epoll_wait_interruptible(int epfd, struct rte_epoll_event *events, * Note: The caller must take care the object deletion after CTL_DEL. * @return * - On success, zero. - * - On failure, a negative value. + * - On failure, a negative errno value, e.g. -EEXIST if the fd is already + * registered on the epoll instance (a fd shared between vectors). */ int rte_epoll_ctl(int epfd, int op, int fd, diff --git a/lib/eal/linux/eal_interrupts.c b/lib/eal/linux/eal_interrupts.c index 5d0607effe..4cfaeba7fe 100644 --- a/lib/eal/linux/eal_interrupts.c +++ b/lib/eal/linux/eal_interrupts.c @@ -1443,7 +1443,7 @@ rte_epoll_ctl(int epfd, int op, int fd, if (!event) { EAL_LOG(ERR, "rte_epoll_event can't be NULL"); - return -1; + return -EINVAL; } /* using per thread epoll fd */ @@ -1460,13 +1460,21 @@ rte_epoll_ctl(int epfd, int op, int fd, ev.events = event->epdata.event; if (epoll_ctl(epfd, op, fd, &ev) < 0) { + int err = errno; + + /* the fd is already in the set (e.g. shared across vectors): + * keep the event valid and report -EEXIST, not a hard error. + */ + if (op == EPOLL_CTL_ADD && err == EEXIST) + return -EEXIST; + EAL_LOG(ERR, "Error op %d fd %d epoll_ctl, %s", - op, fd, strerror(errno)); + op, fd, strerror(err)); if (op == EPOLL_CTL_ADD) /* rollback status when CTL_ADD fail */ rte_atomic_store_explicit(&event->status, RTE_EPOLL_INVALID, rte_memory_order_relaxed); - return -1; + return -err; } if (op == EPOLL_CTL_DEL && rte_atomic_load_explicit(&event->status, @@ -1518,8 +1526,6 @@ rte_intr_rx_ctl(struct rte_intr_handle *intr_handle, int epfd, EAL_LOG(DEBUG, "efd %d associated with vec %d added on epfd %d", rev->fd, vec, epfd); - else - rc = -EPERM; break; case RTE_INTR_EVENT_DEL: epfd_op = EPOLL_CTL_DEL; @@ -1531,8 +1537,6 @@ rte_intr_rx_ctl(struct rte_intr_handle *intr_handle, int epfd, } rc = rte_epoll_ctl(rev->epfd, epfd_op, rev->fd, rev); - if (rc) - rc = -EPERM; break; default: EAL_LOG(ERR, "event op type mismatch"); -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH 2/9] eal/interrupts: keep real errno on epoll error 2026-06-11 15:49 ` [PATCH 2/9] eal/interrupts: keep real errno on epoll error Maxime Leroy @ 2026-06-16 8:02 ` David Marchand 2026-06-16 9:29 ` David Marchand 0 siblings, 1 reply; 51+ messages in thread From: David Marchand @ 2026-06-16 8:02 UTC (permalink / raw) To: Maxime Leroy Cc: hemant.agrawal, sachin.saxena, dev, stable, Harman Kalra, Cunming Liang On Thu, 11 Jun 2026 at 17:50, Maxime Leroy <maxime@leroys.fr> wrote: > > Some interrupt users have several vectors backed by the same eventfd > (e.g. several Rx queues behind one DPAA2 portal eventfd). Adding the > second vector to the same epoll instance then fails with EEXIST. > > Upper layers such as ethdev and bbdev already treat -EEXIST as a > non-fatal duplicate registration (if (ret && ret != -EEXIST)), but > rte_intr_rx_ctl() lost that information: rte_epoll_ctl() returned -1 and > rte_intr_rx_ctl() flattened every failure to -EPERM. > > Return the negative errno from rte_epoll_ctl() (its documented contract > is already "a negative value") and stop rte_intr_rx_ctl() from > flattening errors to -EPERM, so EEXIST reaches the upper layers that > already handle it; other failures carry their real errno. > > Fixes: 9efe9c6cdcac ("eal/linux: add epoll wrappers") > Fixes: c9f3ec1a0f3f ("eal/linux: add Rx interrupt control function") > Cc: stable@dpdk.org > Signed-off-by: Maxime Leroy <maxime@leroys.fr> Reviewed-by: David Marchand <david.marchand@redhat.com> Nit: the eal/ prefix is only for OS specific / arch specific changes. The title prefix should be interrupt: -- David Marchand ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 2/9] eal/interrupts: keep real errno on epoll error 2026-06-16 8:02 ` David Marchand @ 2026-06-16 9:29 ` David Marchand 0 siblings, 0 replies; 51+ messages in thread From: David Marchand @ 2026-06-16 9:29 UTC (permalink / raw) To: Maxime Leroy Cc: hemant.agrawal, sachin.saxena, dev, stable, Harman Kalra, Cunming Liang, Stephen Hemminger, Thomas Monjalon On Tue, 16 Jun 2026 at 10:02, David Marchand <david.marchand@redhat.com> wrote: > > On Thu, 11 Jun 2026 at 17:50, Maxime Leroy <maxime@leroys.fr> wrote: > > > > Some interrupt users have several vectors backed by the same eventfd > > (e.g. several Rx queues behind one DPAA2 portal eventfd). Adding the > > second vector to the same epoll instance then fails with EEXIST. > > > > Upper layers such as ethdev and bbdev already treat -EEXIST as a > > non-fatal duplicate registration (if (ret && ret != -EEXIST)), but > > rte_intr_rx_ctl() lost that information: rte_epoll_ctl() returned -1 and > > rte_intr_rx_ctl() flattened every failure to -EPERM. > > > > Return the negative errno from rte_epoll_ctl() (its documented contract > > is already "a negative value") and stop rte_intr_rx_ctl() from > > flattening errors to -EPERM, so EEXIST reaches the upper layers that > > already handle it; other failures carry their real errno. > > > > Fixes: 9efe9c6cdcac ("eal/linux: add epoll wrappers") > > Fixes: c9f3ec1a0f3f ("eal/linux: add Rx interrupt control function") > > Cc: stable@dpdk.org > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > > Reviewed-by: David Marchand <david.marchand@redhat.com> > > Nit: the eal/ prefix is only for OS specific / arch specific changes. > The title prefix should be interrupt: I took this fix directly in main. Applied, thanks. -- David marchand ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 3/9] bus/fslmc: move DPCON management from event driver to bus 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 2026-06-11 15:49 ` [PATCH 1/9] net/dpaa2: implement RSS RETA query and update Maxime Leroy 2026-06-11 15:49 ` [PATCH 2/9] eal/interrupts: keep real errno on epoll error Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 15:49 ` [PATCH 4/9] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy ` (6 subsequent siblings) 9 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy The DPCON allocation helpers (rte_dpaa2_alloc_dpcon_dev / rte_dpaa2_free_dpcon_dev) lived in the event driver, but a notification channel is a generic QBMan resource. Move dpaa2_hw_dpcon.c to the fslmc bus and export the helpers as internal symbols so both the event PMD and the net driver's rx-queue interrupt path can draw channels from the same pool. No functional change. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/meson.build | 1 + .../dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c | 16 +++++++--------- drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 8 ++++++++ drivers/event/dpaa2/dpaa2_eventdev.h | 5 +++-- drivers/event/dpaa2/meson.build | 1 - 5 files changed, 19 insertions(+), 12 deletions(-) rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (90%) diff --git a/drivers/bus/fslmc/meson.build b/drivers/bus/fslmc/meson.build index ceae1c6c11..50d9e91a37 100644 --- a/drivers/bus/fslmc/meson.build +++ b/drivers/bus/fslmc/meson.build @@ -22,6 +22,7 @@ sources = files( 'mc/mc_sys.c', 'portal/dpaa2_hw_dpbp.c', 'portal/dpaa2_hw_dpci.c', + 'portal/dpaa2_hw_dpcon.c', 'portal/dpaa2_hw_dpio.c', 'portal/dpaa2_hw_dprc.c', 'qbman/qbman_portal.c', diff --git a/drivers/event/dpaa2/dpaa2_hw_dpcon.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c similarity index 90% rename from drivers/event/dpaa2/dpaa2_hw_dpcon.c rename to drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c index ea5b0d4b85..6fd96ec0b9 100644 --- a/drivers/event/dpaa2/dpaa2_hw_dpcon.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c @@ -18,13 +18,12 @@ #include <rte_cycles.h> #include <rte_kvargs.h> #include <dev_driver.h> -#include <ethdev_driver.h> +#include <eal_export.h> #include <bus_fslmc_driver.h> #include <mc/fsl_dpcon.h> #include <portal/dpaa2_hw_pvt.h> -#include "dpaa2_eventdev.h" -#include "dpaa2_eventdev_logs.h" +#include <fslmc_logs.h> TAILQ_HEAD(dpcon_dev_list, dpaa2_dpcon_dev); static struct dpcon_dev_list dpcon_dev_list @@ -55,8 +54,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, /* Allocate DPAA2 dpcon handle */ dpcon_node = rte_malloc(NULL, sizeof(struct dpaa2_dpcon_dev), 0); if (!dpcon_node) { - DPAA2_EVENTDEV_ERR( - "Memory allocation failed for dpcon device"); + DPAA2_BUS_ERR("Memory allocation failed for dpcon device"); return -1; } @@ -65,8 +63,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, ret = dpcon_open(&dpcon_node->dpcon, CMD_PRI_LOW, dpcon_id, &dpcon_node->token); if (ret) { - DPAA2_EVENTDEV_ERR("Unable to open dpcon device: err(%d)", - ret); + DPAA2_BUS_ERR("Unable to open dpcon device: err(%d)", ret); rte_free(dpcon_node); return -1; } @@ -75,8 +72,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, ret = dpcon_get_attributes(&dpcon_node->dpcon, CMD_PRI_LOW, dpcon_node->token, &attr); if (ret != 0) { - DPAA2_EVENTDEV_ERR("dpcon attribute fetch failed: err(%d)", - ret); + DPAA2_BUS_ERR("dpcon attribute fetch failed: err(%d)", ret); rte_free(dpcon_node); return -1; } @@ -92,6 +88,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, return 0; } +RTE_EXPORT_INTERNAL_SYMBOL(rte_dpaa2_alloc_dpcon_dev) struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void) { struct dpaa2_dpcon_dev *dpcon_dev = NULL; @@ -105,6 +102,7 @@ struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void) return dpcon_dev; } +RTE_EXPORT_INTERNAL_SYMBOL(rte_dpaa2_free_dpcon_dev) void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon) { struct dpaa2_dpcon_dev *dpcon_dev = NULL; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h index e625a5c035..79a2ec41e3 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h @@ -274,6 +274,14 @@ struct dpaa2_dpcon_dev { uint8_t channel_index; }; +/* DPCON channel allocation -- managed by the fslmc bus so both the net + * NAPI/DQRR rx path and the event PMD can grab channels. + */ +__rte_internal +struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void); +__rte_internal +void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon); + /* Refer to Table 7-3 in SEC BG */ #define QBMAN_FLE_WORD4_FMT_SBF 0x0 /* Single buffer frame */ #define QBMAN_FLE_WORD4_FMT_SGE 0x2 /* Scatter gather frame */ diff --git a/drivers/event/dpaa2/dpaa2_eventdev.h b/drivers/event/dpaa2/dpaa2_eventdev.h index bb87bdbab2..f53efce61c 100644 --- a/drivers/event/dpaa2/dpaa2_eventdev.h +++ b/drivers/event/dpaa2/dpaa2_eventdev.h @@ -85,8 +85,9 @@ struct dpaa2_eventdev { uint32_t event_dev_cfg; }; -struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void); -void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon); +/* rte_dpaa2_alloc_dpcon_dev()/rte_dpaa2_free_dpcon_dev() now live in the fslmc + * bus (portal/dpaa2_hw_pvt.h), which this header's includers already pull in. + */ int test_eventdev_dpaa2(void); diff --git a/drivers/event/dpaa2/meson.build b/drivers/event/dpaa2/meson.build index dd5063af43..62b8507652 100644 --- a/drivers/event/dpaa2/meson.build +++ b/drivers/event/dpaa2/meson.build @@ -7,7 +7,6 @@ if not is_linux endif deps += ['bus_vdev', 'net_dpaa2', 'crypto_dpaa2_sec'] sources = files( - 'dpaa2_hw_dpcon.c', 'dpaa2_eventdev.c', 'dpaa2_eventdev_selftest.c', ) -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 4/9] bus/fslmc/dpio: make the portal DQRI epoll optional 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (2 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 3/9] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 15:49 ` [PATCH 5/9] net/dpaa2: support Rx queue interrupts Maxime Leroy ` (5 subsequent siblings) 9 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy dpaa2_dpio_intr_init() builds a private epoll instance the event PMD sleeps on. The upcoming net rx-queue-interrupt path waits on the application's own epoll instead, so that instance would be built but never used. Add a build_epoll parameter: pass true to build it (event PMD), false to skip the epoll_create/epoll_ctl. epoll_fd is set to -1 when none is built and closed in intr_deinit only when valid. The sole caller passes true: no functional change. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 44 +++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index 2a9e519668..3a5abb2e6d 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -205,13 +205,12 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } -static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev) +static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) { struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; int threshold = 0x3, timeout = 0xFF; - dpio_epoll_fd = epoll_create(1); ret = rte_dpaa2_intr_enable(dpio_dev->intr_handle, 0); if (ret) { DPAA2_BUS_ERR("Interrupt registration failed"); @@ -231,16 +230,34 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev) qbman_swp_dqrr_thrshld_write(dpio_dev->sw_portal, threshold); qbman_swp_intr_timeout_write(dpio_dev->sw_portal, timeout); - eventfd = rte_intr_fd_get(dpio_dev->intr_handle); - epoll_ev.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_ev.data.fd = eventfd; + dpio_dev->epoll_fd = -1; - ret = epoll_ctl(dpio_epoll_fd, EPOLL_CTL_ADD, eventfd, &epoll_ev); - if (ret < 0) { - DPAA2_BUS_ERR("epoll_ctl failed"); - return -1; + /* The event PMD dequeues by sleeping on a private epoll instance owned + * by the portal, so build it here. A caller that waits on another + * epoll (the net rx-queue-interrupt path uses the application's) skips + * this. + */ + if (build_epoll) { + dpio_epoll_fd = epoll_create(1); + if (dpio_epoll_fd < 0) { + DPAA2_BUS_ERR("epoll_create failed"); + rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); + return -1; + } + + eventfd = rte_intr_fd_get(dpio_dev->intr_handle); + epoll_ev.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_ev.data.fd = eventfd; + + ret = epoll_ctl(dpio_epoll_fd, EPOLL_CTL_ADD, eventfd, &epoll_ev); + if (ret < 0) { + DPAA2_BUS_ERR("epoll_ctl failed"); + rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); + close(dpio_epoll_fd); + return -1; + } + dpio_dev->epoll_fd = dpio_epoll_fd; } - dpio_dev->epoll_fd = dpio_epoll_fd; return 0; } @@ -253,7 +270,10 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) if (ret) DPAA2_BUS_ERR("DPIO interrupt disable failed"); - close(dpio_dev->epoll_fd); + if (dpio_dev->epoll_fd >= 0) { + close(dpio_dev->epoll_fd); + dpio_dev->epoll_fd = -1; + } } #endif @@ -277,7 +297,7 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) } #ifdef RTE_EVENT_DPAA2 - if (dpaa2_dpio_intr_init(dpio_dev)) { + if (dpaa2_dpio_intr_init(dpio_dev, true)) { DPAA2_BUS_ERR("Interrupt registration failed for dpio"); return -1; } -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 5/9] net/dpaa2: support Rx queue interrupts 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (3 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 4/9] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-16 8:05 ` David Marchand 2026-06-11 15:49 ` [PATCH 6/9] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff Maxime Leroy ` (4 subsequent siblings) 9 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy Implement .rx_queue_intr_enable / .rx_queue_intr_disable so a worker can sleep on a queue's data-availability notification instead of busy-polling, through the generic rte_eth_dev_rx_intr_* API. A worker wakes on its software portal's DQRI, which fires when the portal's DQRR holds frames, so the Rx FQ must be scheduled to a channel that portal dequeues. The natural dpni_set_queue with a notification destination holds the global MC lock long enough to wedge the firmware and must target a disabled dpni. But the polling portal is only known once a worker affines, after dev_start, so the destination cannot be the worker's portal. Bind each Rx FQ to its own DPCON channel instead. The default Rx burst pulls frames from the FQ with a volatile dequeue and cannot be interrupt-driven; to wake on the DQRI the FQ must be pushed to the portal's DQRR. dev_start issues the DEST_DPCON set_queue statically on the still-disabled dpni with no knowledge of the polling lcore; a worker later subscribes its own ethrx portal to the channel and arms the DQRI in rx_queue_intr_enable (a one-shot per-portal MC op plus QBMan, never the wedging set_queue). This pushed/DQRR consumption is how the event PMD works, but the DPCON use differs. The event PMD uses one DPCON per worker, concentrates N FQs onto it, and lets the QBMan scheduler load-balance events across cores. Here affinity is static and there is no scheduling, so each FQ gets its own DPCON (one per FQ, more channels, drawn from the shared pool that the DPCON move to the fslmc bus now feeds), bound once at dev_start before the lcore is known. Frames are delivered by rte_eth_rx_burst (dpaa2_dev_rx_dqrr), not as events via rte_event_dequeue. rte_eth_dev_rx_intr_enable(q) subscribes the lcore portal to q's DPCON and arms the DQRI. rte_eth_dev_rx_intr_ctl_q(q) adds q's eventfd (the portal DQRI fd) to the thread epoll. wire | [ DPMAC ] | [ DPNI ] (1) | TC0: FQ0 FQ1 FQ2 FQ3 (2) | | | | (3) [DPCON][DPCON][DPCON][DPCON] \ | | / (4) [ DPIO A ] [ DPIO B ] (5) | | DQRR DQRR (6) | | DQRI DQRI (7) | | eventfd eventfd (8) | | rte_epoll_wait rte_epoll_wait (9) | | dpaa2_dev_rx_dqrr (10) (1) WRIOP picks a TC (QoS), then RSS-hashes within the TC to an FQ (2) FQ0..FQ3 are the rte_eth Rx queues (3) dpni_set_queue(DEST_DPCON): one DPCON per FQ (4) the lcore portal subscribes to its DPCONs (push_set) (5) one QBMan software portal per lcore (6) QMan pushes the FDs into the portal DQRR (7) DQRI is raised when the DQRR is non-empty (8) a portal's queues share one fd (its DQRI eventfd) (9) worker sleeps here when all its queues are idle (10) dpaa2_dev_rx_dqrr drains the DQRR, demuxes FDs to FQs by fqd_ctx The DQRI and eventfd are portal-wide: a queue's eventfd is its portal's DQRI fd, and the inhibit bit is refcounted by armed queues so disabling one queue never masks a sibling. The static per-queue bind also lets a queue be re-homed to another lcore at runtime, the new worker reclaiming the channel, with no set_queue and no port stop. On single-core 64-byte forwarding this interrupt path runs at ~5.0 Mpps versus ~5.86 Mpps polling: per-frame DQRR demux and consume cost about 15 percent over the polling batch dequeue. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/nics/features/dpaa2.ini | 1 + doc/guides/rel_notes/release_26_07.rst | 1 + drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 11 +- drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 4 + drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 27 ++- drivers/bus/fslmc/qbman/qbman_portal.c | 1 + drivers/net/dpaa2/dpaa2_ethdev.c | 293 ++++++++++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 3 + drivers/net/dpaa2/dpaa2_rxtx.c | 122 ++++++++++ 9 files changed, 457 insertions(+), 6 deletions(-) diff --git a/doc/guides/nics/features/dpaa2.ini b/doc/guides/nics/features/dpaa2.ini index 5def653d1d..b53353eb77 100644 --- a/doc/guides/nics/features/dpaa2.ini +++ b/doc/guides/nics/features/dpaa2.ini @@ -7,6 +7,7 @@ Speed capabilities = Y Link status = Y Link status event = Y +Rx interrupt = Y Burst mode info = Y Queue start/stop = Y Scattered Rx = Y diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index 103c4034ca..87c7c57bcc 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -129,6 +129,7 @@ New Features * **Updated NXP dpaa2 driver.** * Added RSS RETA query and update support. + * Added Rx queue interrupt support. * **Updated PCAP ethernet driver.** diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index 3a5abb2e6d..e6b4e74b3b 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -204,13 +204,18 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } +#endif /* RTE_EVENT_DPAA2 */ -static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_init) +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) { struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; int threshold = 0x3, timeout = 0xFF; + if (dpio_dev->intr_enabled) + return 0; + ret = rte_dpaa2_intr_enable(dpio_dev->intr_handle, 0); if (ret) { DPAA2_BUS_ERR("Interrupt registration failed"); @@ -259,9 +264,12 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epol dpio_dev->epoll_fd = dpio_epoll_fd; } + dpio_dev->intr_enabled = 1; + return 0; } +#ifdef RTE_EVENT_DPAA2 static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) { int ret; @@ -274,6 +282,7 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) close(dpio_dev->epoll_fd); dpio_dev->epoll_fd = -1; } + dpio_dev->intr_enabled = 0; } #endif diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h index 328e1e788a..10dd968e5f 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h @@ -50,6 +50,10 @@ int dpaa2_affine_qbman_swp(void); __rte_internal int dpaa2_affine_qbman_ethrx_swp(void); +/* set up a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ +__rte_internal +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll); + /* allocate memory for FQ - dq storage */ __rte_internal int diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h index 79a2ec41e3..af75e96b27 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h @@ -133,6 +133,8 @@ struct dpaa2_dpio_dev { struct rte_intr_handle *intr_handle; /* Interrupt related info */ int32_t epoll_fd; /**< File descriptor created for interrupt polling */ int32_t hw_id; /**< An unique ID of this DPIO device instance */ + uint8_t intr_enabled; /**< DQRI portal interrupt already set up */ + uint16_t ethrx_intr_refcnt; /**< rx queues currently armed on this portal */ struct dpaa2_portal_dqrr dpaa2_held_bufs; }; @@ -164,6 +166,20 @@ typedef void (dpaa2_queue_cb_dqrr_t)(struct qbman_swp *swp, typedef void (dpaa2_queue_cb_eqresp_free_t)(uint16_t eqresp_ci, struct dpaa2_queue *dpaa2_q); +#define DPAA2_NAPI_FD_STASH_SIZE 64 /*!< power of 2; >= 2x rx burst so the + * peer port's frames fit before HW + * backpressure (2 ports/worker) + */ + +/* Lcore-local FIFO of raw FDs demuxed to this queue by another queue's burst + * on the same portal (see dpaa2_queue::napi_stash). + */ +struct dpaa2_napi_stash { + uint16_t head; /*!< pop index (drain) */ + uint16_t tail; /*!< push index (park) */ + struct qbman_fd fd[DPAA2_NAPI_FD_STASH_SIZE]; +}; + struct __rte_cache_aligned dpaa2_queue { struct rte_mempool *mb_pool; /**< mbuf pool to populate RX ring. */ union { @@ -176,7 +192,7 @@ struct __rte_cache_aligned dpaa2_queue { uint8_t cgid; /*! < Congestion Group id for this queue */ uint64_t rx_pkts; uint64_t tx_pkts; - uint64_t err_pkts; + uint64_t err_pkts; /*!< also counts NAPI stash-full drops (imissed) */ union { /**Ingress*/ struct queue_storage_info_t *q_storage[RTE_MAX_LCORE]; @@ -195,6 +211,15 @@ struct __rte_cache_aligned dpaa2_queue { uint64_t offloads; uint64_t lpbk_cntx; uint8_t data_stashing_off; + /* NAPI rx-interrupt: per-queue DPCON bound to this FQ at dev_start + * (DEST_DPCON, static); the polling worker subscribes its ethrx portal + * to the channel and arms the DQRI, rx_dqrr drains+demuxes by fqd_ctx. + */ + struct dpaa2_dpcon_dev *napi_dpcon; /*!< notif channel, NULL = napi off */ + RTE_ATOMIC(struct dpaa2_dpio_dev *) napi_sub_dpio; /*!< subscribed portal or NULL */ + uint8_t napi_channel_index; /*!< portal-local static-dequeue idx */ + uint8_t napi_armed; /*!< this queue requests DQRI wakeups */ + struct dpaa2_napi_stash napi_stash; /*!< NAPI/DQRR demux FDs (~2 KB) */ }; struct swp_active_dqs { diff --git a/drivers/bus/fslmc/qbman/qbman_portal.c b/drivers/bus/fslmc/qbman/qbman_portal.c index 84853924e7..947415363a 100644 --- a/drivers/bus/fslmc/qbman/qbman_portal.c +++ b/drivers/bus/fslmc/qbman/qbman_portal.c @@ -448,6 +448,7 @@ int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p) return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IIR); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_interrupt_set_inhibit) void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit) { qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_IIR, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 8589398324..6407c24755 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -658,6 +658,8 @@ dpaa2_clear_queue_active_dps(struct dpaa2_queue *q, int num_lcores) } } +static void dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q); + static void dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) { @@ -675,6 +677,12 @@ dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) /* cleaning up queue storage */ for (i = 0; i < priv->nb_rx_queues; i++) { dpaa2_q = priv->rx_vq[i]; + if (dpaa2_q->napi_dpcon) { /* release the rx-intr channel */ + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + dpaa2_q->napi_sub_dpio = NULL; + } dpaa2_clear_queue_active_dps(dpaa2_q, RTE_MAX_LCORE); dpaa2_queue_storage_free(dpaa2_q, @@ -880,6 +888,21 @@ dpaa2_eth_dev_configure(struct rte_eth_dev *dev) } } + if (dev->data->dev_conf.intr_conf.rxq) { + if (!dev->intr_handle) + dev->intr_handle = rte_intr_instance_alloc( + RTE_INTR_INSTANCE_F_PRIVATE); + if (!dev->intr_handle || + rte_intr_vec_list_alloc(dev->intr_handle, "rxq_intr", + dev->data->nb_rx_queues) || + rte_intr_nb_efd_set(dev->intr_handle, + dev->data->nb_rx_queues) || + rte_intr_type_set(dev->intr_handle, RTE_INTR_HANDLE_EXT)) { + DPAA2_PMD_ERR("Failed to set up rx-queue interrupts"); + return -rte_errno; + } + } + dpaa2_tm_init(dev); return 0; @@ -898,6 +921,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, { struct dpaa2_dev_priv *priv = dev->data->dev_private; struct fsl_mc_io *dpni = dev->process_private; + bool dpcon_allocated = false; struct dpaa2_queue *dpaa2_q; struct dpni_queue cfg; uint8_t options = 0; @@ -938,6 +962,21 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, dpaa2_q->bp_array = rte_dpaa2_bpid_info; dpaa2_q->offloads = rx_conf->offloads; + /* NAPI: grab a DPCON channel so dev_start can bind this FQ statically. + * The DQRR burst replaces the poll path for every queue at once, so a + * missing channel is fatal rather than a silent per-queue fallback. + */ + dpaa2_q->napi_sub_dpio = NULL; + if (dev->data->dev_conf.intr_conf.rxq && !dpaa2_q->napi_dpcon) { + dpaa2_q->napi_dpcon = rte_dpaa2_alloc_dpcon_dev(); + if (!dpaa2_q->napi_dpcon) { + DPAA2_PMD_ERR("rxq %d: no DPCON for rx-queue interrupts", + rx_queue_id); + return -ENODEV; + } + dpcon_allocated = true; + } + /*Get the flow id from given VQ id*/ flow_id = dpaa2_q->flow_id; memset(&cfg, 0, sizeof(struct dpni_queue)); @@ -945,6 +984,10 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, options = options | DPNI_QUEUE_OPT_USER_CTX; cfg.user_context = (size_t)(dpaa2_q); + /* clear any stale DPIO dest left scheduled by a prior rx-intr run */ + options |= DPNI_QUEUE_OPT_DEST; + cfg.destination.type = DPNI_DEST_NONE; + /* check if a private cgr available. */ for (i = 0; i < priv->max_cgs; i++) { if (!priv->cgid_in_use[i]) { @@ -985,7 +1028,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, dpaa2_q->tc_index, flow_id, options, &cfg); if (ret) { DPAA2_PMD_ERR("Error in setting the rx flow: = %d", ret); - return ret; + goto err_free_dpcon; } dpaa2_q->nb_desc = nb_rx_desc; @@ -1026,7 +1069,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, if (ret) { DPAA2_PMD_ERR("Error in setting taildrop. err=(%d)", ret); - return ret; + goto err_free_dpcon; } } else { /* Disable tail Drop */ struct dpni_taildrop taildrop = {0}; @@ -1046,12 +1089,22 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, if (ret) { DPAA2_PMD_ERR("Error in setting taildrop. err=(%d)", ret); - return ret; + goto err_free_dpcon; } } dev->data->rx_queues[rx_queue_id] = dpaa2_q; return 0; + +err_free_dpcon: + /* free only the DPCON this call allocated; a pre-existing one belongs to + * an earlier setup and is released at dev_close + */ + if (dpcon_allocated) { + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + } + return ret; } static int @@ -1210,6 +1263,62 @@ dpaa2_dev_tx_queue_setup(struct rte_eth_dev *dev, return 0; } +/* Fully release a queue's rx-interrupt state: detach the FQ from its DPCON, + * unbind the static dequeue channel from the portal and free any stashed FDs. + * Teardown only: the port is stopped and the portal quiesced; not a runtime + * rx_queue_intr_disable() replacement. Call before freeing the DPCON. + */ +static void +dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q) +{ + struct dpaa2_dev_priv *priv; + struct dpaa2_dpio_dev *dpio; + struct fsl_mc_io *dpni; + struct dpni_queue cfg; + int ret; + + if (!dpaa2_q || !dpaa2_q->napi_dpcon) + return; + + /* detach the FQ from its DPCON so it no longer points at a channel + * about to be returned to the pool (dpni is disabled at teardown) + */ + priv = dpaa2_q->eth_data->dev_private; + dpni = priv->eth_dev->process_private; + memset(&cfg, 0, sizeof(cfg)); + cfg.destination.type = DPNI_DEST_NONE; + ret = dpni_set_queue(dpni, CMD_PRI_LOW, priv->token, DPNI_QUEUE_RX, + dpaa2_q->tc_index, dpaa2_q->flow_id, + DPNI_QUEUE_OPT_DEST, &cfg); + if (ret) + DPAA2_PMD_ERR("napi: DEST_NONE rxq flow %u: %d", + dpaa2_q->flow_id, ret); + + /* unbind the static dequeue channel from the portal it was armed on */ + dpio = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, + rte_memory_order_acquire); + if (dpio) { + qbman_swp_push_set(dpio->sw_portal, + dpaa2_q->napi_channel_index, 0); + if (dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 0; + if (dpio->ethrx_intr_refcnt > 0 && + --dpio->ethrx_intr_refcnt == 0) + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 1); + } + ret = dpio_remove_static_dequeue_channel(dpio->dpio, CMD_PRI_LOW, + dpio->token, dpaa2_q->napi_dpcon->dpcon_id); + if (ret) + DPAA2_PMD_ERR("napi: remove DPCON %d static dequeue channel: %d", + dpaa2_q->napi_dpcon->dpcon_id, ret); + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, + rte_memory_order_release); + } + + /* free FDs parked for this queue but never drained by a burst */ + dpaa2_dev_rx_queue_napi_stash_drain(dpaa2_q); +} + static void dpaa2_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t rx_queue_id) { @@ -1239,6 +1348,12 @@ dpaa2_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t rx_queue_id) priv->cgid_in_use[dpaa2_q->cgid] = 0; dpaa2_q->cgid = DPAA2_INVALID_CGID; } + + if (dpaa2_q->napi_dpcon) { + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + } } static int @@ -1389,6 +1504,36 @@ dpaa2_dev_start(struct rte_eth_dev *dev) intr_handle = dpaa2_dev->intr_handle; PMD_INIT_FUNC_TRACE(); + + /* NAPI: bind each rx FQ to its own DPCON channel while the dpni is still + * disabled (a DEST set_queue on an enabled dpni wedges the shared MC). + * Static, affinity-free; the polling worker subscribes its portal later. + */ + if (dev->data->dev_conf.intr_conf.rxq) { + for (i = 0; i < data->nb_rx_queues; i++) { + dpaa2_q = data->rx_queues[i]; + if (!dpaa2_q->napi_dpcon) + continue; + memset(&cfg, 0, sizeof(cfg)); + cfg.destination.type = DPNI_DEST_DPCON; + cfg.destination.id = dpaa2_q->napi_dpcon->dpcon_id; + cfg.user_context = (size_t)dpaa2_q; + ret = dpni_set_queue(dpni, CMD_PRI_LOW, priv->token, + DPNI_QUEUE_RX, dpaa2_q->tc_index, + dpaa2_q->flow_id, + DPNI_QUEUE_OPT_DEST | DPNI_QUEUE_OPT_USER_CTX, + &cfg); + if (ret) { + DPAA2_PMD_ERR("napi: DPCON bind rxq %d: %d", i, ret); + return ret; + } + } + /* DQRR burst for all queues; a queue only yields frames once + * rx_queue_intr_enable() has subscribed its portal + */ + dev->rx_pkt_burst = dpaa2_dev_rx_dqrr; + } + ret = dpni_enable(dpni, CMD_PRI_LOW, priv->token); if (ret) { DPAA2_PMD_ERR("Failure in enabling dpni %d device: err=%d", @@ -1859,6 +2004,13 @@ dpaa2_dev_stats_get(struct rte_eth_dev *dev, stats->oerrors = value.page_2.egress_discarded_frames; stats->imissed = value.page_2.ingress_nobuffer_discards; + /* software Rx drops (full napi stash) are not in the HW counters */ + for (i = 0; i < priv->nb_rx_queues; i++) { + dpaa2_rxq = priv->rx_vq[i]; + if (dpaa2_rxq != NULL) + stats->imissed += dpaa2_rxq->err_pkts; + } + /* Fill in per queue stats */ if (qstats != NULL) { for (i = 0; (i < RTE_ETHDEV_QUEUE_STAT_CNTRS) && @@ -2172,8 +2324,10 @@ dpaa2_dev_stats_reset(struct rte_eth_dev *dev) /* Reset the per queue stats in dpaa2_queue structure */ for (i = 0; i < priv->nb_rx_queues; i++) { dpaa2_q = priv->rx_vq[i]; - if (dpaa2_q) + if (dpaa2_q) { dpaa2_q->rx_pkts = 0; + dpaa2_q->err_pkts = 0; + } } for (i = 0; i < priv->nb_tx_queues; i++) { @@ -2901,6 +3055,135 @@ rte_pmd_dpaa2_thread_init(void) } } +/* Arm rx-queue interrupts on the worker lcore: subscribe its ethrx portal to + * the queue's DPCON channel (one-shot per-portal MC) and unmask the portal DQRI + * (pure QBMan). + * + * Affinity is static queue-to-lcore; a lcore may own several rx queues. The + * DQRI and the eventfd are portal-wide, so frames are demuxed by fqd_ctx in the + * burst and the portal's inhibit bit is reference-counted by the number of its + * queues currently armed (ethrx_intr_refcnt) -- disabling one queue must not + * mask wakeups still wanted by its siblings. napi_armed and ethrx_intr_refcnt + * are plain (not atomic): these ops run on the queue's owner lcore against its + * own portal (one portal per lcore), so per-portal isolation keeps them from + * racing, not control-plane serialization. + * + * A re-home reclaims the channel by poking the old portal, so the caller must + * have quiesced the previous owner and disabled the queue there; napi_armed is + * then 0 and only the new portal is counted. + */ +static int +dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + struct dpaa2_dpio_dev *dpio, *old; + int ret; + + if (!dpaa2_q->napi_dpcon) + return -ENOTSUP; /* no channel -> caller keeps polling */ + + if (dpaa2_affine_qbman_ethrx_swp()) + return -EIO; + dpio = DPAA2_PER_LCORE_ETHRX_DPIO; + + /* build_epoll=false: the generic ethdev rx-intr API waits on the + * application epoll, not the portal's private one (event PMD only). + */ + ret = dpaa2_dpio_intr_init(dpio, false); /* VFIO eventfd, no MC */ + if (ret) + return ret; + + old = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, rte_memory_order_acquire); + if (old && old != dpio && dpaa2_q->napi_armed) { + DPAA2_PMD_ERR("rxq %d still armed on another portal; disable it first", + queue_id); + return -EBUSY; + } + if (old != dpio) { + if (old) { /* reclaim from old portal (quiesced; QBMan MMIO unsynced) */ + qbman_swp_push_set(old->sw_portal, + dpaa2_q->napi_channel_index, 0); + ret = dpio_remove_static_dequeue_channel(old->dpio, + CMD_PRI_LOW, old->token, + dpaa2_q->napi_dpcon->dpcon_id); + /* push_set(0) above already stops the old portal from + * dequeuing; a failed unbind only leaks a static-channel + * slot on the old DPIO, so warn and proceed + */ + if (ret) + DPAA2_PMD_WARN("napi: reclaim rxq %d: %d", + queue_id, ret); + /* on no portal until the add below succeeds */ + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, + rte_memory_order_release); + } + ret = dpio_add_static_dequeue_channel(dpio->dpio, CMD_PRI_LOW, + dpio->token, dpaa2_q->napi_dpcon->dpcon_id, + &dpaa2_q->napi_channel_index); + if (ret) { + DPAA2_PMD_ERR("napi: subscribe rxq %d: %d", queue_id, ret); + return ret; + } + qbman_swp_push_set(dpio->sw_portal, + dpaa2_q->napi_channel_index, 1); + /* point this queue's eventfd at the portal's DQRI fd so the + * generic rte_eth_dev_rx_intr_ctl_q epoll wakes on it + */ + if (rte_intr_vec_list_index_set(dev->intr_handle, queue_id, queue_id) || + rte_intr_efds_index_set(dev->intr_handle, queue_id, + rte_intr_fd_get(dpio->intr_handle))) { + DPAA2_PMD_ERR("napi: efd wiring rxq %d", queue_id); + /* unwind the half-done subscription so HW and driver + * state stay consistent + */ + qbman_swp_push_set(dpio->sw_portal, + dpaa2_q->napi_channel_index, 0); + dpio_remove_static_dequeue_channel(dpio->dpio, + CMD_PRI_LOW, dpio->token, + dpaa2_q->napi_dpcon->dpcon_id); + return -EIO; + } + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, dpio, rte_memory_order_release); + } + + /* arm this queue; the portal DQRI is unmasked only on the 0 -> 1 edge + * of its armed-queue count + */ + if (!dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 1; + if (dpio->ethrx_intr_refcnt++ == 0) { + qbman_swp_interrupt_clear_status(dpio->sw_portal, + 0xffffffff); + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 0); + } + } + + return 0; +} + +/* Disarm rx-queue interrupts for this queue. The portal DQRI is masked only + * once the last of its queues disarms; act on the portal the queue is actually + * subscribed to, not the caller's current portal. + */ +static int +dpaa2_dev_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + struct dpaa2_dpio_dev *dpio; + + dpio = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, rte_memory_order_acquire); + if (dpio && dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 0; + if (dpio->ethrx_intr_refcnt > 0 && + --dpio->ethrx_intr_refcnt == 0) + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 1); + } + + return 0; +} + static struct eth_dev_ops dpaa2_ethdev_ops = { .dev_configure = dpaa2_eth_dev_configure, .dev_start = dpaa2_dev_start, @@ -2929,6 +3212,8 @@ static struct eth_dev_ops dpaa2_ethdev_ops = { .vlan_tpid_set = dpaa2_vlan_tpid_set, .rx_queue_setup = dpaa2_dev_rx_queue_setup, .rx_queue_release = dpaa2_dev_rx_queue_release, + .rx_queue_intr_enable = dpaa2_dev_rx_queue_intr_enable, + .rx_queue_intr_disable = dpaa2_dev_rx_queue_intr_disable, .tx_queue_setup = dpaa2_dev_tx_queue_setup, .rx_burst_mode_get = dpaa2_dev_rx_burst_mode_get, .tx_burst_mode_get = dpaa2_dev_tx_burst_mode_get, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.h b/drivers/net/dpaa2/dpaa2_ethdev.h index 3f224c654e..65fb48bd27 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.h +++ b/drivers/net/dpaa2/dpaa2_ethdev.h @@ -500,6 +500,9 @@ uint16_t dpaa2_dev_loopback_rx(void *queue, struct rte_mbuf **bufs, uint16_t dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts); +uint16_t dpaa2_dev_rx_dqrr(void *queue, struct rte_mbuf **bufs, + uint16_t nb_pkts); +void dpaa2_dev_rx_queue_napi_stash_drain(struct dpaa2_queue *dpaa2_q); void dpaa2_dev_process_parallel_event(struct qbman_swp *swp, const struct qbman_fd *fd, const struct qbman_result *dq, diff --git a/drivers/net/dpaa2/dpaa2_rxtx.c b/drivers/net/dpaa2/dpaa2_rxtx.c index b316e23e87..189accc1de 100644 --- a/drivers/net/dpaa2/dpaa2_rxtx.c +++ b/drivers/net/dpaa2/dpaa2_rxtx.c @@ -922,6 +922,128 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) return num_rx; } +/* Convert a DQRR'd FD (single or scatter-gather) to an mbuf and apply software + * VLAN strip, like the poll path. + */ +static inline struct rte_mbuf * +dpaa2_dqrr_fd_to_mbuf(const struct qbman_fd *fd, + struct rte_eth_dev_data *eth_data) +{ + struct rte_mbuf *m; + + if (unlikely(DPAA2_FD_GET_FORMAT(fd) == qbman_fd_sg)) + m = eth_sg_fd_to_mbuf(fd, eth_data->port_id); + else + m = eth_fd_to_mbuf(fd, eth_data->port_id); + if (eth_data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_VLAN_STRIP) + rte_vlan_strip(m); + return m; +} + +/* prefetch a DQRR'd FD's HW annotation (parse area) ahead of conversion */ +static inline void +dpaa2_dqrr_prefetch_annot(const struct qbman_fd *fd) +{ + rte_prefetch0((void *)((size_t)DPAA2_IOVA_TO_VADDR(DPAA2_GET_FD_ADDR(fd)) + + DPAA2_FD_PTA_SIZE)); +} + +/* Free FDs a sibling burst parked in this queue's stash but that were never + * drained (queue released/freed while the lcore still held its frames). + */ +void +dpaa2_dev_rx_queue_napi_stash_drain(struct dpaa2_queue *dpaa2_q) +{ + struct dpaa2_napi_stash *stash = &dpaa2_q->napi_stash; + const struct qbman_fd *fd; + + while (stash->head != stash->tail) { + fd = &stash->fd[stash->head & (DPAA2_NAPI_FD_STASH_SIZE - 1)]; + rte_pktmbuf_free(dpaa2_dqrr_fd_to_mbuf(fd, dpaa2_q->eth_data)); + stash->head++; + } + stash->head = 0; + stash->tail = 0; +} + +/* rx interrupt/DQRR path: the FQ is scheduled to a channel the lcore's ethrx + * portal statically dequeues -- a VDQ on a scheduled FQ never completes, so DQRR + * is the only model compatible with interrupt sleep. One portal serves every + * queue the lcore owns, so the burst demuxes by fqd_ctx: own frames are + * returned, foreign ones have their raw FD parked in the target queue's stash. + * + * The application must therefore poll all queues assigned to the lcore after a + * wakeup -- the same scheduling contract as plain DPDK polling. When a foreign + * queue's stash is full the FD is dropped (freed) rather than left on the shared + * DQRR ring, which would head-of-line block every other queue on the portal. + */ +uint16_t __rte_hot +dpaa2_dev_rx_dqrr(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +{ + struct dpaa2_queue *dpaa2_q = queue; + struct rte_eth_dev_data *eth_data = dpaa2_q->eth_data; + struct dpaa2_napi_stash *stash = &dpaa2_q->napi_stash; + const struct qbman_result *dq; + const struct qbman_fd *fd; + struct dpaa2_queue *rxq; + struct qbman_swp *swp; + uint16_t num_rx = 0; + + if (unlikely(!DPAA2_PER_LCORE_ETHRX_DPIO)) { + if (dpaa2_affine_qbman_ethrx_swp()) { + DPAA2_PMD_ERR("Failure in affining portal"); + return 0; + } + } + swp = DPAA2_PER_LCORE_ETHRX_PORTAL; + + /* our frames parked by another queue's burst -- convert now (hot) */ + while (num_rx < nb_pkts && stash->head != stash->tail) { + fd = &stash->fd[stash->head & (DPAA2_NAPI_FD_STASH_SIZE - 1)]; + if (dpaa2_svr_family != SVR_LX2160A && + (uint16_t)(stash->head + 1) != stash->tail) + dpaa2_dqrr_prefetch_annot(&stash->fd[(stash->head + 1) & + (DPAA2_NAPI_FD_STASH_SIZE - 1)]); + bufs[num_rx++] = dpaa2_dqrr_fd_to_mbuf(fd, eth_data); + stash->head++; + } + + while (num_rx < nb_pkts) { + dq = qbman_swp_dqrr_next(swp); + if (!dq) + break; /* ring momentarily empty */ + qbman_swp_prefetch_dqrr_next(swp); + fd = qbman_result_DQ_fd(dq); + /* parse summary is in the FRC on LX2160A; annotation is HW-stashed */ + if (dpaa2_svr_family != SVR_LX2160A) + dpaa2_dqrr_prefetch_annot(fd); + rxq = (struct dpaa2_queue *)(size_t)qbman_result_DQ_fqd_ctx(dq); + if (unlikely(!rxq)) + rxq = dpaa2_q; + if (rxq == dpaa2_q) { + bufs[num_rx++] = dpaa2_dqrr_fd_to_mbuf(fd, eth_data); + } else { + struct dpaa2_napi_stash *fs = &rxq->napi_stash; + + if (unlikely((uint16_t)(fs->tail - fs->head) >= + DPAA2_NAPI_FD_STASH_SIZE)) { + /* stash full: drop rather than leave it on the ring + * and head-of-line block the shared portal + */ + rte_pktmbuf_free(dpaa2_dqrr_fd_to_mbuf(fd, rxq->eth_data)); + rxq->err_pkts++; + } else { + fs->fd[fs->tail & (DPAA2_NAPI_FD_STASH_SIZE - 1)] = *fd; + fs->tail++; + } + } + qbman_swp_dqrr_consume(swp, dq); + } + + dpaa2_q->rx_pkts += num_rx; + return num_rx; +} + void __rte_hot dpaa2_dev_process_parallel_event(struct qbman_swp *swp, const struct qbman_fd *fd, -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH 5/9] net/dpaa2: support Rx queue interrupts 2026-06-11 15:49 ` [PATCH 5/9] net/dpaa2: support Rx queue interrupts Maxime Leroy @ 2026-06-16 8:05 ` David Marchand 0 siblings, 0 replies; 51+ messages in thread From: David Marchand @ 2026-06-16 8:05 UTC (permalink / raw) To: Maxime Leroy; +Cc: hemant.agrawal, sachin.saxena, dev On Thu, 11 Jun 2026 at 17:51, Maxime Leroy <maxime@leroys.fr> wrote: > diff --git a/drivers/bus/fslmc/qbman/qbman_portal.c b/drivers/bus/fslmc/qbman/qbman_portal.c > index 84853924e7..947415363a 100644 > --- a/drivers/bus/fslmc/qbman/qbman_portal.c > +++ b/drivers/bus/fslmc/qbman/qbman_portal.c > @@ -448,6 +448,7 @@ int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p) > return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IIR); > } > > +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_interrupt_set_inhibit) > void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit) qbman_swp_interrupt_set_inhibit is not declared as __rte_internal. > { > qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_IIR, -- David Marchand ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 6/9] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (4 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 5/9] net/dpaa2: support Rx queue interrupts Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 15:49 ` [PATCH 7/9] net/dpaa2: fix Rx queue count for primary process Maxime Leroy ` (3 subsequent siblings) 9 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy The portal DQRI interrupt used a fixed threshold of 3 and a raw 0xFF timeout. Parameterize dpaa2_dpio_intr_init() with (threshold, timeout) so each mode supplies its own: the event driver keeps the legacy 3 / 0xFF and its DPAA2_PORTAL_INTR_THRESHOLD / DPAA2_PORTAL_INTR_TIMEOUT env-var overrides, while rx-queue interrupts default the threshold to the HW DQRR ring depth (ring-1, =7 on QBMan >= 4.1) and use a coalescing holdoff in microseconds, converted to ITP units from the MC-reported QBMan clock (itp = holdoff_us * clk_MHz / 256, capped at the 12-bit field). The setup is portal-wide and idempotent, so the first mode to arm a given portal wins; a portal is normally driven by a single mode. The net/dpaa2 PMD exposes both rx-queue-interrupt knobs as per-port devargs: drv_rx_intr_holdoff_us (default 100us) and drv_rx_intr_threshold (default 0 = ring-1, clamped to [1, ring-1]). Also expose dpaa2_dpio_intr_deinit() (no longer event-only), and on the intr_init error paths close the epoll fd and disable the interrupt. Add qbman_swp_dqrr_size() to expose the ring depth. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/nics/dpaa2.rst | 10 +++ drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 72 +++++++++++++------ drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 +++- .../fslmc/qbman/include/fsl_qbman_portal.h | 9 +++ drivers/bus/fslmc/qbman/qbman_portal.c | 6 ++ drivers/net/dpaa2/dpaa2_ethdev.c | 60 +++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 7 ++ 7 files changed, 151 insertions(+), 25 deletions(-) diff --git a/doc/guides/nics/dpaa2.rst b/doc/guides/nics/dpaa2.rst index 2d70bd0ab9..47a52c9287 100644 --- a/doc/guides/nics/dpaa2.rst +++ b/doc/guides/nics/dpaa2.rst @@ -492,6 +492,16 @@ for details. packets, so that user can check what is wrong with those packets. e.g. ``fslmc:dpni.1,drv_error_queue=1`` +* Use dev arg option ``drv_rx_intr_holdoff_us=<uint32>`` to set the Rx queue + interrupt coalescing holdoff in microseconds (default 100). Only applies in + Rx queue interrupt mode. + e.g. ``fslmc:dpni.1,drv_rx_intr_holdoff_us=50`` + +* Use dev arg option ``drv_rx_intr_threshold=<uint32>`` to set the Rx queue + interrupt coalescing frame threshold; 0 (default) means the DQRR ring depth + minus one. + e.g. ``fslmc:dpni.1,drv_rx_intr_threshold=4`` + Enabling logs ------------- diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index e6b4e74b3b..c5525a94fa 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -206,12 +206,35 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) } #endif /* RTE_EVENT_DPAA2 */ +/* holdoff (us) -> QBMan ITP units (256 cycles each), capped at the 12-bit field */ +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_holdoff_to_itp) +int dpaa2_dpio_holdoff_to_itp(struct dpaa2_dpio_dev *dpio_dev, uint32_t holdoff_us) +{ + uint32_t qman_mhz = 0; + struct dpio_attr attr; + uint64_t itp; + + if (dpio_get_attributes(dpio_dev->dpio, CMD_PRI_LOW, dpio_dev->token, &attr) == 0) + qman_mhz = attr.clk / 1000000; + itp = qman_mhz ? ((uint64_t)holdoff_us * qman_mhz) / 256 : 0xFF; + if (itp > 0xfff) /* 12-bit ITP field */ + itp = 0xfff; + + return (int)itp; +} + +/* threshold: DQRR fill raising DQRI (< ring depth); timeout: holdoff in ITP units. + * Per-mode values from the caller (eventdev vs rx-queue intr); no env override. + * The DQRI config is portal-wide and this is idempotent: the first caller to + * arm a portal wins, a later caller's values are ignored (a portal normally + * serves a single mode). + */ RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_init) -int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, + int timeout, bool build_epoll) { - struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; - int threshold = 0x3, timeout = 0xFF; + struct epoll_event epoll_ev; if (dpio_dev->intr_enabled) return 0; @@ -222,12 +245,6 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) return -1; } - if (getenv("DPAA2_PORTAL_INTR_THRESHOLD")) - threshold = atoi(getenv("DPAA2_PORTAL_INTR_THRESHOLD")); - - if (getenv("DPAA2_PORTAL_INTR_TIMEOUT")) - sscanf(getenv("DPAA2_PORTAL_INTR_TIMEOUT"), "%x", &timeout); - qbman_swp_interrupt_set_trigger(dpio_dev->sw_portal, QBMAN_SWP_INTERRUPT_DQRI); qbman_swp_interrupt_clear_status(dpio_dev->sw_portal, 0xffffffff); @@ -238,9 +255,9 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) dpio_dev->epoll_fd = -1; /* The event PMD dequeues by sleeping on a private epoll instance owned - * by the portal, so build it here. A caller that waits on another - * epoll (the net rx-queue-interrupt path uses the application's) skips - * this. + * by the portal, so build it here. The net rx-queue-interrupt path + * exposes the raw eventfd through the generic ethdev API and waits on + * the application's own epoll instead, so it skips this. */ if (build_epoll) { dpio_epoll_fd = epoll_create(1); @@ -269,11 +286,14 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) return 0; } -#ifdef RTE_EVENT_DPAA2 -static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_deinit) +void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) { int ret; + if (!dpio_dev->intr_enabled) + return; + ret = rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); if (ret) DPAA2_BUS_ERR("DPIO interrupt disable failed"); @@ -284,7 +304,6 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) } dpio_dev->intr_enabled = 0; } -#endif static int dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) @@ -306,9 +325,18 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) } #ifdef RTE_EVENT_DPAA2 - if (dpaa2_dpio_intr_init(dpio_dev, true)) { - DPAA2_BUS_ERR("Interrupt registration failed for dpio"); - return -1; + { + int threshold = 3, timeout = 0xFF; + + if (getenv("DPAA2_PORTAL_INTR_THRESHOLD")) + threshold = atoi(getenv("DPAA2_PORTAL_INTR_THRESHOLD")); + if (getenv("DPAA2_PORTAL_INTR_TIMEOUT")) + sscanf(getenv("DPAA2_PORTAL_INTR_TIMEOUT"), "%x", &timeout); + + if (dpaa2_dpio_intr_init(dpio_dev, threshold, timeout, true)) { + DPAA2_BUS_ERR("Interrupt registration failed for dpio"); + return -1; + } } dpaa2_affine_dpio_intr_to_respective_core(dpio_dev->hw_id, cpu_id); #endif @@ -319,9 +347,11 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) static void dpaa2_put_qbman_swp(struct dpaa2_dpio_dev *dpio_dev) { if (dpio_dev) { -#ifdef RTE_EVENT_DPAA2 + /* rx-queue interrupts (net PMD) can arm a portal without the + * event driver; tear it down unconditionally. Safe when never + * armed: intr_deinit returns early if intr is not enabled. + */ dpaa2_dpio_intr_deinit(dpio_dev); -#endif rte_atomic16_clear(&dpio_dev->ref_count); } } @@ -512,6 +542,8 @@ dpaa2_create_dpio_device(int vdev_fd, goto err; } + DPAA2_BUS_DEBUG("QBMAN clk = %u Hz (%u MHz)", attr.clk, attr.clk / 1000000); + /* find the SoC type for the first time */ if (!dpaa2_svr_family) { struct mc_soc_version mc_plat_info = {0}; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h index 10dd968e5f..090fa14410 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h @@ -50,9 +50,17 @@ int dpaa2_affine_qbman_swp(void); __rte_internal int dpaa2_affine_qbman_ethrx_swp(void); -/* set up a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ +/* set up / tear down a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ __rte_internal -int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll); +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, + int timeout, bool build_epoll); + +__rte_internal +void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev); + +/* convert a coalescing holdoff (microseconds) to QBMan ITP units */ +__rte_internal +int dpaa2_dpio_holdoff_to_itp(struct dpaa2_dpio_dev *dpio_dev, uint32_t holdoff_us); /* allocate memory for FQ - dq storage */ __rte_internal diff --git a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h index 5375ea386d..842ef6f067 100644 --- a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h +++ b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h @@ -157,6 +157,15 @@ uint32_t qbman_swp_intr_timeout_read_status(struct qbman_swp *p); */ void qbman_swp_intr_timeout_write(struct qbman_swp *p, uint32_t mask); +/** + * qbman_swp_dqrr_size() - Get the HW DQRR ring depth of a software portal. + * @p: the given software portal object. + * + * Returns the number of DQRR entries (4 on QBMan < 4.1, 8 on >= 4.1). Useful + * as the upper bound for the DQRR interrupt coalescing threshold. + */ +uint8_t qbman_swp_dqrr_size(struct qbman_swp *p); + /** * qbman_swp_interrupt_get_trigger() - Get the data in software portal * interrupt enable register. diff --git a/drivers/bus/fslmc/qbman/qbman_portal.c b/drivers/bus/fslmc/qbman/qbman_portal.c index 947415363a..81c2d87e0a 100644 --- a/drivers/bus/fslmc/qbman/qbman_portal.c +++ b/drivers/bus/fslmc/qbman/qbman_portal.c @@ -433,6 +433,12 @@ void qbman_swp_intr_timeout_write(struct qbman_swp *p, uint32_t mask) qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_ITPR, mask); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_dqrr_size) +uint8_t qbman_swp_dqrr_size(struct qbman_swp *p) +{ + return p->dqrr.dqrr_size; +} + uint32_t qbman_swp_interrupt_get_trigger(struct qbman_swp *p) { return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IER); diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 6407c24755..7ca454eaae 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -36,6 +36,9 @@ #define DRIVER_ERROR_QUEUE "drv_err_queue" #define DRIVER_NO_TAILDROP "drv_no_taildrop" #define DRIVER_NO_DATA_STASHING "drv_no_data_stashing" +#define DRIVER_RX_INTR_HOLDOFF_US "drv_rx_intr_holdoff_us" +#define DPAA2_RX_INTR_HOLDOFF_US_DEF 100 +#define DRIVER_RX_INTR_THRESHOLD "drv_rx_intr_threshold" #define CHECK_INTERVAL 100 /* 100ms */ #define MAX_REPEAT_TIME 90 /* 9s (90 * 100ms) in total */ @@ -3078,7 +3081,7 @@ dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) struct dpaa2_dev_priv *priv = dev->data->dev_private; struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; struct dpaa2_dpio_dev *dpio, *old; - int ret; + int ret, threshold, timeout, dqrr_max; if (!dpaa2_q->napi_dpcon) return -ENOTSUP; /* no channel -> caller keeps polling */ @@ -3087,10 +3090,22 @@ dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) return -EIO; dpio = DPAA2_PER_LCORE_ETHRX_DPIO; + /* threshold from drv_rx_intr_threshold (0 = ring-1), holdoff from + * drv_rx_intr_holdoff_us. idempotent: no-op if the dpio is already + * armed (e.g. event driver) + */ + dqrr_max = qbman_swp_dqrr_size(dpio->sw_portal) - 1; + threshold = priv->rx_intr_threshold ? (int)priv->rx_intr_threshold : dqrr_max; + if (threshold < 1 || threshold > dqrr_max) { + DPAA2_PMD_WARN("drv_rx_intr_threshold %d out of [1, %d], clamping", + threshold, dqrr_max); + threshold = threshold < 1 ? 1 : dqrr_max; + } + timeout = dpaa2_dpio_holdoff_to_itp(dpio, priv->rx_intr_holdoff_us); /* build_epoll=false: the generic ethdev rx-intr API waits on the * application epoll, not the portal's private one (event PMD only). */ - ret = dpaa2_dpio_intr_init(dpio, false); /* VFIO eventfd, no MC */ + ret = dpaa2_dpio_intr_init(dpio, threshold, timeout, false); if (ret) return ret; @@ -3346,6 +3361,35 @@ dpaa2_get_devargs(struct rte_devargs *devargs, const char *key) return 1; } +static int +u32_devarg_handler(__rte_unused const char *key, const char *value, void *opaque) +{ + char *end; + unsigned long v = strtoul(value, &end, 0); + + if (*value == '\0' || *end != '\0' || v > UINT32_MAX) + return -1; + *(uint32_t *)opaque = (uint32_t)v; + + return 0; +} + +/* Read a u32-valued devarg into *out, leaving *out untouched if absent. */ +static void +dpaa2_get_devargs_u32(struct rte_devargs *devargs, const char *key, uint32_t *out) +{ + struct rte_kvargs *kvlist; + + if (!devargs) + return; + kvlist = rte_kvargs_parse(devargs->args, NULL); + if (!kvlist) + return; + if (rte_kvargs_count(kvlist, key)) + rte_kvargs_process(kvlist, key, u32_devarg_handler, out); + rte_kvargs_free(kvlist); +} + static int dpaa2_dev_init(struct rte_eth_dev *eth_dev) { @@ -3373,6 +3417,14 @@ dpaa2_dev_init(struct rte_eth_dev *eth_dev) DPAA2_PMD_INFO("No RX prefetch mode"); } + priv->rx_intr_holdoff_us = DPAA2_RX_INTR_HOLDOFF_US_DEF; + dpaa2_get_devargs_u32(dev->devargs, DRIVER_RX_INTR_HOLDOFF_US, + &priv->rx_intr_holdoff_us); + + priv->rx_intr_threshold = 0; + dpaa2_get_devargs_u32(dev->devargs, DRIVER_RX_INTR_THRESHOLD, + &priv->rx_intr_threshold); + if (dpaa2_get_devargs(dev->devargs, DRIVER_LOOPBACK_MODE)) { priv->flags |= DPAA2_RX_LOOPBACK_MODE; DPAA2_PMD_INFO("Rx loopback mode"); @@ -3888,5 +3940,7 @@ RTE_PMD_REGISTER_PARAM_STRING(NET_DPAA2_PMD_DRIVER_NAME, DRIVER_RX_PARSE_ERR_DROP "=<int>" DRIVER_ERROR_QUEUE "=<int>" DRIVER_NO_TAILDROP "=<int>" - DRIVER_NO_DATA_STASHING "=<int>"); + DRIVER_NO_DATA_STASHING "=<int> " + DRIVER_RX_INTR_HOLDOFF_US "=<uint32> " + DRIVER_RX_INTR_THRESHOLD "=<uint32>"); RTE_LOG_REGISTER_DEFAULT(dpaa2_logtype_pmd, NOTICE); diff --git a/drivers/net/dpaa2/dpaa2_ethdev.h b/drivers/net/dpaa2/dpaa2_ethdev.h index 65fb48bd27..d8be1f8bce 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.h +++ b/drivers/net/dpaa2/dpaa2_ethdev.h @@ -412,6 +412,13 @@ struct dpaa2_dev_priv { uint8_t max_cgs; uint8_t cgid_in_use[MAX_RX_QUEUES]; + /* DQRI holdoff (us) for rx-queue interrupts (drv_rx_intr_holdoff_us) */ + uint32_t rx_intr_holdoff_us; + /* DQRI threshold for rx-queue interrupts (drv_rx_intr_threshold); + * 0 = auto (DQRR ring depth - 1) + */ + uint32_t rx_intr_threshold; + /* Current hash distribution size per RX TC, written by * dpaa2_setup_flow_dist_size() and read by reta_query / reta_update. * Zero means "use default" (= nb_rx_queues clamped to dist_queues). -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 7/9] net/dpaa2: fix Rx queue count for primary process 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (5 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 6/9] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 15:49 ` [PATCH 8/9] ethdev: keep fast-path ops valid after port stop Maxime Leroy ` (2 subsequent siblings) 9 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena Cc: dev, Maxime Leroy, stable, Ferruh Yigit, Andrew Rybchenko, David Marchand The rx_queue_count callback was only assigned on the secondary process path of dpaa2_dev_init(), leaving eth_dev->rx_queue_count NULL for the primary process. The fast-path rte_eth_rx_queue_count() performs an unguarded indirect call in non-debug builds, so invoking it on a primary-process dpaa2 port dereferences a NULL function pointer and crashes. Assign the callback once before the process-type split so both the primary and secondary paths set it. Fixes: cbfc6111b557 ("ethdev: move inline device operations") Cc: stable@dpdk.org Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/net/dpaa2/dpaa2_ethdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 7ca454eaae..fb117e761f 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -3617,6 +3617,7 @@ dpaa2_dev_init(struct rte_eth_dev *eth_dev) } eth_dev->dev_ops = &dpaa2_ethdev_ops; + eth_dev->rx_queue_count = dpaa2_dev_rx_queue_count; if (dpaa2_get_devargs(dev->devargs, DRIVER_LOOPBACK_MODE)) { eth_dev->rx_pkt_burst = dpaa2_dev_loopback_rx; -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (6 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 7/9] net/dpaa2: fix Rx queue count for primary process Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 16:01 ` Morten Brørup ` (2 more replies) 2026-06-11 15:49 ` [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 9 siblings, 3 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena Cc: dev, Maxime Leroy, stable, Thomas Monjalon, Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release via a compound literal, so every field it omits is zeroed to NULL. It sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare and the recycle callbacks NULL. In non-debug builds these ops are reached through an unguarded indirect call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a thread calling e.g. rte_eth_rx_queue_count() on a port being stopped dereferences NULL and crashes, while the same race on rte_eth_rx_burst() is harmless because the burst ops are reset to dummies. A poll-mode worker re-checking rx_queue_count before arming the Rx interrupt and sleeping hits exactly this. Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, so a stopped port behaves like a freshly allocated one: every fast-path op is a safe no-op, none is NULL. Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") Cc: stable@dpdk.org Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- lib/ethdev/ethdev_private.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c index 72a0723846..75ea3eedff 100644 --- a/lib/ethdev/ethdev_private.c +++ b/lib/ethdev/ethdev_private.c @@ -263,6 +263,13 @@ eth_dev_fp_ops_reset(struct rte_eth_fp_ops *fpo) *fpo = (struct rte_eth_fp_ops) { .rx_pkt_burst = dummy_eth_rx_burst, .tx_pkt_burst = dummy_eth_tx_burst, + .tx_pkt_prepare = rte_eth_tx_pkt_prepare_dummy, + .rx_queue_count = rte_eth_queue_count_dummy, + .tx_queue_count = rte_eth_queue_count_dummy, + .rx_descriptor_status = rte_eth_descriptor_status_dummy, + .tx_descriptor_status = rte_eth_descriptor_status_dummy, + .recycle_tx_mbufs_reuse = rte_eth_recycle_tx_mbufs_reuse_dummy, + .recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy, .rxq = { .data = (void **)&dummy_queues_array[port_id], .clbk = dummy_data, -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* RE: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-11 15:49 ` [PATCH 8/9] ethdev: keep fast-path ops valid after port stop Maxime Leroy @ 2026-06-11 16:01 ` Morten Brørup 2026-06-11 18:39 ` Maxime Leroy 2026-06-14 19:30 ` Stephen Hemminger 2026-06-15 9:26 ` David Marchand 2 siblings, 1 reply; 51+ messages in thread From: Morten Brørup @ 2026-06-11 16:01 UTC (permalink / raw) To: Maxime Leroy, hemant.agrawal, sachin.saxena Cc: dev, stable, Thomas Monjalon, Andrew Rybchenko, Sunil Kumar Kori > From: Maxime Leroy [mailto:maxime.leroys@gmail.com] On Behalf Of Maxime > Leroy > Sent: Thursday, 11 June 2026 17.49 > > eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release > via a compound literal, so every field it omits is zeroed to NULL. It > sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving > rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare > and the recycle callbacks NULL. > > In non-debug builds these ops are reached through an unguarded indirect > call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a > thread calling e.g. rte_eth_rx_queue_count() on a port being stopped > dereferences NULL and crashes, while the same race on > rte_eth_rx_burst() > is harmless because the burst ops are reset to dummies. A poll-mode > worker re-checking rx_queue_count before arming the Rx interrupt and > sleeping hits exactly this. > > Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, > so a stopped port behaves like a freshly allocated one: every fast-path > op is a safe no-op, none is NULL. > > Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") > Cc: stable@dpdk.org > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > --- Good catch. Acked-by: Morten Brørup <mb@smartsharesystems.com> Not related to the series, consider sending as separate patch. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-11 16:01 ` Morten Brørup @ 2026-06-11 18:39 ` Maxime Leroy 2026-06-12 15:00 ` Thomas Monjalon 0 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 18:39 UTC (permalink / raw) To: Morten Brørup Cc: Hemant Agrawal, Sachin Saxena, dev, stable, Thomas Monjalon, Andrew Rybchenko, Sunil Kumar Kori [-- Attachment #1: Type: text/plain, Size: 2289 bytes --] Le jeu. 11 juin 2026, 18:01, Morten Brørup <mb@smartsharesystems.com> a écrit : > > From: Maxime Leroy [mailto:maxime.leroys@gmail.com] On Behalf Of Maxime > > Leroy > > Sent: Thursday, 11 June 2026 17.49 > > > > eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release > > via a compound literal, so every field it omits is zeroed to NULL. It > > sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving > > rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare > > and the recycle callbacks NULL. > > > > In non-debug builds these ops are reached through an unguarded indirect > > call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a > > thread calling e.g. rte_eth_rx_queue_count() on a port being stopped > > dereferences NULL and crashes, while the same race on > > rte_eth_rx_burst() > > is harmless because the burst ops are reset to dummies. A poll-mode > > worker re-checking rx_queue_count before arming the Rx interrupt and > > sleeping hits exactly this. > > > > Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, > > so a stopped port behaves like a freshly allocated one: every fast-path > > op is a safe no-op, none is NULL. > > > > Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") > > Cc: stable@dpdk.org > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > > --- > > Good catch. > Acked-by: Morten Brørup <mb@smartsharesystems.com> > > Not related to the series, consider sending as separate patch. > Thanks for the review and Ack. Agreed, this is a generic ethdev fix. I kept it in this series because the NAPI user depends on it. The current Grout NAPI loop arms RX queue interrupts and then re-checks rte_eth_rx_queue_count() before blocking, to avoid sleeping when a packet arrived between the last empty poll and epoll_wait. With the current ethdev reset path, rx_burst is replaced by a dummy callback on stop/release, but rx_queue_count becomes NULL. So if the port is stopped concurrently, the NAPI worker dereferences a NULL function pointer and segfaults on that recheck. I can split it out if maintainers prefer, but then the dpaa2 NAPI series has a real dependency on the standalone ethdev fix. > [-- Attachment #2: Type: text/html, Size: 3478 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-11 18:39 ` Maxime Leroy @ 2026-06-12 15:00 ` Thomas Monjalon 0 siblings, 0 replies; 51+ messages in thread From: Thomas Monjalon @ 2026-06-12 15:00 UTC (permalink / raw) To: Maxime Leroy Cc: Morten Brørup, Hemant Agrawal, Sachin Saxena, dev, stable, Andrew Rybchenko, Sunil Kumar Kori 11/06/2026 20:39, Maxime Leroy: > Le jeu. 11 juin 2026, 18:01, Morten Brørup <mb@smartsharesystems.com> a > écrit : > > > > From: Maxime Leroy [mailto:maxime.leroys@gmail.com] On Behalf Of Maxime > > > Leroy > > > > Good catch. > > Acked-by: Morten Brørup <mb@smartsharesystems.com> > > > > Not related to the series, consider sending as separate patch. > > > Thanks for the review and Ack. > > Agreed, this is a generic ethdev fix. I kept it in this series because the > NAPI user depends on it. > > The current Grout NAPI loop arms RX queue interrupts and then re-checks > rte_eth_rx_queue_count() before blocking, to avoid sleeping when a packet > arrived between the last empty poll and epoll_wait. > > With the current ethdev reset path, rx_burst is replaced by a dummy > callback on stop/release, but rx_queue_count becomes NULL. So if the port > is stopped concurrently, the NAPI worker dereferences a NULL function > pointer and > segfaults on that recheck. > > I can split it out if maintainers prefer, but then the dpaa2 NAPI series > has a real dependency on the standalone ethdev fix. You can keep this patch in the series AND send it separately for a quick merge. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-11 15:49 ` [PATCH 8/9] ethdev: keep fast-path ops valid after port stop Maxime Leroy 2026-06-11 16:01 ` Morten Brørup @ 2026-06-14 19:30 ` Stephen Hemminger 2026-06-15 9:26 ` David Marchand 2 siblings, 0 replies; 51+ messages in thread From: Stephen Hemminger @ 2026-06-14 19:30 UTC (permalink / raw) To: Maxime Leroy Cc: hemant.agrawal, sachin.saxena, dev, stable, Thomas Monjalon, Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori On Thu, 11 Jun 2026 17:49:23 +0200 Maxime Leroy <maxime@leroys.fr> wrote: > eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release > via a compound literal, so every field it omits is zeroed to NULL. It > sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving > rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare > and the recycle callbacks NULL. > > In non-debug builds these ops are reached through an unguarded indirect > call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a > thread calling e.g. rte_eth_rx_queue_count() on a port being stopped > dereferences NULL and crashes, while the same race on rte_eth_rx_burst() > is harmless because the burst ops are reset to dummies. A poll-mode > worker re-checking rx_queue_count before arming the Rx interrupt and > sleeping hits exactly this. > > Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, > so a stopped port behaves like a freshly allocated one: every fast-path > op is a safe no-op, none is NULL. > > Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") > Cc: stable@dpdk.org > Signed-off-by: Maxime Leroy <maxime@leroys.fr> I wonder if queue_count dummy should just return 0 instead of -NOTSUP. There are never going to be packets in the queue to receive. Ditto for transmit and the status routines. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-11 15:49 ` [PATCH 8/9] ethdev: keep fast-path ops valid after port stop Maxime Leroy 2026-06-11 16:01 ` Morten Brørup 2026-06-14 19:30 ` Stephen Hemminger @ 2026-06-15 9:26 ` David Marchand 2026-06-16 9:23 ` Maxime Leroy 2 siblings, 1 reply; 51+ messages in thread From: David Marchand @ 2026-06-15 9:26 UTC (permalink / raw) To: Maxime Leroy Cc: hemant.agrawal, sachin.saxena, dev, stable, Thomas Monjalon, Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori On Thu, 11 Jun 2026 at 17:51, Maxime Leroy <maxime@leroys.fr> wrote: > > eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release > via a compound literal, so every field it omits is zeroed to NULL. It > sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving > rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare > and the recycle callbacks NULL. > > In non-debug builds these ops are reached through an unguarded indirect > call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a > thread calling e.g. rte_eth_rx_queue_count() on a port being stopped > dereferences NULL and crashes, while the same race on rte_eth_rx_burst() > is harmless because the burst ops are reset to dummies. A poll-mode > worker re-checking rx_queue_count before arming the Rx interrupt and > sleeping hits exactly this. > > Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, > so a stopped port behaves like a freshly allocated one: every fast-path > op is a safe no-op, none is NULL. > > Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") > Cc: stable@dpdk.org > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > --- > lib/ethdev/ethdev_private.c | 7 +++++++ > 1 file changed, 7 insertions(+) > > diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c > index 72a0723846..75ea3eedff 100644 > --- a/lib/ethdev/ethdev_private.c > +++ b/lib/ethdev/ethdev_private.c > @@ -263,6 +263,13 @@ eth_dev_fp_ops_reset(struct rte_eth_fp_ops *fpo) > *fpo = (struct rte_eth_fp_ops) { > .rx_pkt_burst = dummy_eth_rx_burst, > .tx_pkt_burst = dummy_eth_tx_burst, > + .tx_pkt_prepare = rte_eth_tx_pkt_prepare_dummy, > + .rx_queue_count = rte_eth_queue_count_dummy, > + .tx_queue_count = rte_eth_queue_count_dummy, > + .rx_descriptor_status = rte_eth_descriptor_status_dummy, > + .tx_descriptor_status = rte_eth_descriptor_status_dummy, > + .recycle_tx_mbufs_reuse = rte_eth_recycle_tx_mbufs_reuse_dummy, > + .recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy, > .rxq = { > .data = (void **)&dummy_queues_array[port_id], > .clbk = dummy_data, Could we replace eth_dev_set_dummy_fops() with a call to eth_dev_fp_ops_reset() in rte_eth_dev_allocate? I don't like keeping two separate helpers. -- David Marchand ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-15 9:26 ` David Marchand @ 2026-06-16 9:23 ` Maxime Leroy 2026-06-16 9:33 ` David Marchand 0 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 9:23 UTC (permalink / raw) To: David Marchand Cc: hemant.agrawal, sachin.saxena, dev, stable, Thomas Monjalon, Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori On Mon, Jun 15, 2026 at 11:26 AM David Marchand <david.marchand@redhat.com> wrote: > > On Thu, 11 Jun 2026 at 17:51, Maxime Leroy <maxime@leroys.fr> wrote: > > > > eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release > > via a compound literal, so every field it omits is zeroed to NULL. It > > sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving > > rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare > > and the recycle callbacks NULL. > > > > In non-debug builds these ops are reached through an unguarded indirect > > call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a > > thread calling e.g. rte_eth_rx_queue_count() on a port being stopped > > dereferences NULL and crashes, while the same race on rte_eth_rx_burst() > > is harmless because the burst ops are reset to dummies. A poll-mode > > worker re-checking rx_queue_count before arming the Rx interrupt and > > sleeping hits exactly this. > > > > Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, > > so a stopped port behaves like a freshly allocated one: every fast-path > > op is a safe no-op, none is NULL. > > > > Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") > > Cc: stable@dpdk.org > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > > --- > > lib/ethdev/ethdev_private.c | 7 +++++++ > > 1 file changed, 7 insertions(+) > > > > diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c > > index 72a0723846..75ea3eedff 100644 > > --- a/lib/ethdev/ethdev_private.c > > +++ b/lib/ethdev/ethdev_private.c > > @@ -263,6 +263,13 @@ eth_dev_fp_ops_reset(struct rte_eth_fp_ops *fpo) > > *fpo = (struct rte_eth_fp_ops) { > > .rx_pkt_burst = dummy_eth_rx_burst, > > .tx_pkt_burst = dummy_eth_tx_burst, > > + .tx_pkt_prepare = rte_eth_tx_pkt_prepare_dummy, > > + .rx_queue_count = rte_eth_queue_count_dummy, > > + .tx_queue_count = rte_eth_queue_count_dummy, > > + .rx_descriptor_status = rte_eth_descriptor_status_dummy, > > + .tx_descriptor_status = rte_eth_descriptor_status_dummy, > > + .recycle_tx_mbufs_reuse = rte_eth_recycle_tx_mbufs_reuse_dummy, > > + .recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy, > > .rxq = { > > .data = (void **)&dummy_queues_array[port_id], > > .clbk = dummy_data, > > Could we replace eth_dev_set_dummy_fops() with a call to > eth_dev_fp_ops_reset() in rte_eth_dev_allocate? > I don't like keeping two separate helpers. > > > -- > David Marchand > Hi David, Thanks for the review. Avoiding the duplication is a good idea, but I could not find a clean way to do it: eth_dev_set_dummy_fops() and eth_dev_fp_ops_reset() cannot be unified without making things worse. - They write two different structures. eth_dev_set_dummy_fops() sets struct rte_eth_dev (the source ops); rte_eth_dev_allocate() fills eth_dev->*, and eth_dev_fp_ops_setup() then copies eth_dev->* into the per-port struct rte_eth_fp_ops. eth_dev_fp_ops_reset() writes that fast-path table entry directly. So calling fp_ops_reset() from rte_eth_dev_allocate() would populate the wrong structure. - The burst dummies are intentionally different. set_dummy_fops() uses the silent rte_eth_pkt_burst_dummy (a freshly allocated, not-yet-started port is benign), while fp_ops_reset() uses dummy_eth_rx_burst/dummy_eth_tx_burst, which log an error and dump the stack because hitting the data path on a stopped port is a misuse worth flagging. - fp_ops_reset() also wires fpo->rxq/txq to the dummy_queues_array/dummy_data used by the fast-path table, with no equivalent on rte_eth_dev. The only shared part is the non-burst dummy assignments, but factoring those across the two different struct types would require a token macro, and I don't have a clean solution for it. So for now I have kept the two helpers as they are. Suggestions welcome. Thanks, Maxime ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop 2026-06-16 9:23 ` Maxime Leroy @ 2026-06-16 9:33 ` David Marchand 0 siblings, 0 replies; 51+ messages in thread From: David Marchand @ 2026-06-16 9:33 UTC (permalink / raw) To: Maxime Leroy Cc: hemant.agrawal, sachin.saxena, dev, stable, Thomas Monjalon, Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori On Tue, 16 Jun 2026 at 11:23, Maxime Leroy <maxime@leroys.fr> wrote: > On Mon, Jun 15, 2026 at 11:26 AM David Marchand > <david.marchand@redhat.com> wrote: > > > > On Thu, 11 Jun 2026 at 17:51, Maxime Leroy <maxime@leroys.fr> wrote: > > > > > > eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release > > > via a compound literal, so every field it omits is zeroed to NULL. It > > > sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving > > > rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare > > > and the recycle callbacks NULL. > > > > > > In non-debug builds these ops are reached through an unguarded indirect > > > call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a > > > thread calling e.g. rte_eth_rx_queue_count() on a port being stopped > > > dereferences NULL and crashes, while the same race on rte_eth_rx_burst() > > > is harmless because the burst ops are reset to dummies. A poll-mode > > > worker re-checking rx_queue_count before arming the Rx interrupt and > > > sleeping hits exactly this. > > > > > > Reset these ops to the same dummies eth_dev_set_dummy_fops() installs, > > > so a stopped port behaves like a freshly allocated one: every fast-path > > > op is a safe no-op, none is NULL. > > > > > > Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path") > > > Cc: stable@dpdk.org > > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > > > --- > > > lib/ethdev/ethdev_private.c | 7 +++++++ > > > 1 file changed, 7 insertions(+) > > > > > > diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c > > > index 72a0723846..75ea3eedff 100644 > > > --- a/lib/ethdev/ethdev_private.c > > > +++ b/lib/ethdev/ethdev_private.c > > > @@ -263,6 +263,13 @@ eth_dev_fp_ops_reset(struct rte_eth_fp_ops *fpo) > > > *fpo = (struct rte_eth_fp_ops) { > > > .rx_pkt_burst = dummy_eth_rx_burst, > > > .tx_pkt_burst = dummy_eth_tx_burst, > > > + .tx_pkt_prepare = rte_eth_tx_pkt_prepare_dummy, > > > + .rx_queue_count = rte_eth_queue_count_dummy, > > > + .tx_queue_count = rte_eth_queue_count_dummy, > > > + .rx_descriptor_status = rte_eth_descriptor_status_dummy, > > > + .tx_descriptor_status = rte_eth_descriptor_status_dummy, > > > + .recycle_tx_mbufs_reuse = rte_eth_recycle_tx_mbufs_reuse_dummy, > > > + .recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy, > > > .rxq = { > > > .data = (void **)&dummy_queues_array[port_id], > > > .clbk = dummy_data, > > > > Could we replace eth_dev_set_dummy_fops() with a call to > > eth_dev_fp_ops_reset() in rte_eth_dev_allocate? > > I don't like keeping two separate helpers. > > Thanks for the review. > > Avoiding the duplication is a good idea, but I could not find a clean > way to do it: eth_dev_set_dummy_fops() and eth_dev_fp_ops_reset() > cannot be unified without making things worse. > > - They write two different structures. eth_dev_set_dummy_fops() sets > struct rte_eth_dev (the source ops); rte_eth_dev_allocate() fills > eth_dev->*, and eth_dev_fp_ops_setup() then copies eth_dev->* into the > per-port struct rte_eth_fp_ops. eth_dev_fp_ops_reset() writes that > fast-path table entry directly. So calling fp_ops_reset() from > rte_eth_dev_allocate() would populate the wrong structure. > > - The burst dummies are intentionally different. set_dummy_fops() uses > the silent rte_eth_pkt_burst_dummy (a freshly allocated, > not-yet-started port is benign), while fp_ops_reset() uses > dummy_eth_rx_burst/dummy_eth_tx_burst, which log an error and dump the > stack because hitting the data path on a stopped port is a misuse > worth flagging. > > - fp_ops_reset() also wires fpo->rxq/txq to the > dummy_queues_array/dummy_data used by the fast-path table, with no > equivalent on rte_eth_dev. > > The only shared part is the non-burst dummy assignments, but factoring > those across the two different struct types would require a token > macro, and I don't have a clean solution for it. So for now I have > kept the two helpers as they are. Suggestions welcome. Ok, I had missed the separate structs. Let's keep it simple, and avoid adding macros. Can you add a comment that those helpers should be kept in sync? -- David Marchand ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (7 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 8/9] ethdev: keep fast-path ops valid after port stop Maxime Leroy @ 2026-06-11 15:49 ` Maxime Leroy 2026-06-11 15:56 ` Morten Brørup 2026-06-11 17:30 ` Stephen Hemminger 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 9 siblings, 2 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 15:49 UTC (permalink / raw) To: hemant.agrawal, sachin.saxena; +Cc: dev, Maxime Leroy RTE_ETH_RX_OFFLOAD_VLAN_STRIP is advertised, but no hardware VLAN strip backs it: when enabled, the Rx burst calls rte_vlan_strip() on every frame, a software op masquerading as a hardware offload. It saves a forwarding application nothing: the datapath reads the L2 header anyway to classify or strip. The offload does not remove that read, it relocates it into the driver Rx burst, where it is far more expensive. The cost is a matter of timing. rte_vlan_strip() reaches the L2 header through rte_pktmbuf_mtod(), which dereferences mbuf->buf_addr. On a freshly recycled buffer that mbuf cacheline is cold. eth_fd_to_mbuf() has just written other fields of it (data_off, ol_flags), but buf_addr is a persistent field it does not rewrite. A write does not stall: it posts to the store buffer while the line fills in the background, and the rewritten fields are forwarded straight from there. buf_addr has nothing to forward, so it must be read from the line, whose fill is still in flight, and the read stalls. The ethertype read that follows, on the cold payload line, stalls again. Read later by the application, when the fill has completed, the same read hits. The offload just performs it at the worst possible moment. Measured on a single-core port-to-port forwarding test over two 10G ports (one core at 2 GHz, 64-byte untagged frames): - throughput 4.22 -> 5.00 Mpps (+18 percent) - IPC 0.93 -> 1.25: the cost was memory stall, not compute - L3/DRAM-bound L2 refills 319M -> 200M over 10s (-37 percent) perf confirms it: with the offload, the buf_addr load (the cold mbuf field) and the payload load account for about 84 percent of the Rx burst's L2 refills; removing it, those vanish and only the inherent DQRR dequeue misses remain. Stop advertising VLAN_STRIP and remove the rte_vlan_strip() calls from every Rx path. This is a behavioural change: the tag is left in the frame, so an application must strip it itself, on the L2 header it already reads. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/rel_notes/release_26_07.rst | 3 +++ drivers/net/dpaa2/dpaa2_ethdev.c | 1 - drivers/net/dpaa2/dpaa2_rxtx.c | 23 +++-------------------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index 87c7c57bcc..9d01099dad 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -130,6 +130,9 @@ New Features * Added RSS RETA query and update support. * Added Rx queue interrupt support. + * Removed the software VLAN strip offload: ``RTE_ETH_RX_OFFLOAD_VLAN_STRIP`` + is no longer advertised, as no hardware strip backs it. An application + that needs the tag removed must now strip it itself. * **Updated PCAP ethernet driver.** diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index fb117e761f..b3ea826db9 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -48,7 +48,6 @@ static uint64_t dev_rx_offloads_sup = RTE_ETH_RX_OFFLOAD_SCTP_CKSUM | RTE_ETH_RX_OFFLOAD_OUTER_IPV4_CKSUM | RTE_ETH_RX_OFFLOAD_OUTER_UDP_CKSUM | - RTE_ETH_RX_OFFLOAD_VLAN_STRIP | RTE_ETH_RX_OFFLOAD_VLAN_FILTER | RTE_ETH_RX_OFFLOAD_TIMESTAMP; diff --git a/drivers/net/dpaa2/dpaa2_rxtx.c b/drivers/net/dpaa2/dpaa2_rxtx.c index 189accc1de..d16e4f8f35 100644 --- a/drivers/net/dpaa2/dpaa2_rxtx.c +++ b/drivers/net/dpaa2/dpaa2_rxtx.c @@ -890,10 +890,6 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) } #endif - if (eth_data->dev_conf.rxmode.offloads & - RTE_ETH_RX_OFFLOAD_VLAN_STRIP) - rte_vlan_strip(bufs[num_rx]); - dq_storage++; num_rx++; } while (pending); @@ -922,22 +918,14 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) return num_rx; } -/* Convert a DQRR'd FD (single or scatter-gather) to an mbuf and apply software - * VLAN strip, like the poll path. - */ +/* Convert a DQRR'd FD (single or scatter-gather) to an mbuf. */ static inline struct rte_mbuf * dpaa2_dqrr_fd_to_mbuf(const struct qbman_fd *fd, struct rte_eth_dev_data *eth_data) { - struct rte_mbuf *m; - if (unlikely(DPAA2_FD_GET_FORMAT(fd) == qbman_fd_sg)) - m = eth_sg_fd_to_mbuf(fd, eth_data->port_id); - else - m = eth_fd_to_mbuf(fd, eth_data->port_id); - if (eth_data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_VLAN_STRIP) - rte_vlan_strip(m); - return m; + return eth_sg_fd_to_mbuf(fd, eth_data->port_id); + return eth_fd_to_mbuf(fd, eth_data->port_id); } /* prefetch a DQRR'd FD's HW annotation (parse area) ahead of conversion */ @@ -1222,11 +1210,6 @@ dpaa2_dev_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) } #endif - if (eth_data->dev_conf.rxmode.offloads & - RTE_ETH_RX_OFFLOAD_VLAN_STRIP) { - rte_vlan_strip(bufs[num_rx]); - } - dq_storage++; num_rx++; num_pulled++; -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* RE: [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload 2026-06-11 15:49 ` [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy @ 2026-06-11 15:56 ` Morten Brørup 2026-06-11 16:13 ` Morten Brørup 2026-06-11 16:58 ` Maxime Leroy 2026-06-11 17:30 ` Stephen Hemminger 1 sibling, 2 replies; 51+ messages in thread From: Morten Brørup @ 2026-06-11 15:56 UTC (permalink / raw) To: Maxime Leroy, hemant.agrawal, sachin.saxena; +Cc: dev This patch is unrelated to the series. ^ permalink raw reply [flat|nested] 51+ messages in thread
* RE: [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload 2026-06-11 15:56 ` Morten Brørup @ 2026-06-11 16:13 ` Morten Brørup 2026-06-11 16:58 ` Maxime Leroy 1 sibling, 0 replies; 51+ messages in thread From: Morten Brørup @ 2026-06-11 16:13 UTC (permalink / raw) To: Maxime Leroy, hemant.agrawal, sachin.saxena; +Cc: dev > This patch is unrelated to the series. And also, Acked-by: Morten Brørup <mb@smartsharesystems.com> We should take note of this for other drivers! ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload 2026-06-11 15:56 ` Morten Brørup 2026-06-11 16:13 ` Morten Brørup @ 2026-06-11 16:58 ` Maxime Leroy 1 sibling, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-11 16:58 UTC (permalink / raw) To: Morten Brørup; +Cc: Hemant Agrawal, Sachin Saxena, dev [-- Attachment #1: Type: text/plain, Size: 717 bytes --] Le jeu. 11 juin 2026, 17:56, Morten Brørup <mb@smartsharesystems.com> a écrit : > This patch is unrelated to the series. > > > Splitting this would create an ordering problem. If the NAPI series is merged with a software VLAN strip implementation and the cleanup removing the fake VLAN_STRIP offload is merged separately, the two can land in either order and leave the PMD with inconsistent Rx paths. The new NAPI/DQRR path must match the offloads reported by the PMD at the end of the series. Since VLAN_STRIP is not a real dpaa2 hardware offload, this series removes the advertised offload and the software rte_vlan_strip() calls together, so all Rx paths remain consistent at each merge point. [-- Attachment #2: Type: text/html, Size: 1218 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload 2026-06-11 15:49 ` [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy 2026-06-11 15:56 ` Morten Brørup @ 2026-06-11 17:30 ` Stephen Hemminger 2026-06-12 5:42 ` Maxime Leroy 1 sibling, 1 reply; 51+ messages in thread From: Stephen Hemminger @ 2026-06-11 17:30 UTC (permalink / raw) To: Maxime Leroy; +Cc: hemant.agrawal, sachin.saxena, dev On Thu, 11 Jun 2026 17:49:24 +0200 Maxime Leroy <maxime@leroys.fr> wrote: > It saves a forwarding application nothing: the datapath reads the L2 > header anyway to classify or strip. The offload does not remove that > read, it relocates it into the driver Rx burst, where it is far more > expensive. > > The cost is a matter of timing. rte_vlan_strip() reaches the L2 header > through rte_pktmbuf_mtod(), which dereferences mbuf->buf_addr. On a > freshly recycled buffer that mbuf cacheline is cold. eth_fd_to_mbuf() > has just written other fields of it (data_off, ol_flags), but buf_addr > is a persistent field it does not rewrite. A write does not stall: it > posts to the store buffer while the line fills in the background, and > the rewritten fields are forwarded straight from there. buf_addr has > nothing to forward, so it must be read from the line, whose fill is > still in flight, and the read stalls. The ethertype read that follows, > on the cold payload line, stalls again. Read later by the application, > when the fill has completed, the same read hits. The offload just > performs it at the worst possible moment. > > Measured on a single-core port-to-port forwarding test over two 10G > ports (one core at 2 GHz, 64-byte untagged frames): > > - throughput 4.22 -> 5.00 Mpps (+18 percent) > - IPC 0.93 -> 1.25: the cost was memory stall, not compute > - L3/DRAM-bound L2 refills 319M -> 200M over 10s (-37 percent) > > perf confirms it: with the offload, the buf_addr load (the cold mbuf > field) and the payload load account for about 84 percent of the Rx > burst's L2 refills; removing it, those vanish and only the inherent DQRR > dequeue misses remain. > > Stop advertising VLAN_STRIP and remove the rte_vlan_strip() calls from > every Rx path. This is a behavioural change: the tag is left in the > frame, so an application must strip it itself, on the L2 header it > already reads. > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > --- In general I agree, but you overstate the impact. Any real application is going to look at the mbuf anyway. Relying on testpmd numbers is BS. The NBL driver does the same thing. So does PCAP but it has no choice, and is slow anyway. Virtio/vhost does as well. ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload 2026-06-11 17:30 ` Stephen Hemminger @ 2026-06-12 5:42 ` Maxime Leroy 0 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-12 5:42 UTC (permalink / raw) To: Stephen Hemminger; +Cc: Hemant Agrawal, Sachin Saxena, dev [-- Attachment #1: Type: text/plain, Size: 3898 bytes --] Le jeu. 11 juin 2026, 19:31, Stephen Hemminger <stephen@networkplumber.org> a écrit : > On Thu, 11 Jun 2026 17:49:24 +0200 > Maxime Leroy <maxime@leroys.fr> wrote: > > > It saves a forwarding application nothing: the datapath reads the L2 > > header anyway to classify or strip. The offload does not remove that > > read, it relocates it into the driver Rx burst, where it is far more > > expensive. > > > > The cost is a matter of timing. rte_vlan_strip() reaches the L2 header > > through rte_pktmbuf_mtod(), which dereferences mbuf->buf_addr. On a > > freshly recycled buffer that mbuf cacheline is cold. eth_fd_to_mbuf() > > has just written other fields of it (data_off, ol_flags), but buf_addr > > is a persistent field it does not rewrite. A write does not stall: it > > posts to the store buffer while the line fills in the background, and > > the rewritten fields are forwarded straight from there. buf_addr has > > nothing to forward, so it must be read from the line, whose fill is > > still in flight, and the read stalls. The ethertype read that follows, > > on the cold payload line, stalls again. Read later by the application, > > when the fill has completed, the same read hits. The offload just > > performs it at the worst possible moment. > > > > Measured on a single-core port-to-port forwarding test over two 10G > > ports (one core at 2 GHz, 64-byte untagged frames): > > > > - throughput 4.22 -> 5.00 Mpps (+18 percent) > > - IPC 0.93 -> 1.25: the cost was memory stall, not compute > > - L3/DRAM-bound L2 refills 319M -> 200M over 10s (-37 percent) > > > > perf confirms it: with the offload, the buf_addr load (the cold mbuf > > field) and the payload load account for about 84 percent of the Rx > > burst's L2 refills; removing it, those vanish and only the inherent DQRR > > dequeue misses remain. > > > > Stop advertising VLAN_STRIP and remove the rte_vlan_strip() calls from > > every Rx path. This is a behavioural change: the tag is left in the > > frame, so an application must strip it itself, on the L2 header it > > already reads. > > > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > > --- > > In general I agree, but you overstate the impact. Any real application > is going to look at the mbuf anyway. Relying on testpmd numbers is BS. > > The NBL driver does the same thing. > So does PCAP but it has no choice, and is slow anyway. > Virtio/vhost does as well. This was not measured with testpmd, but with Grout in I/O forwarding mode. The comparison is exactly between Grout's software fallback and the advertised offload path. Without VLAN_STRIP, Grout's rx_process() reads the Ethernet header and strips the VLAN tag itself if needed. With VLAN_STRIP enabled, Grout uses rx_offload_process(), which only consumes RTE_MBUF_F_RX_VLAN_STRIPPED/vlan_tci and does not inspect the Ethernet header for VLAN stripping. For dpaa2, however, VLAN_STRIP is not done by the device. The PMD implements the advertised offload by calling rte_vlan_strip() in the Rx burst path. So enabling the "offload" just moves the same software work from Grout into the driver. The cost is timing. rte_vlan_strip() calls rte_pktmbuf_mtod(), which needs mbuf->buf_addr. That value is persistent mbuf metadata; it is not produced by the FD-to-mbuf conversion. eth_fd_to_mbuf() has just written other mbuf fields such as data_off and ol_flags; those writes can be posted or forwarded, but they do not provide buf_addr. If the mbuf cacheline is cold, the buf_addr load has to wait for that line to be fetched before the driver can reach the Ethernet header. Grout does the same L2 read later in rx_process(), where it is already processing L2. So the fake PMD offload performs the same software fallback, but injects an extra mbuf-metadata dependency at a worse point in the Rx burst path. [-- Attachment #2: Type: text/html, Size: 4993 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (8 preceding siblings ...) 2026-06-11 15:49 ` [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 1/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy ` (7 more replies) 9 siblings, 8 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev; +Cc: Maxime Leroy This series lets a dpaa2 worker sleep on a queue's data-availability notification instead of busy-polling, exposed through the generic rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, arm the interrupt and sleep when the queue runs dry). Why it is not a trivial .rx_queue_intr_enable ---------------------------------------------- A worker wakes on its software portal's DQRI, which fires when the portal's DQRR holds frames. The default dpaa2 Rx burst pulls frames from the FQ with a volatile dequeue and cannot be interrupt-driven; to wake on the DQRI the FQ must instead be pushed to the portal's DQRR. The natural dpni_set_queue with a notification destination would have to target the worker's portal, but that portal is only known once a worker affines, after dev_start, and that MC command holds the global MC lock long enough to wedge the firmware while traffic runs. So the bind cannot be done late, against the polling lcore. Design ------ Each Rx FQ is bound to its own DPCON channel, statically, at dev_start while the dpni is still disabled (no knowledge of the polling lcore). A worker later subscribes its own ethrx portal to the channel and arms the DQRI in rx_queue_intr_enable, a one-shot per-portal op, never the wedging set_queue. One portal serves every queue a worker owns, so the DQRR burst demuxes frames to their FQ by fqd_ctx; foreign frames are parked in the target queue's stash, so the application polls all its queues after a wakeup, the same scheduling contract as plain DPDK polling. A queue can be re-homed to another lcore at runtime with no set_queue and no port stop. This reuses the event PMD's pushed/DQRR model but with one DPCON per FQ and static affinity (no QBMan scheduling), so the DPCON allocator is moved from the event driver to the fslmc bus and shared. Patches 1 and 2 move the DPCON allocator to the fslmc bus and make the portal DQRI epoll optional; patch 3 adds the interrupt support proper and patch 4 tunes the DQRI coalescing holdoff. Patch 5 (rx_queue_count NULL on the primary process) is a real fix the path depends on and uncovered, tagged for stable and backportable on its own. Patch 6 (drop the software VLAN strip) is an independent net/dpaa2 change the interrupt path does not require. The path also depends on two fixes sent separately: an eal change so the shared portal eventfd does not fail with -EEXIST (already applied to main) and the ethdev fix for fast-path ops left NULL after port stop (see Depends-on below). Tested on LX2160A (lx2160acex7). Depends-on: series-38450 ("ethdev: fix fast-path ops on a stopped port") v2: - Dropped the RSS RETA patch, an independent net/dpaa2 change the interrupt path does not require; it will be sent as its own series. - Dropped the ethdev fast-path ops fix; it is now a standalone series (Depends-on above). - Dropped the eal/interrupts -EEXIST fix, applied to main by David Marchand. - Declared qbman_swp_interrupt_set_inhibit and qbman_swp_dqrr_size __rte_internal (David Marchand). - Minor formatting cleanup in the Rx interrupt setup. Maxime Leroy (6): bus/fslmc: move DPCON management from event driver to bus bus/fslmc/dpio: make the portal DQRI epoll optional net/dpaa2: support Rx queue interrupts bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff net/dpaa2: fix Rx queue count for primary process net/dpaa2: drop the fake software VLAN strip offload doc/guides/nics/dpaa2.rst | 10 + doc/guides/nics/features/dpaa2.ini | 1 + doc/guides/rel_notes/release_26_07.rst | 7 + drivers/bus/fslmc/meson.build | 1 + .../fslmc/portal}/dpaa2_hw_dpcon.c | 16 +- drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 113 ++++-- drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 + drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 35 +- .../fslmc/qbman/include/fsl_qbman_portal.h | 11 + drivers/bus/fslmc/qbman/qbman_portal.c | 7 + drivers/event/dpaa2/dpaa2_eventdev.h | 5 +- drivers/event/dpaa2/meson.build | 1 - drivers/net/dpaa2/dpaa2_ethdev.c | 349 +++++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 10 + drivers/net/dpaa2/dpaa2_rxtx.c | 123 +++++- 15 files changed, 647 insertions(+), 54 deletions(-) rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (90%) -- 2.43.0 ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v2 1/6] bus/fslmc: move DPCON management from event driver to bus 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 2/6] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy ` (6 subsequent siblings) 7 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev; +Cc: Maxime Leroy, Hemant Agrawal, Sachin Saxena The DPCON allocation helpers (rte_dpaa2_alloc_dpcon_dev / rte_dpaa2_free_dpcon_dev) lived in the event driver, but a notification channel is a generic QBMan resource. Move dpaa2_hw_dpcon.c to the fslmc bus and export the helpers as internal symbols so both the event PMD and the net driver's rx-queue interrupt path can draw channels from the same pool. No functional change. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/meson.build | 1 + .../dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c | 16 +++++++--------- drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 8 ++++++++ drivers/event/dpaa2/dpaa2_eventdev.h | 5 +++-- drivers/event/dpaa2/meson.build | 1 - 5 files changed, 19 insertions(+), 12 deletions(-) rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (90%) diff --git a/drivers/bus/fslmc/meson.build b/drivers/bus/fslmc/meson.build index ceae1c6c11..50d9e91a37 100644 --- a/drivers/bus/fslmc/meson.build +++ b/drivers/bus/fslmc/meson.build @@ -22,6 +22,7 @@ sources = files( 'mc/mc_sys.c', 'portal/dpaa2_hw_dpbp.c', 'portal/dpaa2_hw_dpci.c', + 'portal/dpaa2_hw_dpcon.c', 'portal/dpaa2_hw_dpio.c', 'portal/dpaa2_hw_dprc.c', 'qbman/qbman_portal.c', diff --git a/drivers/event/dpaa2/dpaa2_hw_dpcon.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c similarity index 90% rename from drivers/event/dpaa2/dpaa2_hw_dpcon.c rename to drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c index ea5b0d4b85..6fd96ec0b9 100644 --- a/drivers/event/dpaa2/dpaa2_hw_dpcon.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c @@ -18,13 +18,12 @@ #include <rte_cycles.h> #include <rte_kvargs.h> #include <dev_driver.h> -#include <ethdev_driver.h> +#include <eal_export.h> #include <bus_fslmc_driver.h> #include <mc/fsl_dpcon.h> #include <portal/dpaa2_hw_pvt.h> -#include "dpaa2_eventdev.h" -#include "dpaa2_eventdev_logs.h" +#include <fslmc_logs.h> TAILQ_HEAD(dpcon_dev_list, dpaa2_dpcon_dev); static struct dpcon_dev_list dpcon_dev_list @@ -55,8 +54,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, /* Allocate DPAA2 dpcon handle */ dpcon_node = rte_malloc(NULL, sizeof(struct dpaa2_dpcon_dev), 0); if (!dpcon_node) { - DPAA2_EVENTDEV_ERR( - "Memory allocation failed for dpcon device"); + DPAA2_BUS_ERR("Memory allocation failed for dpcon device"); return -1; } @@ -65,8 +63,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, ret = dpcon_open(&dpcon_node->dpcon, CMD_PRI_LOW, dpcon_id, &dpcon_node->token); if (ret) { - DPAA2_EVENTDEV_ERR("Unable to open dpcon device: err(%d)", - ret); + DPAA2_BUS_ERR("Unable to open dpcon device: err(%d)", ret); rte_free(dpcon_node); return -1; } @@ -75,8 +72,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, ret = dpcon_get_attributes(&dpcon_node->dpcon, CMD_PRI_LOW, dpcon_node->token, &attr); if (ret != 0) { - DPAA2_EVENTDEV_ERR("dpcon attribute fetch failed: err(%d)", - ret); + DPAA2_BUS_ERR("dpcon attribute fetch failed: err(%d)", ret); rte_free(dpcon_node); return -1; } @@ -92,6 +88,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, return 0; } +RTE_EXPORT_INTERNAL_SYMBOL(rte_dpaa2_alloc_dpcon_dev) struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void) { struct dpaa2_dpcon_dev *dpcon_dev = NULL; @@ -105,6 +102,7 @@ struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void) return dpcon_dev; } +RTE_EXPORT_INTERNAL_SYMBOL(rte_dpaa2_free_dpcon_dev) void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon) { struct dpaa2_dpcon_dev *dpcon_dev = NULL; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h index e625a5c035..79a2ec41e3 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h @@ -274,6 +274,14 @@ struct dpaa2_dpcon_dev { uint8_t channel_index; }; +/* DPCON channel allocation -- managed by the fslmc bus so both the net + * NAPI/DQRR rx path and the event PMD can grab channels. + */ +__rte_internal +struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void); +__rte_internal +void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon); + /* Refer to Table 7-3 in SEC BG */ #define QBMAN_FLE_WORD4_FMT_SBF 0x0 /* Single buffer frame */ #define QBMAN_FLE_WORD4_FMT_SGE 0x2 /* Scatter gather frame */ diff --git a/drivers/event/dpaa2/dpaa2_eventdev.h b/drivers/event/dpaa2/dpaa2_eventdev.h index bb87bdbab2..f53efce61c 100644 --- a/drivers/event/dpaa2/dpaa2_eventdev.h +++ b/drivers/event/dpaa2/dpaa2_eventdev.h @@ -85,8 +85,9 @@ struct dpaa2_eventdev { uint32_t event_dev_cfg; }; -struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void); -void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon); +/* rte_dpaa2_alloc_dpcon_dev()/rte_dpaa2_free_dpcon_dev() now live in the fslmc + * bus (portal/dpaa2_hw_pvt.h), which this header's includers already pull in. + */ int test_eventdev_dpaa2(void); diff --git a/drivers/event/dpaa2/meson.build b/drivers/event/dpaa2/meson.build index dd5063af43..62b8507652 100644 --- a/drivers/event/dpaa2/meson.build +++ b/drivers/event/dpaa2/meson.build @@ -7,7 +7,6 @@ if not is_linux endif deps += ['bus_vdev', 'net_dpaa2', 'crypto_dpaa2_sec'] sources = files( - 'dpaa2_hw_dpcon.c', 'dpaa2_eventdev.c', 'dpaa2_eventdev_selftest.c', ) -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 2/6] bus/fslmc/dpio: make the portal DQRI epoll optional 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 1/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 3/6] net/dpaa2: support Rx queue interrupts Maxime Leroy ` (5 subsequent siblings) 7 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev; +Cc: Maxime Leroy, Hemant Agrawal, Sachin Saxena dpaa2_dpio_intr_init() builds a private epoll instance the event PMD sleeps on. The upcoming net rx-queue-interrupt path waits on the application's own epoll instead, so that instance would be built but never used. Add a build_epoll parameter: pass true to build it (event PMD), false to skip the epoll_create/epoll_ctl. epoll_fd is set to -1 when none is built and closed in intr_deinit only when valid. The sole caller passes true: no functional change. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 44 +++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index 2a9e519668..3a5abb2e6d 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -205,13 +205,12 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } -static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev) +static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) { struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; int threshold = 0x3, timeout = 0xFF; - dpio_epoll_fd = epoll_create(1); ret = rte_dpaa2_intr_enable(dpio_dev->intr_handle, 0); if (ret) { DPAA2_BUS_ERR("Interrupt registration failed"); @@ -231,16 +230,34 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev) qbman_swp_dqrr_thrshld_write(dpio_dev->sw_portal, threshold); qbman_swp_intr_timeout_write(dpio_dev->sw_portal, timeout); - eventfd = rte_intr_fd_get(dpio_dev->intr_handle); - epoll_ev.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_ev.data.fd = eventfd; + dpio_dev->epoll_fd = -1; - ret = epoll_ctl(dpio_epoll_fd, EPOLL_CTL_ADD, eventfd, &epoll_ev); - if (ret < 0) { - DPAA2_BUS_ERR("epoll_ctl failed"); - return -1; + /* The event PMD dequeues by sleeping on a private epoll instance owned + * by the portal, so build it here. A caller that waits on another + * epoll (the net rx-queue-interrupt path uses the application's) skips + * this. + */ + if (build_epoll) { + dpio_epoll_fd = epoll_create(1); + if (dpio_epoll_fd < 0) { + DPAA2_BUS_ERR("epoll_create failed"); + rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); + return -1; + } + + eventfd = rte_intr_fd_get(dpio_dev->intr_handle); + epoll_ev.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_ev.data.fd = eventfd; + + ret = epoll_ctl(dpio_epoll_fd, EPOLL_CTL_ADD, eventfd, &epoll_ev); + if (ret < 0) { + DPAA2_BUS_ERR("epoll_ctl failed"); + rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); + close(dpio_epoll_fd); + return -1; + } + dpio_dev->epoll_fd = dpio_epoll_fd; } - dpio_dev->epoll_fd = dpio_epoll_fd; return 0; } @@ -253,7 +270,10 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) if (ret) DPAA2_BUS_ERR("DPIO interrupt disable failed"); - close(dpio_dev->epoll_fd); + if (dpio_dev->epoll_fd >= 0) { + close(dpio_dev->epoll_fd); + dpio_dev->epoll_fd = -1; + } } #endif @@ -277,7 +297,7 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) } #ifdef RTE_EVENT_DPAA2 - if (dpaa2_dpio_intr_init(dpio_dev)) { + if (dpaa2_dpio_intr_init(dpio_dev, true)) { DPAA2_BUS_ERR("Interrupt registration failed for dpio"); return -1; } -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 3/6] net/dpaa2: support Rx queue interrupts 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 1/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 2/6] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-18 8:46 ` David Marchand 2026-06-16 10:27 ` [PATCH v2 4/6] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff Maxime Leroy ` (4 subsequent siblings) 7 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev; +Cc: Maxime Leroy, Hemant Agrawal, Sachin Saxena Implement .rx_queue_intr_enable / .rx_queue_intr_disable so a worker can sleep on a queue's data-availability notification instead of busy-polling, through the generic rte_eth_dev_rx_intr_* API. A worker wakes on its software portal's DQRI, which fires when the portal's DQRR holds frames, so the Rx FQ must be scheduled to a channel that portal dequeues. The natural dpni_set_queue with a notification destination holds the global MC lock long enough to wedge the firmware and must target a disabled dpni. But the polling portal is only known once a worker affines, after dev_start, so the destination cannot be the worker's portal. Bind each Rx FQ to its own DPCON channel instead. The default Rx burst pulls frames from the FQ with a volatile dequeue and cannot be interrupt-driven; to wake on the DQRI the FQ must be pushed to the portal's DQRR. dev_start issues the DEST_DPCON set_queue statically on the still-disabled dpni with no knowledge of the polling lcore; a worker later subscribes its own ethrx portal to the channel and arms the DQRI in rx_queue_intr_enable (a one-shot per-portal MC op plus QBMan, never the wedging set_queue). This pushed/DQRR consumption is how the event PMD works, but the DPCON use differs. The event PMD uses one DPCON per worker, concentrates N FQs onto it, and lets the QBMan scheduler load-balance events across cores. Here affinity is static and there is no scheduling, so each FQ gets its own DPCON (one per FQ, more channels, drawn from the shared pool that the DPCON move to the fslmc bus now feeds), bound once at dev_start before the lcore is known. Frames are delivered by rte_eth_rx_burst (dpaa2_dev_rx_dqrr), not as events via rte_event_dequeue. rte_eth_dev_rx_intr_enable(q) subscribes the lcore portal to q's DPCON and arms the DQRI. rte_eth_dev_rx_intr_ctl_q(q) adds q's eventfd (the portal DQRI fd) to the thread epoll. wire | [ DPMAC ] | [ DPNI ] (1) | TC0: FQ0 FQ1 FQ2 FQ3 (2) | | | | (3) [DPCON][DPCON][DPCON][DPCON] \ | | / (4) [ DPIO A ] [ DPIO B ] (5) | | DQRR DQRR (6) | | DQRI DQRI (7) | | eventfd eventfd (8) | | rte_epoll_wait rte_epoll_wait (9) | | dpaa2_dev_rx_dqrr (10) (1) WRIOP picks a TC (QoS), then RSS-hashes within the TC to an FQ (2) FQ0..FQ3 are the rte_eth Rx queues (3) dpni_set_queue(DEST_DPCON): one DPCON per FQ (4) the lcore portal subscribes to its DPCONs (push_set) (5) one QBMan software portal per lcore (6) QMan pushes the FDs into the portal DQRR (7) DQRI is raised when the DQRR is non-empty (8) a portal's queues share one fd (its DQRI eventfd) (9) worker sleeps here when all its queues are idle (10) dpaa2_dev_rx_dqrr drains the DQRR, demuxes FDs to FQs by fqd_ctx The DQRI and eventfd are portal-wide: a queue's eventfd is its portal's DQRI fd, and the inhibit bit is refcounted by armed queues so disabling one queue never masks a sibling. The static per-queue bind also lets a queue be re-homed to another lcore at runtime, the new worker reclaiming the channel, with no set_queue and no port stop. On single-core 64-byte forwarding this interrupt path runs at ~5.0 Mpps versus ~5.86 Mpps polling: per-frame DQRR demux and consume cost about 15 percent over the polling batch dequeue. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/nics/features/dpaa2.ini | 1 + doc/guides/rel_notes/release_26_07.rst | 4 + drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 11 +- drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 4 + drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 27 +- .../fslmc/qbman/include/fsl_qbman_portal.h | 1 + drivers/bus/fslmc/qbman/qbman_portal.c | 1 + drivers/net/dpaa2/dpaa2_ethdev.c | 291 +++++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 3 + drivers/net/dpaa2/dpaa2_rxtx.c | 122 ++++++++ 10 files changed, 459 insertions(+), 6 deletions(-) diff --git a/doc/guides/nics/features/dpaa2.ini b/doc/guides/nics/features/dpaa2.ini index 5f9c587847..fff313603f 100644 --- a/doc/guides/nics/features/dpaa2.ini +++ b/doc/guides/nics/features/dpaa2.ini @@ -7,6 +7,7 @@ Speed capabilities = Y Link status = Y Link status event = Y +Rx interrupt = Y Burst mode info = Y Queue start/stop = Y Scattered Rx = Y diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index 5d7aa8d1bf..3bc49c3910 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -140,6 +140,10 @@ New Features * Added support for selective Rx in scalar SPRQ Rx path. +* **Updated NXP dpaa2 driver.** + + * Added Rx queue interrupt support. + * **Updated PCAP ethernet driver.** * Added support for VLAN insertion and stripping. diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index 3a5abb2e6d..e6b4e74b3b 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -204,13 +204,18 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } +#endif /* RTE_EVENT_DPAA2 */ -static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_init) +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) { struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; int threshold = 0x3, timeout = 0xFF; + if (dpio_dev->intr_enabled) + return 0; + ret = rte_dpaa2_intr_enable(dpio_dev->intr_handle, 0); if (ret) { DPAA2_BUS_ERR("Interrupt registration failed"); @@ -259,9 +264,12 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epol dpio_dev->epoll_fd = dpio_epoll_fd; } + dpio_dev->intr_enabled = 1; + return 0; } +#ifdef RTE_EVENT_DPAA2 static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) { int ret; @@ -274,6 +282,7 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) close(dpio_dev->epoll_fd); dpio_dev->epoll_fd = -1; } + dpio_dev->intr_enabled = 0; } #endif diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h index 328e1e788a..10dd968e5f 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h @@ -50,6 +50,10 @@ int dpaa2_affine_qbman_swp(void); __rte_internal int dpaa2_affine_qbman_ethrx_swp(void); +/* set up a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ +__rte_internal +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll); + /* allocate memory for FQ - dq storage */ __rte_internal int diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h index 79a2ec41e3..af75e96b27 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h @@ -133,6 +133,8 @@ struct dpaa2_dpio_dev { struct rte_intr_handle *intr_handle; /* Interrupt related info */ int32_t epoll_fd; /**< File descriptor created for interrupt polling */ int32_t hw_id; /**< An unique ID of this DPIO device instance */ + uint8_t intr_enabled; /**< DQRI portal interrupt already set up */ + uint16_t ethrx_intr_refcnt; /**< rx queues currently armed on this portal */ struct dpaa2_portal_dqrr dpaa2_held_bufs; }; @@ -164,6 +166,20 @@ typedef void (dpaa2_queue_cb_dqrr_t)(struct qbman_swp *swp, typedef void (dpaa2_queue_cb_eqresp_free_t)(uint16_t eqresp_ci, struct dpaa2_queue *dpaa2_q); +#define DPAA2_NAPI_FD_STASH_SIZE 64 /*!< power of 2; >= 2x rx burst so the + * peer port's frames fit before HW + * backpressure (2 ports/worker) + */ + +/* Lcore-local FIFO of raw FDs demuxed to this queue by another queue's burst + * on the same portal (see dpaa2_queue::napi_stash). + */ +struct dpaa2_napi_stash { + uint16_t head; /*!< pop index (drain) */ + uint16_t tail; /*!< push index (park) */ + struct qbman_fd fd[DPAA2_NAPI_FD_STASH_SIZE]; +}; + struct __rte_cache_aligned dpaa2_queue { struct rte_mempool *mb_pool; /**< mbuf pool to populate RX ring. */ union { @@ -176,7 +192,7 @@ struct __rte_cache_aligned dpaa2_queue { uint8_t cgid; /*! < Congestion Group id for this queue */ uint64_t rx_pkts; uint64_t tx_pkts; - uint64_t err_pkts; + uint64_t err_pkts; /*!< also counts NAPI stash-full drops (imissed) */ union { /**Ingress*/ struct queue_storage_info_t *q_storage[RTE_MAX_LCORE]; @@ -195,6 +211,15 @@ struct __rte_cache_aligned dpaa2_queue { uint64_t offloads; uint64_t lpbk_cntx; uint8_t data_stashing_off; + /* NAPI rx-interrupt: per-queue DPCON bound to this FQ at dev_start + * (DEST_DPCON, static); the polling worker subscribes its ethrx portal + * to the channel and arms the DQRI, rx_dqrr drains+demuxes by fqd_ctx. + */ + struct dpaa2_dpcon_dev *napi_dpcon; /*!< notif channel, NULL = napi off */ + RTE_ATOMIC(struct dpaa2_dpio_dev *) napi_sub_dpio; /*!< subscribed portal or NULL */ + uint8_t napi_channel_index; /*!< portal-local static-dequeue idx */ + uint8_t napi_armed; /*!< this queue requests DQRI wakeups */ + struct dpaa2_napi_stash napi_stash; /*!< NAPI/DQRR demux FDs (~2 KB) */ }; struct swp_active_dqs { diff --git a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h index 5375ea386d..bb8bd86103 100644 --- a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h +++ b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h @@ -189,6 +189,7 @@ int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p); * @p: the given software portal object. * @mask: The value to set in SWP_IIR register. */ +__rte_internal void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit); /************/ diff --git a/drivers/bus/fslmc/qbman/qbman_portal.c b/drivers/bus/fslmc/qbman/qbman_portal.c index 84853924e7..947415363a 100644 --- a/drivers/bus/fslmc/qbman/qbman_portal.c +++ b/drivers/bus/fslmc/qbman/qbman_portal.c @@ -448,6 +448,7 @@ int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p) return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IIR); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_interrupt_set_inhibit) void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit) { qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_IIR, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 803a8321e0..61e7c820de 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -623,6 +623,8 @@ dpaa2_clear_queue_active_dps(struct dpaa2_queue *q, int num_lcores) } } +static void dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q); + static void dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) { @@ -640,6 +642,12 @@ dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) /* cleaning up queue storage */ for (i = 0; i < priv->nb_rx_queues; i++) { dpaa2_q = priv->rx_vq[i]; + if (dpaa2_q->napi_dpcon) { /* release the rx-intr channel */ + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + dpaa2_q->napi_sub_dpio = NULL; + } dpaa2_clear_queue_active_dps(dpaa2_q, RTE_MAX_LCORE); dpaa2_queue_storage_free(dpaa2_q, @@ -845,6 +853,19 @@ dpaa2_eth_dev_configure(struct rte_eth_dev *dev) } } + if (dev->data->dev_conf.intr_conf.rxq) { + if (!dev->intr_handle) + dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); + if (!dev->intr_handle || + rte_intr_vec_list_alloc(dev->intr_handle, "rxq_intr", + dev->data->nb_rx_queues) || + rte_intr_nb_efd_set(dev->intr_handle, dev->data->nb_rx_queues) || + rte_intr_type_set(dev->intr_handle, RTE_INTR_HANDLE_EXT)) { + DPAA2_PMD_ERR("Failed to set up rx-queue interrupts"); + return -rte_errno; + } + } + dpaa2_tm_init(dev); return 0; @@ -863,6 +884,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, { struct dpaa2_dev_priv *priv = dev->data->dev_private; struct fsl_mc_io *dpni = dev->process_private; + bool dpcon_allocated = false; struct dpaa2_queue *dpaa2_q; struct dpni_queue cfg; uint8_t options = 0; @@ -903,6 +925,21 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, dpaa2_q->bp_array = rte_dpaa2_bpid_info; dpaa2_q->offloads = rx_conf->offloads; + /* NAPI: grab a DPCON channel so dev_start can bind this FQ statically. + * The DQRR burst replaces the poll path for every queue at once, so a + * missing channel is fatal rather than a silent per-queue fallback. + */ + dpaa2_q->napi_sub_dpio = NULL; + if (dev->data->dev_conf.intr_conf.rxq && !dpaa2_q->napi_dpcon) { + dpaa2_q->napi_dpcon = rte_dpaa2_alloc_dpcon_dev(); + if (!dpaa2_q->napi_dpcon) { + DPAA2_PMD_ERR("rxq %d: no DPCON for rx-queue interrupts", + rx_queue_id); + return -ENODEV; + } + dpcon_allocated = true; + } + /*Get the flow id from given VQ id*/ flow_id = dpaa2_q->flow_id; memset(&cfg, 0, sizeof(struct dpni_queue)); @@ -910,6 +947,10 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, options = options | DPNI_QUEUE_OPT_USER_CTX; cfg.user_context = (size_t)(dpaa2_q); + /* clear any stale DPIO dest left scheduled by a prior rx-intr run */ + options |= DPNI_QUEUE_OPT_DEST; + cfg.destination.type = DPNI_DEST_NONE; + /* check if a private cgr available. */ for (i = 0; i < priv->max_cgs; i++) { if (!priv->cgid_in_use[i]) { @@ -950,7 +991,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, dpaa2_q->tc_index, flow_id, options, &cfg); if (ret) { DPAA2_PMD_ERR("Error in setting the rx flow: = %d", ret); - return ret; + goto err_free_dpcon; } dpaa2_q->nb_desc = nb_rx_desc; @@ -991,7 +1032,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, if (ret) { DPAA2_PMD_ERR("Error in setting taildrop. err=(%d)", ret); - return ret; + goto err_free_dpcon; } } else { /* Disable tail Drop */ struct dpni_taildrop taildrop = {0}; @@ -1011,12 +1052,22 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, if (ret) { DPAA2_PMD_ERR("Error in setting taildrop. err=(%d)", ret); - return ret; + goto err_free_dpcon; } } dev->data->rx_queues[rx_queue_id] = dpaa2_q; return 0; + +err_free_dpcon: + /* free only the DPCON this call allocated; a pre-existing one belongs to + * an earlier setup and is released at dev_close + */ + if (dpcon_allocated) { + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + } + return ret; } static int @@ -1175,6 +1226,62 @@ dpaa2_dev_tx_queue_setup(struct rte_eth_dev *dev, return 0; } +/* Fully release a queue's rx-interrupt state: detach the FQ from its DPCON, + * unbind the static dequeue channel from the portal and free any stashed FDs. + * Teardown only: the port is stopped and the portal quiesced; not a runtime + * rx_queue_intr_disable() replacement. Call before freeing the DPCON. + */ +static void +dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q) +{ + struct dpaa2_dev_priv *priv; + struct dpaa2_dpio_dev *dpio; + struct fsl_mc_io *dpni; + struct dpni_queue cfg; + int ret; + + if (!dpaa2_q || !dpaa2_q->napi_dpcon) + return; + + /* detach the FQ from its DPCON so it no longer points at a channel + * about to be returned to the pool (dpni is disabled at teardown) + */ + priv = dpaa2_q->eth_data->dev_private; + dpni = priv->eth_dev->process_private; + memset(&cfg, 0, sizeof(cfg)); + cfg.destination.type = DPNI_DEST_NONE; + ret = dpni_set_queue(dpni, CMD_PRI_LOW, priv->token, DPNI_QUEUE_RX, + dpaa2_q->tc_index, dpaa2_q->flow_id, + DPNI_QUEUE_OPT_DEST, &cfg); + if (ret) + DPAA2_PMD_ERR("napi: DEST_NONE rxq flow %u: %d", + dpaa2_q->flow_id, ret); + + /* unbind the static dequeue channel from the portal it was armed on */ + dpio = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, + rte_memory_order_acquire); + if (dpio) { + qbman_swp_push_set(dpio->sw_portal, + dpaa2_q->napi_channel_index, 0); + if (dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 0; + if (dpio->ethrx_intr_refcnt > 0 && + --dpio->ethrx_intr_refcnt == 0) + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 1); + } + ret = dpio_remove_static_dequeue_channel(dpio->dpio, CMD_PRI_LOW, + dpio->token, dpaa2_q->napi_dpcon->dpcon_id); + if (ret) + DPAA2_PMD_ERR("napi: remove DPCON %d static dequeue channel: %d", + dpaa2_q->napi_dpcon->dpcon_id, ret); + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, + rte_memory_order_release); + } + + /* free FDs parked for this queue but never drained by a burst */ + dpaa2_dev_rx_queue_napi_stash_drain(dpaa2_q); +} + static void dpaa2_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t rx_queue_id) { @@ -1204,6 +1311,12 @@ dpaa2_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t rx_queue_id) priv->cgid_in_use[dpaa2_q->cgid] = 0; dpaa2_q->cgid = DPAA2_INVALID_CGID; } + + if (dpaa2_q->napi_dpcon) { + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + } } static int @@ -1354,6 +1467,36 @@ dpaa2_dev_start(struct rte_eth_dev *dev) intr_handle = dpaa2_dev->intr_handle; PMD_INIT_FUNC_TRACE(); + + /* NAPI: bind each rx FQ to its own DPCON channel while the dpni is still + * disabled (a DEST set_queue on an enabled dpni wedges the shared MC). + * Static, affinity-free; the polling worker subscribes its portal later. + */ + if (dev->data->dev_conf.intr_conf.rxq) { + for (i = 0; i < data->nb_rx_queues; i++) { + dpaa2_q = data->rx_queues[i]; + if (!dpaa2_q->napi_dpcon) + continue; + memset(&cfg, 0, sizeof(cfg)); + cfg.destination.type = DPNI_DEST_DPCON; + cfg.destination.id = dpaa2_q->napi_dpcon->dpcon_id; + cfg.user_context = (size_t)dpaa2_q; + ret = dpni_set_queue(dpni, CMD_PRI_LOW, priv->token, + DPNI_QUEUE_RX, dpaa2_q->tc_index, + dpaa2_q->flow_id, + DPNI_QUEUE_OPT_DEST | DPNI_QUEUE_OPT_USER_CTX, + &cfg); + if (ret) { + DPAA2_PMD_ERR("napi: DPCON bind rxq %d: %d", i, ret); + return ret; + } + } + /* DQRR burst for all queues; a queue only yields frames once + * rx_queue_intr_enable() has subscribed its portal + */ + dev->rx_pkt_burst = dpaa2_dev_rx_dqrr; + } + ret = dpni_enable(dpni, CMD_PRI_LOW, priv->token); if (ret) { DPAA2_PMD_ERR("Failure in enabling dpni %d device: err=%d", @@ -1824,6 +1967,13 @@ dpaa2_dev_stats_get(struct rte_eth_dev *dev, stats->oerrors = value.page_2.egress_discarded_frames; stats->imissed = value.page_2.ingress_nobuffer_discards; + /* software Rx drops (full napi stash) are not in the HW counters */ + for (i = 0; i < priv->nb_rx_queues; i++) { + dpaa2_rxq = priv->rx_vq[i]; + if (dpaa2_rxq != NULL) + stats->imissed += dpaa2_rxq->err_pkts; + } + /* Fill in per queue stats */ if (qstats != NULL) { for (i = 0; (i < RTE_ETHDEV_QUEUE_STAT_CNTRS) && @@ -2137,8 +2287,10 @@ dpaa2_dev_stats_reset(struct rte_eth_dev *dev) /* Reset the per queue stats in dpaa2_queue structure */ for (i = 0; i < priv->nb_rx_queues; i++) { dpaa2_q = priv->rx_vq[i]; - if (dpaa2_q) + if (dpaa2_q) { dpaa2_q->rx_pkts = 0; + dpaa2_q->err_pkts = 0; + } } for (i = 0; i < priv->nb_tx_queues; i++) { @@ -2698,6 +2850,135 @@ rte_pmd_dpaa2_thread_init(void) } } +/* Arm rx-queue interrupts on the worker lcore: subscribe its ethrx portal to + * the queue's DPCON channel (one-shot per-portal MC) and unmask the portal DQRI + * (pure QBMan). + * + * Affinity is static queue-to-lcore; a lcore may own several rx queues. The + * DQRI and the eventfd are portal-wide, so frames are demuxed by fqd_ctx in the + * burst and the portal's inhibit bit is reference-counted by the number of its + * queues currently armed (ethrx_intr_refcnt) -- disabling one queue must not + * mask wakeups still wanted by its siblings. napi_armed and ethrx_intr_refcnt + * are plain (not atomic): these ops run on the queue's owner lcore against its + * own portal (one portal per lcore), so per-portal isolation keeps them from + * racing, not control-plane serialization. + * + * A re-home reclaims the channel by poking the old portal, so the caller must + * have quiesced the previous owner and disabled the queue there; napi_armed is + * then 0 and only the new portal is counted. + */ +static int +dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + struct dpaa2_dpio_dev *dpio, *old; + int ret; + + if (!dpaa2_q->napi_dpcon) + return -ENOTSUP; /* no channel -> caller keeps polling */ + + if (dpaa2_affine_qbman_ethrx_swp()) + return -EIO; + dpio = DPAA2_PER_LCORE_ETHRX_DPIO; + + /* build_epoll=false: the generic ethdev rx-intr API waits on the + * application epoll, not the portal's private one (event PMD only). + */ + ret = dpaa2_dpio_intr_init(dpio, false); /* VFIO eventfd, no MC */ + if (ret) + return ret; + + old = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, rte_memory_order_acquire); + if (old && old != dpio && dpaa2_q->napi_armed) { + DPAA2_PMD_ERR("rxq %d still armed on another portal; disable it first", + queue_id); + return -EBUSY; + } + if (old != dpio) { + if (old) { /* reclaim from old portal (quiesced; QBMan MMIO unsynced) */ + qbman_swp_push_set(old->sw_portal, + dpaa2_q->napi_channel_index, 0); + ret = dpio_remove_static_dequeue_channel(old->dpio, + CMD_PRI_LOW, old->token, + dpaa2_q->napi_dpcon->dpcon_id); + /* push_set(0) above already stops the old portal from + * dequeuing; a failed unbind only leaks a static-channel + * slot on the old DPIO, so warn and proceed + */ + if (ret) + DPAA2_PMD_WARN("napi: reclaim rxq %d: %d", + queue_id, ret); + /* on no portal until the add below succeeds */ + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, + rte_memory_order_release); + } + ret = dpio_add_static_dequeue_channel(dpio->dpio, CMD_PRI_LOW, + dpio->token, dpaa2_q->napi_dpcon->dpcon_id, + &dpaa2_q->napi_channel_index); + if (ret) { + DPAA2_PMD_ERR("napi: subscribe rxq %d: %d", queue_id, ret); + return ret; + } + qbman_swp_push_set(dpio->sw_portal, + dpaa2_q->napi_channel_index, 1); + /* point this queue's eventfd at the portal's DQRI fd so the + * generic rte_eth_dev_rx_intr_ctl_q epoll wakes on it + */ + if (rte_intr_vec_list_index_set(dev->intr_handle, queue_id, queue_id) || + rte_intr_efds_index_set(dev->intr_handle, queue_id, + rte_intr_fd_get(dpio->intr_handle))) { + DPAA2_PMD_ERR("napi: efd wiring rxq %d", queue_id); + /* unwind the half-done subscription so HW and driver + * state stay consistent + */ + qbman_swp_push_set(dpio->sw_portal, + dpaa2_q->napi_channel_index, 0); + dpio_remove_static_dequeue_channel(dpio->dpio, + CMD_PRI_LOW, dpio->token, + dpaa2_q->napi_dpcon->dpcon_id); + return -EIO; + } + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, dpio, rte_memory_order_release); + } + + /* arm this queue; the portal DQRI is unmasked only on the 0 -> 1 edge + * of its armed-queue count + */ + if (!dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 1; + if (dpio->ethrx_intr_refcnt++ == 0) { + qbman_swp_interrupt_clear_status(dpio->sw_portal, + 0xffffffff); + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 0); + } + } + + return 0; +} + +/* Disarm rx-queue interrupts for this queue. The portal DQRI is masked only + * once the last of its queues disarms; act on the portal the queue is actually + * subscribed to, not the caller's current portal. + */ +static int +dpaa2_dev_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + struct dpaa2_dpio_dev *dpio; + + dpio = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, rte_memory_order_acquire); + if (dpio && dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 0; + if (dpio->ethrx_intr_refcnt > 0 && + --dpio->ethrx_intr_refcnt == 0) + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 1); + } + + return 0; +} + static struct eth_dev_ops dpaa2_ethdev_ops = { .dev_configure = dpaa2_eth_dev_configure, .dev_start = dpaa2_dev_start, @@ -2726,6 +3007,8 @@ static struct eth_dev_ops dpaa2_ethdev_ops = { .vlan_tpid_set = dpaa2_vlan_tpid_set, .rx_queue_setup = dpaa2_dev_rx_queue_setup, .rx_queue_release = dpaa2_dev_rx_queue_release, + .rx_queue_intr_enable = dpaa2_dev_rx_queue_intr_enable, + .rx_queue_intr_disable = dpaa2_dev_rx_queue_intr_disable, .tx_queue_setup = dpaa2_dev_tx_queue_setup, .rx_burst_mode_get = dpaa2_dev_rx_burst_mode_get, .tx_burst_mode_get = dpaa2_dev_tx_burst_mode_get, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.h b/drivers/net/dpaa2/dpaa2_ethdev.h index 4da47a543a..3765f79e84 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.h +++ b/drivers/net/dpaa2/dpaa2_ethdev.h @@ -491,6 +491,9 @@ uint16_t dpaa2_dev_loopback_rx(void *queue, struct rte_mbuf **bufs, uint16_t dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts); +uint16_t dpaa2_dev_rx_dqrr(void *queue, struct rte_mbuf **bufs, + uint16_t nb_pkts); +void dpaa2_dev_rx_queue_napi_stash_drain(struct dpaa2_queue *dpaa2_q); void dpaa2_dev_process_parallel_event(struct qbman_swp *swp, const struct qbman_fd *fd, const struct qbman_result *dq, diff --git a/drivers/net/dpaa2/dpaa2_rxtx.c b/drivers/net/dpaa2/dpaa2_rxtx.c index b316e23e87..189accc1de 100644 --- a/drivers/net/dpaa2/dpaa2_rxtx.c +++ b/drivers/net/dpaa2/dpaa2_rxtx.c @@ -922,6 +922,128 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) return num_rx; } +/* Convert a DQRR'd FD (single or scatter-gather) to an mbuf and apply software + * VLAN strip, like the poll path. + */ +static inline struct rte_mbuf * +dpaa2_dqrr_fd_to_mbuf(const struct qbman_fd *fd, + struct rte_eth_dev_data *eth_data) +{ + struct rte_mbuf *m; + + if (unlikely(DPAA2_FD_GET_FORMAT(fd) == qbman_fd_sg)) + m = eth_sg_fd_to_mbuf(fd, eth_data->port_id); + else + m = eth_fd_to_mbuf(fd, eth_data->port_id); + if (eth_data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_VLAN_STRIP) + rte_vlan_strip(m); + return m; +} + +/* prefetch a DQRR'd FD's HW annotation (parse area) ahead of conversion */ +static inline void +dpaa2_dqrr_prefetch_annot(const struct qbman_fd *fd) +{ + rte_prefetch0((void *)((size_t)DPAA2_IOVA_TO_VADDR(DPAA2_GET_FD_ADDR(fd)) + + DPAA2_FD_PTA_SIZE)); +} + +/* Free FDs a sibling burst parked in this queue's stash but that were never + * drained (queue released/freed while the lcore still held its frames). + */ +void +dpaa2_dev_rx_queue_napi_stash_drain(struct dpaa2_queue *dpaa2_q) +{ + struct dpaa2_napi_stash *stash = &dpaa2_q->napi_stash; + const struct qbman_fd *fd; + + while (stash->head != stash->tail) { + fd = &stash->fd[stash->head & (DPAA2_NAPI_FD_STASH_SIZE - 1)]; + rte_pktmbuf_free(dpaa2_dqrr_fd_to_mbuf(fd, dpaa2_q->eth_data)); + stash->head++; + } + stash->head = 0; + stash->tail = 0; +} + +/* rx interrupt/DQRR path: the FQ is scheduled to a channel the lcore's ethrx + * portal statically dequeues -- a VDQ on a scheduled FQ never completes, so DQRR + * is the only model compatible with interrupt sleep. One portal serves every + * queue the lcore owns, so the burst demuxes by fqd_ctx: own frames are + * returned, foreign ones have their raw FD parked in the target queue's stash. + * + * The application must therefore poll all queues assigned to the lcore after a + * wakeup -- the same scheduling contract as plain DPDK polling. When a foreign + * queue's stash is full the FD is dropped (freed) rather than left on the shared + * DQRR ring, which would head-of-line block every other queue on the portal. + */ +uint16_t __rte_hot +dpaa2_dev_rx_dqrr(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +{ + struct dpaa2_queue *dpaa2_q = queue; + struct rte_eth_dev_data *eth_data = dpaa2_q->eth_data; + struct dpaa2_napi_stash *stash = &dpaa2_q->napi_stash; + const struct qbman_result *dq; + const struct qbman_fd *fd; + struct dpaa2_queue *rxq; + struct qbman_swp *swp; + uint16_t num_rx = 0; + + if (unlikely(!DPAA2_PER_LCORE_ETHRX_DPIO)) { + if (dpaa2_affine_qbman_ethrx_swp()) { + DPAA2_PMD_ERR("Failure in affining portal"); + return 0; + } + } + swp = DPAA2_PER_LCORE_ETHRX_PORTAL; + + /* our frames parked by another queue's burst -- convert now (hot) */ + while (num_rx < nb_pkts && stash->head != stash->tail) { + fd = &stash->fd[stash->head & (DPAA2_NAPI_FD_STASH_SIZE - 1)]; + if (dpaa2_svr_family != SVR_LX2160A && + (uint16_t)(stash->head + 1) != stash->tail) + dpaa2_dqrr_prefetch_annot(&stash->fd[(stash->head + 1) & + (DPAA2_NAPI_FD_STASH_SIZE - 1)]); + bufs[num_rx++] = dpaa2_dqrr_fd_to_mbuf(fd, eth_data); + stash->head++; + } + + while (num_rx < nb_pkts) { + dq = qbman_swp_dqrr_next(swp); + if (!dq) + break; /* ring momentarily empty */ + qbman_swp_prefetch_dqrr_next(swp); + fd = qbman_result_DQ_fd(dq); + /* parse summary is in the FRC on LX2160A; annotation is HW-stashed */ + if (dpaa2_svr_family != SVR_LX2160A) + dpaa2_dqrr_prefetch_annot(fd); + rxq = (struct dpaa2_queue *)(size_t)qbman_result_DQ_fqd_ctx(dq); + if (unlikely(!rxq)) + rxq = dpaa2_q; + if (rxq == dpaa2_q) { + bufs[num_rx++] = dpaa2_dqrr_fd_to_mbuf(fd, eth_data); + } else { + struct dpaa2_napi_stash *fs = &rxq->napi_stash; + + if (unlikely((uint16_t)(fs->tail - fs->head) >= + DPAA2_NAPI_FD_STASH_SIZE)) { + /* stash full: drop rather than leave it on the ring + * and head-of-line block the shared portal + */ + rte_pktmbuf_free(dpaa2_dqrr_fd_to_mbuf(fd, rxq->eth_data)); + rxq->err_pkts++; + } else { + fs->fd[fs->tail & (DPAA2_NAPI_FD_STASH_SIZE - 1)] = *fd; + fs->tail++; + } + } + qbman_swp_dqrr_consume(swp, dq); + } + + dpaa2_q->rx_pkts += num_rx; + return num_rx; +} + void __rte_hot dpaa2_dev_process_parallel_event(struct qbman_swp *swp, const struct qbman_fd *fd, -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v2 3/6] net/dpaa2: support Rx queue interrupts 2026-06-16 10:27 ` [PATCH v2 3/6] net/dpaa2: support Rx queue interrupts Maxime Leroy @ 2026-06-18 8:46 ` David Marchand 0 siblings, 0 replies; 51+ messages in thread From: David Marchand @ 2026-06-18 8:46 UTC (permalink / raw) To: Maxime Leroy; +Cc: dev, Hemant Agrawal, Sachin Saxena Hello Maxime, On Tue, 16 Jun 2026 at 12:28, Maxime Leroy <maxime@leroys.fr> wrote: > > Implement .rx_queue_intr_enable / .rx_queue_intr_disable so a worker > can sleep on a queue's data-availability notification instead of > busy-polling, through the generic rte_eth_dev_rx_intr_* API. > > A worker wakes on its software portal's DQRI, which fires when the > portal's DQRR holds frames, so the Rx FQ must be scheduled to a channel > that portal dequeues. The natural dpni_set_queue with a notification > destination holds the global MC lock long enough to wedge the firmware > and must target a disabled dpni. But the polling portal is only known > once a worker affines, after dev_start, so the destination cannot be > the worker's portal. > > Bind each Rx FQ to its own DPCON channel instead. The default Rx burst > pulls frames from the FQ with a volatile dequeue and cannot be > interrupt-driven; to wake on the DQRI the FQ must be pushed to the > portal's DQRR. dev_start issues the DEST_DPCON set_queue statically on > the still-disabled dpni with no knowledge of the polling lcore; a worker > later subscribes its own ethrx portal to the channel and arms the DQRI > in rx_queue_intr_enable (a one-shot per-portal MC op plus QBMan, never > the wedging set_queue). > > This pushed/DQRR consumption is how the event PMD works, but the DPCON > use differs. The event PMD uses one DPCON per worker, concentrates N > FQs onto it, and lets the QBMan scheduler load-balance events across > cores. Here affinity is static and there is no scheduling, so each FQ > gets its own DPCON (one per FQ, more channels, drawn from the shared > pool that the DPCON move to the fslmc bus now feeds), bound once at > dev_start before the lcore is known. Frames are delivered by > rte_eth_rx_burst (dpaa2_dev_rx_dqrr), not as events via > rte_event_dequeue. > > rte_eth_dev_rx_intr_enable(q) subscribes the lcore portal to q's DPCON > and arms the DQRI. rte_eth_dev_rx_intr_ctl_q(q) adds q's eventfd (the > portal DQRI fd) to the thread epoll. > > wire > | > [ DPMAC ] > | > [ DPNI ] (1) > | > TC0: FQ0 FQ1 FQ2 FQ3 (2) > | | | | (3) > [DPCON][DPCON][DPCON][DPCON] > \ | | / (4) > [ DPIO A ] [ DPIO B ] (5) > | | > DQRR DQRR (6) > | | > DQRI DQRI (7) > | | > eventfd eventfd (8) > | | > rte_epoll_wait rte_epoll_wait (9) > | | > dpaa2_dev_rx_dqrr (10) > > (1) WRIOP picks a TC (QoS), then RSS-hashes within the TC to an FQ > (2) FQ0..FQ3 are the rte_eth Rx queues > (3) dpni_set_queue(DEST_DPCON): one DPCON per FQ > (4) the lcore portal subscribes to its DPCONs (push_set) > (5) one QBMan software portal per lcore > (6) QMan pushes the FDs into the portal DQRR > (7) DQRI is raised when the DQRR is non-empty > (8) a portal's queues share one fd (its DQRI eventfd) > (9) worker sleeps here when all its queues are idle > (10) dpaa2_dev_rx_dqrr drains the DQRR, demuxes FDs to FQs by fqd_ctx > > The DQRI and eventfd are portal-wide: a queue's eventfd is its portal's > DQRI fd, and the inhibit bit is refcounted by armed queues so disabling > one queue never masks a sibling. The static per-queue bind also lets a > queue be re-homed to another lcore at runtime, the new worker > reclaiming the channel, with no set_queue and no port stop. > > On single-core 64-byte forwarding this interrupt path runs at ~5.0 Mpps > versus ~5.86 Mpps polling: per-frame DQRR demux and consume cost about > 15 percent over the polling batch dequeue. > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> I did not review in detail, but one aspect caught my eye: [snip] > diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c > index 803a8321e0..61e7c820de 100644 > --- a/drivers/net/dpaa2/dpaa2_ethdev.c > +++ b/drivers/net/dpaa2/dpaa2_ethdev.c [snip] > @@ -845,6 +853,19 @@ dpaa2_eth_dev_configure(struct rte_eth_dev *dev) > } > } > > + if (dev->data->dev_conf.intr_conf.rxq) { > + if (!dev->intr_handle) > + dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); Something is strange here. I plan to move this allocation in the probe_device handler of the bus (https://patchwork.dpdk.org/project/dpdk/patch/20260611094551.1514962-6-david.marchand@redhat.com/). However, even without this change of mine, the intr_handle should be allocated in the bus code (see allocation in scan_one_fslmc_device). A NULL pointer during configure indicates a bug somewhere around the device pointer life. > + if (!dev->intr_handle || > + rte_intr_vec_list_alloc(dev->intr_handle, "rxq_intr", > + dev->data->nb_rx_queues) || > + rte_intr_nb_efd_set(dev->intr_handle, dev->data->nb_rx_queues) || > + rte_intr_type_set(dev->intr_handle, RTE_INTR_HANDLE_EXT)) { > + DPAA2_PMD_ERR("Failed to set up rx-queue interrupts"); > + return -rte_errno; > + } > + } > + > dpaa2_tm_init(dev); > > return 0; -- David Marchand ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v2 4/6] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (2 preceding siblings ...) 2026-06-16 10:27 ` [PATCH v2 3/6] net/dpaa2: support Rx queue interrupts Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 5/6] net/dpaa2: fix Rx queue count for primary process Maxime Leroy ` (3 subsequent siblings) 7 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev; +Cc: Maxime Leroy, Hemant Agrawal, Sachin Saxena The portal DQRI interrupt used a fixed threshold of 3 and a raw 0xFF timeout. Parameterize dpaa2_dpio_intr_init() with (threshold, timeout) so each mode supplies its own: the event driver keeps the legacy 3 / 0xFF and its DPAA2_PORTAL_INTR_THRESHOLD / DPAA2_PORTAL_INTR_TIMEOUT env-var overrides, while rx-queue interrupts default the threshold to the HW DQRR ring depth (ring-1, =7 on QBMan >= 4.1) and use a coalescing holdoff in microseconds, converted to ITP units from the MC-reported QBMan clock (itp = holdoff_us * clk_MHz / 256, capped at the 12-bit field). The setup is portal-wide and idempotent, so the first mode to arm a given portal wins; a portal is normally driven by a single mode. The net/dpaa2 PMD exposes both rx-queue-interrupt knobs as per-port devargs: drv_rx_intr_holdoff_us (default 100us) and drv_rx_intr_threshold (default 0 = ring-1, clamped to [1, ring-1]). Also expose dpaa2_dpio_intr_deinit() (no longer event-only), and on the intr_init error paths close the epoll fd and disable the interrupt. Add qbman_swp_dqrr_size() to expose the ring depth. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/nics/dpaa2.rst | 10 +++ drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 72 +++++++++++++------ drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 +++- .../fslmc/qbman/include/fsl_qbman_portal.h | 10 +++ drivers/bus/fslmc/qbman/qbman_portal.c | 6 ++ drivers/net/dpaa2/dpaa2_ethdev.c | 60 +++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 7 ++ 7 files changed, 152 insertions(+), 25 deletions(-) diff --git a/doc/guides/nics/dpaa2.rst b/doc/guides/nics/dpaa2.rst index 2d70bd0ab9..47a52c9287 100644 --- a/doc/guides/nics/dpaa2.rst +++ b/doc/guides/nics/dpaa2.rst @@ -492,6 +492,16 @@ for details. packets, so that user can check what is wrong with those packets. e.g. ``fslmc:dpni.1,drv_error_queue=1`` +* Use dev arg option ``drv_rx_intr_holdoff_us=<uint32>`` to set the Rx queue + interrupt coalescing holdoff in microseconds (default 100). Only applies in + Rx queue interrupt mode. + e.g. ``fslmc:dpni.1,drv_rx_intr_holdoff_us=50`` + +* Use dev arg option ``drv_rx_intr_threshold=<uint32>`` to set the Rx queue + interrupt coalescing frame threshold; 0 (default) means the DQRR ring depth + minus one. + e.g. ``fslmc:dpni.1,drv_rx_intr_threshold=4`` + Enabling logs ------------- diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index e6b4e74b3b..c5525a94fa 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -206,12 +206,35 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) } #endif /* RTE_EVENT_DPAA2 */ +/* holdoff (us) -> QBMan ITP units (256 cycles each), capped at the 12-bit field */ +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_holdoff_to_itp) +int dpaa2_dpio_holdoff_to_itp(struct dpaa2_dpio_dev *dpio_dev, uint32_t holdoff_us) +{ + uint32_t qman_mhz = 0; + struct dpio_attr attr; + uint64_t itp; + + if (dpio_get_attributes(dpio_dev->dpio, CMD_PRI_LOW, dpio_dev->token, &attr) == 0) + qman_mhz = attr.clk / 1000000; + itp = qman_mhz ? ((uint64_t)holdoff_us * qman_mhz) / 256 : 0xFF; + if (itp > 0xfff) /* 12-bit ITP field */ + itp = 0xfff; + + return (int)itp; +} + +/* threshold: DQRR fill raising DQRI (< ring depth); timeout: holdoff in ITP units. + * Per-mode values from the caller (eventdev vs rx-queue intr); no env override. + * The DQRI config is portal-wide and this is idempotent: the first caller to + * arm a portal wins, a later caller's values are ignored (a portal normally + * serves a single mode). + */ RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_init) -int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, + int timeout, bool build_epoll) { - struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; - int threshold = 0x3, timeout = 0xFF; + struct epoll_event epoll_ev; if (dpio_dev->intr_enabled) return 0; @@ -222,12 +245,6 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) return -1; } - if (getenv("DPAA2_PORTAL_INTR_THRESHOLD")) - threshold = atoi(getenv("DPAA2_PORTAL_INTR_THRESHOLD")); - - if (getenv("DPAA2_PORTAL_INTR_TIMEOUT")) - sscanf(getenv("DPAA2_PORTAL_INTR_TIMEOUT"), "%x", &timeout); - qbman_swp_interrupt_set_trigger(dpio_dev->sw_portal, QBMAN_SWP_INTERRUPT_DQRI); qbman_swp_interrupt_clear_status(dpio_dev->sw_portal, 0xffffffff); @@ -238,9 +255,9 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) dpio_dev->epoll_fd = -1; /* The event PMD dequeues by sleeping on a private epoll instance owned - * by the portal, so build it here. A caller that waits on another - * epoll (the net rx-queue-interrupt path uses the application's) skips - * this. + * by the portal, so build it here. The net rx-queue-interrupt path + * exposes the raw eventfd through the generic ethdev API and waits on + * the application's own epoll instead, so it skips this. */ if (build_epoll) { dpio_epoll_fd = epoll_create(1); @@ -269,11 +286,14 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) return 0; } -#ifdef RTE_EVENT_DPAA2 -static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_deinit) +void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) { int ret; + if (!dpio_dev->intr_enabled) + return; + ret = rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); if (ret) DPAA2_BUS_ERR("DPIO interrupt disable failed"); @@ -284,7 +304,6 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) } dpio_dev->intr_enabled = 0; } -#endif static int dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) @@ -306,9 +325,18 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) } #ifdef RTE_EVENT_DPAA2 - if (dpaa2_dpio_intr_init(dpio_dev, true)) { - DPAA2_BUS_ERR("Interrupt registration failed for dpio"); - return -1; + { + int threshold = 3, timeout = 0xFF; + + if (getenv("DPAA2_PORTAL_INTR_THRESHOLD")) + threshold = atoi(getenv("DPAA2_PORTAL_INTR_THRESHOLD")); + if (getenv("DPAA2_PORTAL_INTR_TIMEOUT")) + sscanf(getenv("DPAA2_PORTAL_INTR_TIMEOUT"), "%x", &timeout); + + if (dpaa2_dpio_intr_init(dpio_dev, threshold, timeout, true)) { + DPAA2_BUS_ERR("Interrupt registration failed for dpio"); + return -1; + } } dpaa2_affine_dpio_intr_to_respective_core(dpio_dev->hw_id, cpu_id); #endif @@ -319,9 +347,11 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) static void dpaa2_put_qbman_swp(struct dpaa2_dpio_dev *dpio_dev) { if (dpio_dev) { -#ifdef RTE_EVENT_DPAA2 + /* rx-queue interrupts (net PMD) can arm a portal without the + * event driver; tear it down unconditionally. Safe when never + * armed: intr_deinit returns early if intr is not enabled. + */ dpaa2_dpio_intr_deinit(dpio_dev); -#endif rte_atomic16_clear(&dpio_dev->ref_count); } } @@ -512,6 +542,8 @@ dpaa2_create_dpio_device(int vdev_fd, goto err; } + DPAA2_BUS_DEBUG("QBMAN clk = %u Hz (%u MHz)", attr.clk, attr.clk / 1000000); + /* find the SoC type for the first time */ if (!dpaa2_svr_family) { struct mc_soc_version mc_plat_info = {0}; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h index 10dd968e5f..090fa14410 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h @@ -50,9 +50,17 @@ int dpaa2_affine_qbman_swp(void); __rte_internal int dpaa2_affine_qbman_ethrx_swp(void); -/* set up a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ +/* set up / tear down a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ __rte_internal -int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll); +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, + int timeout, bool build_epoll); + +__rte_internal +void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev); + +/* convert a coalescing holdoff (microseconds) to QBMan ITP units */ +__rte_internal +int dpaa2_dpio_holdoff_to_itp(struct dpaa2_dpio_dev *dpio_dev, uint32_t holdoff_us); /* allocate memory for FQ - dq storage */ __rte_internal diff --git a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h index bb8bd86103..e9eda31927 100644 --- a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h +++ b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h @@ -157,6 +157,16 @@ uint32_t qbman_swp_intr_timeout_read_status(struct qbman_swp *p); */ void qbman_swp_intr_timeout_write(struct qbman_swp *p, uint32_t mask); +/** + * qbman_swp_dqrr_size() - Get the HW DQRR ring depth of a software portal. + * @p: the given software portal object. + * + * Returns the number of DQRR entries (4 on QBMan < 4.1, 8 on >= 4.1). Useful + * as the upper bound for the DQRR interrupt coalescing threshold. + */ +__rte_internal +uint8_t qbman_swp_dqrr_size(struct qbman_swp *p); + /** * qbman_swp_interrupt_get_trigger() - Get the data in software portal * interrupt enable register. diff --git a/drivers/bus/fslmc/qbman/qbman_portal.c b/drivers/bus/fslmc/qbman/qbman_portal.c index 947415363a..81c2d87e0a 100644 --- a/drivers/bus/fslmc/qbman/qbman_portal.c +++ b/drivers/bus/fslmc/qbman/qbman_portal.c @@ -433,6 +433,12 @@ void qbman_swp_intr_timeout_write(struct qbman_swp *p, uint32_t mask) qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_ITPR, mask); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_dqrr_size) +uint8_t qbman_swp_dqrr_size(struct qbman_swp *p) +{ + return p->dqrr.dqrr_size; +} + uint32_t qbman_swp_interrupt_get_trigger(struct qbman_swp *p) { return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IER); diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 61e7c820de..76e2df6167 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -36,6 +36,9 @@ #define DRIVER_ERROR_QUEUE "drv_err_queue" #define DRIVER_NO_TAILDROP "drv_no_taildrop" #define DRIVER_NO_DATA_STASHING "drv_no_data_stashing" +#define DRIVER_RX_INTR_HOLDOFF_US "drv_rx_intr_holdoff_us" +#define DPAA2_RX_INTR_HOLDOFF_US_DEF 100 +#define DRIVER_RX_INTR_THRESHOLD "drv_rx_intr_threshold" #define CHECK_INTERVAL 100 /* 100ms */ #define MAX_REPEAT_TIME 90 /* 9s (90 * 100ms) in total */ @@ -2873,7 +2876,7 @@ dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) struct dpaa2_dev_priv *priv = dev->data->dev_private; struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; struct dpaa2_dpio_dev *dpio, *old; - int ret; + int ret, threshold, timeout, dqrr_max; if (!dpaa2_q->napi_dpcon) return -ENOTSUP; /* no channel -> caller keeps polling */ @@ -2882,10 +2885,22 @@ dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) return -EIO; dpio = DPAA2_PER_LCORE_ETHRX_DPIO; + /* threshold from drv_rx_intr_threshold (0 = ring-1), holdoff from + * drv_rx_intr_holdoff_us. idempotent: no-op if the dpio is already + * armed (e.g. event driver) + */ + dqrr_max = qbman_swp_dqrr_size(dpio->sw_portal) - 1; + threshold = priv->rx_intr_threshold ? (int)priv->rx_intr_threshold : dqrr_max; + if (threshold < 1 || threshold > dqrr_max) { + DPAA2_PMD_WARN("drv_rx_intr_threshold %d out of [1, %d], clamping", + threshold, dqrr_max); + threshold = threshold < 1 ? 1 : dqrr_max; + } + timeout = dpaa2_dpio_holdoff_to_itp(dpio, priv->rx_intr_holdoff_us); /* build_epoll=false: the generic ethdev rx-intr API waits on the * application epoll, not the portal's private one (event PMD only). */ - ret = dpaa2_dpio_intr_init(dpio, false); /* VFIO eventfd, no MC */ + ret = dpaa2_dpio_intr_init(dpio, threshold, timeout, false); if (ret) return ret; @@ -3139,6 +3154,35 @@ dpaa2_get_devargs(struct rte_devargs *devargs, const char *key) return 1; } +static int +u32_devarg_handler(__rte_unused const char *key, const char *value, void *opaque) +{ + char *end; + unsigned long v = strtoul(value, &end, 0); + + if (*value == '\0' || *end != '\0' || v > UINT32_MAX) + return -1; + *(uint32_t *)opaque = (uint32_t)v; + + return 0; +} + +/* Read a u32-valued devarg into *out, leaving *out untouched if absent. */ +static void +dpaa2_get_devargs_u32(struct rte_devargs *devargs, const char *key, uint32_t *out) +{ + struct rte_kvargs *kvlist; + + if (!devargs) + return; + kvlist = rte_kvargs_parse(devargs->args, NULL); + if (!kvlist) + return; + if (rte_kvargs_count(kvlist, key)) + rte_kvargs_process(kvlist, key, u32_devarg_handler, out); + rte_kvargs_free(kvlist); +} + static int dpaa2_dev_init(struct rte_eth_dev *eth_dev) { @@ -3166,6 +3210,14 @@ dpaa2_dev_init(struct rte_eth_dev *eth_dev) DPAA2_PMD_INFO("No RX prefetch mode"); } + priv->rx_intr_holdoff_us = DPAA2_RX_INTR_HOLDOFF_US_DEF; + dpaa2_get_devargs_u32(dev->devargs, DRIVER_RX_INTR_HOLDOFF_US, + &priv->rx_intr_holdoff_us); + + priv->rx_intr_threshold = 0; + dpaa2_get_devargs_u32(dev->devargs, DRIVER_RX_INTR_THRESHOLD, + &priv->rx_intr_threshold); + if (dpaa2_get_devargs(dev->devargs, DRIVER_LOOPBACK_MODE)) { priv->flags |= DPAA2_RX_LOOPBACK_MODE; DPAA2_PMD_INFO("Rx loopback mode"); @@ -3681,5 +3733,7 @@ RTE_PMD_REGISTER_PARAM_STRING(NET_DPAA2_PMD_DRIVER_NAME, DRIVER_RX_PARSE_ERR_DROP "=<int>" DRIVER_ERROR_QUEUE "=<int>" DRIVER_NO_TAILDROP "=<int>" - DRIVER_NO_DATA_STASHING "=<int>"); + DRIVER_NO_DATA_STASHING "=<int> " + DRIVER_RX_INTR_HOLDOFF_US "=<uint32> " + DRIVER_RX_INTR_THRESHOLD "=<uint32>"); RTE_LOG_REGISTER_DEFAULT(dpaa2_logtype_pmd, NOTICE); diff --git a/drivers/net/dpaa2/dpaa2_ethdev.h b/drivers/net/dpaa2/dpaa2_ethdev.h index 3765f79e84..84785c0561 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.h +++ b/drivers/net/dpaa2/dpaa2_ethdev.h @@ -412,6 +412,13 @@ struct dpaa2_dev_priv { uint8_t max_cgs; uint8_t cgid_in_use[MAX_RX_QUEUES]; + /* DQRI holdoff (us) for rx-queue interrupts (drv_rx_intr_holdoff_us) */ + uint32_t rx_intr_holdoff_us; + /* DQRI threshold for rx-queue interrupts (drv_rx_intr_threshold); + * 0 = auto (DQRR ring depth - 1) + */ + uint32_t rx_intr_threshold; + uint16_t dpni_ver_major; uint16_t dpni_ver_minor; uint32_t speed_capa; -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 5/6] net/dpaa2: fix Rx queue count for primary process 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (3 preceding siblings ...) 2026-06-16 10:27 ` [PATCH v2 4/6] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy ` (2 subsequent siblings) 7 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev Cc: Maxime Leroy, stable, Hemant Agrawal, Sachin Saxena, Andrew Rybchenko, Ferruh Yigit, David Marchand The rx_queue_count callback was only assigned on the secondary process path of dpaa2_dev_init(), leaving eth_dev->rx_queue_count NULL for the primary process. The fast-path rte_eth_rx_queue_count() performs an unguarded indirect call in non-debug builds, so invoking it on a primary-process dpaa2 port dereferences a NULL function pointer and crashes. Assign the callback once before the process-type split so both the primary and secondary paths set it. Fixes: cbfc6111b557 ("ethdev: move inline device operations") Cc: stable@dpdk.org Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/net/dpaa2/dpaa2_ethdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 76e2df6167..ac7303c116 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -3410,6 +3410,7 @@ dpaa2_dev_init(struct rte_eth_dev *eth_dev) } eth_dev->dev_ops = &dpaa2_ethdev_ops; + eth_dev->rx_queue_count = dpaa2_dev_rx_queue_count; if (dpaa2_get_devargs(dev->devargs, DRIVER_LOOPBACK_MODE)) { eth_dev->rx_pkt_burst = dpaa2_dev_loopback_rx; -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (4 preceding siblings ...) 2026-06-16 10:27 ` [PATCH v2 5/6] net/dpaa2: fix Rx queue count for primary process Maxime Leroy @ 2026-06-16 10:27 ` Maxime Leroy 2026-06-16 14:11 ` Stephen Hemminger 2026-06-21 4:33 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Hemant Agrawal 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy 7 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-16 10:27 UTC (permalink / raw) To: dev; +Cc: Maxime Leroy, Hemant Agrawal, Sachin Saxena RTE_ETH_RX_OFFLOAD_VLAN_STRIP is advertised, but no hardware VLAN strip backs it: when enabled, the Rx burst calls rte_vlan_strip() on every frame, a software op masquerading as a hardware offload. It saves a forwarding application nothing: the datapath reads the L2 header anyway to classify or strip. The offload does not remove that read, it relocates it into the driver Rx burst, where it is far more expensive. The cost is a matter of timing. rte_vlan_strip() reaches the L2 header through rte_pktmbuf_mtod(), which dereferences mbuf->buf_addr. On a freshly recycled buffer that mbuf cacheline is cold. eth_fd_to_mbuf() has just written other fields of it (data_off, ol_flags), but buf_addr is a persistent field it does not rewrite. A write does not stall: it posts to the store buffer while the line fills in the background, and the rewritten fields are forwarded straight from there. buf_addr has nothing to forward, so it must be read from the line, whose fill is still in flight, and the read stalls. The ethertype read that follows, on the cold payload line, stalls again. Read later by the application, when the fill has completed, the same read hits. The offload just performs it at the worst possible moment. Measured on a single-core port-to-port forwarding test over two 10G ports (one core at 2 GHz, 64-byte untagged frames): - throughput 4.22 -> 5.00 Mpps (+18 percent) - IPC 0.93 -> 1.25: the cost was memory stall, not compute - L3/DRAM-bound L2 refills 319M -> 200M over 10s (-37 percent) perf confirms it: with the offload, the buf_addr load (the cold mbuf field) and the payload load account for about 84 percent of the Rx burst's L2 refills; removing it, those vanish and only the inherent DQRR dequeue misses remain. Stop advertising VLAN_STRIP and remove the rte_vlan_strip() calls from every Rx path. This is a behavioural change: the tag is left in the frame, so an application must strip it itself, on the L2 header it already reads. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/rel_notes/release_26_07.rst | 3 +++ drivers/net/dpaa2/dpaa2_ethdev.c | 1 - drivers/net/dpaa2/dpaa2_rxtx.c | 23 +++-------------------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index 3bc49c3910..1da1d7b729 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -143,6 +143,9 @@ New Features * **Updated NXP dpaa2 driver.** * Added Rx queue interrupt support. + * Removed the software VLAN strip offload: ``RTE_ETH_RX_OFFLOAD_VLAN_STRIP`` + is no longer advertised, as no hardware strip backs it. An application + that needs the tag removed must now strip it itself. * **Updated PCAP ethernet driver.** diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index ac7303c116..e0451d4ac6 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -48,7 +48,6 @@ static uint64_t dev_rx_offloads_sup = RTE_ETH_RX_OFFLOAD_SCTP_CKSUM | RTE_ETH_RX_OFFLOAD_OUTER_IPV4_CKSUM | RTE_ETH_RX_OFFLOAD_OUTER_UDP_CKSUM | - RTE_ETH_RX_OFFLOAD_VLAN_STRIP | RTE_ETH_RX_OFFLOAD_VLAN_FILTER | RTE_ETH_RX_OFFLOAD_TIMESTAMP; diff --git a/drivers/net/dpaa2/dpaa2_rxtx.c b/drivers/net/dpaa2/dpaa2_rxtx.c index 189accc1de..d16e4f8f35 100644 --- a/drivers/net/dpaa2/dpaa2_rxtx.c +++ b/drivers/net/dpaa2/dpaa2_rxtx.c @@ -890,10 +890,6 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) } #endif - if (eth_data->dev_conf.rxmode.offloads & - RTE_ETH_RX_OFFLOAD_VLAN_STRIP) - rte_vlan_strip(bufs[num_rx]); - dq_storage++; num_rx++; } while (pending); @@ -922,22 +918,14 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) return num_rx; } -/* Convert a DQRR'd FD (single or scatter-gather) to an mbuf and apply software - * VLAN strip, like the poll path. - */ +/* Convert a DQRR'd FD (single or scatter-gather) to an mbuf. */ static inline struct rte_mbuf * dpaa2_dqrr_fd_to_mbuf(const struct qbman_fd *fd, struct rte_eth_dev_data *eth_data) { - struct rte_mbuf *m; - if (unlikely(DPAA2_FD_GET_FORMAT(fd) == qbman_fd_sg)) - m = eth_sg_fd_to_mbuf(fd, eth_data->port_id); - else - m = eth_fd_to_mbuf(fd, eth_data->port_id); - if (eth_data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_VLAN_STRIP) - rte_vlan_strip(m); - return m; + return eth_sg_fd_to_mbuf(fd, eth_data->port_id); + return eth_fd_to_mbuf(fd, eth_data->port_id); } /* prefetch a DQRR'd FD's HW annotation (parse area) ahead of conversion */ @@ -1222,11 +1210,6 @@ dpaa2_dev_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) } #endif - if (eth_data->dev_conf.rxmode.offloads & - RTE_ETH_RX_OFFLOAD_VLAN_STRIP) { - rte_vlan_strip(bufs[num_rx]); - } - dq_storage++; num_rx++; num_pulled++; -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload 2026-06-16 10:27 ` [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy @ 2026-06-16 14:11 ` Stephen Hemminger 2026-06-16 15:40 ` Hemant Agrawal 0 siblings, 1 reply; 51+ messages in thread From: Stephen Hemminger @ 2026-06-16 14:11 UTC (permalink / raw) To: Maxime Leroy; +Cc: dev, Hemant Agrawal, Sachin Saxena On Tue, 16 Jun 2026 12:27:26 +0200 Maxime Leroy <maxime@leroys.fr> wrote: > RTE_ETH_RX_OFFLOAD_VLAN_STRIP is advertised, but no hardware VLAN strip > backs it: when enabled, the Rx burst calls rte_vlan_strip() on every > frame, a software op masquerading as a hardware offload. > > It saves a forwarding application nothing: the datapath reads the L2 > header anyway to classify or strip. The offload does not remove that > read, it relocates it into the driver Rx burst, where it is far more > expensive. > > The cost is a matter of timing. rte_vlan_strip() reaches the L2 header > through rte_pktmbuf_mtod(), which dereferences mbuf->buf_addr. On a > freshly recycled buffer that mbuf cacheline is cold. eth_fd_to_mbuf() > has just written other fields of it (data_off, ol_flags), but buf_addr > is a persistent field it does not rewrite. A write does not stall: it > posts to the store buffer while the line fills in the background, and > the rewritten fields are forwarded straight from there. buf_addr has > nothing to forward, so it must be read from the line, whose fill is > still in flight, and the read stalls. The ethertype read that follows, > on the cold payload line, stalls again. Read later by the application, > when the fill has completed, the same read hits. The offload just > performs it at the worst possible moment. > > Measured on a single-core port-to-port forwarding test over two 10G > ports (one core at 2 GHz, 64-byte untagged frames): > > - throughput 4.22 -> 5.00 Mpps (+18 percent) > - IPC 0.93 -> 1.25: the cost was memory stall, not compute > - L3/DRAM-bound L2 refills 319M -> 200M over 10s (-37 percent) > > perf confirms it: with the offload, the buf_addr load (the cold mbuf > field) and the payload load account for about 84 percent of the Rx > burst's L2 refills; removing it, those vanish and only the inherent DQRR > dequeue misses remain. > > Stop advertising VLAN_STRIP and remove the rte_vlan_strip() calls from > every Rx path. This is a behavioural change: the tag is left in the > frame, so an application must strip it itself, on the L2 header it > already reads. > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> Acked-by: Stephen Hemminger <stephen@networkplumber.org> ^ permalink raw reply [flat|nested] 51+ messages in thread
* RE: [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload 2026-06-16 14:11 ` Stephen Hemminger @ 2026-06-16 15:40 ` Hemant Agrawal 0 siblings, 0 replies; 51+ messages in thread From: Hemant Agrawal @ 2026-06-16 15:40 UTC (permalink / raw) To: Stephen Hemminger, Maxime Leroy; +Cc: dev@dpdk.org, Sachin Saxena > -----Original Message----- > From: Stephen Hemminger <stephen@networkplumber.org> > Sent: 16 June 2026 19:41 > To: Maxime Leroy <maxime@leroys.fr> > Cc: dev@dpdk.org; Hemant Agrawal <hemant.agrawal@nxp.com>; Sachin > Saxena <sachin.saxena@nxp.com> > Subject: Re: [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip > offload > Importance: High > > On Tue, 16 Jun 2026 12:27:26 +0200 > Maxime Leroy <maxime@leroys.fr> wrote: > > > RTE_ETH_RX_OFFLOAD_VLAN_STRIP is advertised, but no hardware VLAN > > strip backs it: when enabled, the Rx burst calls rte_vlan_strip() on > > every frame, a software op masquerading as a hardware offload. > > > > It saves a forwarding application nothing: the datapath reads the L2 > > header anyway to classify or strip. The offload does not remove that > > read, it relocates it into the driver Rx burst, where it is far more > > expensive. > > > > The cost is a matter of timing. rte_vlan_strip() reaches the L2 header > > through rte_pktmbuf_mtod(), which dereferences mbuf->buf_addr. On a > > freshly recycled buffer that mbuf cacheline is cold. eth_fd_to_mbuf() > > has just written other fields of it (data_off, ol_flags), but buf_addr > > is a persistent field it does not rewrite. A write does not stall: it > > posts to the store buffer while the line fills in the background, and > > the rewritten fields are forwarded straight from there. buf_addr has > > nothing to forward, so it must be read from the line, whose fill is > > still in flight, and the read stalls. The ethertype read that follows, > > on the cold payload line, stalls again. Read later by the application, > > when the fill has completed, the same read hits. The offload just > > performs it at the worst possible moment. > > > > Measured on a single-core port-to-port forwarding test over two 10G > > ports (one core at 2 GHz, 64-byte untagged frames): > > > > - throughput 4.22 -> 5.00 Mpps (+18 percent) > > - IPC 0.93 -> 1.25: the cost was memory stall, not compute > > - L3/DRAM-bound L2 refills 319M -> 200M over 10s (-37 percent) > > > > perf confirms it: with the offload, the buf_addr load (the cold mbuf > > field) and the payload load account for about 84 percent of the Rx > > burst's L2 refills; removing it, those vanish and only the inherent > > DQRR dequeue misses remain. > > > > Stop advertising VLAN_STRIP and remove the rte_vlan_strip() calls from > > every Rx path. This is a behavioural change: the tag is left in the > > frame, so an application must strip it itself, on the L2 header it > > already reads. > > > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > > Acked-by: Stephen Hemminger <stephen@networkplumber.org> Acked-by: Hemant Agrawal <hemant.agrawal@nxp.com> ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (5 preceding siblings ...) 2026-06-16 10:27 ` [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy @ 2026-06-21 4:33 ` Hemant Agrawal 2026-06-29 13:13 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy 7 siblings, 1 reply; 51+ messages in thread From: Hemant Agrawal @ 2026-06-21 4:33 UTC (permalink / raw) To: Maxime Leroy, dev Hi Maxime, On 16-06-2026 15:57, Maxime Leroy wrote: > This series lets a dpaa2 worker sleep on a queue's data-availability > notification instead of busy-polling, exposed through the generic > rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, > arm the interrupt and sleep when the queue runs dry). > We need to try out few things on it. I am afraid that it will take some time and our review for this patch will be late. Regards Hemant ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-06-21 4:33 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Hemant Agrawal @ 2026-06-29 13:13 ` Maxime Leroy 0 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-29 13:13 UTC (permalink / raw) To: Hemant Agrawal; +Cc: dev Hi Hemant, On Sun, Jun 21, 2026 at 6:33 AM Hemant Agrawal <hemant.agrawal@oss.nxp.com> wrote: > > Hi Maxime, > > On 16-06-2026 15:57, Maxime Leroy wrote: > > This series lets a dpaa2 worker sleep on a queue's data-availability > > notification instead of busy-polling, exposed through the generic > > rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, > > arm the interrupt and sleep when the queue runs dry). > > > We need to try out few things on it. I am afraid that it will take > some time and our review for this patch will be late. > Thanks for the update. No problem, please take the time you need. I am also preparing a v2 using CDAN only for notifications, while keeping packets on the regular volatile-dequeue path. This should improve PPS compared to v1, but I still need more time for validation and performance testing before sending it. I will also send the VLAN strip cleanup separately soon. Regards, Maxime ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy ` (6 preceding siblings ...) 2026-06-21 4:33 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Hemant Agrawal @ 2026-06-30 14:43 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 1/6] event/dpaa2: disable channel before closing it Maxime Leroy ` (6 more replies) 7 siblings, 7 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy This series lets a dpaa2 worker sleep on a queue's data-availability notification instead of busy-polling, exposed through the generic rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, arm the interrupt and sleep when the queue runs dry). Why it is not a trivial .rx_queue_intr_enable ---------------------------------------------- A worker wakes on its software portal's DQRI, which fires when the portal's DQRR holds frames. The default dpaa2 Rx burst pulls frames from the FQ with a volatile dequeue and cannot be interrupt-driven; to wake on the DQRI the FQ must instead be pushed to the portal's DQRR. The natural dpni_set_queue with a notification destination would have to target the worker's portal, but that portal is only known once a worker affines, after dev_start, and that MC command holds the global MC lock long enough to wedge the firmware while traffic runs. So the bind cannot be done late, against the polling lcore. Design ------ Each Rx FQ is bound to its own DPCON channel, statically, at dev_start while the dpni is still disabled (no knowledge of the polling lcore). A worker later subscribes its own ethrx portal to the channel and arms the DQRI in rx_queue_intr_enable, a one-shot per-portal op, never the wedging set_queue. On a wakeup the worker drains each of its queues by a volatile dequeue on the queue's own DPCON channel (one FQ per channel, so no per-frame demux); it polls all its queues, the same scheduling contract as plain DPDK polling. A queue can be re-homed to another lcore at runtime with no set_queue and no port stop. This reuses the event PMD's pushed/DQRR model but with one DPCON per FQ and static affinity (no QBMan scheduling), so the DPCON allocator is moved from the event driver to the fslmc bus and shared. Patch 1 disables the DPCON channel before closing it, an event/dpaa2 fix the shared allocator depends on. Patches 2 to 4 move the DPCON allocator to the fslmc bus, make the portal DQRI epoll optional, and add the dpcon_set_notification MC command. Patch 5 adds the interrupt support proper; patch 6 pins each DPIO's MSI to the lcore that arms it, a latency optimisation. Tested on LX2160A (lx2160acex7). v3: - Reworked the Rx drain. Both versions bind one DPCON per FQ, but v2 drained the shared portal DQRR and demuxed frames to their FQ by fqd_ctx, stashing foreign frames in a per-queue FIFO. v3 drains each queue with a volatile dequeue on its own channel (one FQ per channel), which drops the demux and stash code. - Dropped the rx_queue_count fix; it is applied to main. - Dropped the software VLAN strip patch; an independent net/dpaa2 cleanup, sent standalone and now applied to next-net. - Dropped the Depends-on: the ethdev fast-path ops fix is now in main. - Split the dpcon_set_notification MC command into its own patch. - Added an event/dpaa2 fix to disable the DPCON channel before close, needed once the allocator is shared. - Dropped the DQRI holdoff-tuning patch; the immediate-DQRI holdoff is now set inline in the arm path. - Added a patch reusing the event driver's MSI-affinity helper (exposed from its RTE_EVENT_DPAA2 guard) to pin the portal MSI to the lcore that arms it, so a CDAN wake lands on the worker's own core. v2: - Dropped the RSS RETA patch, an independent net/dpaa2 change the interrupt path does not require; it will be sent as its own series. - Dropped the ethdev fast-path ops fix; it is now a standalone series. - Dropped the eal/interrupts -EEXIST fix, applied to main by David Marchand. - Declared qbman_swp_interrupt_set_inhibit and qbman_swp_dqrr_size __rte_internal (David Marchand). - Minor formatting cleanup in the Rx interrupt setup. Maxime Leroy (6): event/dpaa2: disable channel before closing it bus/fslmc: move DPCON management from event driver to bus bus/fslmc/dpio: make the portal DQRI epoll optional bus/fslmc/mc: implement dpcon_set_notification net/dpaa2: support Rx queue interrupts net/dpaa2: pin Rx queue interrupt to the polling core doc/guides/nics/dpaa2.rst | 21 + doc/guides/nics/features/dpaa2.ini | 1 + doc/guides/rel_notes/release_26_07.rst | 1 + drivers/bus/fslmc/mc/dpcon.c | 31 ++ drivers/bus/fslmc/mc/fsl_dpcon.h | 18 + drivers/bus/fslmc/meson.build | 1 + .../fslmc/portal}/dpaa2_hw_dpcon.c | 17 +- drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 112 ++++-- drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 + drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 12 + .../fslmc/qbman/include/fsl_qbman_portal.h | 5 + drivers/bus/fslmc/qbman/qbman_portal.c | 5 + drivers/event/dpaa2/dpaa2_eventdev.h | 3 - drivers/event/dpaa2/meson.build | 1 - drivers/net/dpaa2/dpaa2_ethdev.c | 372 +++++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 4 + drivers/net/dpaa2/dpaa2_rxtx.c | 104 +++-- 17 files changed, 649 insertions(+), 71 deletions(-) rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (88%) base-commit: 030328f5f920a87dabde54dacd4f5ac411ddcac9 -- 2.43.0 ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 1/6] event/dpaa2: disable channel before closing it 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy @ 2026-06-30 14:43 ` Maxime Leroy 2026-07-03 17:21 ` Hemant Agrawal 2026-06-30 14:43 ` [PATCH v3 2/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy ` (5 subsequent siblings) 6 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy rte_dpaa2_close_dpcon_device() called dpcon_close() but not dpcon_disable(). close only releases the MC control session; it does not disable the channel. Cosmetic: an idle DPCON generates nothing and every consumer reprograms on setup, so disable before close only to return the channel clean, as the symmetric counterpart of the dpcon_enable() done on setup. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/event/dpaa2/dpaa2_hw_dpcon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/event/dpaa2/dpaa2_hw_dpcon.c b/drivers/event/dpaa2/dpaa2_hw_dpcon.c index ea5b0d4b85..f65a63a786 100644 --- a/drivers/event/dpaa2/dpaa2_hw_dpcon.c +++ b/drivers/event/dpaa2/dpaa2_hw_dpcon.c @@ -128,6 +128,7 @@ rte_dpaa2_close_dpcon_device(int object_id) if (dpcon_dev) { rte_dpaa2_free_dpcon_dev(dpcon_dev); + dpcon_disable(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); dpcon_close(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); TAILQ_REMOVE(&dpcon_dev_list, dpcon_dev, next); rte_free(dpcon_dev); -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 1/6] event/dpaa2: disable channel before closing it 2026-06-30 14:43 ` [PATCH v3 1/6] event/dpaa2: disable channel before closing it Maxime Leroy @ 2026-07-03 17:21 ` Hemant Agrawal 2026-07-06 9:11 ` Maxime Leroy 0 siblings, 1 reply; 51+ messages in thread From: Hemant Agrawal @ 2026-07-03 17:21 UTC (permalink / raw) To: Maxime Leroy, dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand [-- Attachment #1: Type: text/plain, Size: 2016 bytes --] On 30-06-2026 20:13, Maxime Leroy wrote: > rte_dpaa2_close_dpcon_device() called dpcon_close() but not > dpcon_disable(). close only releases the MC control session; it does not > disable the channel. Cosmetic: an idle DPCON generates nothing and every > consumer reprograms on setup, so disable before close only to return the > channel clean, as the symmetric counterpart of the dpcon_enable() done on > setup. > > Signed-off-by: Maxime Leroy<maxime@leroys.fr> > --- > drivers/event/dpaa2/dpaa2_hw_dpcon.c | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/drivers/event/dpaa2/dpaa2_hw_dpcon.c b/drivers/event/dpaa2/dpaa2_hw_dpcon.c > index ea5b0d4b85..f65a63a786 100644 > --- a/drivers/event/dpaa2/dpaa2_hw_dpcon.c > +++ b/drivers/event/dpaa2/dpaa2_hw_dpcon.c > @@ -128,6 +128,7 @@ rte_dpaa2_close_dpcon_device(int object_id) > > if (dpcon_dev) { > rte_dpaa2_free_dpcon_dev(dpcon_dev); > + dpcon_disable(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); > dpcon_close(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); > TAILQ_REMOVE(&dpcon_dev_list, dpcon_dev, next); > rte_free(dpcon_dev); The ordering here is wrong. |rte_dpaa2_free_dpcon_dev(dpcon_dev)| is called *before* |dpcon_disable()|. Once the device is returned to the shared pool via |rte_dpaa2_free_dpcon_dev()|, another thread can immediately re-allocate the same |dpcon_dev| pointer. The subsequent |dpcon_disable()| call would then quiesce a channel already in active use by the new owner — a use-after-free on the pool object. The correct teardown sequence must be: 1. dpcon_disable() — quiesce the hardware channel 2. dpcon_close() — release the MC control session 3. rte_dpaa2_free_dpcon_dev() — return to pool Please reorder accordingly: dpcon_disable(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); dpcon_close(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); TAILQ_REMOVE(&dpcon_dev_list, dpcon_dev, next); rte_dpaa2_free_dpcon_dev(dpcon_dev); rte_free(dpcon_dev); [-- Attachment #2: Type: text/html, Size: 3979 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 1/6] event/dpaa2: disable channel before closing it 2026-07-03 17:21 ` Hemant Agrawal @ 2026-07-06 9:11 ` Maxime Leroy 0 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-07-06 9:11 UTC (permalink / raw) To: Hemant Agrawal; +Cc: dev, hemant.agrawal, sachin.saxena, david.marchand Hi Hemant, On Fri, Jul 3, 2026 at 7:21 PM Hemant Agrawal <hemant.agrawal@oss.nxp.com> wrote: > > > On 30-06-2026 20:13, Maxime Leroy wrote: > > rte_dpaa2_close_dpcon_device() called dpcon_close() but not > dpcon_disable(). close only releases the MC control session; it does not > disable the channel. Cosmetic: an idle DPCON generates nothing and every > consumer reprograms on setup, so disable before close only to return the > channel clean, as the symmetric counterpart of the dpcon_enable() done on > setup. > > Signed-off-by: Maxime Leroy <maxime@leroys.fr> > --- > drivers/event/dpaa2/dpaa2_hw_dpcon.c | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/drivers/event/dpaa2/dpaa2_hw_dpcon.c b/drivers/event/dpaa2/dpaa2_hw_dpcon.c > index ea5b0d4b85..f65a63a786 100644 > --- a/drivers/event/dpaa2/dpaa2_hw_dpcon.c > +++ b/drivers/event/dpaa2/dpaa2_hw_dpcon.c > @@ -128,6 +128,7 @@ rte_dpaa2_close_dpcon_device(int object_id) > > if (dpcon_dev) { > rte_dpaa2_free_dpcon_dev(dpcon_dev); > + dpcon_disable(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); > dpcon_close(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); > TAILQ_REMOVE(&dpcon_dev_list, dpcon_dev, next); > rte_free(dpcon_dev); > > The ordering here is wrong. rte_dpaa2_free_dpcon_dev(dpcon_dev) is called before dpcon_disable(). Once the device is returned to the shared pool via rte_dpaa2_free_dpcon_dev(), another thread can immediately re-allocate the same dpcon_dev pointer. The subsequent dpcon_disable() call would then quiesce a channel already in active use by the new owner — a use-after-free on the pool object. > > The correct teardown sequence must be: > > dpcon_disable() — quiesce the hardware channel > dpcon_close() — release the MC control session > rte_dpaa2_free_dpcon_dev() — return to pool > > Please reorder accordingly: > > dpcon_disable(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); > > dpcon_close(&dpcon_dev->dpcon, CMD_PRI_LOW, dpcon_dev->token); > > TAILQ_REMOVE(&dpcon_dev_list, dpcon_dev, next); > > rte_dpaa2_free_dpcon_dev(dpcon_dev); > > rte_free(dpcon_dev); > > > On the ordering: there's no real race. This is the .close handler, run once at teardown from fslmc_vfio_close_group() on a single thread, not concurrent with the datapath, so nothing can re-acquire the node between free and close. But the patch shouldn't exist at all. The pooled DPAA2 objects use a reprogram-on-borrow model: the free helpers (rte_dpaa2_free_dpcon_dev, rte_dpaa2_free_dpci_dev, dpaa2_free_dpbp_dev) only drop in_use, and nothing un-programs an object on return, the next borrower reprograms. The sibling DPCI close handler confirms it: it does dpci_close() with no dpci_disable(). Adding dpcon_disable() here makes DPCON the lone outlier for no benefit, an enabled channel returned to the pool is inert (FQ parked at unbind, enable is re-applied idempotently on the next borrow). So there's no reason to treat dpcon/CDAN differently. Dropping the patch. Thanks, Maxime ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 2/6] bus/fslmc: move DPCON management from event driver to bus 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 1/6] event/dpaa2: disable channel before closing it Maxime Leroy @ 2026-06-30 14:43 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 3/6] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy ` (4 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy The DPCON allocation helpers (rte_dpaa2_alloc_dpcon_dev / rte_dpaa2_free_dpcon_dev) lived in the event driver, but a notification channel is a generic QBMan resource. Move dpaa2_hw_dpcon.c to the fslmc bus and export the helpers as internal symbols so both the event PMD and the net driver's rx-queue interrupt path can draw channels from the same pool. No functional change. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/meson.build | 1 + .../dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c | 16 +++++++--------- drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 6 ++++++ drivers/event/dpaa2/dpaa2_eventdev.h | 3 --- drivers/event/dpaa2/meson.build | 1 - 5 files changed, 14 insertions(+), 13 deletions(-) rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (90%) diff --git a/drivers/bus/fslmc/meson.build b/drivers/bus/fslmc/meson.build index ceae1c6c11..50d9e91a37 100644 --- a/drivers/bus/fslmc/meson.build +++ b/drivers/bus/fslmc/meson.build @@ -22,6 +22,7 @@ sources = files( 'mc/mc_sys.c', 'portal/dpaa2_hw_dpbp.c', 'portal/dpaa2_hw_dpci.c', + 'portal/dpaa2_hw_dpcon.c', 'portal/dpaa2_hw_dpio.c', 'portal/dpaa2_hw_dprc.c', 'qbman/qbman_portal.c', diff --git a/drivers/event/dpaa2/dpaa2_hw_dpcon.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c similarity index 90% rename from drivers/event/dpaa2/dpaa2_hw_dpcon.c rename to drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c index f65a63a786..6bcd6b9e6d 100644 --- a/drivers/event/dpaa2/dpaa2_hw_dpcon.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpcon.c @@ -18,13 +18,12 @@ #include <rte_cycles.h> #include <rte_kvargs.h> #include <dev_driver.h> -#include <ethdev_driver.h> +#include <eal_export.h> #include <bus_fslmc_driver.h> #include <mc/fsl_dpcon.h> #include <portal/dpaa2_hw_pvt.h> -#include "dpaa2_eventdev.h" -#include "dpaa2_eventdev_logs.h" +#include <fslmc_logs.h> TAILQ_HEAD(dpcon_dev_list, dpaa2_dpcon_dev); static struct dpcon_dev_list dpcon_dev_list @@ -55,8 +54,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, /* Allocate DPAA2 dpcon handle */ dpcon_node = rte_malloc(NULL, sizeof(struct dpaa2_dpcon_dev), 0); if (!dpcon_node) { - DPAA2_EVENTDEV_ERR( - "Memory allocation failed for dpcon device"); + DPAA2_BUS_ERR("Memory allocation failed for dpcon device"); return -1; } @@ -65,8 +63,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, ret = dpcon_open(&dpcon_node->dpcon, CMD_PRI_LOW, dpcon_id, &dpcon_node->token); if (ret) { - DPAA2_EVENTDEV_ERR("Unable to open dpcon device: err(%d)", - ret); + DPAA2_BUS_ERR("Unable to open dpcon device: err(%d)", ret); rte_free(dpcon_node); return -1; } @@ -75,8 +72,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, ret = dpcon_get_attributes(&dpcon_node->dpcon, CMD_PRI_LOW, dpcon_node->token, &attr); if (ret != 0) { - DPAA2_EVENTDEV_ERR("dpcon attribute fetch failed: err(%d)", - ret); + DPAA2_BUS_ERR("dpcon attribute fetch failed: err(%d)", ret); rte_free(dpcon_node); return -1; } @@ -92,6 +88,7 @@ rte_dpaa2_create_dpcon_device(int dev_fd __rte_unused, return 0; } +RTE_EXPORT_INTERNAL_SYMBOL(rte_dpaa2_alloc_dpcon_dev) struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void) { struct dpaa2_dpcon_dev *dpcon_dev = NULL; @@ -105,6 +102,7 @@ struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void) return dpcon_dev; } +RTE_EXPORT_INTERNAL_SYMBOL(rte_dpaa2_free_dpcon_dev) void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon) { struct dpaa2_dpcon_dev *dpcon_dev = NULL; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h index e625a5c035..2a6d970101 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h @@ -274,6 +274,12 @@ struct dpaa2_dpcon_dev { uint8_t channel_index; }; +/* DPCON channel pool, shared by the net rx-interrupt path and the event PMD */ +__rte_internal +struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void); +__rte_internal +void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon); + /* Refer to Table 7-3 in SEC BG */ #define QBMAN_FLE_WORD4_FMT_SBF 0x0 /* Single buffer frame */ #define QBMAN_FLE_WORD4_FMT_SGE 0x2 /* Scatter gather frame */ diff --git a/drivers/event/dpaa2/dpaa2_eventdev.h b/drivers/event/dpaa2/dpaa2_eventdev.h index bb87bdbab2..3883a26c91 100644 --- a/drivers/event/dpaa2/dpaa2_eventdev.h +++ b/drivers/event/dpaa2/dpaa2_eventdev.h @@ -85,9 +85,6 @@ struct dpaa2_eventdev { uint32_t event_dev_cfg; }; -struct dpaa2_dpcon_dev *rte_dpaa2_alloc_dpcon_dev(void); -void rte_dpaa2_free_dpcon_dev(struct dpaa2_dpcon_dev *dpcon); - int test_eventdev_dpaa2(void); #endif /* __DPAA2_EVENTDEV_H__ */ diff --git a/drivers/event/dpaa2/meson.build b/drivers/event/dpaa2/meson.build index dd5063af43..62b8507652 100644 --- a/drivers/event/dpaa2/meson.build +++ b/drivers/event/dpaa2/meson.build @@ -7,7 +7,6 @@ if not is_linux endif deps += ['bus_vdev', 'net_dpaa2', 'crypto_dpaa2_sec'] sources = files( - 'dpaa2_hw_dpcon.c', 'dpaa2_eventdev.c', 'dpaa2_eventdev_selftest.c', ) -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v3 3/6] bus/fslmc/dpio: make the portal DQRI epoll optional 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 1/6] event/dpaa2: disable channel before closing it Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 2/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy @ 2026-06-30 14:43 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 4/6] bus/fslmc/mc: implement dpcon_set_notification Maxime Leroy ` (3 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy dpaa2_dpio_intr_init() builds a private epoll instance the event PMD sleeps on. The upcoming net rx-queue-interrupt path waits on the application's own epoll instead, so that instance would be built but never used. Add a build_epoll parameter: pass true to build it (event PMD), false to skip the epoll_create/epoll_ctl. epoll_fd is set to -1 when none is built and closed in intr_deinit only when valid. The sole caller passes true: no functional change. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 40 +++++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index 2a9e519668..f0b843e3a3 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -205,13 +205,12 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } -static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev) +static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) { struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; int threshold = 0x3, timeout = 0xFF; - dpio_epoll_fd = epoll_create(1); ret = rte_dpaa2_intr_enable(dpio_dev->intr_handle, 0); if (ret) { DPAA2_BUS_ERR("Interrupt registration failed"); @@ -231,16 +230,30 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev) qbman_swp_dqrr_thrshld_write(dpio_dev->sw_portal, threshold); qbman_swp_intr_timeout_write(dpio_dev->sw_portal, timeout); - eventfd = rte_intr_fd_get(dpio_dev->intr_handle); - epoll_ev.events = EPOLLIN | EPOLLPRI | EPOLLET; - epoll_ev.data.fd = eventfd; + dpio_dev->epoll_fd = -1; - ret = epoll_ctl(dpio_epoll_fd, EPOLL_CTL_ADD, eventfd, &epoll_ev); - if (ret < 0) { - DPAA2_BUS_ERR("epoll_ctl failed"); - return -1; + /* private epoll for the event PMD; the net rx-intr path uses the app's */ + if (build_epoll) { + dpio_epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (dpio_epoll_fd < 0) { + DPAA2_BUS_ERR("epoll_create failed"); + rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); + return -1; + } + + eventfd = rte_intr_fd_get(dpio_dev->intr_handle); + epoll_ev.events = EPOLLIN | EPOLLPRI | EPOLLET; + epoll_ev.data.fd = eventfd; + + ret = epoll_ctl(dpio_epoll_fd, EPOLL_CTL_ADD, eventfd, &epoll_ev); + if (ret < 0) { + DPAA2_BUS_ERR("epoll_ctl failed"); + rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); + close(dpio_epoll_fd); + return -1; + } + dpio_dev->epoll_fd = dpio_epoll_fd; } - dpio_dev->epoll_fd = dpio_epoll_fd; return 0; } @@ -253,7 +266,10 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) if (ret) DPAA2_BUS_ERR("DPIO interrupt disable failed"); - close(dpio_dev->epoll_fd); + if (dpio_dev->epoll_fd >= 0) { + close(dpio_dev->epoll_fd); + dpio_dev->epoll_fd = -1; + } } #endif @@ -277,7 +293,7 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) } #ifdef RTE_EVENT_DPAA2 - if (dpaa2_dpio_intr_init(dpio_dev)) { + if (dpaa2_dpio_intr_init(dpio_dev, true)) { DPAA2_BUS_ERR("Interrupt registration failed for dpio"); return -1; } -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v3 4/6] bus/fslmc/mc: implement dpcon_set_notification 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy ` (2 preceding siblings ...) 2026-06-30 14:43 ` [PATCH v3 3/6] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy @ 2026-06-30 14:43 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts Maxime Leroy ` (2 subsequent siblings) 6 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy The DPCON_CMDID_SET_NOTIFICATION command id and its wire struct were already present in the flib, but the dpcon_set_notification() function and the public dpcon_notification_cfg were never implemented. Add them so a DPCON channel can be pointed at a DPIO for CDAN delivery, which the net/dpaa2 Rx-queue-interrupt path needs. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/mc/dpcon.c | 31 +++++++++++++++++++++++++++++++ drivers/bus/fslmc/mc/fsl_dpcon.h | 18 ++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/bus/fslmc/mc/dpcon.c b/drivers/bus/fslmc/mc/dpcon.c index cd909fcac4..3ab2a12637 100644 --- a/drivers/bus/fslmc/mc/dpcon.c +++ b/drivers/bus/fslmc/mc/dpcon.c @@ -338,3 +338,34 @@ int dpcon_get_api_version(struct fsl_mc_io *mc_io, return 0; } + +/** + * dpcon_set_notification() - Set the DPCON notification destination + * @mc_io: Pointer to MC portal's I/O object + * @cmd_flags: Command flags; one or more of 'MC_CMD_FLAG_' + * @token: Token of DPCON object + * @cfg: Notification parameters (DPIO, priority, user context) + * + * Return: '0' on Success; Error code otherwise + */ +RTE_EXPORT_INTERNAL_SYMBOL(dpcon_set_notification) +int dpcon_set_notification(struct fsl_mc_io *mc_io, + uint32_t cmd_flags, + uint16_t token, + const struct dpcon_notification_cfg *cfg) +{ + struct dpcon_cmd_set_notification *dpcon_cmd; + struct mc_command cmd = { 0 }; + + /* prepare command */ + cmd.header = mc_encode_cmd_header(DPCON_CMDID_SET_NOTIFICATION, + cmd_flags, + token); + dpcon_cmd = (struct dpcon_cmd_set_notification *)cmd.params; + dpcon_cmd->dpio_id = cpu_to_le32(cfg->dpio_id); + dpcon_cmd->priority = cfg->priority; + dpcon_cmd->user_ctx = cpu_to_le64(cfg->user_ctx); + + /* send command to mc*/ + return mc_send_command(mc_io, &cmd); +} diff --git a/drivers/bus/fslmc/mc/fsl_dpcon.h b/drivers/bus/fslmc/mc/fsl_dpcon.h index f2668da1b1..3bef97f6f5 100644 --- a/drivers/bus/fslmc/mc/fsl_dpcon.h +++ b/drivers/bus/fslmc/mc/fsl_dpcon.h @@ -95,4 +95,22 @@ int dpcon_get_api_version(struct fsl_mc_io *mc_io, uint16_t *major_ver, uint16_t *minor_ver); +/** + * struct dpcon_notification_cfg - Notification parameters + * @dpio_id: DPIO object id that receives the channel's CDAN + * @priority: Priority within the DPIO channel (0-7) + * @user_ctx: User context provided with each CDAN message + */ +struct dpcon_notification_cfg { + int dpio_id; + uint8_t priority; + uint64_t user_ctx; +}; + +__rte_internal +int dpcon_set_notification(struct fsl_mc_io *mc_io, + uint32_t cmd_flags, + uint16_t token, + const struct dpcon_notification_cfg *cfg); + #endif /* __FSL_DPCON_H */ -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy ` (3 preceding siblings ...) 2026-06-30 14:43 ` [PATCH v3 4/6] bus/fslmc/mc: implement dpcon_set_notification Maxime Leroy @ 2026-06-30 14:43 ` Maxime Leroy 2026-07-03 17:21 ` Hemant Agrawal 2026-06-30 14:43 ` [PATCH v3 6/6] net/dpaa2: pin Rx queue interrupt to the polling core Maxime Leroy 2026-07-03 16:03 ` [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts Stephen Hemminger 6 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy Implement .rx_queue_intr_enable / .rx_queue_intr_disable so a worker can sleep on a queue's data-availability notification instead of busy-polling, through the generic rte_eth_dev_rx_intr_* API. A worker wakes on its software portal's DQRI, which fires when the portal's DQRR is non-empty. To wake on a specific Rx FQ, the FQ is scheduled to its own DPCON channel, and that channel is told to post a CDAN (Channel Dequeue Available Notification) to the worker's portal when it goes non-empty. dev_start binds each Rx FQ DEST_DPCON on the still-disabled dpni (the polling lcore is not known yet); a worker later, in rx_queue_intr_enable, points the channel's CDAN at its own DPIO (dpcon_set_notification), enables it on its portal and arms the DQRI. One DPCON per FQ (drawn from the shared pool the DPCON move to the fslmc bus feeds), so each queue keeps its own notification and can be re-homed to another lcore without a set_queue or a port stop. Unlike the event PMD, which concentrates N FQs onto one DPCON and lets the QBMan scheduler load-balance across cores, affinity here is static and there is no scheduling. A CDAN only notifies; it does not carry frames. On wake the worker drains each queue with a volatile dequeue on its DPCON channel (dpaa2_dev_prefetch_rx_channel, the prefetch poll path retargeted from the FQ to the channel) and consumes the CDAN from the DQRR. The notification is one-shot, re-armed on the next idle->sleep. The DQRI is fired immediately on the first CDAN: the arm sets the ITP holdoff to 0, so the interrupt is raised as soon as the DQRR goes non-empty; with a zero holdoff the DQRR threshold is immaterial. wire | [ DPMAC ] | [ DPNI ] (1) | TC0: FQ0 FQ1 FQ2 FQ3 (2) | | | | (3) [DPCON][DPCON][DPCON][DPCON] \ | | / (4) [ DPIO A ] [ DPIO B ] (5) | | DQRR DQRR (6) | | DQRI DQRI (7) | | eventfd eventfd (8) | | rte_epoll_wait rte_epoll_wait (9) | dpaa2_dev_prefetch_rx_channel (10) | frames -> store (1) WRIOP picks a TC (QoS), then RSS-hashes within the TC to an FQ (2) FQ0..FQ3 are the rte_eth Rx queues (3) dpni_set_queue(DEST_DPCON): one DPCON per FQ (FQ scheduled to it) (4) the lcore portal points each DPCON's CDAN at itself (dpcon_set_notification) and enables it; its dedicated channel static-dequeues the notifications (5) one QBMan software portal per lcore (6) a non-empty FQ makes its DPCON post a CDAN (a notification, not the frames) into the portal DQRR (7) DQRI is raised when the DQRR is non-empty (a CDAN is pending) (8) a portal's queues share one fd (its DQRI eventfd) (9) worker sleeps here when all its queues are idle (10) on wake the worker pulls each notified queue's channel; frames land in the per-queue store, the CDAN is consumed from the DQRR The DQRI and eventfd are portal-wide: a queue's eventfd is its portal's DQRI fd, and the inhibit bit is refcounted by armed queues so disabling one queue never masks a sibling. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- doc/guides/nics/dpaa2.rst | 21 + doc/guides/nics/features/dpaa2.ini | 1 + doc/guides/rel_notes/release_26_07.rst | 1 + drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 67 +++- drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 8 + drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 6 + .../fslmc/qbman/include/fsl_qbman_portal.h | 5 + drivers/bus/fslmc/qbman/qbman_portal.c | 5 + drivers/net/dpaa2/dpaa2_ethdev.c | 370 +++++++++++++++++- drivers/net/dpaa2/dpaa2_ethdev.h | 4 + drivers/net/dpaa2/dpaa2_rxtx.c | 104 +++-- 11 files changed, 546 insertions(+), 46 deletions(-) diff --git a/doc/guides/nics/dpaa2.rst b/doc/guides/nics/dpaa2.rst index 2d70bd0ab9..ec7ffaf171 100644 --- a/doc/guides/nics/dpaa2.rst +++ b/doc/guides/nics/dpaa2.rst @@ -406,6 +406,7 @@ Features of the DPAA2 PMD are: - Jumbo frames - Link flow control - Scattered and gather for TX and RX +- Rx queue interrupts - :ref:`Traffic Management API <dptmapi>` @@ -553,6 +554,26 @@ is fixed and cannot be changed. So, even when the ``rxmode.mtu`` member of ``struct rte_eth_conf`` is set to a value lower than 10240, frames up to 10240 bytes can still reach the host interface. +Rx queue interrupts +~~~~~~~~~~~~~~~~~~~ + +Rx queue interrupts (``rte_eth_dev_rx_intr_*``) let a worker sleep on a queue +instead of busy-polling. Note: + +- Each Rx queue needs its own DPCON (notification channel), drawn from the + shared fslmc bus pool. The DPRC must provision at least as many DPCON + objects as Rx queues (these are shared with the event PMD); queue setup + fails with ``-ENODEV`` if the pool is exhausted. +- ``rte_eth_dev_rx_intr_enable()`` / ``rte_eth_dev_rx_intr_disable()`` drive the + per-lcore QBMan software portal and must be called from the lcore that polls + the queue. The application must call ``rte_eth_dev_rx_intr_disable()`` on that + lcore before stopping or closing the port. +- The Rx error queue (``drv_err_queue`` devarg) is not drained while a queue + runs in interrupt mode. +- The DPIO MSI is pinned to the polling core as a best-effort latency + optimisation (writes ``/proc/irq/<n>/smp_affinity``, needs privilege, and + irqbalance may move it back); wakeups are delivered correctly regardless. + Other Limitations ~~~~~~~~~~~~~~~~~ diff --git a/doc/guides/nics/features/dpaa2.ini b/doc/guides/nics/features/dpaa2.ini index 5def653d1d..4788afef8d 100644 --- a/doc/guides/nics/features/dpaa2.ini +++ b/doc/guides/nics/features/dpaa2.ini @@ -8,6 +8,7 @@ Speed capabilities = Y Link status = Y Link status event = Y Burst mode info = Y +Rx interrupt = Y Queue start/stop = Y Scattered Rx = Y MTU update = Y diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst index c40d3d73a2..ca44a1cf32 100644 --- a/doc/guides/rel_notes/release_26_07.rst +++ b/doc/guides/rel_notes/release_26_07.rst @@ -155,6 +155,7 @@ New Features * Removed the software VLAN strip offload: ``RTE_ETH_RX_OFFLOAD_VLAN_STRIP`` is no longer advertised, as no hardware strip backs it. An application that needs the tag removed must now strip it itself. + * Added Rx queue interrupt support (rte_eth_dev_rx_intr_*). * **Updated PCAP ethernet driver.** diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index f0b843e3a3..354d81b4d1 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -204,12 +204,18 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } +#endif /* RTE_EVENT_DPAA2 */ -static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epoll) +/* arm the portal DQRI (threshold/timeout); idempotent, first caller per portal wins */ +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_init) +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, + int timeout, bool build_epoll) { - struct epoll_event epoll_ev; int eventfd, dpio_epoll_fd, ret; - int threshold = 0x3, timeout = 0xFF; + struct epoll_event epoll_ev; + + if (dpio_dev->intr_enabled) + return 0; ret = rte_dpaa2_intr_enable(dpio_dev->intr_handle, 0); if (ret) { @@ -217,12 +223,6 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epol return -1; } - if (getenv("DPAA2_PORTAL_INTR_THRESHOLD")) - threshold = atoi(getenv("DPAA2_PORTAL_INTR_THRESHOLD")); - - if (getenv("DPAA2_PORTAL_INTR_TIMEOUT")) - sscanf(getenv("DPAA2_PORTAL_INTR_TIMEOUT"), "%x", &timeout); - qbman_swp_interrupt_set_trigger(dpio_dev->sw_portal, QBMAN_SWP_INTERRUPT_DQRI); qbman_swp_interrupt_clear_status(dpio_dev->sw_portal, 0xffffffff); @@ -255,13 +255,19 @@ static int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, bool build_epol dpio_dev->epoll_fd = dpio_epoll_fd; } + dpio_dev->intr_enabled = 1; + return 0; } -static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_deinit) +void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) { int ret; + if (!dpio_dev->intr_enabled) + return; + ret = rte_dpaa2_intr_disable(dpio_dev->intr_handle, 0); if (ret) DPAA2_BUS_ERR("DPIO interrupt disable failed"); @@ -270,11 +276,11 @@ static void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev) close(dpio_dev->epoll_fd); dpio_dev->epoll_fd = -1; } + dpio_dev->intr_enabled = 0; } -#endif static int -dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) +dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id, bool ethrx) { int sdest, ret; @@ -293,9 +299,29 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) } #ifdef RTE_EVENT_DPAA2 - if (dpaa2_dpio_intr_init(dpio_dev, true)) { - DPAA2_BUS_ERR("Interrupt registration failed for dpio"); - return -1; + { + /* ethrx portal: immediate DQRI (1, 0); event portal: coalesced (3, 0xFF). + * Each mode is tunable through its own env vars. + */ + const char *thr_env = "DPAA2_PORTAL_INTR_THRESHOLD"; + const char *to_env = "DPAA2_PORTAL_INTR_TIMEOUT"; + int threshold = 3, timeout = 0xFF; + + if (ethrx) { + thr_env = "DPAA2_PORTAL_ETHRX_INTR_THRESHOLD"; + to_env = "DPAA2_PORTAL_ETHRX_INTR_TIMEOUT"; + threshold = 1; + timeout = 0; + } + if (getenv(thr_env)) + threshold = atoi(getenv(thr_env)); + if (getenv(to_env)) + sscanf(getenv(to_env), "%x", &timeout); + + if (dpaa2_dpio_intr_init(dpio_dev, threshold, timeout, !ethrx)) { + DPAA2_BUS_ERR("Interrupt registration failed for dpio"); + return -1; + } } dpaa2_affine_dpio_intr_to_respective_core(dpio_dev->hw_id, cpu_id); #endif @@ -306,14 +332,13 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id) static void dpaa2_put_qbman_swp(struct dpaa2_dpio_dev *dpio_dev) { if (dpio_dev) { -#ifdef RTE_EVENT_DPAA2 + /* tear down unconditionally; intr_deinit returns early if not enabled */ dpaa2_dpio_intr_deinit(dpio_dev); -#endif rte_atomic16_clear(&dpio_dev->ref_count); } } -static struct dpaa2_dpio_dev *dpaa2_get_qbman_swp(void) +static struct dpaa2_dpio_dev *dpaa2_get_qbman_swp(bool ethrx) { struct dpaa2_dpio_dev *dpio_dev = NULL; int cpu_id; @@ -339,7 +364,7 @@ static struct dpaa2_dpio_dev *dpaa2_get_qbman_swp(void) if (dpaa2_svr_family != SVR_LX2160A) qbman_swp_update(dpio_dev->sw_portal, 1); } else { - ret = dpaa2_configure_stashing(dpio_dev, cpu_id); + ret = dpaa2_configure_stashing(dpio_dev, cpu_id, ethrx); if (ret) { DPAA2_BUS_ERR("dpaa2_configure_stashing failed"); rte_atomic16_clear(&dpio_dev->ref_count); @@ -366,7 +391,7 @@ dpaa2_affine_qbman_swp(void) /* Populate the dpaa2_io_portal structure */ if (!RTE_PER_LCORE(_dpaa2_io).dpio_dev) { - dpio_dev = dpaa2_get_qbman_swp(); + dpio_dev = dpaa2_get_qbman_swp(false); if (!dpio_dev) { DPAA2_BUS_ERR("Error in software portal allocation"); return -1; @@ -388,7 +413,7 @@ dpaa2_affine_qbman_ethrx_swp(void) /* Populate the dpaa2_io_portal structure */ if (!RTE_PER_LCORE(_dpaa2_io).ethrx_dpio_dev) { - dpio_dev = dpaa2_get_qbman_swp(); + dpio_dev = dpaa2_get_qbman_swp(true); if (!dpio_dev) { DPAA2_BUS_ERR("Error in software portal allocation"); return -1; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h index 328e1e788a..1f6e521341 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h @@ -50,6 +50,14 @@ int dpaa2_affine_qbman_swp(void); __rte_internal int dpaa2_affine_qbman_ethrx_swp(void); +/* set up / tear down a DPIO portal's DQRI interrupt (rx-queue interrupt mode) */ +__rte_internal +int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, + int timeout, bool build_epoll); + +__rte_internal +void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev); + /* allocate memory for FQ - dq storage */ __rte_internal int diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h index 2a6d970101..194dcec16a 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_pvt.h @@ -133,6 +133,8 @@ struct dpaa2_dpio_dev { struct rte_intr_handle *intr_handle; /* Interrupt related info */ int32_t epoll_fd; /**< File descriptor created for interrupt polling */ int32_t hw_id; /**< An unique ID of this DPIO device instance */ + uint8_t intr_enabled; /**< DQRI portal interrupt already set up */ + uint16_t ethrx_intr_refcnt; /**< rx queues currently armed on this portal */ struct dpaa2_portal_dqrr dpaa2_held_bufs; }; @@ -195,6 +197,10 @@ struct __rte_cache_aligned dpaa2_queue { uint64_t offloads; uint64_t lpbk_cntx; uint8_t data_stashing_off; + /* NAPI rx-interrupt: per-queue DPCON statically bound to this FQ at dev_start */ + struct dpaa2_dpcon_dev *napi_dpcon; /*!< notif channel, NULL = napi off */ + RTE_ATOMIC(struct dpaa2_dpio_dev *) napi_sub_dpio; /*!< subscribed portal or NULL */ + uint8_t napi_armed; /*!< this queue requests DQRI wakeups */ }; struct swp_active_dqs { diff --git a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h index 5375ea386d..b9e7789766 100644 --- a/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h +++ b/drivers/bus/fslmc/qbman/include/fsl_qbman_portal.h @@ -189,6 +189,7 @@ int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p); * @p: the given software portal object. * @mask: The value to set in SWP_IIR register. */ +__rte_internal void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit); /************/ @@ -400,6 +401,7 @@ void qbman_pull_desc_set_wq(struct qbman_pull_desc *d, uint32_t wqid, * @chid: the channel id to be dequeued. * @dct: the dequeue command type. */ +__rte_internal void qbman_pull_desc_set_channel(struct qbman_pull_desc *d, uint32_t chid, enum qbman_pull_type_e dct); @@ -1298,6 +1300,7 @@ int qbman_swp_fq_xoff(struct qbman_swp *s, uint32_t fqid); * * Return 0 for success, or negative error code for failure. */ +__rte_internal int qbman_swp_CDAN_set_context(struct qbman_swp *s, uint16_t channelid, uint64_t ctx); @@ -1308,6 +1311,7 @@ int qbman_swp_CDAN_set_context(struct qbman_swp *s, uint16_t channelid, * * Return 0 for success, or negative error code for failure. */ +__rte_internal int qbman_swp_CDAN_enable(struct qbman_swp *s, uint16_t channelid); /** @@ -1317,6 +1321,7 @@ int qbman_swp_CDAN_enable(struct qbman_swp *s, uint16_t channelid); * * Return 0 for success, or negative error code for failure. */ +__rte_internal int qbman_swp_CDAN_disable(struct qbman_swp *s, uint16_t channelid); /** diff --git a/drivers/bus/fslmc/qbman/qbman_portal.c b/drivers/bus/fslmc/qbman/qbman_portal.c index 84853924e7..c93bec5dd3 100644 --- a/drivers/bus/fslmc/qbman/qbman_portal.c +++ b/drivers/bus/fslmc/qbman/qbman_portal.c @@ -448,6 +448,7 @@ int qbman_swp_interrupt_get_inhibit(struct qbman_swp *p) return qbman_cinh_read(&p->sys, QBMAN_CINH_SWP_IIR); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_interrupt_set_inhibit) void qbman_swp_interrupt_set_inhibit(struct qbman_swp *p, int inhibit) { qbman_cinh_write(&p->sys, QBMAN_CINH_SWP_IIR, @@ -1878,6 +1879,7 @@ void qbman_pull_desc_set_wq(struct qbman_pull_desc *d, uint32_t wqid, d->pull.dq_src = wqid; } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_pull_desc_set_channel) void qbman_pull_desc_set_channel(struct qbman_pull_desc *d, uint32_t chid, enum qbman_pull_type_e dct) { @@ -2921,6 +2923,7 @@ static int qbman_swp_CDAN_set(struct qbman_swp *s, uint16_t channelid, return 0; } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_CDAN_set_context) int qbman_swp_CDAN_set_context(struct qbman_swp *s, uint16_t channelid, uint64_t ctx) { @@ -2929,6 +2932,7 @@ int qbman_swp_CDAN_set_context(struct qbman_swp *s, uint16_t channelid, 0, ctx); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_CDAN_enable) int qbman_swp_CDAN_enable(struct qbman_swp *s, uint16_t channelid) { return qbman_swp_CDAN_set(s, channelid, @@ -2936,6 +2940,7 @@ int qbman_swp_CDAN_enable(struct qbman_swp *s, uint16_t channelid) 1, 0); } +RTE_EXPORT_INTERNAL_SYMBOL(qbman_swp_CDAN_disable) int qbman_swp_CDAN_disable(struct qbman_swp *s, uint16_t channelid) { return qbman_swp_CDAN_set(s, channelid, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index a68404ee5e..36f8669644 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -5,6 +5,8 @@ #include <time.h> #include <net/if.h> +#include <unistd.h> +#include <errno.h> #include <eal_export.h> #include <rte_mbuf.h> @@ -25,6 +27,7 @@ #include <dpaa2_hw_mempool.h> #include <dpaa2_hw_dpio.h> #include <mc/fsl_dpmng.h> +#include <mc/fsl_dpcon.h> #include "dpaa2_ethdev.h" #include "dpaa2_sparser.h" #include <fsl_qbman_debug.h> @@ -658,6 +661,8 @@ dpaa2_clear_queue_active_dps(struct dpaa2_queue *q, int num_lcores) } } +static void dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q); + static void dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) { @@ -675,6 +680,12 @@ dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) /* cleaning up queue storage */ for (i = 0; i < priv->nb_rx_queues; i++) { dpaa2_q = priv->rx_vq[i]; + if (dpaa2_q->napi_dpcon) { /* release the rx-intr channel */ + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + dpaa2_q->napi_sub_dpio = NULL; + } dpaa2_clear_queue_active_dps(dpaa2_q, RTE_MAX_LCORE); dpaa2_queue_storage_free(dpaa2_q, @@ -880,6 +891,26 @@ dpaa2_eth_dev_configure(struct rte_eth_dev *dev) } } + if (dev->data->dev_conf.intr_conf.rxq) { + if (!dev->intr_handle) + dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); + if (!dev->intr_handle || + rte_intr_vec_list_alloc(dev->intr_handle, "rxq_intr", + dev->data->nb_rx_queues) || + rte_intr_nb_efd_set(dev->intr_handle, dev->data->nb_rx_queues) || + rte_intr_type_set(dev->intr_handle, RTE_INTR_HANDLE_EXT)) { + DPAA2_PMD_ERR("Failed to set up rx-queue interrupts"); + /* capture the error before cleanup may clobber rte_errno */ + ret = rte_errno ? -rte_errno : -EIO; + if (dev->intr_handle) { + rte_intr_vec_list_free(dev->intr_handle); + rte_intr_instance_free(dev->intr_handle); + dev->intr_handle = NULL; + } + return ret; + } + } + dpaa2_tm_init(dev); return 0; @@ -898,6 +929,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, { struct dpaa2_dev_priv *priv = dev->data->dev_private; struct fsl_mc_io *dpni = dev->process_private; + bool dpcon_allocated = false; struct dpaa2_queue *dpaa2_q; struct dpni_queue cfg; uint8_t options = 0; @@ -938,6 +970,25 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, dpaa2_q->bp_array = rte_dpaa2_bpid_info; dpaa2_q->offloads = rx_conf->offloads; + /* NAPI: grab a DPCON channel for dev_start to bind this FQ statically */ + dpaa2_q->napi_sub_dpio = NULL; + if (dev->data->dev_conf.intr_conf.rxq && !dpaa2_q->napi_dpcon) { + dpaa2_q->napi_dpcon = rte_dpaa2_alloc_dpcon_dev(); + if (!dpaa2_q->napi_dpcon) { + DPAA2_PMD_ERR("rxq %d: no DPCON for rx-queue interrupts", + rx_queue_id); + return -ENODEV; + } + dpcon_allocated = true; + /* the DPCON must be enabled to generate CDAN notifications */ + ret = dpcon_enable(&dpaa2_q->napi_dpcon->dpcon, CMD_PRI_LOW, + dpaa2_q->napi_dpcon->token); + if (ret) { + DPAA2_PMD_ERR("rxq %d: dpcon_enable: %d", rx_queue_id, ret); + goto err_free_dpcon; + } + } + /*Get the flow id from given VQ id*/ flow_id = dpaa2_q->flow_id; memset(&cfg, 0, sizeof(struct dpni_queue)); @@ -945,6 +996,10 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, options = options | DPNI_QUEUE_OPT_USER_CTX; cfg.user_context = (size_t)(dpaa2_q); + /* clear any stale DPIO dest left scheduled by a prior rx-intr run */ + options |= DPNI_QUEUE_OPT_DEST; + cfg.destination.type = DPNI_DEST_NONE; + /* check if a private cgr available. */ for (i = 0; i < priv->max_cgs; i++) { if (!priv->cgid_in_use[i]) { @@ -985,7 +1040,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, dpaa2_q->tc_index, flow_id, options, &cfg); if (ret) { DPAA2_PMD_ERR("Error in setting the rx flow: = %d", ret); - return ret; + goto err_free_dpcon; } dpaa2_q->nb_desc = nb_rx_desc; @@ -1026,7 +1081,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, if (ret) { DPAA2_PMD_ERR("Error in setting taildrop. err=(%d)", ret); - return ret; + goto err_free_dpcon; } } else { /* Disable tail Drop */ struct dpni_taildrop taildrop = {0}; @@ -1046,12 +1101,20 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, if (ret) { DPAA2_PMD_ERR("Error in setting taildrop. err=(%d)", ret); - return ret; + goto err_free_dpcon; } } dev->data->rx_queues[rx_queue_id] = dpaa2_q; return 0; + +err_free_dpcon: + /* free only the DPCON this call allocated; a pre-existing one is freed at dev_close */ + if (dpcon_allocated) { + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + } + return ret; } static int @@ -1210,6 +1273,45 @@ dpaa2_dev_tx_queue_setup(struct rte_eth_dev *dev, return 0; } +/* Release a queue's rx-interrupt state (teardown only): detach the FQ from its + * DPCON and free stashed FDs. Call before freeing the DPCON. + */ +static void +dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q) +{ + struct dpaa2_dev_priv *priv; + struct fsl_mc_io *dpni; + struct dpni_queue cfg; + int ret; + + if (!dpaa2_q || !dpaa2_q->napi_dpcon) + return; + + /* the portal (CDAN, DQRI) is torn down on the polling lcore via intr_disable */ + if (dpaa2_q->napi_armed) + DPAA2_PMD_WARN("rxq flow %u freed while armed; call " + "rte_eth_dev_rx_intr_disable() on the polling " + "lcore before stop/close", dpaa2_q->flow_id); + + /* detach the FQ from its DPCON before the channel returns to the pool */ + priv = dpaa2_q->eth_data->dev_private; + dpni = priv->eth_dev->process_private; + memset(&cfg, 0, sizeof(cfg)); + cfg.destination.type = DPNI_DEST_NONE; + ret = dpni_set_queue(dpni, CMD_PRI_LOW, priv->token, DPNI_QUEUE_RX, + dpaa2_q->tc_index, dpaa2_q->flow_id, + DPNI_QUEUE_OPT_DEST, &cfg); + if (ret) + DPAA2_PMD_ERR("napi: DEST_NONE rxq flow %u: %d", + dpaa2_q->flow_id, ret); + + /* DEST_NONE parks the FQ, so no further CDAN; CDAN-disable and DQRI-mask + * run on the polling lcore via rte_eth_dev_rx_intr_disable() + */ + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, + rte_memory_order_release); +} + static void dpaa2_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t rx_queue_id) { @@ -1239,6 +1341,12 @@ dpaa2_dev_rx_queue_release(struct rte_eth_dev *dev, uint16_t rx_queue_id) priv->cgid_in_use[dpaa2_q->cgid] = 0; dpaa2_q->cgid = DPAA2_INVALID_CGID; } + + if (dpaa2_q->napi_dpcon) { + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); + dpaa2_q->napi_dpcon = NULL; + } } static int @@ -1389,6 +1497,40 @@ dpaa2_dev_start(struct rte_eth_dev *dev) intr_handle = dpaa2_dev->intr_handle; PMD_INIT_FUNC_TRACE(); + + /* NAPI: bind each rx FQ to its DPCON channel while the dpni is still + * disabled (a DEST set_queue on an enabled dpni wedges the shared MC) + */ + if (dev->data->dev_conf.intr_conf.rxq) { + for (i = 0; i < data->nb_rx_queues; i++) { + dpaa2_q = data->rx_queues[i]; + /* the channel burst is set device-wide below and derefs + * napi_dpcon, so every rx queue must own a channel here + */ + if (!dpaa2_q->napi_dpcon) { + DPAA2_PMD_ERR("napi: rxq %d has no DPCON channel", i); + return -ENODEV; + } + memset(&cfg, 0, sizeof(cfg)); + cfg.destination.type = DPNI_DEST_DPCON; + cfg.destination.id = dpaa2_q->napi_dpcon->dpcon_id; + cfg.user_context = (size_t)dpaa2_q; + ret = dpni_set_queue(dpni, CMD_PRI_LOW, priv->token, + DPNI_QUEUE_RX, dpaa2_q->tc_index, + dpaa2_q->flow_id, + DPNI_QUEUE_OPT_DEST | DPNI_QUEUE_OPT_USER_CTX, + &cfg); + if (ret) { + DPAA2_PMD_ERR("napi: DPCON bind rxq %d: %d", i, ret); + return ret; + } + } + if (priv->flags & DPAA2_NO_PREFETCH_RX) + dev->rx_pkt_burst = dpaa2_dev_rx_channel; + else + dev->rx_pkt_burst = dpaa2_dev_prefetch_rx_channel; + } + ret = dpni_enable(dpni, CMD_PRI_LOW, priv->token); if (ret) { DPAA2_PMD_ERR("Failure in enabling dpni %d device: err=%d", @@ -1569,6 +1711,12 @@ dpaa2_dev_close(struct rte_eth_dev *dev) /* Free private queues memory */ dpaa2_free_rx_tx_queues(dev); + /* release the rx-intr vector allocated at dev_configure (configure/close pair) */ + if (dev->intr_handle) { + rte_intr_vec_list_free(dev->intr_handle); + rte_intr_instance_free(dev->intr_handle); + dev->intr_handle = NULL; + } /* Close the device at underlying layer*/ ret = dpni_close(dpni, CMD_PRI_LOW, priv->token); if (ret) { @@ -2897,6 +3045,219 @@ rte_pmd_dpaa2_thread_init(void) } } +/* rx-queue interrupts: static queue-to-lcore affinity; the portal DQRI/eventfd + * is refcounted by armed queues (per-lcore portal isolation, no atomics needed) + */ +/* Drain the portal DQRR (consume CDANs) then clear SWPn_ISR; drain first or a + * non-empty ring re-asserts the DQRI + */ +static void +dpaa2_napi_drain_portal(struct dpaa2_dpio_dev *dpio) +{ + const struct qbman_result *dq; + + while ((dq = qbman_swp_dqrr_next(dpio->sw_portal))) + qbman_swp_dqrr_consume(dpio->sw_portal, dq); + qbman_swp_interrupt_clear_status(dpio->sw_portal, 0xffffffff); +} + +/* Quiesce the in-flight prefetch VDQ before a sleep (a stranded pull yields no + * CDAN). Returns -EAGAIN if the completed pull captured a frame: keep polling. + */ +static int +dpaa2_napi_quiesce_vdq(struct dpaa2_queue *dpaa2_q) +{ + struct queue_storage_info_t *qs = dpaa2_q->q_storage[rte_lcore_id()]; + struct qbman_result *dq; + + if (!qs || !qs->active_dqs) + return 0; + dq = qs->active_dqs; + while (!qbman_check_command_complete(dq)) + ; + /* a completed pull holding a frame must be kept: inspect without + * qbman_check_new_result(), which would zero the token the next burst needs + */ + if (!qbman_result_DQ_is_pull_complete(dq) || + (qbman_result_DQ_flags(dq) & QBMAN_DQ_STAT_VALIDFRAME)) { + return -EAGAIN; + } + /* empty pull-complete: consume the token, drop the storage for a fresh pull */ + while (!qbman_check_new_result(dq)) + ; + clear_swp_active_dqs(qs->active_dpio_id); + qs->active_dqs = NULL; + return 0; +} + +/* Route this DPCON's CDAN to the worker's DPIO (re-issued on re-home) */ +static int +dpaa2_napi_set_cdan_dest(struct dpaa2_queue *dpaa2_q, struct dpaa2_dpio_dev *dpio) +{ + struct dpcon_notification_cfg ncfg = { + .dpio_id = dpio->hw_id, + .priority = 0, + .user_ctx = (uint64_t)(uintptr_t)dpaa2_q, + }; + + return dpcon_set_notification(&dpaa2_q->napi_dpcon->dpcon, + CMD_PRI_LOW, dpaa2_q->napi_dpcon->token, &ncfg); +} + +/* (Re)subscribe the queue's DPCON channel to dpio: set CDAN context, route + * notifications, wire the eventfd into the rx-intr epoll. CDAN enabled by arm. + */ +static int +dpaa2_napi_subscribe(struct rte_eth_dev *dev, uint16_t queue_id, + struct dpaa2_dpio_dev *dpio, struct dpaa2_dpio_dev *old) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + uint16_t chid = dpaa2_q->napi_dpcon->qbman_ch_id; + int ret; + + if (old) { + /* re-home: CDAN already off old; just drop the sub */ + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, + rte_memory_order_release); + } + /* push_set ORs the channel-0 bit (idempotent); CDAN context = the queue */ + qbman_swp_push_set(dpio->sw_portal, 0, 1); + /* context only; the arm path is the sole place that enables the CDAN */ + ret = qbman_swp_CDAN_set_context(dpio->sw_portal, chid, + (uint64_t)(uintptr_t)dpaa2_q); + if (ret) { + DPAA2_PMD_ERR("napi: CDAN set_context rxq %d: %d", queue_id, ret); + return ret; + } + ret = dpaa2_napi_set_cdan_dest(dpaa2_q, dpio); + if (ret) { + DPAA2_PMD_ERR("napi: dpcon_set_notification rxq %d: %d", + queue_id, ret); + return ret; /* CDAN not enabled here -> nothing to unwind */ + } + DPAA2_PMD_DEBUG("napi-cdan rxq=%u dpio=%u chid=%u", + queue_id, dpio->index, chid); + /* point the queue's eventfd at the portal DQRI fd for the generic epoll */ + if (rte_intr_vec_list_index_set(dev->intr_handle, queue_id, + queue_id + RTE_INTR_VEC_RXTX_OFFSET) || + rte_intr_efds_index_set(dev->intr_handle, queue_id, + rte_intr_fd_get(dpio->intr_handle))) { + DPAA2_PMD_ERR("napi: efd wiring rxq %d", queue_id); + return -EIO; + } + rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, dpio, + rte_memory_order_release); + return 0; +} + +static int +dpaa2_dev_rx_queue_intr_enable(struct rte_eth_dev *dev, uint16_t queue_id) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + struct dpaa2_dpio_dev *dpio, *old; + int ret; + + /* the generic API has no dev_started guard (unlike the burst dummy ops), + * so it can reach us after a stop; do not arm a stopped port, let the + * caller fall back to polling. + */ + if (!dev->data->dev_started) + return -EIO; + if (!dpaa2_q->napi_dpcon) + return -ENOTSUP; /* no channel -> caller keeps polling */ + + if (dpaa2_affine_qbman_ethrx_swp()) + return -EIO; + dpio = DPAA2_PER_LCORE_ETHRX_DPIO; + + old = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, rte_memory_order_acquire); + if (old && old != dpio && dpaa2_q->napi_armed) { + DPAA2_PMD_ERR("rxq %d still armed on another portal; disable it first", + queue_id); + return -EBUSY; + } + + ret = dpaa2_napi_quiesce_vdq(dpaa2_q); + if (ret) + return ret; + + /* immediate DQRI (threshold 1, holdoff 0); build_epoll=false so the generic + * rx-intr API waits on the application epoll + */ + ret = dpaa2_dpio_intr_init(dpio, 1, 0, false); + if (ret) + return ret; + + if (old != dpio) { + ret = dpaa2_napi_subscribe(dev, queue_id, dpio, old); + if (ret) + return ret; + } + + /* first arm on this portal: mask + drain stale CDANs so the unmask below opens it */ + if (!dpaa2_q->napi_armed && dpio->ethrx_intr_refcnt == 0) { + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 1); + dpaa2_napi_drain_portal(dpio); + } + + /* CDAN is one-shot, re-armed every sleep; publish state only on success */ + ret = qbman_swp_CDAN_enable(dpio->sw_portal, dpaa2_q->napi_dpcon->qbman_ch_id); + if (ret) { + DPAA2_PMD_DEBUG("napi: CDAN arm rxq %d: %d", queue_id, ret); + /* initial arm: nothing published; re-arm: state kept. Don't sleep. */ + return -EAGAIN; + } + + if (!dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 1; + /* unmask the portal DQRI once, on the 0 -> 1 armed-queue edge */ + if (dpio->ethrx_intr_refcnt++ == 0) + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 0); + } + + return 0; +} + +/* Disarm rx-queue interrupts; the portal DQRI is masked only when the last of + * its queues disarms. Act on the portal the queue is subscribed to. + */ +static int +dpaa2_dev_rx_queue_intr_disable(struct rte_eth_dev *dev, uint16_t queue_id) +{ + struct dpaa2_dev_priv *priv = dev->data->dev_private; + struct dpaa2_queue *dpaa2_q = priv->rx_vq[queue_id]; + struct dpaa2_dpio_dev *dpio; + uint64_t ev; + ssize_t nb; + + dpio = rte_atomic_load_explicit(&dpaa2_q->napi_sub_dpio, rte_memory_order_acquire); + if (dpio && dpaa2_q->napi_armed) { + dpaa2_q->napi_armed = 0; + /* silence this queue's CDAN on the portal the worker owns (the only + * safe place to drive the single-owner portal); siblings keep theirs + */ + qbman_swp_CDAN_disable(dpio->sw_portal, + dpaa2_q->napi_dpcon->qbman_ch_id); + if (dpio->ethrx_intr_refcnt > 0 && + --dpio->ethrx_intr_refcnt == 0) { + /* last queue on the portal: drain + clear for a clean next arm */ + dpaa2_napi_drain_portal(dpio); + qbman_swp_interrupt_set_inhibit(dpio->sw_portal, 1); + /* drain the VFIO eventfd (EPOLLET, left undrained by EAL) so a + * stale count cannot suppress the next wake edge + */ + nb = read(rte_intr_fd_get(dpio->intr_handle), &ev, sizeof(ev)); + if (nb != (ssize_t)sizeof(ev) && errno != EAGAIN) + DPAA2_PMD_DEBUG("napi: eventfd drain rxq %d (%zd)", + queue_id, nb); + } + } + + return 0; +} + static struct eth_dev_ops dpaa2_ethdev_ops = { .dev_configure = dpaa2_eth_dev_configure, .dev_start = dpaa2_dev_start, @@ -2925,6 +3286,9 @@ static struct eth_dev_ops dpaa2_ethdev_ops = { .vlan_tpid_set = dpaa2_vlan_tpid_set, .rx_queue_setup = dpaa2_dev_rx_queue_setup, .rx_queue_release = dpaa2_dev_rx_queue_release, + /* arm/disarm drive the per-lcore portal; call intr_disable() before stop/close */ + .rx_queue_intr_enable = dpaa2_dev_rx_queue_intr_enable, + .rx_queue_intr_disable = dpaa2_dev_rx_queue_intr_disable, .tx_queue_setup = dpaa2_dev_tx_queue_setup, .rx_burst_mode_get = dpaa2_dev_rx_burst_mode_get, .tx_burst_mode_get = dpaa2_dev_tx_burst_mode_get, diff --git a/drivers/net/dpaa2/dpaa2_ethdev.h b/drivers/net/dpaa2/dpaa2_ethdev.h index 3f224c654e..a497999624 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.h +++ b/drivers/net/dpaa2/dpaa2_ethdev.h @@ -500,6 +500,10 @@ uint16_t dpaa2_dev_loopback_rx(void *queue, struct rte_mbuf **bufs, uint16_t dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts); +uint16_t dpaa2_dev_prefetch_rx_channel(void *queue, struct rte_mbuf **bufs, + uint16_t nb_pkts); +uint16_t dpaa2_dev_rx_channel(void *queue, struct rte_mbuf **bufs, + uint16_t nb_pkts); void dpaa2_dev_process_parallel_event(struct qbman_swp *swp, const struct qbman_fd *fd, const struct qbman_result *dq, diff --git a/drivers/net/dpaa2/dpaa2_rxtx.c b/drivers/net/dpaa2/dpaa2_rxtx.c index 884cea43c9..633cd51424 100644 --- a/drivers/net/dpaa2/dpaa2_rxtx.c +++ b/drivers/net/dpaa2/dpaa2_rxtx.c @@ -764,13 +764,13 @@ dump_err_pkts(struct dpaa2_queue *dpaa2_q) * It will return the packets as requested in previous call without honoring * the current nb_pkts or bufs space. */ -uint16_t -dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +static __rte_always_inline uint16_t +dpaa2_dev_prefetch_rx_common(void *queue, struct rte_mbuf **bufs, + uint16_t nb_pkts, bool by_channel) { /* Function receive frames for a given device and VQ*/ struct dpaa2_queue *dpaa2_q = queue; struct qbman_result *dq_storage, *dq_storage1 = NULL; - uint32_t fqid = dpaa2_q->fqid; int ret, num_rx = 0, pull_size; uint8_t pending, status; struct qbman_swp *swp; @@ -778,12 +778,16 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) struct qbman_pull_desc pulldesc; struct queue_storage_info_t *q_storage; struct rte_eth_dev_data *eth_data = dpaa2_q->eth_data; - struct dpaa2_dev_priv *priv = eth_data->dev_private; q_storage = dpaa2_q->q_storage[rte_lcore_id()]; - if (unlikely(priv->flags & DPAAX_RX_ERROR_QUEUE_FLAG)) - dump_err_pkts(priv->rx_err_vq); + /* error-queue drain uses the regular portal; skip on the channel/intr path */ + if (!by_channel) { + struct dpaa2_dev_priv *priv = eth_data->dev_private; + + if (unlikely(priv->flags & DPAAX_RX_ERROR_QUEUE_FLAG)) + dump_err_pkts(priv->rx_err_vq); + } if (unlikely(!DPAA2_PER_LCORE_ETHRX_DPIO)) { ret = dpaa2_affine_qbman_ethrx_swp(); @@ -793,7 +797,7 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) } } - if (unlikely(!rte_dpaa2_bpid_info && + if (!by_channel && unlikely(!rte_dpaa2_bpid_info && rte_eal_process_type() == RTE_PROC_SECONDARY)) rte_dpaa2_bpid_info = dpaa2_q->bp_array; @@ -806,7 +810,12 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) qbman_pull_desc_clear(&pulldesc); qbman_pull_desc_set_numframes(&pulldesc, q_storage->last_num_pkts); - qbman_pull_desc_set_fq(&pulldesc, fqid); + if (by_channel) + qbman_pull_desc_set_channel(&pulldesc, + dpaa2_q->napi_dpcon->qbman_ch_id, + qbman_pull_type_active); + else + qbman_pull_desc_set_fq(&pulldesc, dpaa2_q->fqid); qbman_pull_desc_set_storage(&pulldesc, dq_storage, (uint64_t)(DPAA2_VADDR_TO_IOVA(dq_storage)), 1); if (check_swp_active_dqs(DPAA2_PER_LCORE_ETHRX_DPIO->index)) { @@ -842,7 +851,12 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) dq_storage1 = q_storage->dq_storage[q_storage->toggle]; qbman_pull_desc_clear(&pulldesc); qbman_pull_desc_set_numframes(&pulldesc, pull_size); - qbman_pull_desc_set_fq(&pulldesc, fqid); + if (by_channel) + qbman_pull_desc_set_channel(&pulldesc, + dpaa2_q->napi_dpcon->qbman_ch_id, + qbman_pull_type_active); + else + qbman_pull_desc_set_fq(&pulldesc, dpaa2_q->fqid); qbman_pull_desc_set_storage(&pulldesc, dq_storage1, (uint64_t)(DPAA2_VADDR_TO_IOVA(dq_storage1)), 1); @@ -885,6 +899,8 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) bufs[num_rx] = eth_fd_to_mbuf(fd, eth_data->port_id); #if defined(RTE_LIBRTE_IEEE1588) if (bufs[num_rx]->ol_flags & RTE_MBUF_F_RX_IEEE1588_TMST) { + struct dpaa2_dev_priv *priv = eth_data->dev_private; + priv->rx_timestamp = *dpaa2_timestamp_dynfield(bufs[num_rx]); } @@ -918,6 +934,22 @@ dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) return num_rx; } +uint16_t __rte_hot +dpaa2_dev_prefetch_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +{ + return dpaa2_dev_prefetch_rx_common(queue, bufs, nb_pkts, false); +} + +/* CDAN burst: drain the FQ's DPCON channel by a volatile channel pull (a VDQ on a + * scheduled FQ never completes); same prefetch pipeline, quiesced before a sleep. + */ +uint16_t __rte_hot +dpaa2_dev_prefetch_rx_channel(void *queue, struct rte_mbuf **bufs, + uint16_t nb_pkts) +{ + return dpaa2_dev_prefetch_rx_common(queue, bufs, nb_pkts, true); +} + void __rte_hot dpaa2_dev_process_parallel_event(struct qbman_swp *swp, const struct qbman_fd *fd, @@ -997,8 +1029,9 @@ dpaa2_dev_process_ordered_event(struct qbman_swp *swp, qbman_swp_dqrr_consume(swp, dq); } -uint16_t -dpaa2_dev_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +static __rte_always_inline uint16_t +dpaa2_dev_rx_common(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts, + bool by_channel) { /* Function receive frames for a given device and VQ */ struct dpaa2_queue *dpaa2_q = queue; @@ -1012,24 +1045,39 @@ dpaa2_dev_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) struct rte_eth_dev_data *eth_data = dpaa2_q->eth_data; struct dpaa2_dev_priv *priv = eth_data->dev_private; - if (unlikely(priv->flags & DPAAX_RX_ERROR_QUEUE_FLAG)) + if (!by_channel && unlikely(priv->flags & DPAAX_RX_ERROR_QUEUE_FLAG)) dump_err_pkts(priv->rx_err_vq); - if (unlikely(!DPAA2_PER_LCORE_DPIO)) { - ret = dpaa2_affine_qbman_swp(); - if (ret) { - DPAA2_PMD_ERR( - "Failed to allocate IO portal, tid: %d", - rte_gettid()); - return 0; + if (by_channel) { + if (unlikely(!DPAA2_PER_LCORE_ETHRX_DPIO)) { + ret = dpaa2_affine_qbman_ethrx_swp(); + if (ret) { + DPAA2_PMD_ERR("Failure in affining portal"); + return 0; + } } + swp = DPAA2_PER_LCORE_ETHRX_PORTAL; + } else { + if (unlikely(!DPAA2_PER_LCORE_DPIO)) { + ret = dpaa2_affine_qbman_swp(); + if (ret) { + DPAA2_PMD_ERR("Failed to allocate IO portal, tid: %d", + rte_gettid()); + return 0; + } + } + swp = DPAA2_PER_LCORE_PORTAL; } - swp = DPAA2_PER_LCORE_PORTAL; do { dq_storage = dpaa2_q->q_storage[0]->dq_storage[0]; qbman_pull_desc_clear(&pulldesc); - qbman_pull_desc_set_fq(&pulldesc, fqid); + if (by_channel) + qbman_pull_desc_set_channel(&pulldesc, + dpaa2_q->napi_dpcon->qbman_ch_id, + qbman_pull_type_active); + else + qbman_pull_desc_set_fq(&pulldesc, fqid); qbman_pull_desc_set_storage(&pulldesc, dq_storage, (size_t)(DPAA2_VADDR_TO_IOVA(dq_storage)), 1); @@ -1108,6 +1156,19 @@ dpaa2_dev_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) return num_rx; } +uint16_t +dpaa2_dev_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +{ + return dpaa2_dev_rx_common(queue, bufs, nb_pkts, false); +} + +/* rx-interrupt path: synchronous channel volatile dequeue, nothing left in flight */ +uint16_t __rte_hot +dpaa2_dev_rx_channel(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) +{ + return dpaa2_dev_rx_common(queue, bufs, nb_pkts, true); +} + uint16_t dpaa2_dev_tx_conf(void *queue) { /* Function receive frames for a given device and VQ */ @@ -1500,7 +1561,6 @@ dpaa2_dev_tx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts) } skip_tx: dpaa2_q->tx_pkts += num_tx; - for (loop = 0; loop < free_count; loop++) { if (buf_to_free[loop].pkt_id < num_tx) rte_pktmbuf_free_seg(buf_to_free[loop].seg); -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts 2026-06-30 14:43 ` [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts Maxime Leroy @ 2026-07-03 17:21 ` Hemant Agrawal 2026-07-06 10:36 ` Maxime Leroy 0 siblings, 1 reply; 51+ messages in thread From: Hemant Agrawal @ 2026-07-03 17:21 UTC (permalink / raw) To: Maxime Leroy, dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand [-- Attachment #1: Type: text/plain, Size: 4179 bytes --] On 30-06-2026 20:13, Maxime Leroy wrote: > diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c > index a68404ee5e..36f8669644 100644 > --- a/drivers/net/dpaa2/dpaa2_ethdev.c > +++ b/drivers/net/dpaa2/dpaa2_ethdev.c > @@ -5,6 +5,8 @@ > > #include <time.h> > #include <net/if.h> > +#include <unistd.h> > +#include <errno.h> > > #include <eal_export.h> > #include <rte_mbuf.h> > @@ -25,6 +27,7 @@ > #include <dpaa2_hw_mempool.h> > #include <dpaa2_hw_dpio.h> > #include <mc/fsl_dpmng.h> > +#include <mc/fsl_dpcon.h> > #include "dpaa2_ethdev.h" > #include "dpaa2_sparser.h" > #include <fsl_qbman_debug.h> > @@ -658,6 +661,8 @@ dpaa2_clear_queue_active_dps(struct dpaa2_queue *q, int num_lcores) > } > } > > +static void dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q); > + > static void > dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) > { > @@ -675,6 +680,12 @@ dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) > /* cleaning up queue storage */ > for (i = 0; i < priv->nb_rx_queues; i++) { > dpaa2_q = priv->rx_vq[i]; > + if (dpaa2_q->napi_dpcon) { /* release the rx-intr channel */ > + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); > + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); > + dpaa2_q->napi_dpcon = NULL; > + dpaa2_q->napi_sub_dpio = NULL; > + } > dpaa2_clear_queue_active_dps(dpaa2_q, > RTE_MAX_LCORE); > dpaa2_queue_storage_free(dpaa2_q, > @@ -880,6 +891,26 @@ dpaa2_eth_dev_configure(struct rte_eth_dev *dev) > } > } > > + if (dev->data->dev_conf.intr_conf.rxq) { > + if (!dev->intr_handle) > + dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); > + if (!dev->intr_handle || > + rte_intr_vec_list_alloc(dev->intr_handle, "rxq_intr", > + dev->data->nb_rx_queues) || > + rte_intr_nb_efd_set(dev->intr_handle, dev->data->nb_rx_queues) || > + rte_intr_type_set(dev->intr_handle, RTE_INTR_HANDLE_EXT)) { > + DPAA2_PMD_ERR("Failed to set up rx-queue interrupts"); > + /* capture the error before cleanup may clobber rte_errno */ > + ret = rte_errno ? -rte_errno : -EIO; > + if (dev->intr_handle) { > + rte_intr_vec_list_free(dev->intr_handle); > + rte_intr_instance_free(dev->intr_handle); > + dev->intr_handle = NULL; > + } > + return ret; > + } > + } > + If |dev_configure()| is called a second time (e.g. after changing |nb_rx_queues|), |dev->intr_handle| is already non-NULL. The code skips |rte_intr_instance_alloc()| but calls |rte_intr_vec_list_alloc()| on the existing handle without first freeing the old vector list. This leaks the previously allocated vector list and may also cause a size mismatch if |nb_rx_queues| changed between calls. Should not we free the old vector list before reallocating: if (dev->intr_handle) rte_intr_vec_list_free(dev->intr_handle); else dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); /* then always call rte_intr_vec_list_alloc() */ > dpaa2_tm_init(dev); > > return 0; > @@ -898,6 +929,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, > { > struct dpaa2_dev_priv *priv = dev->data->dev_private; > struct fsl_mc_io *dpni = dev->process_private; > + bool dpcon_allocated = false; > struct dpaa2_queue *dpaa2_q; > struct dpni_queue cfg; > uint8_t options = 0; > @@ -938,6 +970,25 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, > dpaa2_q->bp_array = rte_dpaa2_bpid_info; > dpaa2_q->offloads = rx_conf->offloads; > > + /* NAPI: grab a DPCON channel for dev_start to bind this FQ statically */ > + dpaa2_q->napi_sub_dpio = NULL; > |dpaa2_q->napi_sub_dpio| is declared as |RTE_ATOMIC(struct > dpaa2_dpio_dev *)| in |dpaa2_hw_pvt.h|, but here it is assigned with a > plain |= NULL| instead of |rte_atomic_store_explicit(...)|. This is > inconsistent with the atomic stores used elsewhere (e.g. in > |dpaa2_dev_rx_queue_intr_unbind|) and may cause memory-ordering issues > on weakly-ordered architectures (ARM/aarch64). > > Please use: > > rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, > rte_memory_order_release); > [-- Attachment #2: Type: text/html, Size: 8006 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts 2026-07-03 17:21 ` Hemant Agrawal @ 2026-07-06 10:36 ` Maxime Leroy 0 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-07-06 10:36 UTC (permalink / raw) To: Hemant Agrawal; +Cc: dev, hemant.agrawal, sachin.saxena, david.marchand Hi Hemant, On Fri, Jul 3, 2026 at 7:21 PM Hemant Agrawal <hemant.agrawal@oss.nxp.com> wrote: > > > On 30-06-2026 20:13, Maxime Leroy wrote: > > diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c > index a68404ee5e..36f8669644 100644 > --- a/drivers/net/dpaa2/dpaa2_ethdev.c > +++ b/drivers/net/dpaa2/dpaa2_ethdev.c > @@ -5,6 +5,8 @@ > > #include <time.h> > #include <net/if.h> > +#include <unistd.h> > +#include <errno.h> > > #include <eal_export.h> > #include <rte_mbuf.h> > @@ -25,6 +27,7 @@ > #include <dpaa2_hw_mempool.h> > #include <dpaa2_hw_dpio.h> > #include <mc/fsl_dpmng.h> > +#include <mc/fsl_dpcon.h> > #include "dpaa2_ethdev.h" > #include "dpaa2_sparser.h" > #include <fsl_qbman_debug.h> > @@ -658,6 +661,8 @@ dpaa2_clear_queue_active_dps(struct dpaa2_queue *q, int num_lcores) > } > } > > +static void dpaa2_dev_rx_queue_intr_unbind(struct dpaa2_queue *dpaa2_q); > + > static void > dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) > { > @@ -675,6 +680,12 @@ dpaa2_free_rx_tx_queues(struct rte_eth_dev *dev) > /* cleaning up queue storage */ > for (i = 0; i < priv->nb_rx_queues; i++) { > dpaa2_q = priv->rx_vq[i]; > + if (dpaa2_q->napi_dpcon) { /* release the rx-intr channel */ > + dpaa2_dev_rx_queue_intr_unbind(dpaa2_q); > + rte_dpaa2_free_dpcon_dev(dpaa2_q->napi_dpcon); > + dpaa2_q->napi_dpcon = NULL; > + dpaa2_q->napi_sub_dpio = NULL; > + } > dpaa2_clear_queue_active_dps(dpaa2_q, > RTE_MAX_LCORE); > dpaa2_queue_storage_free(dpaa2_q, > @@ -880,6 +891,26 @@ dpaa2_eth_dev_configure(struct rte_eth_dev *dev) > } > } > > + if (dev->data->dev_conf.intr_conf.rxq) { > + if (!dev->intr_handle) > + dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); > + if (!dev->intr_handle || > + rte_intr_vec_list_alloc(dev->intr_handle, "rxq_intr", > + dev->data->nb_rx_queues) || > + rte_intr_nb_efd_set(dev->intr_handle, dev->data->nb_rx_queues) || > + rte_intr_type_set(dev->intr_handle, RTE_INTR_HANDLE_EXT)) { > + DPAA2_PMD_ERR("Failed to set up rx-queue interrupts"); > + /* capture the error before cleanup may clobber rte_errno */ > + ret = rte_errno ? -rte_errno : -EIO; > + if (dev->intr_handle) { > + rte_intr_vec_list_free(dev->intr_handle); > + rte_intr_instance_free(dev->intr_handle); > + dev->intr_handle = NULL; > + } > + return ret; > + } > + } > + > > If dev_configure() is called a second time (e.g. after changing nb_rx_queues), dev->intr_handle is already non-NULL. The code skips rte_intr_instance_alloc() but calls rte_intr_vec_list_alloc() on the existing handle without first freeing the old vector list. This leaks the previously allocated vector list and may also cause a size mismatch if nb_rx_queues changed between calls. > > Should not we free the old vector list before reallocating: > > if (dev->intr_handle) > > rte_intr_vec_list_free(dev->intr_handle); > > else > > dev->intr_handle = rte_intr_instance_alloc(RTE_INTR_INSTANCE_F_PRIVATE); > > /* then always call rte_intr_vec_list_alloc() */ > real bug, but not a leak. rte_intr_vec_list_alloc() returns early when intr_vec != NULL, so a second configure() allocates nothing. The opposite happens: the vector list keeps its old size, and if nb_rx_queues grew (stop/reconfigure/start, no close, which the API allows), arming the new queues fails with -ERANGE. Freeing only the vector list isn't enough either, it leaves nb_efd/the eventfd array stale. The fix is to rebuild the whole handle on re-entry: if (dev->intr_handle) { rte_intr_vec_list_free(dev->intr_handle); rte_intr_instance_free(dev->intr_handle); dev->intr_handle = NULL; } dev->intr_handle = rte_intr_instance_alloc(...); I'll fix this in v4. > > dpaa2_tm_init(dev); > > return 0; > @@ -898,6 +929,7 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, > { > struct dpaa2_dev_priv *priv = dev->data->dev_private; > struct fsl_mc_io *dpni = dev->process_private; > + bool dpcon_allocated = false; > struct dpaa2_queue *dpaa2_q; > struct dpni_queue cfg; > uint8_t options = 0; > @@ -938,6 +970,25 @@ dpaa2_dev_rx_queue_setup(struct rte_eth_dev *dev, > dpaa2_q->bp_array = rte_dpaa2_bpid_info; > dpaa2_q->offloads = rx_conf->offloads; > > + /* NAPI: grab a DPCON channel for dev_start to bind this FQ statically */ > + dpaa2_q->napi_sub_dpio = NULL; > > > dpaa2_q->napi_sub_dpio is declared as RTE_ATOMIC(struct dpaa2_dpio_dev *) in dpaa2_hw_pvt.h, but here it is assigned with a plain = NULL instead of rte_atomic_store_explicit(...). This is inconsistent with the atomic stores used elsewhere (e.g. in dpaa2_dev_rx_queue_intr_unbind) and may cause memory-ordering issues on weakly-ordered architectures (ARM/aarch64). > > Please use: > > rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, NULL, rte_memory_order_release); I'll switch to rte_atomic_store_explicit() for consistency and MSVC (where RTE_ATOMIC(x) is a plain type). But it's not an ordering bug: the field is _Atomic (plain store is already seq_cst), and this is rx_queue_setup, before dev_start, with no reader yet. So this is cleanup, not a correctness fix, I'll change it in v4 Thanks, Maxime ^ permalink raw reply [flat|nested] 51+ messages in thread
* [PATCH v3 6/6] net/dpaa2: pin Rx queue interrupt to the polling core 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy ` (4 preceding siblings ...) 2026-06-30 14:43 ` [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts Maxime Leroy @ 2026-06-30 14:43 ` Maxime Leroy 2026-07-03 16:03 ` [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts Stephen Hemminger 6 siblings, 0 replies; 51+ messages in thread From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw) To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy A CDAN wakes the worker through the portal's VFIO eventfd, which is signalled regardless of the core that took the MSI, so the wake is correct either way. By default the MSI affinity is spread across the housekeeping cores, so the wake crosses cores and adds a little latency. Pin each DPIO's MSI to the lcore that arms it (as the event driver does) so the wake lands on the worker's own core. This is a latency optimisation, not a correctness fix. Split dpaa2_affine_dpio_intr_to_respective_core out of the RTE_EVENT_DPAA2 guard, expose dpaa2_dpio_affine_intr_to_core, and call it from the rx-queue interrupt arm path. The pin is best-effort: it writes /proc/irq/<n>/smp_affinity (needs privilege) and irqbalance may move it back. Signed-off-by: Maxime Leroy <maxime@leroys.fr> --- drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 45 +++++++++++++----------- drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 4 +++ drivers/net/dpaa2/dpaa2_ethdev.c | 2 ++ 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c index 354d81b4d1..057f2bb70d 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.c @@ -149,7 +149,6 @@ dpaa2_core_cluster_sdest(int cpu_id) return dpaa2_core_cluster_base + x; } -#ifdef RTE_EVENT_DPAA2 static void dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) { @@ -204,7 +203,19 @@ dpaa2_affine_dpio_intr_to_respective_core(int32_t dpio_id, int cpu_id) fclose(file); } -#endif /* RTE_EVENT_DPAA2 */ + +/* Pin the dpio's MSI IRQ to the calling lcore's core (best-effort), so a + * worker sleeping on this portal's eventfd is woken on its own core. + */ +RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_affine_intr_to_core) +void +dpaa2_dpio_affine_intr_to_core(int32_t dpio_id) +{ + int cpu_id = dpaa2_get_core_id(); + + if (cpu_id >= 0) + dpaa2_affine_dpio_intr_to_respective_core(dpio_id, cpu_id); +} /* arm the portal DQRI (threshold/timeout); idempotent, first caller per portal wins */ RTE_EXPORT_INTERNAL_SYMBOL(dpaa2_dpio_intr_init) @@ -299,31 +310,25 @@ dpaa2_configure_stashing(struct dpaa2_dpio_dev *dpio_dev, int cpu_id, bool ethrx } #ifdef RTE_EVENT_DPAA2 - { - /* ethrx portal: immediate DQRI (1, 0); event portal: coalesced (3, 0xFF). - * Each mode is tunable through its own env vars. - */ - const char *thr_env = "DPAA2_PORTAL_INTR_THRESHOLD"; - const char *to_env = "DPAA2_PORTAL_INTR_TIMEOUT"; + /* only the event PMD's portal is set up here; the net ethrx portal is + * configured by the net PMD in rx_queue_intr_enable + */ + if (!ethrx) { int threshold = 3, timeout = 0xFF; - if (ethrx) { - thr_env = "DPAA2_PORTAL_ETHRX_INTR_THRESHOLD"; - to_env = "DPAA2_PORTAL_ETHRX_INTR_TIMEOUT"; - threshold = 1; - timeout = 0; - } - if (getenv(thr_env)) - threshold = atoi(getenv(thr_env)); - if (getenv(to_env)) - sscanf(getenv(to_env), "%x", &timeout); + if (getenv("DPAA2_PORTAL_INTR_THRESHOLD")) + threshold = atoi(getenv("DPAA2_PORTAL_INTR_THRESHOLD")); + if (getenv("DPAA2_PORTAL_INTR_TIMEOUT")) + sscanf(getenv("DPAA2_PORTAL_INTR_TIMEOUT"), "%x", &timeout); - if (dpaa2_dpio_intr_init(dpio_dev, threshold, timeout, !ethrx)) { + if (dpaa2_dpio_intr_init(dpio_dev, threshold, timeout, true)) { DPAA2_BUS_ERR("Interrupt registration failed for dpio"); return -1; } + dpaa2_affine_dpio_intr_to_respective_core(dpio_dev->hw_id, cpu_id); } - dpaa2_affine_dpio_intr_to_respective_core(dpio_dev->hw_id, cpu_id); +#else + RTE_SET_USED(ethrx); #endif return 0; diff --git a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h index 1f6e521341..7e2271cc35 100644 --- a/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h +++ b/drivers/bus/fslmc/portal/dpaa2_hw_dpio.h @@ -58,6 +58,10 @@ int dpaa2_dpio_intr_init(struct dpaa2_dpio_dev *dpio_dev, int threshold, __rte_internal void dpaa2_dpio_intr_deinit(struct dpaa2_dpio_dev *dpio_dev); +/* pin a DPIO's MSI IRQ to the calling lcore's core (rx-queue interrupt mode) */ +__rte_internal +void dpaa2_dpio_affine_intr_to_core(int32_t dpio_id); + /* allocate memory for FQ - dq storage */ __rte_internal int diff --git a/drivers/net/dpaa2/dpaa2_ethdev.c b/drivers/net/dpaa2/dpaa2_ethdev.c index 36f8669644..2a721eb005 100644 --- a/drivers/net/dpaa2/dpaa2_ethdev.c +++ b/drivers/net/dpaa2/dpaa2_ethdev.c @@ -3148,6 +3148,8 @@ dpaa2_napi_subscribe(struct rte_eth_dev *dev, uint16_t queue_id, } rte_atomic_store_explicit(&dpaa2_q->napi_sub_dpio, dpio, rte_memory_order_release); + /* pin this portal's MSI to the worker's core so a CDAN wake lands locally */ + dpaa2_dpio_affine_intr_to_core(dpio->hw_id); return 0; } -- 2.43.0 ^ permalink raw reply related [flat|nested] 51+ messages in thread
* Re: [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy ` (5 preceding siblings ...) 2026-06-30 14:43 ` [PATCH v3 6/6] net/dpaa2: pin Rx queue interrupt to the polling core Maxime Leroy @ 2026-07-03 16:03 ` Stephen Hemminger 2026-07-03 18:56 ` Maxime Leroy 6 siblings, 1 reply; 51+ messages in thread From: Stephen Hemminger @ 2026-07-03 16:03 UTC (permalink / raw) To: Maxime Leroy; +Cc: dev, hemant.agrawal, sachin.saxena, david.marchand On Tue, 30 Jun 2026 16:43:23 +0200 Maxime Leroy <maxime@leroys.fr> wrote: > This series lets a dpaa2 worker sleep on a queue's data-availability > notification instead of busy-polling, exposed through the generic > rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, > arm the interrupt and sleep when the queue runs dry). > > Why it is not a trivial .rx_queue_intr_enable > ---------------------------------------------- > A worker wakes on its software portal's DQRI, which fires when the > portal's DQRR holds frames. The default dpaa2 Rx burst pulls frames > from the FQ with a volatile dequeue and cannot be interrupt-driven; to > wake on the DQRI the FQ must instead be pushed to the portal's DQRR. > > The natural dpni_set_queue with a notification destination would have to > target the worker's portal, but that portal is only known once a worker > affines, after dev_start, and that MC command holds the global MC lock > long enough to wedge the firmware while traffic runs. So the bind cannot > be done late, against the polling lcore. > > Design > ------ > Each Rx FQ is bound to its own DPCON channel, statically, at dev_start > while the dpni is still disabled (no knowledge of the polling lcore). A > worker later subscribes its own ethrx portal to the channel and arms the > DQRI in rx_queue_intr_enable, a one-shot per-portal op, never the wedging > set_queue. On a wakeup the worker drains each of its queues by a volatile > dequeue on the queue's own DPCON channel (one FQ per channel, so no > per-frame demux); it polls all its queues, the same scheduling contract > as plain DPDK polling. A queue can be re-homed to another lcore at > runtime with no set_queue and no port stop. > > This reuses the event PMD's pushed/DQRR model but with one DPCON per FQ > and static affinity (no QBMan scheduling), so the DPCON allocator is > moved from the event driver to the fslmc bus and shared. > > Patch 1 disables the DPCON channel before closing it, an event/dpaa2 fix > the shared allocator depends on. Patches 2 to 4 move the DPCON allocator > to the fslmc bus, make the portal DQRI epoll optional, and add the > dpcon_set_notification MC command. Patch 5 adds the interrupt support > proper; patch 6 pins each DPIO's MSI to the lcore that arms it, a latency > optimisation. > > Tested on LX2160A (lx2160acex7). > > v3: > - Reworked the Rx drain. Both versions bind one DPCON per FQ, but v2 > drained the shared portal DQRR and demuxed frames to their FQ by > fqd_ctx, stashing foreign frames in a per-queue FIFO. v3 drains each > queue with a volatile dequeue on its own channel (one FQ per channel), > which drops the demux and stash code. > - Dropped the rx_queue_count fix; it is applied to main. > - Dropped the software VLAN strip patch; an independent net/dpaa2 cleanup, > sent standalone and now applied to next-net. > - Dropped the Depends-on: the ethdev fast-path ops fix is now in main. > - Split the dpcon_set_notification MC command into its own patch. > - Added an event/dpaa2 fix to disable the DPCON channel before close, > needed once the allocator is shared. > - Dropped the DQRI holdoff-tuning patch; the immediate-DQRI holdoff is now > set inline in the arm path. > - Added a patch reusing the event driver's MSI-affinity helper (exposed > from its RTE_EVENT_DPAA2 guard) to pin the portal MSI to the lcore that > arms it, so a CDAN wake lands on the worker's own core. > > v2: > - Dropped the RSS RETA patch, an independent net/dpaa2 change the > interrupt path does not require; it will be sent as its own series. > - Dropped the ethdev fast-path ops fix; it is now a standalone series. > - Dropped the eal/interrupts -EEXIST fix, applied to main by David > Marchand. > - Declared qbman_swp_interrupt_set_inhibit and qbman_swp_dqrr_size > __rte_internal (David Marchand). > - Minor formatting cleanup in the Rx interrupt setup. > > Maxime Leroy (6): > event/dpaa2: disable channel before closing it > bus/fslmc: move DPCON management from event driver to bus > bus/fslmc/dpio: make the portal DQRI epoll optional > bus/fslmc/mc: implement dpcon_set_notification > net/dpaa2: support Rx queue interrupts > net/dpaa2: pin Rx queue interrupt to the polling core > > doc/guides/nics/dpaa2.rst | 21 + > doc/guides/nics/features/dpaa2.ini | 1 + > doc/guides/rel_notes/release_26_07.rst | 1 + > drivers/bus/fslmc/mc/dpcon.c | 31 ++ > drivers/bus/fslmc/mc/fsl_dpcon.h | 18 + > drivers/bus/fslmc/meson.build | 1 + > .../fslmc/portal}/dpaa2_hw_dpcon.c | 17 +- > drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 112 ++++-- > drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 + > drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 12 + > .../fslmc/qbman/include/fsl_qbman_portal.h | 5 + > drivers/bus/fslmc/qbman/qbman_portal.c | 5 + > drivers/event/dpaa2/dpaa2_eventdev.h | 3 - > drivers/event/dpaa2/meson.build | 1 - > drivers/net/dpaa2/dpaa2_ethdev.c | 372 +++++++++++++++++- > drivers/net/dpaa2/dpaa2_ethdev.h | 4 + > drivers/net/dpaa2/dpaa2_rxtx.c | 104 +++-- > 17 files changed, 649 insertions(+), 71 deletions(-) > rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (88%) > > > base-commit: 030328f5f920a87dabde54dacd4f5ac411ddcac9 Looks good, applied to net-next ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-07-03 16:03 ` [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts Stephen Hemminger @ 2026-07-03 18:56 ` Maxime Leroy 2026-07-03 20:26 ` Stephen Hemminger 0 siblings, 1 reply; 51+ messages in thread From: Maxime Leroy @ 2026-07-03 18:56 UTC (permalink / raw) To: Stephen Hemminger; +Cc: dev, Hemant Agrawal, Sachin Saxena, David Marchand [-- Attachment #1: Type: text/plain, Size: 6090 bytes --] Hi Stephen, Le ven. 3 juil. 2026, 18:03, Stephen Hemminger <stephen@networkplumber.org> a écrit : > On Tue, 30 Jun 2026 16:43:23 +0200 > Maxime Leroy <maxime@leroys.fr> wrote: > > > This series lets a dpaa2 worker sleep on a queue's data-availability > > notification instead of busy-polling, exposed through the generic > > rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, > > arm the interrupt and sleep when the queue runs dry). > > > > Why it is not a trivial .rx_queue_intr_enable > > ---------------------------------------------- > > A worker wakes on its software portal's DQRI, which fires when the > > portal's DQRR holds frames. The default dpaa2 Rx burst pulls frames > > from the FQ with a volatile dequeue and cannot be interrupt-driven; to > > wake on the DQRI the FQ must instead be pushed to the portal's DQRR. > > > > The natural dpni_set_queue with a notification destination would have to > > target the worker's portal, but that portal is only known once a worker > > affines, after dev_start, and that MC command holds the global MC lock > > long enough to wedge the firmware while traffic runs. So the bind cannot > > be done late, against the polling lcore. > > > > Design > > ------ > > Each Rx FQ is bound to its own DPCON channel, statically, at dev_start > > while the dpni is still disabled (no knowledge of the polling lcore). A > > worker later subscribes its own ethrx portal to the channel and arms the > > DQRI in rx_queue_intr_enable, a one-shot per-portal op, never the wedging > > set_queue. On a wakeup the worker drains each of its queues by a volatile > > dequeue on the queue's own DPCON channel (one FQ per channel, so no > > per-frame demux); it polls all its queues, the same scheduling contract > > as plain DPDK polling. A queue can be re-homed to another lcore at > > runtime with no set_queue and no port stop. > > > > This reuses the event PMD's pushed/DQRR model but with one DPCON per FQ > > and static affinity (no QBMan scheduling), so the DPCON allocator is > > moved from the event driver to the fslmc bus and shared. > > > > Patch 1 disables the DPCON channel before closing it, an event/dpaa2 fix > > the shared allocator depends on. Patches 2 to 4 move the DPCON allocator > > to the fslmc bus, make the portal DQRI epoll optional, and add the > > dpcon_set_notification MC command. Patch 5 adds the interrupt support > > proper; patch 6 pins each DPIO's MSI to the lcore that arms it, a latency > > optimisation. > > > > Tested on LX2160A (lx2160acex7). > > > > v3: > > - Reworked the Rx drain. Both versions bind one DPCON per FQ, but v2 > > drained the shared portal DQRR and demuxed frames to their FQ by > > fqd_ctx, stashing foreign frames in a per-queue FIFO. v3 drains each > > queue with a volatile dequeue on its own channel (one FQ per channel), > > which drops the demux and stash code. > > - Dropped the rx_queue_count fix; it is applied to main. > > - Dropped the software VLAN strip patch; an independent net/dpaa2 > cleanup, > > sent standalone and now applied to next-net. > > - Dropped the Depends-on: the ethdev fast-path ops fix is now in main. > > - Split the dpcon_set_notification MC command into its own patch. > > - Added an event/dpaa2 fix to disable the DPCON channel before close, > > needed once the allocator is shared. > > - Dropped the DQRI holdoff-tuning patch; the immediate-DQRI holdoff is > now > > set inline in the arm path. > > - Added a patch reusing the event driver's MSI-affinity helper (exposed > > from its RTE_EVENT_DPAA2 guard) to pin the portal MSI to the lcore that > > arms it, so a CDAN wake lands on the worker's own core. > > > > v2: > > - Dropped the RSS RETA patch, an independent net/dpaa2 change the > > interrupt path does not require; it will be sent as its own series. > > - Dropped the ethdev fast-path ops fix; it is now a standalone series. > > - Dropped the eal/interrupts -EEXIST fix, applied to main by David > > Marchand. > > - Declared qbman_swp_interrupt_set_inhibit and qbman_swp_dqrr_size > > __rte_internal (David Marchand). > > - Minor formatting cleanup in the Rx interrupt setup. > > > > Maxime Leroy (6): > > event/dpaa2: disable channel before closing it > > bus/fslmc: move DPCON management from event driver to bus > > bus/fslmc/dpio: make the portal DQRI epoll optional > > bus/fslmc/mc: implement dpcon_set_notification > > net/dpaa2: support Rx queue interrupts > > net/dpaa2: pin Rx queue interrupt to the polling core > > > > doc/guides/nics/dpaa2.rst | 21 + > > doc/guides/nics/features/dpaa2.ini | 1 + > > doc/guides/rel_notes/release_26_07.rst | 1 + > > drivers/bus/fslmc/mc/dpcon.c | 31 ++ > > drivers/bus/fslmc/mc/fsl_dpcon.h | 18 + > > drivers/bus/fslmc/meson.build | 1 + > > .../fslmc/portal}/dpaa2_hw_dpcon.c | 17 +- > > drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 112 ++++-- > > drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 + > > drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 12 + > > .../fslmc/qbman/include/fsl_qbman_portal.h | 5 + > > drivers/bus/fslmc/qbman/qbman_portal.c | 5 + > > drivers/event/dpaa2/dpaa2_eventdev.h | 3 - > > drivers/event/dpaa2/meson.build | 1 - > > drivers/net/dpaa2/dpaa2_ethdev.c | 372 +++++++++++++++++- > > drivers/net/dpaa2/dpaa2_ethdev.h | 4 + > > drivers/net/dpaa2/dpaa2_rxtx.c | 104 +++-- > > 17 files changed, 649 insertions(+), 71 deletions(-) > > rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (88%) > > > > > > base-commit: 030328f5f920a87dabde54dacd4f5ac411ddcac9 > > Looks good, applied to net-next Thanks for the merge. But this patchset have not been acked by Hemant. Right now, Hemant have asked some fixes on this serie. Hemant also said it needs some time to test it. [-- Attachment #2: Type: text/html, Size: 7539 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
* Re: [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts 2026-07-03 18:56 ` Maxime Leroy @ 2026-07-03 20:26 ` Stephen Hemminger 0 siblings, 0 replies; 51+ messages in thread From: Stephen Hemminger @ 2026-07-03 20:26 UTC (permalink / raw) To: Maxime Leroy; +Cc: dev, Hemant Agrawal, Sachin Saxena, David Marchand [-- Attachment #1: Type: text/plain, Size: 6335 bytes --] Right I pulled it back On Fri, Jul 3, 2026, 11:56 Maxime Leroy <maxime@leroys.fr> wrote: > Hi Stephen, > > Le ven. 3 juil. 2026, 18:03, Stephen Hemminger <stephen@networkplumber.org> > a écrit : > >> On Tue, 30 Jun 2026 16:43:23 +0200 >> Maxime Leroy <maxime@leroys.fr> wrote: >> >> > This series lets a dpaa2 worker sleep on a queue's data-availability >> > notification instead of busy-polling, exposed through the generic >> > rte_eth_dev_rx_intr_* API (NAPI-style: poll while frames keep coming, >> > arm the interrupt and sleep when the queue runs dry). >> > >> > Why it is not a trivial .rx_queue_intr_enable >> > ---------------------------------------------- >> > A worker wakes on its software portal's DQRI, which fires when the >> > portal's DQRR holds frames. The default dpaa2 Rx burst pulls frames >> > from the FQ with a volatile dequeue and cannot be interrupt-driven; to >> > wake on the DQRI the FQ must instead be pushed to the portal's DQRR. >> > >> > The natural dpni_set_queue with a notification destination would have to >> > target the worker's portal, but that portal is only known once a worker >> > affines, after dev_start, and that MC command holds the global MC lock >> > long enough to wedge the firmware while traffic runs. So the bind cannot >> > be done late, against the polling lcore. >> > >> > Design >> > ------ >> > Each Rx FQ is bound to its own DPCON channel, statically, at dev_start >> > while the dpni is still disabled (no knowledge of the polling lcore). A >> > worker later subscribes its own ethrx portal to the channel and arms the >> > DQRI in rx_queue_intr_enable, a one-shot per-portal op, never the >> wedging >> > set_queue. On a wakeup the worker drains each of its queues by a >> volatile >> > dequeue on the queue's own DPCON channel (one FQ per channel, so no >> > per-frame demux); it polls all its queues, the same scheduling contract >> > as plain DPDK polling. A queue can be re-homed to another lcore at >> > runtime with no set_queue and no port stop. >> > >> > This reuses the event PMD's pushed/DQRR model but with one DPCON per FQ >> > and static affinity (no QBMan scheduling), so the DPCON allocator is >> > moved from the event driver to the fslmc bus and shared. >> > >> > Patch 1 disables the DPCON channel before closing it, an event/dpaa2 fix >> > the shared allocator depends on. Patches 2 to 4 move the DPCON allocator >> > to the fslmc bus, make the portal DQRI epoll optional, and add the >> > dpcon_set_notification MC command. Patch 5 adds the interrupt support >> > proper; patch 6 pins each DPIO's MSI to the lcore that arms it, a >> latency >> > optimisation. >> > >> > Tested on LX2160A (lx2160acex7). >> > >> > v3: >> > - Reworked the Rx drain. Both versions bind one DPCON per FQ, but v2 >> > drained the shared portal DQRR and demuxed frames to their FQ by >> > fqd_ctx, stashing foreign frames in a per-queue FIFO. v3 drains each >> > queue with a volatile dequeue on its own channel (one FQ per channel), >> > which drops the demux and stash code. >> > - Dropped the rx_queue_count fix; it is applied to main. >> > - Dropped the software VLAN strip patch; an independent net/dpaa2 >> cleanup, >> > sent standalone and now applied to next-net. >> > - Dropped the Depends-on: the ethdev fast-path ops fix is now in main. >> > - Split the dpcon_set_notification MC command into its own patch. >> > - Added an event/dpaa2 fix to disable the DPCON channel before close, >> > needed once the allocator is shared. >> > - Dropped the DQRI holdoff-tuning patch; the immediate-DQRI holdoff is >> now >> > set inline in the arm path. >> > - Added a patch reusing the event driver's MSI-affinity helper (exposed >> > from its RTE_EVENT_DPAA2 guard) to pin the portal MSI to the lcore >> that >> > arms it, so a CDAN wake lands on the worker's own core. >> > >> > v2: >> > - Dropped the RSS RETA patch, an independent net/dpaa2 change the >> > interrupt path does not require; it will be sent as its own series. >> > - Dropped the ethdev fast-path ops fix; it is now a standalone series. >> > - Dropped the eal/interrupts -EEXIST fix, applied to main by David >> > Marchand. >> > - Declared qbman_swp_interrupt_set_inhibit and qbman_swp_dqrr_size >> > __rte_internal (David Marchand). >> > - Minor formatting cleanup in the Rx interrupt setup. >> > >> > Maxime Leroy (6): >> > event/dpaa2: disable channel before closing it >> > bus/fslmc: move DPCON management from event driver to bus >> > bus/fslmc/dpio: make the portal DQRI epoll optional >> > bus/fslmc/mc: implement dpcon_set_notification >> > net/dpaa2: support Rx queue interrupts >> > net/dpaa2: pin Rx queue interrupt to the polling core >> > >> > doc/guides/nics/dpaa2.rst | 21 + >> > doc/guides/nics/features/dpaa2.ini | 1 + >> > doc/guides/rel_notes/release_26_07.rst | 1 + >> > drivers/bus/fslmc/mc/dpcon.c | 31 ++ >> > drivers/bus/fslmc/mc/fsl_dpcon.h | 18 + >> > drivers/bus/fslmc/meson.build | 1 + >> > .../fslmc/portal}/dpaa2_hw_dpcon.c | 17 +- >> > drivers/bus/fslmc/portal/dpaa2_hw_dpio.c | 112 ++++-- >> > drivers/bus/fslmc/portal/dpaa2_hw_dpio.h | 12 + >> > drivers/bus/fslmc/portal/dpaa2_hw_pvt.h | 12 + >> > .../fslmc/qbman/include/fsl_qbman_portal.h | 5 + >> > drivers/bus/fslmc/qbman/qbman_portal.c | 5 + >> > drivers/event/dpaa2/dpaa2_eventdev.h | 3 - >> > drivers/event/dpaa2/meson.build | 1 - >> > drivers/net/dpaa2/dpaa2_ethdev.c | 372 +++++++++++++++++- >> > drivers/net/dpaa2/dpaa2_ethdev.h | 4 + >> > drivers/net/dpaa2/dpaa2_rxtx.c | 104 +++-- >> > 17 files changed, 649 insertions(+), 71 deletions(-) >> > rename drivers/{event/dpaa2 => bus/fslmc/portal}/dpaa2_hw_dpcon.c (88%) >> > >> > >> > base-commit: 030328f5f920a87dabde54dacd4f5ac411ddcac9 >> >> Looks good, applied to net-next > > > Thanks for the merge. > > But this patchset have not been acked by Hemant. Right now, Hemant have > asked some fixes on this serie. Hemant also said it needs some time to test > it. > > [-- Attachment #2: Type: text/html, Size: 7967 bytes --] ^ permalink raw reply [flat|nested] 51+ messages in thread
end of thread, other threads:[~2026-07-06 10:36 UTC | newest] Thread overview: 51+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-11 15:49 [PATCH 0/9] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 2026-06-11 15:49 ` [PATCH 1/9] net/dpaa2: implement RSS RETA query and update Maxime Leroy 2026-06-11 15:49 ` [PATCH 2/9] eal/interrupts: keep real errno on epoll error Maxime Leroy 2026-06-16 8:02 ` David Marchand 2026-06-16 9:29 ` David Marchand 2026-06-11 15:49 ` [PATCH 3/9] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy 2026-06-11 15:49 ` [PATCH 4/9] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy 2026-06-11 15:49 ` [PATCH 5/9] net/dpaa2: support Rx queue interrupts Maxime Leroy 2026-06-16 8:05 ` David Marchand 2026-06-11 15:49 ` [PATCH 6/9] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff Maxime Leroy 2026-06-11 15:49 ` [PATCH 7/9] net/dpaa2: fix Rx queue count for primary process Maxime Leroy 2026-06-11 15:49 ` [PATCH 8/9] ethdev: keep fast-path ops valid after port stop Maxime Leroy 2026-06-11 16:01 ` Morten Brørup 2026-06-11 18:39 ` Maxime Leroy 2026-06-12 15:00 ` Thomas Monjalon 2026-06-14 19:30 ` Stephen Hemminger 2026-06-15 9:26 ` David Marchand 2026-06-16 9:23 ` Maxime Leroy 2026-06-16 9:33 ` David Marchand 2026-06-11 15:49 ` [PATCH 9/9] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy 2026-06-11 15:56 ` Morten Brørup 2026-06-11 16:13 ` Morten Brørup 2026-06-11 16:58 ` Maxime Leroy 2026-06-11 17:30 ` Stephen Hemminger 2026-06-12 5:42 ` Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 1/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 2/6] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 3/6] net/dpaa2: support Rx queue interrupts Maxime Leroy 2026-06-18 8:46 ` David Marchand 2026-06-16 10:27 ` [PATCH v2 4/6] bus/fslmc/dpio: tune DQRI interrupt coalescing holdoff Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 5/6] net/dpaa2: fix Rx queue count for primary process Maxime Leroy 2026-06-16 10:27 ` [PATCH v2 6/6] net/dpaa2: drop the fake software VLAN strip offload Maxime Leroy 2026-06-16 14:11 ` Stephen Hemminger 2026-06-16 15:40 ` Hemant Agrawal 2026-06-21 4:33 ` [PATCH v2 0/6] net/dpaa2: NAPI-style Rx queue interrupts Hemant Agrawal 2026-06-29 13:13 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 " Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 1/6] event/dpaa2: disable channel before closing it Maxime Leroy 2026-07-03 17:21 ` Hemant Agrawal 2026-07-06 9:11 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 2/6] bus/fslmc: move DPCON management from event driver to bus Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 3/6] bus/fslmc/dpio: make the portal DQRI epoll optional Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 4/6] bus/fslmc/mc: implement dpcon_set_notification Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts Maxime Leroy 2026-07-03 17:21 ` Hemant Agrawal 2026-07-06 10:36 ` Maxime Leroy 2026-06-30 14:43 ` [PATCH v3 6/6] net/dpaa2: pin Rx queue interrupt to the polling core Maxime Leroy 2026-07-03 16:03 ` [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts Stephen Hemminger 2026-07-03 18:56 ` Maxime Leroy 2026-07-03 20:26 ` Stephen Hemminger
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox