* Re: [PATCH v3 4/6] net/ice: timestamp all received packets when PTP is enabled
From: Bruce Richardson @ 2026-06-30 15:39 UTC (permalink / raw)
To: Dawid Wesierski; +Cc: dev, thomas, stephen, marek.kasiewicz
In-Reply-To: <20260630120657.1046588-5-dawid.wesierski@intel.com>
On Tue, Jun 30, 2026 at 08:06:54AM -0400, Dawid Wesierski wrote:
> From: Marek Kasiewicz <marek.kasiewicz@intel.com>
>
> When PTP is enabled on the ICE PMD, hardware RX timestamps are only
> applied to packets classified as IEEE 1588 (Ethertype 0x88F7). This
> prevents applications from obtaining hardware timestamps on regular
> UDP/IP traffic.
>
> Remove the TIMESYNC packet type filter so that all received packets
> get hardware timestamps when PTP is enabled. This is required for
> time-sensitive networking applications that need per-packet arrival
> timing on media traffic, such as ST 2110-21 receiver compliance
> monitoring.
>
> The change affects all three RX paths: scan, scattered, and single
> packet receive functions.
>
> Signed-off-by: Marek Kasiewicz <marek.kasiewicz@intel.com>
> Signed-off-by: Dawid Wesierski <dawid.wesierski@intel.com>
> ---
> drivers/net/intel/ice/ice_rxtx.c | 9 +++------
> 1 file changed, 3 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/net/intel/ice/ice_rxtx.c b/drivers/net/intel/ice/ice_rxtx.c
> index c4b5454c53..8d709125f7 100644
> --- a/drivers/net/intel/ice/ice_rxtx.c
> +++ b/drivers/net/intel/ice/ice_rxtx.c
> @@ -2023,8 +2023,7 @@ ice_rx_scan_hw_ring(struct ci_rx_queue *rxq)
> pkt_flags |= rxq->ts_flag;
> }
>
> - if (ad->ptp_ena && ((mb->packet_type &
> - RTE_PTYPE_L2_MASK) == RTE_PTYPE_L2_ETHER_TIMESYNC)) {
> + if (ad->ptp_ena) {
Dropping this condition means that all packets are now marked as PTP
packets, even though they are not, which is incorrect. At minimum we need
to check the packet type before setting the PTP flag on them.
However, at a higher level again, I think if we want timestamps for all
packets that that should be a separate feature from PTP support, which
should be more limited in scope. I see there is already an
RTE_ETH_RX_OFFLOAD_TIMESTAMP block above this in the code - does specifying
that offload flag not cause all packets to get properly timestamped? If
not, I would suggest that that be enhanced rather than pretending all
packets are PTP. If that doesn't work, do we need a third alternative,
perhaps?
Regards,
/Bruce
> rxq->time_high =
> rte_le_to_cpu_32(rxdp[j].wb.flex_ts.ts_high);
> mb->timesync = rxq->queue_id;
> @@ -2390,8 +2389,7 @@ ice_recv_scattered_pkts(void *rx_queue,
> pkt_flags |= rxq->ts_flag;
> }
>
> - if (ad->ptp_ena && ((first_seg->packet_type & RTE_PTYPE_L2_MASK)
> - == RTE_PTYPE_L2_ETHER_TIMESYNC)) {
> + if (ad->ptp_ena) {
> rxq->time_high =
> rte_le_to_cpu_32(rxd.wb.flex_ts.ts_high);
> first_seg->timesync = rxq->queue_id;
> @@ -2881,8 +2879,7 @@ ice_recv_pkts(void *rx_queue,
> pkt_flags |= rxq->ts_flag;
> }
>
> - if (ad->ptp_ena && ((rxm->packet_type & RTE_PTYPE_L2_MASK) ==
> - RTE_PTYPE_L2_ETHER_TIMESYNC)) {
> + if (ad->ptp_ena) {
> rxq->time_high =
> rte_le_to_cpu_32(rxd.wb.flex_ts.ts_high);
> rxm->timesync = rxq->queue_id;
> --
> 2.47.3
>
> ---------------------------------------------------------------------
> Intel Technology Poland sp. z o.o.
> ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
> Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
>
> Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
> This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
>
^ permalink raw reply
* [PATCH v2 8/8] doc: add release note about ip_frag changes
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
The changes to ip_frag might impact some conformance or
performance tests.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/rel_notes/release_26_07.rst | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index 4ca0a9ac77..a419803401 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -241,6 +241,12 @@ API Changes
- ``rte_pmd_mlx5_enable_steering``
- ``rte_pmd_mlx5_disable_steering``
+* **ip_frag: hardened IP reassembly against malformed fragments.**
+
+ Duplicate, overlapping, oversized, and (for IPv6) per-fragment-header
+ fragments are now rejected rather than mis-reassembled. This changes which
+ fragment streams reassemble and may affect throughput.
+
ABI Changes
-----------
--
2.53.0
^ permalink raw reply related
* [PATCH v2 7/8] ip_frag: remove use of rte_memcpy
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
Replace rte_memcpy with assignment or plain memcpy to
get more static checking.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/ip_frag/rte_ipv6_fragmentation.c | 3 +--
lib/ip_frag/rte_ipv6_reassembly.c | 5 ++---
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/lib/ip_frag/rte_ipv6_fragmentation.c b/lib/ip_frag/rte_ipv6_fragmentation.c
index c81f2402e3..86dd2e65ce 100644
--- a/lib/ip_frag/rte_ipv6_fragmentation.c
+++ b/lib/ip_frag/rte_ipv6_fragmentation.c
@@ -6,7 +6,6 @@
#include <errno.h>
#include <eal_export.h>
-#include <rte_memcpy.h>
#include "ip_frag_common.h"
@@ -24,7 +23,7 @@ __fill_ipv6hdr_frag(struct rte_ipv6_hdr *dst,
{
struct rte_ipv6_fragment_ext *fh;
- rte_memcpy(dst, src, sizeof(*dst));
+ *dst = *src;
dst->payload_len = rte_cpu_to_be_16(len);
dst->proto = IPPROTO_FRAGMENT;
diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
index 9aa2f2d08b..cae4266f97 100644
--- a/lib/ip_frag/rte_ipv6_reassembly.c
+++ b/lib/ip_frag/rte_ipv6_reassembly.c
@@ -5,7 +5,6 @@
#include <stddef.h>
#include <eal_export.h>
-#include <rte_memcpy.h>
#include "ip_frag_common.h"
@@ -145,8 +144,8 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
int32_t ip_len;
int32_t trim;
- rte_memcpy(&key.src_dst[0], &ip_hdr->src_addr, 16);
- rte_memcpy(&key.src_dst[2], &ip_hdr->dst_addr, 16);
+ memcpy(&key.src_dst[0], &ip_hdr->src_addr, 16);
+ memcpy(&key.src_dst[2], &ip_hdr->dst_addr, 16);
key.id = frag_hdr->id;
key.key_len = IPV6_KEYLEN;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 6/8] app/test: add test for IP reassembly
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
There was no functional test for IPv4/IPv6 reassembly,
only a performance test. Add a new test modeled on the Linux kernel
selftest tools/testing/selftests/net/ip_defrag.c. This is new
code so no license conflict.
Test covers:
size and fragment-size sweep across in-order, reverse,
odd/even and block delivery orders with byte-exact payload validation;
minimum 8-byte fragments; the fragment-count limit;
timeout of incomplete datagrams;
and the duplicate, overlap, extension-header and
oversized-fragment cases fixed earlier in this series.
The reassembled packet is checked with rte_mbuf_check() to catch
malformed segment chains in addition to wrong content.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/meson.build | 1 +
app/test/test_reassembly.c | 675 +++++++++++++++++++++++++++++++++++++
2 files changed, 676 insertions(+)
create mode 100644 app/test/test_reassembly.c
diff --git a/app/test/meson.build b/app/test/meson.build
index 61024125a7..b8c2208d0b 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -160,6 +160,7 @@ source_file_deps = {
'test_rawdev.c': ['rawdev', 'bus_vdev', 'raw_skeleton'],
'test_rcu_qsbr.c': ['rcu', 'hash'],
'test_rcu_qsbr_perf.c': ['rcu', 'hash'],
+ 'test_reassembly.c': ['net', 'ip_frag'],
'test_reassembly_perf.c': ['net', 'ip_frag'],
'test_reciprocal_division.c': [],
'test_reciprocal_division_perf.c': [],
diff --git a/app/test/test_reassembly.c b/app/test/test_reassembly.c
new file mode 100644
index 0000000000..480de8d1dd
--- /dev/null
+++ b/app/test/test_reassembly.c
@@ -0,0 +1,675 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026
+ *
+ * Functional unit tests for the IP reassembly path of librte_ip_frag.
+ *
+ * Coverage mirrors the Linux selftest tools/testing/selftests/net/ip_defrag.c
+ * adapted to the library API and to DPDK-specific constraints:
+ *
+ * - size / fragment-size sweep, bounded by RTE_LIBRTE_IP_FRAG_MAX_FRAG
+ * - in-order, reverse, odd-then-even, and block-reordered delivery
+ * - byte-exact validation of the reassembled payload (not just length)
+ * - minimum (8-byte) fragments
+ * - fragment-count boundary: exactly MAX reassembles, MAX + 1 fails
+ * - incomplete datagram reaped on timeout
+ * - zero-length fragment rejected
+ * - duplicate fragment tolerated in a reordered set
+ * - overlapping fragments (leading/trailing/contained) discarded
+ * - IPv6 fragment with extension headers in the unfragmentable part dropped
+ * - fragment whose end exceeds the maximum datagram size dropped
+ *
+ * The last four groups depend on the corresponding reassembly fixes
+ * (duplicate tolerance, overlap discard, extension-header drop, oversize
+ * drop); they pass once those are applied and fail on unpatched code. The
+ * remaining cases pass regardless.
+ *
+ * Fragments use l2_len == 0; the library reads the L3 header at offset 0.
+ */
+
+#include "test.h"
+
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_cycles.h>
+#include <rte_ip.h>
+#include <rte_ip_frag.h>
+#include <rte_log.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+
+#define NB_MBUF 1024
+#define MBUF_CACHE 0 /* exact accounting for leak checks */
+#define MBUF_DATA 2048
+#define V4_L3_LEN ((uint16_t)sizeof(struct rte_ipv4_hdr))
+#define V6_L3_LEN ((uint16_t)(sizeof(struct rte_ipv6_hdr) + \
+ RTE_IPV6_FRAG_HDR_SIZE))
+#define TEST_ID 0x4242
+
+#ifndef RTE_LIBRTE_IP_FRAG_MAX_FRAG
+#define RTE_LIBRTE_IP_FRAG_MAX_FRAG 8
+#endif
+#define MAX_FRAG RTE_LIBRTE_IP_FRAG_MAX_FRAG
+
+#define MAX_PAYLOAD (MAX_FRAG * 256) /* keeps a fragment in one mbuf */
+
+enum family { V4, V6 };
+enum order { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
+
+struct frag_desc {
+ uint16_t ofs; /* byte offset into the payload */
+ uint16_t plen; /* payload bytes after L3 */
+ uint8_t mf;
+};
+
+static struct rte_mempool *pkt_pool;
+
+/* position-dependent payload pattern, non-periodic at 256 so a misordered
+ * reassembly is detected even when lengths line up.
+ */
+static inline uint8_t
+pat(uint32_t k)
+{
+ return (uint8_t)(k * 31u + 7u);
+}
+
+/* ------------------------------- harness -------------------------------- */
+
+static int
+testsuite_setup(void)
+{
+ /* the table create/destroy per case is chatty at INFO level */
+ rte_log_set_level_pattern("lib.ip_frag", RTE_LOG_NOTICE);
+
+ pkt_pool = rte_pktmbuf_pool_create("REASM_POOL", NB_MBUF, MBUF_CACHE,
+ 0, MBUF_DATA, SOCKET_ID_ANY);
+ return pkt_pool == NULL ? TEST_FAILED : TEST_SUCCESS;
+}
+
+static void
+testsuite_teardown(void)
+{
+ rte_mempool_free(pkt_pool);
+ pkt_pool = NULL;
+}
+
+/* Every case must start and end with a full pool, so a leak in one case is
+ * pinpointed here rather than silently masking the next one.
+ */
+static int
+ut_setup(void)
+{
+ if (rte_mempool_avail_count(pkt_pool) != NB_MBUF) {
+ printf("pool not full at case start: %u/%u\n",
+ rte_mempool_avail_count(pkt_pool), NB_MBUF);
+ return TEST_FAILED;
+ }
+ return TEST_SUCCESS;
+}
+
+static struct rte_ip_frag_tbl *
+tbl_new(uint64_t max_cycles)
+{
+ return rte_ip_frag_table_create(16, MAX_FRAG, 16, max_cycles,
+ rte_socket_id());
+}
+
+/* Build one fragment with a position-dependent payload. */
+static struct rte_mbuf *
+build_frag(enum family fam, uint16_t ofs, uint16_t plen, uint8_t mf)
+{
+ struct rte_mbuf *m = rte_pktmbuf_alloc(pkt_pool);
+ uint16_t l3 = (fam == V4) ? V4_L3_LEN : V6_L3_LEN;
+ char *p;
+ uint16_t i;
+
+ if (m == NULL)
+ return NULL;
+ m->data_off = 0;
+
+ if (fam == V4) {
+ struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m,
+ struct rte_ipv4_hdr *);
+ uint16_t fo = ofs / RTE_IPV4_HDR_OFFSET_UNITS;
+
+ memset(ip, 0, V4_L3_LEN);
+ if (mf)
+ fo |= RTE_IPV4_HDR_MF_FLAG;
+ ip->version_ihl = 0x45;
+ ip->total_length = rte_cpu_to_be_16(V4_L3_LEN + plen);
+ ip->packet_id = rte_cpu_to_be_16(TEST_ID);
+ ip->fragment_offset = rte_cpu_to_be_16(fo);
+ ip->time_to_live = 64;
+ ip->next_proto_id = IPPROTO_UDP;
+ ip->src_addr = rte_cpu_to_be_32(0x0a000001);
+ ip->dst_addr = rte_cpu_to_be_32(0x0a000002);
+ } else {
+ struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m,
+ struct rte_ipv6_hdr *);
+ struct rte_ipv6_fragment_ext *fh =
+ rte_pktmbuf_mtod_offset(m,
+ struct rte_ipv6_fragment_ext *,
+ sizeof(struct rte_ipv6_hdr));
+
+ memset(ip, 0, V6_L3_LEN);
+ ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
+ ip->payload_len = rte_cpu_to_be_16(RTE_IPV6_FRAG_HDR_SIZE + plen);
+ ip->proto = IPPROTO_FRAGMENT;
+ ip->hop_limits = 64;
+ ip->src_addr.a[15] = 1;
+ ip->dst_addr.a[15] = 2;
+ fh->next_header = IPPROTO_UDP;
+ fh->reserved = 0;
+ fh->frag_data = rte_cpu_to_be_16(
+ RTE_IPV6_SET_FRAG_DATA(ofs, mf ? 1 : 0));
+ fh->id = rte_cpu_to_be_32(TEST_ID);
+ }
+
+ p = rte_pktmbuf_mtod_offset(m, char *, l3);
+ for (i = 0; i < plen; i++)
+ p[i] = (char)pat(ofs + i);
+
+ m->data_len = m->pkt_len = l3 + plen;
+ m->l2_len = 0;
+ m->l3_len = l3;
+ return m;
+}
+
+static struct rte_mbuf *
+feed(enum family fam, struct rte_ip_frag_tbl *tbl,
+ struct rte_ip_frag_death_row *dr, const struct frag_desc *d, uint64_t tms)
+{
+ struct rte_mbuf *m = build_frag(fam, d->ofs, d->plen, d->mf);
+
+ if (m == NULL)
+ return NULL;
+ if (fam == V4) {
+ struct rte_ipv4_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv4_hdr *);
+ return rte_ipv4_frag_reassemble_packet(tbl, dr, m, tms, ip);
+ } else {
+ struct rte_ipv6_hdr *ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
+ struct rte_ipv6_fragment_ext *fh =
+ rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
+ sizeof(struct rte_ipv6_hdr));
+ return rte_ipv6_frag_reassemble_packet(tbl, dr, m, tms, ip, fh);
+ }
+}
+
+/* Split a datagram of total_plen into fragments of frag_size (multiple of 8).
+ * Returns the fragment count, or -1 if it would exceed MAX_FRAG.
+ */
+static int
+make_datagram(uint16_t total_plen, uint16_t frag_size, struct frag_desc *out)
+{
+ int n = 0;
+ uint16_t ofs = 0;
+
+ while (ofs < total_plen) {
+ uint16_t rem = total_plen - ofs;
+ uint16_t len = rem <= frag_size ? rem : frag_size;
+
+ if (n >= MAX_FRAG)
+ return -1;
+ out[n].ofs = ofs;
+ out[n].plen = len;
+ out[n].mf = (ofs + len < total_plen);
+ ofs += len;
+ n++;
+ }
+ return n;
+}
+
+/* Produce a delivery order (array of indices into descs). */
+static void
+make_order(enum order ord, int n, int *idx)
+{
+ int i, k = 0;
+
+ switch (ord) {
+ case IN_ORDER:
+ for (i = 0; i < n; i++)
+ idx[i] = i;
+ break;
+ case REVERSE:
+ for (i = 0; i < n; i++)
+ idx[i] = n - 1 - i;
+ break;
+ case ODD_EVEN:
+ for (i = 1; i < n; i += 2)
+ idx[k++] = i;
+ for (i = 0; i < n; i += 2)
+ idx[k++] = i;
+ break;
+ case BLOCK: {
+ int t = n / 3 ? n / 3 : 1;
+
+ for (i = 2 * t; i < n; i++)
+ idx[k++] = i;
+ for (i = t; i < 2 * t && i < n; i++)
+ idx[k++] = i;
+ for (i = 0; i < t && i < n; i++)
+ idx[k++] = i;
+ break;
+ }
+ }
+}
+
+/* Feed descs in the given order; return reassembled mbuf or NULL. */
+static struct rte_mbuf *
+run_ordered(enum family fam, const struct frag_desc *descs, int n,
+ const int *idx)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct rte_mbuf *out = NULL;
+ uint64_t tms = rte_rdtsc();
+ int i;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ if (tbl == NULL)
+ return NULL;
+ for (i = 0; i < n; i++) {
+ struct rte_mbuf *r = feed(fam, tbl, &dr, &descs[idx[i]], tms);
+
+ if (r != NULL)
+ out = r;
+ }
+ rte_ip_frag_free_death_row(&dr, 0);
+ rte_ip_frag_table_destroy(tbl);
+ return out;
+}
+
+/* Validate length and byte-exact payload, then free. Returns 0 on success.
+ * Note: reassembly strips the IPv6 fragment header, so the reassembled v6
+ * header is sizeof(rte_ipv6_hdr), not the V6_L3_LEN the fragments were built
+ * with. v4 has no fragment header to remove.
+ */
+static int
+validate(struct rte_mbuf *m, enum family fam, uint16_t total_plen)
+{
+ uint16_t l3 = (fam == V4) ? V4_L3_LEN :
+ (uint16_t)sizeof(struct rte_ipv6_hdr);
+ uint8_t buf[MAX_PAYLOAD];
+ const uint8_t *p;
+ const char *reason;
+ uint16_t k;
+ int rc = 0;
+
+ if (m == NULL)
+ return -1;
+ if (rte_mbuf_check(m, 1, &reason) != 0) {
+ printf(" bad mbuf fam=%d total=%u: %s\n", fam, total_plen,
+ reason);
+ rte_pktmbuf_free(m);
+ return -1;
+ }
+ if (m->pkt_len != (uint32_t)(l3 + total_plen)) {
+ rte_pktmbuf_free(m);
+ return -1;
+ }
+ p = rte_pktmbuf_read(m, l3, total_plen, buf);
+ if (p == NULL) {
+ rte_pktmbuf_free(m);
+ return -1;
+ }
+ for (k = 0; k < total_plen; k++) {
+ if (p[k] != pat(k)) {
+ rc = -1;
+ break;
+ }
+ }
+ rte_pktmbuf_free(m);
+ return rc;
+}
+
+/* --------------------------- baseline / sweep --------------------------- */
+
+static int
+sweep_one(enum family fam, uint16_t total_plen, uint16_t frag_size)
+{
+ struct frag_desc descs[MAX_FRAG];
+ int idx[MAX_FRAG];
+ const enum order orders[] = { IN_ORDER, REVERSE, ODD_EVEN, BLOCK };
+ int n = make_datagram(total_plen, frag_size, descs);
+ unsigned int o;
+
+ if (n < 2) /* skip single-fragment / oversized for sweep */
+ return 0;
+
+ for (o = 0; o < RTE_DIM(orders); o++) {
+ make_order(orders[o], n, idx);
+ if (validate(run_ordered(fam, descs, n, idx), fam,
+ total_plen) != 0) {
+ printf(" sweep fail: fam=%d total=%u fs=%u order=%u n=%d\n",
+ fam, total_plen, frag_size, orders[o], n);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+sweep(enum family fam)
+{
+ const uint16_t fsizes[] = { 8, 16, 64, 256 };
+ unsigned int f;
+
+ for (f = 0; f < RTE_DIM(fsizes); f++) {
+ uint16_t fs = fsizes[f];
+ uint16_t total;
+
+ /* cover 2..MAX_FRAG fragments, last fragment partial */
+ for (total = fs + 8; total <= fs * MAX_FRAG; total += fs) {
+ if (sweep_one(fam, total, fs) != 0)
+ return TEST_FAILED;
+ if (total > fs + 4 &&
+ sweep_one(fam, total - 4, fs) != 0)
+ return TEST_FAILED;
+ }
+ }
+ return TEST_SUCCESS;
+}
+
+static int test_sweep_v4(void) { return sweep(V4); }
+static int test_sweep_v6(void) { return sweep(V6); }
+
+/* Minimum 8-byte fragments. */
+static int
+test_min_fragment(void)
+{
+ struct frag_desc d[3] = {
+ { 0, 8, 1 }, { 8, 8, 1 }, { 16, 8, 0 },
+ };
+ int idx[3];
+
+ make_order(REVERSE, 3, idx);
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 3, idx), V4, 24),
+ "min 8-byte fragments not reassembled");
+ make_order(ODD_EVEN, 3, idx);
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V6, d, 3, idx), V6, 24),
+ "min 8-byte fragments not reassembled (v6)");
+ return TEST_SUCCESS;
+}
+
+/* Exactly MAX_FRAG fragments reassembles; MAX_FRAG + 1 fails. */
+static int
+test_cap_boundary(void)
+{
+ struct frag_desc d[MAX_FRAG + 1];
+ int idx[MAX_FRAG + 1];
+ uint16_t fs = 8, total = fs * MAX_FRAG;
+ int n, i;
+
+ n = make_datagram(total, fs, d);
+ TEST_ASSERT_EQUAL(n, MAX_FRAG, "expected MAX_FRAG fragments");
+ make_order(IN_ORDER, n, idx);
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, n, idx), V4, total),
+ "MAX_FRAG fragments should reassemble");
+
+ /* one more fragment than the table can hold */
+ for (i = 0; i <= MAX_FRAG; i++) {
+ d[i].ofs = i * fs;
+ d[i].plen = fs;
+ d[i].mf = (i < MAX_FRAG);
+ idx[i] = i;
+ }
+ TEST_ASSERT_NULL(run_ordered(V4, d, MAX_FRAG + 1, idx),
+ "MAX_FRAG + 1 fragments should not reassemble");
+ TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+ "overflowing set leaked mbufs");
+ return TEST_SUCCESS;
+}
+
+/* Incomplete datagram: no output, reaped on timeout. */
+static int
+test_incomplete_timeout(void)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ uint64_t mc = rte_get_tsc_hz(), tms = rte_rdtsc();
+ struct frag_desc d[2] = { { 0, 64, 1 }, { 128, 64, 0 } }; /* gap */
+ struct rte_mbuf *out = NULL;
+ int i;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(mc);
+ TEST_ASSERT_NOT_NULL(tbl, "table create failed");
+ for (i = 0; i < 2; i++) {
+ struct rte_mbuf *r = feed(V4, tbl, &dr, &d[i], tms);
+
+ if (r != NULL)
+ out = r;
+ }
+ TEST_ASSERT_NULL(out, "incomplete datagram reassembled");
+ rte_ip_frag_table_del_expired_entries(tbl, &dr, tms + mc + 1);
+ rte_ip_frag_free_death_row(&dr, 0);
+ rte_ip_frag_table_destroy(tbl);
+ TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+ "expired fragments not freed");
+ return TEST_SUCCESS;
+}
+
+static int
+test_zero_len(void)
+{
+ struct frag_desc d = { 0, 0, 1 };
+ int idx = 0;
+
+ TEST_ASSERT_NULL(run_ordered(V4, &d, 1, &idx),
+ "zero-length fragment accepted");
+ TEST_ASSERT_EQUAL(rte_mempool_avail_count(pkt_pool), NB_MBUF,
+ "zero-length fragment leaked");
+ return TEST_SUCCESS;
+}
+
+/* --------------------- duplicate / overlap / reject --------------------- */
+
+/* A duplicate anywhere in a reordered set must not break reassembly. */
+static int
+test_dup_tolerated(void)
+{
+ /* offsets 0,64,128,192 with 64B frags; inject a dup of frag 1 */
+ struct frag_desc d[5] = {
+ { 0, 64, 1 }, { 64, 64, 1 }, { 64, 64, 1 }, /* dup */
+ { 128, 64, 1 }, { 192, 64, 0 },
+ };
+ int idx[5] = { 1, 4, 2, 0, 3 }; /* reordered, dup interleaved */
+
+ TEST_ASSERT_SUCCESS(validate(run_ordered(V4, d, 5, idx), V4, 256),
+ "duplicate fragment broke reassembly");
+ return TEST_SUCCESS;
+}
+
+/* Overlap geometries; the datagram must be discarded and every collected
+ * fragment freed. The last fragment is withheld so that on unfixed code the
+ * entry is *retained* (total_size stays UINT32_MAX) rather than torn down by
+ * the frag_size > total_size path: that retention is what we detect. We
+ * capture the mbufs still held in the table after draining the death row,
+ * before destroying the table (destroy frees held mbufs, hiding the leak).
+ */
+static int
+overlap_case(enum family fam, const struct frag_desc *d, int n, const char *what)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct rte_mbuf *out = NULL;
+ uint64_t tms = rte_rdtsc();
+ unsigned int held;
+ int i;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ if (tbl == NULL)
+ return -1;
+ for (i = 0; i < n; i++) {
+ struct rte_mbuf *r = feed(fam, tbl, &dr, &d[i], tms);
+
+ if (r != NULL)
+ out = r;
+ }
+ rte_ip_frag_free_death_row(&dr, 0);
+ held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+ rte_ip_frag_table_destroy(tbl);
+
+ if (out != NULL) {
+ rte_pktmbuf_free(out);
+ printf(" overlap reassembled instead of discarded: %s\n", what);
+ return -1;
+ }
+ if (held != 0) {
+ printf(" overlap kept %u fragment(s) instead of discarding: %s\n",
+ held, what);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+test_overlap(void)
+{
+ /* last fragment withheld in every case (all MF=1) */
+
+ /* overlapping fragment arrives second */
+ const struct frag_desc tail[2] = { { 0, 600, 1 }, { 300, 600, 1 } };
+ /* overlapping fragment arrives first */
+ const struct frag_desc head[2] = { { 300, 600, 1 }, { 0, 600, 1 } };
+ /* a fragment fully contained in an existing one */
+ const struct frag_desc cont[2] = { { 0, 600, 1 }, { 200, 200, 1 } };
+
+ TEST_ASSERT_SUCCESS(overlap_case(V6, tail, 2, "v6 overlap second"), "");
+ TEST_ASSERT_SUCCESS(overlap_case(V6, head, 2, "v6 overlap first"), "");
+ TEST_ASSERT_SUCCESS(overlap_case(V6, cont, 2, "v6 contained"), "");
+ TEST_ASSERT_SUCCESS(overlap_case(V4, tail, 2, "v4 overlap second"), "");
+ return TEST_SUCCESS;
+}
+
+/*
+ * An IPv6 fragment whose fragment header does not directly follow the base
+ * header (a per-fragment extension header precedes it) is dropped, not stored.
+ * Build base hdr + an 8-byte routing header + fragment header, and pass the
+ * fragment header at its real offset (48), so the library sees frag_hdr !=
+ * ip_hdr + 1. Captures whether the fragment is still held in the table after
+ * the death row is drained but before the table is destroyed.
+ */
+static int
+test_v6_ext_header_drop(void)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct rte_mbuf *m, *r;
+ struct rte_ipv6_hdr *ip;
+ struct rte_ipv6_fragment_ext *fh;
+ uint8_t *rthdr;
+ unsigned int held;
+ const uint16_t plen = 64;
+ const uint16_t ext = 8; /* one 8-byte routing header */
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ TEST_ASSERT_NOT_NULL(tbl, "table create failed");
+
+ m = rte_pktmbuf_alloc(pkt_pool);
+ TEST_ASSERT_NOT_NULL(m, "alloc failed");
+ m->data_off = 0;
+ ip = rte_pktmbuf_mtod(m, struct rte_ipv6_hdr *);
+ memset(ip, 0, sizeof(*ip));
+ ip->vtc_flow = rte_cpu_to_be_32(6u << 28);
+ ip->payload_len = rte_cpu_to_be_16(ext + RTE_IPV6_FRAG_HDR_SIZE + plen);
+ ip->proto = IPPROTO_ROUTING; /* per-fragment header before frag hdr */
+ ip->hop_limits = 64;
+ ip->src_addr.a[15] = 1;
+ ip->dst_addr.a[15] = 2;
+
+ /* 8-byte routing header, next = fragment */
+ rthdr = rte_pktmbuf_mtod_offset(m, uint8_t *, sizeof(*ip));
+ memset(rthdr, 0, ext);
+ rthdr[0] = IPPROTO_FRAGMENT; /* next header */
+ rthdr[1] = 0; /* hdr ext len: (0 + 1) * 8 = 8 bytes */
+
+ /* fragment header at offset 48, not 40 */
+ fh = rte_pktmbuf_mtod_offset(m, struct rte_ipv6_fragment_ext *,
+ sizeof(*ip) + ext);
+ fh->next_header = IPPROTO_UDP;
+ fh->reserved = 0;
+ fh->frag_data = rte_cpu_to_be_16(RTE_IPV6_SET_FRAG_DATA(0, 1));
+ fh->id = rte_cpu_to_be_32(TEST_ID);
+
+ m->data_len = m->pkt_len = sizeof(*ip) + ext + RTE_IPV6_FRAG_HDR_SIZE +
+ plen;
+ m->l2_len = 0;
+ m->l3_len = sizeof(*ip) + ext + RTE_IPV6_FRAG_HDR_SIZE;
+
+ r = rte_ipv6_frag_reassemble_packet(tbl, &dr, m, rte_rdtsc(), ip, fh);
+ rte_ip_frag_free_death_row(&dr, 0);
+ held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+ rte_ip_frag_table_destroy(tbl);
+
+ TEST_ASSERT_NULL(r, "fragment with per-fragment header accepted");
+ TEST_ASSERT_EQUAL(held, 0,
+ "per-fragment-header fragment stored instead of dropped");
+ return TEST_SUCCESS;
+}
+
+/* A fragment whose end exceeds the max datagram size is dropped, not stored. */
+static int
+oversize_drop_one(enum family fam)
+{
+ struct rte_ip_frag_death_row dr;
+ struct rte_ip_frag_tbl *tbl;
+ struct frag_desc d = { 0xFFF8, 64, 0 }; /* offset 65528 + 64 > 65535 */
+ struct rte_mbuf *r;
+ unsigned int held;
+
+ memset(&dr, 0, sizeof(dr));
+ tbl = tbl_new(rte_get_tsc_hz());
+ if (tbl == NULL)
+ return -1;
+ r = feed(fam, tbl, &dr, &d, rte_rdtsc());
+ rte_ip_frag_free_death_row(&dr, 0);
+ held = NB_MBUF - rte_mempool_avail_count(pkt_pool);
+ rte_ip_frag_table_destroy(tbl);
+
+ if (r != NULL) {
+ rte_pktmbuf_free(r);
+ return -1;
+ }
+ return held == 0 ? 0 : -1;
+}
+
+static int
+test_oversize_drop(void)
+{
+ TEST_ASSERT_SUCCESS(oversize_drop_one(V4),
+ "oversized v4 fragment stored instead of dropped");
+ TEST_ASSERT_SUCCESS(oversize_drop_one(V6),
+ "oversized v6 fragment stored instead of dropped");
+ return TEST_SUCCESS;
+}
+
+static struct unit_test_suite reassembly_testsuite = {
+ .suite_name = "IP Reassembly Unit Test Suite",
+ .setup = testsuite_setup,
+ .teardown = testsuite_teardown,
+ .unit_test_cases = {
+ TEST_CASE_ST(ut_setup, NULL, test_sweep_v4),
+ TEST_CASE_ST(ut_setup, NULL, test_sweep_v6),
+ TEST_CASE_ST(ut_setup, NULL, test_min_fragment),
+ TEST_CASE_ST(ut_setup, NULL, test_cap_boundary),
+ TEST_CASE_ST(ut_setup, NULL, test_incomplete_timeout),
+ TEST_CASE_ST(ut_setup, NULL, test_zero_len),
+ TEST_CASE_ST(ut_setup, NULL, test_dup_tolerated),
+ TEST_CASE_ST(ut_setup, NULL, test_overlap),
+ TEST_CASE_ST(ut_setup, NULL, test_v6_ext_header_drop),
+ TEST_CASE_ST(ut_setup, NULL, test_oversize_drop),
+ TEST_CASES_END()
+ }
+};
+
+static int
+test_reassembly(void)
+{
+ return unit_test_suite_runner(&reassembly_testsuite);
+}
+
+REGISTER_FAST_TEST(reassembly_autotest, NOHUGE_OK, ASAN_OK, test_reassembly);
--
2.53.0
^ permalink raw reply related
* [PATCH v2 5/8] ip_frag: reject oversized reassembled datagrams
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable, Konstantin Ananyev
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
The reassembled total length of a packet must not exceed 65535.
A fragment with a high offset could drive the sum past that,
causing silent truncation since IP payload_len/total_length is 16 bits.
When reassembling a packet the total length should not be allowed
to exceed 65535. A fragment with high offset could drive the sum
past that, causing silent truncation.
A valid datagram never exceeds 65535 bytes, so reject any fragment
whose resulting length would exceed that.
Fold the test into the existing zero-length check.
Fixes: cc8f4d020c0b ("examples/ip_reassembly: initial import")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
lib/ip_frag/rte_ipv4_reassembly.c | 9 +++++++--
lib/ip_frag/rte_ipv6_reassembly.c | 9 +++++++--
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/lib/ip_frag/rte_ipv4_reassembly.c b/lib/ip_frag/rte_ipv4_reassembly.c
index 980f7a3b77..727fc58243 100644
--- a/lib/ip_frag/rte_ipv4_reassembly.c
+++ b/lib/ip_frag/rte_ipv4_reassembly.c
@@ -136,8 +136,13 @@ rte_ipv4_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
tbl, tbl->max_cycles, tbl->entry_mask, tbl->max_entries,
tbl->use_entries);
- /* check that fragment length is greater then zero. */
- if (ip_len <= 0) {
+ /*
+ * Drop fragments with no payload, and any fragment whose end would
+ * make the reassembled datagram exceed the maximum IPv4 size. The
+ * total_length field is 16 bits, so otherwise it is silently
+ * truncated while the mbuf still holds the full length.
+ */
+ if (ip_len <= 0 || ip_ofs + ip_len + mb->l3_len > UINT16_MAX) {
IP_FRAG_MBUF2DR(dr, mb);
return NULL;
}
diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
index b6f623d53b..9aa2f2d08b 100644
--- a/lib/ip_frag/rte_ipv6_reassembly.c
+++ b/lib/ip_frag/rte_ipv6_reassembly.c
@@ -174,8 +174,13 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
tbl, tbl->max_cycles, tbl->entry_mask, tbl->max_entries,
tbl->use_entries);
- /* check that fragment length is greater then zero. */
- if (ip_len <= 0) {
+ /*
+ * Drop fragments with no payload, and any fragment whose end would
+ * make the reassembled payload exceed 65535 bytes. The payload_len
+ * field is 16 bits, so otherwise it is silently truncated while the
+ * mbuf still holds the full length.
+ */
+ if (ip_len <= 0 || ip_ofs + ip_len > UINT16_MAX) {
IP_FRAG_MBUF2DR(dr, mb);
return NULL;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v2 4/8] ip_frag: drop IPv6 fragments with per-fragment headers
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
IPv6 reassembly assumes the fragment header directly follows the IPv6
header. ipv6_frag_reassemble() patches the next-header field and removes
the fragment header at a fixed offset, so a fragment with per-fragment
extension headers before the fragment header reassembles to a corrupt
datagram.
Drop the fragment when its fragment header does not directly follow the
IPv6 header. Headers after the fragment header are payload and are
unaffected.
RFC 8200 permits discarding packets with extension headers out of the
recommended order, and RFC 9099 recommends dropping non-conforming
fragmented packets.
Fixes: 4f1a8f633862 ("ip_frag: add IPv6 reassembly")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/ip_frag/rte_ipv6_reassembly.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/lib/ip_frag/rte_ipv6_reassembly.c b/lib/ip_frag/rte_ipv6_reassembly.c
index 0e809a01e5..b6f623d53b 100644
--- a/lib/ip_frag/rte_ipv6_reassembly.c
+++ b/lib/ip_frag/rte_ipv6_reassembly.c
@@ -180,6 +180,28 @@ rte_ipv6_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
return NULL;
}
+ /*
+ * Only a fragment header directly following the IPv6 header is supported.
+ * Per-fragment (unfragmentable) extension headers placed
+ * before the fragment header are not handled: ipv6_frag_reassemble()
+ * patches the IPv6 header's next-header field and removes the fragment
+ * header assuming it sits immediately after the IPv6 header, so such a
+ * fragment would be reassembled into a corrupt datagram. Drop it.
+ *
+ * Extension headers after the fragment header (destination options,
+ * AH, ESP, upper-layer) are part of the fragmentable payload and are
+ * reassembled as opaque bytes, so they are not affected. The test uses
+ * the fragment header's position rather than l3_len so that callers
+ * which include later headers in l3_len are not rejected.
+ */
+ if ((uintptr_t)frag_hdr != (uintptr_t)(ip_hdr + 1)) {
+ IP_FRAG_LOG(DEBUG,
+ "%s:%d: drop fragment with header before frag header, offset %zu\n",
+ __func__, __LINE__, (uintptr_t)frag_hdr - (uintptr_t)ip_hdr);
+ IP_FRAG_MBUF2DR(dr, mb);
+ return NULL;
+ }
+
if (unlikely(trim > 0))
rte_pktmbuf_trim(mb, trim);
--
2.53.0
^ permalink raw reply related
* [PATCH v2 3/8] ip_frag: include protocol in IPv4 reassembly key
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable, Konstantin Ananyev
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
DPDK IPv4 reassembly code was not following RFC 791 section 3.2
which says:
The internet identification field (ID) is used together with the
source and destination address, and the protocol fields, to identify
datagram fragments for reassembly.
Omitting the protocol means two datagrams between the
same pair of hosts that share an IP id but carry different protocols
(for example UDP and ICMP) are merged into a single reassembly context,
producing a corrupted datagram.
Fold the protocol into the unused upper bits of the 32-bit id field
of the key. The IPv4 identification is 16 bits and occupies the low
half, so the protocol can be carried in the upper bits without changing
the key layout, the key comparison or the hash.
Fixes: cc8f4d020c0b ("examples/ip_reassembly: initial import")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
lib/ip_frag/rte_ipv4_reassembly.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/lib/ip_frag/rte_ipv4_reassembly.c b/lib/ip_frag/rte_ipv4_reassembly.c
index 3c8ae113ba..980f7a3b77 100644
--- a/lib/ip_frag/rte_ipv4_reassembly.c
+++ b/lib/ip_frag/rte_ipv4_reassembly.c
@@ -111,9 +111,15 @@ rte_ipv4_frag_reassemble_packet(struct rte_ip_frag_tbl *tbl,
ip_ofs = (uint16_t)(flag_offset & RTE_IPV4_HDR_OFFSET_MASK);
ip_flag = (uint16_t)(flag_offset & RTE_IPV4_HDR_MF_FLAG);
+ /*
+ * RFC 791 requires using: source, destination, identifier field and protocol
+ */
+
/* use first 8 bytes only */
memcpy(&key.src_dst[0], &ip_hdr->src_addr, 8);
- key.id = ip_hdr->packet_id;
+
+ /* packet_id is 16 bits and proto id is 8 bits */
+ key.id = ((uint32_t) ip_hdr->next_proto_id << 16) | ip_hdr->packet_id;
key.key_len = IPV4_KEYLEN;
ip_ofs *= RTE_IPV4_HDR_OFFSET_UNITS;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 2/8] ip_frag: discard datagrams with overlapping fragments
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, stable, Konstantin Ananyev
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
Existing code does not handle overlapping fragments.
RFC 8200 (IPv6) requires that on overlap all reassembly is abandoned
and all received fragments are dropped. RFC 791 (IPv4) originally called
for trimming and rewriting, but Linux discards for IPv4 as well, since
overlap has no legitimate use and is a known attack vector.
Depends on the duplicate-tolerance change so that an exact duplicate is
dropped on its own rather than discarding the whole datagram.
Fixes: cc8f4d020c0b ("examples/ip_reassembly: initial import")
Cc: stable@dpdk.org
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
lib/ip_frag/ip_frag_internal.c | 38 +++++++++++++++++++++++++++-------
1 file changed, 30 insertions(+), 8 deletions(-)
diff --git a/lib/ip_frag/ip_frag_internal.c b/lib/ip_frag/ip_frag_internal.c
index 9a03ef995a..55fc4e9343 100644
--- a/lib/ip_frag/ip_frag_internal.c
+++ b/lib/ip_frag/ip_frag_internal.c
@@ -92,16 +92,38 @@ ip_frag_process(struct ip_frag_pkt *fp, struct rte_ip_frag_death_row *dr,
uint32_t i, idx;
/*
- * Discard an exact duplicate fragment. If a previously stored fragment
- * already covers the same offset and length, this fragment carries no
- * new data. Reassembly is tolerant of duplicates (RFC 791), so drop
- * only this mbuf and keep the reassembly entry intact rather than
- * treating it as an error. Fragments overlapping an existing one with
- * different bounds are not handled here.
+ * Scan the fragments already collected for this datagram before
+ * storing the new one. The stored set is kept free of duplicates and
+ * overlaps, so a single pass is sufficient.
*/
for (i = 0; i != fp->last_idx; i++) {
- if (fp->frags[i].mb != NULL && fp->frags[i].ofs == ofs &&
- fp->frags[i].len == len) {
+ if (fp->frags[i].mb == NULL)
+ continue;
+
+ /*
+ * Exact duplicate: carries no new data. Reassembly tolerates
+ * duplicates (RFC 791), so drop only this mbuf and keep the
+ * entry.
+ */
+ if (fp->frags[i].ofs == ofs && fp->frags[i].len == len) {
+ IP_FRAG_MBUF2DR(dr, mb);
+ return NULL;
+ }
+
+ /*
+ * Overlap with an existing fragment. Per RFC 8200 section 4.5
+ * (and RFC 5722) the datagram must be discarded; the same is
+ * applied to IPv4. Free all collected fragments, drop this one,
+ * and invalidate the entry.
+ */
+ if (ofs < fp->frags[i].ofs + fp->frags[i].len && fp->frags[i].ofs < ofs + len) {
+ IP_FRAG_LOG(DEBUG,
+ "%s:%d overlap ofs: %u len: %u\n"
+ "fragment: %p ofs: %u len %u\n\n",
+ __func__, __LINE__, ofs, len,
+ fp, fp->frags[i].ofs, fp->frags[i].len);
+ ip_frag_free(fp, dr);
+ ip_frag_key_invalidate(&fp->key);
IP_FRAG_MBUF2DR(dr, mb);
return NULL;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v2 1/8] ip_frag: tolerate duplicate fragments
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Samyak Jain, Konstantin Ananyev
In-Reply-To: <20260630153723.525167-1-stephen@networkplumber.org>
The reassembly code tracked only a running byte total and reserved slots
for the first and last fragments, with no check for a fragment
duplicating data already received. A single duplicate could destroy a
recoverable datagram:
- a duplicate first or last fragment collided with the reserved slot and
sent the whole entry down the error path, freeing every collected
fragment;
- a duplicate intermediate fragment was appended to a new slot, inflating
frag_size past total_size so reassembly never completed.
RFC 791 reassembly tolerates duplicates: a fragment covering bytes
already present carries no new information. Check for an exact duplicate
(stored fragment with the same offset and length) and drop only that
mbuf, before frag_size is updated, leaving the entry's accounting
unchanged.
Overlapping fragments with differing bounds are a separate issue
addressed in the next patch.
Reported-by: Samyak Jain <samyak.jain@amantyatech.com>
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
lib/ip_frag/ip_frag_internal.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/lib/ip_frag/ip_frag_internal.c b/lib/ip_frag/ip_frag_internal.c
index 382f42d0e1..9a03ef995a 100644
--- a/lib/ip_frag/ip_frag_internal.c
+++ b/lib/ip_frag/ip_frag_internal.c
@@ -89,7 +89,23 @@ struct rte_mbuf *
ip_frag_process(struct ip_frag_pkt *fp, struct rte_ip_frag_death_row *dr,
struct rte_mbuf *mb, uint16_t ofs, uint16_t len, uint16_t more_frags)
{
- uint32_t idx;
+ uint32_t i, idx;
+
+ /*
+ * Discard an exact duplicate fragment. If a previously stored fragment
+ * already covers the same offset and length, this fragment carries no
+ * new data. Reassembly is tolerant of duplicates (RFC 791), so drop
+ * only this mbuf and keep the reassembly entry intact rather than
+ * treating it as an error. Fragments overlapping an existing one with
+ * different bounds are not handled here.
+ */
+ for (i = 0; i != fp->last_idx; i++) {
+ if (fp->frags[i].mb != NULL && fp->frags[i].ofs == ofs &&
+ fp->frags[i].len == len) {
+ IP_FRAG_MBUF2DR(dr, mb);
+ return NULL;
+ }
+ }
fp->frag_size += len;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 0/8] ip_frag: fix reassembly defects and add test
From: Stephen Hemminger @ 2026-06-30 15:36 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260616210656.464062-1-stephen@networkplumber.org>
The IP reassembly library tracks only a running byte total and reserved
slots for the first and last fragments, with no coverage map. As a result
it mishandles duplicate, overlapping, oversized, and misheadered
fragments, and the IPv4 key is missing a field RFC 791 requires. There
was also no functional test to catch any of it.
These came out of reviewing a duplicate-fragment report on the list.
Patches 1 and 2 are interdependent: the overlap discard relies on the
duplicate handling so an exact duplicate is dropped on its own rather
than discarding the whole datagram. The rest are independent.
Patch 6 adds a functional test modeled on the Linux selftest ip_defrag.c.
It passes on this series; with any single fix reverted the matching case
fails.
Stephen Hemminger (8):
ip_frag: tolerate duplicate fragments
ip_frag: discard datagrams with overlapping fragments
ip_frag: include protocol in IPv4 reassembly key
ip_frag: drop IPv6 fragments with per-fragment headers
ip_frag: reject oversized reassembled datagrams
app/test: add test for IP reassembly
ip_frag: remove use of rte_memcpy
doc: add release note about ip_frag changes
app/test/meson.build | 1 +
app/test/test_reassembly.c | 675 +++++++++++++++++++++++++
doc/guides/rel_notes/release_26_07.rst | 6 +
lib/ip_frag/ip_frag_internal.c | 40 +-
lib/ip_frag/rte_ipv4_reassembly.c | 17 +-
lib/ip_frag/rte_ipv6_fragmentation.c | 3 +-
lib/ip_frag/rte_ipv6_reassembly.c | 36 +-
7 files changed, 767 insertions(+), 11 deletions(-)
create mode 100644 app/test/test_reassembly.c
--
2.53.0
^ permalink raw reply
* Re: [PATCH v3 3/6] net/ice: add scheduler rate-limiter burst size devarg
From: Bruce Richardson @ 2026-06-30 15:20 UTC (permalink / raw)
To: Dawid Wesierski; +Cc: dev, thomas, stephen, marek.kasiewicz
In-Reply-To: <20260630120657.1046588-4-dawid.wesierski@intel.com>
On Tue, Jun 30, 2026 at 08:06:53AM -0400, Dawid Wesierski wrote:
> From: Marek Kasiewicz <marek.kasiewicz@intel.com>
>
> The E810 Tx scheduler uses a token bucket algorithm where the burst
> size controls the maximum bytes sent in a single burst before the
> rate limiter throttles. The hardware default of 15 KB allows
> micro-bursts of ~10 max-size frames, which violates tight
> inter-packet spacing requirements in time-sensitive networking
> applications such as SMPTE ST 2110-21 narrow-sender compliance.
>
> Add a "rl_burst_size" device argument that lets the application lower
> the scheduler rate-limiter burst size (for example to 2 KB) to force
> near-constant-rate output matching the configured shaper profile.
> The burst size is a global scheduler resource, so the override is
> applied once at probe time and only when the user explicitly requests
> it; the hardware default is left unchanged otherwise.
>
> Signed-off-by: Marek Kasiewicz <marek.kasiewicz@intel.com>
> Signed-off-by: Dawid Wesierski <dawid.wesierski@intel.com>
> ---
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
However, one issue flagged below that needs resolution before merge.
> doc/guides/nics/ice.rst | 12 +++++++
> doc/guides/rel_notes/release_26_07.rst | 5 +++
> drivers/net/intel/ice/ice_ethdev.c | 46 ++++++++++++++++++++++++++
> drivers/net/intel/ice/ice_ethdev.h | 1 +
> 4 files changed, 64 insertions(+)
>
> diff --git a/doc/guides/nics/ice.rst b/doc/guides/nics/ice.rst
> index 8251416918..187c7e821f 100644
> --- a/doc/guides/nics/ice.rst
> +++ b/doc/guides/nics/ice.rst
> @@ -158,6 +158,18 @@ Runtime Configuration
>
> -a 80:00.0,source-prune=1
>
> +- ``Scheduler rate-limiter burst size`` (default ``0``)
> +
> + The hardware Tx scheduler uses a default rate-limiter burst size that favours
> + throughput. Time-sensitive applications can lower this value to reduce Tx
> + latency jitter at the cost of throughput by setting the ``rl_burst_size``
> + devargs parameter, in bytes. The value is clamped to the hardware-allowed
> + range. A value of ``0`` (the default) keeps the hardware default.
The documentation here is wrong, in that values are not clamped, but are
instead rejected if they are out of range.
ICE_INIT: ice_dev_init(): Invalid rl_burst_size 2097152 bytes
testpmd: No probed ethernet devices
If you like, I can update the docs on apply to remove this clamping
reference. Alternatively, if you do want to clamp the values, please respin
this patchset. If you do, you can keep my ack on the new version.
> +
> + For example::
> +
> + -a 80:00.0,rl_burst_size=2048
> +
> - ``Protocol extraction for per queue``
>
> Configure the RX queues to do protocol extraction into mbuf for protocol
> diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
> index 5d7aa8d1bf..db8c4d5b16 100644
> --- a/doc/guides/rel_notes/release_26_07.rst
> +++ b/doc/guides/rel_notes/release_26_07.rst
> @@ -136,6 +136,11 @@ New Features
> * Added support for transmitting LLDP packets based on mbuf packet type.
> * Implemented AVX2 context descriptor transmit paths.
>
> +* **Updated Intel ice driver.**
> +
> + * Added ``rl_burst_size`` devarg to configure the scheduler rate-limiter
> + burst size, reducing Tx latency jitter for time-sensitive traffic.
> +
> * **Updated NVIDIA mlx5 ethernet driver.**
>
> * Added support for selective Rx in scalar SPRQ Rx path.
> diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
> index ad9c49b339..465cf07383 100644
> --- a/drivers/net/intel/ice/ice_ethdev.c
> +++ b/drivers/net/intel/ice/ice_ethdev.c
> @@ -41,6 +41,7 @@
> #define ICE_DDP_FILENAME_ARG "ddp_pkg_file"
> #define ICE_DDP_LOAD_SCHED_ARG "ddp_load_sched_topo"
> #define ICE_TM_LEVELS_ARG "tm_sched_levels"
> +#define ICE_RL_BURST_SIZE_ARG "rl_burst_size"
> #define ICE_SOURCE_PRUNE_ARG "source-prune"
> #define ICE_LINK_STATE_ON_CLOSE "link_state_on_close"
>
> @@ -59,6 +60,7 @@ static const char * const ice_valid_args[] = {
> ICE_DDP_FILENAME_ARG,
> ICE_DDP_LOAD_SCHED_ARG,
> ICE_TM_LEVELS_ARG,
> + ICE_RL_BURST_SIZE_ARG,
> ICE_SOURCE_PRUNE_ARG,
> ICE_LINK_STATE_ON_CLOSE,
> NULL
> @@ -2147,6 +2149,29 @@ parse_u64(const char *key, const char *value, void *args)
> return 0;
> }
>
> +static int
> +parse_u32(const char *key, const char *value, void *args)
> +{
> + uint32_t *num = args;
> + unsigned long tmp;
> + char *endptr;
> +
> + errno = 0;
> + tmp = strtoul(value, &endptr, 0);
> + if (errno != 0 || endptr == value || *endptr != '\0') {
> + PMD_DRV_LOG(WARNING, "%s: \"%s\" is not a valid u32", key, value);
> + return -1;
> + }
> + if (tmp > UINT32_MAX) {
> + PMD_DRV_LOG(WARNING, "%s: value \"%s\" is out of range", key, value);
> + return -1;
> + }
> +
> + *num = (uint32_t)tmp;
> +
> + return 0;
> +}
> +
> static int
> parse_tx_sched_levels(const char *key, const char *value, void *args)
> {
> @@ -2448,6 +2473,11 @@ static int ice_parse_devargs(struct rte_eth_dev *dev)
> if (ret)
> goto bail;
>
> + ret = rte_kvargs_process(kvlist, ICE_RL_BURST_SIZE_ARG,
> + &parse_u32, &ad->devargs.rl_burst_size);
> + if (ret)
> + goto bail;
> +
> ret = rte_kvargs_process(kvlist, ICE_SOURCE_PRUNE_ARG,
> &parse_bool, &ad->devargs.source_prune);
> if (ret)
> @@ -2662,6 +2692,21 @@ ice_dev_init(struct rte_eth_dev *dev)
> return -EINVAL;
> }
>
> + /*
> + * Override the hardware default scheduler rate-limiter burst size only
> + * when the user explicitly requests it. A smaller burst reduces Tx
> + * latency jitter for time-sensitive traffic at the cost of throughput,
> + * so it must not change for every port. ice_cfg_rl_burst_size()
> + * validates the value against the hardware-allowed range.
> + */
> + if (ad->devargs.rl_burst_size != 0 &&
> + ice_cfg_rl_burst_size(hw, ad->devargs.rl_burst_size) != 0) {
> + PMD_INIT_LOG(ERR, "Invalid rl_burst_size %u bytes",
> + ad->devargs.rl_burst_size);
> + ice_deinit_hw(hw);
> + return -EINVAL;
> + }
> +
> #ifndef RTE_EXEC_ENV_WINDOWS
> use_dsn = false;
> dsn = 0;
> @@ -7713,6 +7758,7 @@ RTE_PMD_REGISTER_PARAM_STRING(net_ice,
> ICE_DDP_FILENAME_ARG "=</path/to/file>"
> ICE_DDP_LOAD_SCHED_ARG "=<0|1>"
> ICE_TM_LEVELS_ARG "=<N>"
> + ICE_RL_BURST_SIZE_ARG "=<N>"
> ICE_SOURCE_PRUNE_ARG "=<0|1>"
> ICE_RX_LOW_LATENCY_ARG "=<0|1>"
> ICE_LINK_STATE_ON_CLOSE "=<down|up|initial>");
> diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
> index 20e8a13fe9..0a9d75b9cd 100644
> --- a/drivers/net/intel/ice/ice_ethdev.h
> +++ b/drivers/net/intel/ice/ice_ethdev.h
> @@ -631,6 +631,7 @@ struct ice_devargs {
> uint8_t ddp_load_sched;
> uint8_t tm_exposed_levels;
> uint8_t source_prune;
> + uint32_t rl_burst_size;
> int link_state_on_close;
> int xtr_field_offs;
> uint8_t xtr_flag_offs[PROTO_XTR_MAX];
> --
> 2.47.3
>
> ---------------------------------------------------------------------
> Intel Technology Poland sp. z o.o.
> ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
> Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
>
> Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
> This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
>
^ permalink raw reply
* Re: [PATCH v3 2/6] net/iavf: allow runtime queue rate limit configuration
From: Bruce Richardson @ 2026-06-30 14:46 UTC (permalink / raw)
To: Dawid Wesierski; +Cc: dev, thomas, stephen, marek.kasiewicz
In-Reply-To: <20260630120657.1046588-3-dawid.wesierski@intel.com>
On Tue, Jun 30, 2026 at 08:06:52AM -0400, Dawid Wesierski wrote:
> From: Marek Kasiewicz <marek.kasiewicz@intel.com>
>
> Allow per-queue bandwidth rate limiting to be configured without
> stopping the port when only a single TC node and single QoS element
> are involved. This enables dynamic session management where individual
> queue pacing rates can be changed while other queues continue
> transmitting.
>
> Also fix the queue ID assignment in the bandwidth configuration to
> use the actual TM node ID rather than a sequential counter index, and
> only mark the TM hierarchy as committed when the port is stopped to
> permit subsequent reconfiguration.
>
> Signed-off-by: Marek Kasiewicz <marek.kasiewicz@intel.com>
> Signed-off-by: Dawid Wesierski <dawid.wesierski@intel.com>
> ---
> drivers/net/intel/iavf/iavf_tm.c | 25 ++++++++++++++++---------
> 1 file changed, 16 insertions(+), 9 deletions(-)
>
AI review that I ran indicates that this function has some memory leaks
around the tc_mapping, q_bw and qtc_map variables, which are allocated
using rte_zmalloc. However, those are not introduced by this patch, so ok
to take this as-is for now. We should look to ensure leaks are fixed in
future patches. (Also, we should ideally change from using rte_zmalloc to
regular malloc/calloc, unless there is a strong reason why the need these
nodes in hugepage memory).
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> diff --git a/drivers/net/intel/iavf/iavf_tm.c b/drivers/net/intel/iavf/iavf_tm.c
> index 1cf7bfb106..e3492ec491 100644
> --- a/drivers/net/intel/iavf/iavf_tm.c
> +++ b/drivers/net/intel/iavf/iavf_tm.c
> @@ -804,19 +804,25 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
> int index = 0, node_committed = 0;
> int i, ret_val = IAVF_SUCCESS;
>
> - /* check if port is stopped */
> - if (adapter->stopped != 1) {
> - PMD_DRV_LOG(ERR, "Please stop port first");
> - ret_val = IAVF_ERR_NOT_READY;
> - goto err;
> - }
> -
> if (!(vf->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_QOS)) {
> PMD_DRV_LOG(ERR, "VF queue tc mapping is not supported");
> ret_val = IAVF_NOT_SUPPORTED;
> goto fail_clear;
> }
>
> + /*
> + * Allow reconfiguration on a running port only when a single queue is
> + * involved (single TC node and single QoS element); otherwise the port
> + * must be stopped first. qos_cap is valid here because the
> + * VIRTCHNL_VF_OFFLOAD_QOS capability was checked above.
> + */
> + if ((vf->tm_conf.nb_tc_node != 1 || vf->qos_cap->num_elem != 1) &&
> + adapter->stopped != 1) {
> + PMD_DRV_LOG(ERR, "Please stop port first");
> + ret_val = IAVF_ERR_NOT_READY;
> + goto err;
> + }
> +
> /* check if all TC nodes are set with VF vsi */
> if (vf->tm_conf.nb_tc_node != vf->qos_cap->num_elem) {
> PMD_DRV_LOG(ERR, "Does not set VF vsi nodes to all TCs");
> @@ -856,7 +862,7 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
> q_tc_mapping->tc[tm_node->tc].req.queue_count++;
>
> if (tm_node->shaper_profile) {
> - q_bw->cfg[node_committed].queue_id = node_committed;
> + q_bw->cfg[node_committed].queue_id = tm_node->id;
> q_bw->cfg[node_committed].shaper.peak =
> tm_node->shaper_profile->profile.peak.rate /
> 1000 * IAVF_BITS_PER_BYTE;
> @@ -900,7 +906,8 @@ static int iavf_hierarchy_commit(struct rte_eth_dev *dev,
> goto fail_clear;
>
> vf->qtc_map = qtc_map;
> - vf->tm_conf.committed = true;
> + if (adapter->stopped == 1)
> + vf->tm_conf.committed = true;
> return ret_val;
>
> fail_clear:
> --
> 2.47.3
>
> ---------------------------------------------------------------------
> Intel Technology Poland sp. z o.o.
> ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
> Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
>
> Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
> This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
>
^ permalink raw reply
* [PATCH v3 6/6] net/dpaa2: pin Rx queue interrupt to the polling core
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260630144329.457643-1-maxime@leroys.fr>
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
* [PATCH v3 5/6] net/dpaa2: support Rx queue interrupts
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260630144329.457643-1-maxime@leroys.fr>
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
* [PATCH v3 4/6] bus/fslmc/mc: implement dpcon_set_notification
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260630144329.457643-1-maxime@leroys.fr>
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
* [PATCH v3 3/6] bus/fslmc/dpio: make the portal DQRI epoll optional
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260630144329.457643-1-maxime@leroys.fr>
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
* [PATCH v3 2/6] bus/fslmc: move DPCON management from event driver to bus
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260630144329.457643-1-maxime@leroys.fr>
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
* [PATCH v3 1/6] event/dpaa2: disable channel before closing it
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260630144329.457643-1-maxime@leroys.fr>
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
* [PATCH v3 0/6] net/dpaa2: NAPI-style Rx queue interrupts
From: Maxime Leroy @ 2026-06-30 14:43 UTC (permalink / raw)
To: dev; +Cc: hemant.agrawal, sachin.saxena, david.marchand, Maxime Leroy
In-Reply-To: <20260616102727.708948-1-maxime@leroys.fr>
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
* Re: [PATCH v3 1/6] net/iavf: increase max ring descriptors to hardware limit
From: Bruce Richardson @ 2026-06-30 14:29 UTC (permalink / raw)
To: Dawid Wesierski; +Cc: dev, thomas, stephen, marek.kasiewicz
In-Reply-To: <20260630120657.1046588-2-dawid.wesierski@intel.com>
On Tue, Jun 30, 2026 at 08:06:51AM -0400, Dawid Wesierski wrote:
> From: Marek Kasiewicz <marek.kasiewicz@intel.com>
>
> The Intel E810 hardware supports up to 8160 (8K - 32) descriptors per
> TX/RX ring, but IAVF_MAX_RING_DESC caps it at 4096. Applications that
> need deep descriptor rings for hardware rate-limited pacing (e.g.,
> ST2110 video with thousands of packets per frame) cannot queue enough
> packets before the pacing epoch begins.
>
> Increase IAVF_MAX_RING_DESC to the hardware maximum of 8160 to allow
> full utilization of the ring depth on E810 VFs.
>
> Signed-off-by: Marek Kasiewicz <marek.kasiewicz@intel.com>
> Signed-off-by: Dawid Wesierski <dawid.wesierski@intel.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> ---
> .mailmap | 2 ++
> drivers/net/intel/iavf/iavf_rxtx.h | 2 +-
> 2 files changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/.mailmap b/.mailmap
> index 4001e5fb0e..d7b175de2a 100644
> --- a/.mailmap
> +++ b/.mailmap
> @@ -366,6 +366,7 @@ David Zeng <zengxhsh@cn.ibm.com>
> Davide Caratti <dcaratti@redhat.com>
> Dawid Gorecki <dgr@semihalf.com>
> Dawid Jurczak <dawid_jurek@vp.pl>
> +Dawid Wesierski <dawid.wesierski@intel.com>
> Dawid Zielinski <dawid.zielinski@intel.com>
> Dawid Łukwiński <dawid.lukwinski@intel.com>
> Daxue Gao <daxuex.gao@intel.com>
> @@ -1014,6 +1015,7 @@ Marcin Wilk <marcin.wilk@caviumnetworks.com>
> Marcin Wojtas <mw@semihalf.com>
> Marcin Zapolski <marcinx.a.zapolski@intel.com>
> Marco Varlese <mvarlese@suse.de>
> +Marek Kasiewicz <marek.kasiewicz@intel.com>
> Marek Mical <marekx.mical@intel.com>
> Marek Zalfresso-jundzillo <marekx.zalfresso-jundzillo@intel.com>
> Maria Lingemark <maria.lingemark@ericsson.com>
> diff --git a/drivers/net/intel/iavf/iavf_rxtx.h b/drivers/net/intel/iavf/iavf_rxtx.h
> index 8449236d4d..22ea415f44 100644
> --- a/drivers/net/intel/iavf/iavf_rxtx.h
> +++ b/drivers/net/intel/iavf/iavf_rxtx.h
> @@ -16,7 +16,7 @@
> /* In QLEN must be whole number of 32 descriptors. */
> #define IAVF_ALIGN_RING_DESC 32
> #define IAVF_MIN_RING_DESC 64
> -#define IAVF_MAX_RING_DESC 4096
> +#define IAVF_MAX_RING_DESC (8192 - 32)
> #define IAVF_DMA_MEM_ALIGN 4096
> /* Base address of the HW descriptor ring should be 128B aligned. */
> #define IAVF_RING_BASE_ALIGN 128
> --
> 2.47.3
>
> ---------------------------------------------------------------------
> Intel Technology Poland sp. z o.o.
> ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
> Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
>
> Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
> This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
^ permalink raw reply
* Re: [EXTERNAL] [PATCH] crypto/ipsec_mb: fix clearing snow3g digest
From: Ji, Kai @ 2026-06-30 14:28 UTC (permalink / raw)
To: Akhil Goyal, De Lara Guarch, Pablo; +Cc: dev@dpdk.org, weid wei.dai@picocom.com
In-Reply-To: <CO6PR18MB4484A9157D8A409D0A9D9D8FD846A@CO6PR18MB4484.namprd18.prod.outlook.com>
[-- Attachment #1: Type: text/plain, Size: 2587 bytes --]
Acked-by: Kai Ji <kai.ji@intel.com>
Please aware the IPSEC SNOW3G and ZUC PMDs currently remained in DPDK will only be built for the ARM version: https://patches.dpdk.org/project/dpdk/list/?series=38319
The AESNI_MB PMD is recommended for IPSEC-MB lib
________________________________
From: Akhil Goyal <gakhil@marvell.com>
Sent: Tuesday, March 10, 2026 14:05
To: Akhil Goyal <gakhil@marvell.com>; Ji, Kai <kai.ji@intel.com>; De Lara Guarch, Pablo <pablo.de.lara.guarch@intel.com>
Cc: dev@dpdk.org <dev@dpdk.org>; weid wei.dai@picocom.com <wei.dai@picocom.com>
Subject: RE: [EXTERNAL] [PATCH] crypto/ipsec_mb: fix clearing snow3g digest
> Hi Pablo/Kai,
> Please ack if no objection
>
Reminder for ack.
>
> > From: weid <wei.dai@picocom.com>
> >
> > Before this patch, the software crypto_snow3g
> > clear the encrypted auth snow3g digest when
> > the dst mbuf is same as the src mbuf in crypto
> > op.
> >
> > This bug can be checked by following way,
> > start app/dpdk-test --vdev=crypto_snow3g,
> > run pdcp_autotest
> > the case failed when i=42 in
> > run_test_for_one_known_vec() in test_pdcp.c.
> > The case is PDCP control plane auth snow3g gen
> > + cipher snow3g enc.
> > The case failed because the last 4 bytes are
> > cleared in last for() loop in line 167 in
> > process_snow3g_cipher_op() in pmd_snow3g.c .
> >
> > Fixes: 5208d68d30cb ("crypto/ipsec_mb: support snow3g digest appended
> ops")
> >
> > Signed-off-by: weid <wei.dai@picocom.com>
> > ---
> > drivers/crypto/ipsec_mb/pmd_snow3g.c | 3 ++-
> > 1 file changed, 2 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/crypto/ipsec_mb/pmd_snow3g.c
> > b/drivers/crypto/ipsec_mb/pmd_snow3g.c
> > index b3c3b05a8a..35fe23c157 100644
> > --- a/drivers/crypto/ipsec_mb/pmd_snow3g.c
> > +++ b/drivers/crypto/ipsec_mb/pmd_snow3g.c
> > @@ -157,7 +157,8 @@ process_snow3g_cipher_op(struct ipsec_mb_qp *qp,
> > struct rte_crypto_op **ops,
> > for (i = 0; i < num_ops; i++) {
> > if ((session->op ==
> > IPSEC_MB_OP_HASH_VERIFY_THEN_DECRYPT ||
> > session->op ==
> > IPSEC_MB_OP_HASH_GEN_THEN_ENCRYPT) &&
> > - ops[i]->sym->m_dst != NULL) {
> > + ops[i]->sym->m_dst != NULL &&
> > + ops[i]->sym->m_dst != ops[i]->sym->m_src) {
> > digest_appended[i] =
> > snow3g_digest_appended_in_src(ops[i]);
> > /* Clear unencrypted digest from
> > --
> > 2.51.0.windows.1
[-- Attachment #2: Type: text/html, Size: 6010 bytes --]
^ permalink raw reply
* [PATCH 2/2] net/i40e: activate alarm if interrupt delivery unavailable
From: Ciara Loftus @ 2026-06-30 14:16 UTC (permalink / raw)
To: dev; +Cc: Ciara Loftus, stable
In-Reply-To: <20260630141627.1035420-1-ciara.loftus@intel.com>
If interrupt registration fails eg. on FreeBSD with nic_uio, and Rx
queue interrupts are configured (intr_conf.rxq != 0), the interrupt
handler is never called and ICR0 events, including AdminQ messages
carrying link state notifications, are never processed. In polling
mode (rxq == 0) a periodic alarm already drains these events, but in
the interrupt-enabled configuration no such fallback existed.
This gap has always existed but was partially masked by the blocking link
status poll in the i40e dev_start function, which would delay some time
until the link was up before returning. In this case, the link status was
typically correct after dev_start. However after that delay was removed in
111965395b ("net/i40e: fix blocking link wait on device start"), the link
status was often wrong after dev_start, and there was no mechanism to
self-correct.
Rather than restoring the blocking wait removed by that commit which would
re-impose a startup delay on every platform, activate the existing periodic
alarm on platforms where interrupt delivery is unavailable.
Fixes: 111965395b ("net/i40e: fix blocking link wait on device start")
Cc: stable@dpdk.org
Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
---
drivers/net/intel/i40e/i40e_ethdev.c | 9 ++++++++-
drivers/net/intel/i40e/i40e_ethdev.h | 2 ++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/net/intel/i40e/i40e_ethdev.c b/drivers/net/intel/i40e/i40e_ethdev.c
index b2694cd33a..86eb7468c8 100644
--- a/drivers/net/intel/i40e/i40e_ethdev.c
+++ b/drivers/net/intel/i40e/i40e_ethdev.c
@@ -2579,7 +2579,11 @@ i40e_dev_start(struct rte_eth_dev *dev)
i40e_dev_link_update(dev, !rte_intr_allow_others(intr_handle));
pf->mac_config_on_link_up = !dev->data->dev_link.link_status;
/* enable uio intr after callback register */
- rte_intr_enable(intr_handle);
+ if (rte_intr_enable(intr_handle) != 0) {
+ pf->use_aq_polling = true;
+ rte_eal_alarm_set(I40E_ALARM_INTERVAL,
+ i40e_dev_alarm_handler, dev);
+ }
}
i40e_filter_restore(pf);
@@ -2625,6 +2629,9 @@ i40e_dev_stop(struct rte_eth_dev *dev)
if (dev->data->dev_conf.intr_conf.rxq == 0) {
rte_eal_alarm_cancel(i40e_dev_alarm_handler, dev);
rte_intr_enable(intr_handle);
+ } else if (pf->use_aq_polling) {
+ rte_eal_alarm_cancel(i40e_dev_alarm_handler, dev);
+ pf->use_aq_polling = false;
}
/* Disable all queues */
diff --git a/drivers/net/intel/i40e/i40e_ethdev.h b/drivers/net/intel/i40e/i40e_ethdev.h
index 5a009393b0..9d9bde6aeb 100644
--- a/drivers/net/intel/i40e/i40e_ethdev.h
+++ b/drivers/net/intel/i40e/i40e_ethdev.h
@@ -1193,6 +1193,8 @@ struct i40e_pf {
bool fw8_3gt;
/* MAC config needs re-applying when link first comes up */
bool mac_config_on_link_up;
+ /* true when interrupt path unavailable */
+ bool use_aq_polling;
struct i40e_vf_msg_cfg vf_msg_cfg;
uint64_t prev_rx_bytes;
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] net/ice: poll AdminQ if interrupt delivery unavailable
From: Ciara Loftus @ 2026-06-30 14:16 UTC (permalink / raw)
To: dev; +Cc: Ciara Loftus, stable
In-Reply-To: <20260630141627.1035420-1-ciara.loftus@intel.com>
If interrupt registration fails eg. on FreeBSD with nic_uio, the ice
interrupt handler is never called and AdminQ messages, including
unsolicited link state notifications, are never processed.
This gap has always existed but was partially masked by the blocking link
status poll in the ice dev_start function, which would delay some time
until the link was up before returning. In this case, the link status was
typically correct after dev_start. However after that delay was removed in
2dd7e99855 ("net/ice: revert fix link up when starting device"), the link
status was often wrong after dev_start, and there was no mechanism to
self-correct.
Rather than restoring the blocking wait removed by that commit which
would re-impose a startup delay on every platform, instead activate a
periodic alarm that drains the AdminQ on a 50ms interval, replicating the
role of the interrupt handler on platforms where interrupt delivery is not
supported.
Fixes: 2dd7e99855 ("net/ice: revert fix link up when starting device")
Cc: stable@dpdk.org
Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
---
drivers/net/intel/ice/ice_ethdev.c | 45 +++++++++++++++++++++++++++++-
drivers/net/intel/ice/ice_ethdev.h | 1 +
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
index 022d92a2f6..447742ff8d 100644
--- a/drivers/net/intel/ice/ice_ethdev.c
+++ b/drivers/net/intel/ice/ice_ethdev.c
@@ -3,6 +3,7 @@
*/
#include <rte_string_fns.h>
+#include <rte_alarm.h>
#include <ethdev_pci.h>
#include <ctype.h>
@@ -108,7 +109,11 @@ enum ice_link_state_on_close {
#define ICE_RSS_LUT_GLOBAL_QUEUE_NB 64
+/* Polling interval used when the interrupt path is unavailable */
+#define ICE_AQ_ALARM_INTERVAL 50000 /* us */
+
static int ice_dev_configure(struct rte_eth_dev *dev);
+static void ice_aq_alarm_handler(void *param);
static int ice_dev_start(struct rte_eth_dev *dev);
static int ice_dev_stop(struct rte_eth_dev *dev);
static int ice_dev_close(struct rte_eth_dev *dev);
@@ -1473,6 +1478,28 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
}
}
+/*
+ * Alarm-based AdminQ polling handler that drains the AdminQ to process
+ * async events, then re-arms itself.
+ *
+ * @param param
+ * The address of parameter (struct rte_eth_dev *) registered before.
+ *
+ * @return
+ * void
+ */
+static void
+ice_aq_alarm_handler(void *param)
+{
+ struct rte_eth_dev *dev = (struct rte_eth_dev *)param;
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+
+ if (!pf->adapter_stopped) {
+ ice_handle_aq_msg(dev);
+ rte_eal_alarm_set(ICE_AQ_ALARM_INTERVAL, ice_aq_alarm_handler, dev);
+ }
+}
+
/**
* Interrupt handler triggered by NIC for handling
* specific interrupt.
@@ -2913,6 +2940,11 @@ ice_dev_stop(struct rte_eth_dev *dev)
rte_intr_efd_disable(intr_handle);
rte_intr_vec_list_free(intr_handle);
+ if (pf->use_aq_polling) {
+ rte_eal_alarm_cancel(ice_aq_alarm_handler, dev);
+ pf->use_aq_polling = false;
+ }
+
pf->adapter_stopped = true;
dev->data->dev_started = 0;
@@ -4434,7 +4466,13 @@ ice_dev_start(struct rte_eth_dev *dev)
if (ice_rxq_intr_setup(dev))
return -EIO;
- rte_intr_enable(pci_dev->intr_handle);
+ /* Fall back to polling the AdminQ via an alarm on platforms where
+ * interrupt delivery is unavailable.
+ */
+ if (rte_intr_enable(pci_dev->intr_handle) != 0) {
+ pf->use_aq_polling = true;
+ rte_eal_alarm_set(ICE_AQ_ALARM_INTERVAL, ice_aq_alarm_handler, dev);
+ }
/* Enable receiving broadcast packets and transmitting packets */
ice_set_bit(ICE_PROMISC_BCAST_RX, pmask);
@@ -4497,6 +4535,11 @@ ice_dev_start(struct rte_eth_dev *dev)
for (i = 0; i < nb_txq; i++)
ice_tx_queue_stop(dev, i);
+ if (pf->use_aq_polling) {
+ rte_eal_alarm_cancel(ice_aq_alarm_handler, dev);
+ pf->use_aq_polling = false;
+ }
+
return -EIO;
}
diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
index 20e8a13fe9..86b256fc3f 100644
--- a/drivers/net/intel/ice/ice_ethdev.h
+++ b/drivers/net/intel/ice/ice_ethdev.h
@@ -599,6 +599,7 @@ struct ice_pf {
struct ice_eth_stats internal_stats;
bool offset_loaded;
bool adapter_stopped;
+ bool use_aq_polling; /* true when interrupt path unavailable */
struct ice_flow_list flow_list;
rte_spinlock_t flow_ops_lock;
bool init_link_up;
--
2.43.0
^ permalink raw reply related
* [PATCH 0/2] net/intel: fix link status when interrupt delivery unavailable
From: Ciara Loftus @ 2026-06-30 14:16 UTC (permalink / raw)
To: dev; +Cc: Ciara Loftus
On FreeBSD with nic_uio, interrupt registration fails silently and
hardware events, including link state notifications, are never processed
via the interrupt path.
For ice, this affects the typical polling-mode configuration: the
interrupt handler is never invoked, AdminQ messages go unprocessed,
and link status is not reliable. A periodic alarm is added to drain
the AdminQ when interrupt delivery is unavailable. For i40e, an
alarm already runs unconditionally in polling mode, so that
configuration is unaffected. The gap is the interrupt-mode
configuration, where the existing alarm was not activated as a
fallback when interrupt enable fails; this patch extends it to do so.
Ciara Loftus (2):
net/ice: poll AdminQ if interrupt delivery unavailable
net/i40e: activate alarm if interrupt delivery unavailable
drivers/net/intel/i40e/i40e_ethdev.c | 9 +++++-
drivers/net/intel/i40e/i40e_ethdev.h | 2 ++
drivers/net/intel/ice/ice_ethdev.c | 45 +++++++++++++++++++++++++++-
drivers/net/intel/ice/ice_ethdev.h | 1 +
4 files changed, 55 insertions(+), 2 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH v4 0/4] Wangxun fixes and new features
From: Stephen Hemminger @ 2026-06-30 14:15 UTC (permalink / raw)
To: Zaiyu Wang; +Cc: dev
In-Reply-To: <20260630111608.22196-1-zaiyuwang@trustnetic.com>
On Tue, 30 Jun 2026 19:16:00 +0800
Zaiyu Wang <zaiyuwang@trustnetic.com> wrote:
> This patchset introduces three new features and critical fixes for our
> recent release cycle.
>
> Patches 1-2 add support for UDP Segmentation Offload (USO) to improve
> large-packet transmission performance for UDP workloads.
>
> Patch 3 enables VFs to sense PF ifconfig down/up events, allowing
> better fault tolerance and fast recovery in virtualized environments.
>
> Patch 4 adds the missing VF support for the Amber-Lite 40G NICs, which
> was previously omitted in the initial integration.
Applied to net-next. Had to resolve a code merge conflict from earlier
changes in net-next and also release note conflicts.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox