* Re: [PATCH 06/10] test/devargs: check driver presence with public API
From: Bruce Richardson @ 2026-07-17 10:14 UTC (permalink / raw)
To: David Marchand; +Cc: dev
In-Reply-To: <CAJFAV8zaL+CVOaZ7THPc1yzjC-PYn072sUuSF25WngPdRNBLmw@mail.gmail.com>
On Fri, Jul 17, 2026 at 12:08:01PM +0200, David Marchand wrote:
> On Fri, 17 Jul 2026 at 11:56, Bruce Richardson
> <bruce.richardson@intel.com> wrote:
> >
> > On Fri, Jul 17, 2026 at 11:30:01AM +0200, David Marchand wrote:
> > > Let's avoid calling internal drivers API.
> > >
> > > Signed-off-by: David Marchand <david.marchand@redhat.com>
> > > ---
> > Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> >
> > One suggestion inline below.
> >
> > > app/test/test_devargs.c | 10 +++++++---
> > > 1 file changed, 7 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/app/test/test_devargs.c b/app/test/test_devargs.c
> > > index 0bd14c24ec..6c194ecfc6 100644
> > > --- a/app/test/test_devargs.c
> > > +++ b/app/test/test_devargs.c
> > > @@ -10,7 +10,7 @@
> > > #include <rte_common.h>
> > > #include <rte_devargs.h>
> > > #include <rte_kvargs.h>
> > > -#include <bus_driver.h>
> > > +#include <rte_bus.h>
> > > #include <rte_class.h>
> > >
> > > #include "test.h"
> > > @@ -167,14 +167,18 @@ test_valid_devargs(void)
> > > { "net_ring0,iface=test,path=/class/bus/,queues=1",
> > > 0, 0, 3, "vdev", "net_ring0", NULL },
> > > };
> > > - struct rte_bus *vdev_bus = rte_bus_find_by_name("vdev");
> > > + struct rte_devargs da;
> > > int ret;
> > >
> > > ret = test_valid_devargs_cases(list, RTE_DIM(list));
> > > - if (vdev_bus != NULL && vdev_bus->parse("net_ring0", NULL) == 0)
> > > +
> > > + memset(&da, 0, sizeof(da));
> > > + if (rte_devargs_parse(&da, "net_ring0") == 0)
> >
> > From what I/AI can see, there is nothing in this test case that requires
>
> (you could write it as "From what A?I can see")
>
> > the ring PMD specifically. I think it would be good if it were updated to
> > use net/null, so that we standardize on our basic unit tests only relying
> > upon that PMD, rather than requiring a variety of drivers. WDYT?
>
> There may be a hidden (capillotracté) reason.
> Passing path=/class/bus/ seems to be intended at catching errors in
> devargs parsing.
> No argument in net/null could accept such string.
>
Not a path string, no, though it can take other parameters. Do we
specifically want/need it to take a path string?
/Bruce
^ permalink raw reply
* Re: [PATCH] test/dispatcher: transfer impl_opaque field
From: Mattias Rönnblom @ 2026-07-17 10:40 UTC (permalink / raw)
To: Thomas Monjalon, Mattias Rönnblom, dev,
Jerin Jacob Kollanukkaran
Cc: Luka Jankovic, stable
In-Reply-To: <9491ee40-aa15-42b4-9b99-e47f29df217c@app.fastmail.com>
On 7/17/26 11:34, Thomas Monjalon wrote:
>> The dispatcher tests failed to transfer the impl_opaque rte_event
>> field between the dequeued and enqueued event, in violation with the
>> Eventdev API contract.
>>
>> Fixes: ecca8a0be606 ("lib: introduce dispatcher library")
>> Cc: stable@dpdk.org
>>
>> Signed-off-by: Mattias Rönnblom <mattias.ronnblom@ericsson.com>
>
> Applied, thanks.
>
> (better late than never)
Indeed! Thanks.
^ permalink raw reply
* Re: [PATCH 2/2] net/tap: drain queue FD in Rx interrupt mode
From: Stephen Hemminger @ 2026-07-17 12:31 UTC (permalink / raw)
To: Maxime Leroy; +Cc: dev, stable
In-Reply-To: <20260717095842.419130-3-maxime@leroys.fr>
[-- Attachment #1: Type: text/plain, Size: 5139 bytes --]
Don't use int as a boolean. Use bool. Ideally find unused pad hole for it
On Fri, Jul 17, 2026, 11:59 AM Maxime Leroy <maxime@leroys.fr> wrote:
> The tap Rx path is driven by a SIGIO trigger: pmd_rx_burst() returns
> without reading the queue fd unless tap_trigger, bumped by the O_ASYNC
> signal handler, has advanced. That avoids a readv() on every empty poll.
>
> In Rx interrupt mode the application blocks on the queue fd through epoll
> and polls only after a wakeup. The epoll wakeup and the trigger are
> distinct signals, so the burst can return 0 right after a wakeup because
> the trigger has not advanced, leaving the fd readable with no further
> edge: traffic stalls.
>
> When the port is configured for Rx interrupts, drain the fd
> unconditionally in the burst and do not arm the SIGIO trigger on the data
> queue fd. A data queue fd can be closed and recreated on a later setup,
> so the mode must stay constant to keep every fd on the same SIGIO policy:
> it is fixed at configure time and a later change is rejected. Close and
> reopen the port to switch modes.
>
> Fixes: 4870a8cdd968 ("net/tap: support Rx interrupt")
> Cc: stable@dpdk.org
>
> Signed-off-by: Maxime Leroy <maxime@leroys.fr>
> ---
> drivers/net/tap/rte_eth_tap.c | 18 +++++++++++++++++-
> drivers/net/tap/rte_eth_tap.h | 2 ++
> 2 files changed, 19 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
> index 99ede19e49..ea9ef76335 100644
> --- a/drivers/net/tap/rte_eth_tap.c
> +++ b/drivers/net/tap/rte_eth_tap.c
> @@ -254,6 +254,10 @@ tun_alloc(struct pmd_internals *pmd, int
> is_keepalive, int persistent)
> goto error;
> }
>
> + /* interrupt mode wakes through epoll on the data queue fd, not
> the SIGIO trigger */
> + if (pmd->intr_mode)
> + return fd;
> +
> /* Find a free realtime signal */
> for (signo = SIGRTMIN + 1; signo < SIGRTMAX; signo++) {
> struct sigaction sa;
> @@ -477,7 +481,7 @@ pmd_rx_burst(void *queue, struct rte_mbuf **bufs,
> uint16_t nb_pkts)
> unsigned long num_rx_bytes = 0;
> uint32_t trigger = tap_trigger;
>
> - if (trigger == rxq->trigger_seen)
> + if (!rxq->intr_mode && trigger == rxq->trigger_seen)
> return 0;
>
> process_private = rte_eth_devices[rxq->in_port].process_private;
> @@ -937,6 +941,16 @@ tap_dev_configure(struct rte_eth_dev *dev)
> struct pmd_internals *pmd = dev->data->dev_private;
> int intr_mode = !!dev->data->dev_conf.intr_conf.rxq;
>
> + /* The queue fd is created once and its SIGIO trigger is armed for
> poll
> + * mode only; the interrupt mode cannot be toggled on an existing
> port.
> + */
> + if (pmd->intr_mode_set && pmd->intr_mode != intr_mode) {
> + TAP_LOG(ERR,
> + "%s: Rx interrupt mode is fixed after configure,
> close and reopen the port to change it",
> + dev->device->name);
> + return -ENOTSUP;
> + }
> +
> if (dev->data->nb_rx_queues != dev->data->nb_tx_queues) {
> TAP_LOG(ERR,
> "%s: number of rx queues %d must be equal to
> number of tx queues %d",
> @@ -947,6 +961,7 @@ tap_dev_configure(struct rte_eth_dev *dev)
> }
>
> pmd->intr_mode = intr_mode;
> + pmd->intr_mode_set = 1;
>
> TAP_LOG(INFO, "%s: %s: TX configured queues number: %u",
> dev->device->name, pmd->name, dev->data->nb_tx_queues);
> @@ -1620,6 +1635,7 @@ tap_rx_queue_setup(struct rte_eth_dev *dev,
> rxq->queue_id = rx_queue_id;
> rxq->max_rx_segs = max_rx_segs;
> rxq->rxmode = &dev->data->dev_conf.rxmode;
> + rxq->intr_mode = internals->intr_mode;
>
> dev->data->rx_queues[rx_queue_id] = rxq;
> int fd = tap_setup_queue(dev, rx_queue_id, 1);
> diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
> index 3180719c34..abe22aac9f 100644
> --- a/drivers/net/tap/rte_eth_tap.h
> +++ b/drivers/net/tap/rte_eth_tap.h
> @@ -50,6 +50,7 @@ struct rx_queue {
> uint16_t queue_id; /* queue ID*/
> struct queue_stats stats; /* Stats for this RX queue */
> uint16_t max_rx_segs; /* max scatter segments per packet
> */
> + uint16_t intr_mode; /* 1 when Rx queue interrupts are
> used */
> struct rte_eth_rxmode *rxmode; /* RX features */
> struct rte_mbuf *pool; /* mbufs pool for this queue */
> struct tun_pi pi; /* packet info for iovecs */
> @@ -94,6 +95,7 @@ struct pmd_internals {
>
> struct rte_intr_handle *intr_handle; /* LSC interrupt
> handle. */
> int intr_mode; /* Rx queue interrupt mode */
> + int intr_mode_set; /* intr_mode locked after
> configure */
> int ka_fd; /* keep-alive file descriptor */
> struct rte_mempool *gso_ctx_mp; /* Mempool for GSO packets */
> };
> --
> 2.43.0
>
>
[-- Attachment #2: Type: text/html, Size: 6401 bytes --]
^ permalink raw reply
* Re: [PATCH 06/10] test/devargs: check driver presence with public API
From: David Marchand @ 2026-07-17 12:34 UTC (permalink / raw)
To: Bruce Richardson, Thomas Monjalon, Xueming(Steven) Li; +Cc: dev
In-Reply-To: <aloAfU6IdpZOMAlQ@bricha3-mobl1.ger.corp.intel.com>
On Fri, 17 Jul 2026 at 12:15, Bruce Richardson
<bruce.richardson@intel.com> wrote:
>
> On Fri, Jul 17, 2026 at 12:08:01PM +0200, David Marchand wrote:
> > On Fri, 17 Jul 2026 at 11:56, Bruce Richardson
> > <bruce.richardson@intel.com> wrote:
> > >
> > > On Fri, Jul 17, 2026 at 11:30:01AM +0200, David Marchand wrote:
> > > > Let's avoid calling internal drivers API.
> > > >
> > > > Signed-off-by: David Marchand <david.marchand@redhat.com>
> > > > ---
> > > Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> > >
> > > One suggestion inline below.
> > >
> > > > app/test/test_devargs.c | 10 +++++++---
> > > > 1 file changed, 7 insertions(+), 3 deletions(-)
> > > >
> > > > diff --git a/app/test/test_devargs.c b/app/test/test_devargs.c
> > > > index 0bd14c24ec..6c194ecfc6 100644
> > > > --- a/app/test/test_devargs.c
> > > > +++ b/app/test/test_devargs.c
> > > > @@ -10,7 +10,7 @@
> > > > #include <rte_common.h>
> > > > #include <rte_devargs.h>
> > > > #include <rte_kvargs.h>
> > > > -#include <bus_driver.h>
> > > > +#include <rte_bus.h>
> > > > #include <rte_class.h>
> > > >
> > > > #include "test.h"
> > > > @@ -167,14 +167,18 @@ test_valid_devargs(void)
> > > > { "net_ring0,iface=test,path=/class/bus/,queues=1",
> > > > 0, 0, 3, "vdev", "net_ring0", NULL },
> > > > };
> > > > - struct rte_bus *vdev_bus = rte_bus_find_by_name("vdev");
> > > > + struct rte_devargs da;
> > > > int ret;
> > > >
> > > > ret = test_valid_devargs_cases(list, RTE_DIM(list));
> > > > - if (vdev_bus != NULL && vdev_bus->parse("net_ring0", NULL) == 0)
> > > > +
> > > > + memset(&da, 0, sizeof(da));
> > > > + if (rte_devargs_parse(&da, "net_ring0") == 0)
> > >
> > > From what I/AI can see, there is nothing in this test case that requires
> >
> > (you could write it as "From what A?I can see")
> >
> > > the ring PMD specifically. I think it would be good if it were updated to
> > > use net/null, so that we standardize on our basic unit tests only relying
> > > upon that PMD, rather than requiring a variety of drivers. WDYT?
> >
> > There may be a hidden (capillotracté) reason.
> > Passing path=/class/bus/ seems to be intended at catching errors in
> > devargs parsing.
> > No argument in net/null could accept such string.
> >
> Not a path string, no, though it can take other parameters. Do we
> specifically want/need it to take a path string?
This is some special case tested for "legacy".
I prefer asking Xueming and Thomas what the intention was.
--
David Marchand
^ permalink raw reply
* Re: [PATCH v3 6/8] doc: add missing globs to doxy-api.conf.in
From: Thomas Monjalon @ 2026-07-17 12:49 UTC (permalink / raw)
To: Marat Khalili
Cc: dev@dpdk.org, bruce.richardson@intel.com, Konstantin Ananyev
In-Reply-To: <3146b71600564d1fb36690091a1c5d6f@huawei.com>
17/07/2026 10:18, Marat Khalili:
> From: Thomas Monjalon <thomas@monjalon.net>
> > 07/07/2026 10:50, Marat Khalili:
> > > Public headers starting from `cmdline` (except for `cmdline.h` itself)
> > > and `bpf_def.h` were not previously included in the documentation build.
> >
> > Why bpf_def.h is not prefixed with "rte_" ?
>
> I don't know historical reasons, but I would add this "rte_" prefix. As far as I understand 26.11 is the next opportunity to do it, right?
Absolutely
^ permalink raw reply
* Re: [PATCH v3 2/8] doc: document rte_os.h
From: Thomas Monjalon @ 2026-07-17 12:50 UTC (permalink / raw)
To: Marat Khalili; +Cc: dev@dpdk.org, bruce.richardson@intel.com
In-Reply-To: <d118b8d80ba64106ad538a5d214f1f34@huawei.com>
17/07/2026 10:34, Marat Khalili:
> > I don't understand the benefit of adding an empty file.
> > rte_os.h files are defining standard functions which do not need documentation.
>
> It was easier to add a stub file explaining the file purpose than to add an exception just for it, but sure we can exclude it explicitly.
I think an exception would be appropriate, there is nothing to document.
^ permalink raw reply
* Re: [PATCH v3 8/8] doc: add missing headers to doxy-api-index.md
From: Thomas Monjalon @ 2026-07-17 12:51 UTC (permalink / raw)
To: Marat Khalili; +Cc: dev@dpdk.org, bruce.richardson@intel.com
In-Reply-To: <5803e998c8404b5387ba9ef0bcb530b9@huawei.com>
17/07/2026 10:30, Marat Khalili:
> > I'm not sure we should add all in this index,
> > especially the *_trace.h which are not really interesting.
>
> These are all public headers, all of them have a man page and an HTML documentation page generated. Yet when a user clicks `API Reference` on dpdk.org they arrive here: https://doc.dpdk.org/api/ . Without listing all public headers this page is meaningless and misleading; web crawlers could also be confused by lack of direct access to inner pages.
>
> I don't know specifics of *_trace.h pages, maybe they are special and should not be exposed. In this case we can add an exception, although I'd rather put them in a separate section explaining why they are special and not worth bothering with. After all users might be similarly puzzled.
Trace functions are not directly called by users.
Yes they are exposed, but no they are not part of the Application Programming Interface :-)
^ permalink raw reply
* Re: [PATCH 06/10] test/devargs: check driver presence with public API
From: Thomas Monjalon @ 2026-07-17 12:56 UTC (permalink / raw)
To: David Marchand; +Cc: Bruce Richardson, Xueming(Steven) Li, dev
In-Reply-To: <CAJFAV8yr_4Wttt7sDUVzqYnAsMYxoTRg3FcNbZefpYzs1v9e0A@mail.gmail.com>
17/07/2026 14:34, David Marchand:
> On Fri, 17 Jul 2026 at 12:15, Bruce Richardson
> <bruce.richardson@intel.com> wrote:
> >
> > On Fri, Jul 17, 2026 at 12:08:01PM +0200, David Marchand wrote:
> > > On Fri, 17 Jul 2026 at 11:56, Bruce Richardson
> > > <bruce.richardson@intel.com> wrote:
> > > >
> > > > On Fri, Jul 17, 2026 at 11:30:01AM +0200, David Marchand wrote:
> > > > > Let's avoid calling internal drivers API.
> > > > >
> > > > > Signed-off-by: David Marchand <david.marchand@redhat.com>
> > > > > ---
> > > > Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> > > >
> > > > One suggestion inline below.
> > > >
> > > > > app/test/test_devargs.c | 10 +++++++---
> > > > > 1 file changed, 7 insertions(+), 3 deletions(-)
> > > > >
> > > > > diff --git a/app/test/test_devargs.c b/app/test/test_devargs.c
> > > > > index 0bd14c24ec..6c194ecfc6 100644
> > > > > --- a/app/test/test_devargs.c
> > > > > +++ b/app/test/test_devargs.c
> > > > > @@ -10,7 +10,7 @@
> > > > > #include <rte_common.h>
> > > > > #include <rte_devargs.h>
> > > > > #include <rte_kvargs.h>
> > > > > -#include <bus_driver.h>
> > > > > +#include <rte_bus.h>
> > > > > #include <rte_class.h>
> > > > >
> > > > > #include "test.h"
> > > > > @@ -167,14 +167,18 @@ test_valid_devargs(void)
> > > > > { "net_ring0,iface=test,path=/class/bus/,queues=1",
> > > > > 0, 0, 3, "vdev", "net_ring0", NULL },
> > > > > };
> > > > > - struct rte_bus *vdev_bus = rte_bus_find_by_name("vdev");
> > > > > + struct rte_devargs da;
> > > > > int ret;
> > > > >
> > > > > ret = test_valid_devargs_cases(list, RTE_DIM(list));
> > > > > - if (vdev_bus != NULL && vdev_bus->parse("net_ring0", NULL) == 0)
> > > > > +
> > > > > + memset(&da, 0, sizeof(da));
> > > > > + if (rte_devargs_parse(&da, "net_ring0") == 0)
> > > >
> > > > From what I/AI can see, there is nothing in this test case that requires
> > >
> > > (you could write it as "From what A?I can see")
> > >
> > > > the ring PMD specifically. I think it would be good if it were updated to
> > > > use net/null, so that we standardize on our basic unit tests only relying
> > > > upon that PMD, rather than requiring a variety of drivers. WDYT?
> > >
> > > There may be a hidden (capillotracté) reason.
> > > Passing path=/class/bus/ seems to be intended at catching errors in
> > > devargs parsing.
> > > No argument in net/null could accept such string.
> > >
> > Not a path string, no, though it can take other parameters. Do we
> > specifically want/need it to take a path string?
>
> This is some special case tested for "legacy".
> I prefer asking Xueming and Thomas what the intention was.
I have no idea about the original intent.
If it's unclear, it means it can be removed :-)
^ permalink raw reply
* Re: [PATCH] Updated skeleton example code to better emit error
From: Thomas Monjalon @ 2026-07-17 13:29 UTC (permalink / raw)
To: Arin Kharkar; +Cc: dev
In-Reply-To: <20260512192834.130651-1-arinkharkar@gmail.com>
12/05/2026 21:28, Arin Kharkar:
> Previously, the program would check if the number of ports is even and if not, would exit with an error stating "the number of ports must be even". To provide more information, the program now states how many ports were found. This is especially helpful if 0 ports were found, as it explicitly states DPDK wasn't able to find any ports, rather than making the user troubleshoot this.
> ---
> examples/skeleton/basicfwd.c | 6 ++----
> 1 file changed, 2 insertions(+), 4 deletions(-)
>
> diff --git a/examples/skeleton/basicfwd.c b/examples/skeleton/basicfwd.c
> index 133293cf15..381e4d4273 100644
> --- a/examples/skeleton/basicfwd.c
> +++ b/examples/skeleton/basicfwd.c
> @@ -188,11 +188,9 @@ main(int argc, char *argv[])
> /* Check that there is an even number of ports to send/receive on. */
> nb_ports = rte_eth_dev_count_avail();
> if (nb_ports < 2 || (nb_ports & 1))
> - rte_exit(EXIT_FAILURE, "Error: number of ports must be even\n");
> + rte_exit(EXIT_FAILURE, "Error: number of ports must be even, found %u ports\n", nb_ports);
>
> - /* Creates a new mempool in memory to hold the mbufs. */
> -
> - /* Allocates mempool to hold the mbufs. 8< */
> + /* Creates and allocates a mempool to hold the mbufs. 8< */
Changing this comment requires changing the doc referencing it.
I've made this change, added your signoff and other details,
and applied, thanks.
^ permalink raw reply
* Re: [PATCH] usertools/telemetry: load aliases from config directory
From: Thomas Monjalon @ 2026-07-17 13:36 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev, Robin Jarry, Chengwen Feng
In-Reply-To: <aln3w5hRyGXhJas1@bricha3-mobl1.ger.corp.intel.com>
17/07/2026 11:37, Bruce Richardson:
> On Fri, Jul 17, 2026 at 11:16:03AM +0200, Thomas Monjalon wrote:
> > The default path of the telemetry alias file
> > was at the top level of the home directory.
> >
> > Load aliases from the DPDK configuration directory instead,
> > using $XDG_CONFIG_HOME/dpdk/telemetry_aliases
> > or $HOME/.config/dpdk/telemetry_aliases.
> >
> > Keep --alias-file for users who want to provide an explicit path.
> >
> > Fixes: 410d498412e4 ("usertools/telemetry: support aliases for long commands")
> >
> > Signed-off-by: Thomas Monjalon <thomas@monjalon.net>
>
> Tested without moving my existing alias file - no aliases loaded. Then
> tested after moving to .config/dpdk/ and it worked fine.
>
> Doc changes LGTM too.
>
> Tested-by: Bruce Richardson <bruce.richardson@intel.com>
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>
Acked-by: Robin Jarry <rjarry@redhat.com>
Acked-by: Chengwen Feng <fengchengwen@huawei.com>
Applied, thanks for testing.
^ permalink raw reply
* Re: [PATCH v3 0/8] doc: clean-up links in guides
From: Thomas Monjalon @ 2026-07-17 13:39 UTC (permalink / raw)
To: David Marchand, Chengwen Feng; +Cc: dev
In-Reply-To: <CAJFAV8w5W+VP3+vvBuYZWMnHKSftnxq7aYBOFVoa-rWm-FpP1w@mail.gmail.com>
16/07/2026 17:29, David Marchand:
> On Thu, 16 Jul 2026 at 12:36, Thomas Monjalon <thomas@monjalon.net> wrote:
> >
> > Links and references to other guide sections had multiple issues,
> > all solved in this series.
>
> For the series:
> Acked-by: David Marchand <david.marchand@redhat.com>
Series-acked-by: Chengwen Feng <fengchengwen@huawei.com>
Applied
^ permalink raw reply
* [PATCH v1] dts: resolve variance in performance reporting
From: Andrew Bailey @ 2026-07-17 14:49 UTC (permalink / raw)
To: patrickrobb1997, luca.vizzarro, dev; +Cc: knimoji, llavoie, Andrew Bailey
Currently, the single core forward performance test suite reports
variable results with a standard deviation of ~10Mpps. Currently,
traffic is measured over 30 seconds a single time and the stats are
reported from this metric. This commit reduces the time traffic is sent
to only 5 seconds but runs numerous times to take an average and limit
variance to a standard deviation of ~1Mpps.
Bugzilla ID: 1969
Fixes: d77d7f04f24c ("dts: add single-core performance test suite")
Signed-off-by: Andrew Bailey <abailey@iol.unh.edu>
---
.../TestSuite_single_core_forward_perf.py | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/dts/tests/TestSuite_single_core_forward_perf.py b/dts/tests/TestSuite_single_core_forward_perf.py
index acdf8ae2f6..47cb592692 100644
--- a/dts/tests/TestSuite_single_core_forward_perf.py
+++ b/dts/tests/TestSuite_single_core_forward_perf.py
@@ -53,12 +53,13 @@ def set_up_suite(self):
self.test_parameters = self.config.test_parameters
self.delta_tolerance = self.config.delta_tolerance
- def _transmit(self, testpmd: TestPmd, frame_size: int) -> float:
+ def _transmit(self, testpmd: TestPmd, frame_size: int, repetitions: int = 1) -> float:
"""Create a testpmd session with every rule in the given list, verify jump behavior.
Args:
testpmd: The testpmd shell to use for forwarding packets.
frame_size: The size of the frame to transmit.
+ repetitions: The number of times to rerun the transmission.
Returns:
The MPPS (millions of packets per second) forwarded by the SUT.
@@ -71,13 +72,13 @@ def _transmit(self, testpmd: TestPmd, frame_size: int) -> float:
)
testpmd.start()
+ rx_avg: float = 0
- # Transmit for 30 seconds.
- stats = assess_performance_by_packet(packet=packet, duration=30)
-
- rx_mpps = stats.rx_pps / 1_000_000
-
- return rx_mpps
+ for _ in range(repetitions):
+ # Transmit for 5 seconds.
+ stats = assess_performance_by_packet(packet=packet, duration=5)
+ rx_avg += stats.rx_pps
+ return rx_avg / (repetitions * 1_000_000)
def _produce_stats_table(self, test_parameters: list[dict[str, int | float]]) -> None:
"""Display performance results in table format and write to structured JSON file.
@@ -131,7 +132,9 @@ def single_core_forward_perf(self) -> None:
with TestPmd(
**driver_specific_testpmd_args,
) as testpmd:
- params["measured_mpps"] = round(self._transmit(testpmd, frame_size), 3)
+ params["measured_mpps"] = round(
+ self._transmit(testpmd, frame_size, repetitions=5), 3
+ )
params["performance_delta"] = round(
(float(params["measured_mpps"]) - float(params["expected_mpps"]))
/ float(params["expected_mpps"]),
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v1] dts: resolve variance in performance reporting
From: Luca Vizzarro @ 2026-07-17 16:23 UTC (permalink / raw)
To: Andrew Bailey, patrickrobb1997, dev; +Cc: knimoji, llavoie
In-Reply-To: <20260717144933.515139-1-abailey@iol.unh.edu>
Applied to next-dts with Claude's suggestion and a check to ensure that
repetitions is always greater than 0.
Thanks!
^ permalink raw reply
* RE: mempool cache change
From: Kishore Padmanabha @ 2026-07-17 17:10 UTC (permalink / raw)
To: Bruce Richardson
Cc: Morten Brørup, fengchengwen, Thomas Monjalon, dev,
Wisam Jaddo
In-Reply-To: <aliq6kqGI2TfyFoH@bricha3-mobl1.ger.corp.intel.com>
[-- Attachment #1: Type: text/plain, Size: 8443 bytes --]
Hi Bruce,
The below patch works fine for us. We tested all the different packet sizes.
Thanks for the patch. Do you want to push this patch since it is not
changing the ABI/API?
Rgds,
Kishore
-----Original Message-----
From: Bruce Richardson <bruce.richardson@intel.com>
Sent: Thursday, July 16, 2026 5:57 AM
To: Kishore Padmanabha <kishore.padmanabha@broadcom.com>
Cc: Morten Brørup <mb@smartsharesystems.com>; fengchengwen
<fengchengwen@huawei.com>; Thomas Monjalon <thomas@monjalon.net>;
dev@dpdk.org; Wisam Jaddo <wisamm@nvidia.com>
Subject: Re: mempool cache change
On Wed, Jul 15, 2026 at 11:12:59AM -0400, Kishore Padmanabha wrote:
> On Wed, Jul 15, 2026 at 5:02 AM Morten Brørup
> <[1]mb@smartsharesystems.com> wrote:
>
> > From: fengchengwen [mailto:[2]fengchengwen@huawei.com]
> > Sent: Wednesday, 15 July 2026 10.12
> >
> > On 7/15/2026 4:08 AM, Morten Brørup wrote:
> > > Hi Kishore,
> > >
> > > For your testing purposes, please follow the guidance provided
> to
> > Wisam Jaddo:
> > >
> >
> [3]https://inbox.dpdk.org/dev/98CBD80474FA8B44BF855DF32C47DC35F65964
> @smart
> > [4]server.smartshare.dk/
> >
> > We need to recompile in this case. We should try to avoid
> > recompilation.
> > I think it is necessary to adjust RTE_MEMPOOL_CACHE_MAX_SIZE to
> 768 as
> > a default configuration.
> Changing RTE_MEMPOOL_CACHE_MAX_SIZE breaks both the API and the ABI;
> so it has to be done when building locally, where API/ABI breakage
> is acceptable.
> For DPDK 26.11, where API/ABI breakage is acceptable, we can discuss
> increasing the default from 512 to a higher value. I do have some
> input to that discussion, but let's postpone it until after DPDK
> 26.07 has been released.
>
> We should increase this value to avoid performance degradation, as
> users may not realize they need to change it. We do not have do it
> right away for 26.07 release but we should it right after the
> release.
>
Out of interest, does adjusting the 50% fill/flush threshold to be a 75%
one, i.e. fill to 75% rather than 50%, flush to 25% rather than 50%, help at
all? Draft patch below, can you test it quickly, perhaps?
/Bruce
diff --git a/lib/mempool/rte_mempool.h b/lib/mempool/rte_mempool.h index
50d958c7c6..2526a23903 100644
--- a/lib/mempool/rte_mempool.h
+++ b/lib/mempool/rte_mempool.h
@@ -1417,6 +1417,7 @@ rte_mempool_do_generic_put(struct rte_mempool *mp,
void * const *obj_table,
unsigned int n, struct rte_mempool_cache *cache)
{
void **cache_objs;
+ uint32_t quarter, flush;
/* No cache provided? */
if (unlikely(cache == NULL))
@@ -1426,30 +1427,39 @@ rte_mempool_do_generic_put(struct rte_mempool *mp,
void * const *obj_table,
RTE_MEMPOOL_CACHE_STAT_ADD(cache, put_bulk, 1);
RTE_MEMPOOL_CACHE_STAT_ADD(cache, put_objs, n);
+ /* A quarter (25%) of the cache size, computed with a shift to avoid
+ * a divide. Draining the cache down to this level on overflow
leaves
+ * room for a burst of up to (size - quarter), i.e. three quarters
+ * (75%), of the cache size.
+ */
+ quarter = cache->size >> 2;
+
__rte_assume(cache->size <= RTE_MEMPOOL_CACHE_MAX_SIZE);
- __rte_assume(cache->size / 2 <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
+ __rte_assume(quarter <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
__rte_assume(cache->len <= RTE_MEMPOOL_CACHE_MAX_SIZE);
__rte_assume(cache->len <= cache->size);
if (likely(cache->len + n <= cache->size)) {
/* Sufficient room in the cache for the objects. */
cache_objs = &cache->objs[cache->len];
cache->len += n;
- } else if (n <= cache->size / 2) {
+ } else if (n <= cache->size - quarter) {
/*
* The number of objects is within the cache bounce buffer
limit,
* but - as detected by the comparison above - the cache has
* insufficient room for them.
* Flush the cache to the backend to make room for the
objects;
- * flush (size / 2) objects from the bottom of the cache,
where
- * objects are less hot, and move down the remaining
objects, which
- * are more hot, from the upper half of the cache.
+ * flush objects from the bottom of the cache, where objects
are
+ * less hot, draining it down to a quarter (25%) of its
size, and
+ * move down the remaining quarter of objects, which are
more hot,
+ * from the upper part of the cache.
*/
- __rte_assume(cache->len > cache->size / 2);
- rte_mempool_ops_enqueue_bulk(mp, &cache->objs[0],
cache->size / 2);
- rte_memcpy(&cache->objs[0], &cache->objs[cache->size / 2],
- sizeof(void *) * (cache->len - cache->size /
2));
- cache_objs = &cache->objs[cache->len - cache->size / 2];
- cache->len = cache->len - cache->size / 2 + n;
+ __rte_assume(cache->len > quarter);
+ flush = cache->len - quarter;
+ rte_mempool_ops_enqueue_bulk(mp, &cache->objs[0], flush);
+ rte_memcpy(&cache->objs[0], &cache->objs[flush],
+ sizeof(void *) * quarter);
+ cache_objs = &cache->objs[quarter];
+ cache->len = quarter + n;
} else {
/* The request itself is too big for the cache. */
goto driver_enqueue_stats_incremented; @@ -1557,6 +1567,7 @@
rte_mempool_do_generic_get(struct rte_mempool *mp, void **obj_table,
int ret;
unsigned int remaining;
uint32_t index, len;
+ uint32_t quarter, fill;
void **cache_objs;
/* No cache provided? */
@@ -1592,13 +1603,23 @@ rte_mempool_do_generic_get(struct rte_mempool *mp,
void **obj_table,
for (index = 0; index < len; index++)
*obj_table++ = *--cache_objs;
+ /* A quarter (25%) of the cache size, computed with a shift to avoid
+ * a divide; 'fill' is the complementary three quarters (75%), which
+ * is the amount fetched from the backend to fill the cache up to
75%,
+ * and also the burst limit for this bounce buffer (since the cache
+ * was just fully drained above, up to 'fill' objects can be filled
+ * and handed back to the caller in one go).
+ */
+ quarter = cache->size >> 2;
+ fill = cache->size - quarter;
+
/* Dequeue below would exceed the cache bounce buffer limit? */
- __rte_assume(cache->size / 2 <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
- if (unlikely(remaining > cache->size / 2))
+ __rte_assume(fill <= RTE_MEMPOOL_CACHE_MAX_SIZE);
+ if (unlikely(remaining > fill))
goto driver_dequeue;
- /* Fill the cache from the backend; fetch (size / 2) objects. */
- ret = rte_mempool_ops_dequeue_bulk(mp, cache->objs, cache->size /
2);
+ /* Fill the cache from the backend, up to 75% of its size. */
+ ret = rte_mempool_ops_dequeue_bulk(mp, cache->objs, fill);
if (unlikely(ret < 0)) {
/*
* We are buffer constrained, and not able to fetch all
that.
@@ -1612,11 +1633,10 @@ rte_mempool_do_generic_get(struct rte_mempool *mp,
void **obj_table,
RTE_MEMPOOL_CACHE_STAT_ADD(cache, get_success_bulk, 1);
RTE_MEMPOOL_CACHE_STAT_ADD(cache, get_success_objs, n);
- __rte_assume(cache->size / 2 <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
- __rte_assume(remaining <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
- __rte_assume(remaining <= cache->size / 2);
- cache_objs = &cache->objs[cache->size / 2];
- cache->len = cache->size / 2 - remaining;
+ __rte_assume(fill <= RTE_MEMPOOL_CACHE_MAX_SIZE);
+ __rte_assume(remaining <= fill);
+ cache_objs = &cache->objs[fill];
+ cache->len = fill - remaining;
for (index = 0; index < remaining; index++)
*obj_table++ = *--cache_objs;
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 5493 bytes --]
^ permalink raw reply
* RE: [PATCH 01/10] bbdev: fix stats aggregation from queues
From: Chautru, Nicolas @ 2026-07-17 17:22 UTC (permalink / raw)
To: David Marchand, dev@dpdk.org
Cc: stable@dpdk.org, Maxime Coquelin, Akhil Goyal
In-Reply-To: <20260717093006.229370-2-david.marchand@redhat.com>
Hi David,
We use the same structure for device and queue level stats for simplicity.
But really bringing these 2 at device level as an aggregate level do not really make much sense at system evel (ie sum of of queue depth is irrelevant). The other ones are relevant.
I don't think we should change this, it would just create confusion with users. That's a nack to me, let me know if unclear.
Thanks
Nic
> -----Original Message-----
> From: David Marchand <david.marchand@redhat.com>
> Sent: Friday, July 17, 2026 2:30 AM
> To: dev@dpdk.org
> Cc: stable@dpdk.org; Chautru, Nicolas <nicolas.chautru@intel.com>; Maxime
> Coquelin <maxime.coquelin@redhat.com>; Akhil Goyal <gakhil@marvell.com>
> Subject: [PATCH 01/10] bbdev: fix stats aggregation from queues
>
> The device stats retrieval was missing aggregation of enqueue_status_count,
> acc_offload_cycles, and enqueue_depth_avail from per-queue statistics.
>
> Fixes: 4f08028c5e24 ("bbdev: expose queue related warning and status")
> Cc: stable@dpdk.org
>
> Signed-off-by: David Marchand <david.marchand@redhat.com>
> ---
> lib/bbdev/rte_bbdev.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/lib/bbdev/rte_bbdev.c b/lib/bbdev/rte_bbdev.c index
> ea644c1f9b..90095578cb 100644
> --- a/lib/bbdev/rte_bbdev.c
> +++ b/lib/bbdev/rte_bbdev.c
> @@ -745,7 +745,7 @@ rte_bbdev_queue_stop(uint16_t dev_id, uint16_t
> queue_id) static void get_stats_from_queues(struct rte_bbdev *dev, struct
> rte_bbdev_stats *stats) {
> - unsigned int q_id;
> + unsigned int i, q_id;
> for (q_id = 0; q_id < dev->data->num_queues; q_id++) {
> struct rte_bbdev_stats *q_stats =
> &dev->data->queues[q_id].queue_stats;
> @@ -756,6 +756,10 @@ get_stats_from_queues(struct rte_bbdev *dev, struct
> rte_bbdev_stats *stats)
> stats->dequeue_err_count += q_stats->dequeue_err_count;
> stats->enqueue_warn_count += q_stats->enqueue_warn_count;
> stats->dequeue_warn_count += q_stats->dequeue_warn_count;
> + for (i = 0; i < RTE_BBDEV_ENQ_STATUS_SIZE_MAX; i++)
> + stats->enqueue_status_count[i] += q_stats-
> >enqueue_status_count[i];
> + stats->acc_offload_cycles += q_stats->acc_offload_cycles;
> + stats->enqueue_depth_avail += q_stats->enqueue_depth_avail;
> }
> rte_bbdev_log_debug("Got stats on %u", dev->data->dev_id); }
> --
> 2.54.0
^ permalink raw reply
* [PATCH] net/iavf: harden reset recovery and data path on link flap
From: Anurag Mandal @ 2026-07-17 21:15 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, vladimir.medvedkin, ciara.loftus, Anurag Mandal
During PF-initiated reset or a remote/ToR switch link-flap, the VF
might miss the reset event, race on the no_poll gate, leak in-flight
Tx descriptors, and stay down if dev_start ran before the PF VSI was
ready.
This patch builds on the earlier reset-recovery fixes with the
following:
- Reset detection: complement the ARQLEN1 check with VFGEN_RSTAT
(VIRTCHNL_VFR_INPROGRESS) and poll at a 5 ms interval, matching the
kernel iavf driver, so fast ARQ flips are not missed. When the VFR
is still not observed, recover anyway instead of bailing out,
keeping PF and VF state in sync.
- no_poll: make the flag atomic (RTE_ATOMIC) with release/acquire
ordering so the data-plane lcores observe gate changes reliably.
- Tx drain: add iavf_dev_tx_drain() to flush in-flight Tx descriptors
on link-down and impending-reset events before teardown, preventing
MDD events and descriptor leaks.
- Deferred start: when dev_start fails during recovery (PF VSI inactive),
defer it via start_pending and resume on the next link-up event so the
VF comes back without manual intervention.
- AdminQ: discard zeroed (opcode 0) descriptors seen during PF-initiated
resets to avoid the "Request 0 is not supported" log flood.
Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>
---
drivers/net/intel/iavf/iavf.h | 4 +-
drivers/net/intel/iavf/iavf_ethdev.c | 88 +++++++++++++++++++++++-----
drivers/net/intel/iavf/iavf_rxtx.c | 77 +++++++++++++++++++++++-
drivers/net/intel/iavf/iavf_rxtx.h | 6 ++
drivers/net/intel/iavf/iavf_vchnl.c | 40 ++++++++++++-
5 files changed, 195 insertions(+), 20 deletions(-)
diff --git a/drivers/net/intel/iavf/iavf.h b/drivers/net/intel/iavf/iavf.h
index 293adaf6c9..037bc8436f 100644
--- a/drivers/net/intel/iavf/iavf.h
+++ b/drivers/net/intel/iavf/iavf.h
@@ -293,6 +293,7 @@ struct iavf_info {
bool in_reset_recovery;
bool reset_pending;
bool pf_reset_in_progress;
+ bool start_pending;
uint32_t ptp_caps;
rte_spinlock_t phc_time_aq_lock;
@@ -391,7 +392,7 @@ struct iavf_adapter {
alignas(RTE_CACHE_LINE_MIN_SIZE) uint32_t ptype_tbl[IAVF_MAX_PKT_TYPE];
bool stopped;
bool closed;
- bool no_poll;
+ RTE_ATOMIC(bool)no_poll;
enum iavf_rx_func_type rx_func_type;
enum iavf_tx_func_type tx_func_type;
uint16_t fdir_ref_cnt;
@@ -533,4 +534,5 @@ void iavf_handle_hw_reset(struct rte_eth_dev *dev, bool vf_initiated_reset);
void iavf_set_no_poll(struct iavf_adapter *adapter, bool link_change);
bool is_iavf_supported(struct rte_eth_dev *dev);
void iavf_hash_uninit(struct iavf_adapter *ad);
+void iavf_resume_pending_start(struct rte_eth_dev *dev);
#endif /* _IAVF_ETHDEV_H_ */
diff --git a/drivers/net/intel/iavf/iavf_ethdev.c b/drivers/net/intel/iavf/iavf_ethdev.c
index d601ec3b6a..eaabef41ed 100644
--- a/drivers/net/intel/iavf/iavf_ethdev.c
+++ b/drivers/net/intel/iavf/iavf_ethdev.c
@@ -3212,7 +3212,9 @@ iavf_dev_close(struct rte_eth_dev *dev)
/* remove RSS configuration */
iavf_hash_uninit(adapter);
- iavf_flow_flush(dev, NULL);
+ /* Skip the virtchnl-emitting teardow on a PF-initiated reset */
+ if (!vf->pf_reset_in_progress)
+ iavf_flow_flush(dev, NULL);
iavf_flow_uninit(adapter);
/*
@@ -3335,8 +3337,27 @@ iavf_dev_reset(struct rte_eth_dev *dev)
static inline bool
iavf_is_reset(struct iavf_hw *hw)
{
- return !(IAVF_READ_REG(hw, IAVF_VF_ARQLEN1) &
- IAVF_VF_ARQLEN1_ARQENABLE_MASK);
+ uint32_t rstat;
+
+ /* ARQ has been disabled by the PF as part of the VFR. */
+ if (!(IAVF_READ_REG(hw, IAVF_VF_ARQLEN1) &
+ IAVF_VF_ARQLEN1_ARQENABLE_MASK))
+ return true;
+
+ /*
+ * VFGEN_RSTAT reports VIRTCHNL_VFR_INPROGRESS.
+ * At times, the PF flips ARQENABLE so quickly
+ * around a VFR that the ARQLEN1 sample window
+ * misses it. Using VFGEN_RSTAT, as a
+ * complementary indicator, help to prevent
+ * from declaring "reset not start" when a
+ * reset really did happen.
+ */
+ rstat = (IAVF_READ_REG(hw, IAVF_VFGEN_RSTAT) &
+ IAVF_VFGEN_RSTAT_VFR_STATE_MASK) >>
+ IAVF_VFGEN_RSTAT_VFR_STATE_SHIFT;
+
+ return rstat == VIRTCHNL_VFR_INPROGRESS;
}
static bool
@@ -3345,11 +3366,17 @@ iavf_is_reset_detected(struct iavf_adapter *adapter)
struct iavf_hw *hw = IAVF_DEV_PRIVATE_TO_HW(adapter);
int i;
- /* poll until we see the reset actually happen */
- for (i = 0; i < IAVF_RESET_DETECTED_CNT; i++) {
+ /*
+ * Poll until we see the reset actually happen.
+ * Poll every 5 ms to catch the fast ARQ flips.
+ * Total wait time is unchanged (~10 s) since
+ * the count is scale up by the same factor
+ * that the per-iteration delay shrinks.
+ */
+ for (i = 0; i < IAVF_RESET_DETECTED_CNT * 4; i++) {
if (iavf_is_reset(hw))
return true;
- rte_delay_ms(20);
+ rte_delay_us(5000);
}
return false;
@@ -3400,14 +3427,13 @@ iavf_handle_hw_reset(struct rte_eth_dev *dev, bool vf_initiated_reset)
if (!dev->data->dev_started)
return;
- if (!iavf_is_reset_detected(adapter)) {
- PMD_DRV_LOG(DEBUG, "reset not start");
- return;
- }
+ if (!iavf_is_reset_detected(adapter))
+ PMD_DRV_LOG(WARNING, "VFR not observed; recovering anyway");
}
vf->in_reset_recovery = true;
vf->pf_reset_in_progress = !vf_initiated_reset;
+ vf->start_pending = false;
iavf_set_no_poll(adapter, false);
/* Call the pre reset callback */
@@ -3428,10 +3454,17 @@ iavf_handle_hw_reset(struct rte_eth_dev *dev, bool vf_initiated_reset)
if (!vf_initiated_reset || restart_device) {
/* start the device */
ret = iavf_dev_start(dev);
- if (ret)
- goto error;
-
- dev->data->dev_started = 1;
+ if (ret == 0) {
+ dev->data->dev_started = 1;
+ } else {
+ PMD_DRV_LOG(WARNING,
+ "dev_start failed during reset recovery (rc=%d);"
+ "deferring to next link-up event",
+ ret);
+ vf->start_pending = true;
+ dev->data->dev_started = 0;
+ ret = 0;
+ }
}
/* Restore settings after the reset */
@@ -3573,9 +3606,13 @@ void
iavf_set_no_poll(struct iavf_adapter *adapter, bool link_change)
{
struct iavf_info *vf = &adapter->vf;
+ bool no_poll;
- adapter->no_poll = (link_change & !vf->link_up) ||
+ no_poll = (link_change & !vf->link_up) ||
vf->vf_reset || vf->in_reset_recovery;
+
+ rte_atomic_store_explicit(&adapter->no_poll, no_poll,
+ rte_memory_order_release);
}
static int
@@ -3645,6 +3682,27 @@ bool is_iavf_supported(struct rte_eth_dev *dev)
return !strcmp(dev->device->driver->name, rte_iavf_pmd.driver.name);
}
+void
+iavf_resume_pending_start(struct rte_eth_dev *dev)
+{
+ struct iavf_info *vf = IAVF_DEV_PRIVATE_TO_VF(dev->data->dev_private);
+ int ret;
+
+ if (!(vf->link_up && vf->start_pending))
+ return;
+
+ PMD_DRV_LOG(DEBUG, "PF link back up; resuming deferred dev_start");
+ ret = iavf_dev_start(dev);
+ if (ret == 0) {
+ dev->data->dev_started = 1;
+ vf->start_pending = false;
+ } else {
+ PMD_DRV_LOG(ERR,
+ "deferred dev_start failed (ret=%d); will retry on next link-up",
+ ret);
+ }
+}
+
RTE_PMD_REGISTER_PCI(net_iavf, rte_iavf_pmd);
RTE_PMD_REGISTER_PCI_TABLE(net_iavf, pci_id_iavf_map);
RTE_PMD_REGISTER_KMOD_DEP(net_iavf, "* igb_uio | vfio-pci");
diff --git a/drivers/net/intel/iavf/iavf_rxtx.c b/drivers/net/intel/iavf/iavf_rxtx.c
index 4f2ffe6188..1dbc7aa74a 100644
--- a/drivers/net/intel/iavf/iavf_rxtx.c
+++ b/drivers/net/intel/iavf/iavf_rxtx.c
@@ -3722,7 +3722,9 @@ iavf_recv_pkts_no_poll(void *rx_queue, struct rte_mbuf **rx_pkts,
struct ci_rx_queue *rxq = rx_queue;
enum iavf_rx_func_type rx_func_type;
- if (!rxq->iavf_vsi || rxq->iavf_vsi->adapter->no_poll)
+ if (!rxq->iavf_vsi ||
+ rte_atomic_load_explicit(&rxq->iavf_vsi->adapter->no_poll,
+ rte_memory_order_acquire))
return 0;
rx_func_type = rxq->iavf_vsi->adapter->rx_func_type;
@@ -3738,7 +3740,9 @@ iavf_xmit_pkts_no_poll(void *tx_queue, struct rte_mbuf **tx_pkts,
struct ci_tx_queue *txq = tx_queue;
enum iavf_tx_func_type tx_func_type;
- if (!txq->iavf_vsi || txq->iavf_vsi->adapter->no_poll)
+ if (!txq->iavf_vsi ||
+ rte_atomic_load_explicit(&txq->iavf_vsi->adapter->no_poll,
+ rte_memory_order_acquire))
return 0;
tx_func_type = txq->iavf_vsi->adapter->tx_func_type;
@@ -4025,6 +4029,75 @@ iavf_tx_done_cleanup_full(struct ci_tx_queue *txq,
return (int)pkt_cnt;
}
+/*
+ * iavf_dev_tx_drain - drain in-flight Tx descriptors after a link-down or
+ * impending PF reset event.
+ */
+void
+iavf_dev_tx_drain(struct rte_eth_dev *dev)
+{
+ struct ci_tx_queue *txq;
+ uint64_t hz, deadline;
+ int idle_iters = 0;
+ uint16_t qid;
+
+ /*
+ * Allow any Tx burst already in flight on a data-plane lcore to
+ * write its remaining descriptors and notify. After
+ * this window, the no_poll gate set by the caller is observed at
+ * the next burst-entry and no new descriptors will be posted.
+ */
+ rte_delay_us_block(IAVF_TX_DRAIN_SETTLE_US);
+
+ hz = rte_get_timer_hz();
+ deadline = rte_get_timer_cycles() +
+ (hz * IAVF_TX_DRAIN_TIMEOUT_US) / 1000000ULL;
+
+ while (rte_get_timer_cycles() < deadline) {
+ bool any_pending = false;
+ bool any_progress = false;
+
+ for (qid = 0; qid < dev->data->nb_tx_queues; qid++) {
+ txq = dev->data->tx_queues[qid];
+ if (txq == NULL ||
+ dev->data->tx_queue_state[qid] !=
+ RTE_ETH_QUEUE_STATE_STARTED)
+ continue;
+
+ /*
+ * nb_tx_free == nb_tx_desc - 1 means the ring is
+ * empty (one descriptor is always reserved).
+ */
+ if (txq->nb_tx_free >= txq->nb_tx_desc - 1)
+ continue;
+
+ any_pending = true;
+ if (ci_tx_xmit_cleanup(txq) == 0)
+ any_progress = true;
+ }
+
+ if (!any_pending)
+ return;
+
+ if (any_progress) {
+ idle_iters = 0;
+ } else if (++idle_iters >= IAVF_TX_DRAIN_IDLE_MAX) {
+ /*
+ * HW has not advanced the RS-bit write-back for
+ * several polling intervals; either the queue is
+ * quiescent except for the sub-rs_thresh tail
+ * (which we cannot observe here) or HW is no
+ * longer fetching. Further polling is unlikely to
+ * help, and the PF teardown path has its own
+ * grace period for the remainder.
+ */
+ break;
+ }
+
+ rte_delay_us_block(IAVF_TX_DRAIN_POLL_US);
+ }
+}
+
int
iavf_dev_tx_done_cleanup(void *txq, uint32_t free_cnt)
{
diff --git a/drivers/net/intel/iavf/iavf_rxtx.h b/drivers/net/intel/iavf/iavf_rxtx.h
index 22ea415f44..4088bc421c 100644
--- a/drivers/net/intel/iavf/iavf_rxtx.h
+++ b/drivers/net/intel/iavf/iavf_rxtx.h
@@ -506,6 +506,11 @@ enum iavf_tx_ctx_desc_tunnel_l4_tunnel_type {
/* Valid indicator bit for the time_stamp_low field */
#define IAVF_RX_FLX_DESC_TS_VALID (0x1UL)
+#define IAVF_TX_DRAIN_TIMEOUT_US 10000 /* total drain budget: 10 ms */
+#define IAVF_TX_DRAIN_SETTLE_US 100 /* let in-flight burst land */
+#define IAVF_TX_DRAIN_POLL_US 50 /* poll interval */
+#define IAVF_TX_DRAIN_IDLE_MAX 20 /* ~1 ms of no RS write-back */
+
int iavf_dev_rx_queue_setup(struct rte_eth_dev *dev,
uint16_t queue_idx,
uint16_t nb_desc,
@@ -641,6 +646,7 @@ void iavf_set_default_ptype_table(struct rte_eth_dev *dev);
void iavf_rx_queue_release_mbufs_vec(struct ci_rx_queue *rxq);
void iavf_rx_queue_release_mbufs_neon(struct ci_rx_queue *rxq);
enum rte_vect_max_simd iavf_get_max_simd_bitwidth(void);
+void iavf_dev_tx_drain(struct rte_eth_dev *dev);
static inline
void iavf_dump_rx_descriptor(struct ci_rx_queue *rxq,
diff --git a/drivers/net/intel/iavf/iavf_vchnl.c b/drivers/net/intel/iavf/iavf_vchnl.c
index f8fffa7802..513c3e18dd 100644
--- a/drivers/net/intel/iavf/iavf_vchnl.c
+++ b/drivers/net/intel/iavf/iavf_vchnl.c
@@ -260,7 +260,7 @@ iavf_handle_link_change_event(struct rte_eth_dev *dev,
* (link is down or a VF reset is in progress); the watchdog drives
* auto-reset recovery, so it must remain armed in those cases.
*/
- if (vf->link_up && !vf->vf_reset)
+ if (vf->link_up && !vf->vf_reset && !vf->in_reset_recovery)
iavf_dev_watchdog_disable(adapter);
else
iavf_dev_watchdog_enable(adapter);
@@ -268,9 +268,27 @@ iavf_handle_link_change_event(struct rte_eth_dev *dev,
if (adapter->devargs.no_poll_on_link_down) {
iavf_set_no_poll(adapter, true);
PMD_DRV_LOG(DEBUG, "VF no poll turned %s",
- adapter->no_poll ? "on" : "off");
+ rte_atomic_load_explicit(&adapter->no_poll,
+ rte_memory_order_relaxed) ?
+ "on" : "off");
+ if (!vf->link_up)
+ iavf_dev_tx_drain(dev);
}
+ /*
+ * Resume a deferred dev_start.
+ * iavf_handle_hw_reset() sets vf->start_pending when
+ * reset recovery completed dev_init() but iavf_dev_start()
+ * itself failed (typically -EIO from VIRTCHNL_OP_CONFIG_VSI_QUEUES
+ * when the PF VSI was inactive).
+ * A link-up event implies the PF VSI is active again, so retry now.
+ * Run before the LSC event post so the port is ready to accept Tx
+ * by the time the app's link-up callback fires; no_poll has already
+ * been cleared above so bursts go through as soon as
+ * dev_start sets dev_started=1.
+ */
+ iavf_resume_pending_start(dev);
+
iavf_dev_event_post(dev, RTE_ETH_EVENT_INTR_LSC, NULL, 0);
PMD_DRV_LOG(INFO, "Link status update:%s",
@@ -327,6 +345,8 @@ iavf_read_msg_from_pf(struct iavf_adapter *adapter, uint16_t buf_len,
if (!vf->vf_reset) {
vf->vf_reset = true;
iavf_set_no_poll(adapter, false);
+ if (adapter->devargs.no_poll_on_link_down)
+ iavf_dev_tx_drain(vf->eth_dev);
iavf_dev_event_post(vf->eth_dev,
RTE_ETH_EVENT_INTR_RESET,
NULL, 0);
@@ -565,6 +585,9 @@ iavf_handle_pf_event_msg(struct rte_eth_dev *dev, uint8_t *msg,
if (!vf->vf_reset) {
vf->vf_reset = true;
iavf_set_no_poll(adapter, false);
+ iavf_dev_watchdog_enable(adapter);
+ if (adapter->devargs.no_poll_on_link_down)
+ iavf_dev_tx_drain(dev);
iavf_dev_event_post(dev, RTE_ETH_EVENT_INTR_RESET,
NULL, 0);
}
@@ -614,6 +637,19 @@ iavf_handle_virtchnl_msg(struct rte_eth_dev *dev)
break;
}
aq_opc = rte_le_to_cpu_16(info.desc.opcode);
+
+ /*
+ * During PF-initiated resets, iavf_clean_arq_element() has
+ * been observed to return IAVF_SUCCESS with a fully zeroed
+ * descriptor (opcode 0).
+ * Without this guard, such descriptors would fall through
+ * to the default case of the dispatch switch below and the
+ * "Request 0 is not supported yet" log flood reported in
+ * the field would be produced. These are discarded now.
+ */
+ if (aq_opc == 0)
+ continue;
+
/* For the message sent from pf to vf, opcode is stored in
* cookie_high of struct iavf_aq_desc, while return error code
* are stored in cookie_low, Which is done by PF driver.
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2 00/12] doc: improve contributing documentation clarity and style
From: Thomas Monjalon @ 2026-07-17 21:39 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In-Reply-To: <20260116201738.74578-1-stephen@networkplumber.org>
16/01/2026 21:14, Stephen Hemminger:
> Nandini Persad (1):
> doc: correct grammar and typos in design guide
>
> Stephen Hemminger (11):
> doc: improve ABI policy documentation style
> doc: improve coding style guide readability
> doc: improve documentation guidelines style
> doc: improve Linux uAPI header documentation
> doc: improve new driver guide readability
> doc: improve new library guide style
> doc: improve patch submission guide readability
> doc: improve stable releases documentation
> doc: improve vulnerability process documentation
> doc: improve unit test guide readability
> doc: fix grammar and style in ABI versioning guide
Applied, thanks.
^ permalink raw reply
* Re: [PATCH v5 00/54] doc: programmers guide corrections
From: Thomas Monjalon @ 2026-07-17 23:26 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In-Reply-To: <20260118191323.241013-1-stephen@networkplumber.org>
> Stephen Hemminger (54):
> doc: correct grammar and typos in argparse library guide
> doc: correct grammar in service cores guide
> doc: correct grammar and errors in trace library guide
> doc: correct typos in log library guide
> doc: correct errors in command-line library guide
> doc: correct errors in trace library guide
> doc: correct errors in stack library guide
> doc: correct errors in RCU library guide
> doc: correct grammar and formatting in ASan guide
> doc: correct grammar and typos in bbdev guide
> doc: correct grammar and formatting in bpf lib guide
> doc: correct grammar and typos in meson build guide
> doc: correct grammar and typos in cryptodev guide
> doc: correct grammar and formatting in compressdev guide
> doc: correct grammar in dmadev guide
> doc: correct grammar in efd guide
> doc: correct grammar in EAL guide
> doc: correct double space in FIB guide
> doc: correct grammar in GRO guide
> doc: correct grammar in GSO guide
> doc: correct typos and grammar in graph guide
> doc: correct grammar in hash guide
> doc: correct grammar and typos in IP fragment guide
> doc: correct double spaces in IPsec guide
> doc: correct grammar in lcore variables guide
> doc: correct typo in link bonding guide
> doc: correct grammar in LTO guide
> doc: correct grammar in LPM guide
> doc: correct grammar and typo in LPM6 guide
> doc: correct grammar in introduction
> doc: correct grammar in mbuf library guide
> doc: correct grammar in membership library guide
> doc: correct errors in mempool library guide
> doc: correct style in meson unit tests guide
> doc: correct errors in metrics library guide
> doc: correct grammar in mldev library guide
> doc: correct grammar in multi-process guide
> doc: correct grammar in overview
> doc: correct grammar in ACL library guide
> doc: correct typos in packet distributor guide
> doc: correct grammar in packet framework guide
> doc: correct grammar in PDCP library guide
> doc: correct grammar in pdump library guide
> doc: correct typos in power management guide
> doc: correct grammar in profiling guide
> doc: correct errors in regexdev guide
> doc: correct grammar in reorder library guide
> doc: correct whitespace in RIB library guide
> doc: correct incomplete sentence in ring library guide
> doc: correct grammar in security library guide
> doc: correct hyphenation in thread safety guide
> doc: correct errors in toeplitz hash library guide
> doc: correct errors in vhost library guide
> doc: correct whitespace in efficient code guide
Applied, thanks.
^ permalink raw reply
* Re: [PATCH 01/10] bbdev: fix stats aggregation from queues
From: David Marchand @ 2026-07-18 7:47 UTC (permalink / raw)
To: Chautru, Nicolas
Cc: dev@dpdk.org, stable@dpdk.org, Maxime Coquelin, Akhil Goyal
In-Reply-To: <SA1PR11MB82808D1A33417DAF40BD6761F8C62@SA1PR11MB8280.namprd11.prod.outlook.com>
On Fri, 17 Jul 2026 at 19:23, Chautru, Nicolas
<nicolas.chautru@intel.com> wrote:
>
> Hi David,
> We use the same structure for device and queue level stats for simplicity.
> But really bringing these 2 at device level as an aggregate level do not really make much sense at system evel (ie sum of of queue depth is irrelevant). The other ones are relevant.
> I don't think we should change this, it would just create confusion with users. That's a nack to me, let me know if unclear.
I find all those stats confusing, but otohI don't really care.
I'll drop in next revision.
--
David Marchand
^ permalink raw reply
* Re: [PATCH v3] net/virtio-user: fix eventfd sharing in secondary process
From: Samar Yadav @ 2026-07-13 9:25 UTC (permalink / raw)
To: dev; +Cc: chenbox, maxime.coquelin, stable, stephen, tiwei.bie
In-Reply-To: <20260703102530.133813-1-samaryadav5@gmail.com>
Hi Maxime, Stephen,
Gentle ping on this one. v2 addressed the rte_calloc() and
rte_thread_mutex_init_shared() feedback from the initial review, and
v3 cleaned up a few style items (include ordering, named constants)
flagged by the automated review. CI is green apart from the
automated review warning, which I've checked against the actual code
and don't believe blocks anything.
Let me know if there's anything else needed on my end.
Thanks,
Samar Yadav
^ permalink raw reply
* [PATCH] fbarray: check fbarray lock downgrade failure
From: Daniil Iskhakov @ 2026-07-14 8:02 UTC (permalink / raw)
To: Anatoly Burakov, Dmitry Kozlyuk; +Cc: dev, stable, rrv, dish, sdl.dpdk
Return value of eal_file_lock() is ignored in the fbarray destroy error
path.
Check the return value and log the downgrade failure while keeping the
original rte_errno reported by unlink().
Found by Linux Verification Center (linuxtesting.org) with SVACE.
Fixes: 176bb37ca6f3 ("eal: introduce internal wrappers for file operations")
Cc: stable@dpdk.org
Signed-off-by: Daniil Iskhakov <dish@amicon.ru>
---
Cc: dmitry.kozliuk@gmail.com
Cc: sdl.dpdk@linuxtesting.org
Cc: rrv@amicon.ru
---
lib/eal/common/eal_common_fbarray.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/eal/common/eal_common_fbarray.c b/lib/eal/common/eal_common_fbarray.c
index 8bdcefb717..5a694ccdae 100644
--- a/lib/eal/common/eal_common_fbarray.c
+++ b/lib/eal/common/eal_common_fbarray.c
@@ -1022,7 +1022,9 @@ rte_fbarray_destroy(struct rte_fbarray *arr)
* we're still holding an exclusive lock, so drop it to
* shared.
*/
- eal_file_lock(fd, EAL_FLOCK_SHARED, EAL_FLOCK_RETURN);
+ if (eal_file_lock(fd, EAL_FLOCK_SHARED, EAL_FLOCK_RETURN))
+ EAL_LOG(DEBUG, "Cannot restore fbarray shared lock: %s",
+ rte_strerror(rte_errno));
ret = -1;
goto out;
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] common/cnxk: support aggregator enqueue profiles
From: ykanaujia @ 2026-07-15 8:43 UTC (permalink / raw)
To: Nithin Dabilpuram, Kiran Kumar K, Sunil Kumar Kori, Satha Rao,
Harman Kalra
Cc: dev, Satheesh Paul
From: Satheesh Paul <psatheesh@marvell.com>
Support for flow aggregator enqueue profiles.
Signed-off-by: Satheesh Paul <psatheesh@marvell.com>
---
drivers/common/cnxk/roc_mbox.h | 57 +++++++++++
drivers/common/cnxk/roc_nix.c | 41 ++++++++
drivers/common/cnxk/roc_nix.h | 2 +
drivers/common/cnxk/roc_nix_flow_vec.h | 99 +++++++++++++++++++
drivers/common/cnxk/roc_npc_priv.h | 3 +
.../common/cnxk/roc_platform_base_symbols.c | 1 +
6 files changed, 203 insertions(+)
create mode 100644 drivers/common/cnxk/roc_nix_flow_vec.h
diff --git a/drivers/common/cnxk/roc_mbox.h b/drivers/common/cnxk/roc_mbox.h
index 52ecde6563..814999722f 100644
--- a/drivers/common/cnxk/roc_mbox.h
+++ b/drivers/common/cnxk/roc_mbox.h
@@ -359,6 +359,8 @@ struct mbox_msghdr {
msg_rsp) \
M(NIX_RX_INL_QUEUE_CFG, 0x8033, nix_rx_inl_queue_cfg, \
nix_rx_inline_qcfg_req, msg_rsp) \
+ M(NIX_AF_RX_FLOW_VEC_CTRL_SET, 0x8034, nix_af_rx_flow_vec_ctrl_set, \
+ nix_af_rx_flow_vec_ctrl_write_req, msg_rsp) \
/* MCS mbox IDs (range 0xa000 - 0xbFFF) */ \
M(MCS_ALLOC_RESOURCES, 0xa000, mcs_alloc_resources, mcs_alloc_rsrc_req, \
mcs_alloc_rsrc_rsp) \
@@ -2773,6 +2775,61 @@ struct npc_cn20k_mcam_write_entry_req {
uint64_t __io reserved; /* reserved for future use */
};
+union nix_af_rx_flow_vec_ctrl0x {
+ struct nix_af_rx_flow_vec_ctrl0x_s {
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ uint64_t __io reserved : 14;
+ uint64_t __io sov_offset : 8;
+ uint64_t __io sov_mask : 16;
+ uint64_t __io sov_inv : 1;
+ uint64_t __io eov_offset : 8;
+ uint64_t __io eov_mask : 16;
+ uint64_t __io eov_inv : 1;
+#else
+ uint64_t __io eov_inv : 1;
+ uint64_t __io eov_mask : 16;
+ uint64_t __io eov_offset : 8;
+ uint64_t __io sov_inv : 1;
+ uint64_t __io sov_mask : 16;
+ uint64_t __io sov_offset : 8;
+ uint64_t __io reserved : 14;
+#endif
+ } __attribute__((__packed__)) s;
+ uint64_t __io val;
+};
+
+union nix_af_rx_flow_vec_ctrl1x {
+ struct nix_af_rx_flow_vec_ctrl1x_s {
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ uint64_t __io reserved : 35;
+ uint64_t __io valid : 1;
+ uint64_t __io lid : 3;
+ uint64_t __io ltype_match : 4;
+ uint64_t __io ltype_mask : 4;
+ uint64_t __io ver_offset : 9;
+ uint64_t __io ver_match : 4;
+ uint64_t __io ver_mask : 4;
+#else
+ uint64_t __io ver_mask : 4;
+ uint64_t __io ver_match : 4;
+ uint64_t __io ver_offset : 9;
+ uint64_t __io ltype_mask : 4;
+ uint64_t __io ltype_match : 4;
+ uint64_t __io lid : 3;
+ uint64_t __io valid : 1;
+ uint64_t __io reserved : 35;
+#endif
+ } __attribute__((__packed__)) s;
+ uint64_t __io val;
+};
+
+#define NIX_AF_RX_FLOW_VEC_CTRL_MAX 4
+struct nix_af_rx_flow_vec_ctrl_write_req {
+ struct mbox_msghdr hdr;
+ union nix_af_rx_flow_vec_ctrl0x ctrl0x[NIX_AF_RX_FLOW_VEC_CTRL_MAX];
+ union nix_af_rx_flow_vec_ctrl1x ctrl1x[NIX_AF_RX_FLOW_VEC_CTRL_MAX];
+};
+
/* Enable/Disable a given entry */
struct npc_mcam_ena_dis_entry_req {
struct mbox_msghdr hdr;
diff --git a/drivers/common/cnxk/roc_nix.c b/drivers/common/cnxk/roc_nix.c
index 477c7d5ca0..8e3903051d 100644
--- a/drivers/common/cnxk/roc_nix.c
+++ b/drivers/common/cnxk/roc_nix.c
@@ -3,6 +3,7 @@
*/
#include "roc_api.h"
+#include "roc_nix_flow_vec.h"
#include "roc_priv.h"
bool
@@ -524,7 +525,15 @@ roc_nix_dev_init(struct roc_nix *roc_nix)
roc_nix_get_hw_info(roc_nix);
nix->dev.drv_inited = true;
+ if (roc_model_is_cn20k()) {
+ rc = roc_nix_af_rx_flow_vec_ctrl_set(roc_nix);
+ if (rc)
+ goto tm_conf_fini;
+ }
+
return 0;
+tm_conf_fini:
+ nix_tm_conf_fini(roc_nix);
unregister_irqs:
nix_unregister_irqs(nix);
lf_detach:
@@ -588,3 +597,35 @@ roc_nix_max_rep_count(struct roc_nix *roc_nix)
mbox_put(mbox);
return rc;
}
+
+static int
+nix_af_rx_flow_vec_ctrl_set(struct mbox *mbox)
+{
+ struct nix_af_rx_flow_vec_ctrl_write_req *req;
+ struct nix_af_rx_flow_vec_ctrl_write_rsp *rsp;
+ int rc = -ENOSPC;
+
+ req = mbox_alloc_msg_nix_af_rx_flow_vec_ctrl_set(mbox_get(mbox));
+ if (req == NULL)
+ goto exit;
+
+ memcpy(&req->ctrl0x, nix_flow_ctrl0, sizeof(nix_flow_ctrl0));
+ memcpy(&req->ctrl1x, nix_flow_ctrl1, sizeof(nix_flow_ctrl1));
+
+ rc = mbox_process_msg(mbox, (void *)&rsp);
+ if (rc)
+ goto exit;
+
+exit:
+ mbox_put(mbox);
+ return rc;
+}
+
+int
+roc_nix_af_rx_flow_vec_ctrl_set(struct roc_nix *roc_nix)
+{
+ struct nix *nix = roc_nix_to_nix_priv(roc_nix);
+ struct dev *dev = &nix->dev;
+
+ return nix_af_rx_flow_vec_ctrl_set(dev->mbox);
+}
diff --git a/drivers/common/cnxk/roc_nix.h b/drivers/common/cnxk/roc_nix.h
index 0b1e501b16..35a90f982b 100644
--- a/drivers/common/cnxk/roc_nix.h
+++ b/drivers/common/cnxk/roc_nix.h
@@ -1052,6 +1052,8 @@ int __roc_api roc_nix_npc_rx_ena_dis(struct roc_nix *roc_nix, bool enable);
int __roc_api roc_nix_npc_mcast_config(struct roc_nix *roc_nix,
bool mcast_enable, bool prom_enable);
+int __roc_api roc_nix_af_rx_flow_vec_ctrl_set(struct roc_nix *roc_nix);
+
/* RSS */
void __roc_api roc_nix_rss_key_default_fill(struct roc_nix *roc_nix,
uint8_t key[ROC_NIX_RSS_KEY_LEN]);
diff --git a/drivers/common/cnxk/roc_nix_flow_vec.h b/drivers/common/cnxk/roc_nix_flow_vec.h
new file mode 100644
index 0000000000..72dbf44292
--- /dev/null
+++ b/drivers/common/cnxk/roc_nix_flow_vec.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2021 Marvell.
+ */
+
+#ifndef _ROC_NIX_FLOW_VEC_H_
+#define _ROC_NIX_FLOW_VEC_H_
+
+#define IPV4_SOV_OFFSET 6
+#define IPV4_SOV_MASK 0x1FFF
+#define IPV4_EOV_OFFSET 6
+#define IPV4_EOV_MASK 0x2000
+
+#define IPV6_SOV_OFFSET 0x2A
+#define IPV6_SOV_MASK 0xFFF8
+#define IPV6_EOV_OFFSET 0x2A
+#define IPV6_EOV_MASK 0x1
+
+#define CUSTOM_SOV_OFFSET 4
+#define CUSTOM_SOV_MASK 0x7F
+#define CUSTOM_EOV_OFFSET 5
+#define CUSTOM_EOV_MASK 0x80
+
+#define IPV4_VER_OFFSET 0
+#define IPV4_VER_MATCH 0x4
+#define IPV4_VER_MASK 0xF
+#define IPV6_VER_OFFSET 0
+#define IPV6_VER_MATCH 0x6
+#define IPV6_VER_MASK 0xF
+#define CUSTOM_VER_OFFSET 0
+#define CUSTOM_VER_MATCH 0x2
+#define CUSTOM_VER_MASK 0xF
+
+static union nix_af_rx_flow_vec_ctrl0x nix_flow_ctrl0[NIX_AF_RX_FLOW_VEC_CTRL_MAX] = {
+ {.s.eov_inv = 0,
+ .s.eov_mask = 0,
+ .s.eov_offset = 0,
+ .s.sov_inv = 0,
+ .s.sov_mask = 0,
+ .s.sov_offset = 0,
+ .s.reserved = 0},
+ {.s.eov_inv = 1,
+ .s.eov_mask = IPV4_EOV_MASK,
+ .s.eov_offset = IPV4_EOV_OFFSET,
+ .s.sov_inv = 1,
+ .s.sov_mask = IPV4_SOV_MASK,
+ .s.sov_offset = IPV4_SOV_OFFSET,
+ .s.reserved = 0},
+ {.s.eov_inv = 1,
+ .s.eov_mask = IPV6_EOV_MASK,
+ .s.eov_offset = IPV6_EOV_OFFSET,
+ .s.sov_inv = 1,
+ .s.sov_mask = IPV6_SOV_MASK,
+ .s.sov_offset = IPV6_SOV_OFFSET,
+ .s.reserved = 0},
+ {.s.eov_inv = 1,
+ .s.eov_mask = CUSTOM_EOV_MASK,
+ .s.eov_offset = CUSTOM_EOV_OFFSET,
+ .s.sov_inv = 1,
+ .s.sov_mask = CUSTOM_SOV_MASK,
+ .s.sov_offset = CUSTOM_SOV_OFFSET,
+ .s.reserved = 0},
+};
+
+static union nix_af_rx_flow_vec_ctrl1x nix_flow_ctrl1[NIX_AF_RX_FLOW_VEC_CTRL_MAX] = {
+ {.s.ver_mask = 0,
+ .s.ver_match = 0,
+ .s.ver_offset = 0,
+ .s.ltype_mask = 0,
+ .s.ltype_match = 0,
+ .s.lid = 0,
+ .s.valid = 0,
+ .s.reserved = 0},
+ {.s.ver_mask = IPV4_VER_MASK,
+ .s.ver_match = IPV4_VER_MATCH,
+ .s.ver_offset = IPV4_VER_OFFSET,
+ .s.ltype_mask = 0x3,
+ .s.ltype_match = NPC_LT_LC_IP,
+ .s.lid = NPC_LID_LC,
+ .s.valid = 1,
+ .s.reserved = 0},
+ {.s.ver_mask = IPV6_VER_MASK,
+ .s.ver_match = IPV6_VER_MATCH,
+ .s.ver_offset = IPV6_VER_OFFSET,
+ .s.ltype_mask = 0x7,
+ .s.ltype_match = NPC_LT_LC_IP6,
+ .s.lid = NPC_LID_LC,
+ .s.valid = 1,
+ .s.reserved = 0},
+ {.s.ver_mask = CUSTOM_VER_MASK,
+ .s.ver_match = CUSTOM_VER_MATCH,
+ .s.ver_offset = CUSTOM_VER_OFFSET,
+ .s.ltype_mask = 0xF,
+ .s.ltype_match = NPC_LT_LC_CUSTOM0,
+ .s.lid = NPC_LID_LC,
+ .s.valid = 1,
+ .s.reserved = 0},
+};
+
+#endif /* _ROC_NIX_FLOW_VEC_H_ */
diff --git a/drivers/common/cnxk/roc_npc_priv.h b/drivers/common/cnxk/roc_npc_priv.h
index 6a27f0e0fa..c7e7d2d8f6 100644
--- a/drivers/common/cnxk/roc_npc_priv.h
+++ b/drivers/common/cnxk/roc_npc_priv.h
@@ -95,6 +95,9 @@
#define NPC_CN20K_LFLAG_LC_OFFSET (NPC_LTYPE_OFFSET_START + 4)
#define NPC_CN20K_LTYPE_LC_OFFSET (NPC_LTYPE_OFFSET_START + 5)
+#define NPC_ACTION2_FLOW_VEC_VLD_FIELD 0xE
+#define NPC_ACTION2_FLOW_VEC_FIELD 0xC
+
#define CN10K_SDP_CH_START 0x80
#define CN10K_SDP_CH_MASK 0xF80
diff --git a/drivers/common/cnxk/roc_platform_base_symbols.c b/drivers/common/cnxk/roc_platform_base_symbols.c
index 063cb21aae..00746abc06 100644
--- a/drivers/common/cnxk/roc_platform_base_symbols.c
+++ b/drivers/common/cnxk/roc_platform_base_symbols.c
@@ -96,6 +96,7 @@ RTE_EXPORT_INTERNAL_SYMBOL(roc_idev_rvu_lf_free)
RTE_EXPORT_INTERNAL_SYMBOL(roc_idev_mcs_get)
RTE_EXPORT_INTERNAL_SYMBOL(roc_idev_mcs_set)
RTE_EXPORT_INTERNAL_SYMBOL(roc_idev_mcs_free)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_nix_af_rx_flow_vec_ctrl_set)
RTE_EXPORT_INTERNAL_SYMBOL(roc_nix_inl_outb_ring_base_get)
RTE_EXPORT_INTERNAL_SYMBOL(roc_idev_nix_list_get)
RTE_EXPORT_INTERNAL_SYMBOL(roc_idev_cpt_set)
--
2.43.0
^ permalink raw reply related
* [PATCH 2/2] drivers: add support for async RTE FLOW APIs
From: ykanaujia @ 2026-07-15 8:45 UTC (permalink / raw)
To: Nithin Dabilpuram, Kiran Kumar K, Sunil Kumar Kori, Satha Rao,
Harman Kalra
Cc: dev, Yashaswini Kanaujia
From: Yashaswini Kanaujia <ykanaujia@marvell.com>
added support for asynchronous rte_flow APIs
for cn10k device
Signed-off-by: Yashaswini Kanaujia <ykanaujia@marvell.com>
---
drivers/common/cnxk/meson.build | 1 +
drivers/common/cnxk/roc_npc.h | 97 ++
drivers/common/cnxk/roc_npc_flow_template.c | 1326 ++++++++++++++++
drivers/common/cnxk/roc_npc_mcam.c | 45 +-
drivers/common/cnxk/roc_npc_priv.h | 98 ++
drivers/common/cnxk/roc_npc_utils.c | 4 +-
.../common/cnxk/roc_platform_base_symbols.c | 16 +
drivers/net/cnxk/cn10k_ethdev.c | 23 +
drivers/net/cnxk/cn10k_flow.c | 43 +
drivers/net/cnxk/cn10k_flow.h | 2 +
drivers/net/cnxk/cn20k_ethdev.c | 24 +-
drivers/net/cnxk/cn20k_flow.c | 43 +
drivers/net/cnxk/cn20k_flow.h | 2 +
drivers/net/cnxk/cnxk_ethdev.h | 44 +
drivers/net/cnxk/cnxk_flow.c | 1338 ++++++++++++++++-
drivers/net/cnxk/cnxk_flow.h | 77 +
16 files changed, 3161 insertions(+), 22 deletions(-)
create mode 100644 drivers/common/cnxk/roc_npc_flow_template.c
diff --git a/drivers/common/cnxk/meson.build b/drivers/common/cnxk/meson.build
index 3303ad9354..68374ee135 100644
--- a/drivers/common/cnxk/meson.build
+++ b/drivers/common/cnxk/meson.build
@@ -59,6 +59,7 @@ sources = files(
'roc_npa_type.c',
'roc_npc.c',
'roc_npc_aging.c',
+ 'roc_npc_flow_template.c',
'roc_npc_mcam.c',
'roc_npc_mcam_dump.c',
'roc_npc_parse.c',
diff --git a/drivers/common/cnxk/roc_npc.h b/drivers/common/cnxk/roc_npc.h
index a7254f35ca..21fa493c74 100644
--- a/drivers/common/cnxk/roc_npc.h
+++ b/drivers/common/cnxk/roc_npc.h
@@ -473,6 +473,9 @@ int __roc_api roc_npc_mcam_alloc_entries(struct roc_npc *roc_npc, int ref_entry,
int __roc_api roc_npc_mcam_ena_dis_entry(struct roc_npc *roc_npc, struct roc_npc_flow *mcam,
bool enable);
int __roc_api roc_npc_mcam_write_entry(struct roc_npc *roc_npc, struct roc_npc_flow *mcam);
+
+void __roc_api roc_npc_mcam_write_rx_finalize(struct roc_npc *roc_npc, struct roc_npc_flow *flow);
+
void __roc_api roc_npc_defrag_mcam_banks(struct roc_npc *roc_npc);
uint8_t __roc_api roc_npc_get_key_type(struct roc_npc *roc_npc, struct roc_npc_flow *flow);
uint8_t __roc_api roc_npc_kex_key_type_config_get(struct roc_npc *roc_npc);
@@ -508,4 +511,98 @@ void __roc_api roc_npc_sdp_channel_get(struct roc_npc *roc_npc, uint16_t *chan_b
int __roc_api roc_npc_mcam_get_stats(struct roc_npc *roc_npc, struct roc_npc_flow *flow,
uint64_t *count);
int __roc_api roc_npc_skip_size_pkind_get(struct roc_npc *roc_npc);
+
+/*
+ * Queue-based (async) template flow API.
+ */
+enum roc_npc_template_table_insertion_type {
+ /* HW MCAM entry allocated per rule at create time (match based). */
+ ROC_NPC_TEMPLATE_INSERTION_PATTERN,
+ /* MCAM range reserved up front; rules placed at an app-chosen index. */
+ ROC_NPC_TEMPLATE_INSERTION_INDEX_WITH_PATTERN,
+};
+
+struct roc_npc_flow_queue_attr {
+ uint32_t size; /**< Number of in-flight async ops the queue can hold. */
+};
+
+struct roc_npc_pattern_template_attr {
+ uint32_t relaxed_matching : 1;
+ uint32_t ingress : 1;
+ uint32_t egress : 1;
+ uint32_t transfer : 1;
+ uint32_t reserved : 28;
+};
+
+struct roc_npc_actions_template_attr {
+ uint32_t ingress : 1;
+ uint32_t egress : 1;
+ uint32_t transfer : 1;
+ uint32_t reserved : 29;
+};
+
+struct roc_npc_template_table_attr {
+ struct roc_npc_attr flow_attr; /**< Priority / ingress / egress. */
+ uint32_t transfer : 1; /**< roc_npc_attr has no transfer bit. */
+ uint32_t reserved : 31;
+ enum roc_npc_template_table_insertion_type insertion_type;
+ uint32_t nb_flows; /**< Max rules the table can hold. */
+ void *cookie; /**< Opaque consumer context, retrievable per rule. */
+};
+
+/** Result of one completed async operation, returned by roc_npc_flow_pull. */
+struct roc_npc_flow_op_result {
+ void *user_data; /**< user_data passed at async_create/destroy time. */
+ int rc; /**< 0 on success, negative errno on failure. */
+};
+
+/* Opaque template/table/flow handles owned by the roc layer. */
+struct roc_npc_pattern_template;
+struct roc_npc_actions_template;
+struct roc_npc_template_table;
+struct roc_npc_template_flow;
+
+int __roc_api roc_npc_flow_configure(struct roc_npc *roc_npc, uint16_t nb_queues,
+ const struct roc_npc_flow_queue_attr *queue_attr[]);
+
+struct roc_npc_pattern_template *__roc_api roc_npc_pattern_template_create(
+ struct roc_npc *roc_npc, const struct roc_npc_pattern_template_attr *attr,
+ const struct roc_npc_item_info pattern[], int *errcode);
+int __roc_api roc_npc_pattern_template_destroy(struct roc_npc *roc_npc,
+ struct roc_npc_pattern_template *tmpl);
+
+struct roc_npc_actions_template *__roc_api roc_npc_actions_template_create(
+ struct roc_npc *roc_npc, const struct roc_npc_actions_template_attr *attr,
+ const struct roc_npc_action actions[], const struct roc_npc_action masks[], int *errcode);
+int __roc_api roc_npc_actions_template_destroy(struct roc_npc *roc_npc,
+ struct roc_npc_actions_template *tmpl);
+
+struct roc_npc_template_table *__roc_api roc_npc_template_table_create(
+ struct roc_npc *roc_npc, const struct roc_npc_template_table_attr *attr,
+ struct roc_npc_pattern_template *pattern_templates[], uint8_t nb_pattern_templates,
+ struct roc_npc_actions_template *actions_templates[], uint8_t nb_actions_templates,
+ int *errcode);
+int __roc_api roc_npc_template_table_destroy(struct roc_npc *roc_npc,
+ struct roc_npc_template_table *table);
+
+struct roc_npc_template_flow *__roc_api roc_npc_async_flow_create(
+ struct roc_npc *roc_npc, uint32_t queue_id, struct roc_npc_template_table *table,
+ const struct roc_npc_item_info pattern[], const struct roc_npc_action actions[],
+ uint16_t dst_pf_func, uint64_t npc_default_action, void *user_data, int *errcode);
+int __roc_api roc_npc_async_flow_destroy(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_template_flow *flow, void *user_data);
+struct roc_npc_template_flow *__roc_api roc_npc_async_flow_create_by_index_with_pattern(
+ struct roc_npc *roc_npc, uint32_t queue_id, struct roc_npc_template_table *table,
+ uint32_t rule_index, const struct roc_npc_item_info pattern[],
+ const struct roc_npc_action actions[], void *user_data, int *errcode);
+int __roc_api roc_npc_async_flow_actions_update(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_template_flow *flow,
+ const struct roc_npc_action actions[],
+ void *user_data);
+int __roc_api roc_npc_flow_push(struct roc_npc *roc_npc, uint32_t queue_id);
+int __roc_api roc_npc_flow_pull(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_flow_op_result res[], uint16_t n_res);
+bool __roc_api roc_npc_async_flow_resolve(struct roc_npc *roc_npc, void *handle,
+ struct roc_npc_flow **flow);
+void *__roc_api roc_npc_async_flow_table_cookie(struct roc_npc_template_flow *flow);
#endif /* _ROC_NPC_H_ */
diff --git a/drivers/common/cnxk/roc_npc_flow_template.c b/drivers/common/cnxk/roc_npc_flow_template.c
new file mode 100644
index 0000000000..5c10cfe39a
--- /dev/null
+++ b/drivers/common/cnxk/roc_npc_flow_template.c
@@ -0,0 +1,1326 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2024 Marvell.
+ */
+
+#include "roc_api.h"
+#include "roc_priv.h"
+
+/* Size of an action's conf for the supported set; -1 = nested/unsupported. */
+static int
+roc_npc_action_conf_size(enum roc_npc_action_type type)
+{
+ switch (type) {
+ case ROC_NPC_ACTION_TYPE_END:
+ case ROC_NPC_ACTION_TYPE_VOID:
+ case ROC_NPC_ACTION_TYPE_FLAG:
+ case ROC_NPC_ACTION_TYPE_DROP:
+ case ROC_NPC_ACTION_TYPE_COUNT:
+ case ROC_NPC_ACTION_TYPE_PF:
+ case ROC_NPC_ACTION_TYPE_VLAN_STRIP:
+ return 0; /* no conf */
+ case ROC_NPC_ACTION_TYPE_MARK:
+ return sizeof(struct roc_npc_action_mark);
+ case ROC_NPC_ACTION_TYPE_VF:
+ return sizeof(struct roc_npc_action_vf);
+ case ROC_NPC_ACTION_TYPE_PORT_ID:
+ return sizeof(struct roc_npc_action_port_id);
+ case ROC_NPC_ACTION_TYPE_QUEUE:
+ return sizeof(struct roc_npc_action_queue);
+ case ROC_NPC_ACTION_TYPE_VLAN_INSERT:
+ return sizeof(struct roc_npc_action_of_set_vlan_vid);
+ case ROC_NPC_ACTION_TYPE_VLAN_ETHTYPE_INSERT:
+ return sizeof(struct roc_npc_action_of_push_vlan);
+ case ROC_NPC_ACTION_TYPE_VLAN_PCP_INSERT:
+ return sizeof(struct roc_npc_action_of_set_vlan_pcp);
+ case ROC_NPC_ACTION_TYPE_AGE:
+ return sizeof(struct roc_npc_action_age);
+ default:
+ return -1; /* RSS handled separately; others unsupported */
+ }
+}
+
+/* Deep-copy an item spec/last/mask payload; *dst = NULL when nothing to copy. */
+static int
+roc_npc_dup_item_field(const void *src, uint32_t sz, const void **dst)
+{
+ void *copy;
+
+ *dst = NULL;
+ if (src == NULL || sz == 0)
+ return 0;
+ copy = plt_zmalloc(sz, 0);
+ if (copy == NULL)
+ return -ENOMEM;
+ memcpy(copy, src, sz);
+ *dst = copy;
+ return 0;
+}
+
+/* Deep-copy an action conf; RSS packs queue[]/key[] into one blob. */
+static void *
+roc_npc_dup_action_conf(const struct roc_npc_action *act)
+{
+ if (act->conf == NULL)
+ return NULL;
+
+ if (act->type == ROC_NPC_ACTION_TYPE_RSS) {
+ const struct roc_npc_action_rss *rss = act->conf;
+ size_t rss_queue_bytes = (size_t)rss->queue_num * sizeof(uint16_t);
+ size_t rss_key_bytes = rss->key_len;
+ size_t rss_blob_bytes = sizeof(*rss);
+ struct roc_npc_action_rss *rss_copy;
+ uint8_t *rss_blob;
+
+ if ((rss_queue_bytes && rss->queue == NULL) || (rss_key_bytes && rss->key == NULL))
+ return NULL;
+
+ rss_blob = plt_zmalloc(rss_blob_bytes + rss_queue_bytes + rss_key_bytes, 0);
+ if (rss_blob == NULL)
+ return NULL;
+ rss_copy = (struct roc_npc_action_rss *)rss_blob;
+ *rss_copy = *rss;
+ if (rss_queue_bytes) {
+ memcpy(rss_blob + rss_blob_bytes, rss->queue, rss_queue_bytes);
+ rss_copy->queue = (const uint16_t *)(rss_blob + rss_blob_bytes);
+ } else {
+ rss_copy->queue = NULL;
+ }
+ if (rss_key_bytes) {
+ memcpy(rss_blob + rss_blob_bytes + rss_queue_bytes, rss->key,
+ rss_key_bytes);
+ rss_copy->key =
+ (const uint8_t *)(rss_blob + rss_blob_bytes + rss_queue_bytes);
+ } else {
+ rss_copy->key = NULL;
+ }
+ return rss_copy;
+ }
+
+ {
+ int conf_size = roc_npc_action_conf_size(act->type);
+ void *copy;
+
+ if (conf_size <= 0)
+ return NULL;
+ copy = plt_zmalloc(conf_size, 0);
+ if (copy != NULL)
+ memcpy(copy, act->conf, conf_size);
+ return copy;
+ }
+}
+
+/* Free the deep-copied items/actions arrays retained for a rule. */
+static void
+roc_npc_template_flow_free_rule(struct roc_npc_template_flow *flow)
+{
+ uint16_t k;
+
+ if (flow->items != NULL) {
+ for (k = 0; k < flow->nb_items; k++) {
+ plt_free((void *)(uintptr_t)flow->items[k].spec);
+ plt_free((void *)(uintptr_t)flow->items[k].last);
+ plt_free((void *)(uintptr_t)flow->items[k].mask);
+ }
+
+ plt_free(flow->items);
+ flow->items = NULL;
+ }
+ if (flow->actions != NULL) {
+ for (k = 0; k < flow->nb_actions; k++)
+ plt_free((void *)(uintptr_t)flow->actions[k].conf);
+
+ plt_free(flow->actions);
+ flow->actions = NULL;
+ }
+}
+
+int
+roc_npc_flow_configure(struct roc_npc *roc_npc, uint16_t nb_queues,
+ const struct roc_npc_flow_queue_attr *queue_attr[])
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_flow_queue *queues;
+ int rc = -ENOMEM;
+ uint16_t i;
+
+ if (nb_queues == 0)
+ return 0;
+
+ if (queue_attr == NULL)
+ return -EINVAL;
+
+ if (npc->flow_queues != NULL) {
+ /* Don't tear down live queues while async ops are in flight. */
+ for (i = 0; i < npc->nb_flow_queues; i++) {
+ if (npc->flow_queues[i].head != npc->flow_queues[i].tail)
+ return -EBUSY;
+ }
+ }
+
+ if (npc->flow_queues != NULL) {
+ for (i = 0; i < npc->nb_flow_queues; i++)
+ plt_free(npc->flow_queues[i].ops);
+
+ plt_free(npc->flow_queues);
+ npc->flow_queues = NULL;
+ npc->nb_flow_queues = 0;
+ }
+
+ queues = plt_zmalloc(nb_queues * sizeof(struct roc_npc_flow_queue), 0);
+ if (queues == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < nb_queues; i++) {
+ if (queue_attr[i] == NULL || queue_attr[i]->size == 0) {
+ rc = -EINVAL;
+ goto err_free;
+ }
+ queues[i].size = plt_align32pow2(queue_attr[i]->size);
+ queues[i].ops = plt_zmalloc(queues[i].size * sizeof(struct roc_npc_async_op), 0);
+ if (queues[i].ops == NULL) {
+ rc = -ENOMEM;
+ goto err_free;
+ }
+ queues[i].head = 0;
+ queues[i].pushed = 0;
+ queues[i].tail = 0;
+ }
+
+ npc->flow_queues = queues;
+ npc->nb_flow_queues = nb_queues;
+ return 0;
+
+err_free:
+ for (i = 0; i < nb_queues; i++)
+ plt_free(queues[i].ops);
+
+ plt_free(queues);
+ return rc;
+}
+
+struct roc_npc_pattern_template *
+roc_npc_pattern_template_create(struct roc_npc *roc_npc,
+ const struct roc_npc_pattern_template_attr *attr,
+ const struct roc_npc_item_info pattern[], int *errcode)
+{
+ struct roc_npc_pattern_template *tmpl = NULL;
+ int i, nb_items = 0, rc = -ENOMEM;
+ uint16_t j;
+
+ PLT_SET_USED(roc_npc);
+
+ if (attr == NULL || pattern == NULL) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+
+ while (pattern[nb_items].type != ROC_NPC_ITEM_TYPE_END)
+ nb_items++;
+
+ /* Bound template size to the fixed-size rule arrays. */
+ if (nb_items > ROC_NPC_ITEM_TYPE_END) {
+ *errcode = -ENOTSUP;
+ return NULL;
+ }
+
+ tmpl = plt_zmalloc(sizeof(*tmpl), 0);
+ if (tmpl == NULL)
+ goto err;
+
+ tmpl->attr = *attr;
+ tmpl->nb_items = nb_items;
+
+ tmpl->pattern = plt_zmalloc((nb_items + 1) * sizeof(struct roc_npc_item_info), 0);
+ if (tmpl->pattern == NULL)
+ goto err;
+
+ /* Reserve up to 3 payload copies per item. */
+ tmpl->copies = plt_zmalloc(3 * (nb_items + 1) * sizeof(void *), 0);
+ if (tmpl->copies == NULL)
+ goto err;
+
+ for (i = 0; i < nb_items; i++) {
+ enum roc_npc_item_type t = pattern[i].type;
+ uint32_t sz = pattern[i].size;
+
+ /* RAW is not flat-copyable. */
+ if (t == ROC_NPC_ITEM_TYPE_RAW) {
+ rc = -ENOTSUP;
+ goto err;
+ }
+
+ tmpl->pattern[i].type = t;
+ tmpl->pattern[i].size = sz;
+ /* Track each copy for the error path. */
+ if (roc_npc_dup_item_field(pattern[i].spec, sz, &tmpl->pattern[i].spec))
+ goto err;
+ if (tmpl->pattern[i].spec != NULL)
+ tmpl->copies[tmpl->nb_copies++] = (void *)(uintptr_t)tmpl->pattern[i].spec;
+ if (roc_npc_dup_item_field(pattern[i].last, sz, &tmpl->pattern[i].last))
+ goto err;
+ if (tmpl->pattern[i].last != NULL)
+ tmpl->copies[tmpl->nb_copies++] = (void *)(uintptr_t)tmpl->pattern[i].last;
+ if (roc_npc_dup_item_field(pattern[i].mask, sz, &tmpl->pattern[i].mask))
+ goto err;
+ if (tmpl->pattern[i].mask != NULL)
+ tmpl->copies[tmpl->nb_copies++] = (void *)(uintptr_t)tmpl->pattern[i].mask;
+ }
+ tmpl->pattern[nb_items].type = ROC_NPC_ITEM_TYPE_END;
+ tmpl->refcnt = 0;
+
+ return tmpl;
+
+err:
+ if (tmpl != NULL) {
+ for (j = 0; j < tmpl->nb_copies; j++)
+ plt_free(tmpl->copies[j]);
+ plt_free(tmpl->copies);
+ plt_free(tmpl->pattern);
+ plt_free(tmpl);
+ }
+ *errcode = rc;
+ return NULL;
+}
+
+int
+roc_npc_pattern_template_destroy(struct roc_npc *roc_npc, struct roc_npc_pattern_template *tmpl)
+{
+ uint16_t i;
+
+ PLT_SET_USED(roc_npc);
+
+ if (tmpl == NULL)
+ return 0;
+
+ if (tmpl->refcnt != 0)
+ return -EBUSY;
+
+ for (i = 0; i < tmpl->nb_copies; i++)
+ plt_free(tmpl->copies[i]);
+
+ plt_free(tmpl->copies);
+ plt_free(tmpl->pattern);
+ plt_free(tmpl);
+ return 0;
+}
+
+struct roc_npc_actions_template *
+roc_npc_actions_template_create(struct roc_npc *roc_npc,
+ const struct roc_npc_actions_template_attr *attr,
+ const struct roc_npc_action actions[],
+ const struct roc_npc_action masks[], int *errcode)
+{
+ struct roc_npc_actions_template *tmpl = NULL;
+ int i, nb_actions = 0, rc = -ENOMEM;
+ uint16_t j;
+
+ PLT_SET_USED(roc_npc);
+
+ if (attr == NULL || actions == NULL) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+
+ while (actions[nb_actions].type != ROC_NPC_ACTION_TYPE_END)
+ nb_actions++;
+
+ /* Bound template size to the fixed-size rule arrays. */
+ if (nb_actions > ROC_NPC_MAX_ACTION_COUNT - 1) {
+ *errcode = -ENOTSUP;
+ return NULL;
+ }
+
+ tmpl = plt_zmalloc(sizeof(*tmpl), 0);
+ if (tmpl == NULL)
+ goto err;
+
+ tmpl->attr = *attr;
+ tmpl->nb_actions = nb_actions;
+
+ tmpl->actions = plt_zmalloc((nb_actions + 1) * sizeof(struct roc_npc_action), 0);
+ if (tmpl->actions == NULL)
+ goto err;
+
+ tmpl->masks = plt_zmalloc((nb_actions + 1) * sizeof(struct roc_npc_action), 0);
+ if (tmpl->masks == NULL)
+ goto err;
+
+ /* Reserve up to 2 conf copies per action. */
+ tmpl->copies = plt_zmalloc(2 * (nb_actions + 1) * sizeof(void *), 0);
+ if (tmpl->copies == NULL)
+ goto err;
+
+ for (i = 0; i < nb_actions; i++) {
+ enum roc_npc_action_type t = actions[i].type;
+ int sz = roc_npc_action_conf_size(t);
+
+ if (sz < 0 && t != ROC_NPC_ACTION_TYPE_RSS) {
+ rc = -ENOTSUP;
+ goto err;
+ }
+
+ tmpl->actions[i].type = t;
+ tmpl->actions[i].rss_repte_pf_func = actions[i].rss_repte_pf_func;
+
+ /* A malformed RSS conf is invalid input, not an allocation failure. */
+ if (t == ROC_NPC_ACTION_TYPE_RSS && actions[i].conf != NULL) {
+ const struct roc_npc_action_rss *rss = actions[i].conf;
+
+ if ((rss->queue_num && rss->queue == NULL) ||
+ (rss->key_len && rss->key == NULL)) {
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+
+ tmpl->actions[i].conf = roc_npc_dup_action_conf(&actions[i]);
+ if (tmpl->actions[i].conf == NULL && actions[i].conf != NULL &&
+ (sz > 0 || t == ROC_NPC_ACTION_TYPE_RSS))
+ goto err;
+ if (tmpl->actions[i].conf != NULL)
+ tmpl->copies[tmpl->nb_copies++] = (void *)(uintptr_t)tmpl->actions[i].conf;
+
+ if (masks != NULL) {
+ if (masks[i].type != t) {
+ rc = -EINVAL;
+ goto err;
+ }
+ tmpl->masks[i].type = t;
+ /* RSS masks cannot carry a nested pointer. */
+ if (t == ROC_NPC_ACTION_TYPE_RSS) {
+ if (masks[i].conf != NULL) {
+ rc = -ENOTSUP;
+ goto err;
+ }
+ tmpl->masks[i].conf = NULL;
+ } else {
+ tmpl->masks[i].conf = roc_npc_dup_action_conf(&masks[i]);
+ if (tmpl->masks[i].conf == NULL && masks[i].conf != NULL && sz > 0)
+ goto err;
+ if (tmpl->masks[i].conf != NULL)
+ tmpl->copies[tmpl->nb_copies++] =
+ (void *)(uintptr_t)tmpl->masks[i].conf;
+ }
+ }
+ }
+
+ tmpl->actions[nb_actions].type = ROC_NPC_ACTION_TYPE_END;
+ tmpl->masks[nb_actions].type = ROC_NPC_ACTION_TYPE_END;
+ tmpl->refcnt = 0;
+
+ return tmpl;
+
+err:
+ if (tmpl != NULL) {
+ for (j = 0; j < tmpl->nb_copies; j++)
+ plt_free(tmpl->copies[j]);
+ plt_free(tmpl->copies);
+ plt_free(tmpl->masks);
+ plt_free(tmpl->actions);
+ plt_free(tmpl);
+ }
+ *errcode = rc;
+ return NULL;
+}
+
+int
+roc_npc_actions_template_destroy(struct roc_npc *roc_npc, struct roc_npc_actions_template *tmpl)
+{
+ uint16_t i;
+
+ PLT_SET_USED(roc_npc);
+
+ if (tmpl == NULL)
+ return 0;
+
+ if (tmpl->refcnt != 0)
+ return -EBUSY;
+
+ for (i = 0; i < tmpl->nb_copies; i++)
+ plt_free(tmpl->copies[i]);
+
+ plt_free(tmpl->copies);
+ plt_free(tmpl->masks);
+ plt_free(tmpl->actions);
+ plt_free(tmpl);
+ return 0;
+}
+
+struct roc_npc_template_table *
+roc_npc_template_table_create(struct roc_npc *roc_npc,
+ const struct roc_npc_template_table_attr *attr,
+ struct roc_npc_pattern_template *pattern_templates[],
+ uint8_t nb_pattern_templates,
+ struct roc_npc_actions_template *actions_templates[],
+ uint8_t nb_actions_templates, int *errcode)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_template_table *table;
+ uint32_t nb_flows;
+ uint8_t i;
+
+ if (attr == NULL) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+
+ nb_flows = attr->nb_flows;
+
+ table = plt_zmalloc(sizeof(*table), 0);
+ if (table == NULL)
+ goto enomem;
+
+ table->insertion_type = attr->insertion_type;
+ table->nb_flows = nb_flows;
+ table->nb_live = 0;
+ table->cookie = attr->cookie;
+ plt_spinlock_init(&table->lock);
+
+ table->flow_attr = attr->flow_attr;
+ table->transfer = attr->transfer;
+
+ if (attr->insertion_type == ROC_NPC_TEMPLATE_INSERTION_INDEX_WITH_PATTERN) {
+ int resp_count = 0;
+ uint32_t j;
+ int rc;
+
+ table->mcam_ids = plt_zmalloc(nb_flows * sizeof(int), 0);
+ if (table->mcam_ids == NULL)
+ goto free_index;
+ for (j = 0; j < nb_flows; j++)
+ table->mcam_ids[j] = -1;
+
+ table->flow_pool = plt_zmalloc(nb_flows * sizeof(struct roc_npc_template_flow), 0);
+ if (table->flow_pool == NULL)
+ goto free_index;
+
+ rc = roc_npc_mcam_alloc_entries(roc_npc, 0, table->mcam_ids, nb_flows,
+ NPC_MCAM_ANY_PRIO, &resp_count, false);
+ if (rc != 0 || resp_count < (int)nb_flows) {
+ for (j = 0; j < (uint32_t)resp_count; j++) {
+ if (table->mcam_ids[j] != -1)
+ roc_npc_mcam_free_entry(roc_npc, table->mcam_ids[j]);
+ }
+
+ *errcode = -ENOSPC;
+ goto free_index;
+ }
+
+ for (j = 0; j < nb_flows; j++) {
+ table->flow_pool[j].table = table;
+ table->flow_pool[j].nb_items = 0;
+ table->flow_pool[j].nb_actions = 0;
+ }
+ }
+ /* PATTERN: nothing reserved; each push allocates at the right priority. */
+
+ if (nb_pattern_templates != 0) {
+ table->pattern_templates =
+ plt_zmalloc(nb_pattern_templates * sizeof(*table->pattern_templates), 0);
+ if (table->pattern_templates == NULL)
+ goto free_table;
+ }
+ if (nb_actions_templates != 0) {
+ table->actions_templates =
+ plt_zmalloc(nb_actions_templates * sizeof(*table->actions_templates), 0);
+ if (table->actions_templates == NULL)
+ goto free_table;
+ }
+
+ for (i = 0; i < nb_pattern_templates; i++) {
+ table->pattern_templates[i] = pattern_templates[i];
+ table->pattern_templates[i]->refcnt++;
+ }
+ table->nb_pattern_templates = nb_pattern_templates;
+ for (i = 0; i < nb_actions_templates; i++) {
+ table->actions_templates[i] = actions_templates[i];
+ table->actions_templates[i]->refcnt++;
+ }
+ table->nb_actions_templates = nb_actions_templates;
+
+ /* Track the table on the port so a flush can reconcile it. */
+ TAILQ_INIT(&table->live_flows);
+ table->next = npc->flow_tables;
+ npc->flow_tables = table;
+
+ return table;
+
+free_index:
+ plt_free(table->mcam_ids);
+ plt_free(table->flow_pool);
+ table->mcam_ids = NULL;
+ table->flow_pool = NULL;
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+ if (*errcode == 0)
+ *errcode = -ENOMEM;
+ return NULL;
+
+free_table:
+ if (attr->insertion_type == ROC_NPC_TEMPLATE_INSERTION_INDEX_WITH_PATTERN) {
+ uint32_t j;
+
+ if (table->mcam_ids != NULL) {
+ for (j = 0; j < nb_flows; j++) {
+ if (table->mcam_ids[j] != -1)
+ roc_npc_mcam_free_entry(roc_npc, table->mcam_ids[j]);
+ }
+
+ plt_free(table->mcam_ids);
+ }
+ plt_free(table->flow_pool);
+ }
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+
+enomem:
+ *errcode = -ENOMEM;
+ return NULL;
+}
+
+int
+roc_npc_template_table_destroy(struct roc_npc *roc_npc, struct roc_npc_template_table *table)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_template_table **pp;
+ uint8_t i;
+
+ if (table == NULL)
+ return 0;
+
+ if (table->insertion_type == ROC_NPC_TEMPLATE_INSERTION_PATTERN) {
+ if (table->nb_live != 0)
+ return -EBUSY;
+ } else {
+ uint32_t j;
+
+ /* A live (enabled) index rule still owns its MCAM entry. */
+ if (!table->hw_released && table->flow_pool != NULL) {
+ for (j = 0; j < table->nb_flows; j++) {
+ if (table->flow_pool[j].flow.enable != 0)
+ return -EBUSY;
+ }
+ }
+
+ if (table->mcam_ids != NULL) {
+ /* Skip if a flush already freed the entries. */
+ if (!table->hw_released) {
+ for (j = 0; j < table->nb_flows; j++) {
+ int mcam_id = table->mcam_ids[j];
+
+ if (mcam_id != -1)
+ roc_npc_mcam_free_entry(roc_npc, mcam_id);
+ }
+ }
+ plt_free(table->mcam_ids);
+ }
+ plt_free(table->flow_pool);
+ }
+
+ for (i = 0; i < table->nb_pattern_templates; i++)
+ table->pattern_templates[i]->refcnt--;
+
+ for (i = 0; i < table->nb_actions_templates; i++)
+ table->actions_templates[i]->refcnt--;
+
+ /* Unlink from the port's table list. */
+ pp = &npc->flow_tables;
+ while (*pp != NULL) {
+ if (*pp == table) {
+ *pp = table->next;
+ break;
+ }
+ pp = &(*pp)->next;
+ }
+
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+ return 0;
+}
+
+struct roc_npc_template_flow *
+roc_npc_async_flow_create(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_template_table *table,
+ const struct roc_npc_item_info pattern[],
+ const struct roc_npc_action actions[], uint16_t dst_pf_func,
+ uint64_t npc_default_action, void *user_data, int *errcode)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_template_flow *flow = NULL;
+ int nb_items = 0, nb_actions = 0;
+ struct roc_npc_flow_queue *q;
+ struct roc_npc_async_op *op;
+ int k;
+
+ if (table == NULL || pattern == NULL || actions == NULL || npc->flow_queues == NULL ||
+ queue_id >= npc->nb_flow_queues) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+ if (table->insertion_type != ROC_NPC_TEMPLATE_INSERTION_PATTERN) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+
+ while (pattern[nb_items].type != ROC_NPC_ITEM_TYPE_END)
+ nb_items++;
+ while (actions[nb_actions].type != ROC_NPC_ACTION_TYPE_END)
+ nb_actions++;
+ if (nb_items > ROC_NPC_ITEM_TYPE_END || nb_actions > ROC_NPC_MAX_ACTION_COUNT - 1) {
+ *errcode = -ENOTSUP;
+ return NULL;
+ }
+
+ q = &npc->flow_queues[queue_id];
+ if (q->head - q->tail >= q->size) {
+ *errcode = -ENOSPC;
+ return NULL;
+ }
+
+ flow = plt_zmalloc(sizeof(*flow), 0);
+ if (flow == NULL) {
+ *errcode = -ENOMEM;
+ return NULL;
+ }
+ flow->table = table;
+ flow->nb_items = nb_items;
+ flow->nb_actions = nb_actions;
+ flow->items = plt_zmalloc((nb_items + 1) * sizeof(struct roc_npc_item_info), 0);
+ if (flow->items == NULL) {
+ *errcode = -ENOMEM;
+ goto err;
+ }
+
+ flow->actions = plt_zmalloc((nb_actions + 1) * sizeof(struct roc_npc_action), 0);
+ if (flow->actions == NULL) {
+ *errcode = -ENOMEM;
+ goto err;
+ }
+
+ /* Deep-copy each item so push() owns a self-contained rule. */
+ for (k = 0; k < nb_items; k++) {
+ uint32_t sz = pattern[k].size;
+
+ flow->items[k].type = pattern[k].type;
+ flow->items[k].size = sz;
+ if (roc_npc_dup_item_field(pattern[k].spec, sz, &flow->items[k].spec) ||
+ roc_npc_dup_item_field(pattern[k].last, sz, &flow->items[k].last) ||
+ roc_npc_dup_item_field(pattern[k].mask, sz, &flow->items[k].mask)) {
+ *errcode = -ENOMEM;
+ goto err;
+ }
+ }
+ flow->items[nb_items].type = ROC_NPC_ITEM_TYPE_END;
+
+ /* Deep-copy each action conf. */
+ for (k = 0; k < nb_actions; k++) {
+ int sz = roc_npc_action_conf_size(actions[k].type);
+
+ flow->actions[k].type = actions[k].type;
+ flow->actions[k].rss_repte_pf_func = actions[k].rss_repte_pf_func;
+
+ /* A malformed RSS conf (non-zero length with a NULL nested
+ * pointer) is invalid input, not an allocation failure.
+ */
+ if (actions[k].type == ROC_NPC_ACTION_TYPE_RSS && actions[k].conf != NULL) {
+ const struct roc_npc_action_rss *rss = actions[k].conf;
+
+ if ((rss->queue_num && rss->queue == NULL) ||
+ (rss->key_len && rss->key == NULL)) {
+ *errcode = -EINVAL;
+ goto err;
+ }
+ }
+
+ flow->actions[k].conf = roc_npc_dup_action_conf(&actions[k]);
+ /* A NULL copy from a non-NULL conf now means the deep copy failed. */
+ if (flow->actions[k].conf == NULL && actions[k].conf != NULL &&
+ (sz > 0 || actions[k].type == ROC_NPC_ACTION_TYPE_RSS)) {
+ *errcode = -ENOMEM;
+ goto err;
+ }
+ }
+ flow->actions[nb_actions].type = ROC_NPC_ACTION_TYPE_END;
+
+ /* Capture the rule attributes used by push() to program hardware. */
+ flow->attr = table->flow_attr;
+ if (table->transfer)
+ flow->attr.ingress = 1;
+ flow->dst_pf_func = dst_pf_func;
+ flow->npc_default_action = npc_default_action;
+ /* Capture the caller's RSS flow key so batched rules keep their own. */
+ flow->flowkey_cfg = roc_npc->flowkey_cfg_state;
+
+ op = &q->ops[q->head & (q->size - 1)];
+ op->flow = flow;
+ op->is_create = true;
+ op->is_update = false;
+ op->user_data = user_data;
+ op->done = false;
+ op->rc = 0;
+ q->head++;
+
+ return flow;
+
+err:
+ roc_npc_template_flow_free_rule(flow);
+ plt_free(flow);
+ return NULL;
+}
+
+int
+roc_npc_async_flow_destroy(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_template_flow *flow, void *user_data)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_flow_queue *q;
+ struct roc_npc_async_op *op;
+
+ if (flow == NULL || npc->flow_queues == NULL || queue_id >= npc->nb_flow_queues)
+ return -EINVAL;
+
+ q = &npc->flow_queues[queue_id];
+ if (q->head - q->tail >= q->size)
+ return -ENOSPC;
+
+ op = &q->ops[q->head & (q->size - 1)];
+ op->flow = flow;
+ op->is_create = false;
+ op->is_update = false;
+ op->user_data = user_data;
+ op->done = false;
+ op->rc = 0;
+ q->head++;
+
+ return 0;
+}
+
+/* Create a rule at an app-chosen index in an INDEX_WITH_PATTERN table. */
+struct roc_npc_template_flow *
+roc_npc_async_flow_create_by_index_with_pattern(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_template_table *table,
+ uint32_t rule_index,
+ const struct roc_npc_item_info pattern[],
+ const struct roc_npc_action actions[],
+ void *user_data, int *errcode)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_template_flow *flow;
+ uint32_t mark_save, vtag_save;
+ struct roc_npc_flow_queue *q;
+ struct roc_npc_async_op *op;
+ struct roc_npc_attr attr;
+ int rc;
+
+ if (table == NULL || pattern == NULL || actions == NULL || npc->flow_queues == NULL ||
+ queue_id >= npc->nb_flow_queues) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+ if (table->insertion_type != ROC_NPC_TEMPLATE_INSERTION_INDEX_WITH_PATTERN) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+ if (rule_index >= table->nb_flows) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+
+ /* Reject creates into a flushed table. */
+ if (table->hw_released) {
+ *errcode = -EINVAL;
+ return NULL;
+ }
+
+ q = &npc->flow_queues[queue_id];
+ if (q->head - q->tail >= q->size) {
+ *errcode = -ENOSPC;
+ return NULL;
+ }
+
+ /* App owns the index: use the reserved pool slot directly. */
+ flow = &table->flow_pool[rule_index];
+ flow->table = table;
+ flow->slot = rule_index;
+ memset(&flow->flow, 0, sizeof(flow->flow));
+
+ attr = table->flow_attr;
+ if (table->transfer)
+ attr.ingress = 1;
+
+ /* Snapshot MARK/VLAN refcounts before parsing. */
+ mark_save = npc->mark_actions;
+ vtag_save = npc->vtag_strip_actions;
+
+ rc = roc_npc_flow_parse(roc_npc, &attr, pattern, actions, &flow->flow);
+ if (rc != 0) {
+ npc->mark_actions = mark_save;
+ npc->vtag_strip_actions = vtag_save;
+ *errcode = rc;
+ return NULL;
+ }
+
+ roc_npc_mcam_write_rx_finalize(roc_npc, &flow->flow);
+ flow->flow.mcam_id = table->mcam_ids[rule_index];
+ flow->flow.enable = 1;
+
+ op = &q->ops[q->head & (q->size - 1)];
+ op->flow = flow;
+ op->is_create = true;
+ op->is_update = false;
+ op->user_data = user_data;
+ op->done = false;
+ op->rc = 0;
+ q->head++;
+
+ return flow;
+}
+
+/* Update a rule's actions in place and swap them in at push. */
+int
+roc_npc_async_flow_actions_update(struct roc_npc *roc_npc, uint32_t queue_id,
+ struct roc_npc_template_flow *flow,
+ const struct roc_npc_action actions[], void *user_data)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_item_info end_pattern[1];
+ struct roc_npc_template_table *table;
+ struct roc_npc_flow *fptr, tmpflow;
+ uint32_t mark_save, vtag_save;
+ struct roc_npc_flow_queue *q;
+ struct roc_npc_async_op *op;
+ struct roc_npc_attr attr;
+ uint16_t old_match_id;
+ bool old_vtag;
+ int rc;
+
+ if (flow == NULL || actions == NULL || npc->flow_queues == NULL ||
+ queue_id >= npc->nb_flow_queues)
+ return -EINVAL;
+
+ table = flow->table;
+ if (table == NULL)
+ return -EINVAL;
+ /* PATTERN rules have roc_flow only after create commit. */
+ if (table->insertion_type == ROC_NPC_TEMPLATE_INSERTION_PATTERN && flow->roc_flow == NULL)
+ return -EINVAL;
+
+ /* Reject a second update until the pending one is flushed. */
+ if (flow->pending_update.pending)
+ return -EBUSY;
+
+ q = &npc->flow_queues[queue_id];
+ if (q->head - q->tail >= q->size)
+ return -ENOSPC;
+
+ end_pattern[0].type = ROC_NPC_ITEM_TYPE_END;
+
+ attr = table->flow_attr;
+ if (table->transfer)
+ attr.ingress = 1;
+
+ memset(&tmpflow, 0, sizeof(tmpflow));
+
+ /* Resolve the live flow before parsing the new actions. */
+ fptr = flow->roc_flow ? flow->roc_flow : &flow->flow;
+ old_match_id = (fptr->npc_action >> NPC_RX_ACT_MATCH_OFFSET) & NPC_RX_ACT_MATCH_MASK;
+ old_vtag = fptr->nix_intf == ROC_NPC_INTF_RX && fptr->vtag_action != 0;
+ mark_save = npc->mark_actions;
+ vtag_save = npc->vtag_strip_actions;
+
+ rc = roc_npc_flow_parse(roc_npc, &attr, end_pattern, actions, &tmpflow);
+ if (rc != 0) {
+ /* Undo any refcount bumps a partial parse made on failure. */
+ npc->mark_actions = mark_save;
+ npc->vtag_strip_actions = vtag_save;
+ return rc;
+ }
+
+ /* Stash the parsed update so push can commit or roll it back. */
+ flow->pending_update.npc_action = tmpflow.npc_action;
+ flow->pending_update.npc_action2 = tmpflow.npc_action2;
+ flow->pending_update.vtag_action = tmpflow.vtag_action;
+ flow->pending_update.recv_queue = tmpflow.recv_queue;
+ flow->pending_update.old_match_id = old_match_id != 0;
+ flow->pending_update.old_vtag = old_vtag;
+ flow->pending_update.new_match_id =
+ ((tmpflow.npc_action >> NPC_RX_ACT_MATCH_OFFSET) & NPC_RX_ACT_MATCH_MASK) != 0;
+ flow->pending_update.new_vtag =
+ tmpflow.nix_intf == ROC_NPC_INTF_RX && tmpflow.vtag_action != 0;
+ flow->pending_update.pending = true;
+
+ op = &q->ops[q->head & (q->size - 1)];
+ op->flow = flow;
+ op->is_create = false;
+ op->is_update = true;
+ op->user_data = user_data;
+ op->done = false;
+ op->rc = 0;
+ q->head++;
+
+ return 0;
+}
+
+/* Commit one retained create to hardware. */
+static int
+roc_npc_flow_commit_create(struct roc_npc *roc_npc, struct roc_npc_template_flow *flow)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ uint32_t mark_save, vtag_save;
+ struct roc_npc_flow *rflow;
+ int errcode = 0;
+
+ /* Restore this rule's RSS flow key before programming (see create). */
+ roc_npc->flowkey_cfg_state = flow->flowkey_cfg;
+
+ /* Restore MARK/VLAN refcounts if create fails. */
+ mark_save = npc->mark_actions;
+ vtag_save = npc->vtag_strip_actions;
+
+ rflow = roc_npc_flow_create(roc_npc, &flow->attr, flow->items, flow->actions,
+ flow->dst_pf_func, flow->npc_default_action, &errcode);
+ if (rflow == NULL) {
+ npc->mark_actions = mark_save;
+ npc->vtag_strip_actions = vtag_save;
+ return errcode ? errcode : -EINVAL;
+ }
+
+ flow->roc_flow = rflow;
+ return 0;
+}
+
+/* Apply a pending actions update and roll it back on failure. */
+static int
+roc_npc_flow_apply_pattern_update(struct roc_npc *roc_npc, struct npc *npc,
+ struct roc_npc_template_flow *flow)
+{
+ struct roc_npc_flow *fptr = flow->roc_flow;
+ uint64_t save_action2, save_action, save_vtag;
+ uint32_t save_rq;
+ int rc;
+
+ if (fptr == NULL)
+ return -EINVAL;
+
+ save_action2 = fptr->npc_action2;
+ save_action = fptr->npc_action;
+ save_vtag = fptr->vtag_action;
+ save_rq = fptr->recv_queue;
+
+ fptr->npc_action = flow->pending_update.npc_action;
+ fptr->npc_action2 = flow->pending_update.npc_action2;
+ fptr->vtag_action = flow->pending_update.vtag_action;
+ fptr->recv_queue = flow->pending_update.recv_queue;
+ fptr->enable = 1;
+ roc_npc_mcam_write_rx_finalize(roc_npc, fptr);
+ rc = roc_npc_mcam_write_entry(roc_npc, fptr);
+ if (rc == 0) {
+ /* Drop the old action set's refcount contribution. */
+ if (flow->pending_update.old_match_id && npc->mark_actions > 0)
+ npc->mark_actions--;
+ if (flow->pending_update.old_vtag && npc->vtag_strip_actions > 0)
+ npc->vtag_strip_actions--;
+ } else {
+ /* Restore the live entry and undo the new contribution. */
+ fptr->npc_action = save_action;
+ fptr->npc_action2 = save_action2;
+ fptr->vtag_action = save_vtag;
+ fptr->recv_queue = save_rq;
+ if (flow->pending_update.new_match_id && npc->mark_actions > 0)
+ npc->mark_actions--;
+ if (flow->pending_update.new_vtag && npc->vtag_strip_actions > 0)
+ npc->vtag_strip_actions--;
+ }
+ return rc;
+}
+
+int
+roc_npc_flow_push(struct roc_npc *roc_npc, uint32_t queue_id)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_flow_queue *q;
+ uint32_t i;
+
+ if (npc->flow_queues == NULL || queue_id >= npc->nb_flow_queues)
+ return -EINVAL;
+
+ q = &npc->flow_queues[queue_id];
+
+ for (i = q->pushed; i != q->head; i++) {
+ struct roc_npc_async_op *op = &q->ops[i & (q->size - 1)];
+ struct roc_npc_template_flow *flow = op->flow;
+ struct roc_npc_template_table *table = flow->table;
+ int rc;
+
+ if (table->insertion_type == ROC_NPC_TEMPLATE_INSERTION_PATTERN) {
+ if (op->is_update) {
+ rc = roc_npc_flow_apply_pattern_update(roc_npc, npc, flow);
+ /* Clear the pending update after push. */
+ flow->pending_update.pending = false;
+ } else if (op->is_create) {
+ rc = roc_npc_flow_commit_create(roc_npc, flow);
+ /* Rule arrays are consumed on push. */
+ roc_npc_template_flow_free_rule(flow);
+ if (rc == 0) {
+ plt_spinlock_lock(&table->lock);
+ table->nb_live++;
+ TAILQ_INSERT_TAIL(&table->live_flows, flow, link);
+ plt_spinlock_unlock(&table->lock);
+ }
+ /* Keep the handle valid on failure. */
+ } else if (flow->roc_flow == NULL) {
+ /* Never committed (or a failed create): just free it. */
+ rc = 0;
+ roc_npc_template_flow_free_rule(flow);
+ plt_free(flow);
+ } else {
+ uint16_t match_id;
+ uint8_t nix_intf;
+ bool had_vtag;
+
+ /* Capture match_id and interface before destroy. */
+ match_id = (flow->roc_flow->npc_action >> NPC_RX_ACT_MATCH_OFFSET) &
+ NPC_RX_ACT_MATCH_MASK;
+ nix_intf = flow->roc_flow->nix_intf;
+ had_vtag = flow->roc_flow->vtag_action != 0;
+
+ rc = roc_npc_flow_destroy(roc_npc, flow->roc_flow);
+ if (rc == 0 && match_id)
+ roc_npc_mark_actions_sub_return(roc_npc, 1);
+ if (rc == 0 && nix_intf == ROC_NPC_INTF_RX && had_vtag)
+ roc_npc_vtag_actions_sub_return(roc_npc, 1);
+ if (rc == 0) {
+ plt_spinlock_lock(&table->lock);
+ table->nb_live--;
+ TAILQ_REMOVE(&table->live_flows, flow, link);
+ plt_spinlock_unlock(&table->lock);
+ flow->roc_flow = NULL;
+ roc_npc_template_flow_free_rule(flow);
+ plt_free(flow);
+ }
+ /* Keep the handle on failure so the caller can retry. */
+ }
+ } else {
+ /* INDEX_WITH_PATTERN: entry fixed at the app-chosen index. */
+ struct roc_npc_flow *fptr = &flow->flow;
+ bool destroy = !op->is_create && !op->is_update;
+ uint16_t match_id = (fptr->npc_action >> NPC_RX_ACT_MATCH_OFFSET) &
+ NPC_RX_ACT_MATCH_MASK;
+ uint64_t save_action2 = fptr->npc_action2;
+ uint64_t save_action = fptr->npc_action;
+ uint64_t save_vtag = fptr->vtag_action;
+ uint32_t save_rq = fptr->recv_queue;
+
+ /* Swap in the stashed update before writing hardware. */
+ if (op->is_update) {
+ fptr->npc_action = flow->pending_update.npc_action;
+ fptr->npc_action2 = flow->pending_update.npc_action2;
+ fptr->vtag_action = flow->pending_update.vtag_action;
+ fptr->recv_queue = flow->pending_update.recv_queue;
+ }
+
+ /* Set enable before writing hardware. */
+ fptr->enable = destroy ? 0 : 1;
+ if (op->is_update)
+ roc_npc_mcam_write_rx_finalize(roc_npc, fptr);
+ rc = roc_npc_mcam_write_entry(roc_npc, fptr);
+ if (rc != 0) {
+ /* Restore the previous enable state on failure. */
+ fptr->enable = destroy ? 1 : 0;
+ /* Roll back the swapped-in action words too. */
+ if (op->is_update) {
+ fptr->npc_action = save_action;
+ fptr->npc_action2 = save_action2;
+ fptr->vtag_action = save_vtag;
+ fptr->recv_queue = save_rq;
+ }
+ }
+
+ /* Commit or roll back the update refcount change. */
+ if (op->is_update) {
+ if (rc == 0) {
+ if (flow->pending_update.old_match_id &&
+ npc->mark_actions > 0)
+ npc->mark_actions--;
+ if (flow->pending_update.old_vtag &&
+ npc->vtag_strip_actions > 0)
+ npc->vtag_strip_actions--;
+ } else {
+ if (flow->pending_update.new_match_id &&
+ npc->mark_actions > 0)
+ npc->mark_actions--;
+ if (flow->pending_update.new_vtag &&
+ npc->vtag_strip_actions > 0)
+ npc->vtag_strip_actions--;
+ }
+ flow->pending_update.pending = false;
+ }
+
+ /* Release the COUNT counter on destroy. */
+ if (destroy && rc == 0 && fptr->use_ctr &&
+ fptr->ctr_id != NPC_COUNTER_NONE) {
+ roc_npc_mcam_free_counter(roc_npc, fptr->ctr_id);
+ fptr->ctr_id = NPC_COUNTER_NONE;
+ }
+
+ /* Balance the mark_actions refcount for MARK/FLAG rules. */
+ if (destroy && rc == 0 && match_id)
+ roc_npc_mark_actions_sub_return(roc_npc, 1);
+
+ /* Balance the vtag_strip_actions refcount for RX VLAN strip rules. */
+ if (destroy && rc == 0 && fptr->nix_intf == ROC_NPC_INTF_RX &&
+ fptr->vtag_action != 0)
+ roc_npc_vtag_actions_sub_return(roc_npc, 1);
+ }
+
+ op->rc = rc;
+ op->done = true;
+ }
+
+ q->pushed = q->head;
+ return 0;
+}
+
+int
+roc_npc_flow_pull(struct roc_npc *roc_npc, uint32_t queue_id, struct roc_npc_flow_op_result res[],
+ uint16_t n_res)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_flow_queue *q;
+ uint16_t cnt = 0;
+
+ if (npc->flow_queues == NULL || queue_id >= npc->nb_flow_queues)
+ return -EINVAL;
+
+ q = &npc->flow_queues[queue_id];
+
+ /* Return ops that were pushed but not yet reported. */
+ while (q->tail != q->pushed && cnt < n_res) {
+ struct roc_npc_async_op *op = &q->ops[q->tail & (q->size - 1)];
+
+ res[cnt].user_data = op->user_data;
+ res[cnt].rc = op->rc;
+ cnt++;
+ q->tail++;
+ }
+
+ return cnt;
+}
+
+/* Resolve a candidate handle to a template flow or a sync flow. */
+bool
+roc_npc_async_flow_resolve(struct roc_npc *roc_npc, void *handle, struct roc_npc_flow **flow)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ struct roc_npc_template_flow *cand = handle;
+ struct roc_npc_template_table *table;
+ uint16_t i;
+
+ if (handle == NULL || flow == NULL)
+ return false;
+
+ for (table = npc->flow_tables; table != NULL; table = table->next) {
+ struct roc_npc_template_flow *match;
+
+ /* Integer range check: pointer comparison across objects is UB. */
+ if (table->flow_pool != NULL) {
+ uintptr_t p = (uintptr_t)cand;
+ uintptr_t base = (uintptr_t)table->flow_pool;
+ uintptr_t end = (uintptr_t)(table->flow_pool + table->nb_flows);
+
+ if (p >= base && p < end) {
+ *flow = &cand->flow;
+ return true;
+ }
+ }
+
+ plt_spinlock_lock(&table->lock);
+ /* clang-format off */
+ TAILQ_FOREACH(match, &table->live_flows, link) {
+ if (match == cand) {
+ plt_spinlock_unlock(&table->lock);
+ *flow = cand->roc_flow;
+ return true;
+ }
+ }
+ /* clang-format on */
+ plt_spinlock_unlock(&table->lock);
+ }
+
+ /* Handle still queued: a PATTERN rule not yet committed. */
+ for (i = 0; i < npc->nb_flow_queues; i++) {
+ struct roc_npc_flow_queue *q = &npc->flow_queues[i];
+ uint32_t j;
+
+ for (j = q->tail; j != q->head; j++) {
+ struct roc_npc_async_op *op = &q->ops[j & (q->size - 1)];
+
+ if (op->flow == cand) {
+ *flow = cand->roc_flow;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* Return the table cookie used to recover update context. */
+void *
+roc_npc_async_flow_table_cookie(struct roc_npc_template_flow *flow)
+{
+ if (flow == NULL || flow->table == NULL)
+ return NULL;
+
+ return flow->table->cookie;
+}
+
+/* Reconcile template-table bookkeeping after flush or port close. */
+void
+npc_template_tables_flush_reconcile(struct npc *npc)
+{
+ struct roc_npc_template_table *table;
+ struct roc_npc_template_flow *flow;
+ struct roc_npc_flow *fptr;
+ uint16_t match_id;
+ uint32_t j;
+
+ for (table = npc->flow_tables; table != NULL; table = table->next) {
+ plt_spinlock_lock(&table->lock);
+ while ((flow = TAILQ_FIRST(&table->live_flows)) != NULL) {
+ TAILQ_REMOVE(&table->live_flows, flow, link);
+ /* roc_flow is already freed by free_all_resources. */
+ roc_npc_template_flow_free_rule(flow);
+ plt_free(flow);
+ }
+
+ /* INDEX_WITH_PATTERN rules live in flow_pool[]; clear stale state. */
+ if (table->flow_pool != NULL) {
+ for (j = 0; j < table->nb_flows; j++) {
+ fptr = &table->flow_pool[j].flow;
+
+ if (fptr->enable == 0)
+ continue;
+
+ match_id = (fptr->npc_action >> NPC_RX_ACT_MATCH_OFFSET) &
+ NPC_RX_ACT_MATCH_MASK;
+ if (match_id && npc->mark_actions > 0)
+ npc->mark_actions--;
+
+ if (fptr->nix_intf == ROC_NPC_INTF_RX && fptr->vtag_action != 0 &&
+ npc->vtag_strip_actions > 0)
+ npc->vtag_strip_actions--;
+
+ memset(fptr, 0, sizeof(*fptr));
+ fptr->ctr_id = NPC_COUNTER_NONE;
+ }
+ }
+
+ table->nb_live = 0;
+ table->hw_released = true;
+ plt_spinlock_unlock(&table->lock);
+ }
+}
diff --git a/drivers/common/cnxk/roc_npc_mcam.c b/drivers/common/cnxk/roc_npc_mcam.c
index 286cfd2dbd..34665f1bc3 100644
--- a/drivers/common/cnxk/roc_npc_mcam.c
+++ b/drivers/common/cnxk/roc_npc_mcam.c
@@ -714,7 +714,6 @@ npc_mcam_fetch_kex_cfg(struct npc *npc)
struct mbox *mbox = mbox_get(npc->mbox);
int rc = 0;
-
if (!roc_model_is_cn20k()) {
mbox_alloc_msg_npc_get_kex_cfg(mbox);
rc = mbox_process_msg(mbox, (void *)&kex_rsp);
@@ -786,6 +785,46 @@ npc_mcam_set_channel(struct roc_npc_flow *flow, struct npc_cn20k_mcam_write_entr
flow->mcam_mask[0] |= (uint64_t)mask;
}
+/* Fold RQ into the action word and program the RX channel into kw[0],
+ * matching the RX steps the sync path does in npc_mcam_alloc_and_write().
+ */
+void
+roc_npc_mcam_write_rx_finalize(struct roc_npc *roc_npc, struct roc_npc_flow *flow)
+{
+ struct npc *npc = roc_npc_to_npc_priv(roc_npc);
+ uint16_t chan, mask;
+ uint64_t op;
+
+ if (flow->nix_intf != NIX_INTF_RX)
+ return;
+
+ /* Set the RQ in the action index field, but only for UCAST* ops that
+ * use it as a receive queue. Clear then set so a re-finalize on an
+ * async QUEUE update leaves no stale bits.
+ */
+ op = flow->npc_action & 0xFULL;
+ if (op == NIX_RX_ACTIONOP_UCAST || op == NIX_RX_ACTIONOP_UCAST_IPSEC ||
+ op == NIX_RX_ACTIONOP_UCAST_CPT) {
+ flow->npc_action &= ~GENMASK_ULL(39, 20);
+ flow->npc_action |= ((uint64_t)flow->recv_queue << 20) & GENMASK_ULL(39, 20);
+ }
+
+ /* Program the RX channel into kw[0]. */
+ chan = npc->channel;
+ mask = BIT_ULL(12) - 1;
+
+ flow->mcam_data[0] &= ~(GENMASK(11, 0));
+ flow->mcam_mask[0] &= ~(GENMASK(11, 0));
+
+ if (roc_model_runtime_is_cn10k() && !roc_npc_action_is_rx_inline(flow->npc_action)) {
+ chan = (chan & NIX_CHAN_CPT_X2P_MASK);
+ mask = (mask & NIX_CHAN_CPT_X2P_MASK);
+ }
+
+ flow->mcam_data[0] |= (uint64_t)chan;
+ flow->mcam_mask[0] |= (uint64_t)mask;
+}
+
#define NPC_PF_FUNC_WIDTH 2
#define NPC_KEX_PF_FUNC_MASK 0xFFFF
@@ -1430,5 +1469,9 @@ npc_flow_free_all_resources(struct npc *npc)
plt_free(flow);
}
}
+
+ /* Reconcile template-table bookkeeping for all teardown paths. */
+ npc_template_tables_flush_reconcile(npc);
+
return rc;
}
diff --git a/drivers/common/cnxk/roc_npc_priv.h b/drivers/common/cnxk/roc_npc_priv.h
index c7e7d2d8f6..c6ab033d9f 100644
--- a/drivers/common/cnxk/roc_npc_priv.h
+++ b/drivers/common/cnxk/roc_npc_priv.h
@@ -427,6 +427,99 @@ struct npc_age_flow_entry {
TAILQ_HEAD(npc_age_flow_list_head, npc_age_flow_entry);
+/* Queue-based async template flow engine types. */
+
+/* One in-flight async op in a queue ring. */
+struct roc_npc_async_op {
+ struct roc_npc_template_flow *flow; /**< Rule the op acts on. */
+ void *user_data; /**< Echoed back by flow_pull. */
+ bool is_create; /**< true = create, false = destroy. */
+ bool is_update; /**< true = in-place actions rewrite. */
+ bool done; /**< Set once pushed to hardware. */
+ int rc; /**< Result captured at push time. */
+};
+
+/* Fixed-size op ring; head/pushed/tail are free-running indices. */
+struct roc_npc_flow_queue {
+ struct roc_npc_async_op *ops; /**< size entries, power-of-two. */
+ uint32_t size; /**< Ring capacity (power of two). */
+ uint32_t head; /**< Next slot to enqueue into. */
+ uint32_t pushed; /**< Ops up to here are committed. */
+ uint32_t tail; /**< Next slot to return via pull. */
+};
+
+/* A deep-copied, retained rule awaiting (or holding) a hardware entry. */
+struct roc_npc_template_flow {
+ struct roc_npc_template_table *table; /**< Owning table. */
+ struct roc_npc_flow *roc_flow; /**< HW flow (PATTERN, once committed). */
+ struct roc_npc_item_info *items; /**< Deep-copied merged pattern. */
+ struct roc_npc_action *actions; /**< Deep-copied merged actions. */
+ struct roc_npc_flow flow; /**< Embedded HW flow (INDEX path). */
+ struct roc_npc_attr attr; /**< Priority / ingress / egress. */
+ uint64_t npc_default_action; /**< Resolved port default action. */
+ uint32_t flowkey_cfg; /**< RSS flow key captured at create. */
+ uint32_t slot; /**< Pre-reserved index (INDEX path). */
+ uint16_t dst_pf_func; /**< Resolved destination pf_func. */
+ uint16_t nb_items; /**< Item count (excluding END). */
+ uint16_t nb_actions; /**< Action count (excluding END). */
+ /* Pending in-place actions update, applied at push. */
+ struct {
+ uint64_t npc_action;
+ uint64_t npc_action2;
+ uint64_t vtag_action;
+ uint32_t recv_queue;
+ bool pending; /**< An update is enqueued, awaiting push. */
+ bool old_match_id; /**< Old action set carried a mark/match_id. */
+ bool old_vtag; /**< Old action set carried RX vtag strip. */
+ bool new_match_id; /**< New action set carries a mark/match_id. */
+ bool new_vtag; /**< New action set carries RX vtag strip. */
+ } pending_update;
+ TAILQ_ENTRY(roc_npc_template_flow) link;
+};
+
+TAILQ_HEAD(roc_npc_template_flow_list, roc_npc_template_flow);
+
+/* Pattern template blueprint. */
+struct roc_npc_pattern_template {
+ struct roc_npc_pattern_template_attr attr;
+ struct roc_npc_item_info *pattern; /**< nb_items + END terminator. */
+ void **copies; /**< Tracked payload allocations. */
+ uint16_t nb_copies; /**< Entries used in copies[]. */
+ uint16_t nb_items; /**< Item count (excluding END). */
+ uint32_t refcnt; /**< Tables referencing this template. */
+};
+
+/* Actions template blueprint. */
+struct roc_npc_actions_template {
+ struct roc_npc_actions_template_attr attr;
+ struct roc_npc_action *actions; /**< nb_actions + END terminator. */
+ struct roc_npc_action *masks; /**< Per-action masks (optional). */
+ void **copies; /**< Tracked conf allocations. */
+ uint16_t nb_copies; /**< Entries used in copies[]. */
+ uint16_t nb_actions; /**< Action count (excluding END). */
+ uint32_t refcnt; /**< Tables referencing this template. */
+};
+
+/* Template table and rule storage. */
+struct roc_npc_template_table {
+ int *mcam_ids; /**< Reserved entries (index). */
+ struct roc_npc_template_flow *flow_pool; /**< Per-index rule slots. */
+ struct roc_npc_pattern_template **pattern_templates;
+ struct roc_npc_actions_template **actions_templates;
+ struct roc_npc_template_flow_list live_flows; /**< Live pattern rules. */
+ struct roc_npc_template_table *next; /**< Port table list link. */
+ void *cookie; /**< Opaque consumer context. */
+ struct roc_npc_attr flow_attr; /**< Priority/ingress/egress. */
+ enum roc_npc_template_table_insertion_type insertion_type;
+ plt_spinlock_t lock; /**< Guards live_flows/nb_live. */
+ uint32_t nb_flows; /**< Max rules the table holds. */
+ uint32_t nb_live; /**< Live pattern rules count. */
+ uint32_t transfer; /**< Transfer (port) rule. */
+ uint8_t nb_pattern_templates; /**< pattern_templates entries. */
+ uint8_t nb_actions_templates; /**< actions_templates entries. */
+ bool hw_released; /**< Entries freed by a flush. */
+};
+
struct npc {
struct mbox *mbox; /* Mbox */
uint64_t keyx_supp_nmask[NPC_MAX_INTF]; /* nibble mask */
@@ -459,6 +552,10 @@ struct npc {
struct plt_bitmap *rss_grp_entries;
struct npc_flow_list ipsec_list;
uint8_t enable_debug;
+ /* Queue-based async template flow state owned by roc. */
+ struct roc_npc_flow_queue *flow_queues;
+ uint16_t nb_flow_queues;
+ struct roc_npc_template_table *flow_tables;
};
#define NPC_HASH_FIELD_LEN 16
@@ -533,6 +630,7 @@ int npc_mcam_fetch_hw_cap(struct npc *npc, uint8_t *npc_hw_cap);
int npc_get_free_mcam_entry(struct mbox *mbox, struct roc_npc_flow *flow, struct npc *npc);
void npc_delete_prio_list_entry(struct npc *npc, struct roc_npc_flow *flow);
int npc_flow_free_all_resources(struct npc *npc);
+void npc_template_tables_flush_reconcile(struct npc *npc);
const struct roc_npc_item_info *
npc_parse_skip_void_and_any_items(const struct roc_npc_item_info *pattern);
int npc_program_mcam(struct npc *npc, struct npc_parse_state *pst, bool mcam_alloc);
diff --git a/drivers/common/cnxk/roc_npc_utils.c b/drivers/common/cnxk/roc_npc_utils.c
index 8e83b8662d..7ae877aa98 100644
--- a/drivers/common/cnxk/roc_npc_utils.c
+++ b/drivers/common/cnxk/roc_npc_utils.c
@@ -1088,7 +1088,7 @@ npc_get_free_mcam_entry(struct mbox *mbox, struct roc_npc_flow *flow, struct npc
flow->key_type);
if (rc) {
plt_npc_dbg("npc: failed to allocate MCAM entry.");
- return rc;
+ goto err;
}
new_entry->flow = flow;
@@ -1096,7 +1096,7 @@ npc_get_free_mcam_entry(struct mbox *mbox, struct roc_npc_flow *flow, struct npc
rc = npc_alloc_mcam_by_ref_entry(mbox, flow, npc, &rsp_local);
if (rc)
- return rc;
+ goto err;
new_entry->flow = flow;
diff --git a/drivers/common/cnxk/roc_platform_base_symbols.c b/drivers/common/cnxk/roc_platform_base_symbols.c
index 00746abc06..de8de910f6 100644
--- a/drivers/common/cnxk/roc_platform_base_symbols.c
+++ b/drivers/common/cnxk/roc_platform_base_symbols.c
@@ -487,6 +487,7 @@ RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_mcam_enable_all_entries)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_mcam_alloc_entry)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_mcam_ena_dis_entry)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_mcam_write_entry)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_mcam_write_rx_finalize)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_get_low_priority_mcam)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_profile_name_get)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_kex_capa_get)
@@ -505,6 +506,21 @@ RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_aged_flow_ctx_get)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_defrag_mcam_banks)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_get_key_type)
RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_flow_mcam_dump)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_flow_configure)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_pattern_template_create)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_pattern_template_destroy)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_actions_template_create)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_actions_template_destroy)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_template_table_create)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_template_table_destroy)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_async_flow_create)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_async_flow_destroy)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_async_flow_create_by_index_with_pattern)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_async_flow_actions_update)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_flow_push)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_flow_pull)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_async_flow_resolve)
+RTE_EXPORT_INTERNAL_SYMBOL(roc_npc_async_flow_table_cookie)
RTE_EXPORT_INTERNAL_SYMBOL(roc_re_ml_zeta_get)
RTE_EXPORT_INTERNAL_SYMBOL(roc_re_ml_zeta_put)
RTE_EXPORT_INTERNAL_SYMBOL(roc_ree_queues_attach)
diff --git a/drivers/net/cnxk/cn10k_ethdev.c b/drivers/net/cnxk/cn10k_ethdev.c
index 23a2341c8b..bcf4e23286 100644
--- a/drivers/net/cnxk/cn10k_ethdev.c
+++ b/drivers/net/cnxk/cn10k_ethdev.c
@@ -5,6 +5,7 @@
#include "cn10k_flow.h"
#include "cn10k_rx.h"
#include "cn10k_tx.h"
+#include "cnxk_flow.h"
static uint16_t
nix_rx_offload_flags(struct rte_eth_dev *eth_dev)
@@ -916,8 +917,27 @@ npc_flow_ops_override(void)
cnxk_flow_ops.create = cn10k_flow_create;
cnxk_flow_ops.destroy = cn10k_flow_destroy;
cnxk_flow_ops.info_get = cn10k_flow_info_get;
+
+ cnxk_flow_ops.configure = cnxk_flow_configure;
+ cnxk_flow_ops.pattern_template_create = cnxk_flow_pattern_template_create;
+ cnxk_flow_ops.pattern_template_destroy = cnxk_flow_pattern_template_destroy;
+ cnxk_flow_ops.actions_template_create = cnxk_flow_actions_template_create;
+ cnxk_flow_ops.actions_template_destroy = cnxk_flow_actions_template_destroy;
+ cnxk_flow_ops.template_table_create = cnxk_flow_template_table_create;
+ cnxk_flow_ops.template_table_destroy = cnxk_flow_template_table_destroy;
}
+/* Fast-path (async) flow ops. Installed per-port in cn10k_nix_probe(). */
+static const struct rte_flow_fp_ops cn10k_flow_fp_ops = {
+ .async_create = cnxk_flow_async_create,
+ .async_create_by_index = cnxk_flow_async_create_by_index,
+ .async_create_by_index_with_pattern = cnxk_flow_async_create_by_index_with_pattern,
+ .async_actions_update = cnxk_flow_async_actions_update,
+ .async_destroy = cnxk_flow_async_destroy,
+ .push = cn10k_flow_push,
+ .pull = cnxk_flow_pull,
+};
+
static int
cn10k_nix_remove(struct rte_pci_device *pci_dev)
{
@@ -966,6 +986,9 @@ cn10k_nix_probe(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
dev = cnxk_eth_pmd_priv(eth_dev);
+ /* Install fast-path (async) flow ops for this port. */
+ eth_dev->flow_fp_ops = &cn10k_flow_fp_ops;
+
/* DROP_RE is not supported with inline IPSec for CN10K A0 and
* when vector mode is enabled.
*/
diff --git a/drivers/net/cnxk/cn10k_flow.c b/drivers/net/cnxk/cn10k_flow.c
index b95fb83f08..02bc002507 100644
--- a/drivers/net/cnxk/cn10k_flow.c
+++ b/drivers/net/cnxk/cn10k_flow.c
@@ -48,6 +48,49 @@ cn10k_flow_info_get(struct rte_eth_dev *dev, struct rte_flow_port_info *port_inf
return cnxk_flow_info_get_common(dev, port_info, queue_info, err);
}
+int
+cn10k_flow_push(struct rte_eth_dev *eth_dev, uint32_t queue, struct rte_flow_error *error)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ bool reset_rx = false;
+ int rc;
+
+ /* Flush the pending async ops to hardware first. */
+ rc = cnxk_flow_push(eth_dev, queue, error);
+ if (rc)
+ return rc;
+
+ /* Async push, unlike sync create/destroy, does not toggle the MARK and
+ * VLAN-strip RX offloads; reconcile each with its refcount so the Rx
+ * function matches the live rules.
+ */
+ if (roc_npc_mark_actions_get(npc) &&
+ !(dev->rx_offload_flags & NIX_RX_OFFLOAD_MARK_UPDATE_F)) {
+ dev->rx_offload_flags |= NIX_RX_OFFLOAD_MARK_UPDATE_F;
+ reset_rx = true;
+ } else if (!roc_npc_mark_actions_get(npc) &&
+ (dev->rx_offload_flags & NIX_RX_OFFLOAD_MARK_UPDATE_F)) {
+ dev->rx_offload_flags &= ~NIX_RX_OFFLOAD_MARK_UPDATE_F;
+ reset_rx = true;
+ }
+
+ if (roc_npc_vtag_actions_get(npc) &&
+ !(dev->rx_offload_flags & NIX_RX_OFFLOAD_VLAN_STRIP_F)) {
+ dev->rx_offload_flags |= NIX_RX_OFFLOAD_VLAN_STRIP_F;
+ reset_rx = true;
+ } else if (!roc_npc_vtag_actions_get(npc) &&
+ (dev->rx_offload_flags & NIX_RX_OFFLOAD_VLAN_STRIP_F)) {
+ dev->rx_offload_flags &= ~NIX_RX_OFFLOAD_VLAN_STRIP_F;
+ reset_rx = true;
+ }
+
+ if (reset_rx)
+ cn10k_eth_set_rx_function(eth_dev);
+
+ return 0;
+}
+
int
cn10k_flow_destroy(struct rte_eth_dev *eth_dev, struct rte_flow *rte_flow,
struct rte_flow_error *error)
diff --git a/drivers/net/cnxk/cn10k_flow.h b/drivers/net/cnxk/cn10k_flow.h
index c9e8c86e96..c0d239c20f 100644
--- a/drivers/net/cnxk/cn10k_flow.h
+++ b/drivers/net/cnxk/cn10k_flow.h
@@ -16,6 +16,8 @@ int cn10k_flow_destroy(struct rte_eth_dev *dev, struct rte_flow *flow,
int cn10k_flow_info_get(struct rte_eth_dev *dev, struct rte_flow_port_info *port_info,
struct rte_flow_queue_info *queue_info, struct rte_flow_error *err);
+int cn10k_flow_push(struct rte_eth_dev *dev, uint32_t queue, struct rte_flow_error *error);
+
#define CNXK_NPC_COUNTERS_MAX 512
#endif /* __CN10K_FLOW_H__ */
diff --git a/drivers/net/cnxk/cn20k_ethdev.c b/drivers/net/cnxk/cn20k_ethdev.c
index f08079f612..9fac027650 100644
--- a/drivers/net/cnxk/cn20k_ethdev.c
+++ b/drivers/net/cnxk/cn20k_ethdev.c
@@ -5,7 +5,7 @@
#include "cn20k_flow.h"
#include "cn20k_rx.h"
#include "cn20k_tx.h"
-
+#include "cnxk_flow.h"
static uint16_t
nix_rx_offload_flags(struct rte_eth_dev *eth_dev)
{
@@ -962,8 +962,27 @@ npc_flow_ops_override(void)
cnxk_flow_ops.create = cn20k_flow_create;
cnxk_flow_ops.destroy = cn20k_flow_destroy;
cnxk_flow_ops.info_get = cn20k_flow_info_get;
+
+ cnxk_flow_ops.configure = cnxk_flow_configure;
+ cnxk_flow_ops.pattern_template_create = cnxk_flow_pattern_template_create;
+ cnxk_flow_ops.pattern_template_destroy = cnxk_flow_pattern_template_destroy;
+ cnxk_flow_ops.actions_template_create = cnxk_flow_actions_template_create;
+ cnxk_flow_ops.actions_template_destroy = cnxk_flow_actions_template_destroy;
+ cnxk_flow_ops.template_table_create = cnxk_flow_template_table_create;
+ cnxk_flow_ops.template_table_destroy = cnxk_flow_template_table_destroy;
}
+/* Fast-path (async) flow ops. Installed per-port in cn20k_nix_probe(). */
+static const struct rte_flow_fp_ops cn20k_flow_fp_ops = {
+ .async_create = cnxk_flow_async_create,
+ .async_create_by_index = cnxk_flow_async_create_by_index,
+ .async_create_by_index_with_pattern = cnxk_flow_async_create_by_index_with_pattern,
+ .async_actions_update = cnxk_flow_async_actions_update,
+ .async_destroy = cnxk_flow_async_destroy,
+ .push = cn20k_flow_push,
+ .pull = cnxk_flow_pull,
+};
+
static int
cn20k_nix_remove(struct rte_pci_device *pci_dev)
{
@@ -1012,6 +1031,9 @@ cn20k_nix_probe(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
dev = cnxk_eth_pmd_priv(eth_dev);
+ /* Install fast-path (async) flow ops for this port. */
+ eth_dev->flow_fp_ops = &cn20k_flow_fp_ops;
+
/* Register up msg callbacks for PTP information */
roc_nix_ptp_info_cb_register(&dev->nix, cn20k_nix_ptp_info_update_cb);
diff --git a/drivers/net/cnxk/cn20k_flow.c b/drivers/net/cnxk/cn20k_flow.c
index fd50f516ee..ff6e220103 100644
--- a/drivers/net/cnxk/cn20k_flow.c
+++ b/drivers/net/cnxk/cn20k_flow.c
@@ -48,6 +48,49 @@ cn20k_flow_info_get(struct rte_eth_dev *dev, struct rte_flow_port_info *port_inf
return cnxk_flow_info_get_common(dev, port_info, queue_info, err);
}
+int
+cn20k_flow_push(struct rte_eth_dev *eth_dev, uint32_t queue, struct rte_flow_error *error)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ bool reset_rx = false;
+ int rc;
+
+ /* Flush the pending async ops to hardware first. */
+ rc = cnxk_flow_push(eth_dev, queue, error);
+ if (rc)
+ return rc;
+
+ /* Async push, unlike sync create/destroy, does not toggle the MARK and
+ * VLAN-strip RX offloads; reconcile each with its refcount so the Rx
+ * function matches the live rules.
+ */
+ if (roc_npc_mark_actions_get(npc) &&
+ !(dev->rx_offload_flags & NIX_RX_OFFLOAD_MARK_UPDATE_F)) {
+ dev->rx_offload_flags |= NIX_RX_OFFLOAD_MARK_UPDATE_F;
+ reset_rx = true;
+ } else if (!roc_npc_mark_actions_get(npc) &&
+ (dev->rx_offload_flags & NIX_RX_OFFLOAD_MARK_UPDATE_F)) {
+ dev->rx_offload_flags &= ~NIX_RX_OFFLOAD_MARK_UPDATE_F;
+ reset_rx = true;
+ }
+
+ if (roc_npc_vtag_actions_get(npc) &&
+ !(dev->rx_offload_flags & NIX_RX_OFFLOAD_VLAN_STRIP_F)) {
+ dev->rx_offload_flags |= NIX_RX_OFFLOAD_VLAN_STRIP_F;
+ reset_rx = true;
+ } else if (!roc_npc_vtag_actions_get(npc) &&
+ (dev->rx_offload_flags & NIX_RX_OFFLOAD_VLAN_STRIP_F)) {
+ dev->rx_offload_flags &= ~NIX_RX_OFFLOAD_VLAN_STRIP_F;
+ reset_rx = true;
+ }
+
+ if (reset_rx)
+ cn20k_eth_set_rx_function(eth_dev);
+
+ return 0;
+}
+
int
cn20k_flow_destroy(struct rte_eth_dev *eth_dev, struct rte_flow *rte_flow,
struct rte_flow_error *error)
diff --git a/drivers/net/cnxk/cn20k_flow.h b/drivers/net/cnxk/cn20k_flow.h
index 1e4bd6214a..ed8a2aa1f3 100644
--- a/drivers/net/cnxk/cn20k_flow.h
+++ b/drivers/net/cnxk/cn20k_flow.h
@@ -16,6 +16,8 @@ int cn20k_flow_destroy(struct rte_eth_dev *dev, struct rte_flow *flow,
int cn20k_flow_info_get(struct rte_eth_dev *dev, struct rte_flow_port_info *port_info,
struct rte_flow_queue_info *queue_info, struct rte_flow_error *err);
+int cn20k_flow_push(struct rte_eth_dev *dev, uint32_t queue, struct rte_flow_error *error);
+
#define CNXK_NPC_COUNTERS_MAX 512
#endif /* __CN20K_FLOW_H__ */
diff --git a/drivers/net/cnxk/cnxk_ethdev.h b/drivers/net/cnxk/cnxk_ethdev.h
index fc66135c66..342d249cf4 100644
--- a/drivers/net/cnxk/cnxk_ethdev.h
+++ b/drivers/net/cnxk/cnxk_ethdev.h
@@ -328,6 +328,50 @@ struct cnxk_macsec_sess {
};
TAILQ_HEAD(cnxk_macsec_sess_list, cnxk_macsec_sess);
+/* Pattern template: owns deep copies tracked in copies[]. */
+struct cnxk_flow_pattern_template {
+ struct rte_flow_pattern_template_attr attr;
+ struct rte_flow_item *pattern;
+ /* Mirrored roc handle for table-level ownership/lifecycle in roc. */
+ struct roc_npc_pattern_template *roc_tmpl;
+ uint16_t nb_items;
+ void **copies;
+ uint16_t nb_copies;
+ uint32_t refcnt;
+};
+
+/* Actions template: owns deep copies tracked in copies[]. */
+struct cnxk_flow_actions_template {
+ struct rte_flow_actions_template_attr attr;
+ struct rte_flow_action *actions;
+ struct rte_flow_action *masks;
+ /* Mirrored roc handle for table-level ownership/lifecycle in roc. */
+ struct roc_npc_actions_template *roc_tmpl;
+ uint16_t nb_actions;
+ void **copies;
+ uint16_t nb_copies;
+ uint32_t refcnt;
+};
+
+/* Template table wrapper. The roc layer owns rule storage, MCAM reservation
+ * and rule lifecycle; cnxk keeps rte templates for per-rule translation.
+ */
+struct cnxk_flow_table {
+ /* roc engine table handle: storage, MCAM, rule lifecycle. */
+ struct roc_npc_template_table *roc_table;
+
+ struct cnxk_flow_pattern_template **pattern_templates;
+ struct cnxk_flow_actions_template **actions_templates;
+ uint8_t nb_pattern_templates;
+ uint8_t nb_actions_templates;
+
+ uint32_t nb_flows;
+ struct roc_npc_attr flow_attr;
+ /* roc_npc_attr has no transfer field; replay it into each rule attr. */
+ bool transfer;
+ enum rte_flow_table_insertion_type insertion_type;
+};
+
struct cnxk_eth_dev {
/* ROC NIX */
struct roc_nix nix;
diff --git a/drivers/net/cnxk/cnxk_flow.c b/drivers/net/cnxk/cnxk_flow.c
index c1c48eb7ab..1562b7389e 100644
--- a/drivers/net/cnxk/cnxk_flow.c
+++ b/drivers/net/cnxk/cnxk_flow.c
@@ -8,6 +8,7 @@
#define TNL_DCP_MATCH_ID 5
#define NRML_MATCH_ID 1
+
const struct cnxk_rte_flow_term_info term[] = {
[RTE_FLOW_ITEM_TYPE_ETH] = {ROC_NPC_ITEM_TYPE_ETH, sizeof(struct rte_flow_item_eth)},
[RTE_FLOW_ITEM_TYPE_VLAN] = {ROC_NPC_ITEM_TYPE_VLAN, sizeof(struct rte_flow_item_vlan)},
@@ -49,8 +50,7 @@ const struct cnxk_rte_flow_term_info term[] = {
[RTE_FLOW_ITEM_TYPE_REPRESENTED_PORT] = {ROC_NPC_ITEM_TYPE_REPRESENTED_PORT,
sizeof(struct rte_flow_item_ethdev)},
[RTE_FLOW_ITEM_TYPE_PPPOES] = {ROC_NPC_ITEM_TYPE_PPPOES,
- sizeof(struct rte_flow_item_pppoe)}
-};
+ sizeof(struct rte_flow_item_pppoe)}};
static int
npc_rss_action_validate(struct rte_eth_dev *eth_dev, const struct rte_flow_attr *attr,
@@ -239,7 +239,7 @@ append_rss_action(struct cnxk_eth_dev *dev, struct roc_npc_action *in_actions, u
/* Add RSS action */
rss_conf->queue_num = nb_rxq;
- queue_arr = calloc(1, rss_conf->queue_num * sizeof(uint16_t));
+ queue_arr = plt_zmalloc(rss_conf->queue_num * sizeof(uint16_t), 0);
if (!queue_arr) {
plt_err("Failed to allocate memory for rss queue");
rc = -ENOMEM;
@@ -266,10 +266,12 @@ append_rss_action(struct cnxk_eth_dev *dev, struct roc_npc_action *in_actions, u
while (free_allocs[j] != 0)
j++;
free_allocs[j] = (uint64_t)rss_conf;
+ j++;
+ free_allocs[j] = (uint64_t)queue_arr;
return 0;
free_rss:
- rte_free(rss_conf);
+ plt_free(rss_conf);
fail:
return rc;
}
@@ -1070,22 +1072,18 @@ cnxk_flow_query_common(struct rte_eth_dev *eth_dev, struct rte_flow *flow,
const struct rte_flow_action *action, void *data,
struct rte_flow_error *error, bool is_rep)
{
- struct roc_npc_flow *in_flow = (struct roc_npc_flow *)flow;
struct rte_flow_query_count *query = data;
+ struct roc_npc_flow *in_flow;
struct cnxk_rep_dev *rep_dev;
- struct cnxk_eth_dev *dev;
- struct roc_npc *npc;
const char *errmsg = NULL;
+ struct cnxk_eth_dev *dev;
int errcode = ENOTSUP;
+ struct roc_npc *npc;
int rc;
- if (action->type != RTE_FLOW_ACTION_TYPE_COUNT) {
- errmsg = "Only COUNT is supported in query";
- goto err_exit;
- }
-
- if (in_flow->ctr_id == NPC_COUNTER_NONE) {
- errmsg = "Counter is not available";
+ if (flow == NULL) {
+ errmsg = "Invalid flow handle";
+ errcode = EINVAL;
goto err_exit;
}
@@ -1098,6 +1096,29 @@ cnxk_flow_query_common(struct rte_eth_dev *eth_dev, struct rte_flow *flow,
npc = &rep_dev->parent_dev->npc;
}
+ /* Let the roc layer resolve async/template handles to the underlying
+ * roc_npc_flow (NULL until committed); else treat as a sync flow.
+ */
+ in_flow = (struct roc_npc_flow *)flow;
+ if (!is_rep)
+ roc_npc_async_flow_resolve(npc, flow, &in_flow);
+
+ if (in_flow == NULL) {
+ errmsg = "Flow is not committed yet";
+ errcode = EINVAL;
+ goto err_exit;
+ }
+
+ if (action->type != RTE_FLOW_ACTION_TYPE_COUNT) {
+ errmsg = "Only COUNT is supported in query";
+ goto err_exit;
+ }
+
+ if (in_flow->ctr_id == NPC_COUNTER_NONE) {
+ errmsg = "Counter is not available";
+ goto err_exit;
+ }
+
if (in_flow->use_pre_alloc) {
rc = roc_npc_inl_mcam_read_counter(in_flow->ctr_id, &query->hits);
} else {
@@ -1149,8 +1170,8 @@ cnxk_flow_isolate(struct rte_eth_dev *eth_dev __rte_unused, int enable __rte_unu
* entry for this port.
*/
- rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
- NULL, "Flow isolation not supported");
+ rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "Flow isolation not supported");
return -rte_errno;
}
@@ -1159,8 +1180,9 @@ int
cnxk_flow_dev_dump_common(struct rte_eth_dev *eth_dev, struct rte_flow *flow, FILE *file,
struct rte_flow_error *error, bool is_rep)
{
+ struct roc_npc_flow *in_flow = NULL;
+ struct cnxk_eth_dev *dev = NULL;
struct cnxk_rep_dev *rep_dev;
- struct cnxk_eth_dev *dev;
struct roc_npc *npc;
/* is_rep set for operation performed via representor ports */
@@ -1179,7 +1201,17 @@ cnxk_flow_dev_dump_common(struct rte_eth_dev *eth_dev, struct rte_flow *flow, FI
}
if (flow != NULL) {
- roc_npc_flow_mcam_dump(file, npc, (struct roc_npc_flow *)flow);
+ /* Resolve async/template handles to the underlying roc_npc_flow;
+ * else treat as a sync flow.
+ */
+ if (is_rep || !roc_npc_async_flow_resolve(npc, flow, &in_flow))
+ in_flow = (struct roc_npc_flow *)flow;
+
+ if (in_flow == NULL)
+ return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+ "flow is not committed yet");
+
+ roc_npc_flow_mcam_dump(file, npc, in_flow);
return 0;
}
@@ -1187,6 +1219,1276 @@ cnxk_flow_dev_dump_common(struct rte_eth_dev *eth_dev, struct rte_flow *flow, FI
return 0;
}
+/* Configure async flow queues for the port. */
+int
+cnxk_flow_configure(struct rte_eth_dev *eth_dev, const struct rte_flow_port_attr *port_attr,
+ uint16_t nb_queue, const struct rte_flow_queue_attr *queue_attr[],
+ struct rte_flow_error *err)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ const struct roc_npc_flow_queue_attr **rptrs = NULL;
+ struct roc_npc_flow_queue_attr *rattr = NULL;
+ struct roc_npc *npc = &dev->npc;
+ uint16_t i;
+ int rc;
+
+ RTE_SET_USED(port_attr);
+
+ /* Translate the rte queue attributes into roc_npc types; the roc
+ * engine validates nb_queue == 0 and a NULL queue_attr.
+ */
+ if (nb_queue != 0 && queue_attr != NULL) {
+ rattr = plt_zmalloc(nb_queue * sizeof(*rattr), 0);
+ if (rattr == NULL)
+ return rte_flow_error_set(err, ENOMEM, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+ NULL, "Failed to allocate flow queues");
+
+ rptrs = plt_zmalloc(nb_queue * sizeof(*rptrs), 0);
+ if (rptrs == NULL) {
+ plt_free(rattr);
+ return rte_flow_error_set(err, ENOMEM, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+ NULL, "Failed to allocate flow queues");
+ }
+ for (i = 0; i < nb_queue; i++) {
+ if (queue_attr[i] != NULL) {
+ rattr[i].size = queue_attr[i]->size;
+ rptrs[i] = &rattr[i];
+ } else {
+ rptrs[i] = NULL;
+ }
+ }
+ }
+
+ rc = roc_npc_flow_configure(npc, nb_queue, rptrs);
+
+ plt_free(rattr);
+ plt_free(rptrs);
+
+ if (rc)
+ return rte_flow_error_set(err, -rc, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "Failed to configure flow queues");
+ return 0;
+}
+
+/* Deep-copy a spec/last/mask/conf payload into template-owned memory. */
+static int
+cnxk_flow_dup_conf(void **copies, uint16_t *nb_copies, const void *src, size_t sz, const void **dst)
+{
+ void *copy;
+
+ if (src == NULL || sz == 0) {
+ *dst = NULL;
+ return 0;
+ }
+ copy = plt_zmalloc(sz, 0);
+ if (copy == NULL)
+ return -ENOMEM;
+ memcpy(copy, src, sz);
+ copies[(*nb_copies)++] = copy;
+ *dst = copy;
+ return 0;
+}
+
+struct rte_flow_pattern_template *
+cnxk_flow_pattern_template_create(struct rte_eth_dev *eth_dev,
+ const struct rte_flow_pattern_template_attr *attr,
+ const struct rte_flow_item pattern[],
+ struct rte_flow_error *error)
+{
+ const char *errmsg = "Failed to allocate pattern template";
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct cnxk_flow_pattern_template *tmpl = NULL;
+ struct roc_npc_pattern_template_attr roc_attr;
+ struct roc_npc_item_info *roc_pattern = NULL;
+ int i, nb_items = 0, errcode = ENOMEM;
+ struct roc_npc *npc = &dev->npc;
+ int roc_err;
+
+ if (attr == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "NULL template attr");
+ return NULL;
+ }
+
+ if (pattern == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "NULL pattern");
+ return NULL;
+ }
+
+ while (pattern[nb_items].type != RTE_FLOW_ITEM_TYPE_END)
+ nb_items++;
+
+ /* Reject oversized templates; rules are built into fixed-size stack
+ * arrays (combined_items[ROC_NPC_ITEM_TYPE_END + 1]).
+ */
+ if (nb_items > ROC_NPC_ITEM_TYPE_END) {
+ errcode = ENOTSUP;
+ errmsg = "too many pattern items for hardware";
+ goto err_nomem;
+ }
+
+ tmpl = plt_zmalloc(sizeof(*tmpl), 0);
+ if (tmpl == NULL)
+ goto err_nomem;
+
+ tmpl->attr = *attr;
+ tmpl->nb_items = nb_items;
+
+ tmpl->pattern = plt_zmalloc((nb_items + 1) * sizeof(struct rte_flow_item), 0);
+ if (tmpl->pattern == NULL)
+ goto err_free;
+
+ /* At most 3 payload copies (spec/last/mask) per item. */
+ tmpl->copies = plt_zmalloc(3 * (nb_items + 1) * sizeof(void *), 0);
+ if (tmpl->copies == NULL)
+ goto err_free;
+
+ for (i = 0; i < nb_items; i++) {
+ enum rte_flow_item_type t = pattern[i].type;
+ size_t sz = (t < (int)RTE_DIM(term)) ? term[t].item_size : 0;
+
+ /* Reject unrepresentable item types (sz 0), which would drop
+ * spec/last/mask. VOID/ANY legitimately carry no payload.
+ */
+ if (t != RTE_FLOW_ITEM_TYPE_VOID && t != RTE_FLOW_ITEM_TYPE_ANY && sz == 0) {
+ errcode = ENOTSUP;
+ errmsg = "unsupported pattern item type in template";
+ goto err_free;
+ }
+
+ /* Reject items with nested variable-length payloads; a flat
+ * memcpy would retain a dangling pointer into caller memory.
+ */
+ if (t == RTE_FLOW_ITEM_TYPE_RAW) {
+ errcode = ENOTSUP;
+ errmsg = "Item type with nested pointers is not supported "
+ "in pattern template (e.g. RAW)";
+ goto err_free;
+ }
+
+ tmpl->pattern[i].type = t;
+ if (cnxk_flow_dup_conf(tmpl->copies, &tmpl->nb_copies, pattern[i].spec, sz,
+ &tmpl->pattern[i].spec) ||
+ cnxk_flow_dup_conf(tmpl->copies, &tmpl->nb_copies, pattern[i].last, sz,
+ &tmpl->pattern[i].last) ||
+ cnxk_flow_dup_conf(tmpl->copies, &tmpl->nb_copies, pattern[i].mask, sz,
+ &tmpl->pattern[i].mask))
+ goto err_free;
+ }
+ tmpl->pattern[nb_items].type = RTE_FLOW_ITEM_TYPE_END;
+ tmpl->roc_tmpl = NULL;
+ tmpl->refcnt = 0;
+
+ roc_pattern = plt_zmalloc((nb_items + 1) * sizeof(*roc_pattern), 0);
+ if (roc_pattern == NULL)
+ goto err_free;
+
+ for (i = 0; i < nb_items; i++) {
+ enum rte_flow_item_type t = tmpl->pattern[i].type;
+ size_t sz = (t < (int)RTE_DIM(term)) ? term[t].item_size : 0;
+
+ roc_pattern[i].type =
+ (t < (int)RTE_DIM(term)) ? term[t].item_type : ROC_NPC_ITEM_TYPE_VOID;
+ roc_pattern[i].size = sz;
+ roc_pattern[i].spec = tmpl->pattern[i].spec;
+ roc_pattern[i].last = tmpl->pattern[i].last;
+ roc_pattern[i].mask = tmpl->pattern[i].mask;
+ }
+ roc_pattern[nb_items].type = ROC_NPC_ITEM_TYPE_END;
+
+ memset(&roc_attr, 0, sizeof(roc_attr));
+ roc_attr.relaxed_matching = attr->relaxed_matching;
+ roc_attr.ingress = attr->ingress;
+ roc_attr.egress = attr->egress;
+ roc_attr.transfer = attr->transfer;
+
+ roc_err = 0;
+ tmpl->roc_tmpl = roc_npc_pattern_template_create(npc, &roc_attr, roc_pattern, &roc_err);
+ if (tmpl->roc_tmpl == NULL) {
+ errcode = (roc_err < 0) ? -roc_err : ENOMEM;
+ errmsg = "Failed to create ROC pattern template";
+ goto err_free;
+ }
+
+ plt_free(roc_pattern);
+
+ return (struct rte_flow_pattern_template *)tmpl;
+
+err_free:
+ plt_free(roc_pattern);
+ if (tmpl != NULL) {
+ uint16_t j;
+ if (tmpl->roc_tmpl != NULL)
+ roc_npc_pattern_template_destroy(npc, tmpl->roc_tmpl);
+ for (j = 0; j < tmpl->nb_copies; j++)
+ plt_free(tmpl->copies[j]);
+ plt_free(tmpl->copies);
+ plt_free(tmpl->pattern);
+ plt_free(tmpl);
+ }
+err_nomem:
+ rte_flow_error_set(error, errcode, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL, errmsg);
+ return NULL;
+}
+
+int
+cnxk_flow_pattern_template_destroy(struct rte_eth_dev *eth_dev,
+ struct rte_flow_pattern_template *templ,
+ struct rte_flow_error *error)
+{
+ struct cnxk_flow_pattern_template *tmpl = (struct cnxk_flow_pattern_template *)templ;
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ uint16_t i;
+ int rc;
+
+ if (tmpl == NULL)
+ return 0;
+
+ if (tmpl->refcnt != 0)
+ return rte_flow_error_set(error, EBUSY, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "pattern template still in use by a table");
+
+ rc = roc_npc_pattern_template_destroy(npc, tmpl->roc_tmpl);
+ if (rc)
+ return rte_flow_error_set(error, -rc, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "failed to destroy ROC pattern template");
+
+ for (i = 0; i < tmpl->nb_copies; i++)
+ plt_free(tmpl->copies[i]);
+ plt_free(tmpl->copies);
+ plt_free(tmpl->pattern);
+ plt_free(tmpl);
+ return 0;
+}
+/* Size of an action's conf for the v1-supported set; -1 = unsupported. */
+static int
+cnxk_flow_action_conf_size(enum rte_flow_action_type type)
+{
+ switch (type) {
+ case RTE_FLOW_ACTION_TYPE_VOID:
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ case RTE_FLOW_ACTION_TYPE_PF:
+ case RTE_FLOW_ACTION_TYPE_FLAG:
+ case RTE_FLOW_ACTION_TYPE_OF_POP_VLAN:
+ case RTE_FLOW_ACTION_TYPE_VXLAN_DECAP:
+ return 0; /* no conf */
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ return sizeof(struct rte_flow_action_queue);
+ case RTE_FLOW_ACTION_TYPE_VF:
+ return sizeof(struct rte_flow_action_vf);
+ case RTE_FLOW_ACTION_TYPE_MARK:
+ return sizeof(struct rte_flow_action_mark);
+ case RTE_FLOW_ACTION_TYPE_COUNT:
+ return sizeof(struct rte_flow_action_count);
+ case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+ return sizeof(struct rte_flow_action_of_push_vlan);
+ case RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID:
+ return sizeof(struct rte_flow_action_of_set_vlan_vid);
+ case RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP:
+ return sizeof(struct rte_flow_action_of_set_vlan_pcp);
+ case RTE_FLOW_ACTION_TYPE_AGE:
+ return sizeof(struct rte_flow_action_age);
+ case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
+ case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
+ return sizeof(struct rte_flow_action_ethdev);
+ case RTE_FLOW_ACTION_TYPE_PORT_ID:
+ return sizeof(struct rte_flow_action_port_id);
+ default:
+ return -1;
+ }
+}
+
+/* Deep-copy an action conf. RSS nests queue[]/key[] pointers, so pack them
+ * into one blob and fix the pointers; other confs use a flat memcpy.
+ */
+static void *
+cnxk_flow_dup_action_conf(const struct rte_flow_action *act)
+{
+ if (act->conf == NULL)
+ return NULL;
+
+ if (act->type == RTE_FLOW_ACTION_TYPE_RSS) {
+ const struct rte_flow_action_rss *rss = act->conf;
+ size_t rss_blob_bytes = sizeof(*rss);
+ size_t rss_queue_bytes = (size_t)rss->queue_num * sizeof(uint16_t);
+ size_t rss_key_bytes = rss->key_len;
+ struct rte_flow_action_rss *rss_copy;
+ uint8_t *rss_blob;
+
+ /* Reject a non-zero length paired with a NULL pointer, which
+ * would make the memcpy below dereference NULL.
+ */
+ if ((rss_queue_bytes && rss->queue == NULL) || (rss_key_bytes && rss->key == NULL))
+ return NULL;
+
+ rss_blob = plt_zmalloc(rss_blob_bytes + rss_queue_bytes + rss_key_bytes, 0);
+ if (rss_blob == NULL)
+ return NULL;
+ rss_copy = (struct rte_flow_action_rss *)rss_blob;
+ *rss_copy = *rss;
+ if (rss_queue_bytes) {
+ memcpy(rss_blob + rss_blob_bytes, rss->queue, rss_queue_bytes);
+ rss_copy->queue = (const uint16_t *)(rss_blob + rss_blob_bytes);
+ } else {
+ rss_copy->queue = NULL;
+ }
+ if (rss_key_bytes) {
+ memcpy(rss_blob + rss_blob_bytes + rss_queue_bytes, rss->key,
+ rss_key_bytes);
+ rss_copy->key =
+ (const uint8_t *)(rss_blob + rss_blob_bytes + rss_queue_bytes);
+ } else {
+ rss_copy->key = NULL;
+ }
+ return rss_copy;
+ }
+
+ {
+ int conf_size = cnxk_flow_action_conf_size(act->type);
+ void *copy;
+
+ if (conf_size <= 0)
+ return NULL;
+ copy = plt_zmalloc(conf_size, 0);
+ if (copy != NULL)
+ memcpy(copy, act->conf, conf_size);
+ return copy;
+ }
+}
+
+/* Map a supported rte_flow action type to its roc_npc action type, to mirror
+ * the actions template into the roc engine for lifecycle/refcount ownership.
+ */
+static int
+cnxk_flow_roc_action_type(enum rte_flow_action_type type, enum roc_npc_action_type *roc_type)
+{
+ switch (type) {
+ case RTE_FLOW_ACTION_TYPE_VOID:
+ case RTE_FLOW_ACTION_TYPE_VXLAN_DECAP:
+ *roc_type = ROC_NPC_ACTION_TYPE_VOID;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_DROP:
+ *roc_type = ROC_NPC_ACTION_TYPE_DROP;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_PF:
+ *roc_type = ROC_NPC_ACTION_TYPE_PF;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_VF:
+ *roc_type = ROC_NPC_ACTION_TYPE_VF;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_FLAG:
+ *roc_type = ROC_NPC_ACTION_TYPE_FLAG;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_MARK:
+ *roc_type = ROC_NPC_ACTION_TYPE_MARK;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_COUNT:
+ *roc_type = ROC_NPC_ACTION_TYPE_COUNT;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_QUEUE:
+ *roc_type = ROC_NPC_ACTION_TYPE_QUEUE;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_RSS:
+ *roc_type = ROC_NPC_ACTION_TYPE_RSS;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_OF_POP_VLAN:
+ *roc_type = ROC_NPC_ACTION_TYPE_VLAN_STRIP;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN:
+ *roc_type = ROC_NPC_ACTION_TYPE_VLAN_ETHTYPE_INSERT;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID:
+ *roc_type = ROC_NPC_ACTION_TYPE_VLAN_INSERT;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP:
+ *roc_type = ROC_NPC_ACTION_TYPE_VLAN_PCP_INSERT;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_AGE:
+ *roc_type = ROC_NPC_ACTION_TYPE_AGE;
+ return 0;
+ case RTE_FLOW_ACTION_TYPE_PORT_ID:
+ case RTE_FLOW_ACTION_TYPE_REPRESENTED_PORT:
+ case RTE_FLOW_ACTION_TYPE_PORT_REPRESENTOR:
+ *roc_type = ROC_NPC_ACTION_TYPE_PORT_ID;
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+struct rte_flow_actions_template *
+cnxk_flow_actions_template_create(struct rte_eth_dev *eth_dev,
+ const struct rte_flow_actions_template_attr *attr,
+ const struct rte_flow_action actions[],
+ const struct rte_flow_action masks[],
+ struct rte_flow_error *error)
+{
+ const char *errmsg = "Failed to allocate actions template";
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct cnxk_flow_actions_template *tmpl = NULL;
+ struct roc_npc_actions_template_attr roc_attr;
+ struct roc_npc_action *roc_actions = NULL;
+ int i, nb_actions = 0, errcode = ENOMEM;
+ struct roc_npc *npc = &dev->npc;
+ int roc_err = 0;
+
+ if (attr == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "NULL template attr");
+ return NULL;
+ }
+
+ if (actions == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "NULL actions");
+ return NULL;
+ }
+
+ while (actions[nb_actions].type != RTE_FLOW_ACTION_TYPE_END)
+ nb_actions++;
+
+ /* Reject oversized templates; rules are built into fixed-size stack
+ * arrays (combined_actions[ROC_NPC_MAX_ACTION_COUNT] plus END).
+ */
+ if (nb_actions > ROC_NPC_MAX_ACTION_COUNT - 1) {
+ errcode = ENOTSUP;
+ errmsg = "too many actions for hardware";
+ goto err;
+ }
+
+ tmpl = plt_zmalloc(sizeof(*tmpl), 0);
+ if (tmpl == NULL)
+ goto err;
+
+ tmpl->attr = *attr;
+ tmpl->nb_actions = nb_actions;
+
+ tmpl->actions = plt_zmalloc((nb_actions + 1) * sizeof(struct rte_flow_action), 0);
+ if (tmpl->actions == NULL)
+ goto err;
+
+ tmpl->masks = plt_zmalloc((nb_actions + 1) * sizeof(struct rte_flow_action), 0);
+ if (tmpl->masks == NULL)
+ goto err;
+
+ /* up to 2 conf copies (action + mask) per action */
+ tmpl->copies = plt_zmalloc(2 * (nb_actions + 1) * sizeof(void *), 0);
+ if (tmpl->copies == NULL)
+ goto err;
+
+ for (i = 0; i < nb_actions; i++) {
+ enum rte_flow_action_type t = actions[i].type;
+ int sz = cnxk_flow_action_conf_size(t);
+
+ if (sz < 0 && t != RTE_FLOW_ACTION_TYPE_RSS) {
+ errcode = ENOTSUP;
+ errmsg = "Unsupported action in template "
+ "(supports VOID/DROP/QUEUE/PF/VF/MARK/FLAG/COUNT/RSS/"
+ "OF_POP_VLAN/OF_PUSH_VLAN/OF_SET_VLAN_VID/"
+ "OF_SET_VLAN_PCP/AGE/REPRESENTED_PORT/"
+ "PORT_REPRESENTOR/PORT_ID/VXLAN_DECAP)";
+ goto err;
+ }
+
+ tmpl->actions[i].type = t;
+
+ if (t == RTE_FLOW_ACTION_TYPE_RSS) {
+ /* RSS has nested pointers — use deep-copy helper. */
+ void *copy = cnxk_flow_dup_action_conf(&actions[i]);
+
+ if (copy == NULL && actions[i].conf != NULL) {
+ const struct rte_flow_action_rss *rss = actions[i].conf;
+
+ /* Malformed conf vs allocation failure: report a
+ * precise errno instead of the default ENOMEM.
+ */
+ if ((rss->queue_num && rss->queue == NULL) ||
+ (rss->key_len && rss->key == NULL)) {
+ errcode = EINVAL;
+ errmsg = "Malformed RSS action conf in template";
+ }
+ goto err;
+ }
+ tmpl->actions[i].conf = copy;
+ if (copy)
+ tmpl->copies[tmpl->nb_copies++] = copy;
+ } else {
+ if (cnxk_flow_dup_conf(tmpl->copies, &tmpl->nb_copies, actions[i].conf, sz,
+ &tmpl->actions[i].conf))
+ goto err;
+ }
+
+ if (masks != NULL) {
+ if (masks[i].type != t) {
+ errcode = EINVAL;
+ errmsg = "actions/masks type mismatch in template";
+ goto err;
+ }
+ tmpl->masks[i].type = t;
+ if (t == RTE_FLOW_ACTION_TYPE_RSS) {
+ /* RSS nests pointers and cannot be copied by the flat
+ * helper; reject a non-NULL RSS mask.
+ */
+ if (masks[i].conf != NULL) {
+ errcode = ENOTSUP;
+ errmsg = "Non-NULL RSS mask conf is not "
+ "supported in template";
+ goto err;
+ }
+ tmpl->masks[i].conf = NULL;
+ } else if (cnxk_flow_dup_conf(tmpl->copies, &tmpl->nb_copies, masks[i].conf,
+ sz > 0 ? sz : 0, &tmpl->masks[i].conf)) {
+ goto err;
+ }
+ }
+ }
+
+ tmpl->actions[nb_actions].type = RTE_FLOW_ACTION_TYPE_END;
+ tmpl->masks[nb_actions].type = RTE_FLOW_ACTION_TYPE_END;
+ tmpl->roc_tmpl = NULL;
+ tmpl->refcnt = 0;
+
+ /* Mirror only action types into the roc engine for template lifecycle
+ * and refcount ownership; per-rule conf translation stays in cnxk.
+ */
+ roc_actions = plt_zmalloc((nb_actions + 1) * sizeof(*roc_actions), 0);
+ if (roc_actions == NULL)
+ goto err;
+
+ for (i = 0; i < nb_actions; i++) {
+ enum roc_npc_action_type rt;
+
+ if (cnxk_flow_roc_action_type(tmpl->actions[i].type, &rt)) {
+ errcode = ENOTSUP;
+ errmsg = "Unsupported action in template";
+ goto err;
+ }
+ roc_actions[i].type = rt;
+ roc_actions[i].conf = NULL;
+ }
+ roc_actions[nb_actions].type = ROC_NPC_ACTION_TYPE_END;
+
+ memset(&roc_attr, 0, sizeof(roc_attr));
+ roc_attr.ingress = attr->ingress;
+ roc_attr.egress = attr->egress;
+ roc_attr.transfer = attr->transfer;
+
+ tmpl->roc_tmpl =
+ roc_npc_actions_template_create(npc, &roc_attr, roc_actions, NULL, &roc_err);
+ plt_free(roc_actions);
+ roc_actions = NULL;
+ if (tmpl->roc_tmpl == NULL) {
+ errcode = (roc_err < 0) ? -roc_err : ENOMEM;
+ errmsg = "Failed to create ROC actions template";
+ goto err;
+ }
+
+ return (struct rte_flow_actions_template *)tmpl;
+
+err:
+ plt_free(roc_actions);
+ if (tmpl != NULL) {
+ uint16_t j;
+ if (tmpl->roc_tmpl != NULL)
+ roc_npc_actions_template_destroy(npc, tmpl->roc_tmpl);
+ for (j = 0; j < tmpl->nb_copies; j++)
+ plt_free(tmpl->copies[j]);
+ plt_free(tmpl->copies);
+ plt_free(tmpl->masks);
+ plt_free(tmpl->actions);
+ plt_free(tmpl);
+ }
+ rte_flow_error_set(error, errcode, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL, errmsg);
+ return NULL;
+}
+
+int
+cnxk_flow_actions_template_destroy(struct rte_eth_dev *eth_dev,
+ struct rte_flow_actions_template *templ,
+ struct rte_flow_error *error)
+{
+ struct cnxk_flow_actions_template *tmpl = (struct cnxk_flow_actions_template *)templ;
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ uint16_t i;
+ int rc;
+
+ if (tmpl == NULL)
+ return 0;
+
+ if (tmpl->refcnt != 0)
+ return rte_flow_error_set(error, EBUSY, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "actions template still in use by a table");
+
+ rc = roc_npc_actions_template_destroy(npc, tmpl->roc_tmpl);
+ if (rc)
+ return rte_flow_error_set(error, -rc, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "failed to destroy ROC actions template");
+
+ for (i = 0; i < tmpl->nb_copies; i++)
+ plt_free(tmpl->copies[i]);
+ plt_free(tmpl->copies);
+ plt_free(tmpl->masks);
+ plt_free(tmpl->actions);
+ plt_free(tmpl);
+ return 0;
+}
+
+struct rte_flow_template_table *
+cnxk_flow_template_table_create(struct rte_eth_dev *eth_dev,
+ const struct rte_flow_template_table_attr *table_attr,
+ struct rte_flow_pattern_template *pattern_templates[],
+ uint8_t nb_pattern_templates,
+ struct rte_flow_actions_template *actions_templates[],
+ uint8_t nb_actions_templates, struct rte_flow_error *error)
+{
+ struct roc_npc_pattern_template **roc_pattern_templates = NULL;
+ struct roc_npc_actions_template **roc_actions_templates = NULL;
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc_template_table_attr roc_attr;
+ enum rte_flow_table_insertion_type itype;
+ const struct rte_flow_attr *fattr;
+ struct roc_npc *npc = &dev->npc;
+ struct cnxk_flow_table *table;
+ uint32_t nb_flows;
+ int errcode = 0;
+ uint8_t i;
+
+ if (table_attr == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "NULL table_attr");
+ return NULL;
+ }
+
+ itype = table_attr->insertion_type;
+ fattr = &table_attr->flow_attr;
+ nb_flows = table_attr->nb_flows;
+
+ if (itype == RTE_FLOW_TABLE_INSERTION_TYPE_INDEX) {
+ rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "pure-index insertion not supported; NPC is match-based");
+ return NULL;
+ }
+
+ /* Validate the template handles before allocating anything. */
+ for (i = 0; i < nb_pattern_templates; i++) {
+ if (pattern_templates[i] == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "invalid template handle");
+ return NULL;
+ }
+ }
+ for (i = 0; i < nb_actions_templates; i++) {
+ if (actions_templates[i] == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "invalid template handle");
+ return NULL;
+ }
+ }
+
+ table = plt_zmalloc(sizeof(*table), 0);
+ if (table == NULL)
+ goto enomem;
+
+ table->insertion_type = itype;
+ table->nb_flows = nb_flows;
+
+ memset(&table->flow_attr, 0, sizeof(table->flow_attr));
+ table->flow_attr.priority = fattr->priority;
+ table->flow_attr.ingress = fattr->ingress;
+ table->flow_attr.egress = fattr->egress;
+ table->transfer = fattr->transfer;
+
+ if (nb_pattern_templates != 0) {
+ table->pattern_templates =
+ plt_zmalloc(nb_pattern_templates * sizeof(*table->pattern_templates), 0);
+ if (table->pattern_templates == NULL)
+ goto free_table;
+ roc_pattern_templates =
+ plt_zmalloc(nb_pattern_templates * sizeof(*roc_pattern_templates), 0);
+ if (roc_pattern_templates == NULL)
+ goto free_table;
+ }
+ if (nb_actions_templates != 0) {
+ table->actions_templates =
+ plt_zmalloc(nb_actions_templates * sizeof(*table->actions_templates), 0);
+ if (table->actions_templates == NULL)
+ goto free_table;
+ roc_actions_templates =
+ plt_zmalloc(nb_actions_templates * sizeof(*roc_actions_templates), 0);
+ if (roc_actions_templates == NULL)
+ goto free_table;
+ }
+
+ /* Hand the table to the roc engine, which owns rule storage, MCAM
+ * reservation and the rule lifecycle; cnxk keeps per-rule translation.
+ */
+ memset(&roc_attr, 0, sizeof(roc_attr));
+ roc_attr.flow_attr = table->flow_attr;
+ roc_attr.transfer = table->transfer;
+ roc_attr.nb_flows = nb_flows;
+ roc_attr.insertion_type = (itype == RTE_FLOW_TABLE_INSERTION_TYPE_INDEX_WITH_PATTERN) ?
+ ROC_NPC_TEMPLATE_INSERTION_INDEX_WITH_PATTERN :
+ ROC_NPC_TEMPLATE_INSERTION_PATTERN;
+ /* Stash this wrapper as the table cookie so async_actions_update can
+ * recover the rte templates from just the flow handle.
+ */
+ roc_attr.cookie = table;
+
+ for (i = 0; i < nb_pattern_templates; i++) {
+ table->pattern_templates[i] =
+ (struct cnxk_flow_pattern_template *)pattern_templates[i];
+ roc_pattern_templates[i] = table->pattern_templates[i]->roc_tmpl;
+ if (roc_pattern_templates[i] == NULL)
+ goto bad_handle;
+ }
+ for (i = 0; i < nb_actions_templates; i++) {
+ table->actions_templates[i] =
+ (struct cnxk_flow_actions_template *)actions_templates[i];
+ roc_actions_templates[i] = table->actions_templates[i]->roc_tmpl;
+ if (roc_actions_templates[i] == NULL)
+ goto bad_handle;
+ }
+
+ /* clang-format off */
+ table->roc_table = roc_npc_template_table_create(npc, &roc_attr,
+ roc_pattern_templates, nb_pattern_templates,
+ roc_actions_templates, nb_actions_templates, &errcode);
+ /* clang-format on */
+ plt_free(roc_pattern_templates);
+ plt_free(roc_actions_templates);
+ roc_pattern_templates = NULL;
+ roc_actions_templates = NULL;
+ if (table->roc_table == NULL) {
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+ rte_flow_error_set(error, errcode < 0 ? -errcode : ENOMEM,
+ RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "could not create template table");
+ return NULL;
+ }
+
+ for (i = 0; i < nb_pattern_templates; i++)
+ table->pattern_templates[i]->refcnt++;
+ table->nb_pattern_templates = nb_pattern_templates;
+ for (i = 0; i < nb_actions_templates; i++)
+ table->actions_templates[i]->refcnt++;
+ table->nb_actions_templates = nb_actions_templates;
+
+ return (struct rte_flow_template_table *)table;
+
+bad_handle:
+ plt_free(roc_pattern_templates);
+ plt_free(roc_actions_templates);
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "invalid template handle");
+ return NULL;
+
+free_table:
+ plt_free(roc_pattern_templates);
+ plt_free(roc_actions_templates);
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+
+enomem:
+ rte_flow_error_set(error, ENOMEM, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL, "out of memory");
+ return NULL;
+}
+
+int
+cnxk_flow_template_table_destroy(struct rte_eth_dev *eth_dev,
+ struct rte_flow_template_table *tbl_handle,
+ struct rte_flow_error *error)
+{
+ struct cnxk_flow_table *table = (struct cnxk_flow_table *)tbl_handle;
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ uint8_t i;
+ int rc;
+
+ if (table == NULL)
+ return 0;
+
+ /* The roc engine guards teardown: a busy table returns -EBUSY and
+ * nothing is freed, so the cnxk wrapper and refcounts stay intact.
+ */
+ rc = roc_npc_template_table_destroy(npc, table->roc_table);
+ if (rc == -EBUSY)
+ return rte_flow_error_set(error, EBUSY, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "table still has live flows; destroy them first");
+ if (rc)
+ return rte_flow_error_set(error, -rc, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "failed to destroy template table");
+
+ for (i = 0; i < table->nb_pattern_templates; i++)
+ table->pattern_templates[i]->refcnt--;
+ for (i = 0; i < table->nb_actions_templates; i++)
+ table->actions_templates[i]->refcnt--;
+
+ plt_free(table->pattern_templates);
+ plt_free(table->actions_templates);
+ plt_free(table);
+ return 0;
+}
+
+/* Reject per-rule pattern/actions arrays whose types/order do not mirror the
+ * chosen templates; the merge takes the type from the template but the
+ * spec/conf from the per-rule array. A NULL array uses template defaults.
+ */
+static int
+cnxk_flow_check_items(const struct cnxk_flow_pattern_template *pt,
+ const struct rte_flow_item items[])
+{
+ uint16_t k;
+
+ if (items == NULL)
+ return 0;
+ for (k = 0; k < pt->nb_items; k++)
+ if (items[k].type != pt->pattern[k].type)
+ return -EINVAL;
+ if (items[pt->nb_items].type != RTE_FLOW_ITEM_TYPE_END)
+ return -EINVAL;
+ return 0;
+}
+
+static int
+cnxk_flow_check_actions(const struct cnxk_flow_actions_template *at,
+ const struct rte_flow_action actions[])
+{
+ uint16_t k;
+
+ if (actions == NULL)
+ return 0;
+ for (k = 0; k < at->nb_actions; k++)
+ if (actions[k].type != at->actions[k].type)
+ return -EINVAL;
+ if (actions[at->nb_actions].type != RTE_FLOW_ACTION_TYPE_END)
+ return -EINVAL;
+ return 0;
+}
+
+/* Enqueue a PATTERN create: merge the per-rule spec with the templates,
+ * translate to roc_npc types, and hand to the roc engine (programmed at push).
+ */
+struct rte_flow *
+cnxk_flow_async_create(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr,
+ struct rte_flow_template_table *tbl_handle,
+ const struct rte_flow_item items[], uint8_t pattern_template_index,
+ const struct rte_flow_action actions[], uint8_t action_template_index,
+ void *user_data, struct rte_flow_error *error)
+{
+ struct cnxk_flow_table *table = (struct cnxk_flow_table *)tbl_handle;
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct cnxk_flow_pattern_template *pt;
+ struct cnxk_flow_actions_template *at;
+ struct roc_npc_template_flow *flow;
+ struct roc_npc *npc = &dev->npc;
+
+ uint64_t free_allocs[ROC_NPC_MAX_ACTION_COUNT + ROC_NPC_ITEM_TYPE_END + 1] = {0};
+ struct roc_npc_item_info in_pattern[ROC_NPC_ITEM_TYPE_END + 1] = {0};
+ struct rte_flow_action combined_actions[ROC_NPC_MAX_ACTION_COUNT];
+ struct roc_npc_action in_actions[ROC_NPC_MAX_ACTION_COUNT] = {0};
+ struct rte_flow_item combined_items[ROC_NPC_ITEM_TYPE_END + 1];
+ struct roc_npc_action_sample in_sample = {0};
+ struct rte_flow_attr rte_attr;
+ struct roc_npc_attr in_attr;
+ uint16_t dst_pf_func = 0;
+ uint64_t def_action = 0;
+ int errcode = 0;
+ uint16_t k;
+ int rc, j;
+
+ RTE_SET_USED(attr);
+
+ if (table == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "bad table");
+ return NULL;
+ }
+ if (table->insertion_type != RTE_FLOW_TABLE_INSERTION_TYPE_PATTERN) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "table is index-insertion; use create_by_index_with_pattern");
+ return NULL;
+ }
+ if (pattern_template_index >= table->nb_pattern_templates ||
+ action_template_index >= table->nb_actions_templates) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "template index out of range");
+ return NULL;
+ }
+
+ pt = table->pattern_templates[pattern_template_index];
+ at = table->actions_templates[action_template_index];
+
+ if (cnxk_flow_check_items(pt, items) != 0 || cnxk_flow_check_actions(at, actions) != 0) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "pattern/actions do not match the selected templates");
+ return NULL;
+ }
+
+ /* Merge: spec comes from the per-rule call, last/mask from the template. */
+ for (k = 0; k < pt->nb_items; k++) {
+ combined_items[k].type = pt->pattern[k].type;
+ combined_items[k].spec = items ? items[k].spec : NULL;
+ combined_items[k].last = pt->pattern[k].last;
+ combined_items[k].mask = pt->pattern[k].mask;
+ }
+ combined_items[pt->nb_items].type = RTE_FLOW_ITEM_TYPE_END;
+
+ /* Merge: per-rule conf overrides the template conf. */
+ for (k = 0; k < at->nb_actions; k++) {
+ combined_actions[k].type = at->actions[k].type;
+ combined_actions[k].conf =
+ (actions && actions[k].conf) ? actions[k].conf : at->actions[k].conf;
+ }
+ combined_actions[at->nb_actions].type = RTE_FLOW_ACTION_TYPE_END;
+
+ memset(&rte_attr, 0, sizeof(rte_attr));
+ rte_attr.priority = table->flow_attr.priority;
+ rte_attr.ingress = table->flow_attr.ingress;
+ rte_attr.egress = table->flow_attr.egress;
+ rte_attr.transfer = table->transfer;
+
+ /* Translate into roc_npc types. The RSS flow key is stashed in
+ * npc->flowkey_cfg_state so the engine can restore it at push().
+ */
+ memset(&in_attr, 0, sizeof(in_attr));
+ rc = cnxk_map_flow_data(eth_dev, &rte_attr, combined_items, combined_actions, &in_attr,
+ in_pattern, in_actions, &in_sample, &npc->flowkey_cfg_state,
+ &dst_pf_func, &def_action, false, free_allocs, 0);
+ if (rc == 0)
+ flow = roc_npc_async_flow_create(npc, queue, table->roc_table, in_pattern,
+ in_actions, dst_pf_func, def_action, user_data,
+ &errcode);
+ else
+ flow = NULL;
+
+ for (j = 0; j < (int)RTE_DIM(free_allocs) && free_allocs[j]; j++)
+ plt_free((void *)free_allocs[j]);
+
+ if (rc != 0) {
+ rte_flow_error_set(error, rc < 0 ? -rc : rc, RTE_FLOW_ERROR_TYPE_ACTION_NUM, NULL,
+ "Failed to map flow data");
+ return NULL;
+ }
+ if (flow == NULL) {
+ rte_flow_error_set(error, errcode < 0 ? -errcode : EINVAL,
+ RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "async flow create failed");
+ return NULL;
+ }
+
+ return (struct rte_flow *)flow;
+}
+
+/* Enqueue a destroy operation. */
+int
+cnxk_flow_async_destroy(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr, struct rte_flow *flow, void *user_data,
+ struct rte_flow_error *error)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ int rc;
+
+ RTE_SET_USED(attr);
+
+ rc = roc_npc_async_flow_destroy(npc, queue, (struct roc_npc_template_flow *)flow,
+ user_data);
+ if (rc)
+ return rte_flow_error_set(error, -rc, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+ "bad queue or flow handle");
+ return 0;
+}
+
+/* Flush all pending async operations to hardware in one batch. */
+int
+cnxk_flow_push(struct rte_eth_dev *eth_dev, uint32_t queue, struct rte_flow_error *error)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ int rc;
+
+ rc = roc_npc_flow_push(npc, queue);
+ if (rc)
+ return rte_flow_error_set(error, -rc, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "bad queue");
+ return 0;
+}
+
+/* Return results for completed ops back to the application. */
+int
+cnxk_flow_pull(struct rte_eth_dev *eth_dev, uint32_t queue, struct rte_flow_op_result res[],
+ uint16_t n_res, struct rte_flow_error *error)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct roc_npc *npc = &dev->npc;
+ uint16_t done = 0;
+
+ /* Drain roc engine results in fixed-size batches, translating each
+ * roc op result into the rte op result without a per-pull allocation.
+ */
+ while (done < n_res) {
+ struct roc_npc_flow_op_result rres[64];
+ uint16_t batch = RTE_MIN((uint16_t)(n_res - done), (uint16_t)RTE_DIM(rres));
+ int got = roc_npc_flow_pull(npc, queue, rres, batch);
+ uint16_t k;
+
+ if (got < 0) {
+ if (done != 0)
+ break;
+ return rte_flow_error_set(error, -got, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+ NULL, "bad queue");
+ }
+
+ for (k = 0; k < (uint16_t)got; k++) {
+ res[done + k].status =
+ (rres[k].rc == 0) ? RTE_FLOW_OP_SUCCESS : RTE_FLOW_OP_ERROR;
+ res[done + k].user_data = rres[k].user_data;
+ }
+ done += (uint16_t)got;
+
+ if (got < batch)
+ break;
+ }
+
+ return done;
+}
+
+struct rte_flow *
+cnxk_flow_async_create_by_index(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr,
+ struct rte_flow_template_table *table, uint32_t rule_index,
+ const struct rte_flow_action actions[],
+ uint8_t action_template_index, void *user_data,
+ struct rte_flow_error *error)
+{
+ RTE_SET_USED(eth_dev);
+ RTE_SET_USED(queue);
+ RTE_SET_USED(attr);
+ RTE_SET_USED(table);
+ RTE_SET_USED(rule_index);
+ RTE_SET_USED(actions);
+ RTE_SET_USED(action_template_index);
+ RTE_SET_USED(user_data);
+
+ rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "index-only insertion needs a match key; NPC is match-based");
+ return NULL;
+}
+
+/* Like async_create but with an app-specified index; the app must avoid
+ * concurrent creates to the same index and use a valid table index.
+ */
+struct rte_flow *
+/* clang-format off */
+cnxk_flow_async_create_by_index_with_pattern(struct rte_eth_dev *eth_dev,
+ uint32_t queue, const struct rte_flow_op_attr *attr,
+ struct rte_flow_template_table *tbl_handle, uint32_t rule_index,
+ const struct rte_flow_item items[], uint8_t pattern_template_index,
+ const struct rte_flow_action actions[],
+ uint8_t action_template_index, void *user_data,
+ struct rte_flow_error *error)
+/* clang-format on */
+{
+ struct cnxk_flow_table *table = (struct cnxk_flow_table *)tbl_handle;
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct cnxk_flow_pattern_template *pt;
+ struct cnxk_flow_actions_template *at;
+ struct roc_npc_template_flow *flow;
+ struct roc_npc *npc = &dev->npc;
+
+ uint64_t free_allocs[ROC_NPC_MAX_ACTION_COUNT + ROC_NPC_ITEM_TYPE_END + 1] = {0};
+ struct roc_npc_item_info in_pattern[ROC_NPC_ITEM_TYPE_END + 1] = {0};
+ struct rte_flow_action combined_actions[ROC_NPC_MAX_ACTION_COUNT];
+ struct roc_npc_action in_actions[ROC_NPC_MAX_ACTION_COUNT] = {0};
+ struct rte_flow_item combined_items[ROC_NPC_ITEM_TYPE_END + 1];
+ struct roc_npc_action_sample in_sample = {0};
+ struct rte_flow_attr rte_attr;
+ struct roc_npc_attr in_attr;
+ uint32_t flowkey_cfg = 0;
+ uint16_t dst_pf_func = 0;
+ uint64_t def_action = 0;
+ int errcode = 0;
+ uint16_t k;
+ int rc, j;
+
+ RTE_SET_USED(attr);
+
+ if (table == NULL) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "bad table");
+ return NULL;
+ }
+ if (table->insertion_type != RTE_FLOW_TABLE_INSERTION_TYPE_INDEX_WITH_PATTERN) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "table is not index-with-pattern insertion");
+ return NULL;
+ }
+ if (pattern_template_index >= table->nb_pattern_templates ||
+ action_template_index >= table->nb_actions_templates) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "template index out of range");
+ return NULL;
+ }
+ if (rule_index >= table->nb_flows) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "rule_index out of range");
+ return NULL;
+ }
+
+ pt = table->pattern_templates[pattern_template_index];
+ at = table->actions_templates[action_template_index];
+
+ if (cnxk_flow_check_items(pt, items) != 0 || cnxk_flow_check_actions(at, actions) != 0) {
+ rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "pattern/actions do not match the selected templates");
+ return NULL;
+ }
+
+ for (k = 0; k < pt->nb_items; k++) {
+ combined_items[k].type = pt->pattern[k].type;
+ combined_items[k].spec = items ? items[k].spec : NULL;
+ combined_items[k].last = pt->pattern[k].last;
+ combined_items[k].mask = pt->pattern[k].mask;
+ }
+ combined_items[pt->nb_items].type = RTE_FLOW_ITEM_TYPE_END;
+
+ for (k = 0; k < at->nb_actions; k++) {
+ combined_actions[k].type = at->actions[k].type;
+ combined_actions[k].conf =
+ (actions && actions[k].conf) ? actions[k].conf : at->actions[k].conf;
+ }
+ combined_actions[at->nb_actions].type = RTE_FLOW_ACTION_TYPE_END;
+
+ memset(&rte_attr, 0, sizeof(rte_attr));
+ rte_attr.priority = table->flow_attr.priority;
+ rte_attr.ingress = table->flow_attr.ingress;
+ rte_attr.egress = table->flow_attr.egress;
+ rte_attr.transfer = table->transfer;
+
+ memset(&in_attr, 0, sizeof(in_attr));
+ rc = cnxk_map_flow_data(eth_dev, &rte_attr, combined_items, combined_actions, &in_attr,
+ in_pattern, in_actions, &in_sample, &flowkey_cfg, &dst_pf_func,
+ &def_action, false, free_allocs, 0);
+ if (rc == 0)
+ /* clang-format off */
+ flow = roc_npc_async_flow_create_by_index_with_pattern(npc, queue,
+ table->roc_table, rule_index, in_pattern, in_actions,
+ user_data, &errcode);
+ /* clang-format on */
+ else
+ flow = NULL;
+
+ for (j = 0; j < (int)RTE_DIM(free_allocs) && free_allocs[j]; j++)
+ plt_free((void *)free_allocs[j]);
+
+ if (rc != 0) {
+ rte_flow_error_set(error, rc < 0 ? -rc : rc, RTE_FLOW_ERROR_TYPE_ACTION_NUM, NULL,
+ "Failed to map flow data");
+ return NULL;
+ }
+ if (flow == NULL) {
+ rte_flow_error_set(error, errcode < 0 ? -errcode : EINVAL,
+ RTE_FLOW_ERROR_TYPE_HANDLE, NULL, "flow parse failed");
+ return NULL;
+ }
+
+ return (struct rte_flow *)flow;
+}
+
+/* Update the actions of an existing flow, keeping key/mask/mcam_id/enable. */
+int
+cnxk_flow_async_actions_update(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr, struct rte_flow *flow,
+ const struct rte_flow_action actions[],
+ uint8_t action_template_index, void *user_data,
+ struct rte_flow_error *error)
+{
+ struct cnxk_eth_dev *dev = cnxk_eth_pmd_priv(eth_dev);
+ struct cnxk_flow_actions_template *at;
+ struct roc_npc *npc = &dev->npc;
+ struct cnxk_flow_table *table;
+
+ uint64_t free_allocs[ROC_NPC_MAX_ACTION_COUNT + ROC_NPC_ITEM_TYPE_END + 1] = {0};
+ struct roc_npc_item_info in_pattern[ROC_NPC_ITEM_TYPE_END + 1] = {0};
+ struct rte_flow_action combined_actions[ROC_NPC_MAX_ACTION_COUNT];
+ struct roc_npc_action in_actions[ROC_NPC_MAX_ACTION_COUNT] = {0};
+ struct roc_npc_action_sample in_sample = {0};
+ struct rte_flow_item end_pattern[1];
+ struct rte_flow_attr rte_attr;
+ struct roc_npc_attr in_attr;
+ uint32_t flowkey_cfg = 0;
+ uint16_t dst_pf_func = 0;
+ uint64_t def_action = 0;
+ uint16_t k;
+ int rc, j;
+
+ RTE_SET_USED(attr);
+
+ if (flow == NULL)
+ return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+ "bad flow handle");
+
+ /* Recover the cnxk table context from the cookie the engine kept for
+ * this rule's table.
+ */
+ table = roc_npc_async_flow_table_cookie((struct roc_npc_template_flow *)flow);
+ if (table == NULL)
+ return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+ "flow handle has no associated table");
+ if (action_template_index >= table->nb_actions_templates)
+ return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+ "actions template index out of range");
+
+ at = table->actions_templates[action_template_index];
+
+ if (cnxk_flow_check_actions(at, actions) != 0)
+ return rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ACTION, NULL,
+ "actions do not match the selected template");
+
+ for (k = 0; k < at->nb_actions; k++) {
+ combined_actions[k].type = at->actions[k].type;
+ combined_actions[k].conf =
+ (actions && actions[k].conf) ? actions[k].conf : at->actions[k].conf;
+ }
+ combined_actions[at->nb_actions].type = RTE_FLOW_ACTION_TYPE_END;
+
+ end_pattern[0].type = RTE_FLOW_ITEM_TYPE_END;
+
+ memset(&rte_attr, 0, sizeof(rte_attr));
+ rte_attr.priority = table->flow_attr.priority;
+ rte_attr.ingress = table->flow_attr.ingress;
+ rte_attr.egress = table->flow_attr.egress;
+ rte_attr.transfer = table->transfer;
+
+ memset(&in_attr, 0, sizeof(in_attr));
+ rc = cnxk_map_flow_data(eth_dev, &rte_attr, end_pattern, combined_actions, &in_attr,
+ in_pattern, in_actions, &in_sample, &flowkey_cfg, &dst_pf_func,
+ &def_action, false, free_allocs, 0);
+ if (rc == 0)
+ /* clang-format off */
+ rc = roc_npc_async_flow_actions_update(npc, queue,
+ (struct roc_npc_template_flow *)flow, in_actions, user_data);
+ /* clang-format on */
+
+ for (j = 0; j < (int)RTE_DIM(free_allocs) && free_allocs[j]; j++)
+ plt_free((void *)free_allocs[j]);
+
+ if (rc != 0)
+ return rte_flow_error_set(error, rc < 0 ? -rc : EINVAL, RTE_FLOW_ERROR_TYPE_ACTION,
+ NULL, "failed to update flow actions");
+
+ return 0;
+}
static int
cnxk_flow_dev_dump(struct rte_eth_dev *eth_dev, struct rte_flow *flow, FILE *file,
diff --git a/drivers/net/cnxk/cnxk_flow.h b/drivers/net/cnxk/cnxk_flow.h
index 2986ea81d1..2202f4f3b8 100644
--- a/drivers/net/cnxk/cnxk_flow.h
+++ b/drivers/net/cnxk/cnxk_flow.h
@@ -48,4 +48,81 @@ int cnxk_flow_dev_dump_common(struct rte_eth_dev *eth_dev, struct rte_flow *flow
struct rte_flow_error *error, bool is_rep);
int cnxk_mtr_destroy(struct rte_eth_dev *eth_dev, uint32_t mtr_id);
+int cnxk_flow_configure(struct rte_eth_dev *eth_dev, const struct rte_flow_port_attr *port_attr,
+ uint16_t nb_queue, const struct rte_flow_queue_attr *queue_attr[],
+ struct rte_flow_error *err);
+
+/* clang-format off */
+struct rte_flow_pattern_template *cnxk_flow_pattern_template_create(struct rte_eth_dev *eth_dev,
+ const struct rte_flow_pattern_template_attr *attr,
+ const struct rte_flow_item pattern[],
+ struct rte_flow_error *error);
+/* clang-format on */
+
+int cnxk_flow_pattern_template_destroy(struct rte_eth_dev *eth_dev,
+ struct rte_flow_pattern_template *templ,
+ struct rte_flow_error *error);
+
+/* clang-format off */
+struct rte_flow_actions_template *cnxk_flow_actions_template_create(struct rte_eth_dev *eth_dev,
+ const struct rte_flow_actions_template_attr *attr,
+ const struct rte_flow_action actions[],
+ const struct rte_flow_action masks[],
+ struct rte_flow_error *error);
+/* clang-format on */
+
+int cnxk_flow_actions_template_destroy(struct rte_eth_dev *eth_dev,
+ struct rte_flow_actions_template *templ,
+ struct rte_flow_error *error);
+
+/* clang-format off */
+struct rte_flow_template_table *cnxk_flow_template_table_create(struct rte_eth_dev *eth_dev,
+ const struct rte_flow_template_table_attr *table_attr,
+ struct rte_flow_pattern_template *pattern_templates[],
+ uint8_t nb_pattern_templates,
+ struct rte_flow_actions_template *actions_templates[],
+ uint8_t nb_actions_templates,
+ struct rte_flow_error *error);
+/* clang-format on */
+
+int cnxk_flow_template_table_destroy(struct rte_eth_dev *eth_dev,
+ struct rte_flow_template_table *tbl_handle,
+ struct rte_flow_error *error);
+struct rte_flow *
+cnxk_flow_async_create(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr, struct rte_flow_template_table *table,
+ const struct rte_flow_item items[], uint8_t pattern_template_index,
+ const struct rte_flow_action actions[], uint8_t action_template_index,
+ void *user_data, struct rte_flow_error *error);
+int cnxk_flow_async_destroy(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr, struct rte_flow *flow,
+ void *user_data, struct rte_flow_error *error);
+
+int cnxk_flow_push(struct rte_eth_dev *eth_dev, uint32_t queue, struct rte_flow_error *error);
+int cnxk_flow_pull(struct rte_eth_dev *eth_dev, uint32_t queue, struct rte_flow_op_result res[],
+ uint16_t n_res, struct rte_flow_error *error);
+
+struct rte_flow *cnxk_flow_async_create_by_index(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr,
+ struct rte_flow_template_table *table,
+ uint32_t rule_index,
+ const struct rte_flow_action actions[],
+ uint8_t action_template_index, void *user_data,
+ struct rte_flow_error *error);
+
+/* clang-format off */
+struct rte_flow *cnxk_flow_async_create_by_index_with_pattern(struct rte_eth_dev *eth_dev,
+ uint32_t queue, const struct rte_flow_op_attr *attr,
+ struct rte_flow_template_table *tbl_handle, uint32_t rule_index,
+ const struct rte_flow_item items[], uint8_t pattern_template_index,
+ const struct rte_flow_action actions[],
+ uint8_t action_template_index, void *user_data,
+ struct rte_flow_error *error);
+/* clang-format on */
+
+int cnxk_flow_async_actions_update(struct rte_eth_dev *eth_dev, uint32_t queue,
+ const struct rte_flow_op_attr *attr, struct rte_flow *flow,
+ const struct rte_flow_action actions[],
+ uint8_t action_template_index, void *user_data,
+ struct rte_flow_error *error);
#endif /* __CNXK_RTE_FLOW_H__ */
--
2.43.0
^ permalink raw reply related
* RE: mempool cache change
From: Morten Brørup @ 2026-07-18 14:07 UTC (permalink / raw)
To: Kishore Padmanabha, Bruce Richardson
Cc: fengchengwen, Thomas Monjalon, dev, Wisam Jaddo, Andrew Rybchenko
In-Reply-To: <00fe4a299adc03ff7d39ef943e62b921@mail.gmail.com>
> From: Kishore Padmanabha [mailto:kishore.padmanabha@broadcom.com]
> Sent: Friday, 17 July 2026 19.10
>
> Hi Bruce,
>
> The below patch works fine for us. We tested all the different packet
> sizes.
> Thanks for the patch. Do you want to push this patch since it is not
> changing the ABI/API?
Too late in the release process.
Let's postpone the discussion for DPDK 26.11, where API/ABI breakage is allowed.
I'm not strongly opposed to Bruce's algorithm, targeting a fill level of 25 % from the edges and flushing/refilling up to 75 % of the cache when necessary. It does have its advantages for some mempool access patterns (which are not exotic).
I just prefer the current algorithm, targeting a fill level of 50 % and only flushing/refilling up to 50 % of the cache when necessary. It performs better at random get/put access patterns, and the backend transactions are smaller.
For DPDK 26.11, where we can break the API/ABI, we can simply double RTE_MEMPOOL_CACHE_MAX_SIZE to 1024, to compensate for reducing the effective cache size from 150 % to 100 %.
The mempool cache objs array will no longer be [RTE_MEMPOOL_CACHE_MAX_SIZE * 2], but only [RTE_MEMPOOL_CACHE_MAX_SIZE], so doubling RTE_MEMPOOL_CACHE_MAX_SIZE will not increase the memory footprint, but allow using a cache size up to 1024.
Please also note that the current implementation is carefully designed to keep the transfers to/from the mempool backend CPU cache aligned (assuming cache->size is 2^N and large enough).
Refer to the parameters passed to rte_mempool_ops_enqueue/dequeue_bulk().
E.g. with mempool cache size 256, backend transfers are 128 objects, 16 full cache lines.
Using CPU cache aligned transfers has a few advantages:
- There are no cache line ownership issues across different CPU cores repeatedly accessing the backend.
- The mempool backend drivers can be performance optimized for transferring full CPU cache lines. (Both source and destination addresses, and number of objects copied, are CPU cache aligned. Assuming all transfers go via the mempool cache.)
These details should be fine tuned in the implementation, if we do proceed with Bruce's algorithm.
-Morten
>
> Rgds,
> Kishore
>
> -----Original Message-----
> From: Bruce Richardson <bruce.richardson@intel.com>
> Sent: Thursday, July 16, 2026 5:57 AM
> To: Kishore Padmanabha <kishore.padmanabha@broadcom.com>
>
> On Wed, Jul 15, 2026 at 11:12:59AM -0400, Kishore Padmanabha wrote:
> > On Wed, Jul 15, 2026 at 5:02 AM Morten Brørup
> > <[1]mb@smartsharesystems.com> wrote:
> >
> > > From: fengchengwen [mailto:[2]fengchengwen@huawei.com]
> > > Sent: Wednesday, 15 July 2026 10.12
> > >
> > > On 7/15/2026 4:08 AM, Morten Brørup wrote:
> > > > Hi Kishore,
> > > >
> > > > For your testing purposes, please follow the guidance
> provided
> > to
> > > Wisam Jaddo:
> > > >
> > >
> >
> [3]https://inbox.dpdk.org/dev/98CBD80474FA8B44BF855DF32C47DC35F65964
> > @smart
> > > [4]server.smartshare.dk/
> > >
> > > We need to recompile in this case. We should try to avoid
> > > recompilation.
> > > I think it is necessary to adjust RTE_MEMPOOL_CACHE_MAX_SIZE
> to
> > 768 as
> > > a default configuration.
> > Changing RTE_MEMPOOL_CACHE_MAX_SIZE breaks both the API and the
> ABI;
> > so it has to be done when building locally, where API/ABI
> breakage
> > is acceptable.
> > For DPDK 26.11, where API/ABI breakage is acceptable, we can
> discuss
> > increasing the default from 512 to a higher value. I do have
> some
> > input to that discussion, but let's postpone it until after DPDK
> > 26.07 has been released.
> >
> > We should increase this value to avoid performance degradation, as
> > users may not realize they need to change it. We do not have do
> it
> > right away for 26.07 release but we should it right after the
> > release.
> >
>
> Out of interest, does adjusting the 50% fill/flush threshold to be a
> 75%
> one, i.e. fill to 75% rather than 50%, flush to 25% rather than 50%,
> help at
> all? Draft patch below, can you test it quickly, perhaps?
>
> /Bruce
>
> diff --git a/lib/mempool/rte_mempool.h b/lib/mempool/rte_mempool.h
> index
> 50d958c7c6..2526a23903 100644
> --- a/lib/mempool/rte_mempool.h
> +++ b/lib/mempool/rte_mempool.h
> @@ -1417,6 +1417,7 @@ rte_mempool_do_generic_put(struct rte_mempool
> *mp,
> void * const *obj_table,
> unsigned int n, struct rte_mempool_cache
> *cache)
> {
> void **cache_objs;
> + uint32_t quarter, flush;
>
> /* No cache provided? */
> if (unlikely(cache == NULL))
> @@ -1426,30 +1427,39 @@ rte_mempool_do_generic_put(struct rte_mempool
> *mp,
> void * const *obj_table,
> RTE_MEMPOOL_CACHE_STAT_ADD(cache, put_bulk, 1);
> RTE_MEMPOOL_CACHE_STAT_ADD(cache, put_objs, n);
>
> + /* A quarter (25%) of the cache size, computed with a shift to
> avoid
> + * a divide. Draining the cache down to this level on overflow
> leaves
> + * room for a burst of up to (size - quarter), i.e. three
> quarters
> + * (75%), of the cache size.
> + */
> + quarter = cache->size >> 2;
> +
> __rte_assume(cache->size <= RTE_MEMPOOL_CACHE_MAX_SIZE);
> - __rte_assume(cache->size / 2 <= RTE_MEMPOOL_CACHE_MAX_SIZE /
> 2);
> + __rte_assume(quarter <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
> __rte_assume(cache->len <= RTE_MEMPOOL_CACHE_MAX_SIZE);
> __rte_assume(cache->len <= cache->size);
> if (likely(cache->len + n <= cache->size)) {
> /* Sufficient room in the cache for the objects. */
> cache_objs = &cache->objs[cache->len];
> cache->len += n;
> - } else if (n <= cache->size / 2) {
> + } else if (n <= cache->size - quarter) {
> /*
> * The number of objects is within the cache bounce
> buffer
> limit,
> * but - as detected by the comparison above - the
> cache has
> * insufficient room for them.
> * Flush the cache to the backend to make room for the
> objects;
> - * flush (size / 2) objects from the bottom of the
> cache,
> where
> - * objects are less hot, and move down the remaining
> objects, which
> - * are more hot, from the upper half of the cache.
> + * flush objects from the bottom of the cache, where
> objects
> are
> + * less hot, draining it down to a quarter (25%) of its
> size, and
> + * move down the remaining quarter of objects, which
> are
> more hot,
> + * from the upper part of the cache.
> */
> - __rte_assume(cache->len > cache->size / 2);
> - rte_mempool_ops_enqueue_bulk(mp, &cache->objs[0],
> cache->size / 2);
> - rte_memcpy(&cache->objs[0], &cache->objs[cache->size /
> 2],
> - sizeof(void *) * (cache->len - cache-
> >size /
> 2));
> - cache_objs = &cache->objs[cache->len - cache->size /
> 2];
> - cache->len = cache->len - cache->size / 2 + n;
> + __rte_assume(cache->len > quarter);
> + flush = cache->len - quarter;
> + rte_mempool_ops_enqueue_bulk(mp, &cache->objs[0],
> flush);
> + rte_memcpy(&cache->objs[0], &cache->objs[flush],
> + sizeof(void *) * quarter);
> + cache_objs = &cache->objs[quarter];
> + cache->len = quarter + n;
> } else {
> /* The request itself is too big for the cache. */
> goto driver_enqueue_stats_incremented; @@ -1557,6
> +1567,7 @@
> rte_mempool_do_generic_get(struct rte_mempool *mp, void **obj_table,
> int ret;
> unsigned int remaining;
> uint32_t index, len;
> + uint32_t quarter, fill;
> void **cache_objs;
>
> /* No cache provided? */
> @@ -1592,13 +1603,23 @@ rte_mempool_do_generic_get(struct rte_mempool
> *mp,
> void **obj_table,
> for (index = 0; index < len; index++)
> *obj_table++ = *--cache_objs;
>
> + /* A quarter (25%) of the cache size, computed with a shift to
> avoid
> + * a divide; 'fill' is the complementary three quarters (75%),
> which
> + * is the amount fetched from the backend to fill the cache up
> to
> 75%,
> + * and also the burst limit for this bounce buffer (since the
> cache
> + * was just fully drained above, up to 'fill' objects can be
> filled
> + * and handed back to the caller in one go).
> + */
> + quarter = cache->size >> 2;
> + fill = cache->size - quarter;
> +
> /* Dequeue below would exceed the cache bounce buffer limit? */
> - __rte_assume(cache->size / 2 <= RTE_MEMPOOL_CACHE_MAX_SIZE /
> 2);
> - if (unlikely(remaining > cache->size / 2))
> + __rte_assume(fill <= RTE_MEMPOOL_CACHE_MAX_SIZE);
> + if (unlikely(remaining > fill))
> goto driver_dequeue;
>
> - /* Fill the cache from the backend; fetch (size / 2) objects.
> */
> - ret = rte_mempool_ops_dequeue_bulk(mp, cache->objs, cache->size
> /
> 2);
> + /* Fill the cache from the backend, up to 75% of its size. */
> + ret = rte_mempool_ops_dequeue_bulk(mp, cache->objs, fill);
> if (unlikely(ret < 0)) {
> /*
> * We are buffer constrained, and not able to fetch all
> that.
> @@ -1612,11 +1633,10 @@ rte_mempool_do_generic_get(struct rte_mempool
> *mp,
> void **obj_table,
> RTE_MEMPOOL_CACHE_STAT_ADD(cache, get_success_bulk, 1);
> RTE_MEMPOOL_CACHE_STAT_ADD(cache, get_success_objs, n);
>
> - __rte_assume(cache->size / 2 <= RTE_MEMPOOL_CACHE_MAX_SIZE /
> 2);
> - __rte_assume(remaining <= RTE_MEMPOOL_CACHE_MAX_SIZE / 2);
> - __rte_assume(remaining <= cache->size / 2);
> - cache_objs = &cache->objs[cache->size / 2];
> - cache->len = cache->size / 2 - remaining;
> + __rte_assume(fill <= RTE_MEMPOOL_CACHE_MAX_SIZE);
> + __rte_assume(remaining <= fill);
> + cache_objs = &cache->objs[fill];
> + cache->len = fill - remaining;
> for (index = 0; index < remaining; index++)
> *obj_table++ = *--cache_objs;
^ permalink raw reply
* Re: [PATCH] doc: using container to build and run applications
From: Thomas Monjalon @ 2026-07-18 20:22 UTC (permalink / raw)
To: Andrea Panattoni; +Cc: dev
In-Reply-To: <20260603140421.3023794-1-apanatto@redhat.com>
03/06/2026 16:04, Andrea Panattoni:
> Add explanation about how container runtimes like podman
> or docker can be used to build and run DPDK application.
>
> Signed-off-by: Andrea Panattoni <apanatto@redhat.com>
[...]
> +.. _building_dpdk_in_container:
Adding a prefix linux_gsg_ to this label.
> +
> +Building Applications in a Container
> +------------------------------------
Actually it should be "Building DPDK in a Container"
> +Running Sample Application in a Container
> +-----------------------------------------
It should be "Running an Application in a Container" as testpmd is not a sample app.
Applied with adjustments, thanks.
^ 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