DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH] net/iavf: fix scalar Rx path zero-length segment
From: Bruce Richardson @ 2026-06-15  9:33 UTC (permalink / raw)
  To: Loftus, Ciara; +Cc: dev@dpdk.org, stable@dpdk.org, Doherty, Declan
In-Reply-To: <IA4PR11MB92788A2FA65444A5BDC62C0E8EE62@IA4PR11MB9278.namprd11.prod.outlook.com>

On Mon, Jun 15, 2026 at 10:17:41AM +0100, Loftus, Ciara wrote:
> > Subject: Re: [PATCH] net/iavf: fix scalar Rx path zero-length segment
> > 
> > On Fri, Jun 12, 2026 at 02:35:31PM +0000, Ciara Loftus wrote:
> > > When hardware CRC stripping is active, a frame whose on-wire size is an
> > > exact multiple of the Rx buffer size can cause the NIC to fill the final
> > > data descriptor and place the four CRC bytes into a separate trailing
> > > descriptor. After hardware stripping, that descriptor carries zero bytes
> > > of payload.
> > >
> > > The existing CRC cleanup code only handles a zero-length trailing segment
> > > when software CRC stripping is enabled. When hardware stripping is
> > > active, the zero-length mbuf is silently chained to the reassembled
> > > packet. Forwarding such a packet causes a zero-length Tx descriptor,
> > > triggering a Malicious Driver Detection event on the PF and resetting
> > > the VF.
> > >
> > > Fix by adding logic to detect a zero-length final segment when hardware
> > > CRC stripping is active, and freeing it.
> > >
> > > Fixes: a2b29a7733ef ("net/avf: enable basic Rx Tx")
> > > Fixes: b8b4c54ef9b0 ("net/iavf: support flexible Rx descriptor in normal
> > path")
> > > Cc: stable@dpdk.org
> > >
> > > Signed-off-by: Declan Doherty <declan.doherty@intel.com>
> > > Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> > > ---
> > >  drivers/net/intel/iavf/iavf_rxtx.c | 16 ++++++++++++++++
> > >  1 file changed, 16 insertions(+)
> > >
> > > diff --git a/drivers/net/intel/iavf/iavf_rxtx.c
> > b/drivers/net/intel/iavf/iavf_rxtx.c
> > > index a57af7faed..86ebb2618d 100644
> > > --- a/drivers/net/intel/iavf/iavf_rxtx.c
> > > +++ b/drivers/net/intel/iavf/iavf_rxtx.c
> > > @@ -1716,6 +1716,14 @@ iavf_recv_scattered_pkts_flex_rxd(void
> > *rx_queue, struct rte_mbuf **rx_pkts,
> > >  				rxm->data_len = (uint16_t)(rx_packet_len -
> > >
> > 	RTE_ETHER_CRC_LEN);
> > >  			}
> > > +		} else if (unlikely(rx_packet_len == 0)) {
> > > +			/*
> > > +			 * NIC split CRC bytes into a trailing segment which is
> > > +			 * now empty after hardware CRC stripping. Free it.
> > > +			 */
> > > +			rte_pktmbuf_free_seg(rxm);
> > > +			first_seg->nb_segs--;
> > > +			last_seg->next = NULL;
> > >  		}
> > >
> > 
> > The vector paths also handle scattered packets (via reassembly). Do they
> > need a fix for this? What about the other drivers that work on the PF, such
> > as ice/i40e?
> 
> The vector paths use the common ci_rx_reassemble_packets which already
> handles the zero-length trailing segment case correctly. When
> crc_len == 0 and the last segment has data_len == 0, the empty segment
> is freed.
> 
> The ice scalar path had the same issue but it was patched in 2022:
> https://git.dpdk.org/dpdk/commit/?id=90ba4442058a14763e57ca96d03ab1e6044e3e5c
> I cannot reproduce the behaviour on i40e hardware (either PF or VF) so I
> don't think it needs to be patched as the HW seems to behave
> differently.
> 

Thanks for clarifying.

Acked-by: Bruce Richardson <bruce.richardson@intel.com>

As an asside for future work: we should consider if we can also convert the
scalar Rx paths for our drivers to match that of the vector, where we do a
simplified receive per descriptor, pretending that each descriptor is its
own packet, and then use the reassemble_packets call to put any scattered
packets back together. I would if that would lead to better scalar Rx
performance.

/Bruce

^ permalink raw reply

* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop
From: David Marchand @ 2026-06-15  9:26 UTC (permalink / raw)
  To: Maxime Leroy
  Cc: hemant.agrawal, sachin.saxena, dev, stable, Thomas Monjalon,
	Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori
In-Reply-To: <20260611154926.392670-9-maxime@leroys.fr>

On Thu, 11 Jun 2026 at 17:51, Maxime Leroy <maxime@leroys.fr> wrote:
>
> eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release
> via a compound literal, so every field it omits is zeroed to NULL. It
> sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving
> rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare
> and the recycle callbacks NULL.
>
> In non-debug builds these ops are reached through an unguarded indirect
> call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a
> thread calling e.g. rte_eth_rx_queue_count() on a port being stopped
> dereferences NULL and crashes, while the same race on rte_eth_rx_burst()
> is harmless because the burst ops are reset to dummies. A poll-mode
> worker re-checking rx_queue_count before arming the Rx interrupt and
> sleeping hits exactly this.
>
> Reset these ops to the same dummies eth_dev_set_dummy_fops() installs,
> so a stopped port behaves like a freshly allocated one: every fast-path
> op is a safe no-op, none is NULL.
>
> Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path")
> Cc: stable@dpdk.org
> Signed-off-by: Maxime Leroy <maxime@leroys.fr>
> ---
>  lib/ethdev/ethdev_private.c | 7 +++++++
>  1 file changed, 7 insertions(+)
>
> diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c
> index 72a0723846..75ea3eedff 100644
> --- a/lib/ethdev/ethdev_private.c
> +++ b/lib/ethdev/ethdev_private.c
> @@ -263,6 +263,13 @@ eth_dev_fp_ops_reset(struct rte_eth_fp_ops *fpo)
>         *fpo = (struct rte_eth_fp_ops) {
>                 .rx_pkt_burst = dummy_eth_rx_burst,
>                 .tx_pkt_burst = dummy_eth_tx_burst,
> +               .tx_pkt_prepare = rte_eth_tx_pkt_prepare_dummy,
> +               .rx_queue_count = rte_eth_queue_count_dummy,
> +               .tx_queue_count = rte_eth_queue_count_dummy,
> +               .rx_descriptor_status = rte_eth_descriptor_status_dummy,
> +               .tx_descriptor_status = rte_eth_descriptor_status_dummy,
> +               .recycle_tx_mbufs_reuse = rte_eth_recycle_tx_mbufs_reuse_dummy,
> +               .recycle_rx_descriptors_refill = rte_eth_recycle_rx_descriptors_refill_dummy,
>                 .rxq = {
>                         .data = (void **)&dummy_queues_array[port_id],
>                         .clbk = dummy_data,

Could we replace eth_dev_set_dummy_fops() with a call to
eth_dev_fp_ops_reset() in rte_eth_dev_allocate?
I don't like keeping two separate helpers.


-- 
David Marchand


^ permalink raw reply

* [DPDK/core Bug 1955] [dpdk26.07-rc1] power_intel_uncore: start dpdk-l3fwd-power failed on Ubuntu26.04
From: bugzilla @ 2026-06-15  9:25 UTC (permalink / raw)
  To: dev

http://bugs.dpdk.org/show_bug.cgi?id=1955

            Bug ID: 1955
           Summary: [dpdk26.07-rc1] power_intel_uncore: start
                    dpdk-l3fwd-power failed on Ubuntu26.04
           Product: DPDK
           Version: 26.07
          Hardware: All
                OS: All
            Status: UNCONFIRMED
          Severity: normal
          Priority: Normal
         Component: core
          Assignee: dev@dpdk.org
          Reporter: daxuex.gao@intel.com
  Target Milestone: ---

Environment
===========
DPDK version: dpdk-26.07-rc1 (c429b06df56788795f8)
OS: Ubuntu 26.04 LTS/Linux 7.0.0-14-generic
Compiler: gcc version 15.2.0 (Ubuntu 15.2.0-16ubuntu1)
NIC hardware: Ethernet Controller E810-C for SFP 1593 [8086:1593 (rev 01)]
NIC firmware: 
  driver: vfio-pci
  kdriver: ice-2.6.4
  fw: 5.00 0x80021c11 1.4002.0
  ddp: ICE OS Package version 1.3.59.0

Test Setup
Steps to reproduce
==================
1.Build dpdk 
CC=gcc meson -Dlibdir=lib  --default-library=static x86_64-native-linuxapp-gcc
ninja -C x86_64-native-linuxapp-gcc
meson configure -Dexamples=l3fwd-power x86_64-native-linuxapp-gcc
ninja -C x86_64-native-linuxapp-gcc

2.Start dpdk-l3fwd-power
usertools/dpdk-devbind.py --force --bind=vfio-pci 0000:38:00.0 0000:38:00.1
/root/dpdk/x86_64-native-linuxapp-gcc/examples/dpdk-l3fwd-power  -l 1-2 -n 1 --
-p 0x1 -P --config="(0,0,2)" -u

Results: 
========
# /root/dpdk/x86_64-native-linuxapp-gcc/examples/dpdk-l3fwd-power  -l 1-2 -n 1
-- -p 0x1 -P --config="(0,0,2)" -u
EAL: Detected CPU lcores: 112
EAL: Detected NUMA nodes: 8
EAL: Detected static linkage of DPDK
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'VA'
EAL: VFIO support initialized
Promiscuous mode selected
/root/dpdk/x86_64-native-linuxapp-gcc/examples/dpdk-l3fwd-power [EAL options]
-- -p PORTMASK -P  [--config (port,queue,lcore)[,(port,queue,lcore]] 
[--high-perf-cores CORELIST  [--perf-config
(port,queue,hi_perf,lcore_index)[,(port,queue,hi_perf,lcore_index]] 
[--max-pkt-len PKTLEN]
  -p PORTMASK: hexadecimal bitmask of ports to configure
  -P: enable promiscuous mode
  -u: set min/max frequency for uncore to minimum value
  -U: set min/max frequency for uncore to maximum value
  -i (frequency index): set min/max frequency for uncore to specified frequency
index
  --config (port,queue,lcore): rx queues configuration
  --eth-link-speed: force link speed
  --cpu-resume-latency LATENCY: set CPU resume latency to control C-state
selection, 0 : just allow to enter C0-state
  --high-perf-cores CORELIST: list of high performance cores
  --perf-config: similar as config, cores specified as indices for bins
containing high or regular performance cores
  --no-numa: optional, disable numa awareness
  --max-pkt-len PKTLEN: maximum packet length in decimal (64-9600)
  --parse-ptype: parse packet type by software
  --legacy: use legacy interrupt-based scaling
 --telemetry: enable telemetry mode, to update empty polls, full polls, and
core busyness to telemetry
 --interrupt-only: enable interrupt-only mode
 --pmd-mgmt MODE: enable PMD power management mode. Currently supported modes:
baseline, monitor, pause, scale
  --max-empty-polls MAX_EMPTY_POLLS: number of empty polls to wait before
entering sleep state
  --pause-duration DURATION: set the duration, in microseconds, of the pause
callback
  --scale-freq-min FREQ_MIN: set minimum frequency for scaling mode for all
application lcores (FREQ_MIN must be in kHz, in increments of 100MHz)
  --scale-freq-max FREQ_MAX: set maximum frequency for scaling mode for all
application lcores (FREQ_MAX must be in kHz, in increments of 100MHz)
EAL: Error - exiting with code: 1
Invalid L3FWD parameters


Expected Result:
Startup successful 

Regression
Is this issue a regression: (Y/N)Y

-- 
You are receiving this mail because:
You are the assignee for the bug.

^ permalink raw reply

* Re: [PATCH v4 0/9] power: centralize lcore ID validation
From: fengchengwen @ 2026-06-15  9:22 UTC (permalink / raw)
  To: Huisong Li, thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, yangxingui, zhanjie9
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

LGTM
Series-acked-by: Chengwen Feng <fengchengwen@huawei.com>

On 6/15/2026 3:30 PM, Huisong Li wrote:
> This series centralizes the lcore ID verification in the power cpufreq
> framework, replacing the per-driver range checks with a common validation.
> 
> Background
> ----------
> Currently, various cpufreq drivers implement their own lcore ID checks,
> which are limited to simple range validation against RTE_MAX_LCORE and
> involve significant code duplication across 12+ functions per driver.
> The checks are duplicated across all drivers — any change requires
> updating 5+ drivers identically. Moreover, these checks do not verify
> whether the lcore is actually managed by the application. So it is better
> to verify lcore ID in cpufreq core.
> 
> For cpufreq-related APIs, although service cores do not typically invoke
> these APIs, they may operate in polling modes where power management is
> required. To maintain compatibility with applications using service cores,
> the validation logic now explicitly accepts both ROLE_RTE and ROLE_SERVICE.
> 
> The usage of power QoS APIs are similar to that of cpufreq. They also can
> accepts ROLE_RTE and ROLE_SERVICE.
> 
> For PMD power management APIs, the lcore must be ROLE_RTE because these
> are used together with the data plane of ethdev PMD. Hence,
> rte_lcore_is_enabled() is used for validation.
> 
> Key Changes:
> ------------
> Patch 1: Adds a common macro (RTE_POWER_VALID_LCOREID_OR_ERR_RET)
>          that accepts both roles.
> Patch 2: Adds the validation to the cpufreq framework layer.
> Patches 3-7: Remove the now-redundant per-driver RTE_MAX_LCORE checks.
> Patch 8: Update power QoS to use the new validation, allowing
>          service cores to configure QoS parameters.
> Patch 9: Add lcore validation to PMD management functions.
> 
> Changes:
> --------
> v4: remove the patch that add the helper function rte_lcore_is_eal_managed.
> 
> v3:
>  - update release note.
>  - add __rte_experimental for new helper function.
>  - restructure this patch set to facilitate review.
> 
> v2:
>  - allow the service cores to set power API.
> 
> ----
> 
> Huisong Li (9):
>   power: add a common macro to verify lcore ID
>   power/cpufreq: add the lcore ID verification to framework
>   power/acpi: remove redundant lcore ID checks
>   power/amd_pstate: remove redundant lcore ID checks
>   power/cppc: remove redundant lcore ID checks
>   power/intel_pstate: remove redundant lcore ID checks
>   power/kvm_vm: remove redundant lcore ID checks
>   power: allow the service core to config power QoS
>   power: add lcore ID check for PMD mgmt
> 
>  doc/guides/rel_notes/release_26_07.rst        |  7 ++
>  drivers/power/acpi/acpi_cpufreq.c             | 65 -------------------
>  drivers/power/amd_pstate/amd_pstate_cpufreq.c | 65 -------------------
>  drivers/power/cppc/cppc_cpufreq.c             | 65 -------------------
>  .../power/intel_pstate/intel_pstate_cpufreq.c | 65 -------------------
>  drivers/power/kvm_vm/guest_channel.c          | 22 -------
>  drivers/power/kvm_vm/kvm_vm.c                 | 10 ---
>  lib/power/power_common.h                      |  8 +++
>  lib/power/rte_power_cpufreq.c                 | 13 ++++
>  lib/power/rte_power_pmd_mgmt.c                | 21 +++---
>  lib/power/rte_power_qos.c                     | 10 +--
>  11 files changed, 41 insertions(+), 310 deletions(-)
> 


^ permalink raw reply

* RE: [PATCH] net/iavf: fix scalar Rx path zero-length segment
From: Loftus, Ciara @ 2026-06-15  9:17 UTC (permalink / raw)
  To: Richardson, Bruce; +Cc: dev@dpdk.org, stable@dpdk.org, Doherty, Declan
In-Reply-To: <aiwo_4aTWi8kBIkG@bricha3-mobl1.ger.corp.intel.com>

> Subject: Re: [PATCH] net/iavf: fix scalar Rx path zero-length segment
> 
> On Fri, Jun 12, 2026 at 02:35:31PM +0000, Ciara Loftus wrote:
> > When hardware CRC stripping is active, a frame whose on-wire size is an
> > exact multiple of the Rx buffer size can cause the NIC to fill the final
> > data descriptor and place the four CRC bytes into a separate trailing
> > descriptor. After hardware stripping, that descriptor carries zero bytes
> > of payload.
> >
> > The existing CRC cleanup code only handles a zero-length trailing segment
> > when software CRC stripping is enabled. When hardware stripping is
> > active, the zero-length mbuf is silently chained to the reassembled
> > packet. Forwarding such a packet causes a zero-length Tx descriptor,
> > triggering a Malicious Driver Detection event on the PF and resetting
> > the VF.
> >
> > Fix by adding logic to detect a zero-length final segment when hardware
> > CRC stripping is active, and freeing it.
> >
> > Fixes: a2b29a7733ef ("net/avf: enable basic Rx Tx")
> > Fixes: b8b4c54ef9b0 ("net/iavf: support flexible Rx descriptor in normal
> path")
> > Cc: stable@dpdk.org
> >
> > Signed-off-by: Declan Doherty <declan.doherty@intel.com>
> > Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> > ---
> >  drivers/net/intel/iavf/iavf_rxtx.c | 16 ++++++++++++++++
> >  1 file changed, 16 insertions(+)
> >
> > diff --git a/drivers/net/intel/iavf/iavf_rxtx.c
> b/drivers/net/intel/iavf/iavf_rxtx.c
> > index a57af7faed..86ebb2618d 100644
> > --- a/drivers/net/intel/iavf/iavf_rxtx.c
> > +++ b/drivers/net/intel/iavf/iavf_rxtx.c
> > @@ -1716,6 +1716,14 @@ iavf_recv_scattered_pkts_flex_rxd(void
> *rx_queue, struct rte_mbuf **rx_pkts,
> >  				rxm->data_len = (uint16_t)(rx_packet_len -
> >
> 	RTE_ETHER_CRC_LEN);
> >  			}
> > +		} else if (unlikely(rx_packet_len == 0)) {
> > +			/*
> > +			 * NIC split CRC bytes into a trailing segment which is
> > +			 * now empty after hardware CRC stripping. Free it.
> > +			 */
> > +			rte_pktmbuf_free_seg(rxm);
> > +			first_seg->nb_segs--;
> > +			last_seg->next = NULL;
> >  		}
> >
> 
> The vector paths also handle scattered packets (via reassembly). Do they
> need a fix for this? What about the other drivers that work on the PF, such
> as ice/i40e?

The vector paths use the common ci_rx_reassemble_packets which already
handles the zero-length trailing segment case correctly. When
crc_len == 0 and the last segment has data_len == 0, the empty segment
is freed.

The ice scalar path had the same issue but it was patched in 2022:
https://git.dpdk.org/dpdk/commit/?id=90ba4442058a14763e57ca96d03ab1e6044e3e5c
I cannot reproduce the behaviour on i40e hardware (either PF or VF) so I
don't think it needs to be patched as the HW seems to behave
differently.

> 
> /Bruce
> 
> >  		first_seg->port = rxq->port_id;
> > @@ -1884,6 +1892,14 @@ iavf_recv_scattered_pkts(void *rx_queue, struct
> rte_mbuf **rx_pkts,
> >  			} else
> >  				rxm->data_len = (uint16_t)(rx_packet_len -
> >
> 	RTE_ETHER_CRC_LEN);
> > +		} else if (unlikely(rx_packet_len == 0)) {
> > +			/*
> > +			 * NIC split CRC bytes into a trailing segment which is
> > +			 * now empty after hardware CRC stripping. Free it.
> > +			 */
> > +			rte_pktmbuf_free_seg(rxm);
> > +			first_seg->nb_segs--;
> > +			last_seg->next = NULL;
> >  		}
> >
> >  		first_seg->port = rxq->port_id;
> > --
> > 2.43.0
> >

^ permalink raw reply

* Re: [PATCH v2] app/testpmd: add padding mode to txonly engine
From: yangxingui @ 2026-06-15  9:12 UTC (permalink / raw)
  To: Stephen Hemminger
  Cc: dev, david.marchand, aman.deep.singh, fengchengwen, yangshuaisong,
	lihuisong, liuyonglong, kangfenglong
In-Reply-To: <20260612091343.14344ef7@phoenix.local>



On 2026/6/13 0:13, Stephen Hemminger wrote:
> On Fri, 12 Jun 2026 17:12:17 +0800
> Xingui Yang <yangxingui@huawei.com> wrote:
> 
>> Add a new padding mode to the txonly forwarding engine, which allows
>> sending packets with configurable small sizes without standard L2/L3
>> headers. This is useful for testing NIC padding logic.
>>
>> When padding mode is enabled via --tx-pkt-pad-mode flag:
>> - l2_len and l3_len are set to 0 instead of standard header lengths
>> - Packet data is filled with a static pattern instead of
>>    Ethernet/IP/UDP headers
>> - Minimum packet length validation is bypassed to allow small
>>    packet sizes (e.g., set txpkts 14)
>>
>> Signed-off-by: Xingui Yang <yangxingui@huawei.com>
>> Signed-off-by: Huisong Li <lihuisong@huawei.com>
>> ---
>> v2: Fix compilation exception of unterminated-string-initialization
>> ---
> 
> What about something like this (*not tested*) patch.

Hi Stephen,

Thank you for the valuable suggestion! Your approach is cleaner and
more elegant for generating runt frames with truncated headers.

However, our use case requires sending packets smaller than the
Ethernet header size (e.g., `set txpkts 9` or `set txpkts 2`).
These ultra-small packets are needed to test NIC padding logic where
no standard L2/L3 headers are present at all.

We could combine both approaches:
- Allow packet lengths down to 1 byte (remove the 14-byte minimum)
- Dynamically compute l2_len/l3_len based on actual packet length
- Use a fill pattern for packets too small to hold Ethernet header
- Keep the checksum offload handling from your patch

This way testpmd can support the full range:
- Normal packets (>= 14 + 20 + 8 bytes): full headers with checksum
- Runt frames (14-42 bytes): truncated headers, checksums disabled
- Ultra-small packets (<14 bytes): fill pattern only, l2_len/l3_len = 0

Would this combined approach be acceptable? We can submit a v3 patch
incorporating your suggestions plus support for ultra-small packets.

Thanks,
Xingui Yang

^ permalink raw reply

* Re: [PATCH v2] eal: add destructor to unregister tailq on unload
From: David Marchand @ 2026-06-15  7:57 UTC (permalink / raw)
  To: Stephen Hemminger, fengchengwen
  Cc: dev, stable, Bruce Richardson, Neil Horman
In-Reply-To: <20260610085749.7bb0a4f3@phoenix.local>

On Wed, 10 Jun 2026 at 17:58, Stephen Hemminger
<stephen@networkplumber.org> wrote:
>
> On Wed, 10 Jun 2026 09:19:42 +0800
> fengchengwen <fengchengwen@huawei.com> wrote:
>
> > >
> > > +RTE_EXPORT_SYMBOL(rte_eal_tailq_unregister)
> >
> > this should be with EXPERIMENTAL
>
> Not possible, this is part of the EAL_REGISTER_TAILQ macro and usage
> is under the covers. So if anything was marked experimental it would
> fail code that did not allow experimental

Indeed.


> > > +void
> > > +rte_eal_tailq_unregister(struct rte_tailq_elem *t)
> > > +{
> > > +   TAILQ_REMOVE(&rte_tailq_elem_head, t, next);
> >
> > We need first make sure it exist the tailq, just like TAILQ_FOREACH rte_eal_tailq_local_register()
>
> Ok cheap scan since not in critical path.

I had excluded this point when looking at the v2 patch, considering
that abort() is called in the constructor on failure, and destructors
are not called after abort().

Is your concern that rte_eal_tailq_unregister could be called
directory by the application with random pointer?
A check would be safer on paper, but it seems strange to protect here.

Am I missing another case?


-- 
David Marchand


^ permalink raw reply

* [PATCH v4 9/9] power: add lcore ID check for PMD mgmt
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

The pmd_mgmt lib is mainly used together with the data plane of ethdev
PMD. The core in data plane is ROLE_RTE. So use rte_lcore_is_enabled
to verify it.

Fixes: 426511683762 ("power: add get/set min/max scaling frequencies API")
Cc: stable@dpdk.org

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 lib/power/rte_power_pmd_mgmt.c | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/lib/power/rte_power_pmd_mgmt.c b/lib/power/rte_power_pmd_mgmt.c
index a4d53aac2a..a5fc1c3a94 100644
--- a/lib/power/rte_power_pmd_mgmt.c
+++ b/lib/power/rte_power_pmd_mgmt.c
@@ -511,7 +511,8 @@ rte_power_ethdev_pmgmt_queue_enable(unsigned int lcore_id, uint16_t port_id,
 
 	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL);
 
-	if (queue_id >= RTE_MAX_QUEUES_PER_PORT || lcore_id >= RTE_MAX_LCORE) {
+	if (queue_id >= RTE_MAX_QUEUES_PER_PORT ||
+	    !rte_lcore_is_enabled(lcore_id)) {
 		ret = -EINVAL;
 		goto end;
 	}
@@ -627,7 +628,7 @@ rte_power_ethdev_pmgmt_queue_disable(unsigned int lcore_id,
 
 	RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -EINVAL);
 
-	if (lcore_id >= RTE_MAX_LCORE || queue_id >= RTE_MAX_QUEUES_PER_PORT)
+	if (!rte_lcore_is_enabled(lcore_id) || queue_id >= RTE_MAX_QUEUES_PER_PORT)
 		return -EINVAL;
 
 	/* check if the queue is stopped */
@@ -729,8 +730,8 @@ RTE_EXPORT_SYMBOL(rte_power_pmd_mgmt_set_scaling_freq_min)
 int
 rte_power_pmd_mgmt_set_scaling_freq_min(unsigned int lcore, unsigned int min)
 {
-	if (lcore >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID: %u", lcore);
+	if (!rte_lcore_is_enabled(lcore)) {
+		POWER_LOG(ERR, "lcore id %u is not enabled", lcore);
 		return -EINVAL;
 	}
 
@@ -747,8 +748,8 @@ RTE_EXPORT_SYMBOL(rte_power_pmd_mgmt_set_scaling_freq_max)
 int
 rte_power_pmd_mgmt_set_scaling_freq_max(unsigned int lcore, unsigned int max)
 {
-	if (lcore >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID: %u", lcore);
+	if (!rte_lcore_is_enabled(lcore)) {
+		POWER_LOG(ERR, "lcore id %u is not enabled", lcore);
 		return -EINVAL;
 	}
 
@@ -769,8 +770,8 @@ RTE_EXPORT_SYMBOL(rte_power_pmd_mgmt_get_scaling_freq_min)
 int
 rte_power_pmd_mgmt_get_scaling_freq_min(unsigned int lcore)
 {
-	if (lcore >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID: %u", lcore);
+	if (!rte_lcore_is_enabled(lcore)) {
+		POWER_LOG(ERR, "lcore id %u is not enabled", lcore);
 		return -EINVAL;
 	}
 
@@ -784,8 +785,8 @@ RTE_EXPORT_SYMBOL(rte_power_pmd_mgmt_get_scaling_freq_max)
 int
 rte_power_pmd_mgmt_get_scaling_freq_max(unsigned int lcore)
 {
-	if (lcore >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID: %u", lcore);
+	if (!rte_lcore_is_enabled(lcore)) {
+		POWER_LOG(ERR, "lcore id %u is not enabled", lcore);
 		return -EINVAL;
 	}
 
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 8/9] power: allow the service core to config power QoS
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

The lcore ID verification in power QoS API used to use
rte_lcore_is_enabled(), which only accepts the lcore with
ROLE_RTE role. But service core thread (ROLE_SERVICE) can
also use power QoS API.

So use RTE_POWER_VALID_LCOREID_OR_ERR_RET to verify the
lcore ID. This change makes the power QoS API accept both
ROLE_RTE and ROLE_SERVICE lcores.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 doc/guides/rel_notes/release_26_07.rst |  5 ++++-
 lib/power/rte_power_qos.c              | 10 ++--------
 2 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index aeabb908ec..5eb3974ddb 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -158,7 +158,10 @@ New Features
 * **Added a common macro to verify lcore ID in power core.**
 
   Added the ``RTE_POWER_VALID_LCOREID_OR_ERR_RET`` macro to verify lcore ID
-  in power core.
+  in power core. The power QoS library also updated its lcore validation to
+  use this macro, so service cores (``ROLE_SERVICE``) are now permitted.
+
+
 
 Removed Items
 -------------
diff --git a/lib/power/rte_power_qos.c b/lib/power/rte_power_qos.c
index f991230532..d8d8d36a76 100644
--- a/lib/power/rte_power_qos.c
+++ b/lib/power/rte_power_qos.c
@@ -27,10 +27,7 @@ rte_power_qos_set_cpu_resume_latency(uint16_t lcore_id, int latency)
 	FILE *f;
 	int ret;
 
-	if (!rte_lcore_is_enabled(lcore_id)) {
-		POWER_LOG(ERR, "lcore id %u is not enabled", lcore_id);
-		return -EINVAL;
-	}
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -EINVAL);
 	ret = power_get_lcore_mapped_cpu_id(lcore_id, &cpu_id);
 	if (ret != 0)
 		return ret;
@@ -82,10 +79,7 @@ rte_power_qos_get_cpu_resume_latency(uint16_t lcore_id)
 	FILE *f;
 	int ret;
 
-	if (!rte_lcore_is_enabled(lcore_id)) {
-		POWER_LOG(ERR, "lcore id %u is not enabled", lcore_id);
-		return -EINVAL;
-	}
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -EINVAL);
 	ret = power_get_lcore_mapped_cpu_id(lcore_id, &cpu_id);
 	if (ret != 0)
 		return ret;
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 7/9] power/kvm_vm: remove redundant lcore ID checks
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

Now that the cpufreq framework validates the lcore ID using
RTE_POWER_VALID_LCOREID_OR_ERR_RET() before dispatching to any driver,
each individual cpufreq driver no longer needs its own range
check against RTE_MAX_LCORE.

Remove the duplicated lcore ID checks from the kvm_vm cpufreq
driver and its guest_channel helper.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 drivers/power/kvm_vm/guest_channel.c | 22 ----------------------
 drivers/power/kvm_vm/kvm_vm.c        | 10 ----------
 2 files changed, 32 deletions(-)

diff --git a/drivers/power/kvm_vm/guest_channel.c b/drivers/power/kvm_vm/guest_channel.c
index 42bfcedb56..dc8fe05fef 100644
--- a/drivers/power/kvm_vm/guest_channel.c
+++ b/drivers/power/kvm_vm/guest_channel.c
@@ -61,11 +61,6 @@ guest_channel_host_connect(const char *path, unsigned int lcore_id)
 	char fd_path[PATH_MAX];
 	int fd = -1;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		GUEST_CHANNEL_LOG(ERR, "Channel(%u) is out of range 0...%d",
-				lcore_id, RTE_MAX_LCORE-1);
-		return -1;
-	}
 	/* check if path is already open */
 	if (global_fds[lcore_id] != -1) {
 		GUEST_CHANNEL_LOG(ERR, "Channel(%u) is already open with fd %d",
@@ -127,12 +122,6 @@ guest_channel_send_msg(struct rte_power_channel_packet *pkt,
 	int ret, buffer_len = sizeof(*pkt);
 	void *buffer = pkt;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		GUEST_CHANNEL_LOG(ERR, "Channel(%u) is out of range 0...%d",
-				lcore_id, RTE_MAX_LCORE-1);
-		return -1;
-	}
-
 	if (global_fds[lcore_id] < 0) {
 		GUEST_CHANNEL_LOG(ERR, "Channel is not connected");
 		return -1;
@@ -169,12 +158,6 @@ int power_guest_channel_read_msg(void *pkt,
 	if (pkt_len == 0 || pkt == NULL)
 		return -1;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		GUEST_CHANNEL_LOG(ERR, "Channel(%u) is out of range 0...%d",
-				lcore_id, RTE_MAX_LCORE-1);
-		return -1;
-	}
-
 	if (global_fds[lcore_id] < 0) {
 		GUEST_CHANNEL_LOG(ERR, "Channel is not connected");
 		return -1;
@@ -225,11 +208,6 @@ int rte_power_guest_channel_receive_msg(void *pkt,
 void
 guest_channel_host_disconnect(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		GUEST_CHANNEL_LOG(ERR, "Channel(%u) is out of range 0...%d",
-				lcore_id, RTE_MAX_LCORE-1);
-		return;
-	}
 	if (global_fds[lcore_id] < 0)
 		return;
 	close(global_fds[lcore_id]);
diff --git a/drivers/power/kvm_vm/kvm_vm.c b/drivers/power/kvm_vm/kvm_vm.c
index 5754a441cd..e8b454bb55 100644
--- a/drivers/power/kvm_vm/kvm_vm.c
+++ b/drivers/power/kvm_vm/kvm_vm.c
@@ -24,11 +24,6 @@ power_kvm_vm_check_supported(void)
 int
 power_kvm_vm_init(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Core(%u) is out of range 0...%d",
-				lcore_id, RTE_MAX_LCORE-1);
-		return -1;
-	}
 	pkt[lcore_id].command = RTE_POWER_CPU_POWER;
 	pkt[lcore_id].resource_id = lcore_id;
 	return guest_channel_host_connect(FD_PATH, lcore_id);
@@ -73,11 +68,6 @@ send_msg(unsigned int lcore_id, uint32_t scale_direction)
 {
 	int ret;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Core(%u) is out of range 0...%d",
-				lcore_id, RTE_MAX_LCORE-1);
-		return -1;
-	}
 	pkt[lcore_id].unit = scale_direction;
 	ret = guest_channel_send_msg(&pkt[lcore_id], lcore_id);
 	if (ret == 0)
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 6/9] power/intel_pstate: remove redundant lcore ID checks
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

Now that the cpufreq framework validates the lcore ID using
RTE_POWER_VALID_LCOREID_OR_ERR_RET() before dispatching to any driver,
each individual cpufreq driver no longer needs its own range
check against RTE_MAX_LCORE.

Remove the duplicated lcore ID checks from the intel_pstate
cpufreq driver ops.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 .../power/intel_pstate/intel_pstate_cpufreq.c | 65 -------------------
 1 file changed, 65 deletions(-)

diff --git a/drivers/power/intel_pstate/intel_pstate_cpufreq.c b/drivers/power/intel_pstate/intel_pstate_cpufreq.c
index 22a1b4465a..dfbb5635a1 100644
--- a/drivers/power/intel_pstate/intel_pstate_cpufreq.c
+++ b/drivers/power/intel_pstate/intel_pstate_cpufreq.c
@@ -540,12 +540,6 @@ power_pstate_cpufreq_init(unsigned int lcore_id)
 		return -1;
 	}
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceed %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_IDLE;
 	/* The power in use state works as a guard variable between
@@ -622,11 +616,6 @@ power_pstate_cpufreq_exit(unsigned int lcore_id)
 	struct pstate_power_info *pi;
 	uint32_t exp_state;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
 	pi = &lcore_power_info[lcore_id];
 
 	exp_state = POWER_USED;
@@ -680,11 +669,6 @@ power_pstate_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return 0;
-	}
-
 	if (freqs == NULL) {
 		POWER_LOG(ERR, "NULL buffer supplied");
 		return 0;
@@ -703,11 +687,6 @@ power_pstate_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num)
 uint32_t
 power_pstate_cpufreq_get_freq(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return RTE_POWER_INVALID_FREQ_INDEX;
-	}
-
 	return lcore_power_info[lcore_id].curr_idx;
 }
 
@@ -715,11 +694,6 @@ power_pstate_cpufreq_get_freq(unsigned int lcore_id)
 int
 power_pstate_cpufreq_set_freq(unsigned int lcore_id, uint32_t index)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	return set_freq_internal(&(lcore_power_info[lcore_id]), index);
 }
 
@@ -728,11 +702,6 @@ power_pstate_cpufreq_freq_up(unsigned int lcore_id)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx == 0 ||
 	    (pi->curr_idx == 1 && pi->turbo_available && !pi->turbo_enable))
@@ -747,11 +716,6 @@ power_pstate_cpufreq_freq_down(unsigned int lcore_id)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx + 1 == pi->nb_freqs)
 		return 0;
@@ -763,11 +727,6 @@ power_pstate_cpufreq_freq_down(unsigned int lcore_id)
 int
 power_pstate_cpufreq_freq_max(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	/* Frequencies in the array are from high to low. */
 	if (lcore_power_info[lcore_id].turbo_available) {
 		if (lcore_power_info[lcore_id].turbo_enable)
@@ -788,11 +747,6 @@ power_pstate_cpufreq_freq_min(unsigned int lcore_id)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	/* Frequencies in the array are from high to low. */
@@ -805,11 +759,6 @@ power_pstate_turbo_status(unsigned int lcore_id)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	return pi->turbo_enable;
@@ -820,11 +769,6 @@ power_pstate_enable_turbo(unsigned int lcore_id)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	if (pi->turbo_available)
@@ -846,11 +790,6 @@ power_pstate_disable_turbo(unsigned int lcore_id)
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	pi->turbo_enable = 0;
@@ -874,10 +813,6 @@ int power_pstate_get_capabilities(unsigned int lcore_id,
 {
 	struct pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
 	if (caps == NULL) {
 		POWER_LOG(ERR, "Invalid argument");
 		return -1;
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 5/9] power/cppc: remove redundant lcore ID checks
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

Now that the cpufreq framework validates the lcore ID using
RTE_POWER_VALID_LCOREID_OR_ERR_RET() before dispatching to any driver,
each individual cpufreq driver no longer needs its own range
check against RTE_MAX_LCORE.

Remove the duplicated lcore ID checks from the cppc cpufreq
driver ops.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 drivers/power/cppc/cppc_cpufreq.c | 65 -------------------------------
 1 file changed, 65 deletions(-)

diff --git a/drivers/power/cppc/cppc_cpufreq.c b/drivers/power/cppc/cppc_cpufreq.c
index 9ae25bad27..aed44c1212 100644
--- a/drivers/power/cppc/cppc_cpufreq.c
+++ b/drivers/power/cppc/cppc_cpufreq.c
@@ -337,12 +337,6 @@ power_cppc_cpufreq_init(unsigned int lcore_id)
 		return -1;
 	}
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_IDLE;
 	/* The power in use state works as a guard variable between
@@ -420,11 +414,6 @@ power_cppc_cpufreq_exit(unsigned int lcore_id)
 	struct cppc_power_info *pi;
 	uint32_t exp_state;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_USED;
 	/* The power in use state works as a guard variable between
@@ -470,11 +459,6 @@ power_cppc_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return 0;
-	}
-
 	if (freqs == NULL) {
 		POWER_LOG(ERR, "NULL buffer supplied");
 		return 0;
@@ -493,22 +477,12 @@ power_cppc_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num)
 uint32_t
 power_cppc_cpufreq_get_freq(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return RTE_POWER_INVALID_FREQ_INDEX;
-	}
-
 	return lcore_power_info[lcore_id].curr_idx;
 }
 
 int
 power_cppc_cpufreq_set_freq(unsigned int lcore_id, uint32_t index)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	return set_freq_internal(&(lcore_power_info[lcore_id]), index);
 }
 
@@ -517,11 +491,6 @@ power_cppc_cpufreq_freq_down(unsigned int lcore_id)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx + 1 == pi->nb_freqs)
 		return 0;
@@ -535,11 +504,6 @@ power_cppc_cpufreq_freq_up(unsigned int lcore_id)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx == 0 || (pi->curr_idx == 1 &&
 		pi->turbo_available && !pi->turbo_enable))
@@ -552,11 +516,6 @@ power_cppc_cpufreq_freq_up(unsigned int lcore_id)
 int
 power_cppc_cpufreq_freq_max(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	/* Frequencies in the array are from high to low. */
 	if (lcore_power_info[lcore_id].turbo_available) {
 		if (lcore_power_info[lcore_id].turbo_enable)
@@ -576,11 +535,6 @@ power_cppc_cpufreq_freq_min(unsigned int lcore_id)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	/* Frequencies in the array are from high to low. */
@@ -592,11 +546,6 @@ power_cppc_turbo_status(unsigned int lcore_id)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	return pi->turbo_enable;
@@ -607,11 +556,6 @@ power_cppc_enable_turbo(unsigned int lcore_id)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	if (pi->turbo_available)
@@ -643,11 +587,6 @@ power_cppc_disable_turbo(unsigned int lcore_id)
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	pi->turbo_enable = 0;
@@ -671,10 +610,6 @@ power_cppc_get_capabilities(unsigned int lcore_id,
 {
 	struct cppc_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
 	if (caps == NULL) {
 		POWER_LOG(ERR, "Invalid argument");
 		return -1;
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 4/9] power/amd_pstate: remove redundant lcore ID checks
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

Now that the cpufreq framework validates the lcore ID using
RTE_POWER_VALID_LCOREID_OR_ERR_RET() before dispatching to any driver,
each individual cpufreq driver no longer needs its own range
check against RTE_MAX_LCORE.

Remove the duplicated lcore ID checks from the amd_pstate
cpufreq driver ops.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 drivers/power/amd_pstate/amd_pstate_cpufreq.c | 65 -------------------
 1 file changed, 65 deletions(-)

diff --git a/drivers/power/amd_pstate/amd_pstate_cpufreq.c b/drivers/power/amd_pstate/amd_pstate_cpufreq.c
index bc67981d71..af9c1309f3 100644
--- a/drivers/power/amd_pstate/amd_pstate_cpufreq.c
+++ b/drivers/power/amd_pstate/amd_pstate_cpufreq.c
@@ -351,12 +351,6 @@ power_amd_pstate_cpufreq_init(unsigned int lcore_id)
 		return -1;
 	}
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_IDLE;
 	/* The power in use state works as a guard variable between
@@ -434,11 +428,6 @@ power_amd_pstate_cpufreq_exit(unsigned int lcore_id)
 	struct amd_pstate_power_info *pi;
 	uint32_t exp_state;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_USED;
 	/* The power in use state works as a guard variable between
@@ -484,11 +473,6 @@ power_amd_pstate_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return 0;
-	}
-
 	if (freqs == NULL) {
 		POWER_LOG(ERR, "NULL buffer supplied");
 		return 0;
@@ -507,22 +491,12 @@ power_amd_pstate_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t
 uint32_t
 power_amd_pstate_cpufreq_get_freq(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return RTE_POWER_INVALID_FREQ_INDEX;
-	}
-
 	return lcore_power_info[lcore_id].curr_idx;
 }
 
 int
 power_amd_pstate_cpufreq_set_freq(unsigned int lcore_id, uint32_t index)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	return set_freq_internal(&(lcore_power_info[lcore_id]), index);
 }
 
@@ -531,11 +505,6 @@ power_amd_pstate_cpufreq_freq_down(unsigned int lcore_id)
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx + 1 == pi->nb_freqs)
 		return 0;
@@ -549,11 +518,6 @@ power_amd_pstate_cpufreq_freq_up(unsigned int lcore_id)
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx == 0 || (pi->curr_idx == pi->nom_idx &&
 		pi->turbo_available && !pi->turbo_enable))
@@ -566,11 +530,6 @@ power_amd_pstate_cpufreq_freq_up(unsigned int lcore_id)
 int
 power_amd_pstate_cpufreq_freq_max(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	/* Frequencies in the array are from high to low. */
 	if (lcore_power_info[lcore_id].turbo_available) {
 		if (lcore_power_info[lcore_id].turbo_enable)
@@ -591,11 +550,6 @@ power_amd_pstate_cpufreq_freq_min(unsigned int lcore_id)
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	/* Frequencies in the array are from high to low. */
@@ -607,11 +561,6 @@ power_amd_pstate_turbo_status(unsigned int lcore_id)
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	return pi->turbo_enable;
@@ -622,11 +571,6 @@ power_amd_pstate_enable_turbo(unsigned int lcore_id)
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	if (pi->turbo_available)
@@ -658,11 +602,6 @@ power_amd_pstate_disable_turbo(unsigned int lcore_id)
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	pi->turbo_enable = 0;
@@ -686,10 +625,6 @@ power_amd_pstate_get_capabilities(unsigned int lcore_id,
 {
 	struct amd_pstate_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
 	if (caps == NULL) {
 		POWER_LOG(ERR, "Invalid argument");
 		return -1;
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 1/9] power: add a common macro to verify lcore ID
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

There are many places to verify lcore ID in power. It is necessary
to add a common macro in power core.

According to the applicattion in l3fwd-power, the lcore must be at
least within the RTE_MAX_LCORE range and be a core of the ROLE_RTE
role. But the service on the ROLE_SERVICE core also can use and
require power management feature.

So this common macro restricts that the lcore ID must be ROLE_RTE
and ROLE_SERVICE.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 doc/guides/rel_notes/release_26_07.rst | 4 ++++
 lib/power/power_common.h               | 8 ++++++++
 2 files changed, 12 insertions(+)

diff --git a/doc/guides/rel_notes/release_26_07.rst b/doc/guides/rel_notes/release_26_07.rst
index 5d7aa8d1bf..aeabb908ec 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -155,6 +155,10 @@ New Features
   Added AGENTS.md file for AI review
   and supporting scripts to review patches and documentation.
 
+* **Added a common macro to verify lcore ID in power core.**
+
+  Added the ``RTE_POWER_VALID_LCOREID_OR_ERR_RET`` macro to verify lcore ID
+  in power core.
 
 Removed Items
 -------------
diff --git a/lib/power/power_common.h b/lib/power/power_common.h
index e2d5b68a17..370c5246c6 100644
--- a/lib/power/power_common.h
+++ b/lib/power/power_common.h
@@ -25,6 +25,14 @@ extern int rte_power_logtype;
 
 #define POWER_CONVERT_TO_DECIMAL 10
 
+#define RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, retval) do {   \
+	if (rte_eal_lcore_role(lcore_id) != ROLE_RTE &&             \
+	    rte_eal_lcore_role(lcore_id) != ROLE_SERVICE) {         \
+		POWER_LOG(ERR, "lcore id %u is invalid", lcore_id); \
+		return retval;                                      \
+	}                                                           \
+} while (0)
+
 /* check if scaling driver matches one we want */
 __rte_internal
 int cpufreq_check_scaling_driver(const char *driver);
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 3/9] power/acpi: remove redundant lcore ID checks
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

Now that the cpufreq framework validates the lcore ID using
RTE_POWER_VALID_LCOREID_OR_ERR_RET() before dispatching to any driver,
each individual cpufreq driver no longer needs its own range
check against RTE_MAX_LCORE.

Remove the duplicated lcore ID checks from the acpi cpufreq
driver ops.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 drivers/power/acpi/acpi_cpufreq.c | 65 -------------------------------
 1 file changed, 65 deletions(-)

diff --git a/drivers/power/acpi/acpi_cpufreq.c b/drivers/power/acpi/acpi_cpufreq.c
index 875c66336d..af85a8cdec 100644
--- a/drivers/power/acpi/acpi_cpufreq.c
+++ b/drivers/power/acpi/acpi_cpufreq.c
@@ -234,12 +234,6 @@ power_acpi_cpufreq_init(unsigned int lcore_id)
 		return -1;
 	}
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_IDLE;
 	/* The power in use state works as a guard variable between
@@ -311,11 +305,6 @@ power_acpi_cpufreq_exit(unsigned int lcore_id)
 	struct acpi_power_info *pi;
 	uint32_t exp_state;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Lcore id %u can not exceeds %u",
-				lcore_id, RTE_MAX_LCORE - 1U);
-		return -1;
-	}
 	pi = &lcore_power_info[lcore_id];
 	exp_state = POWER_USED;
 	/* The power in use state works as a guard variable between
@@ -365,11 +354,6 @@ power_acpi_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return 0;
-	}
-
 	if (freqs == NULL) {
 		POWER_LOG(ERR, "NULL buffer supplied");
 		return 0;
@@ -388,22 +372,12 @@ power_acpi_cpufreq_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t num)
 uint32_t
 power_acpi_cpufreq_get_freq(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return RTE_POWER_INVALID_FREQ_INDEX;
-	}
-
 	return lcore_power_info[lcore_id].curr_idx;
 }
 
 int
 power_acpi_cpufreq_set_freq(unsigned int lcore_id, uint32_t index)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	return set_freq_internal(&(lcore_power_info[lcore_id]), index);
 }
 
@@ -412,11 +386,6 @@ power_acpi_cpufreq_freq_down(unsigned int lcore_id)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx + 1 == pi->nb_freqs)
 		return 0;
@@ -430,11 +399,6 @@ power_acpi_cpufreq_freq_up(unsigned int lcore_id)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 	if (pi->curr_idx == 0 ||
 	    (pi->curr_idx == 1 && pi->turbo_available && !pi->turbo_enable))
@@ -447,11 +411,6 @@ power_acpi_cpufreq_freq_up(unsigned int lcore_id)
 int
 power_acpi_cpufreq_freq_max(unsigned int lcore_id)
 {
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	/* Frequencies in the array are from high to low. */
 	if (lcore_power_info[lcore_id].turbo_available) {
 		if (lcore_power_info[lcore_id].turbo_enable)
@@ -471,11 +430,6 @@ power_acpi_cpufreq_freq_min(unsigned int lcore_id)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	/* Frequencies in the array are from high to low. */
@@ -488,11 +442,6 @@ power_acpi_turbo_status(unsigned int lcore_id)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	return pi->turbo_enable;
@@ -504,11 +453,6 @@ power_acpi_enable_turbo(unsigned int lcore_id)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	if (pi->turbo_available)
@@ -537,11 +481,6 @@ power_acpi_disable_turbo(unsigned int lcore_id)
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
-
 	pi = &lcore_power_info[lcore_id];
 
 	 pi->turbo_enable = 0;
@@ -564,10 +503,6 @@ int power_acpi_get_capabilities(unsigned int lcore_id,
 {
 	struct acpi_power_info *pi;
 
-	if (lcore_id >= RTE_MAX_LCORE) {
-		POWER_LOG(ERR, "Invalid lcore ID");
-		return -1;
-	}
 	if (caps == NULL) {
 		POWER_LOG(ERR, "Invalid argument");
 		return -1;
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 2/9] power/cpufreq: add the lcore ID verification to framework
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong
In-Reply-To: <20260615073050.1996063-1-lihuisong@huawei.com>

Currently, many cpufreq drivers verify the lcore ID in their
own driver and just restrict it to the RTE_MAX_LCORE range.
Actually, the lcore ID verification is common for each cpufreq
driver. It is better to add this verification to framework.

In addition, the lcore ID for cpufreq library is used by the
application, which is missing in cpufreq library or driver.
So use RTE_POWER_VALID_LCOREID_OR_ERR_RET to verify it in
cpufreq framework.

Please note the lcore ID for cpufreq library must be ROLE_RTE
or ROLE_SERVICE after this patch.

Signed-off-by: Huisong Li <lihuisong@huawei.com>
---
 lib/power/rte_power_cpufreq.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/lib/power/rte_power_cpufreq.c b/lib/power/rte_power_cpufreq.c
index f63e976dc2..8394cbc77f 100644
--- a/lib/power/rte_power_cpufreq.c
+++ b/lib/power/rte_power_cpufreq.c
@@ -116,6 +116,7 @@ rte_power_init(unsigned int lcore_id)
 	struct rte_power_cpufreq_ops *ops;
 	uint8_t env;
 
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	if (global_default_env != PM_ENV_NOT_SET)
 		return global_cpufreq_ops->init(lcore_id);
 
@@ -147,6 +148,7 @@ RTE_EXPORT_SYMBOL(rte_power_exit)
 int
 rte_power_exit(unsigned int lcore_id)
 {
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	if (global_default_env != PM_ENV_NOT_SET)
 		return global_cpufreq_ops->exit(lcore_id);
 
@@ -161,6 +163,7 @@ uint32_t
 rte_power_freqs(unsigned int lcore_id, uint32_t *freqs, uint32_t n)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, 0);
 	return global_cpufreq_ops->get_avail_freqs(lcore_id, freqs, n);
 }
 
@@ -169,6 +172,7 @@ uint32_t
 rte_power_get_freq(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, RTE_POWER_INVALID_FREQ_INDEX);
 	return global_cpufreq_ops->get_freq(lcore_id);
 }
 
@@ -177,6 +181,7 @@ uint32_t
 rte_power_set_freq(unsigned int lcore_id, uint32_t index)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->set_freq(lcore_id, index);
 }
 
@@ -185,6 +190,7 @@ int
 rte_power_freq_up(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->freq_up(lcore_id);
 }
 
@@ -193,6 +199,7 @@ int
 rte_power_freq_down(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->freq_down(lcore_id);
 }
 
@@ -201,6 +208,7 @@ int
 rte_power_freq_max(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->freq_max(lcore_id);
 }
 
@@ -209,6 +217,7 @@ int
 rte_power_freq_min(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->freq_min(lcore_id);
 }
 
@@ -217,6 +226,7 @@ int
 rte_power_turbo_status(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->turbo_status(lcore_id);
 }
 
@@ -225,6 +235,7 @@ int
 rte_power_freq_enable_turbo(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->enable_turbo(lcore_id);
 }
 
@@ -233,6 +244,7 @@ int
 rte_power_freq_disable_turbo(unsigned int lcore_id)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->disable_turbo(lcore_id);
 }
 
@@ -242,5 +254,6 @@ rte_power_get_capabilities(unsigned int lcore_id,
 		struct rte_power_core_capabilities *caps)
 {
 	RTE_ASSERT(global_cpufreq_ops != NULL);
+	RTE_POWER_VALID_LCOREID_OR_ERR_RET(lcore_id, -1);
 	return global_cpufreq_ops->get_caps(lcore_id, caps);
 }
-- 
2.33.0


^ permalink raw reply related

* [PATCH v4 0/9] power: centralize lcore ID validation
From: Huisong Li @ 2026-06-15  7:30 UTC (permalink / raw)
  To: thomas, anatoly.burakov, sivaprasad.tummala
  Cc: dev, stephen, fengchengwen, yangxingui, zhanjie9, lihuisong

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset="yes", Size: 3156 bytes --]

This series centralizes the lcore ID verification in the power cpufreq
framework, replacing the per-driver range checks with a common validation.

Background
----------
Currently, various cpufreq drivers implement their own lcore ID checks,
which are limited to simple range validation against RTE_MAX_LCORE and
involve significant code duplication across 12+ functions per driver.
The checks are duplicated across all drivers — any change requires
updating 5+ drivers identically. Moreover, these checks do not verify
whether the lcore is actually managed by the application. So it is better
to verify lcore ID in cpufreq core.

For cpufreq-related APIs, although service cores do not typically invoke
these APIs, they may operate in polling modes where power management is
required. To maintain compatibility with applications using service cores,
the validation logic now explicitly accepts both ROLE_RTE and ROLE_SERVICE.

The usage of power QoS APIs are similar to that of cpufreq. They also can
accepts ROLE_RTE and ROLE_SERVICE.

For PMD power management APIs, the lcore must be ROLE_RTE because these
are used together with the data plane of ethdev PMD. Hence,
rte_lcore_is_enabled() is used for validation.

Key Changes:
------------
Patch 1: Adds a common macro (RTE_POWER_VALID_LCOREID_OR_ERR_RET)
         that accepts both roles.
Patch 2: Adds the validation to the cpufreq framework layer.
Patches 3-7: Remove the now-redundant per-driver RTE_MAX_LCORE checks.
Patch 8: Update power QoS to use the new validation, allowing
         service cores to configure QoS parameters.
Patch 9: Add lcore validation to PMD management functions.

Changes:
--------
v4: remove the patch that add the helper function rte_lcore_is_eal_managed.

v3:
 - update release note.
 - add __rte_experimental for new helper function.
 - restructure this patch set to facilitate review.

v2:
 - allow the service cores to set power API.

----

Huisong Li (9):
  power: add a common macro to verify lcore ID
  power/cpufreq: add the lcore ID verification to framework
  power/acpi: remove redundant lcore ID checks
  power/amd_pstate: remove redundant lcore ID checks
  power/cppc: remove redundant lcore ID checks
  power/intel_pstate: remove redundant lcore ID checks
  power/kvm_vm: remove redundant lcore ID checks
  power: allow the service core to config power QoS
  power: add lcore ID check for PMD mgmt

 doc/guides/rel_notes/release_26_07.rst        |  7 ++
 drivers/power/acpi/acpi_cpufreq.c             | 65 -------------------
 drivers/power/amd_pstate/amd_pstate_cpufreq.c | 65 -------------------
 drivers/power/cppc/cppc_cpufreq.c             | 65 -------------------
 .../power/intel_pstate/intel_pstate_cpufreq.c | 65 -------------------
 drivers/power/kvm_vm/guest_channel.c          | 22 -------
 drivers/power/kvm_vm/kvm_vm.c                 | 10 ---
 lib/power/power_common.h                      |  8 +++
 lib/power/rte_power_cpufreq.c                 | 13 ++++
 lib/power/rte_power_pmd_mgmt.c                | 21 +++---
 lib/power/rte_power_qos.c                     | 10 +--
 11 files changed, 41 insertions(+), 310 deletions(-)

-- 
2.33.0


^ permalink raw reply

* [PATCH v5 2/4] net/zxdh: optimize queue structure to improve performance
From: Junlong Wang @ 2026-06-15  1:19 UTC (permalink / raw)
  To: stephen; +Cc: dev, Junlong Wang
In-Reply-To: <20260615011931.940545-1-wang.junlong1@zte.com.cn>


[-- Attachment #1.1.1: Type: text/plain, Size: 16846 bytes --]

1. Reorganize structure fields for better cache locality.
2. Remove RX software ring (sw_ring) to reduce memory allocation and
   copy.
3. Remove zxdh_mb(), use native rte_mb().
4. optimize zxdh_queue_notify() functions, remove unnecessary feature
   check.

Signed-off-by: Junlong Wang <wang.junlong1@zte.com.cn>
---
 drivers/net/zxdh/zxdh_ethdev.c |  33 +--------
 drivers/net/zxdh/zxdh_pci.c    |   2 +-
 drivers/net/zxdh/zxdh_queue.c  |  11 ++-
 drivers/net/zxdh/zxdh_queue.h  | 120 ++++++++++++++++-----------------
 drivers/net/zxdh/zxdh_rxtx.c   |  22 +++---
 5 files changed, 77 insertions(+), 111 deletions(-)

diff --git a/drivers/net/zxdh/zxdh_ethdev.c b/drivers/net/zxdh/zxdh_ethdev.c
index 80ff19b3ea..a383619419 100644
--- a/drivers/net/zxdh/zxdh_ethdev.c
+++ b/drivers/net/zxdh/zxdh_ethdev.c
@@ -644,7 +644,6 @@ zxdh_init_queue(struct rte_eth_dev *dev, uint16_t vtpci_logic_qidx)
 	struct zxdh_virtnet_tx *txvq = NULL;
 	struct zxdh_virtqueue *vq = NULL;
 	size_t sz_hdr_mz = 0;
-	void *sw_ring = NULL;
 	int32_t queue_type = zxdh_get_queue_type(vtpci_logic_qidx);
 	int32_t numa_node = dev->device->numa_node;
 	uint16_t vtpci_phy_qidx = 0;
@@ -692,11 +691,10 @@ zxdh_init_queue(struct rte_eth_dev *dev, uint16_t vtpci_logic_qidx)
 	vq->vq_queue_index = vtpci_phy_qidx;
 	vq->vq_nentries = vq_size;
 
-	vq->vq_packed.used_wrap_counter = 1;
-	vq->vq_packed.cached_flags = ZXDH_VRING_PACKED_DESC_F_AVAIL;
-	vq->vq_packed.event_flags_shadow = 0;
+	vq->used_wrap_counter = 1;
+	vq->cached_flags = ZXDH_VRING_PACKED_DESC_F_AVAIL;
 	if (queue_type == ZXDH_VTNET_RQ)
-		vq->vq_packed.cached_flags |= ZXDH_VRING_DESC_F_WRITE;
+		vq->cached_flags |= ZXDH_VRING_DESC_F_WRITE;
 
 	/*
 	 * Reserve a memzone for vring elements
@@ -741,16 +739,6 @@ zxdh_init_queue(struct rte_eth_dev *dev, uint16_t vtpci_logic_qidx)
 	}
 
 	if (queue_type == ZXDH_VTNET_RQ) {
-		size_t sz_sw = (ZXDH_MBUF_BURST_SZ + vq_size) * sizeof(vq->sw_ring[0]);
-
-		sw_ring = rte_zmalloc_socket("sw_ring", sz_sw, RTE_CACHE_LINE_SIZE, numa_node);
-		if (!sw_ring) {
-			PMD_DRV_LOG(ERR, "can not allocate RX soft ring");
-			ret = -ENOMEM;
-			goto fail_q_alloc;
-		}
-
-		vq->sw_ring = sw_ring;
 		rxvq = &vq->rxq;
 		rxvq->vq = vq;
 		rxvq->port_id = dev->data->port_id;
@@ -764,23 +752,9 @@ zxdh_init_queue(struct rte_eth_dev *dev, uint16_t vtpci_logic_qidx)
 		txvq->zxdh_net_hdr_mem = hdr_mz->iova;
 	}
 
-	vq->offset = offsetof(struct rte_mbuf, buf_iova);
 	if (queue_type == ZXDH_VTNET_TQ) {
 		struct zxdh_tx_region *txr = hdr_mz->addr;
-		uint32_t i;
-
 		memset(txr, 0, vq_size * sizeof(*txr));
-		for (i = 0; i < vq_size; i++) {
-			/* first indirect descriptor is always the tx header */
-			struct zxdh_vring_packed_desc *start_dp = txr[i].tx_packed_indir;
-
-			zxdh_vring_desc_init_indirect_packed(start_dp,
-					RTE_DIM(txr[i].tx_packed_indir));
-			start_dp->addr = txvq->zxdh_net_hdr_mem + i * sizeof(*txr) +
-					offsetof(struct zxdh_tx_region, tx_hdr);
-			/* length will be updated to actual pi hdr size when xmit pkt */
-			start_dp->len = 0;
-		}
 	}
 	if (ZXDH_VTPCI_OPS(hw)->setup_queue(hw, vq) < 0) {
 		PMD_DRV_LOG(ERR, "setup_queue failed");
@@ -788,7 +762,6 @@ zxdh_init_queue(struct rte_eth_dev *dev, uint16_t vtpci_logic_qidx)
 	}
 	return 0;
 fail_q_alloc:
-	rte_free(sw_ring);
 	rte_memzone_free(hdr_mz);
 	rte_memzone_free(mz);
 	rte_free(vq);
diff --git a/drivers/net/zxdh/zxdh_pci.c b/drivers/net/zxdh/zxdh_pci.c
index 4ba31905fc..0bc27ed111 100644
--- a/drivers/net/zxdh/zxdh_pci.c
+++ b/drivers/net/zxdh/zxdh_pci.c
@@ -231,7 +231,7 @@ zxdh_notify_queue(struct zxdh_hw *hw, struct zxdh_virtqueue *vq)
 
 	notify_data = ((uint32_t)vq->vq_avail_idx << 16) | vq->vq_queue_index;
 	if (zxdh_pci_with_feature(hw, ZXDH_F_RING_PACKED) &&
-			(vq->vq_packed.cached_flags & ZXDH_VRING_PACKED_DESC_F_AVAIL))
+			(vq->cached_flags & ZXDH_VRING_PACKED_DESC_F_AVAIL))
 		notify_data |= RTE_BIT32(31);
 
 	PMD_DRV_LOG(DEBUG, "queue:%d notify_data 0x%x notify_addr 0x%p",
diff --git a/drivers/net/zxdh/zxdh_queue.c b/drivers/net/zxdh/zxdh_queue.c
index 7162593b16..4668cb5d13 100644
--- a/drivers/net/zxdh/zxdh_queue.c
+++ b/drivers/net/zxdh/zxdh_queue.c
@@ -407,7 +407,7 @@ int32_t zxdh_enqueue_recv_refill_packed(struct zxdh_virtqueue *vq,
 {
 	struct zxdh_vring_packed_desc *start_dp = vq->vq_packed.ring.desc;
 	struct zxdh_vq_desc_extra *dxp;
-	uint16_t flags = vq->vq_packed.cached_flags;
+	uint16_t flags = vq->cached_flags;
 	int32_t i;
 	uint16_t idx;
 
@@ -415,7 +415,6 @@ int32_t zxdh_enqueue_recv_refill_packed(struct zxdh_virtqueue *vq,
 		idx = vq->vq_avail_idx;
 		dxp = &vq->vq_descx[idx];
 		dxp->cookie = (void *)cookie[i];
-		dxp->ndescs = 1;
 		/* rx pkt fill in data_off */
 		start_dp[idx].addr = rte_mbuf_iova_get(cookie[i]) + RTE_PKTMBUF_HEADROOM;
 		start_dp[idx].len = cookie[i]->buf_len - RTE_PKTMBUF_HEADROOM;
@@ -423,8 +422,8 @@ int32_t zxdh_enqueue_recv_refill_packed(struct zxdh_virtqueue *vq,
 		zxdh_queue_store_flags_packed(&start_dp[idx], flags);
 		if (++vq->vq_avail_idx >= vq->vq_nentries) {
 			vq->vq_avail_idx -= vq->vq_nentries;
-			vq->vq_packed.cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
-			flags = vq->vq_packed.cached_flags;
+			vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
+			flags = vq->cached_flags;
 		}
 	}
 	vq->vq_free_cnt = (uint16_t)(vq->vq_free_cnt - num);
@@ -467,7 +466,7 @@ void zxdh_queue_rxvq_flush(struct zxdh_virtqueue *vq)
 	int32_t cnt = 0;
 
 	i = vq->vq_used_cons_idx;
-	while (zxdh_desc_used(&descs[i], vq) && cnt++ < vq->vq_nentries) {
+	while (desc_is_used(&descs[i], vq) && cnt++ < vq->vq_nentries) {
 		dxp = &vq->vq_descx[descs[i].id];
 		if (dxp->cookie != NULL) {
 			rte_pktmbuf_free(dxp->cookie);
@@ -477,7 +476,7 @@ void zxdh_queue_rxvq_flush(struct zxdh_virtqueue *vq)
 		vq->vq_used_cons_idx++;
 		if (vq->vq_used_cons_idx >= vq->vq_nentries) {
 			vq->vq_used_cons_idx -= vq->vq_nentries;
-			vq->vq_packed.used_wrap_counter ^= 1;
+			vq->used_wrap_counter ^= 1;
 		}
 		i = vq->vq_used_cons_idx;
 	}
diff --git a/drivers/net/zxdh/zxdh_queue.h b/drivers/net/zxdh/zxdh_queue.h
index 711ea291d0..b079272162 100644
--- a/drivers/net/zxdh/zxdh_queue.h
+++ b/drivers/net/zxdh/zxdh_queue.h
@@ -9,6 +9,7 @@
 
 #include <rte_common.h>
 #include <rte_atomic.h>
+#include <rte_io.h>
 
 #include "zxdh_ethdev.h"
 #include "zxdh_rxtx.h"
@@ -117,7 +118,6 @@ struct zxdh_vring_packed_desc_event {
 };
 
 struct zxdh_vring_packed {
-	uint32_t num;
 	struct zxdh_vring_packed_desc *desc;
 	struct zxdh_vring_packed_desc_event *driver;
 	struct zxdh_vring_packed_desc_event *device;
@@ -129,50 +129,59 @@ struct zxdh_vq_desc_extra {
 	uint16_t next;
 };
 
+struct zxdh_vring {
+	uint32_t num;
+	struct zxdh_vring_desc  *desc;
+	struct zxdh_vring_avail *avail;
+	struct zxdh_vring_used  *used;
+};
+
 struct zxdh_virtqueue {
+	union {
+		struct {
+			struct zxdh_vring ring; /**< vring keeping desc, used and avail */
+		} vq_split;
+		struct __rte_packed_begin {
+			struct zxdh_vring_packed ring;
+		} __rte_packed_end vq_packed;
+	};
 	struct zxdh_hw  *hw; /* < zxdh_hw structure pointer. */
 
-	struct {
-		/* vring keeping descs and events */
-		struct zxdh_vring_packed ring;
-		uint8_t used_wrap_counter;
-		uint8_t rsv;
-		uint16_t cached_flags; /* < cached flags for descs */
-		uint16_t event_flags_shadow;
-		uint16_t rsv1;
-	} vq_packed;
-
-	uint16_t vq_used_cons_idx; /* < last consumed descriptor */
-	uint16_t vq_nentries;  /* < vring desc numbers */
-	uint16_t vq_free_cnt;  /* < num of desc available */
-	uint16_t vq_avail_idx; /* < sync until needed */
-	uint16_t vq_free_thresh; /* < free threshold */
-	uint16_t rsv2;
-
-	void *vq_ring_virt_mem;  /* < linear address of vring */
-	uint32_t vq_ring_size;
+	uint16_t vq_used_cons_idx; /**< last consumed descriptor */
+	uint16_t vq_avail_idx; /**< sync until needed */
+	uint16_t vq_nentries;  /**< vring desc numbers */
+	uint16_t vq_free_cnt;  /**< num of desc available */
+
+	uint16_t cached_flags; /**< cached flags for descs */
+	uint8_t used_wrap_counter;
+	uint8_t rsv;
+	uint16_t vq_free_thresh; /**< free threshold */
+	uint16_t next_qidx;
+
+	void *notify_addr;
 
 	union {
 		struct zxdh_virtnet_rx rxq;
 		struct zxdh_virtnet_tx txq;
 	};
 
-	/*
-	 * physical address of vring, or virtual address
-	 */
-	rte_iova_t vq_ring_mem;
+	uint16_t vq_queue_index; /* PACKED: phy_idx, SPLIT: logic_idx */
+	uint16_t event_flags_shadow;
+	uint32_t vq_ring_size;
 
-	/*
+	/**
 	 * Head of the free chain in the descriptor table. If
 	 * there are no free descriptors, this will be set to
 	 * VQ_RING_DESC_CHAIN_END.
-	 */
+	 **/
 	uint16_t  vq_desc_head_idx;
 	uint16_t  vq_desc_tail_idx;
-	uint16_t  vq_queue_index;   /* < PCI queue index */
-	uint16_t  offset; /* < relative offset to obtain addr in mbuf */
-	uint16_t *notify_addr;
-	struct rte_mbuf **sw_ring;  /* < RX software ring. */
+	uint32_t rsv_8B;
+
+	void *vq_ring_virt_mem;  /**< linear address of vring*/
+	/* physical address of vring, or virtual address for virtio_user. */
+	rte_iova_t vq_ring_mem;
+
 	struct zxdh_vq_desc_extra vq_descx[];
 };
 
@@ -296,10 +305,9 @@ static inline void
 zxdh_vring_init_packed(struct zxdh_vring_packed *vr, uint8_t *p,
 		unsigned long align, uint32_t num)
 {
-	vr->num    = num;
 	vr->desc   = (struct zxdh_vring_packed_desc *)p;
 	vr->driver = (struct zxdh_vring_packed_desc_event *)(p +
-				 vr->num * sizeof(struct zxdh_vring_packed_desc));
+				 num * sizeof(struct zxdh_vring_packed_desc));
 	vr->device = (struct zxdh_vring_packed_desc_event *)RTE_ALIGN_CEIL(((uintptr_t)vr->driver +
 				 sizeof(struct zxdh_vring_packed_desc_event)), align);
 }
@@ -331,30 +339,21 @@ zxdh_vring_desc_init_indirect_packed(struct zxdh_vring_packed_desc *dp, int32_t
 static inline void
 zxdh_queue_disable_intr(struct zxdh_virtqueue *vq)
 {
-	if (vq->vq_packed.event_flags_shadow != ZXDH_RING_EVENT_FLAGS_DISABLE) {
-		vq->vq_packed.event_flags_shadow = ZXDH_RING_EVENT_FLAGS_DISABLE;
-		vq->vq_packed.ring.driver->desc_event_flags = vq->vq_packed.event_flags_shadow;
+	if (vq->event_flags_shadow != ZXDH_RING_EVENT_FLAGS_DISABLE) {
+		vq->event_flags_shadow = ZXDH_RING_EVENT_FLAGS_DISABLE;
+		vq->vq_packed.ring.driver->desc_event_flags = vq->event_flags_shadow;
 	}
 }
 
 static inline void
 zxdh_queue_enable_intr(struct zxdh_virtqueue *vq)
 {
-	if (vq->vq_packed.event_flags_shadow != ZXDH_RING_EVENT_FLAGS_ENABLE) {
-		vq->vq_packed.event_flags_shadow = ZXDH_RING_EVENT_FLAGS_ENABLE;
-		vq->vq_packed.ring.driver->desc_event_flags = vq->vq_packed.event_flags_shadow;
+	if (vq->event_flags_shadow != ZXDH_RING_EVENT_FLAGS_ENABLE) {
+		vq->event_flags_shadow = ZXDH_RING_EVENT_FLAGS_ENABLE;
+		vq->vq_packed.ring.driver->desc_event_flags = vq->event_flags_shadow;
 	}
 }
 
-static inline void
-zxdh_mb(uint8_t weak_barriers)
-{
-	if (weak_barriers)
-		rte_atomic_thread_fence(rte_memory_order_seq_cst);
-	else
-		rte_mb();
-}
-
 static inline
 int32_t desc_is_used(struct zxdh_vring_packed_desc *desc, struct zxdh_virtqueue *vq)
 {
@@ -365,7 +364,7 @@ int32_t desc_is_used(struct zxdh_vring_packed_desc *desc, struct zxdh_virtqueue
 	rte_io_rmb();
 	used = !!(flags & ZXDH_VRING_PACKED_DESC_F_USED);
 	avail = !!(flags & ZXDH_VRING_PACKED_DESC_F_AVAIL);
-	return avail == used && used == vq->vq_packed.used_wrap_counter;
+	return avail == used && used == vq->used_wrap_counter;
 }
 
 static inline int32_t
@@ -381,22 +380,17 @@ zxdh_queue_store_flags_packed(struct zxdh_vring_packed_desc *dp, uint16_t flags)
 	dp->flags = flags;
 }
 
-static inline int32_t
-zxdh_desc_used(struct zxdh_vring_packed_desc *desc, struct zxdh_virtqueue *vq)
-{
-	uint16_t flags;
-	uint16_t used, avail;
-
-	flags = desc->flags;
-	rte_io_rmb();
-	used = !!(flags & ZXDH_VRING_PACKED_DESC_F_USED);
-	avail = !!(flags & ZXDH_VRING_PACKED_DESC_F_AVAIL);
-	return avail == used && used == vq->vq_packed.used_wrap_counter;
-}
-
 static inline void zxdh_queue_notify(struct zxdh_virtqueue *vq)
 {
-	ZXDH_VTPCI_OPS(vq->hw)->notify_queue(vq->hw, vq);
+	/* Bit[0:15]: vq queue index
+	 * Bit[16:30]: avail index
+	 * Bit[31]: avail wrap counter
+	 */
+	uint32_t notify_data = ((uint32_t)(!!(vq->cached_flags &
+		ZXDH_VRING_PACKED_DESC_F_AVAIL)) << 31) |
+		((uint32_t)vq->vq_avail_idx << 16) |
+		vq->vq_queue_index;
+	rte_write32(notify_data, vq->notify_addr);
 }
 
 static inline int32_t
@@ -404,7 +398,7 @@ zxdh_queue_kick_prepare_packed(struct zxdh_virtqueue *vq)
 {
 	uint16_t flags = 0;
 
-	zxdh_mb(1);
+	rte_mb();
 	flags = vq->vq_packed.ring.device->desc_event_flags;
 
 	return (flags != ZXDH_RING_EVENT_FLAGS_DISABLE);
diff --git a/drivers/net/zxdh/zxdh_rxtx.c b/drivers/net/zxdh/zxdh_rxtx.c
index db86922aea..93506a4b49 100644
--- a/drivers/net/zxdh/zxdh_rxtx.c
+++ b/drivers/net/zxdh/zxdh_rxtx.c
@@ -216,7 +216,7 @@ zxdh_xmit_cleanup_inorder_packed(struct zxdh_virtqueue *vq, int32_t num)
 	/* desc_is_used has a load-acquire or rte_io_rmb inside
 	 * and wait for used desc in virtqueue.
 	 */
-	while (num > 0 && zxdh_desc_used(&desc[used_idx], vq)) {
+	while (num > 0 && desc_is_used(&desc[used_idx], vq)) {
 		id = desc[used_idx].id;
 		do {
 			curr_id = used_idx;
@@ -226,7 +226,7 @@ zxdh_xmit_cleanup_inorder_packed(struct zxdh_virtqueue *vq, int32_t num)
 			num -= dxp->ndescs;
 			if (used_idx >= size) {
 				used_idx -= size;
-				vq->vq_packed.used_wrap_counter ^= 1;
+				vq->used_wrap_counter ^= 1;
 			}
 			if (dxp->cookie != NULL) {
 				rte_pktmbuf_free(dxp->cookie);
@@ -340,7 +340,7 @@ zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,
 	struct zxdh_virtqueue *vq = txvq->vq;
 	uint16_t id = vq->vq_avail_idx;
 	struct zxdh_vq_desc_extra *dxp = &vq->vq_descx[id];
-	uint16_t flags = vq->vq_packed.cached_flags;
+	uint16_t flags = vq->cached_flags;
 	struct zxdh_net_hdr_dl *hdr = NULL;
 	uint8_t hdr_len = vq->hw->dl_net_hdr_len;
 	struct zxdh_vring_packed_desc *dp = &vq->vq_packed.ring.desc[id];
@@ -355,7 +355,7 @@ zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,
 	dp->id   = id;
 	if (++vq->vq_avail_idx >= vq->vq_nentries) {
 		vq->vq_avail_idx -= vq->vq_nentries;
-		vq->vq_packed.cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
+		vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
 	}
 	vq->vq_free_cnt--;
 	zxdh_queue_store_flags_packed(dp, flags);
@@ -381,7 +381,7 @@ zxdh_enqueue_xmit_packed(struct zxdh_virtnet_tx *txvq,
 
 	dxp->ndescs = needed;
 	dxp->cookie = cookie;
-	head_flags |= vq->vq_packed.cached_flags;
+	head_flags |= vq->cached_flags;
 
 	start_dp[idx].addr = txvq->zxdh_net_hdr_mem + RTE_PTR_DIFF(&txr[idx].tx_hdr, txr);
 	start_dp[idx].len  = hdr_len;
@@ -392,7 +392,7 @@ zxdh_enqueue_xmit_packed(struct zxdh_virtnet_tx *txvq,
 	idx++;
 	if (idx >= vq->vq_nentries) {
 		idx -= vq->vq_nentries;
-		vq->vq_packed.cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
+		vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
 	}
 
 	zxdh_xmit_fill_net_hdr(vq, cookie, hdr);
@@ -404,14 +404,14 @@ zxdh_enqueue_xmit_packed(struct zxdh_virtnet_tx *txvq,
 		if (likely(idx != head_idx)) {
 			uint16_t flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;
 
-			flags |= vq->vq_packed.cached_flags;
+			flags |= vq->cached_flags;
 			start_dp[idx].flags = flags;
 		}
 
 		idx++;
 		if (idx >= vq->vq_nentries) {
 			idx -= vq->vq_nentries;
-			vq->vq_packed.cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
+			vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
 		}
 	} while ((cookie = cookie->next) != NULL);
 
@@ -480,7 +480,7 @@ zxdh_xmit_flush(struct zxdh_virtqueue *vq)
 			free_cnt += dxp->ndescs;
 			if (used_idx >= size) {
 				used_idx -= size;
-				vq->vq_packed.used_wrap_counter ^= 1;
+				vq->used_wrap_counter ^= 1;
 			}
 			if (dxp->cookie != NULL) {
 				rte_pktmbuf_free(dxp->cookie);
@@ -619,7 +619,7 @@ zxdh_dequeue_burst_rx_packed(struct zxdh_virtqueue *vq,
 		 * desc_is_used has a load-acquire or rte_io_rmb inside
 		 * and wait for used desc in virtqueue.
 		 */
-		if (!zxdh_desc_used(&desc[used_idx], vq))
+		if (!desc_is_used(&desc[used_idx], vq))
 			return i;
 		len[i] = desc[used_idx].len;
 		id = desc[used_idx].id;
@@ -637,7 +637,7 @@ zxdh_dequeue_burst_rx_packed(struct zxdh_virtqueue *vq,
 		vq->vq_used_cons_idx++;
 		if (vq->vq_used_cons_idx >= vq->vq_nentries) {
 			vq->vq_used_cons_idx -= vq->vq_nentries;
-			vq->vq_packed.used_wrap_counter ^= 1;
+			vq->used_wrap_counter ^= 1;
 		}
 	}
 	return i;
-- 
2.27.0

[-- Attachment #1.1.2: Type: text/html , Size: 38856 bytes --]

^ permalink raw reply related

* [PATCH v5 3/4] net/zxdh: optimize Rx recv pkts performance
From: Junlong Wang @ 2026-06-15  1:19 UTC (permalink / raw)
  To: stephen; +Cc: dev, Junlong Wang
In-Reply-To: <20260615011931.940545-1-wang.junlong1@zte.com.cn>


[-- Attachment #1.1.1: Type: text/plain, Size: 16239 bytes --]

1. Add simple RX recv functions (zxdh_recv_single_pkts)
   for single-segment packet recv.
2. And optimize Rx recv pkts packed ops.
3. Remove unnecessary ZXDH_NET_F_MRG_RXBUF negotiation check and
   some unnecessary statistical counters form the xstats name tables.

Signed-off-by: Junlong Wang <wang.junlong1@zte.com.cn>
---
 drivers/net/zxdh/zxdh_ethdev.c     |  39 +++++--
 drivers/net/zxdh/zxdh_ethdev_ops.c |  23 ++--
 drivers/net/zxdh/zxdh_ethdev_ops.h |   4 +
 drivers/net/zxdh/zxdh_rxtx.c       | 174 +++++++++++++++++++++++------
 drivers/net/zxdh/zxdh_rxtx.h       |  16 +--
 5 files changed, 193 insertions(+), 63 deletions(-)

diff --git a/drivers/net/zxdh/zxdh_ethdev.c b/drivers/net/zxdh/zxdh_ethdev.c
index a383619419..fe76139f3d 100644
--- a/drivers/net/zxdh/zxdh_ethdev.c
+++ b/drivers/net/zxdh/zxdh_ethdev.c
@@ -1263,18 +1263,43 @@ zxdh_dev_close(struct rte_eth_dev *dev)
 	return ret;
 }
 
-static int32_t
-zxdh_set_rxtx_funcs(struct rte_eth_dev *eth_dev)
+/*
+ * Determine whether the current configuration requires support for scattered
+ * receive; return 1 if scattered receive is required and 0 if not.
+ */
+static int zxdh_scattered_rx(struct rte_eth_dev *eth_dev)
 {
-	struct zxdh_hw *hw = eth_dev->data->dev_private;
+	uint16_t buf_size;
 
-	if (!zxdh_pci_with_feature(hw, ZXDH_NET_F_MRG_RXBUF)) {
-		PMD_DRV_LOG(ERR, "port %u not support rx mergeable", eth_dev->data->port_id);
-		return -1;
+	if (eth_dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_TCP_LRO) {
+		eth_dev->data->lro = 1;
+		return 1;
 	}
+
+	if (eth_dev->data->dev_conf.rxmode.offloads & RTE_ETH_RX_OFFLOAD_SCATTER)
+		return 1;
+
+	PMD_DRV_LOG(DEBUG, "port %u min_rx_buf_size %u",
+		eth_dev->data->port_id, eth_dev->data->min_rx_buf_size);
+	buf_size = eth_dev->data->min_rx_buf_size - RTE_PKTMBUF_HEADROOM;
+	if (eth_dev->data->mtu + ZXDH_ETH_OVERHEAD > buf_size)
+		return 1;
+
+	return 0;
+}
+
+static int32_t
+zxdh_set_rxtx_funcs(struct rte_eth_dev *eth_dev)
+{
 	eth_dev->tx_pkt_prepare = zxdh_xmit_pkts_prepare;
+	eth_dev->data->scattered_rx = zxdh_scattered_rx(eth_dev);
+
 	eth_dev->tx_pkt_burst = &zxdh_xmit_pkts_packed;
-	eth_dev->rx_pkt_burst = &zxdh_recv_pkts_packed;
+
+	if (eth_dev->data->scattered_rx)
+		eth_dev->rx_pkt_burst = &zxdh_recv_pkts_packed;
+	else
+		eth_dev->rx_pkt_burst = &zxdh_recv_single_pkts;
 
 	return 0;
 }
diff --git a/drivers/net/zxdh/zxdh_ethdev_ops.c b/drivers/net/zxdh/zxdh_ethdev_ops.c
index 50247116d9..9a8e05e941 100644
--- a/drivers/net/zxdh/zxdh_ethdev_ops.c
+++ b/drivers/net/zxdh/zxdh_ethdev_ops.c
@@ -95,10 +95,6 @@ static const struct rte_zxdh_xstats_name_off zxdh_rxq_stat_strings[] = {
 	{"good_bytes",             offsetof(struct zxdh_virtnet_rx, stats.bytes)},
 	{"errors",                 offsetof(struct zxdh_virtnet_rx, stats.errors)},
 	{"idle",                   offsetof(struct zxdh_virtnet_rx, stats.idle)},
-	{"full",                   offsetof(struct zxdh_virtnet_rx, stats.full)},
-	{"norefill",               offsetof(struct zxdh_virtnet_rx, stats.norefill)},
-	{"multicast_packets",      offsetof(struct zxdh_virtnet_rx, stats.multicast)},
-	{"broadcast_packets",      offsetof(struct zxdh_virtnet_rx, stats.broadcast)},
 	{"truncated_err",          offsetof(struct zxdh_virtnet_rx, stats.truncated_err)},
 	{"offload_cfg_err",        offsetof(struct zxdh_virtnet_rx, stats.offload_cfg_err)},
 	{"invalid_hdr_len_err",    offsetof(struct zxdh_virtnet_rx, stats.invalid_hdr_len_err)},
@@ -117,14 +113,12 @@ static const struct rte_zxdh_xstats_name_off zxdh_txq_stat_strings[] = {
 	{"good_packets",           offsetof(struct zxdh_virtnet_tx, stats.packets)},
 	{"good_bytes",             offsetof(struct zxdh_virtnet_tx, stats.bytes)},
 	{"errors",                 offsetof(struct zxdh_virtnet_tx, stats.errors)},
-	{"idle",                   offsetof(struct zxdh_virtnet_tx, stats.idle)},
-	{"norefill",               offsetof(struct zxdh_virtnet_tx, stats.norefill)},
-	{"multicast_packets",      offsetof(struct zxdh_virtnet_tx, stats.multicast)},
-	{"broadcast_packets",      offsetof(struct zxdh_virtnet_tx, stats.broadcast)},
+	{"idle",                 offsetof(struct zxdh_virtnet_tx, stats.idle)},
 	{"truncated_err",          offsetof(struct zxdh_virtnet_tx, stats.truncated_err)},
 	{"offload_cfg_err",        offsetof(struct zxdh_virtnet_tx, stats.offload_cfg_err)},
 	{"invalid_hdr_len_err",    offsetof(struct zxdh_virtnet_tx, stats.invalid_hdr_len_err)},
 	{"no_segs_err",            offsetof(struct zxdh_virtnet_tx, stats.no_segs_err)},
+	{"no_free_tx_desc_err",    offsetof(struct zxdh_virtnet_tx, stats.no_free_tx_desc_err)},
 	{"undersize_packets",      offsetof(struct zxdh_virtnet_tx, stats.size_bins[0])},
 	{"size_64_packets",        offsetof(struct zxdh_virtnet_tx, stats.size_bins[1])},
 	{"size_65_127_packets",    offsetof(struct zxdh_virtnet_tx, stats.size_bins[2])},
@@ -2026,6 +2020,19 @@ int zxdh_dev_mtu_set(struct rte_eth_dev *dev, uint16_t new_mtu)
 	uint16_t vfid = zxdh_vport_to_vfid(hw->vport);
 	int ret;
 
+	/* If device is started, refuse mtu that requires the support of
+	 * scattered packets when this feature has not been enabled before.
+	 */
+	if (dev->data->dev_started) {
+		uint32_t buf_size = dev->data->min_rx_buf_size - RTE_PKTMBUF_HEADROOM;
+		uint8_t need_scatter = (uint32_t)ZXDH_MTU_TO_PKTLEN(new_mtu) > buf_size;
+
+		if (need_scatter != dev->data->scattered_rx) {
+			PMD_DRV_LOG(ERR, "Stop port first.");
+			return -EINVAL;
+		}
+	}
+
 	if (hw->is_pf) {
 		ret = zxdh_get_panel_attr(dev, &panel);
 		if (ret != 0) {
diff --git a/drivers/net/zxdh/zxdh_ethdev_ops.h b/drivers/net/zxdh/zxdh_ethdev_ops.h
index 6dfe4be473..c49d79c232 100644
--- a/drivers/net/zxdh/zxdh_ethdev_ops.h
+++ b/drivers/net/zxdh/zxdh_ethdev_ops.h
@@ -40,6 +40,10 @@
 #define ZXDH_SPM_SPEED_4X_100G         RTE_BIT32(10)
 #define ZXDH_SPM_SPEED_4X_200G         RTE_BIT32(11)
 
+#define ZXDH_VLAN_TAG_LEN   4
+#define ZXDH_ETH_OVERHEAD  (RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN + ZXDH_VLAN_TAG_LEN * 2)
+#define ZXDH_MTU_TO_PKTLEN(mtu) ((mtu) + ZXDH_ETH_OVERHEAD)
+
 struct zxdh_np_stats_data {
 	uint64_t n_pkts_dropped;
 	uint64_t n_bytes_dropped;
diff --git a/drivers/net/zxdh/zxdh_rxtx.c b/drivers/net/zxdh/zxdh_rxtx.c
index 93506a4b49..ab0510a753 100644
--- a/drivers/net/zxdh/zxdh_rxtx.c
+++ b/drivers/net/zxdh/zxdh_rxtx.c
@@ -613,10 +613,12 @@ zxdh_dequeue_burst_rx_packed(struct zxdh_virtqueue *vq,
 	uint16_t i, used_idx;
 	uint16_t id;
 
+	used_idx = vq->vq_used_cons_idx;
+	rte_prefetch0(&desc[used_idx]);
+
 	for (i = 0; i < num; i++) {
 		used_idx = vq->vq_used_cons_idx;
-		/**
-		 * desc_is_used has a load-acquire or rte_io_rmb inside
+		/* desc_is_used has a load-acquire or rte_io_rmb inside
 		 * and wait for used desc in virtqueue.
 		 */
 		if (!desc_is_used(&desc[used_idx], vq))
@@ -823,17 +825,52 @@ zxdh_rx_update_mbuf(struct zxdh_hw *hw, struct rte_mbuf *m, struct zxdh_net_hdr_
 	}
 }
 
-static void zxdh_discard_rxbuf(struct zxdh_virtqueue *vq, struct rte_mbuf *m)
+static void refill_desc_unwrap(struct zxdh_virtqueue *vq,
+		struct rte_mbuf **cookie, uint16_t nb_pkts)
 {
-	int32_t error = 0;
-	/*
-	 * Requeue the discarded mbuf. This should always be
-	 * successful since it was just dequeued.
-	 */
-	error = zxdh_enqueue_recv_refill_packed(vq, &m, 1);
-	if (unlikely(error)) {
-		PMD_RX_LOG(ERR, "cannot enqueue discarded mbuf");
-		rte_pktmbuf_free(m);
+	struct zxdh_vring_packed_desc *start_dp = vq->vq_packed.ring.desc;
+	struct zxdh_vq_desc_extra *dxp;
+	uint16_t flags = vq->cached_flags;
+	int32_t i;
+	uint16_t idx;
+
+	idx = vq->vq_avail_idx;
+	for (i = 0; i < nb_pkts; i++) {
+		dxp = &vq->vq_descx[idx];
+		dxp->cookie = (void *)cookie[i];
+		start_dp[idx].addr = rte_mbuf_iova_get(cookie[i]) + RTE_PKTMBUF_HEADROOM;
+		start_dp[idx].len = cookie[i]->buf_len - RTE_PKTMBUF_HEADROOM;
+		zxdh_queue_store_flags_packed(&start_dp[idx], flags);
+		idx++;
+	}
+	vq->vq_avail_idx += nb_pkts;
+	vq->vq_free_cnt = vq->vq_free_cnt - nb_pkts;
+}
+
+static void refill_que_descs(struct zxdh_virtqueue *vq, struct rte_eth_dev *dev)
+{
+	/* free_cnt may include mrg descs */
+	struct rte_mbuf *new_pkts[ZXDH_MBUF_BURST_SZ];
+	uint16_t free_cnt = RTE_MIN(ZXDH_MBUF_BURST_SZ, vq->vq_free_cnt);
+	struct zxdh_virtnet_rx *rxvq = &vq->rxq;
+	uint16_t  unwrap_cnt, left_cnt;
+
+	if (!rte_pktmbuf_alloc_bulk(rxvq->mpool, new_pkts, free_cnt)) {
+		left_cnt = free_cnt;
+		unwrap_cnt = 0;
+		if ((vq->vq_avail_idx + free_cnt) >= vq->vq_nentries) {
+			unwrap_cnt = vq->vq_nentries - vq->vq_avail_idx;
+			left_cnt = free_cnt - unwrap_cnt;
+			refill_desc_unwrap(vq, new_pkts, unwrap_cnt);
+			vq->vq_avail_idx = 0;
+			vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
+		}
+		if (left_cnt)
+			refill_desc_unwrap(vq, new_pkts + unwrap_cnt, left_cnt);
+
+		rte_io_wmb();
+	} else {
+		dev->data->rx_mbuf_alloc_failed += free_cnt;
 	}
 }
 
@@ -852,7 +889,6 @@ zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts,
 	uint16_t len = 0;
 	uint32_t seg_num = 0;
 	uint32_t seg_res = 0;
-	uint32_t error = 0;
 	uint16_t hdr_size = 0;
 	uint16_t nb_rx = 0;
 	uint16_t i;
@@ -873,7 +909,8 @@ zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts,
 		rx_pkts[nb_rx] = rxm;
 		prev = rxm;
 		len = lens[i];
-		header = rte_pktmbuf_mtod(rxm, struct zxdh_net_hdr_ul *);
+		header = (struct zxdh_net_hdr_ul *)((char *)
+					rxm->buf_addr + RTE_PKTMBUF_HEADROOM);
 
 		seg_num  = header->type_hdr.num_buffers;
 
@@ -886,7 +923,7 @@ zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts,
 			rxvq->stats.invalid_hdr_len_err++;
 			continue;
 		}
-		rxm->data_off += hdr_size;
+		rxm->data_off = RTE_PKTMBUF_HEADROOM + hdr_size;
 		rxm->nb_segs = seg_num;
 		rxm->ol_flags = 0;
 		rcvd_pkt_len = len - hdr_size;
@@ -902,18 +939,19 @@ zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts,
 			len = lens[i];
 			rxm = rcv_pkts[i];
 			rxm->data_len = len;
+			rxm->data_off = RTE_PKTMBUF_HEADROOM;
 			rcvd_pkt_len += len;
 			prev->next = rxm;
 			prev = rxm;
 			rxm->next = NULL;
-			seg_res -= 1;
+			seg_res--;
 		}
 
 		if (!seg_res) {
 			if (rcvd_pkt_len != rx_pkts[nb_rx]->pkt_len) {
 				PMD_RX_LOG(ERR, "dropped rcvd_pkt_len %d pktlen %d",
 					rcvd_pkt_len, rx_pkts[nb_rx]->pkt_len);
-				zxdh_discard_rxbuf(vq, rx_pkts[nb_rx]);
+				rte_pktmbuf_free(rx_pkts[nb_rx]);
 				rxvq->stats.errors++;
 				rxvq->stats.truncated_err++;
 				continue;
@@ -942,14 +980,14 @@ zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts,
 			prev->next = rxm;
 			prev = rxm;
 			rxm->next = NULL;
-			extra_idx += 1;
+			extra_idx++;
 		}
 		seg_res -= rcv_cnt;
 		if (!seg_res) {
 			if (unlikely(rcvd_pkt_len != rx_pkts[nb_rx]->pkt_len)) {
 				PMD_RX_LOG(ERR, "dropped rcvd_pkt_len %d pktlen %d",
 					rcvd_pkt_len, rx_pkts[nb_rx]->pkt_len);
-				zxdh_discard_rxbuf(vq, rx_pkts[nb_rx]);
+				rte_pktmbuf_free(rx_pkts[nb_rx]);
 				rxvq->stats.errors++;
 				rxvq->stats.truncated_err++;
 				continue;
@@ -961,26 +999,88 @@ zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts,
 	rxvq->stats.packets += nb_rx;
 
 refill:
-	/* Allocate new mbuf for the used descriptor */
-	if (likely(!zxdh_queue_full(vq))) {
-		struct rte_mbuf *new_pkts[ZXDH_MBUF_BURST_SZ];
-		/* free_cnt may include mrg descs */
-		uint16_t free_cnt = RTE_MIN(vq->vq_free_cnt, ZXDH_MBUF_BURST_SZ);
-
-		if (!rte_pktmbuf_alloc_bulk(rxvq->mpool, new_pkts, free_cnt)) {
-			error = zxdh_enqueue_recv_refill_packed(vq, new_pkts, free_cnt);
-			if (unlikely(error)) {
-				for (i = 0; i < free_cnt; i++)
-					rte_pktmbuf_free(new_pkts[i]);
-			}
+	if (vq->vq_free_cnt > 0) {
+		struct rte_eth_dev *dev = hw->eth_dev;
+		refill_que_descs(vq, dev);
+		zxdh_queue_notify(vq);
+	}
 
-			if (unlikely(zxdh_queue_kick_prepare_packed(vq)))
-				zxdh_queue_notify(vq);
-		} else {
-			struct rte_eth_dev *dev = hw->eth_dev;
+	return nb_rx;
+}
 
-			dev->data->rx_mbuf_alloc_failed += free_cnt;
-		}
+static inline int zxdh_init_mbuf(struct rte_mbuf *rxm, uint16_t len,
+		struct zxdh_hw *hw, struct zxdh_virtnet_rx *rxvq)
+{
+	uint16_t hdr_size = 0;
+	struct zxdh_net_hdr_ul *header;
+
+	header = rte_pktmbuf_mtod(rxm, struct zxdh_net_hdr_ul *);
+	rxm->ol_flags = 0;
+	rxm->vlan_tci = 0;
+	rxm->vlan_tci_outer = 0;
+
+	hdr_size = header->type_hdr.pd_len << 1;
+	if (unlikely(header->type_hdr.num_buffers != 1)) {
+		PMD_RX_LOG(DEBUG, "hdr_size:%u nb_segs %d is invalid",
+			hdr_size, header->type_hdr.num_buffers);
+		rte_pktmbuf_free(rxm);
+		rxvq->stats.invalid_hdr_len_err++;
+		return -1;
+	}
+	zxdh_rx_update_mbuf(hw, rxm, header);
+
+	rxm->nb_segs = 1;
+	rxm->data_off = RTE_PKTMBUF_HEADROOM + hdr_size;
+	rxm->data_len = len - hdr_size;
+	rxm->port = hw->port_id;
+
+	if (rxm->data_len != rxm->pkt_len) {
+		PMD_RX_LOG(ERR, "dropped rcvd_pkt_len %d pktlen %d  bufaddr %p.",
+					rxm->data_len, rxm->pkt_len, rxm->buf_addr);
+		rte_pktmbuf_free(rxm);
+		rxvq->stats.truncated_err++;
+		rxvq->stats.errors++;
+		return -1;
+	}
+	return 0;
+}
+
+uint16_t zxdh_recv_single_pkts(void *rx_queue, struct rte_mbuf **rcv_pkts, uint16_t nb_pkts)
+{
+	struct zxdh_virtnet_rx *rxvq = rx_queue;
+	struct zxdh_virtqueue *vq = rxvq->vq;
+	struct zxdh_hw *hw = vq->hw;
+	uint32_t lens[ZXDH_MBUF_BURST_SZ];
+	uint16_t nb_rx = 0;
+	uint16_t num;
+	uint16_t i;
+
+	num = nb_pkts;
+	if (unlikely(num > ZXDH_MBUF_BURST_SZ))
+		num = ZXDH_MBUF_BURST_SZ;
+	num = zxdh_dequeue_burst_rx_packed(vq, rcv_pkts, lens, num);
+	if (num == 0) {
+		rxvq->stats.idle++;
+		goto refill;
+	}
+
+	for (i = 0; i < num; i++) {
+		struct rte_mbuf *rxm = rcv_pkts[i];
+		uint16_t len = lens[i];
+
+		if (unlikely(zxdh_init_mbuf(rxm, len, hw, &vq->rxq) < 0))
+			continue;
+		rcv_pkts[nb_rx] = rxm;
+		zxdh_update_packet_stats(&rxvq->stats, rxm);
+		nb_rx++;
+	}
+	rxvq->stats.packets += nb_rx;
+
+refill:
+	if (vq->vq_free_cnt > 0) {
+		struct rte_eth_dev *dev = hw->eth_dev;
+		refill_que_descs(vq, dev);
+		zxdh_queue_notify(vq);
 	}
 	return nb_rx;
 }
diff --git a/drivers/net/zxdh/zxdh_rxtx.h b/drivers/net/zxdh/zxdh_rxtx.h
index 424048607e..dba9567414 100644
--- a/drivers/net/zxdh/zxdh_rxtx.h
+++ b/drivers/net/zxdh/zxdh_rxtx.h
@@ -36,29 +36,22 @@ struct zxdh_virtnet_stats {
 	uint64_t bytes;
 	uint64_t errors;
 	uint64_t idle;
-	uint64_t full;
-	uint64_t norefill;
-	uint64_t multicast;
-	uint64_t broadcast;
 	uint64_t truncated_err;
 	uint64_t offload_cfg_err;
 	uint64_t invalid_hdr_len_err;
 	uint64_t no_segs_err;
+	uint64_t no_free_tx_desc_err;
 	uint64_t size_bins[8];
 };
 
 struct __rte_cache_aligned zxdh_virtnet_rx {
 	struct zxdh_virtqueue         *vq;
-
-	uint64_t                  mbuf_initializer; /* value to init mbufs. */
 	struct rte_mempool       *mpool;            /* mempool for mbuf allocation */
-	uint16_t                  queue_id;         /* DPDK queue index. */
-	uint16_t                  port_id;          /* Device port identifier. */
 	struct zxdh_virtnet_stats      stats;
 	const struct rte_memzone *mz;               /* mem zone to populate RX ring. */
-
-	/* dummy mbuf, for wraparound when processing RX ring. */
-	struct rte_mbuf           fake_mbuf;
+	uint64_t offloads;
+	uint16_t                  queue_id;         /* DPDK queue index. */
+	uint16_t                  port_id;          /* Device port identifier. */
 };
 
 struct __rte_cache_aligned zxdh_virtnet_tx {
@@ -75,5 +68,6 @@ struct __rte_cache_aligned zxdh_virtnet_tx {
 uint16_t zxdh_xmit_pkts_packed(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 uint16_t zxdh_xmit_pkts_prepare(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 uint16_t zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
+uint16_t zxdh_recv_single_pkts(void *rx_queue, struct rte_mbuf **rcv_pkts, uint16_t nb_pkts);
 
 #endif  /* ZXDH_RXTX_H */
-- 
2.27.0

[-- Attachment #1.1.2: Type: text/html , Size: 39105 bytes --]

^ permalink raw reply related

* [PATCH v5 1/4] net/zxdh: fix queue enable intr issues
From: Junlong Wang @ 2026-06-15  1:19 UTC (permalink / raw)
  To: stephen; +Cc: dev, Junlong Wang, stable
In-Reply-To: <20260615011931.940545-1-wang.junlong1@zte.com.cn>


[-- Attachment #1.1.1: Type: text/plain, Size: 1196 bytes --]

Fix incorrect condition check in zxdh_queue_enable_intr.
Change "==" to "!=", consistent with zxdh_queue_disable_intr logic,
to properly enable interrupts when event_flags_shadow is not
already set to ENABLE state.

Fixes: 7677f3871ef3 ("net/zxdh: setup Rx/Tx queues and interrupt")
Cc: stable@dpdk.org

Signed-off-by: Junlong Wang <wang.junlong1@zte.com.cn>
---
 drivers/net/zxdh/zxdh_queue.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/zxdh/zxdh_queue.h b/drivers/net/zxdh/zxdh_queue.h
index 1a0c8a0d90..711ea291d0 100644
--- a/drivers/net/zxdh/zxdh_queue.h
+++ b/drivers/net/zxdh/zxdh_queue.h
@@ -340,8 +340,8 @@ zxdh_queue_disable_intr(struct zxdh_virtqueue *vq)
 static inline void
 zxdh_queue_enable_intr(struct zxdh_virtqueue *vq)
 {
-	if (vq->vq_packed.event_flags_shadow == ZXDH_RING_EVENT_FLAGS_DISABLE) {
-		vq->vq_packed.event_flags_shadow = ZXDH_RING_EVENT_FLAGS_DISABLE;
+	if (vq->vq_packed.event_flags_shadow != ZXDH_RING_EVENT_FLAGS_ENABLE) {
+		vq->vq_packed.event_flags_shadow = ZXDH_RING_EVENT_FLAGS_ENABLE;
 		vq->vq_packed.ring.driver->desc_event_flags = vq->vq_packed.event_flags_shadow;
 	}
 }
-- 
2.27.0

[-- Attachment #1.1.2: Type: text/html , Size: 2002 bytes --]

^ permalink raw reply related

* [PATCH v5 4/4] net/zxdh: optimize Tx xmit pkts performance
From: Junlong Wang @ 2026-06-15  1:19 UTC (permalink / raw)
  To: stephen; +Cc: dev, Junlong Wang
In-Reply-To: <20260615011931.940545-1-wang.junlong1@zte.com.cn>


[-- Attachment #1.1.1: Type: text/plain, Size: 19008 bytes --]

Add simple Tx xmit functions (zxdh_xmit_pkts_simple)
for single-segment packet xmit.

Signed-off-by: Junlong Wang <wang.junlong1@zte.com.cn>
---
 drivers/net/zxdh/zxdh_ethdev.c |  11 +-
 drivers/net/zxdh/zxdh_queue.h  |   2 +-
 drivers/net/zxdh/zxdh_rxtx.c   | 330 ++++++++++++++++++++++++---------
 drivers/net/zxdh/zxdh_rxtx.h   |  11 +-
 4 files changed, 260 insertions(+), 94 deletions(-)

diff --git a/drivers/net/zxdh/zxdh_ethdev.c b/drivers/net/zxdh/zxdh_ethdev.c
index fe76139f3d..43f823253d 100644
--- a/drivers/net/zxdh/zxdh_ethdev.c
+++ b/drivers/net/zxdh/zxdh_ethdev.c
@@ -490,7 +490,7 @@ zxdh_dev_free_mbufs(struct rte_eth_dev *dev)
 		if (!vq)
 			continue;
 		while ((buf = zxdh_queue_detach_unused(vq)) != NULL)
-			rte_pktmbuf_free(buf);
+			rte_pktmbuf_free_seg(buf);
 		PMD_DRV_LOG(DEBUG, "freeing %s[%d] used and unused buf",
 		"rxq", i * 2);
 	}
@@ -499,7 +499,7 @@ zxdh_dev_free_mbufs(struct rte_eth_dev *dev)
 		if (!vq)
 			continue;
 		while ((buf = zxdh_queue_detach_unused(vq)) != NULL)
-			rte_pktmbuf_free(buf);
+			rte_pktmbuf_free_seg(buf);
 		PMD_DRV_LOG(DEBUG, "freeing %s[%d] used and unused buf",
 		"txq", i * 2 + 1);
 	}
@@ -1291,10 +1291,15 @@ static int zxdh_scattered_rx(struct rte_eth_dev *eth_dev)
 static int32_t
 zxdh_set_rxtx_funcs(struct rte_eth_dev *eth_dev)
 {
+	uint64_t tx_offloads = eth_dev->data->dev_conf.txmode.offloads;
+
 	eth_dev->tx_pkt_prepare = zxdh_xmit_pkts_prepare;
 	eth_dev->data->scattered_rx = zxdh_scattered_rx(eth_dev);
 
-	eth_dev->tx_pkt_burst = &zxdh_xmit_pkts_packed;
+	if (!(tx_offloads & RTE_ETH_TX_OFFLOAD_MULTI_SEGS))
+		eth_dev->tx_pkt_burst = &zxdh_xmit_pkts_simple;
+	else
+		eth_dev->tx_pkt_burst = &zxdh_xmit_pkts_packed;
 
 	if (eth_dev->data->scattered_rx)
 		eth_dev->rx_pkt_burst = &zxdh_recv_pkts_packed;
diff --git a/drivers/net/zxdh/zxdh_queue.h b/drivers/net/zxdh/zxdh_queue.h
index b079272162..091d1f25db 100644
--- a/drivers/net/zxdh/zxdh_queue.h
+++ b/drivers/net/zxdh/zxdh_queue.h
@@ -374,7 +374,7 @@ zxdh_queue_full(const struct zxdh_virtqueue *vq)
 }
 
 static inline void
-zxdh_queue_store_flags_packed(struct zxdh_vring_packed_desc *dp, uint16_t flags)
+zxdh_queue_store_flags_packed(volatile struct zxdh_vring_packed_desc *dp, uint16_t flags)
 {
 	rte_io_wmb();
 	dp->flags = flags;
diff --git a/drivers/net/zxdh/zxdh_rxtx.c b/drivers/net/zxdh/zxdh_rxtx.c
index ab0510a753..0ccbe0917a 100644
--- a/drivers/net/zxdh/zxdh_rxtx.c
+++ b/drivers/net/zxdh/zxdh_rxtx.c
@@ -114,6 +114,22 @@
 		RTE_MBUF_F_TX_SEC_OFFLOAD |     \
 		RTE_MBUF_F_TX_UDP_SEG)
 
+#if RTE_CACHE_LINE_SIZE == 128
+#define NEXT_CACHELINE_OFF_16B   8
+#define NEXT_CACHELINE_OFF_8B   16
+#elif RTE_CACHE_LINE_SIZE == 64
+#define NEXT_CACHELINE_OFF_16B   4
+#define NEXT_CACHELINE_OFF_8B    8
+#else
+#define NEXT_CACHELINE_OFF_16B  (RTE_CACHE_LINE_SIZE / 16)
+#define NEXT_CACHELINE_OFF_8B   (RTE_CACHE_LINE_SIZE / 8)
+#endif
+#define N_PER_LOOP  NEXT_CACHELINE_OFF_8B
+#define N_PER_LOOP_MASK (N_PER_LOOP - 1)
+
+#define rxq_get_vq(q) ((q)->vq)
+#define txq_get_vq(q) ((q)->vq)
+
 uint32_t zxdh_outer_l2_type[16] = {
 	0,
 	RTE_PTYPE_L2_ETHER,
@@ -201,43 +217,6 @@ uint32_t zxdh_inner_l4_type[16] = {
 	0,
 };
 
-static void
-zxdh_xmit_cleanup_inorder_packed(struct zxdh_virtqueue *vq, int32_t num)
-{
-	uint16_t used_idx = 0;
-	uint16_t id       = 0;
-	uint16_t curr_id  = 0;
-	uint16_t free_cnt = 0;
-	uint16_t size     = vq->vq_nentries;
-	struct zxdh_vring_packed_desc *desc = vq->vq_packed.ring.desc;
-	struct zxdh_vq_desc_extra     *dxp  = NULL;
-
-	used_idx = vq->vq_used_cons_idx;
-	/* desc_is_used has a load-acquire or rte_io_rmb inside
-	 * and wait for used desc in virtqueue.
-	 */
-	while (num > 0 && desc_is_used(&desc[used_idx], vq)) {
-		id = desc[used_idx].id;
-		do {
-			curr_id = used_idx;
-			dxp = &vq->vq_descx[used_idx];
-			used_idx += dxp->ndescs;
-			free_cnt += dxp->ndescs;
-			num -= dxp->ndescs;
-			if (used_idx >= size) {
-				used_idx -= size;
-				vq->used_wrap_counter ^= 1;
-			}
-			if (dxp->cookie != NULL) {
-				rte_pktmbuf_free(dxp->cookie);
-				dxp->cookie = NULL;
-			}
-		} while (curr_id != id);
-	}
-	vq->vq_used_cons_idx = used_idx;
-	vq->vq_free_cnt += free_cnt;
-}
-
 static inline uint16_t
 zxdh_get_mtu(struct zxdh_virtqueue *vq)
 {
@@ -334,7 +313,7 @@ zxdh_xmit_fill_net_hdr(struct zxdh_virtqueue *vq, struct rte_mbuf *cookie,
 }
 
 static inline void
-zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,
+zxdh_xmit_enqueue_push(struct zxdh_virtnet_tx *txvq,
 						struct rte_mbuf *cookie)
 {
 	struct zxdh_virtqueue *vq = txvq->vq;
@@ -345,7 +324,6 @@ zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,
 	uint8_t hdr_len = vq->hw->dl_net_hdr_len;
 	struct zxdh_vring_packed_desc *dp = &vq->vq_packed.ring.desc[id];
 
-	dxp->ndescs = 1;
 	dxp->cookie = cookie;
 	hdr = rte_pktmbuf_mtod_offset(cookie, struct zxdh_net_hdr_dl *, -hdr_len);
 	zxdh_xmit_fill_net_hdr(vq, cookie, hdr);
@@ -362,52 +340,57 @@ zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,
 }
 
 static inline void
-zxdh_enqueue_xmit_packed(struct zxdh_virtnet_tx *txvq,
+zxdh_xmit_enqueue_append(struct zxdh_virtnet_tx *txvq,
 						struct rte_mbuf *cookie,
 						uint16_t needed)
 {
 	struct zxdh_tx_region *txr = txvq->zxdh_net_hdr_mz->addr;
 	struct zxdh_virtqueue *vq = txvq->vq;
-	uint16_t id = vq->vq_avail_idx;
-	struct zxdh_vq_desc_extra *dxp = &vq->vq_descx[id];
+	struct zxdh_vq_desc_extra *dep = &vq->vq_descx[0];
 	uint16_t head_idx = vq->vq_avail_idx;
 	uint16_t idx = head_idx;
 	struct zxdh_vring_packed_desc *start_dp = vq->vq_packed.ring.desc;
 	struct zxdh_vring_packed_desc *head_dp = &vq->vq_packed.ring.desc[idx];
 	struct zxdh_net_hdr_dl *hdr = NULL;
-
-	uint16_t head_flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;
+	uint16_t id = vq->vq_avail_idx;
+	struct zxdh_vq_desc_extra *dxp = &vq->vq_descx[id];
 	uint8_t hdr_len = vq->hw->dl_net_hdr_len;
+	uint16_t head_flags = 0;
 
-	dxp->ndescs = needed;
-	dxp->cookie = cookie;
-	head_flags |= vq->cached_flags;
+	/*
+	 * IMPORTANT: For multi-seg packets, we set the head descriptor's cookie to NULL
+	 * and store each segment's mbuf in its corresponding vq_descx[idx].cookie.
+	 * This is required for the per-descriptor mbuf free in zxdh_xmit_fast_flush()
+	 * which uses rte_pktmbuf_free_seg() to free individual segments.
+	 * Any code path that attempts to read vq_descx[head_id].cookie will see NULL
+	 * and must handle this case appropriately.
+	 */
+	dxp->cookie = NULL;
 
+	/* setup first tx ring slot to point to header stored in reserved region. */
 	start_dp[idx].addr = txvq->zxdh_net_hdr_mem + RTE_PTR_DIFF(&txr[idx].tx_hdr, txr);
 	start_dp[idx].len  = hdr_len;
-	head_flags |= ZXDH_VRING_DESC_F_NEXT;
+	start_dp[idx].id = idx;
+	head_flags |= vq->cached_flags | ZXDH_VRING_DESC_F_NEXT;
 	hdr = (void *)&txr[idx].tx_hdr;
 
-	rte_prefetch1(hdr);
+	zxdh_xmit_fill_net_hdr(vq, cookie, hdr);
+
 	idx++;
 	if (idx >= vq->vq_nentries) {
 		idx -= vq->vq_nentries;
 		vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
 	}
 
-	zxdh_xmit_fill_net_hdr(vq, cookie, hdr);
-
 	do {
 		start_dp[idx].addr = rte_pktmbuf_iova(cookie);
 		start_dp[idx].len  = cookie->data_len;
-		start_dp[idx].id = id;
-		if (likely(idx != head_idx)) {
-			uint16_t flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;
-
-			flags |= vq->cached_flags;
-			start_dp[idx].flags = flags;
-		}
+		start_dp[idx].id = idx;
 
+		dep[idx].cookie = cookie;
+		uint16_t flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;
+		flags |= vq->cached_flags;
+		start_dp[idx].flags = flags;
 		idx++;
 		if (idx >= vq->vq_nentries) {
 			idx -= vq->vq_nentries;
@@ -417,7 +400,6 @@ zxdh_enqueue_xmit_packed(struct zxdh_virtnet_tx *txvq,
 
 	vq->vq_free_cnt = (uint16_t)(vq->vq_free_cnt - needed);
 	vq->vq_avail_idx = idx;
-
 	zxdh_queue_store_flags_packed(head_dp, head_flags);
 }
 
@@ -456,7 +438,7 @@ zxdh_update_packet_stats(struct zxdh_virtnet_stats *stats, struct rte_mbuf *mbuf
 }
 
 static void
-zxdh_xmit_flush(struct zxdh_virtqueue *vq)
+zxdh_xmit_fast_flush(struct zxdh_virtqueue *vq)
 {
 	uint16_t id       = 0;
 	uint16_t curr_id  = 0;
@@ -472,20 +454,22 @@ zxdh_xmit_flush(struct zxdh_virtqueue *vq)
 	 * for a used descriptor in the virtqueue.
 	 */
 	while (desc_is_used(&desc[used_idx], vq)) {
+		rte_prefetch0(&desc[used_idx + NEXT_CACHELINE_OFF_16B]);
 		id = desc[used_idx].id;
 		do {
+			desc[used_idx].id = used_idx;
 			curr_id = used_idx;
 			dxp = &vq->vq_descx[used_idx];
-			used_idx += dxp->ndescs;
-			free_cnt += dxp->ndescs;
-			if (used_idx >= size) {
-				used_idx -= size;
-				vq->used_wrap_counter ^= 1;
-			}
 			if (dxp->cookie != NULL) {
-				rte_pktmbuf_free(dxp->cookie);
+				rte_pktmbuf_free_seg(dxp->cookie);
 				dxp->cookie = NULL;
 			}
+			used_idx += 1;
+			free_cnt += 1;
+			if (unlikely(used_idx == size)) {
+				used_idx = 0;
+				vq->used_wrap_counter ^= 1;
+			}
 		} while (curr_id != id);
 	}
 	vq->vq_used_cons_idx = used_idx;
@@ -499,13 +483,12 @@ zxdh_xmit_pkts_packed(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 	struct zxdh_virtqueue  *vq   = txvq->vq;
 	uint16_t nb_tx = 0;
 
-	zxdh_xmit_flush(vq);
+	zxdh_xmit_fast_flush(vq);
 
 	for (nb_tx = 0; nb_tx < nb_pkts; nb_tx++) {
 		struct rte_mbuf *txm = tx_pkts[nb_tx];
 		int32_t can_push     = 0;
 		int32_t slots        = 0;
-		int32_t need         = 0;
 
 		rte_prefetch0(txm);
 		/* optimize ring usage */
@@ -522,26 +505,15 @@ zxdh_xmit_pkts_packed(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 		 * default    => number of segments + 1
 		 **/
 		slots = txm->nb_segs + !can_push;
-		need = slots - vq->vq_free_cnt;
 		/* Positive value indicates it need free vring descriptors */
-		if (unlikely(need > 0)) {
-			zxdh_xmit_cleanup_inorder_packed(vq, need);
-			need = slots - vq->vq_free_cnt;
-			if (unlikely(need > 0)) {
-				PMD_TX_LOG(ERR,
-						" No enough %d free tx descriptors to transmit."
-						"freecnt %d",
-						need,
-						vq->vq_free_cnt);
-				break;
-			}
-		}
+		if (unlikely(slots >  vq->vq_free_cnt))
+			break;
 
 		/* Enqueue Packet buffers */
 		if (can_push)
-			zxdh_enqueue_xmit_packed_fast(txvq, txm);
+			zxdh_xmit_enqueue_push(txvq, txm);
 		else
-			zxdh_enqueue_xmit_packed(txvq, txm, slots);
+			zxdh_xmit_enqueue_append(txvq, txm, slots);
 		zxdh_update_packet_stats(&txvq->stats, txm);
 	}
 	txvq->stats.packets += nb_tx;
@@ -1070,7 +1042,6 @@ uint16_t zxdh_recv_single_pkts(void *rx_queue, struct rte_mbuf **rcv_pkts, uint1
 
 		if (unlikely(zxdh_init_mbuf(rxm, len, hw, &vq->rxq) < 0))
 			continue;
-		rcv_pkts[nb_rx] = rxm;
 		zxdh_update_packet_stats(&rxvq->stats, rxm);
 		nb_rx++;
 	}
@@ -1084,3 +1055,192 @@ uint16_t zxdh_recv_single_pkts(void *rx_queue, struct rte_mbuf **rcv_pkts, uint1
 	}
 	return nb_rx;
 }
+
+static inline int pkt_padding(struct rte_mbuf *cookie, struct zxdh_hw *hw)
+{
+	uint16_t mtu_or_mss = 0;
+	uint16_t pkt_flag_lw16 = ZXDH_NO_IPID_UPDATE;
+	uint16_t l3_offset;
+	uint8_t pcode = ZXDH_PCODE_NO_IP_PKT_TYPE;
+	uint8_t l3_ptype = ZXDH_PI_L3TYPE_NOIP;
+	struct zxdh_pi_hdr *pi_hdr;
+	struct zxdh_pd_hdr_dl *pd_hdr;
+	struct zxdh_net_hdr_dl *net_hdr_dl = hw->net_hdr_dl;
+	uint8_t hdr_len = hw->dl_net_hdr_len;
+	uint16_t ol_flag = 0;
+	struct zxdh_net_hdr_dl *hdr;
+
+	hdr = (struct zxdh_net_hdr_dl *)rte_pktmbuf_prepend(cookie, hdr_len);
+	if (unlikely(hdr == NULL))
+		return -1;
+	rte_memcpy(hdr, net_hdr_dl, hdr_len);
+
+	if (hw->has_tx_offload) {
+		pi_hdr = &hdr->pipd_hdr_dl.pi_hdr;
+		pd_hdr = &hdr->pipd_hdr_dl.pd_hdr;
+
+		pcode = ZXDH_PCODE_IP_PKT_TYPE;
+		if (cookie->ol_flags & RTE_MBUF_F_TX_IPV6)
+			l3_ptype = ZXDH_PI_L3TYPE_IPV6;
+		else if (cookie->ol_flags & RTE_MBUF_F_TX_IPV4)
+			l3_ptype = ZXDH_PI_L3TYPE_IP;
+		else
+			pcode = ZXDH_PCODE_NO_IP_PKT_TYPE;
+
+		if (cookie->ol_flags & RTE_MBUF_F_TX_TCP_SEG) {
+			mtu_or_mss = (cookie->tso_segsz >= ZXDH_MIN_MSS) ?
+				cookie->tso_segsz : ZXDH_MIN_MSS;
+			pi_hdr->pkt_flag_hi8  |= ZXDH_TX_TCPUDP_CKSUM_CAL;
+			pkt_flag_lw16 |= ZXDH_NO_IP_FRAGMENT | ZXDH_TX_IP_CKSUM_CAL;
+			pcode = ZXDH_PCODE_TCP_PKT_TYPE;
+		} else if (cookie->ol_flags & RTE_MBUF_F_TX_UDP_SEG) {
+			mtu_or_mss = hw->eth_dev->data->mtu;
+			mtu_or_mss = (mtu_or_mss >= ZXDH_MIN_MSS) ? mtu_or_mss : ZXDH_MIN_MSS;
+			pkt_flag_lw16 |= ZXDH_TX_IP_CKSUM_CAL;
+			pi_hdr->pkt_flag_hi8 |= ZXDH_NO_TCP_FRAGMENT | ZXDH_TX_TCPUDP_CKSUM_CAL;
+			pcode = ZXDH_PCODE_UDP_PKT_TYPE;
+		} else {
+			pkt_flag_lw16 |= ZXDH_NO_IP_FRAGMENT;
+			pi_hdr->pkt_flag_hi8 |= ZXDH_NO_TCP_FRAGMENT;
+		}
+
+		if (cookie->ol_flags & RTE_MBUF_F_TX_IP_CKSUM)
+			pkt_flag_lw16 |= ZXDH_TX_IP_CKSUM_CAL;
+
+		if ((cookie->ol_flags & RTE_MBUF_F_TX_UDP_CKSUM) == RTE_MBUF_F_TX_UDP_CKSUM) {
+			pcode = ZXDH_PCODE_UDP_PKT_TYPE;
+			pi_hdr->pkt_flag_hi8 |= ZXDH_TX_TCPUDP_CKSUM_CAL;
+		} else if ((cookie->ol_flags & RTE_MBUF_F_TX_TCP_CKSUM) ==
+			RTE_MBUF_F_TX_TCP_CKSUM) {
+			pcode = ZXDH_PCODE_TCP_PKT_TYPE;
+			pi_hdr->pkt_flag_hi8 |= ZXDH_TX_TCPUDP_CKSUM_CAL;
+		}
+		pkt_flag_lw16 |= (mtu_or_mss >> ZXDH_MTU_MSS_UNIT_SHIFTBIT) & ZXDH_MTU_MSS_MASK;
+		pi_hdr->pkt_flag_lw16 = rte_be_to_cpu_16(pkt_flag_lw16);
+		pi_hdr->pkt_type = l3_ptype | ZXDH_PKT_FORM_CPU | pcode;
+
+		l3_offset = hdr_len + cookie->l2_len;
+		l3_offset += (cookie->ol_flags & RTE_MBUF_F_TX_TUNNEL_MASK) ?
+					cookie->outer_l2_len + cookie->outer_l3_len : 0;
+		pi_hdr->l3_offset = rte_be_to_cpu_16(l3_offset);
+		pi_hdr->l4_offset = rte_be_to_cpu_16(l3_offset + cookie->l3_len);
+		if (cookie->ol_flags & RTE_MBUF_F_TX_OUTER_IP_CKSUM)
+			ol_flag |= ZXDH_PD_OFFLOAD_OUTER_IPCSUM;
+	} else {
+		pd_hdr = &hdr->pd_hdr;
+	}
+
+	pd_hdr->dst_vfid = rte_be_to_cpu_16(cookie->port);
+
+	if (cookie->ol_flags & (RTE_MBUF_F_TX_VLAN | RTE_MBUF_F_TX_QINQ)) {
+		ol_flag |= ZXDH_PD_OFFLOAD_CVLAN_INSERT;
+		pd_hdr->cvlan_insert = rte_be_to_cpu_16(cookie->vlan_tci);
+		if (cookie->ol_flags & RTE_MBUF_F_TX_QINQ) {
+			ol_flag |= ZXDH_PD_OFFLOAD_SVLAN_INSERT;
+			pd_hdr->svlan_insert = rte_be_to_cpu_16(cookie->vlan_tci_outer);
+		}
+	}
+
+	pd_hdr->ol_flag = rte_be_to_cpu_16(ol_flag);
+
+	return 0;
+}
+
+/* Populate 1 descriptor with data from 1 single-segment mbuf */
+static inline void
+tx1(struct zxdh_virtqueue *vq, volatile struct zxdh_vring_packed_desc *txdp,
+		struct rte_mbuf *pkts, uint16_t id)
+{
+	uint16_t flags = vq->cached_flags;
+	txdp->addr = rte_mbuf_data_iova(pkts);
+	txdp->len = pkts->data_len;
+	txdp->id = id;
+	zxdh_queue_store_flags_packed(txdp, flags);
+}
+
+static void submit_to_backend_simple(struct zxdh_virtqueue  *vq,
+			struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	struct zxdh_hw *hw = vq->hw;
+	struct rte_mbuf *m = NULL;
+	uint16_t id =  vq->vq_avail_idx;
+	struct zxdh_vring_packed_desc *txdp = &vq->vq_packed.ring.desc[id];
+	struct zxdh_vq_desc_extra *dxp = &vq->vq_descx[id];
+	int mainpart, leftover;
+	int i, j;
+
+	/*
+	 * Process most of the packets in chunks of N pkts.  Any
+	 * leftover packets will get processed one at a time.
+	 */
+	mainpart = (nb_pkts & ~N_PER_LOOP_MASK);
+	leftover = (nb_pkts & N_PER_LOOP_MASK);
+
+	for (i = 0; i < mainpart; i += N_PER_LOOP) {
+		rte_prefetch0(dxp + i);
+		rte_prefetch0(tx_pkts + i);
+		for (j = 0; j < N_PER_LOOP; ++j) {
+			m  = *(tx_pkts + i + j);
+			if (unlikely(pkt_padding(m, hw) < 0)) {
+				vq->txq.stats.errors++;
+				continue;
+			}
+			(dxp + i + j)->cookie = (void *)m;
+			tx1(vq, txdp + i + j, m, id + i + j);
+			zxdh_update_packet_stats(&vq->txq.stats, m);
+		}
+	}
+
+	if (leftover > 0) {
+		rte_prefetch0(dxp + mainpart);
+		rte_prefetch0(tx_pkts + mainpart);
+
+		for (i = 0; i < leftover; ++i) {
+			m =  *(tx_pkts + mainpart + i);
+			if (unlikely(pkt_padding(m, hw) < 0)) {
+				vq->txq.stats.errors++;
+				continue;
+			}
+			(dxp + mainpart + i)->cookie = m;
+			tx1(vq, txdp + mainpart + i, *(tx_pkts + mainpart + i), id + mainpart + i);
+			zxdh_update_packet_stats(&vq->txq.stats, m);
+		}
+	}
+}
+
+uint16_t zxdh_xmit_pkts_simple(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts)
+{
+	struct zxdh_virtnet_tx *txvq = tx_queue;
+	struct zxdh_virtqueue  *vq   = txq_get_vq(txvq);
+	uint16_t nb_tx = 0, nb_tx_left;
+
+	zxdh_xmit_fast_flush(vq);
+
+	nb_pkts = (uint16_t)RTE_MIN(nb_pkts, vq->vq_free_cnt);
+	if (unlikely(nb_pkts == 0)) {
+		txvq->stats.idle++;
+		return 0;
+	}
+
+	nb_tx_left = nb_pkts;
+	if ((vq->vq_avail_idx + nb_pkts) >= vq->vq_nentries) {
+		nb_tx = vq->vq_nentries - vq->vq_avail_idx;
+		nb_tx_left = nb_pkts - nb_tx;
+		submit_to_backend_simple(vq, tx_pkts, nb_tx);
+		vq->vq_avail_idx = 0;
+		vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;
+
+		vq->vq_free_cnt -= nb_tx;
+		tx_pkts += nb_tx;
+	}
+	if (nb_tx_left) {
+		submit_to_backend_simple(vq, tx_pkts, nb_tx_left);
+		vq->vq_avail_idx  += nb_tx_left;
+		vq->vq_free_cnt  -= nb_tx_left;
+	}
+
+	zxdh_queue_notify(vq);
+	txvq->stats.packets += nb_pkts;
+
+	return nb_pkts;
+}
diff --git a/drivers/net/zxdh/zxdh_rxtx.h b/drivers/net/zxdh/zxdh_rxtx.h
index dba9567414..783fb456de 100644
--- a/drivers/net/zxdh/zxdh_rxtx.h
+++ b/drivers/net/zxdh/zxdh_rxtx.h
@@ -56,18 +56,19 @@ struct __rte_cache_aligned zxdh_virtnet_rx {
 
 struct __rte_cache_aligned zxdh_virtnet_tx {
 	struct zxdh_virtqueue         *vq;
-
-	rte_iova_t                zxdh_net_hdr_mem; /* hdr for each xmit packet */
-	uint16_t                  queue_id;           /* DPDK queue index. */
-	uint16_t                  port_id;            /* Device port identifier. */
+	const struct rte_memzone *zxdh_net_hdr_mz;  /* memzone to populate hdr. */
+	rte_iova_t               zxdh_net_hdr_mem; /* hdr for each xmit packet */
 	struct zxdh_virtnet_stats      stats;
 	const struct rte_memzone *mz;                 /* mem zone to populate TX ring. */
-	const struct rte_memzone *zxdh_net_hdr_mz;  /* memzone to populate hdr. */
+	uint64_t offloads;
+	uint16_t                  queue_id;           /* DPDK queue index. */
+	uint16_t                  port_id;            /* Device port identifier. */
 };
 
 uint16_t zxdh_xmit_pkts_packed(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 uint16_t zxdh_xmit_pkts_prepare(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 uint16_t zxdh_recv_pkts_packed(void *rx_queue, struct rte_mbuf **rx_pkts, uint16_t nb_pkts);
 uint16_t zxdh_recv_single_pkts(void *rx_queue, struct rte_mbuf **rcv_pkts, uint16_t nb_pkts);
+uint16_t zxdh_xmit_pkts_simple(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkts);
 
 #endif  /* ZXDH_RXTX_H */
-- 
2.27.0

[-- Attachment #1.1.2: Type: text/html , Size: 47933 bytes --]

^ permalink raw reply related

* [PATCH v5 0/4] net/zxdh: optimize Rx/Tx path performance
From: Junlong Wang @ 2026-06-15  1:19 UTC (permalink / raw)
  To: stephen; +Cc: dev, Junlong Wang
In-Reply-To: <20260606063226.491848-1-wang.junlong1@zte.com.cn>


[-- Attachment #1.1.1: Type: text/plain, Size: 3564 bytes --]

v5:
  - Reorganize patch series, placing interrupt fix as the first patch
    and fix condition check to properly enable interrupts.
  - Fix zxdh_recv_single_pkts() not compacting rcv_pkts[] on failure,
    which could cause use-after-free and mbuf leak.
  - Fix tx_bunch() and tx1() missing store barrier before setting AVAIL flag,
    preventing data race on weakly-ordered architectures.
  - Fix submit_to_backend_simple() writing descriptors for packets that
    failed pkt_padding(), causing mbuf leak.

v4:
  - fix some AI review issues.
  - fix queue enable intr bug.

v3:
  - remove unnecessary NULL check in zxdh_init_queue.
  - Split Ring: Bit[31] is unused and reserved, zxdh_queue_notify(): removing the
    zxdh_pci_with_feature(hw, ZXDH_F_RING_PACKED) check;
  - remove unnecessary double-free in in zxdh_recv_single_pkts();
  - used rte_pktmbuf_mtod();
  - remove rxq_get_vq(q) macro, use q->vq and apply it consistently;
  - Refactoring scatter and mtu check logic in zxdh_dev_mtu_set();
  - set txdp->id = avail_idx + i in tx_bunch/tx1.
  - add comment documenting zxdh_xmit_enqueue_append() now sets dxp->cookie = NULL for
    the head slot and stores cookies per descriptor via dep[idx].cookie.
  - add one-line comment noting tx_bunch() is the simple path handles single-segment.
  - remove unnecessary Extra initialization and the uint32_t cast.

v2:
  - zxdh_rxtx.c, pkt_padding(): modifyed the return value of pkt_padding();
  - zxdh_rxtx.c, zxdh_recv_single_pkts(): modifyed When zxdh_init_mbuf() fails
    the loop does "continue" and free mbufs;
  - zxdh_rxtx.c, refill_desc_unwrap(): Add rte_io_wmb() before writing flags
    in the refill_que_descs();
  - zxdh_queue.h, zxdh_queue_enable_intr(): Remove unnecessary function of zxdh_queue_enable_intr;
  - zxdh_ethdev.c, zxdh_init_queue(): changed the hdr_mz NULL check logic;

  - zxdh_rxtx.c, zxdh_xmit_pkts_simple()、zxdh_recv_single_pkts(): add stats.bytes count;
  - zxdh_rxtx.c, zxdh_init_mbuf():remove  rte_pktmbuf_dump(stdout, rxm, 40);
  - zxdh_ethdev.c, zxdh_dev_free_mbufs(): using rte_pktmbuf_free() to free mbufs;
  - Splitting into separate patches, structure reorganization and sw_ring removal、
    RX recv optimize、Tx xmit optimize、Tx;

v1:
  This patch optimizes the ZXDH PMD's receive and transmit path for better
  performance through several improvements:

- Add simple TX/RX burst functions (zxdh_xmit_pkts_simple and
  zxdh_recv_single_pkts) for single-segment packet scenarios.
- Remove RX software ring (sw_ring) to reduce memory allocation and
  copy.
- Optimize descriptor management with prefetching and simplified
  cleanup.
- Reorganize structure fields for better cache locality.

  These changes reduce CPU cycles and memory bandwidth consumption,
  resulting in improved packet processing throughput.

Junlong Wang (4):
  net/zxdh: fix queue enable intr issues
  net/zxdh: optimize queue structure to improve performance
  net/zxdh: optimize Rx recv pkts performance
  net/zxdh: optimize Tx xmit pkts performance

 drivers/net/zxdh/zxdh_ethdev.c     |  81 ++---
 drivers/net/zxdh/zxdh_ethdev_ops.c |  23 +-
 drivers/net/zxdh/zxdh_ethdev_ops.h |   4 +
 drivers/net/zxdh/zxdh_pci.c        |   2 +-
 drivers/net/zxdh/zxdh_queue.c      |  11 +-
 drivers/net/zxdh/zxdh_queue.h      | 122 ++++---
 drivers/net/zxdh/zxdh_rxtx.c       | 512 ++++++++++++++++++++++-------
 drivers/net/zxdh/zxdh_rxtx.h       |  27 +-
 8 files changed, 522 insertions(+), 260 deletions(-)

-- 
2.27.0

[-- Attachment #1.1.2: Type: text/html , Size: 6430 bytes --]

^ permalink raw reply

* Re: [PATCH v3 01/10] eal: add interface to check if lcore is EAL managed
From: lihuisong (C) @ 2026-06-15  1:08 UTC (permalink / raw)
  To: Thomas Monjalon
  Cc: anatoly.burakov, sivaprasad.tummala, dev, stephen, fengchengwen,
	yangxingui, zhanjie9, lihuisong
In-Reply-To: <JJjyblymSgmJdG-9r0OlIA@monjalon.net>


On 6/11/2026 5:10 PM, Thomas Monjalon wrote:
> 11/06/2026 08:16, lihuisong (C):
>> On 6/11/2026 7:28 AM, Thomas Monjalon wrote:
>>> 22/05/2026 06:11, Huisong Li:
>>>> Add a new helper function rte_lcore_is_eal_managed() to determine
>>>> if a logical core is managed by EAL.
>>>>
>>>> This interface returns true if the lcore role is either ROLE_RTE
>>>> (standard worker/main cores) or ROLE_SERVICE (service cores).
>>> [...]
>>>> +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_lcore_is_eal_managed, 26.07)
>>>> +int rte_lcore_is_eal_managed(unsigned int lcore_id)
>>>> +{
>>>> +	struct rte_config *cfg = rte_eal_get_configuration();
>>>> +
>>>> +	if (lcore_id >= RTE_MAX_LCORE)
>>>> +		return 0;
>>>> +	return cfg->lcore_role[lcore_id] == ROLE_RTE ||
>>>> +		cfg->lcore_role[lcore_id] == ROLE_SERVICE;
>>>> +}
>>> I'm not sure about adding this function in the API.
>>> We already have rte_eal_lcore_role()
>>> and I feel having this explicit ROLE_RTE || ROLE_SERVICE
>>> in the code where needed may be less confusing.
>> Ack.
>>
>>> Note: we should prefix these constants with RTE_LCORE_
>> Yeah, it's good.
>>
>> This will break API. And we can do this in 26.11.
> We can keep the old names as aliases.
> Better to not break the API, even in 26.11.
> We could decide later, after some time, to remove the aliases.
ok, sounds good.
Will do this in separated series.
>

^ permalink raw reply

* Re: [PATCH 8/9] ethdev: keep fast-path ops valid after port stop
From: Stephen Hemminger @ 2026-06-14 19:30 UTC (permalink / raw)
  To: Maxime Leroy
  Cc: hemant.agrawal, sachin.saxena, dev, stable, Thomas Monjalon,
	Andrew Rybchenko, Morten Brørup, Sunil Kumar Kori
In-Reply-To: <20260611154926.392670-9-maxime@leroys.fr>

On Thu, 11 Jun 2026 17:49:23 +0200
Maxime Leroy <maxime@leroys.fr> wrote:

> eth_dev_fp_ops_reset() restores a port's fast-path ops on stop/release
> via a compound literal, so every field it omits is zeroed to NULL. It
> sets only rx_pkt_burst/tx_pkt_burst (and the rxq/txq data), leaving
> rx_queue_count, tx_queue_count, rx/tx_descriptor_status, tx_pkt_prepare
> and the recycle callbacks NULL.
> 
> In non-debug builds these ops are reached through an unguarded indirect
> call (the NULL check exists only under RTE_ETHDEV_DEBUG_RX/TX). So a
> thread calling e.g. rte_eth_rx_queue_count() on a port being stopped
> dereferences NULL and crashes, while the same race on rte_eth_rx_burst()
> is harmless because the burst ops are reset to dummies. A poll-mode
> worker re-checking rx_queue_count before arming the Rx interrupt and
> sleeping hits exactly this.
> 
> Reset these ops to the same dummies eth_dev_set_dummy_fops() installs,
> so a stopped port behaves like a freshly allocated one: every fast-path
> op is a safe no-op, none is NULL.
> 
> Fixes: 066f3d9cc21c ("ethdev: remove callback checks from fast path")
> Cc: stable@dpdk.org
> Signed-off-by: Maxime Leroy <maxime@leroys.fr>

I wonder if queue_count dummy should just return 0 instead of -NOTSUP.
There are never going to be packets in the queue to receive.

Ditto for transmit and the status routines.

^ permalink raw reply

* [PATCH v2 07/20] net/sxe2: support IPsec inline protocol offload
From: liujie5 @ 2026-06-14  9:23 UTC (permalink / raw)
  To: stephen; +Cc: dev, Jie Liu
In-Reply-To: <20260614092328.201826-1-liujie5@linkdatatechnology.com>

From: Jie Liu <liujie5@linkdatatechnology.com>

This patch adds support for IPsec inline protocol offload for both
inbound and outbound traffic.

- Implement rte_security_ops: session_create, session_destroy.
- Add hardware SA table management.
- Update Rx/Tx data path to handle security offload flags.

The hardware offloads the ESP encapsulation/decapsulation and
cryptographic processing.

Signed-off-by: Jie Liu <liujie5@linkdatatechnology.com>
---
 drivers/net/sxe2/meson.build      |    2 +
 drivers/net/sxe2/sxe2_cmd_chnl.c  |  197 ++++
 drivers/net/sxe2/sxe2_cmd_chnl.h  |   20 +
 drivers/net/sxe2/sxe2_drv_cmd.h   |   61 ++
 drivers/net/sxe2/sxe2_ethdev.c    |   14 +
 drivers/net/sxe2/sxe2_ethdev.h    |    3 +
 drivers/net/sxe2/sxe2_ipsec.c     | 1565 +++++++++++++++++++++++++++++
 drivers/net/sxe2/sxe2_ipsec.h     |  254 +++++
 drivers/net/sxe2/sxe2_rx.c        |    5 +
 drivers/net/sxe2/sxe2_security.c  |  335 ++++++
 drivers/net/sxe2/sxe2_security.h  |   77 ++
 drivers/net/sxe2/sxe2_tx.c        |    8 +
 drivers/net/sxe2/sxe2_txrx_poll.c |   55 +
 13 files changed, 2596 insertions(+)
 create mode 100644 drivers/net/sxe2/sxe2_ipsec.c
 create mode 100644 drivers/net/sxe2/sxe2_ipsec.h
 create mode 100644 drivers/net/sxe2/sxe2_security.c
 create mode 100644 drivers/net/sxe2/sxe2_security.h

diff --git a/drivers/net/sxe2/meson.build b/drivers/net/sxe2/meson.build
index f03ea15356..86973edc99 100644
--- a/drivers/net/sxe2/meson.build
+++ b/drivers/net/sxe2/meson.build
@@ -64,4 +64,6 @@ sources += files(
         'sxe2_filter.c',
         'sxe2_rss.c',
         'sxe2_tm.c',
+        'sxe2_ipsec.c',
+        'sxe2_security.c',
 )
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.c b/drivers/net/sxe2/sxe2_cmd_chnl.c
index 19323ffcc4..7711e8e57d 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.c
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.c
@@ -877,3 +877,200 @@ int32_t sxe2_drv_tm_commit(struct sxe2_adapter *adapter)
 l_end:
 	return ret;
 }
+
+
+int32_t sxe2_drv_ipsec_get_capa(struct sxe2_adapter *adapter)
+{
+	int32_t ret = -1;
+	struct sxe2_drv_cmd_params cmd = { 0 };
+	struct sxe2_drv_ipsec_capa_resq resp;
+	struct sxe2_common_device *cdev = adapter->cdev;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_CAP_GET,
+				 NULL, 0,
+				 &resp, sizeof(resp));
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to get ipsec specifications, ret=%d", ret);
+		goto l_end;
+	}
+
+	adapter->security_ctx.ipsec_ctx.max_tx_sa = rte_le_to_cpu_16(resp.tx_sa_cnt);
+	adapter->security_ctx.ipsec_ctx.max_rx_sa = rte_le_to_cpu_16(resp.rx_sa_cnt);
+	adapter->security_ctx.ipsec_ctx.max_tcam = rte_le_to_cpu_16(resp.ip_id_cnt);
+	adapter->security_ctx.ipsec_ctx.max_udp_group = rte_le_to_cpu_16(resp.udp_group_cnt);
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "Max tx sa:%u, max rx sa:%u, max tcam:%u, udp group:%u.",
+			 rte_le_to_cpu_16(resp.tx_sa_cnt),
+			 rte_le_to_cpu_16(resp.rx_sa_cnt),
+			 rte_le_to_cpu_16(resp.ip_id_cnt),
+			 rte_le_to_cpu_16(resp.udp_group_cnt));
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_resource_clear(struct sxe2_adapter *adapter)
+{
+	int32_t ret = -1;
+	struct sxe2_drv_cmd_params cmd = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_RESOURCE_CLEAR,
+				 NULL, 0,
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to clear ipsec resource, ret=%d", ret);
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_txsa_add(struct sxe2_adapter *adapter,
+		struct sxe2_ipsec_tx_sa *tx_sa)
+{
+	struct sxe2_drv_cmd_params cmd               = { 0 };
+	struct sxe2_drv_ipsec_txsa_add_req req   = { 0 };
+	struct sxe2_drv_ipsec_txsa_add_resp resp = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                   = -1;
+	uint32_t mode                                  = 0;
+	uint32_t i                                     = 0;
+
+	if (tx_sa->algo == SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC)
+		mode |= IPSEC_TX_ENGINE_SM4;
+	if (tx_sa->mode == SXE2_IPSEC_MODE_ENC_AND_AUTH)
+		mode |= IPSEC_TX_ENCRYPT;
+	req.mode = rte_cpu_to_le_32(mode);
+	for (i = 0; i < SXE2_IPSEC_KEY_LEN; i++) {
+		req.encrypt_keys[i] = tx_sa->enc_key[i];
+		req.auth_keys[i] = tx_sa->auth_key[i];
+	}
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_TXSA_ADD,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "failed to add tx sa, ret=%d", ret);
+		goto l_end;
+	}
+	tx_sa->hw_sa_id = rte_le_to_cpu_16(resp.index);
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_rxsa_add(struct sxe2_adapter *adapter,
+		struct sxe2_ipsec_rx_sa *rx_sa,
+		struct sxe2_ipsec_rx_tcam *rx_tcam,
+		struct sxe2_ipsec_rx_udp_group *rx_udp_group)
+{
+	struct sxe2_drv_cmd_params cmd               = { 0 };
+	struct sxe2_drv_ipsec_rxsa_add_req req   = { 0 };
+	struct sxe2_drv_ipsec_rxsa_add_resp resp = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                   = -1;
+	uint32_t mode                                  = 0;
+	uint32_t i                                     = 0;
+
+	if (rx_sa->algo == SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC)
+		mode |= IPSEC_RX_ENGINE_SM4;
+	if (rx_sa->mode == SXE2_IPSEC_MODE_ENC_AND_AUTH)
+		mode |= IPSEC_RX_DECRYPT;
+	if (rx_tcam->ip_addr.type == RTE_SECURITY_IPSEC_TUNNEL_IPV6) {
+		mode |= IPSEC_RX_IPV6;
+		memcpy(req.ipaddr, rx_tcam->ip_addr.dst_ipv6, sizeof(req.ipaddr));
+	} else {
+		req.ipaddr[0] = rx_tcam->ip_addr.dst_ipv4;
+	}
+	req.mode = rte_cpu_to_le_32(mode);
+	req.spi = rte_cpu_to_le_32(rx_sa->spi);
+	if (rx_udp_group != NULL) {
+		req.udp_port = rte_cpu_to_le_32((uint32_t)rx_udp_group->udp_port);
+		req.sport_en = rx_udp_group->sport_en;
+		req.dport_en = rx_udp_group->dport_en;
+	}
+
+	PMD_DEV_LOG_INFO(adapter, DRV, "Add rx sa, mode: 0x%x, spi: 0x%x, udp_port: %u, "
+			 "sport_en: %u, dport_en: %u.",
+			 req.mode, req.spi, req.udp_port, req.sport_en, req.dport_en);
+
+	/* encrypt and auth keys */
+	for (i = 0; i < SXE2_IPSEC_KEY_LEN; i++) {
+		req.encrypt_keys[i] = rx_sa->enc_key[i];
+		req.auth_keys[i] = rx_sa->auth_key[i];
+	}
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_RXSA_ADD,
+				 &req, sizeof(req),
+				 &resp, sizeof(resp));
+
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret) {
+		PMD_DEV_LOG_ERR(adapter, DRV, "Failed to add rx sa, ret=%d", ret);
+		goto l_end;
+	}
+	rx_sa->hw_sa_id = rte_le_to_cpu_16(resp.sa_idx);
+	rx_sa->hw_ip_id = resp.ip_id;
+	rx_tcam->hw_ip_id = resp.ip_id;
+	rx_sa->hw_udp_group_id = resp.udp_group_id;
+	if (rx_udp_group != NULL)
+		rx_udp_group->hw_group_id = resp.udp_group_id;
+
+l_end:
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_rxsa_delete(struct sxe2_adapter *adapter,
+					struct sxe2_ipsec_rx_sa *rx_sa)
+{
+	struct sxe2_drv_ipsec_rxsa_del_req req = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	req.sa_idx = rte_cpu_to_le_16(rx_sa->hw_sa_id);
+	req.spi = rte_cpu_to_le_32(rx_sa->spi);
+	req.ip_id = rx_sa->hw_ip_id;
+	req.group_id = rx_sa->hw_udp_group_id;
+
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_RXSA_DEL,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV,
+				"Failed to delete rx sa, sa id: %u, spi: %u, "
+				"ip id: %u, udp group id: %u, ret: %d.",
+				rx_sa->hw_sa_id, rx_sa->spi, rx_sa->hw_ip_id,
+				rx_sa->hw_udp_group_id, ret);
+
+	return ret;
+}
+
+int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
+					   uint16_t sa_id)
+{
+	struct sxe2_drv_ipsec_txsa_del_req req = { 0 };
+	struct sxe2_drv_cmd_params cmd             = { 0 };
+	struct sxe2_common_device *cdev = adapter->cdev;
+	int32_t ret                                 = -1;
+
+	req.sa_idx = rte_cpu_to_le_16(sa_id);
+	sxe2_drv_cmd_params_fill(adapter, &cmd, SXE2_DRV_CMD_IPSEC_TXSA_DEL,
+				 &req, sizeof(req),
+				 NULL, 0);
+	ret = sxe2_drv_cmd_exec(cdev, &cmd);
+	if (ret)
+		PMD_DEV_LOG_ERR(adapter, DRV,
+				"Failed to delete tx sa, sa id: %u, ret: %d.",
+				sa_id, ret);
+
+	return ret;
+}
+
diff --git a/drivers/net/sxe2/sxe2_cmd_chnl.h b/drivers/net/sxe2/sxe2_cmd_chnl.h
index 77e689abcd..dac487fe7d 100644
--- a/drivers/net/sxe2/sxe2_cmd_chnl.h
+++ b/drivers/net/sxe2/sxe2_cmd_chnl.h
@@ -44,6 +44,26 @@ int32_t sxe2_drv_root_tree_alloc(struct rte_eth_dev *dev);
 
 int32_t sxe2_drv_tm_commit(struct sxe2_adapter *adapter);
 
+int32_t sxe2_drv_ipsec_resource_clear(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_ipsec_get_capa(struct sxe2_adapter *adapter);
+
+int32_t sxe2_drv_ipsec_rxsa_add(struct sxe2_adapter *adapter,
+			    struct sxe2_ipsec_rx_sa *rx_sa,
+			    struct sxe2_ipsec_rx_tcam *rx_tcam,
+			    struct sxe2_ipsec_rx_udp_group *rx_udp_group);
+
+int32_t sxe2_drv_ipsec_txsa_add(struct sxe2_adapter *adapter,
+			    struct sxe2_ipsec_tx_sa *tx_sa);
+
+int32_t sxe2_drv_ipsec_rxsa_delete(struct sxe2_adapter *adapter,
+			       struct sxe2_ipsec_rx_sa *rx_sa);
+
+int32_t sxe2_drv_ipsec_txsa_delete(struct sxe2_adapter *adapter,
+			       uint16_t sa_id);
+
+int32_t sxe2_drv_promisc_config(struct sxe2_adapter *adapter, bool set);
+
 int32_t sxe2_drv_allmulti_config(struct sxe2_adapter *adapter, bool set);
 
 int32_t sxe2_drv_uc_config(struct sxe2_adapter *adapter, struct rte_ether_addr *addr, bool add);
diff --git a/drivers/net/sxe2/sxe2_drv_cmd.h b/drivers/net/sxe2/sxe2_drv_cmd.h
index 67c6885cae..39a108d76a 100644
--- a/drivers/net/sxe2/sxe2_drv_cmd.h
+++ b/drivers/net/sxe2/sxe2_drv_cmd.h
@@ -375,6 +375,67 @@ struct __rte_aligned(4) __rte_packed_begin sxe2_tm_add_queue_msg {
 	struct sxe2_tm_info info;
 } __rte_packed_end;
 
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_capa_resq {
+	uint16_t tx_sa_cnt;
+	uint16_t rx_sa_cnt;
+	uint16_t ip_id_cnt;
+	uint16_t udp_group_cnt;
+} __rte_packed_end;
+
+#define SXE2_IPSEC_KEY_LEN (32)
+#define SXE2_IPV6_ADDR_LEN (4)
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_txsa_add_req {
+	uint32_t mode;
+	uint8_t encrypt_keys[SXE2_IPSEC_KEY_LEN];
+	uint8_t auth_keys[SXE2_IPSEC_KEY_LEN];
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+} __rte_packed_end;
+
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_txsa_add_resp {
+	uint16_t index;
+} __rte_packed_end;
+
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_rxsa_add_req {
+	uint32_t mode;
+	uint32_t spi;
+	uint32_t ipaddr[SXE2_IPV6_ADDR_LEN];
+	uint32_t udp_port;
+	uint8_t sport_en;
+	uint8_t dport_en;
+	uint8_t is_over_sdn;
+	uint8_t sdn_group_id;
+	uint8_t encrypt_keys[SXE2_IPSEC_KEY_LEN];
+	uint8_t auth_keys[SXE2_IPSEC_KEY_LEN];
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+} __rte_packed_end;
+
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_rxsa_add_resp {
+	uint8_t ip_id;
+	uint8_t udp_group_id;
+	uint16_t sa_idx;
+} __rte_packed_end;
+
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_txsa_del_req {
+	uint16_t sa_idx;
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+} __rte_packed_end;
+
+struct __rte_aligned(4) __rte_packed_begin sxe2_drv_ipsec_rxsa_del_req {
+	uint8_t ip_id;
+	uint8_t group_id;
+	uint16_t sa_idx;
+	uint32_t spi;
+	bool func_type;
+	uint8_t func_id;
+	uint8_t drv_id;
+} __rte_packed_end;
+
 enum sxe2_drv_cmd_module {
 	SXE2_DRV_CMD_MODULE_HANDSHAKE = 0,
 	SXE2_DRV_CMD_MODULE_DEV = 1,
diff --git a/drivers/net/sxe2/sxe2_ethdev.c b/drivers/net/sxe2/sxe2_ethdev.c
index f98cf367f1..9c9d98782b 100644
--- a/drivers/net/sxe2/sxe2_ethdev.c
+++ b/drivers/net/sxe2/sxe2_ethdev.c
@@ -298,6 +298,11 @@ static int32_t sxe2_dev_infos_get(struct rte_eth_dev *dev,
 	if (adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_PTP)
 		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_TIMESTAMP;
 
+	if (sxe2_ipsec_supported(adapter)) {
+		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_SECURITY;
+		dev_info->tx_offload_capa |= RTE_ETH_TX_OFFLOAD_SECURITY;
+	}
+
 	if (adapter->cap_flags & SXE2_DEV_CAPS_OFFLOAD_RSS) {
 		dev_info->rx_offload_capa |= RTE_ETH_RX_OFFLOAD_RSS_HASH;
 		dev_info->flow_type_rss_offloads  |= SXE2_RSS_HF_SUPPORT_ALL;
@@ -1053,6 +1058,12 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 		goto init_eth_err;
 	}
 
+	ret = sxe2_security_init(dev);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to initialize security, ret=%d", ret);
+		goto init_security_err;
+	}
+
 	ret = sxe2_rss_disable(dev);
 	if (ret) {
 		PMD_LOG_ERR(INIT, "Failed to disable rss, ret=%d", ret);
@@ -1067,6 +1078,8 @@ static int32_t sxe2_dev_init(struct rte_eth_dev *dev,
 
 	goto l_end;
 
+init_security_err:
+	sxe2_eth_uinit(dev);
 init_sched_err:
 init_rss_err:
 init_eth_err:
@@ -1085,6 +1098,7 @@ static int32_t sxe2_dev_close(struct rte_eth_dev *dev)
 	(void)sxe2_rss_disable(dev);
 	(void)sxe2_sched_uinit(dev);
 	sxe2_vsi_uninit(dev);
+	sxe2_security_uinit(dev);
 	sxe2_dev_pci_map_uinit(dev);
 	sxe2_eth_uinit(dev);
 
diff --git a/drivers/net/sxe2/sxe2_ethdev.h b/drivers/net/sxe2/sxe2_ethdev.h
index 95594fcbde..fed8ce37d9 100644
--- a/drivers/net/sxe2/sxe2_ethdev.h
+++ b/drivers/net/sxe2/sxe2_ethdev.h
@@ -20,6 +20,8 @@
 #include "sxe2_queue.h"
 #include "sxe2_mac.h"
 #include "sxe2_osal.h"
+#include "sxe2_security.h"
+#include "sxe2_ipsec.h"
 #include "sxe2_tm.h"
 #include "sxe2_filter.h"
 
@@ -313,6 +315,7 @@ struct sxe2_adapter {
 	struct sxe2_sched_hw_cap      sched_ctxt;
 	struct sxe2_tm_context        tm_ctxt;
 	struct sxe2_devargs           devargs;
+	struct sxe2_security_ctx      security_ctx;
 	struct sxe2_switchdev_info    switchdev_info;
 	bool                          rule_started;
 	bool                          flow_isolated;
diff --git a/drivers/net/sxe2/sxe2_ipsec.c b/drivers/net/sxe2/sxe2_ipsec.c
new file mode 100644
index 0000000000..e783a51b85
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_ipsec.c
@@ -0,0 +1,1565 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_malloc.h>
+#include <rte_bitmap.h>
+
+#include "sxe2_ethdev.h"
+#include "sxe2_security.h"
+#include "sxe2_ipsec.h"
+#include "sxe2_cmd_chnl.h"
+#include "sxe2_common_log.h"
+
+bool sxe2_ipsec_supported(struct sxe2_adapter *adapter)
+{
+	uint64_t cap = adapter->cap_flags;
+
+	return !!(cap & SXE2_DEV_CAPS_OFFLOAD_IPSEC);
+}
+
+bool sxe2_ipsec_valid_tx_offloads(uint64_t offloads)
+{
+	bool ret = true;
+	uint64_t tso_features = 0;
+	uint64_t cksum_features = 0;
+
+	if (offloads & RTE_ETH_TX_OFFLOAD_SECURITY) {
+		tso_features = RTE_ETH_TX_OFFLOAD_TCP_TSO |
+			RTE_ETH_TX_OFFLOAD_UDP_TSO |
+			RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO |
+			RTE_ETH_TX_OFFLOAD_GRE_TNL_TSO |
+			RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO |
+			RTE_ETH_TX_OFFLOAD_GENEVE_TNL_TSO;
+		if (offloads & tso_features) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with TSO offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		cksum_features = RTE_ETH_TX_OFFLOAD_IPV4_CKSUM |
+			RTE_ETH_TX_OFFLOAD_UDP_CKSUM |
+			RTE_ETH_TX_OFFLOAD_TCP_CKSUM |
+			RTE_ETH_TX_OFFLOAD_SCTP_CKSUM |
+			RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM |
+			RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM;
+		if (offloads & cksum_features) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with checksum offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & (RTE_ETH_TX_OFFLOAD_VLAN_INSERT | RTE_ETH_TX_OFFLOAD_QINQ_INSERT)) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with vlan offload.");
+			ret = false;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+bool sxe2_ipsec_valid_rx_offloads(uint64_t offloads)
+{
+	bool ret = true;
+
+	if (offloads & RTE_ETH_RX_OFFLOAD_SECURITY) {
+		if (offloads & RTE_ETH_RX_OFFLOAD_TCP_LRO) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with LRO offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & RTE_ETH_RX_OFFLOAD_CHECKSUM) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with checksum offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & RTE_ETH_RX_OFFLOAD_KEEP_CRC) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with keep CRC offload.");
+			ret = false;
+			goto l_end;
+		}
+
+		if (offloads & RTE_ETH_RX_OFFLOAD_VLAN) {
+			PMD_LOG_ERR(DRV, "Security offload is not compatible with vlan offload.");
+			ret = false;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_ipsec_bitmap_mem_init(struct rte_bitmap **d_bmp, void **d_mem, uint32_t bits)
+{
+	struct rte_bitmap *bmp = NULL;
+	uint32_t bmp_size           = 0;
+	void *mem              = NULL;
+	int32_t ret                = -1;
+
+	bmp_size = rte_bitmap_get_memory_footprint(bits);
+
+	mem = rte_zmalloc("ipsec bitmap", bmp_size, RTE_CACHE_LINE_SIZE);
+	if (mem == NULL) {
+		PMD_LOG_ERR(DRV, "Alloc ipsec bitmap memory failed.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	bmp = rte_bitmap_init(bits, mem, bmp_size);
+	if (bmp == NULL) {
+		PMD_LOG_ERR(DRV, "Failed to init ipsec bitmap.");
+		rte_free(mem);
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	*d_bmp = bmp;
+	*d_mem = mem;
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t sxe2_ipsec_bitmap_init(struct sxe2_security_ctx *sxe2_sctx)
+{
+	int32_t ret  = -1;
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem, sxe2_sctx->ipsec_ctx.max_tx_sa);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem, sxe2_sctx->ipsec_ctx.max_rx_sa);
+	if (ret) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem, sxe2_sctx->ipsec_ctx.max_tcam);
+	if (ret) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_bitmap_mem_init(&sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp,
+			&sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem, sxe2_sctx->ipsec_ctx.max_udp_group);
+	if (ret) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+		sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem = NULL;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static uint16_t sxe2_ipsec_id_alloc(struct rte_bitmap *bmp, uint16_t bits)
+{
+	uint16_t i = 0;
+	uint16_t index = 0XFFFF;
+
+	for (i = 0; i < bits; i++) {
+		if (!rte_bitmap_get(bmp, i)) {
+			index = i;
+			rte_bitmap_set(bmp, i);
+			break;
+		}
+	}
+
+	return index;
+}
+
+static void sxe2_ipsec_id_free(struct rte_bitmap *bmp, uint16_t pos)
+{
+	rte_bitmap_clear(bmp, pos);
+}
+
+static struct rte_cryptodev_symmetric_capability *
+sxe2_ipsec_cipher_cap_get(struct rte_cryptodev_capabilities *crypto_cap,
+			enum rte_crypto_cipher_algorithm algo)
+{
+	struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint8_t index                                              = 0;
+
+	for (index = 0; index < SXE2_IPSEC_CAP_MAX; index++) {
+		if (crypto_cap[index].sym.xform_type == RTE_CRYPTO_SYM_XFORM_CIPHER &&
+			crypto_cap[index].sym.cipher.algo == algo) {
+			capability = &crypto_cap[index].sym;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return capability;
+}
+
+static struct rte_cryptodev_symmetric_capability *
+sxe2_ipsec_auth_cap_get(struct rte_cryptodev_capabilities *crypto_cap,
+			enum rte_crypto_auth_algorithm algo)
+{
+	struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint8_t index                                              = 0;
+
+	for (index = 0; index < SXE2_IPSEC_CAP_MAX; index++) {
+		if (crypto_cap[index].sym.xform_type == RTE_CRYPTO_SYM_XFORM_AUTH &&
+			crypto_cap[index].sym.auth.algo == algo) {
+			capability = &crypto_cap[index].sym;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return capability;
+}
+
+static bool sxe2_security_valid_key(uint16_t src_key, uint16_t max_key,
+				    uint16_t min_key, uint16_t increment)
+{
+	bool is_valid = false;
+
+	if (src_key > SXE2_IPSEC_MAX_KEY_LEN) {
+		is_valid = false;
+		goto l_end;
+	}
+
+	if (src_key < min_key || src_key > max_key) {
+		is_valid = false;
+		goto l_end;
+	}
+
+	if (increment == 0) {
+		is_valid = true;
+		goto l_end;
+	}
+
+	if ((uint16_t)(src_key - min_key) % increment) {
+		is_valid = false;
+		goto l_end;
+	}
+
+	is_valid = true;
+
+l_end:
+	return is_valid;
+}
+
+static int32_t
+sxe2_ipsec_valid_cipher(enum rte_crypto_cipher_operation cipher_op,
+			struct rte_cryptodev_capabilities *crypto_cap,
+			struct rte_crypto_sym_xform *xform)
+{
+	const struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint16_t src_key                                = 0;
+	uint16_t max_key                                = 0;
+	uint16_t min_key                                = 0;
+	uint16_t increment                              = 0;
+	int32_t ret                                    = -1;
+
+	if (xform->cipher.op != cipher_op) {
+		PMD_LOG_ERR(DRV, "Invalid cipher direction specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	capability = sxe2_ipsec_cipher_cap_get(crypto_cap, xform->cipher.algo);
+	if (!capability) {
+		PMD_LOG_ERR(DRV, "Invalid cipher algo specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	src_key = xform->cipher.key.length;
+	min_key = capability->cipher.key_size.min;
+	max_key = capability->cipher.key_size.max;
+	increment = capability->cipher.key_size.increment;
+	if (!sxe2_security_valid_key(src_key, max_key, min_key, increment)) {
+		PMD_LOG_ERR(DRV, "Invalid cipher key size specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_valid_auth(enum rte_crypto_auth_operation auth_op,
+		      struct rte_cryptodev_capabilities *crypto_cap,
+		      struct rte_crypto_sym_xform *xform)
+{
+	const struct rte_cryptodev_symmetric_capability *capability = NULL;
+	uint16_t src_key                                = 0;
+	uint16_t max_key                                = 0;
+	uint16_t min_key                                = 0;
+	uint16_t increment                              = 0;
+	int32_t ret                                    = -1;
+
+	if (xform->auth.op != auth_op) {
+		PMD_LOG_ERR(DRV, "Invalid auth direction specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	capability = sxe2_ipsec_auth_cap_get(crypto_cap, xform->auth.algo);
+	if (!capability) {
+		PMD_LOG_ERR(DRV, "Invalid auth algo specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	src_key = xform->auth.key.length;
+	min_key = capability->auth.key_size.min;
+	max_key = capability->auth.key_size.max;
+	increment = capability->auth.key_size.increment;
+	if (!sxe2_security_valid_key(src_key, max_key, min_key, increment)) {
+		PMD_LOG_ERR(DRV, "Invalid auth key size specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static bool
+sxe2_ipsec_valid_algo(enum rte_crypto_auth_algorithm auth_algo,
+		      enum rte_crypto_cipher_algorithm cipher_algo)
+{
+	bool ret = false;
+
+	if ((cipher_algo == SXE2_RTE_CRYPTO_CIPHER_AES_CBC &&
+		 auth_algo == SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC) ||
+		(cipher_algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC &&
+		 auth_algo == SXE2_RTE_CRYPTO_AUTH_SM3_HMAC)) {
+		ret = true;
+		goto l_end;
+	}
+
+l_end:
+	return ret;
+}
+
+static enum sxe2_ipsec_algorithm
+sxe2_ipsec_algo_gen(enum rte_crypto_cipher_algorithm cipher_algo)
+{
+	enum sxe2_ipsec_algorithm algo = SXE2_IPSEC_ALGO_INVALID;
+
+	if (cipher_algo == SXE2_RTE_CRYPTO_CIPHER_AES_CBC)
+		algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+	else if (cipher_algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC)
+		algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+
+	return algo;
+}
+
+static int32_t
+	sxe2_ipsec_valid_xform(struct sxe2_security_ctx *sxe2_sctx,
+			       struct rte_security_session_conf *conf)
+{
+	struct rte_crypto_sym_xform *xform = NULL;
+	struct rte_cryptodev_capabilities *crypto_cap =
+		sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].crypto_capabilities;
+	enum rte_crypto_auth_algorithm auth_algo = RTE_CRYPTO_AUTH_NULL;
+	enum rte_crypto_cipher_algorithm cipher_algo = RTE_CRYPTO_CIPHER_NULL;
+	int32_t ret = -1;
+
+	if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_EGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		xform = conf->crypto_xform;
+		cipher_algo = xform->cipher.algo;
+		ret = sxe2_ipsec_valid_cipher(RTE_CRYPTO_CIPHER_OP_ENCRYPT,
+					      crypto_cap, xform);
+		if (ret)
+			goto l_end;
+
+		if (conf->crypto_xform->next) {
+			if (conf->crypto_xform->next->type == RTE_CRYPTO_SYM_XFORM_AUTH) {
+				auth_algo = conf->crypto_xform->next->auth.algo;
+				if (!sxe2_ipsec_valid_algo(auth_algo, cipher_algo)) {
+					PMD_LOG_ERR(DRV, "Invalid algo group.");
+					ret = -EINVAL;
+					goto l_end;
+				}
+				xform = conf->crypto_xform->next;
+				ret = sxe2_ipsec_valid_auth(RTE_CRYPTO_AUTH_OP_GENERATE,
+									crypto_cap, xform);
+				if (ret)
+					goto l_end;
+			} else {
+				PMD_LOG_ERR(DRV, "Encrypt direction next xform only verify.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+		}
+	} else if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_INGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		xform = conf->crypto_xform;
+		ret = sxe2_ipsec_valid_cipher(RTE_CRYPTO_CIPHER_OP_DECRYPT,
+										crypto_cap, xform);
+		if (ret)
+			goto l_end;
+
+	} else if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_INGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_AUTH) {
+		xform = conf->crypto_xform;
+		ret = sxe2_ipsec_valid_auth(RTE_CRYPTO_AUTH_OP_VERIFY, crypto_cap, xform);
+		if (ret)
+			goto l_end;
+
+		if (conf->crypto_xform->next &&
+			conf->crypto_xform->next->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+			auth_algo = conf->crypto_xform->auth.algo;
+			cipher_algo = conf->crypto_xform->next->cipher.algo;
+			if (!sxe2_ipsec_valid_algo(auth_algo, cipher_algo)) {
+				PMD_LOG_ERR(DRV, "Invalid algo group.");
+				ret = -EINVAL;
+				goto l_end;
+			}
+			xform = conf->crypto_xform->next;
+			ret = sxe2_ipsec_valid_cipher(RTE_CRYPTO_CIPHER_OP_DECRYPT,
+										crypto_cap, xform);
+			if (ret)
+				goto l_end;
+		} else {
+			PMD_LOG_ERR(DRV, "Not support decrypt direction only verify, but not decrypt.");
+			ret = -EINVAL;
+			goto l_end;
+		}
+	} else {
+		PMD_LOG_ERR(DRV, "Encrypt/decrypt xform invalid.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_valid_udp(struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+	uint16_t sport = conf->ipsec.udp.sport;
+	uint16_t dport = conf->ipsec.udp.dport;
+
+	if (conf->ipsec.options.udp_encap == 0) {
+		ret = 0;
+		goto l_end;
+	}
+
+	if (sport == 0 && dport == 0) {
+		PMD_LOG_ERR(DRV, "Invalid udp port, cannot be zero.");
+		ret = -1;
+		goto l_end;
+	}
+
+	if (sport != 0 && dport != 0 && sport != dport) {
+		PMD_LOG_ERR(DRV, "Invalid udp port, if sport and dport is not zero, must be equal.");
+		ret = -1;
+		goto l_end;
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_session_conf_valid(struct sxe2_security_ctx *sxe2_sctx,
+			      struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+
+	if (sxe2_sctx == NULL) {
+		PMD_LOG_ERR(DRV, "Invalid  security ctx.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->action_type !=
+		sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].action) {
+		PMD_LOG_ERR(DRV, "Invalid action specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.mode !=
+		sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].ipsec.mode) {
+		PMD_LOG_ERR(DRV, "Invalid IPsec mode specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.proto !=
+	    sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC].ipsec.proto) {
+		PMD_LOG_ERR(DRV, "Invalid IPsec protocol specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.options.esn) {
+		PMD_LOG_ERR(DRV, "Not support esn.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_INGRESS &&
+		conf->ipsec.spi == 0) {
+		PMD_LOG_ERR(DRV, "spi cannot be zero.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	if (conf->crypto_xform == NULL) {
+		PMD_LOG_ERR(DRV, "Invalid ipsec xform specified");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_valid_udp(conf);
+	if (ret)
+		goto l_end;
+
+	ret = sxe2_ipsec_valid_xform(sxe2_sctx, conf);
+	if (ret)
+		goto l_end;
+
+l_end:
+	return ret;
+}
+
+static void
+sxe2_ipsec_session_save(struct sxe2_security_ctx *sxe2_sctx,
+			struct rte_security_session_conf *conf,
+			struct sxe2_security_session *sxe2_sess, uint16_t sa_id, uint16_t index)
+{
+	enum rte_crypto_cipher_algorithm cipher_algo   = RTE_CRYPTO_CIPHER_NULL;
+
+	sxe2_sess->adapter = sxe2_sctx->adapter;
+	sxe2_sess->direction = conf->ipsec.direction;
+	sxe2_sess->protocol = conf->protocol;
+	sxe2_sess->mode = conf->ipsec.mode;
+	sxe2_sess->sa_proto = conf->ipsec.proto;
+	sxe2_sess->sa.spi = conf->ipsec.spi;
+	sxe2_sess->sa.hw_idx = sa_id;
+	sxe2_sess->sa.sw_idx = index;
+
+	if (conf->ipsec.options.esn) {
+		sxe2_sess->esn.enabled = true;
+		sxe2_sess->esn.value = conf->ipsec.esn.value;
+	}
+
+	if (sxe2_sess->mode == RTE_SECURITY_IPSEC_SA_MODE_TUNNEL)
+		sxe2_sess->type = conf->ipsec.tunnel.type;
+
+	if (conf->ipsec.options.udp_encap) {
+		sxe2_sess->udp_cap.enabled = true;
+		memcpy(&sxe2_sess->udp_cap.value, &conf->ipsec.udp,
+			sizeof(struct rte_security_ipsec_udp_param));
+	}
+
+	sxe2_sess->pkt_metadata_template.sa_idx = sa_id;
+	sxe2_sess->pkt_metadata_template.ol_flags |= SXE2_IPSEC_OL_FLAGS_IS_TUN;
+	sxe2_sess->pkt_metadata_template.ol_flags |= SXE2_IPSEC_OL_FLAGS_IS_ESP;
+
+	if (conf->ipsec.direction == RTE_SECURITY_IPSEC_SA_DIR_EGRESS &&
+		conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		cipher_algo = conf->crypto_xform->cipher.algo;
+		sxe2_sess->pkt_metadata_template.algo = sxe2_ipsec_algo_gen(cipher_algo);
+		if (conf->crypto_xform->next)
+			sxe2_sess->pkt_metadata_template.mode = SXE2_IPSEC_MODE_ENC_AND_AUTH;
+		else
+			sxe2_sess->pkt_metadata_template.mode = SXE2_IPSEC_MODE_ONLY_ENCRYPT;
+	}
+
+	PMD_LOG_INFO(DRV,
+		"Save security info to session ctx, said:%u, spi:%u, mode:%u, algo:%u",
+		sa_id, sxe2_sess->sa.spi,
+		sxe2_sess->pkt_metadata_template.mode,
+		sxe2_sess->pkt_metadata_template.algo);
+}
+
+static void
+sxe2_ipsec_tx_sa_fill(struct sxe2_ipsec_tx_sa *tx_sa,
+		      struct rte_security_session_conf *conf)
+{
+	uint8_t *dst = NULL;
+	uint8_t len  = 0;
+
+	memcpy(&tx_sa->xform, &conf->ipsec, sizeof(struct rte_security_ipsec_xform));
+
+	if (conf->crypto_xform->next)
+		tx_sa->mode = SXE2_IPSEC_MODE_ENC_AND_AUTH;
+	else
+		tx_sa->mode = SXE2_IPSEC_MODE_ONLY_ENCRYPT;
+
+	if (conf->crypto_xform->cipher.algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC)
+		tx_sa->algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+	else
+		tx_sa->algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+
+	dst = tx_sa->enc_key;
+	len = conf->crypto_xform->cipher.key.length;
+	memcpy(dst, conf->crypto_xform->cipher.key.data, len);
+
+	if (conf->crypto_xform->next) {
+		dst = tx_sa->auth_key;
+		len = conf->crypto_xform->next->auth.key.length;
+		memcpy(dst, conf->crypto_xform->next->auth.key.data, len);
+	}
+}
+
+static int32_t
+sxe2_ipsec_tx_sa_add(struct sxe2_security_ctx *sxe2_sctx,
+		     struct rte_security_session_conf *conf,
+		     struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_tx_sa *tx_sa = NULL;
+	struct rte_bitmap *bmp          = sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp;
+	uint16_t bits                        = sxe2_sctx->ipsec_ctx.max_tx_sa;
+	uint16_t index                       = 0xFFFF;
+	int32_t ret                         = -1;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	index = sxe2_ipsec_id_alloc(bmp, bits);
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+	if (index == 0xFFFF) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec tx sa index.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	tx_sa = &sxe2_sctx->ipsec_ctx.tx_sa[index];
+
+	sxe2_ipsec_tx_sa_fill(tx_sa, conf);
+
+	ret = sxe2_drv_ipsec_txsa_add(sxe2_sctx->adapter, tx_sa);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to add tx sa.");
+		ret = -EIO;
+		rte_spinlock_lock(&sxe2_sctx->security_lock);
+		sxe2_ipsec_id_free(bmp, index);
+		rte_spinlock_unlock(&sxe2_sctx->security_lock);
+		goto l_end;
+	}
+
+	sxe2_ipsec_session_save(sxe2_sctx, conf, sxe2_sess, tx_sa->hw_sa_id, tx_sa->id);
+
+	PMD_LOG_INFO(DRV, "Add tx sa success, tx sa id: %u, index: %u.",
+		tx_sa->hw_sa_id, tx_sa->id);
+
+l_end:
+	return ret;
+}
+
+static uint16_t
+sxe2_ipsec_tcam_id_find(struct sxe2_ipsec_rx_tcam *rx_tcam,
+			struct rte_security_ipsec_tunnel_param tunnel, uint16_t len)
+{
+	struct sxe2_ipsec_rx_tcam *per = NULL;
+	uint16_t tcam_id = 0XFFFF;
+	uint16_t i       = 0;
+
+	for (i = 0; i < len; i++) {
+		per = &rx_tcam[i];
+		if (per->ip_addr.type == tunnel.type) {
+			if (tunnel.type == RTE_SECURITY_IPSEC_TUNNEL_IPV4 &&
+			per->ip_addr.dst_ipv4 == (uint32_t)tunnel.ipv4.dst_ip.s_addr) {
+				tcam_id = i;
+				goto l_end;
+			}
+			if (tunnel.type == RTE_SECURITY_IPSEC_TUNNEL_IPV6) {
+				if (!memcmp(&tunnel.ipv6, &per->ip_addr.dst_ipv6,
+				sizeof(tunnel.ipv6))) {
+					tcam_id = i;
+					goto l_end;
+				}
+			}
+		}
+	}
+
+l_end:
+	return tcam_id;
+}
+
+static uint16_t
+sxe2_ipsec_group_id_find(struct sxe2_ipsec_rx_udp_group *rx_udp_group,
+			 uint16_t udp_port, uint8_t sport_en, uint8_t dport_en, uint16_t len)
+{
+	struct sxe2_ipsec_rx_udp_group *per = NULL;
+	uint16_t group_id = 0XFFFF;
+	uint16_t i;
+
+	for (i = 0; i < len; i++) {
+		per = &rx_udp_group[i];
+		if (per->udp_port == udp_port && per->sport_en == sport_en &&
+			per->dport_en == dport_en) {
+			group_id = i;
+			goto l_end;
+		}
+	}
+
+l_end:
+	return group_id;
+}
+
+static void
+sxe2_ipsec_rx_sa_fill(struct sxe2_ipsec_rx_sa *rx_sa,
+		      struct rte_security_session_conf *conf)
+{
+	uint8_t *dst = NULL;
+	uint8_t len = 0;
+
+	memcpy(&rx_sa->xform, &conf->ipsec, sizeof(struct rte_security_ipsec_xform));
+
+	if (conf->crypto_xform->next)
+		rx_sa->mode = SXE2_IPSEC_MODE_ENC_AND_AUTH;
+	else
+		rx_sa->mode = SXE2_IPSEC_MODE_ONLY_ENCRYPT;
+
+	if (conf->crypto_xform->type == RTE_CRYPTO_SYM_XFORM_CIPHER) {
+		if (conf->crypto_xform->cipher.algo == SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC)
+			rx_sa->algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+		else
+			rx_sa->algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+	} else {
+		if (conf->crypto_xform->auth.algo == SXE2_RTE_CRYPTO_AUTH_SM3_HMAC)
+			rx_sa->algo = SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC;
+		else
+			rx_sa->algo = SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC;
+	}
+
+	if (conf->crypto_xform->next) {
+		dst = rx_sa->auth_key;
+		len = conf->crypto_xform->auth.key.length;
+		memcpy(dst, conf->crypto_xform->auth.key.data, len);
+
+		dst = rx_sa->enc_key;
+		len = conf->crypto_xform->next->cipher.key.length;
+		memcpy(dst, conf->crypto_xform->next->cipher.key.data, len);
+	} else {
+		dst = rx_sa->enc_key;
+		len = conf->crypto_xform->cipher.key.length;
+		memcpy(dst, conf->crypto_xform->cipher.key.data, len);
+	}
+
+	rx_sa->spi = conf->ipsec.spi;
+}
+
+static int32_t
+sxe2_ipsec_rx_tcam_fill(struct sxe2_security_ctx *sxe2_sctx, uint16_t *tcam_id,
+			struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+	uint16_t len = sxe2_sctx->ipsec_ctx.max_tcam;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+
+	*tcam_id = sxe2_ipsec_tcam_id_find(sxe2_sctx->ipsec_ctx.rx_tcam,
+			conf->ipsec.tunnel, len);
+	if (*tcam_id == 0XFFFF) {
+		*tcam_id = sxe2_ipsec_id_alloc(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp, len);
+		if (*tcam_id == 0xFFFF) {
+			ret = -ENOMEM;
+			goto l_end;
+		}
+		rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[*tcam_id];
+
+		rx_tcam->ip_addr.type = conf->ipsec.tunnel.type;
+		if (rx_tcam->ip_addr.type == RTE_SECURITY_IPSEC_TUNNEL_IPV4) {
+			rx_tcam->ip_addr.dst_ipv4 = (uint32_t)conf->ipsec.tunnel.ipv4.dst_ip.s_addr;
+		} else {
+			memcpy(&rx_tcam->ip_addr.dst_ipv6, &conf->ipsec.tunnel.ipv6.dst_addr,
+				sizeof(rx_tcam->ip_addr.dst_ipv6));
+		}
+	} else {
+		rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[*tcam_id];
+	}
+	rx_tcam->ref_cnt++;
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_rx_udp_group_fill(struct sxe2_security_ctx *sxe2_sctx, uint16_t *udp_group_id,
+			     struct rte_security_session_conf *conf)
+{
+	int32_t ret = -1;
+	uint16_t len = sxe2_sctx->ipsec_ctx.max_udp_group;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = NULL;
+	uint8_t sport_en = 0;
+	uint8_t dport_en = 0;
+	uint16_t udp_port = 0;
+
+	if (!conf->ipsec.options.udp_encap) {
+		ret = 0;
+		goto l_end;
+	}
+
+	if (conf->ipsec.udp.sport) {
+		sport_en = 1;
+		udp_port = conf->ipsec.udp.sport;
+	} else {
+		sport_en = 0;
+	}
+	if (conf->ipsec.udp.dport) {
+		dport_en = 1;
+		udp_port = conf->ipsec.udp.dport;
+	} else {
+		dport_en = 0;
+	}
+
+	*udp_group_id = sxe2_ipsec_group_id_find(sxe2_sctx->ipsec_ctx.rx_udp_group,
+			udp_port, sport_en, dport_en, len);
+	if (*udp_group_id == 0XFFFF) {
+		*udp_group_id = sxe2_ipsec_id_alloc(sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp, len);
+		if (*udp_group_id == 0xFFFF) {
+			ret = -ENOMEM;
+			goto l_end;
+		}
+		rx_udp_group = &sxe2_sctx->ipsec_ctx.rx_udp_group[*udp_group_id];
+		rx_udp_group->sport_en = sport_en;
+		rx_udp_group->dport_en = dport_en;
+		rx_udp_group->udp_port = udp_port;
+	} else {
+		rx_udp_group = &sxe2_sctx->ipsec_ctx.rx_udp_group[*udp_group_id];
+	}
+	rx_udp_group->ref_cnt++;
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_rx_sa_add(struct sxe2_security_ctx *sxe2_sctx,
+		     struct rte_security_session_conf *conf,
+		     struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+	struct sxe2_ipsec_rx_sa *rx_sa     = NULL;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = NULL;
+	struct rte_bitmap *rx_sa_bmp        = sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp;
+	struct rte_bitmap *rx_tcam_bmp      = sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp;
+	uint16_t sa_bits                         = sxe2_sctx->ipsec_ctx.max_rx_sa;
+	uint16_t sa_id                           = 0xFFFF;
+	uint16_t tcam_id                         = 0xFFFF;
+	uint16_t udp_group_id                    = 0xFFFF;
+	int32_t ret                             = -1;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	sa_id = sxe2_ipsec_id_alloc(rx_sa_bmp, sa_bits);
+	if (sa_id == 0xFFFF) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec rx sa index.");
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	rx_sa = &sxe2_sctx->ipsec_ctx.rx_sa[sa_id];
+	sxe2_ipsec_rx_sa_fill(rx_sa, conf);
+
+	ret = sxe2_ipsec_rx_tcam_fill(sxe2_sctx, &tcam_id, conf);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec rx tcam index.");
+		sxe2_ipsec_id_free(rx_sa_bmp, sa_id);
+		goto l_end;
+	}
+	rx_sa->tcam_id = tcam_id;
+	rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[tcam_id];
+
+	ret = sxe2_ipsec_rx_udp_group_fill(sxe2_sctx, &udp_group_id, conf);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to allocate ipsec rx udp group index.");
+		sxe2_ipsec_id_free(rx_sa_bmp, sa_id);
+		sxe2_ipsec_id_free(rx_tcam_bmp, tcam_id);
+		goto l_end;
+	}
+
+	if (udp_group_id != 0XFFFF) {
+		rx_sa->udp_group_id = (uint8_t)udp_group_id;
+		rx_udp_group = &sxe2_sctx->ipsec_ctx.rx_udp_group[udp_group_id];
+	} else {
+		rx_sa->udp_group_id = 0XFF;
+	}
+
+	ret = sxe2_drv_ipsec_rxsa_add(sxe2_sctx->adapter, rx_sa, rx_tcam, rx_udp_group);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Failed to add rx sa.");
+		sxe2_ipsec_id_free(rx_sa_bmp, sa_id);
+		rx_tcam->ref_cnt--;
+		if (rx_tcam->ref_cnt == 0)
+			sxe2_ipsec_id_free(rx_tcam_bmp, tcam_id);
+
+		if (rx_udp_group != NULL) {
+			rx_udp_group->ref_cnt--;
+			if (rx_udp_group->ref_cnt == 0)
+				sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp,
+						   udp_group_id);
+		}
+
+		ret = -EIO;
+		goto l_end;
+	}
+
+	sxe2_ipsec_session_save(sxe2_sctx, conf, sxe2_sess, rx_sa->hw_sa_id, rx_sa->id);
+
+	PMD_LOG_INFO(DRV, "Add rx sa success, rx sa id: %u, rx ip id: %u, group id: %u, index: %u.",
+				rx_sa->hw_sa_id, rx_sa->hw_ip_id, rx_sa->udp_group_id, rx_sa->id);
+
+l_end:
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_hw_table_add(struct sxe2_security_ctx *sxe2_sctx,
+			struct rte_security_session_conf *conf,
+			struct sxe2_security_session *sxe2_sess)
+{
+	int32_t ret = -1;
+
+	switch (conf->ipsec.direction) {
+	case RTE_SECURITY_IPSEC_SA_DIR_EGRESS:
+		ret = sxe2_ipsec_tx_sa_add(sxe2_sctx, conf, sxe2_sess);
+		break;
+	case RTE_SECURITY_IPSEC_SA_DIR_INGRESS:
+		ret = sxe2_ipsec_rx_sa_add(sxe2_sctx, conf, sxe2_sess);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid sa direction.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+int sxe2_ipsec_session_create(void *device,
+			      struct rte_security_session_conf *conf,
+			      struct sxe2_security_session *sxe2_sess)
+{
+	struct rte_eth_dev *eth_dev = (struct rte_eth_dev *)device;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(eth_dev);
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	int32_t ret = -1;
+
+	ret = sxe2_ipsec_session_conf_valid(sxe2_sctx, conf);
+	if (ret) {
+		PMD_LOG_ERR(DRV, "Input ipsec session conf invalid.");
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_hw_table_add(sxe2_sctx, conf, sxe2_sess);
+	if (ret)
+		goto l_end;
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_tx_sa_delete(struct sxe2_security_ctx *sxe2_sctx,
+			struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_tx_sa *tx_sa = NULL;
+	uint16_t sa_id = sxe2_sess->sa.hw_idx;
+	uint16_t sw_sa_id = sxe2_sess->sa.sw_idx;
+	int32_t ret   = -1;
+
+	if (sw_sa_id >= sxe2_sctx->ipsec_ctx.max_tx_sa) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid sw sa id: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	if (!rte_bitmap_get(sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp, sw_sa_id)) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "bitmap not set, index: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	tx_sa = &sxe2_sctx->ipsec_ctx.tx_sa[sw_sa_id];
+
+	if (tx_sa->hw_sa_id != sa_id) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid hw sa id: %u != %u.", sa_id, tx_sa->hw_sa_id);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_ipsec_txsa_delete(sxe2_sctx->adapter, sa_id);
+	if (ret)
+		goto l_end;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_bmp, sw_sa_id);
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_rx_sa_delete(struct sxe2_security_ctx *sxe2_sctx,
+			struct sxe2_security_session *sxe2_sess)
+{
+	struct sxe2_ipsec_rx_udp_group *rx_udp = NULL;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+	struct sxe2_ipsec_rx_sa *rx_sa = NULL;
+	uint16_t sa_id                            = sxe2_sess->sa.hw_idx;
+	uint16_t sw_sa_id                         = sxe2_sess->sa.sw_idx;
+	int32_t ret                              = -1;
+
+	if (sw_sa_id >= sxe2_sctx->ipsec_ctx.max_rx_sa) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid sw sa id: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	if (!rte_bitmap_get(sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp, sw_sa_id)) {
+		ret = 0;
+		PMD_LOG_INFO(DRV, "bitmap not set, id: %u.", sw_sa_id);
+		goto l_end;
+	}
+
+	rx_sa = &sxe2_sctx->ipsec_ctx.rx_sa[sw_sa_id];
+
+	if (rx_sa->hw_sa_id != sa_id) {
+		ret = 0;
+		PMD_LOG_WARN(DRV, "invalid hw sa id: %u != %u.", sa_id, rx_sa->hw_sa_id);
+		goto l_end;
+	}
+
+	ret = sxe2_drv_ipsec_rxsa_delete(sxe2_sctx->adapter, rx_sa);
+	if (ret)
+		goto l_end;
+
+	rte_spinlock_lock(&sxe2_sctx->security_lock);
+	sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_bmp, sw_sa_id);
+
+	rx_tcam = &sxe2_sctx->ipsec_ctx.rx_tcam[rx_sa->tcam_id];
+	rx_tcam->ref_cnt--;
+	if (rx_tcam->ref_cnt == 0)
+		sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_bmp, rx_sa->tcam_id);
+
+	if (rx_sa->udp_group_id == 0xFF) {
+		PMD_LOG_INFO(DRV, "Not need to release udp group resource.");
+		rte_spinlock_unlock(&sxe2_sctx->security_lock);
+		goto l_end;
+	}
+	rx_udp = &sxe2_sctx->ipsec_ctx.rx_udp_group[rx_sa->udp_group_id];
+	rx_udp->ref_cnt--;
+	if (rx_udp->ref_cnt == 0)
+		sxe2_ipsec_id_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_bmp, rx_sa->udp_group_id);
+	rte_spinlock_unlock(&sxe2_sctx->security_lock);
+
+l_end:
+	return ret;
+}
+
+static int32_t
+sxe2_ipsec_hw_table_delete(struct sxe2_security_ctx *sxe2_sctx,
+			   struct sxe2_security_session *sxe2_sess)
+{
+	int32_t ret = -1;
+
+	switch (sxe2_sess->direction) {
+	case RTE_SECURITY_IPSEC_SA_DIR_EGRESS:
+		ret = sxe2_ipsec_tx_sa_delete(sxe2_sctx, sxe2_sess);
+		break;
+	case RTE_SECURITY_IPSEC_SA_DIR_INGRESS:
+		ret = sxe2_ipsec_rx_sa_delete(sxe2_sctx, sxe2_sess);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid sa direction.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+int sxe2_ipsec_session_destroy(void *device, struct rte_security_session *session)
+{
+	struct rte_eth_dev *eth_dev = (struct rte_eth_dev *)device;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(eth_dev);
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+	int32_t ret = -1;
+
+	if (unlikely(sxe2_sess == NULL || sxe2_sess->adapter != adapter)) {
+		PMD_LOG_ERR(DRV, "Invalid device adapter.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_hw_table_delete(sxe2_sctx, sxe2_sess);
+	if (ret) {
+		ret = -EIO;
+		PMD_LOG_ERR(DRV, "Failed to delete ipsec hw tables.");
+		goto l_end;
+	}
+
+	memset(sxe2_sess, 0, sizeof(struct sxe2_security_session));
+
+	PMD_LOG_INFO(DRV, "Delete ipsec session success, sa_id: %u, spi: %u.",
+			sxe2_sess->sa.hw_idx, sxe2_sess->sa.spi);
+
+l_end:
+	return ret;
+}
+
+int sxe2_ipsec_pkt_metadata_set(void *device, struct rte_security_session *session,
+				struct rte_mbuf *m, void *params)
+{
+	struct rte_eth_dev *eth_dev = (struct rte_eth_dev *)device;
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(eth_dev);
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	struct sxe2_ipsec_pkt_metadata *md             = NULL;
+	uint16_t offset                                      = 0;
+	int32_t ret                                         = -1;
+
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+	if (unlikely(sxe2_sess == NULL || sxe2_sess->adapter != adapter)) {
+		PMD_LOG_ERR(DRV, "Invalid parameters.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	offset = ((struct sxe2_ipsec_metadata_params *)params)->esp_header_offset;
+	if (offset <= IPSEC_ESP_OFFSET_MIN || offset >= IPSEC_ESP_OFFSET_MAX) {
+		PMD_LOG_ERR(DRV, "Invalid esp header offset.");
+		ret = -EINVAL;
+		goto l_end;
+	}
+
+	md = RTE_MBUF_DYNFIELD(m, sxe2_sctx->ipsec_ctx.md_offset, struct sxe2_ipsec_pkt_metadata *);
+
+	memcpy(md, &sxe2_sess->pkt_metadata_template, sizeof(struct sxe2_ipsec_pkt_metadata));
+	md->esp_head_offset = offset;
+
+	PMD_LOG_INFO(DRV, "ipsec metadata set, offset:%u, said:%u, mode:%u, algo:%u.", offset,
+		sxe2_sess->pkt_metadata_template.sa_idx, sxe2_sess->pkt_metadata_template.mode,
+		sxe2_sess->pkt_metadata_template.algo);
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+int sxe2_ipsec_pkt_md_offset_get(struct sxe2_adapter *adapter)
+{
+	return adapter->security_ctx.ipsec_ctx.md_offset;
+}
+
+static void sxe2_ipsec_enc_aes_cbc_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+
+	cap->sym.cipher.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC;
+
+	cap->sym.cipher.block_size = SXE2_SECURITY_BLOCK_SIZE_16;
+
+	cap->sym.cipher.key_size.min = SXE2_IPSEC_AES_KEY_MIN;
+	cap->sym.cipher.key_size.max = SXE2_IPSEC_AES_KEY_MAX;
+	cap->sym.cipher.key_size.increment = SXE2_IPSEC_AES_KEY_INC;
+
+	cap->sym.cipher.iv_size.min = SXE2_IPSEC_AES_IV_MIN;
+	cap->sym.cipher.iv_size.max = SXE2_IPSEC_AES_IV_MAX;
+	cap->sym.cipher.iv_size.increment = SXE2_IPSEC_AES_IV_INC;
+
+	cap->sym.cipher.dataunit_set |= RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES;
+}
+
+static void sxe2_ipsec_enc_sm4_cbc_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER;
+
+	cap->sym.cipher.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC;
+
+	cap->sym.cipher.block_size = SXE2_SECURITY_BLOCK_SIZE_16;
+
+	cap->sym.cipher.key_size.min = SXE2_IPSEC_SM4_KEY_MIN;
+	cap->sym.cipher.key_size.max = SXE2_IPSEC_SM4_KEY_MAX;
+	cap->sym.cipher.key_size.increment = SXE2_IPSEC_SM4_KEY_INC;
+
+	cap->sym.cipher.iv_size.min = SXE2_IPSEC_SM4_IV_MIN;
+	cap->sym.cipher.iv_size.max = SXE2_IPSEC_SM4_IV_MAX;
+	cap->sym.cipher.iv_size.increment = SXE2_IPSEC_SM4_IV_INC;
+
+	cap->sym.cipher.dataunit_set |= RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES;
+}
+
+static void sxe2_ipsec_auth_sha_hmac_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH;
+
+	cap->sym.auth.algo = SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC;
+
+	cap->sym.auth.block_size = SXE2_SECURITY_BLOCK_SIZE_64;
+
+	cap->sym.auth.key_size.min = SXE2_IPSEC_SHA_KEY_MIN;
+	cap->sym.auth.key_size.max = SXE2_IPSEC_SHA_KEY_MAX;
+	cap->sym.auth.key_size.increment = SXE2_IPSEC_SHA_KEY_INC;
+
+	cap->sym.auth.iv_size.min = SXE2_IPSEC_SHA_IV_MIN;
+	cap->sym.auth.iv_size.max = SXE2_IPSEC_SHA_IV_MAX;
+	cap->sym.auth.iv_size.increment = SXE2_IPSEC_SHA_IV_INC;
+
+	cap->sym.auth.digest_size.min = SXE2_IPSEC_SHA_DIGEST_MIN;
+	cap->sym.auth.digest_size.max = SXE2_IPSEC_SHA_DIGEST_MAX;
+	cap->sym.auth.digest_size.increment = SXE2_IPSEC_SHA_DIGEST_INC;
+
+	cap->sym.auth.aad_size.min = SXE2_IPSEC_AAD_MIN;
+	cap->sym.auth.aad_size.max = SXE2_IPSEC_AAD_MAX;
+	cap->sym.auth.aad_size.increment = SXE2_IPSEC_AAD_INC;
+}
+
+static void sxe2_ipsec_auth_sm3_hmac_fill(struct rte_cryptodev_capabilities *cap)
+{
+	cap->sym.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH;
+
+	cap->sym.auth.algo = SXE2_RTE_CRYPTO_AUTH_SM3_HMAC;
+
+	cap->sym.auth.block_size = SXE2_SECURITY_BLOCK_SIZE_64;
+
+	cap->sym.auth.key_size.min = SXE2_IPSEC_SM3_KEY_MIN;
+	cap->sym.auth.key_size.max = SXE2_IPSEC_SM3_KEY_MAX;
+	cap->sym.auth.key_size.increment = SXE2_IPSEC_SM3_KEY_INC;
+
+	cap->sym.auth.iv_size.min = SXE2_IPSEC_SM3_IV_MIN;
+	cap->sym.auth.iv_size.max = SXE2_IPSEC_SM3_IV_MAX;
+	cap->sym.auth.iv_size.increment = SXE2_IPSEC_SM3_IV_INC;
+
+	cap->sym.auth.digest_size.min = SXE2_IPSEC_SM3_DIGEST_MIN;
+	cap->sym.auth.digest_size.max = SXE2_IPSEC_SM3_DIGEST_MAX;
+	cap->sym.auth.digest_size.increment = SXE2_IPSEC_SM3_DIGEST_INC;
+
+	cap->sym.auth.aad_size.min = SXE2_IPSEC_AAD_MIN;
+	cap->sym.auth.aad_size.max = SXE2_IPSEC_AAD_MAX;
+	cap->sym.auth.aad_size.increment = SXE2_IPSEC_AAD_INC;
+}
+
+static int32_t
+sxe2_ipsec_capabilities_init(struct sxe2_security_ctx *sxe2_sctx)
+{
+	struct rte_cryptodev_capabilities *capabilities = NULL;
+	struct sxe2_security_capabilities *sxe2_cap   =
+			&sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC];
+	int32_t ret                                         = -1;
+	uint8_t index                                        = 0;
+
+	sxe2_cap->action = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO;
+	sxe2_cap->ipsec.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP;
+	sxe2_cap->ipsec.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL;
+	sxe2_cap->ipsec.options.stats = 1;
+
+	capabilities = rte_zmalloc("security_caps",
+				sizeof(struct rte_cryptodev_capabilities) * SXE2_IPSEC_CAP_MAX, 0);
+	if (capabilities == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	for (index = 0; index < SXE2_IPSEC_CAP_MAX; index++) {
+		capabilities[index].op = RTE_CRYPTO_OP_TYPE_SYMMETRIC;
+		switch (index) {
+		case SXE2_IPSEC_CAP_ENC_AES_CBC:
+			sxe2_ipsec_enc_aes_cbc_fill(&capabilities[index]);
+			break;
+		case SXE2_IPSEC_CAP_ENC_SM4_CBC:
+			sxe2_ipsec_enc_sm4_cbc_fill(&capabilities[index]);
+			break;
+		case SXE2_IPSEC_CAP_AUTH_SHA256_HMAC:
+			sxe2_ipsec_auth_sha_hmac_fill(&capabilities[index]);
+			break;
+		case SXE2_IPSEC_CAP_AUTH_SM3_HMAC:
+			sxe2_ipsec_auth_sm3_hmac_fill(&capabilities[index]);
+			break;
+		default:
+			break;
+		}
+	}
+
+	sxe2_cap->crypto_capabilities = capabilities;
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+static void
+sxe2_ipsec_tx_sa_init(struct sxe2_ipsec_tx_sa *tx_sa, uint16_t len)
+{
+	struct sxe2_ipsec_tx_sa *per = NULL;
+	uint16_t i;
+
+	memset(tx_sa, 0, sizeof(struct sxe2_ipsec_tx_sa) * len);
+	for (i = 0; i < len; i++) {
+		per = &tx_sa[i];
+		per->id = i;
+	}
+}
+
+static void
+sxe2_ipsec_rx_sa_init(struct sxe2_ipsec_rx_sa *rx_sa, uint16_t len)
+{
+	struct sxe2_ipsec_rx_sa *per = NULL;
+	uint16_t i;
+
+	memset(rx_sa, 0, sizeof(struct sxe2_ipsec_rx_sa) * len);
+	for (i = 0; i < len; i++) {
+		per = &rx_sa[i];
+		per->id = i;
+	}
+}
+
+static void
+sxe2_ipsec_rx_tcam_init(struct sxe2_ipsec_rx_tcam *rx_tcam, uint16_t len)
+{
+	struct sxe2_ipsec_rx_tcam *per = NULL;
+	uint16_t i;
+
+	memset(rx_tcam, 0, sizeof(struct sxe2_ipsec_rx_tcam) * len);
+	for (i = 0; i < len; i++) {
+		per = &rx_tcam[i];
+		per->id = i;
+	}
+}
+
+static void
+sxe2_ipsec_rx_udp_group_init(struct sxe2_ipsec_rx_udp_group *rx_udp_group, uint16_t len)
+{
+	struct sxe2_ipsec_rx_udp_group *per = NULL;
+	uint16_t i;
+
+	memset(rx_udp_group, 0, sizeof(struct sxe2_ipsec_rx_udp_group) * len);
+	for (i = 0; i < len; i++) {
+		per = &rx_udp_group[i];
+		per->id = i;
+	}
+}
+
+static int32_t
+sxe2_ipsec_hw_table_init(struct sxe2_security_ctx *sxe2_sctx)
+{
+	struct sxe2_ipsec_tx_sa *tx_sa = NULL;
+	struct sxe2_ipsec_rx_sa *rx_sa = NULL;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = NULL;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = NULL;
+	uint16_t max_tx_sa = sxe2_sctx->ipsec_ctx.max_tx_sa;
+	uint16_t max_rx_sa = sxe2_sctx->ipsec_ctx.max_rx_sa;
+	uint16_t max_tcam  = sxe2_sctx->ipsec_ctx.max_tcam;
+	uint16_t max_udp_group  = sxe2_sctx->ipsec_ctx.max_udp_group;
+	int32_t ret       = -1;
+
+	tx_sa = rte_zmalloc("sxe2_ipsec_tx_sa", sizeof(struct sxe2_ipsec_tx_sa) * max_tx_sa, 0);
+	if (tx_sa == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_tx_sa_init(tx_sa, max_tx_sa);
+	sxe2_sctx->ipsec_ctx.tx_sa = tx_sa;
+
+	rx_sa = rte_zmalloc("sxe2_ipsec_rx_sa", sizeof(struct sxe2_ipsec_rx_sa) * max_rx_sa, 0);
+	if (rx_sa == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_rx_sa_init(rx_sa, max_rx_sa);
+	sxe2_sctx->ipsec_ctx.rx_sa = rx_sa;
+
+	rx_tcam = rte_zmalloc("sxe2_ipsec_rx_tcam",
+				sizeof(struct sxe2_ipsec_rx_tcam) * max_tcam, 0);
+	if (rx_tcam == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_rx_tcam_init(rx_tcam, max_tcam);
+	sxe2_sctx->ipsec_ctx.rx_tcam = rx_tcam;
+
+	rx_udp_group = rte_zmalloc("sxe2_ipsec_rx_udp_group",
+				sizeof(struct sxe2_ipsec_rx_udp_group) * max_udp_group, 0);
+	if (rx_udp_group == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+	sxe2_ipsec_rx_udp_group_init(rx_udp_group, max_udp_group);
+	sxe2_sctx->ipsec_ctx.rx_udp_group = rx_udp_group;
+
+	ret = 0;
+
+l_end:
+	if (ret) {
+		if (tx_sa != NULL) {
+			rte_free(tx_sa);
+			sxe2_sctx->ipsec_ctx.tx_sa = NULL;
+		}
+		if (rx_sa != NULL) {
+			rte_free(rx_sa);
+			sxe2_sctx->ipsec_ctx.rx_sa = NULL;
+		}
+		if (rx_tcam != NULL) {
+			rte_free(rx_tcam);
+			sxe2_sctx->ipsec_ctx.rx_tcam = NULL;
+		}
+		if (rx_udp_group != NULL) {
+			rte_free(rx_udp_group);
+			sxe2_sctx->ipsec_ctx.rx_udp_group = NULL;
+		}
+	}
+	return ret;
+}
+
+int32_t sxe2_ipsec_init(struct sxe2_adapter *adapter)
+{
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_capabilities *sxe2_cap = NULL;
+	int32_t ret                               = -1;
+	struct rte_mbuf_dynfield pkt_md_dynfield = {
+	.name = "sxe2_ipsec_pkt_metadata",
+		.size = sizeof(struct sxe2_ipsec_pkt_metadata),
+		.align = alignof(struct sxe2_ipsec_pkt_metadata)
+	};
+
+	PMD_LOG_INFO(INIT, "Init ipsec.");
+
+	sxe2_sctx->ipsec_ctx.md_offset = rte_mbuf_dynfield_register(&pkt_md_dynfield);
+	if (sxe2_sctx->ipsec_ctx.md_offset < 0) {
+		PMD_LOG_ERR(INIT, "Failed to register ipsec mbuf dynamic field.");
+		ret = -EIO;
+		goto l_end;
+	}
+
+	ret = sxe2_ipsec_capabilities_init(sxe2_sctx);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init ipsec capabilities.");
+		goto l_end;
+	}
+
+	ret = sxe2_drv_ipsec_get_capa(adapter);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to get ipsec capabilities.");
+		goto l_caps_free;
+	}
+
+	ret = sxe2_ipsec_bitmap_init(sxe2_sctx);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init ipsec bitmap.");
+		goto l_caps_free;
+	}
+
+	ret = sxe2_ipsec_hw_table_init(sxe2_sctx);
+	if (ret) {
+		PMD_LOG_ERR(INIT, "Failed to init ipsec hw table.");
+		goto l_bitmap_free;
+	}
+
+	goto l_end;
+
+l_bitmap_free:
+
+	if (sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem = NULL;
+	}
+l_caps_free:
+	sxe2_cap = &sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC];
+	if (sxe2_cap->crypto_capabilities != NULL) {
+		rte_free(sxe2_cap->crypto_capabilities);
+		sxe2_cap->crypto_capabilities = NULL;
+	}
+l_end:
+	return ret;
+}
+
+void sxe2_ipsec_uinit(struct sxe2_adapter *adapter)
+{
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	struct sxe2_security_capabilities *sxe2_cap   =
+			&sxe2_sctx->sxe2_capabilities[SXE2_SECURITY_PROTOCOL_IPSEC];
+	struct sxe2_ipsec_tx_sa *tx_sa = sxe2_sctx->ipsec_ctx.tx_sa;
+	struct sxe2_ipsec_rx_sa *rx_sa = sxe2_sctx->ipsec_ctx.rx_sa;
+	struct sxe2_ipsec_rx_tcam *rx_tcam = sxe2_sctx->ipsec_ctx.rx_tcam;
+	struct sxe2_ipsec_rx_udp_group *rx_udp_group = sxe2_sctx->ipsec_ctx.rx_udp_group;
+
+	PMD_LOG_INFO(INIT, "Uinit ipsec.");
+
+	(void)sxe2_drv_ipsec_resource_clear(adapter);
+
+	if (sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.tx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_sa_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_tcam_mem = NULL;
+	}
+	if (sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem != NULL) {
+		rte_free(sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem);
+		sxe2_sctx->ipsec_ctx.bmp.rx_udp_mem = NULL;
+	}
+
+	if (tx_sa != NULL) {
+		rte_free(tx_sa);
+		sxe2_sctx->ipsec_ctx.tx_sa = NULL;
+	}
+	if (rx_sa != NULL) {
+		rte_free(rx_sa);
+		sxe2_sctx->ipsec_ctx.rx_sa = NULL;
+	}
+	if (rx_tcam != NULL) {
+		rte_free(rx_tcam);
+		sxe2_sctx->ipsec_ctx.rx_tcam = NULL;
+	}
+	if (rx_udp_group != NULL) {
+		rte_free(rx_udp_group);
+		sxe2_sctx->ipsec_ctx.rx_udp_group = NULL;
+	}
+
+	if (sxe2_cap->crypto_capabilities != NULL) {
+		rte_free(sxe2_cap->crypto_capabilities);
+		sxe2_cap->crypto_capabilities = NULL;
+	}
+}
diff --git a/drivers/net/sxe2/sxe2_ipsec.h b/drivers/net/sxe2/sxe2_ipsec.h
new file mode 100644
index 0000000000..02930ddb4f
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_ipsec.h
@@ -0,0 +1,254 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+#ifndef __SXE2_IPSEC_H__
+#define __SXE2_IPSEC_H__
+
+#include <rte_security.h>
+#include <rte_security_driver.h>
+
+struct sxe2_adapter;
+struct sxe2_security_session;
+
+#define        SXE2_IPSEC_AES_KEY_MIN    (32)
+#define        SXE2_IPSEC_AES_KEY_MAX    (32)
+#define        SXE2_IPSEC_AES_KEY_INC    (0)
+
+#define        SXE2_IPSEC_SM4_KEY_MIN    (16)
+#define        SXE2_IPSEC_SM4_KEY_MAX    (16)
+#define        SXE2_IPSEC_SM4_KEY_INC    (0)
+
+#define        SXE2_IPSEC_SHA_KEY_MIN    (32)
+#define        SXE2_IPSEC_SHA_KEY_MAX    (32)
+#define        SXE2_IPSEC_SHA_KEY_INC    (0)
+
+#define        SXE2_IPSEC_SM3_KEY_MIN    (32)
+#define        SXE2_IPSEC_SM3_KEY_MAX    (32)
+#define        SXE2_IPSEC_SM3_KEY_INC    (0)
+
+#define        SXE2_IPSEC_AES_IV_MIN    (16)
+#define        SXE2_IPSEC_AES_IV_MAX    (16)
+#define        SXE2_IPSEC_AES_IV_INC    (0)
+
+#define        SXE2_IPSEC_SM4_IV_MIN    (16)
+#define        SXE2_IPSEC_SM4_IV_MAX    (16)
+#define        SXE2_IPSEC_SM4_IV_INC    (0)
+
+#define        SXE2_IPSEC_SHA_IV_MIN    (0)
+#define        SXE2_IPSEC_SHA_IV_MAX    (32)
+#define        SXE2_IPSEC_SHA_IV_INC    (16)
+
+#define        SXE2_IPSEC_SM3_IV_MIN    (0)
+#define        SXE2_IPSEC_SM3_IV_MAX    (32)
+#define        SXE2_IPSEC_SM3_IV_INC    (16)
+
+#define        SXE2_IPSEC_SHA_DIGEST_MIN    (32)
+#define        SXE2_IPSEC_SHA_DIGEST_MAX    (32)
+#define        SXE2_IPSEC_SHA_DIGEST_INC    (0)
+
+#define        SXE2_IPSEC_SM3_DIGEST_MIN    (32)
+#define        SXE2_IPSEC_SM3_DIGEST_MAX    (32)
+#define        SXE2_IPSEC_SM3_DIGEST_INC    (0)
+
+#define        SXE2_IPSEC_AAD_MIN           (0)
+#define        SXE2_IPSEC_AAD_MAX           (0)
+#define        SXE2_IPSEC_AAD_INC           (0)
+
+#define        SXE2_IPSEC_MAX_KEY_LEN		 (32)
+#define        SXE2_IPSEC_MIN_KEY_LEN       (0)
+
+#define SXE2_IPSEC_OL_FLAGS_IS_TUN    (0x1 << 0)
+#define SXE2_IPSEC_OL_FLAGS_IS_ESP    (0x1 << 1)
+
+#define SXE2_IPSEC_DEFAULT_SA_OFFSET  (0)
+#define SXE2_IPSEC_DEFAULT_SA_LEN     (1024)
+
+#define IPSEC_TX_ENCRYPT    (RTE_BIT32(0))
+#define IPSEC_TX_ENGINE_SM4 (RTE_BIT32(1))
+
+#define IPSEC_RX_VALID      (RTE_BIT32(0))
+#define IPSEC_RX_IPV6       (RTE_BIT32(2))
+#define IPSEC_RX_DECRYPT    (RTE_BIT32(3))
+#define IPSEC_RX_ENGINE_SM4 (RTE_BIT32(4))
+
+#define IPSEC_IPV6_LEN                 (4)
+#define IPSEC_ESP_OFFSET_MIN           (16)
+#define IPSEC_ESP_OFFSET_MAX           (256)
+
+enum sxe2_ipsec_cap {
+	SXE2_IPSEC_CAP_ENC_AES_CBC      = 0,
+	SXE2_IPSEC_CAP_ENC_SM4_CBC      = 1,
+	SXE2_IPSEC_CAP_AUTH_SHA256_HMAC = 2,
+	SXE2_IPSEC_CAP_AUTH_SM3_HMAC    = 3,
+	SXE2_IPSEC_CAP_MAX              = 4,
+};
+
+enum sxe2_ipsec_icv_len {
+	SXE2_IPSEC_ICV_0_BYTES = 0,
+	SXE2_IPSEC_ICV_12_BYTES,
+	SXE2_IPSEC_ICV_16_BYTES,
+	SXE2_IPSEC_ICV_INVALID,
+};
+
+enum sxe2_ipsec_bypass_dir {
+	SXE2_IPSEC_BYPASS_DIR_RX = 0,
+	SXE2_IPSEC_BYPASS_DIR_TX,
+	SXE2_IPSEC_BYPASS_DIR_INVALID,
+};
+
+enum sxe2_ipsec_bypass_status {
+	SXE2_IPSEC_BYPASS_STATUS_DISABLE = 0,
+	SXE2_IPSEC_BYPASS_STATUS_ENABLE,
+	SXE2_IPSEC_BYPASS_STATUS_INVALID,
+};
+
+enum sxe2_ipsec_status {
+	SXE2_IPSEC_ENC_BYPASS = 0,
+	SXE2_IPSEC_ENC_ENABLE,
+	SXE2_IPSEC_ENC_INVALID,
+};
+
+enum sxe2_ipsec_mode {
+	SXE2_IPSEC_MODE_ENC_AND_AUTH = 0,
+	SXE2_IPSEC_MODE_ONLY_ENCRYPT,
+	SXE2_IPSEC_MODE_INVALID,
+};
+
+struct sxe2_ipsec_ip_param {
+	enum rte_security_ipsec_tunnel_type type;
+	union {
+		uint32_t dst_ipv4;
+		uint32_t dst_ipv6[IPSEC_IPV6_LEN];
+	};
+};
+
+enum sxe2_ipsec_algorithm {
+	SXE2_IPSEC_ALGO_AES_CBC_AND_SHA256_128_HMAC = 0,
+	SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC,
+	SXE2_IPSEC_ALGO_INVALID,
+};
+
+struct sxe2_ipsec_pkt_metadata {
+	uint16_t                  sa_idx;
+	uint16_t                  esp_head_offset;
+	uint8_t                   ol_flags;
+	uint8_t                   mode;
+	uint8_t                   algo;
+};
+
+struct sxe2_ipsec_bitmap {
+	struct rte_bitmap *tx_sa_bmp;
+	struct rte_bitmap *rx_sa_bmp;
+	struct rte_bitmap *rx_tcam_bmp;
+	struct rte_bitmap *rx_udp_bmp;
+	void *tx_sa_mem;
+	void *rx_sa_mem;
+	void *rx_tcam_mem;
+	void *rx_udp_mem;
+};
+
+struct sxe2_ipsec_security_sa {
+	uint32_t spi;
+	uint16_t hw_idx;
+	uint16_t sw_idx;
+};
+
+struct sxe2_ipsec_esn {
+	union {
+		uint64_t value;
+		struct {
+			uint32_t hi;
+			uint32_t low;
+		};
+	};
+	uint8_t enabled;
+};
+
+struct sxe2_ipsec_udp {
+	struct rte_security_ipsec_udp_param value;
+	uint8_t enabled;
+};
+
+struct sxe2_ipsec_tx_sa {
+	struct rte_security_ipsec_xform xform;
+	uint16_t id;
+	uint16_t hw_sa_id;
+	enum sxe2_ipsec_mode mode;
+	enum sxe2_ipsec_algorithm algo;
+	uint8_t enc_key[SXE2_IPSEC_MAX_KEY_LEN];
+	uint8_t auth_key[SXE2_IPSEC_MAX_KEY_LEN];
+};
+
+struct sxe2_ipsec_rx_sa {
+	struct rte_security_ipsec_xform xform;
+	uint32_t spi;
+	uint16_t id;
+	uint16_t hw_sa_id;
+	uint8_t hw_ip_id;
+	uint8_t hw_udp_group_id;
+	uint8_t tcam_id;
+	uint8_t udp_group_id;
+	uint8_t sdn_group_id;
+	enum sxe2_ipsec_mode mode;
+	enum sxe2_ipsec_algorithm algo;
+	uint8_t enc_key[SXE2_IPSEC_MAX_KEY_LEN];
+	uint8_t auth_key[SXE2_IPSEC_MAX_KEY_LEN];
+};
+
+struct sxe2_ipsec_rx_tcam {
+	struct sxe2_ipsec_ip_param ip_addr;
+	uint16_t id;
+	uint8_t hw_ip_id;
+	uint8_t ref_cnt;
+};
+
+struct sxe2_ipsec_rx_udp_group {
+	uint16_t udp_port;
+	uint8_t sport_en;
+	uint8_t dport_en;
+	uint8_t id;
+	uint8_t hw_group_id;
+	uint8_t ref_cnt;
+};
+
+struct sxe2_ipsec_ctx {
+	struct sxe2_ipsec_tx_sa             *tx_sa;
+	struct sxe2_ipsec_rx_sa             *rx_sa;
+	struct sxe2_ipsec_rx_tcam           *rx_tcam;
+	struct sxe2_ipsec_rx_udp_group      *rx_udp_group;
+	struct sxe2_ipsec_bitmap            bmp;
+	int                                  md_offset;
+	uint16_t                                  max_tx_sa;
+	uint16_t                                  max_rx_sa;
+	uint16_t                                  max_tcam;
+	uint8_t                                   max_udp_group;
+};
+
+struct sxe2_ipsec_metadata_params {
+	uint16_t esp_header_offset;
+	uint16_t reserved;
+};
+
+bool sxe2_ipsec_supported(struct sxe2_adapter *adapter);
+
+bool sxe2_ipsec_valid_tx_offloads(uint64_t offloads);
+
+bool sxe2_ipsec_valid_rx_offloads(uint64_t offloads);
+
+int sxe2_ipsec_pkt_md_offset_get(struct sxe2_adapter *adapter);
+
+int sxe2_ipsec_session_create(void *device,
+			      struct rte_security_session_conf *conf,
+			      struct sxe2_security_session *sxe2_sess);
+
+int sxe2_ipsec_session_destroy(void *device,
+		struct rte_security_session *session);
+
+int sxe2_ipsec_pkt_metadata_set(void *device, struct rte_security_session *session,
+				struct rte_mbuf *m, void *params);
+
+int32_t sxe2_ipsec_init(struct sxe2_adapter *adapter);
+
+void sxe2_ipsec_uinit(struct sxe2_adapter *adapter);
+
+#endif /* __SXE2_IPSEC_H__ */
diff --git a/drivers/net/sxe2/sxe2_rx.c b/drivers/net/sxe2/sxe2_rx.c
index 28832d5f71..007192c7d8 100644
--- a/drivers/net/sxe2/sxe2_rx.c
+++ b/drivers/net/sxe2/sxe2_rx.c
@@ -294,6 +294,11 @@ int32_t __rte_cold sxe2_rx_queue_setup(struct rte_eth_dev *dev,
 		goto l_end;
 	}
 
+	if (!sxe2_ipsec_valid_rx_offloads(offloads)) {
+		ret = -EINVAL;
+		goto l_end;
+	}
+
 	rxq = sxe2_rx_queue_alloc(dev, queue_idx, nb_desc, socket_id);
 	if (rxq == NULL) {
 		PMD_LOG_ERR(RX, "rx queue[%d] resource alloc failed", queue_idx);
diff --git a/drivers/net/sxe2/sxe2_security.c b/drivers/net/sxe2/sxe2_security.c
new file mode 100644
index 0000000000..bc59d1b880
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_security.c
@@ -0,0 +1,335 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#include <rte_malloc.h>
+
+#include "sxe2_ethdev.h"
+#include "sxe2_security.h"
+#include "sxe2_ipsec.h"
+#include "sxe2_common_log.h"
+
+static unsigned int
+sxe2_security_session_size_get(void *device __rte_unused)
+{
+	return sizeof(struct sxe2_security_session);
+}
+
+static int
+sxe2_security_session_create(void *device,
+			     struct rte_security_session_conf *conf,
+			     struct rte_security_session *session)
+{
+	int32_t ret = -1;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+
+	switch (conf->protocol) {
+	case RTE_SECURITY_PROTOCOL_IPSEC:
+		ret = sxe2_ipsec_session_create(device, conf, sxe2_sess);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid security protocol.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int
+sxe2_security_session_destroy(void *device, struct rte_security_session *session)
+{
+	int32_t ret = -1;
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+
+	switch (sxe2_sess->protocol) {
+	case RTE_SECURITY_PROTOCOL_IPSEC:
+		ret = sxe2_ipsec_session_destroy(device, session);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid security protocol.");
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int
+sxe2_security_pkt_metadata_set(void *device,
+			       struct rte_security_session *session,
+			       struct rte_mbuf *m, void *params)
+{
+	struct sxe2_security_session *sxe2_sess = NULL;
+	sxe2_sess = SECURITY_GET_SESS_PRIV(session);
+	int32_t ret = -1;
+
+	switch (sxe2_sess->protocol) {
+	case RTE_SECURITY_PROTOCOL_IPSEC:
+		ret = sxe2_ipsec_pkt_metadata_set(device, session, m, params);
+		break;
+	default:
+		PMD_LOG_ERR(DRV, "Invalid security protocol.");
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct rte_security_capability *
+sxe2_security_capabilities_get(void *device __rte_unused)
+{
+	static const struct rte_cryptodev_capabilities
+	ipsec_crypto_capabilities[] = {
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER,
+				{.cipher = {
+					.algo = SXE2_RTE_CRYPTO_CIPHER_AES_CBC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_16,
+					.key_size = {
+						.min = SXE2_IPSEC_AES_KEY_MIN,
+						.max = SXE2_IPSEC_AES_KEY_MAX,
+						.increment = SXE2_IPSEC_AES_KEY_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_AES_IV_MIN,
+						.max = SXE2_IPSEC_AES_IV_MAX,
+						.increment = SXE2_IPSEC_AES_IV_INC
+					},
+					.dataunit_set = RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES,
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_CIPHER,
+				{.cipher = {
+					.algo = SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_16,
+					.key_size = {
+						.min = SXE2_IPSEC_SM4_KEY_MIN,
+						.max = SXE2_IPSEC_SM4_KEY_MAX,
+						.increment = SXE2_IPSEC_SM4_KEY_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_SM4_IV_MIN,
+						.max = SXE2_IPSEC_SM4_IV_MAX,
+						.increment = SXE2_IPSEC_SM4_IV_INC
+					},
+					.dataunit_set = RTE_CRYPTO_CIPHER_DATA_UNIT_LEN_512_BYTES,
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH,
+				{.auth = {
+					.algo = SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_64,
+					.key_size = {
+						.min = SXE2_IPSEC_SHA_KEY_MIN,
+						.max = SXE2_IPSEC_SHA_KEY_MAX,
+						.increment = SXE2_IPSEC_SHA_KEY_INC
+					},
+					.digest_size = {
+						.min = SXE2_IPSEC_SHA_DIGEST_MIN,
+						.max = SXE2_IPSEC_SHA_DIGEST_MAX,
+						.increment = SXE2_IPSEC_SHA_DIGEST_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_SHA_IV_MIN,
+						.max = SXE2_IPSEC_SHA_IV_MAX,
+						.increment = SXE2_IPSEC_SHA_IV_INC
+					},
+					.aad_size = {
+						.min = SXE2_IPSEC_AAD_MIN,
+						.max = SXE2_IPSEC_AAD_MAX,
+						.increment = SXE2_IPSEC_AAD_INC
+					}
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_SYMMETRIC,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_AUTH,
+				{.auth = {
+					.algo = SXE2_RTE_CRYPTO_AUTH_SM3_HMAC,
+					.block_size = SXE2_SECURITY_BLOCK_SIZE_64,
+					.key_size = {
+						.min = SXE2_IPSEC_SM3_KEY_MIN,
+						.max = SXE2_IPSEC_SM3_KEY_MAX,
+						.increment = SXE2_IPSEC_SM3_KEY_INC
+					},
+					.digest_size = {
+						.min = SXE2_IPSEC_SM3_DIGEST_MIN,
+						.max = SXE2_IPSEC_SM3_DIGEST_MAX,
+						.increment = SXE2_IPSEC_SM3_DIGEST_INC
+					},
+					.iv_size = {
+						.min = SXE2_IPSEC_SM3_IV_MIN,
+						.max = SXE2_IPSEC_SM3_IV_MAX,
+						.increment = SXE2_IPSEC_SM3_IV_INC
+					},
+					.aad_size = {
+						.min = SXE2_IPSEC_AAD_MIN,
+						.max = SXE2_IPSEC_AAD_MAX,
+						.increment = SXE2_IPSEC_AAD_INC
+					}
+				}, }
+			}, }
+		},
+		{
+			.op = RTE_CRYPTO_OP_TYPE_UNDEFINED,
+			{.sym = {
+				.xform_type = RTE_CRYPTO_SYM_XFORM_NOT_SPECIFIED
+			}, }
+		}
+	};
+
+	static const struct rte_security_capability
+	sxe2_security_capabilities[] = {
+		{
+			.action = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO,
+			.protocol = RTE_SECURITY_PROTOCOL_IPSEC,
+			{.ipsec = {
+				.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP,
+				.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL,
+				.direction = RTE_SECURITY_IPSEC_SA_DIR_EGRESS,
+				.options = {
+					.esn = 0,
+					.udp_encap = 1,
+					.copy_dscp = 0,
+					.copy_flabel = 0,
+					.copy_df = 0,
+					.dec_ttl = 0,
+					.ecn = 0,
+					.stats = 1,
+					.iv_gen_disable = 0,
+					.tunnel_hdr_verify = 1,
+					.udp_ports_verify = 1,
+					.ip_csum_enable = 0,
+					.l4_csum_enable = 0,
+					.ip_reassembly_en = 0,
+					.ingress_oop = 0
+			} } },
+			.crypto_capabilities = ipsec_crypto_capabilities,
+			.ol_flags = RTE_SECURITY_TX_OLOAD_NEED_MDATA
+		},
+		{
+			.action = RTE_SECURITY_ACTION_TYPE_INLINE_CRYPTO,
+			.protocol = RTE_SECURITY_PROTOCOL_IPSEC,
+			{.ipsec = {
+				.proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP,
+				.mode = RTE_SECURITY_IPSEC_SA_MODE_TUNNEL,
+				.direction = RTE_SECURITY_IPSEC_SA_DIR_INGRESS,
+				.options = {
+					.esn = 0,
+					.udp_encap = 1,
+					.copy_dscp = 0,
+					.copy_flabel = 0,
+					.copy_df = 0,
+					.dec_ttl = 0,
+					.ecn = 0,
+					.stats = 1,
+					.iv_gen_disable = 0,
+					.tunnel_hdr_verify = 1,
+					.udp_ports_verify = 1,
+					.ip_csum_enable = 0,
+					.l4_csum_enable = 0,
+					.ip_reassembly_en = 0,
+					.ingress_oop = 0
+			} } },
+			.crypto_capabilities = ipsec_crypto_capabilities,
+			.ol_flags = 0
+		},
+		{
+			.action = RTE_SECURITY_ACTION_TYPE_NONE
+		}
+	};
+
+	return sxe2_security_capabilities;
+}
+
+static struct rte_security_ops sxe2_security_ops = {
+	.session_get_size		= sxe2_security_session_size_get,
+	.session_create			= sxe2_security_session_create,
+	.session_destroy		= sxe2_security_session_destroy,
+	.set_pkt_metadata		= sxe2_security_pkt_metadata_set,
+	.capabilities_get		= sxe2_security_capabilities_get,
+};
+
+int32_t sxe2_security_init(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_security_ctx *sctx = NULL;
+	struct sxe2_security_ctx *sxe2_sctx = &adapter->security_ctx;
+	int32_t ret = -1;
+
+	if (!sxe2_ipsec_supported(adapter)) {
+		ret = 0;
+		PMD_LOG_INFO(INIT, "Not support security feature.");
+		goto l_end;
+	}
+
+	PMD_LOG_INFO(INIT, "Init security feature.");
+
+	sctx = rte_zmalloc("security_ctx", sizeof(struct rte_security_ctx), 0);
+	if (sctx == NULL) {
+		ret = -ENOMEM;
+		goto l_end;
+	}
+
+	sctx->device = dev;
+	sctx->ops = &sxe2_security_ops;
+	sctx->sess_cnt = 0;
+	sctx->flags = 0;
+	dev->security_ctx = (void *)sctx;
+
+	rte_spinlock_init(&sxe2_sctx->security_lock);
+	sxe2_sctx->adapter = adapter;
+
+	if (sxe2_ipsec_supported(adapter)) {
+		ret = sxe2_ipsec_init(adapter);
+		if (ret) {
+			rte_free(sctx);
+			sctx = NULL;
+			dev->security_ctx = NULL;
+			goto l_end;
+		}
+	}
+
+	ret = 0;
+
+l_end:
+	return ret;
+}
+
+void sxe2_security_uinit(struct rte_eth_dev *dev)
+{
+	struct sxe2_adapter *adapter = SXE2_DEV_PRIVATE_TO_ADAPTER(dev);
+	struct rte_security_ctx *sctx = dev->security_ctx;
+
+	if (!sxe2_ipsec_supported(adapter)) {
+		PMD_LOG_INFO(INIT, "Not support security feature.");
+		goto l_end;
+	}
+
+	PMD_LOG_INFO(INIT, "Uinit security feature.");
+
+	if (sctx != NULL) {
+		rte_free(sctx);
+		sctx = NULL;
+	}
+
+	sxe2_ipsec_uinit(adapter);
+
+l_end:
+	return;
+}
diff --git a/drivers/net/sxe2/sxe2_security.h b/drivers/net/sxe2/sxe2_security.h
new file mode 100644
index 0000000000..366c0614bd
--- /dev/null
+++ b/drivers/net/sxe2/sxe2_security.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (C), 2025, Wuxi Stars Micro System Technologies Co., Ltd.
+ */
+
+#ifndef __SXE2_SECURITY_H__
+#define __SXE2_SECURITY_H__
+
+#include <rte_security.h>
+#include <rte_cryptodev.h>
+#include <rte_security_driver.h>
+
+#include "sxe2_ipsec.h"
+
+#define SXE2_DEV_TO_SECURITY(eth) \
+	((struct rte_security_ctx *)(((struct rte_eth_dev *)eth)->security_ctx))
+
+#define SXE2_RTE_CRYPTO_CIPHER_AES_CBC   (RTE_CRYPTO_CIPHER_AES_CBC)
+
+#define SXE2_RTE_RTE_CRYPTO_CIPHER_SM4_CBC   (RTE_CRYPTO_CIPHER_SM4_CBC)
+
+#define SXE2_RTE_CRYPTO_AUTH_SHA256_HMAC  (RTE_CRYPTO_AUTH_SHA256_HMAC)
+
+#define SXE2_RTE_CRYPTO_AUTH_SM3_HMAC   (RTE_CRYPTO_AUTH_SM3_HMAC)
+
+enum sxe2_security_protocol {
+	SXE2_SECURITY_PROTOCOL_IPSEC       = 0,
+	SXE2_SECURITY_PROTOCOL_MAX         = 1,
+};
+
+enum sxe2_security_xform {
+	SXE2_SECURITY_IPSEC_EN       = 0,
+	SXE2_SECURITY_IPSEC_DE       = 1,
+	SXE2_SECURITY_NUM_MAX        = 2,
+};
+
+enum sxe2_security_block_size {
+	SXE2_SECURITY_BLOCK_SIZE_16        = 16,
+	SXE2_SECURITY_BLOCK_SIZE_64        = 64,
+};
+
+struct sxe2_security_ipsec_caps {
+	enum rte_security_ipsec_sa_protocol   proto;
+	enum rte_security_ipsec_sa_mode       mode;
+	struct rte_security_ipsec_sa_options  options;
+};
+
+struct sxe2_security_capabilities {
+	struct rte_cryptodev_capabilities     *crypto_capabilities;
+	enum rte_security_session_action_type action;
+	struct sxe2_security_ipsec_caps      ipsec;
+};
+
+struct sxe2_security_session {
+	struct sxe2_adapter                   *adapter;
+	struct sxe2_ipsec_pkt_metadata        pkt_metadata_template;
+	struct sxe2_ipsec_security_sa         sa;
+	struct sxe2_ipsec_esn                 esn;
+	struct sxe2_ipsec_udp                 udp_cap;
+	enum rte_security_session_protocol     protocol;
+	enum rte_security_ipsec_sa_direction   direction;
+	enum rte_security_ipsec_sa_mode        mode;
+	enum rte_security_ipsec_sa_protocol    sa_proto;
+	enum rte_security_ipsec_tunnel_type    type;
+};
+
+struct sxe2_security_ctx {
+	struct sxe2_adapter                 *adapter;
+	struct sxe2_security_capabilities   sxe2_capabilities[SXE2_SECURITY_PROTOCOL_MAX];
+	struct sxe2_ipsec_ctx               ipsec_ctx;
+	rte_spinlock_t                       security_lock;
+};
+
+int32_t sxe2_security_init(struct rte_eth_dev *dev);
+
+void sxe2_security_uinit(struct rte_eth_dev *dev);
+
+#endif /* __SXE2_SECURITY_H__ */
diff --git a/drivers/net/sxe2/sxe2_tx.c b/drivers/net/sxe2/sxe2_tx.c
index a280edc9c5..f49238ceef 100644
--- a/drivers/net/sxe2/sxe2_tx.c
+++ b/drivers/net/sxe2/sxe2_tx.c
@@ -304,6 +304,11 @@ int32_t __rte_cold sxe2_tx_queue_setup(struct rte_eth_dev *dev,
 	}
 
 	offloads = tx_conf->offloads | dev->data->dev_conf.txmode.offloads;
+	if (!sxe2_ipsec_valid_tx_offloads(offloads)) {
+		ret = -EINVAL;
+		goto end;
+	}
+
 	txq = sxe2_tx_queue_alloc(dev, queue_idx, nb_desc, socket_id);
 	if (txq == NULL) {
 		PMD_LOG_ERR(TX, "failed to alloc sxe2vf tx queue:%u resource", queue_idx);
@@ -327,6 +332,9 @@ int32_t __rte_cold sxe2_tx_queue_setup(struct rte_eth_dev *dev,
 	txq->ops               = sxe2_tx_default_ops_get();
 	txq->ops.queue_reset(txq);
 
+	if (sxe2_ipsec_supported(adapter) && txq->offloads & RTE_ETH_TX_OFFLOAD_SECURITY)
+		txq->ipsec_pkt_md_offset = sxe2_ipsec_pkt_md_offset_get(adapter);
+
 	dev->data->tx_queues[queue_idx] = txq;
 	ret = 0;
 
diff --git a/drivers/net/sxe2/sxe2_txrx_poll.c b/drivers/net/sxe2/sxe2_txrx_poll.c
index 3c6fe37404..8b6e585c36 100644
--- a/drivers/net/sxe2/sxe2_txrx_poll.c
+++ b/drivers/net/sxe2/sxe2_txrx_poll.c
@@ -307,6 +307,25 @@ static __rte_always_inline void sxe2_desc_tso_fill(struct rte_mbuf *tx_pkt,
 	return;
 }
 
+static __rte_always_inline void sxe2_desc_ipsec_fill(struct rte_mbuf *tx_pkt,
+			struct sxe2_tx_queue *txq, uint16_t *ipsec_offset,
+			uint64_t *desc_type_cmd_tso_mss)
+{
+	struct sxe2_ipsec_pkt_metadata *md = NULL;
+	uint16_t ipsec_pkt_md_offset = txq->ipsec_pkt_md_offset;
+
+	md = RTE_MBUF_DYNFIELD(tx_pkt, ipsec_pkt_md_offset, struct sxe2_ipsec_pkt_metadata *);
+	*ipsec_offset = md->esp_head_offset;
+	*desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_IPSEC_EN;
+	if (md->mode == SXE2_IPSEC_MODE_ONLY_ENCRYPT)
+		*desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_IPSEC_MODE;
+
+	if (md->algo == SXE2_IPSEC_ALGO_SM4_CBC_AND_SM3_96_HMAC)
+		*desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_IPSEC_ENGINE;
+
+	*desc_type_cmd_tso_mss |= (uint64_t)(md->sa_idx) << SXE2_TX_CTXT_DESC_IPSEC_SA_SHIFT;
+}
+
 static __rte_always_inline uint64_t
 sxe2_tx_data_desc_build_cobt(uint32_t cmd, uint32_t offset, uint16_t buf_size, uint16_t l2tag)
 {
@@ -426,6 +445,11 @@ uint16_t sxe2_tx_pkts(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt
 			else if (offloads & RTE_MBUF_F_TX_IEEE1588_TMST)
 				desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_CMD_TSYN_MASK;
 
+			if (offloads & RTE_MBUF_F_TX_SEC_OFFLOAD) {
+				sxe2_desc_ipsec_fill(tx_pkt, txq, &ipsec_offset,
+						     &desc_type_cmd_tso_mss);
+			}
+
 			if (offloads & RTE_MBUF_F_TX_QINQ) {
 				desc_l2tag2 = tx_pkt->vlan_tci_outer;
 				desc_type_cmd_tso_mss |= SXE2_TX_CTXT_DESC_CMD_IL2TAG2_MASK;
@@ -786,6 +810,36 @@ static inline void sxe2_rx_desc_ptp_para_fill(struct sxe2_rx_queue *rxq,
 			     rxq->ts_low);
 	}
 }
+
+static inline void sxe2_rx_desc_ipsec_para_fill(struct sxe2_rx_queue *rxq __rte_unused,
+		struct rte_mbuf *mbuf, union sxe2_rx_desc *desc)
+{
+	uint32_t status_lrocnt_fdpf_id = rte_le_to_cpu_32(desc->wb.status_lrocnt_fdpf_id);
+	enum sxe2_rx_desc_ipsec_status ipsec_status;
+
+	if (status_lrocnt_fdpf_id & SXE2_RX_DESC_IPSEC_PKT_MASK) {
+		mbuf->ol_flags |= RTE_MBUF_F_RX_SEC_OFFLOAD;
+		ipsec_status = SXE2_RX_DESC_IPSEC_STATUS_VAL_GET(status_lrocnt_fdpf_id);
+		switch (ipsec_status) {
+		case SXE2_RX_DESC_IPSEC_STATUS_SUCCESS:
+			break;
+		case SXE2_RX_DESC_IPSEC_STATUS_PKG_OVER_2K:
+		case SXE2_RX_DESC_IPSEC_STATUS_SPI_IP_INVALID:
+		case SXE2_RX_DESC_IPSEC_STATUS_SA_INVALID:
+		case SXE2_RX_DESC_IPSEC_STATUS_NOT_ALIGN:
+		case SXE2_RX_DESC_IPSEC_STATUS_ICV_ERROR:
+		case SXE2_RX_DESC_IPSEC_STATUS_BY_PASSH:
+		case SXE2_RX_DESC_IPSEC_STATUS_MAC_BY_PASSH:
+			PMD_LOG_INFO(RX, "IPsec status error:%d", ipsec_status);
+			mbuf->ol_flags |= RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED;
+			break;
+		default:
+			PMD_LOG_INFO(RX, "Invalid ipsec status:%d", ipsec_status);
+			mbuf->ol_flags |= RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED;
+			break;
+		}
+	}
+}
 #endif
 
 static __rte_always_inline void
@@ -803,6 +857,7 @@ sxe2_rx_mbuf_common_fields_fill(struct sxe2_rx_queue *rxq, struct rte_mbuf *mbuf
 	sxe2_rx_desc_vlan_para_fill(mbuf, rxd);
 	sxe2_rx_desc_filter_para_fill(rxq, mbuf, rxd);
 #ifndef RTE_LIBRTE_SXE2_16BYTE_RX_DESC
+	sxe2_rx_desc_ipsec_para_fill(rxq, mbuf, rxd);
 	sxe2_rx_desc_ptp_para_fill(rxq, mbuf, rxd);
 #endif
 
-- 
2.52.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox