* [TEST PATCH v9] eal/x86: optimize memcpy of small sizes
From: Morten Brørup @ 2026-05-21 10:54 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
In-Reply-To: <20260429103548.220354-1-mb@smartsharesystems.com>
TEST: Were the Intel drivers the only ones triggering the warnings with mingw in Github?
Depends-on: series-38174 ("remove use of rte_memcpy from net/intel")
Venlig hilsen / Kind regards,
-Morten Brørup
The implementation for copying up to 64 bytes does not depend on address
alignment with the size of the CPU's vector registers. Nonetheless, the
exact same code for copying up to 64 bytes was present in both the aligned
copy function and all the CPU vector register size specific variants of
the unaligned copy functions.
With this patch, the implementation for copying up to 64 bytes was
consolidated into one instance, located in the common copy function,
before checking alignment requirements.
This provides three benefits:
1. No copy-paste in the source code.
2. A performance gain for copying up to 64 bytes, because the
address alignment check is avoided in this case.
3. Reduced instruction memory footprint, because the compiler only
generates one instance of the function for copying up to 64 bytes, instead
of two instances (one in the unaligned copy function, and one in the
aligned copy function).
Furthermore, __rte_restrict was added to source and destination addresses.
And finally, the missing implementation of rte_mov48() was added.
Regarding performance...
The memcpy performance test (cache-to-cache copy) shows:
Copying up to 15 bytes takes ca. 4.5 cycles, versus ca. 6.5 cycles before.
Copying 8 bytes takes 4 cycles, versus 7 cycles before.
Copying 16 bytes takes 2 cycles, versus 4 cycles before.
Copying 64 bytes takes 4 cycles, versus 7 cycles before.
Signed-off-by: Morten Brørup <mb@smartsharesystems.com>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
---
v9:
* Removed new functions rte_mov16_to_32() and rte_mov32_to_64(), and moved
their implementations into rte_memcpy() instead.
There is no need for such public functions, and having them separate did
not improve source code readability.
* Kept acks from Bruce and Konstantin (both given to v7).
v8:
* Reverted the first branch from size <= 16 back to size < 16, restored
the original rte_mov15_or_less() function, and removed the new
rte_mov16_or_less() function.
When rte_memcpy() is used for copying an array of pointers, and the
number of pointers to copy is low (size <= 64 bytes), it is more likely
that the number of pointers to copy is 1 than 2.
The rte_mov15_or_less() implementation handles copying 8 bytes more
efficiently than the rte_mov16_or_less() implementation, which copied
the 8-byte pointer twice.
Also note that with rte_mov15_or_less(), the compiler can optimize away
the branches handling n & 1, n & 2 and n & 4 when it is known at compile
time that (8-byte) pointers are being copied. (For 32-bit architecture,
the n & 4 will not be optimized away when copying pointers.)
This reversion also makes the patch less revolutionary and more
incremental.
* Removed a lot of code for handling compile time known sizes. (Bruce)
The rte_memcpy() function should not be used for small copies with
compile time known sizes, so handling it is considered superfluous.
Removing it improves source code readability. And reduces the size of
the patch.
* Kept acks from Bruce and Konstantin (both given to v7).
v7:
* Updated patch description. Mainly to clarify that the changes related to
copying up to 64 bytes simply replaces multiple instances of copy-pasted
code with one common instance.
* Fixed copy of compile time known 16 bytes in rte_mov17_to_32(). (Vipin)
* Rebased.
v6:
* Went back to using rte_uintN_alias structures for copying instead of
using memcpy(). They were there for a reason.
(Inspired by the discussion about optimizing the checksum function.)
* Removed note about copying uninitialized data.
* Added __rte_restrict to source and destination addresses.
Updated function descriptions from "should" to "must" not overlap.
* Changed rte_mov48() AVX implementation to copy 32+16 bytes instead of
copying 32 + 32 overlapping bytes. (Konstantin)
* Ignoring "-Wstringop-overflow" is not needed, so it was removed.
v5:
* Reverted v4: Replace SSE2 _mm_loadu_si128() with SSE3 _mm_lddqu_si128().
It was slower.
* Improved some comments. (Konstantin Ananyev)
* Moved the size range 17..32 inside the size <= 64 branch, so when
building for SSE, the generated code can start copying the first
16 bytes before comparing if the size is greater than 32 or not.
* Just require RTE_MEMCPY_AVX for using rte_mov32() in rte_mov33_to_64().
v4:
* Replace SSE2 _mm_loadu_si128() with SSE3 _mm_lddqu_si128().
v3:
* Fixed typo in comment.
v2:
* Updated patch title to reflect that the performance is improved.
* Use the design pattern of two overlapping stores for small copies too.
* Expanded first branch from size < 16 to size <= 16.
* Handle more compile time constant copy sizes.
---
lib/eal/x86/include/rte_memcpy.h | 250 +++++++++++++------------------
1 file changed, 102 insertions(+), 148 deletions(-)
diff --git a/lib/eal/x86/include/rte_memcpy.h b/lib/eal/x86/include/rte_memcpy.h
index 46d34b8081..8ed8c55010 100644
--- a/lib/eal/x86/include/rte_memcpy.h
+++ b/lib/eal/x86/include/rte_memcpy.h
@@ -22,11 +22,6 @@
extern "C" {
#endif
-#if defined(RTE_TOOLCHAIN_GCC) && (GCC_VERSION >= 100000)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstringop-overflow"
-#endif
-
/*
* GCC older than version 11 doesn't compile AVX properly, so use SSE instead.
* There are no problems with AVX2.
@@ -40,9 +35,6 @@ extern "C" {
/**
* Copy bytes from one location to another. The locations must not overlap.
*
- * @note This is implemented as a macro, so it's address should not be taken
- * and care is needed as parameter expressions may be evaluated multiple times.
- *
* @param dst
* Pointer to the destination of the data.
* @param src
@@ -53,15 +45,15 @@ extern "C" {
* Pointer to the destination data.
*/
static __rte_always_inline void *
-rte_memcpy(void *dst, const void *src, size_t n);
+rte_memcpy(void *__rte_restrict dst, const void *__rte_restrict src, size_t n);
/**
* Copy bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
* Use with n <= 15.
*/
static __rte_always_inline void *
-rte_mov15_or_less(void *dst, const void *src, size_t n)
+rte_mov15_or_less(void *__rte_restrict dst, const void *__rte_restrict src, size_t n)
{
/**
* Use the following structs to avoid violating C standard
@@ -103,10 +95,10 @@ rte_mov15_or_less(void *dst, const void *src, size_t n)
/**
* Copy 16 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov16(uint8_t *dst, const uint8_t *src)
+rte_mov16(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
{
__m128i xmm0;
@@ -116,10 +108,10 @@ rte_mov16(uint8_t *dst, const uint8_t *src)
/**
* Copy 32 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov32(uint8_t *dst, const uint8_t *src)
+rte_mov32(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
{
#if defined RTE_MEMCPY_AVX
__m256i ymm0;
@@ -132,12 +124,29 @@ rte_mov32(uint8_t *dst, const uint8_t *src)
#endif
}
+/**
+ * Copy 48 bytes from one location to another,
+ * locations must not overlap.
+ */
+static __rte_always_inline void
+rte_mov48(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
+{
+#if defined RTE_MEMCPY_AVX
+ rte_mov32((uint8_t *)dst, (const uint8_t *)src);
+ rte_mov16((uint8_t *)dst + 32, (const uint8_t *)src + 32);
+#else /* SSE implementation */
+ rte_mov16((uint8_t *)dst + 0 * 16, (const uint8_t *)src + 0 * 16);
+ rte_mov16((uint8_t *)dst + 1 * 16, (const uint8_t *)src + 1 * 16);
+ rte_mov16((uint8_t *)dst + 2 * 16, (const uint8_t *)src + 2 * 16);
+#endif
+}
+
/**
* Copy 64 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov64(uint8_t *dst, const uint8_t *src)
+rte_mov64(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
{
#if defined __AVX512F__ && defined RTE_MEMCPY_AVX512
__m512i zmm0;
@@ -152,10 +161,10 @@ rte_mov64(uint8_t *dst, const uint8_t *src)
/**
* Copy 128 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov128(uint8_t *dst, const uint8_t *src)
+rte_mov128(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
{
rte_mov64(dst + 0 * 64, src + 0 * 64);
rte_mov64(dst + 1 * 64, src + 1 * 64);
@@ -163,10 +172,10 @@ rte_mov128(uint8_t *dst, const uint8_t *src)
/**
* Copy 256 bytes from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov256(uint8_t *dst, const uint8_t *src)
+rte_mov256(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src)
{
rte_mov128(dst + 0 * 128, src + 0 * 128);
rte_mov128(dst + 1 * 128, src + 1 * 128);
@@ -182,10 +191,10 @@ rte_mov256(uint8_t *dst, const uint8_t *src)
/**
* Copy 128-byte blocks from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
+rte_mov128blocks(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src, size_t n)
{
__m512i zmm0, zmm1;
@@ -202,10 +211,10 @@ rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
/**
* Copy 512-byte blocks from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static inline void
-rte_mov512blocks(uint8_t *dst, const uint8_t *src, size_t n)
+rte_mov512blocks(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src, size_t n)
{
__m512i zmm0, zmm1, zmm2, zmm3, zmm4, zmm5, zmm6, zmm7;
@@ -232,45 +241,22 @@ rte_mov512blocks(uint8_t *dst, const uint8_t *src, size_t n)
}
}
+/**
+ * Copy bytes from one location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
static __rte_always_inline void *
-rte_memcpy_generic(void *dst, const void *src, size_t n)
+rte_memcpy_generic_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+ size_t n)
{
void *ret = dst;
size_t dstofss;
size_t bits;
- /**
- * Copy less than 16 bytes
- */
- if (n < 16) {
- return rte_mov15_or_less(dst, src, n);
- }
-
/**
* Fast way when copy size doesn't exceed 512 bytes
*/
- if (__rte_constant(n) && n == 32) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- return ret;
- }
- if (n <= 32) {
- rte_mov16((uint8_t *)dst, (const uint8_t *)src);
- if (__rte_constant(n) && n == 16)
- return ret; /* avoid (harmless) duplicate copy */
- rte_mov16((uint8_t *)dst - 16 + n,
- (const uint8_t *)src - 16 + n);
- return ret;
- }
- if (__rte_constant(n) && n == 64) {
- rte_mov64((uint8_t *)dst, (const uint8_t *)src);
- return ret;
- }
- if (n <= 64) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- rte_mov32((uint8_t *)dst - 32 + n,
- (const uint8_t *)src - 32 + n);
- return ret;
- }
if (n <= 512) {
if (n >= 256) {
n -= 256;
@@ -351,10 +337,10 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
/**
* Copy 128-byte blocks from one location to another,
- * locations should not overlap.
+ * locations must not overlap.
*/
static __rte_always_inline void
-rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
+rte_mov128blocks(uint8_t *__rte_restrict dst, const uint8_t *__rte_restrict src, size_t n)
{
__m256i ymm0, ymm1, ymm2, ymm3;
@@ -381,41 +367,22 @@ rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n)
}
}
+/**
+ * Copy bytes from one location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
static __rte_always_inline void *
-rte_memcpy_generic(void *dst, const void *src, size_t n)
+rte_memcpy_generic_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+ size_t n)
{
void *ret = dst;
size_t dstofss;
size_t bits;
- /**
- * Copy less than 16 bytes
- */
- if (n < 16) {
- return rte_mov15_or_less(dst, src, n);
- }
-
/**
* Fast way when copy size doesn't exceed 256 bytes
*/
- if (__rte_constant(n) && n == 32) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- return ret;
- }
- if (n <= 32) {
- rte_mov16((uint8_t *)dst, (const uint8_t *)src);
- if (__rte_constant(n) && n == 16)
- return ret; /* avoid (harmless) duplicate copy */
- rte_mov16((uint8_t *)dst - 16 + n,
- (const uint8_t *)src - 16 + n);
- return ret;
- }
- if (n <= 64) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- rte_mov32((uint8_t *)dst - 32 + n,
- (const uint8_t *)src - 32 + n);
- return ret;
- }
if (n <= 256) {
if (n >= 128) {
n -= 128;
@@ -482,7 +449,7 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
/**
* Macro for copying unaligned block from one location to another with constant load offset,
* 47 bytes leftover maximum,
- * locations should not overlap.
+ * locations must not overlap.
* Requirements:
* - Store is aligned
* - Load offset is <offset>, which must be immediate value within [1, 15]
@@ -542,7 +509,7 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
/**
* Macro for copying unaligned block from one location to another,
* 47 bytes leftover maximum,
- * locations should not overlap.
+ * locations must not overlap.
* Use switch here because the aligning instruction requires immediate value for shift count.
* Requirements:
* - Store is aligned
@@ -573,38 +540,23 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
} \
}
+/**
+ * Copy bytes from one location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
static __rte_always_inline void *
-rte_memcpy_generic(void *dst, const void *src, size_t n)
+rte_memcpy_generic_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+ size_t n)
{
__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8;
void *ret = dst;
size_t dstofss;
size_t srcofs;
- /**
- * Copy less than 16 bytes
- */
- if (n < 16) {
- return rte_mov15_or_less(dst, src, n);
- }
-
/**
* Fast way when copy size doesn't exceed 512 bytes
*/
- if (n <= 32) {
- rte_mov16((uint8_t *)dst, (const uint8_t *)src);
- if (__rte_constant(n) && n == 16)
- return ret; /* avoid (harmless) duplicate copy */
- rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
- return ret;
- }
- if (n <= 64) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- if (n > 48)
- rte_mov16((uint8_t *)dst + 32, (const uint8_t *)src + 32);
- rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
- return ret;
- }
if (n <= 128) {
goto COPY_BLOCK_128_BACK15;
}
@@ -696,44 +648,17 @@ rte_memcpy_generic(void *dst, const void *src, size_t n)
#endif /* __AVX512F__ */
+/**
+ * Copy bytes from one vector register size aligned location to another,
+ * locations must not overlap.
+ * Use with n > 64.
+ */
static __rte_always_inline void *
-rte_memcpy_aligned(void *dst, const void *src, size_t n)
+rte_memcpy_aligned_more_than_64(void *__rte_restrict dst, const void *__rte_restrict src,
+ size_t n)
{
void *ret = dst;
- /* Copy size < 16 bytes */
- if (n < 16) {
- return rte_mov15_or_less(dst, src, n);
- }
-
- /* Copy 16 <= size <= 32 bytes */
- if (__rte_constant(n) && n == 32) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- return ret;
- }
- if (n <= 32) {
- rte_mov16((uint8_t *)dst, (const uint8_t *)src);
- if (__rte_constant(n) && n == 16)
- return ret; /* avoid (harmless) duplicate copy */
- rte_mov16((uint8_t *)dst - 16 + n,
- (const uint8_t *)src - 16 + n);
-
- return ret;
- }
-
- /* Copy 32 < size <= 64 bytes */
- if (__rte_constant(n) && n == 64) {
- rte_mov64((uint8_t *)dst, (const uint8_t *)src);
- return ret;
- }
- if (n <= 64) {
- rte_mov32((uint8_t *)dst, (const uint8_t *)src);
- rte_mov32((uint8_t *)dst - 32 + n,
- (const uint8_t *)src - 32 + n);
-
- return ret;
- }
-
/* Copy 64 bytes blocks */
for (; n > 64; n -= 64) {
rte_mov64((uint8_t *)dst, (const uint8_t *)src);
@@ -749,20 +674,49 @@ rte_memcpy_aligned(void *dst, const void *src, size_t n)
}
static __rte_always_inline void *
-rte_memcpy(void *dst, const void *src, size_t n)
+rte_memcpy(void *__rte_restrict dst, const void *__rte_restrict src, size_t n)
{
+ /* Fast way when copy size doesn't exceed 64 bytes. */
+ if (n < 16)
+ return rte_mov15_or_less(dst, src, n);
+ if (n <= 32) {
+ if (__rte_constant(n) && n == 32) {
+ rte_mov32((uint8_t *)dst, (const uint8_t *)src);
+ return dst;
+ }
+ rte_mov16((uint8_t *)dst, (const uint8_t *)src);
+ if (__rte_constant(n) && n == 16)
+ return dst; /* avoid (harmless) duplicate copy */
+ rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
+ return dst;
+ }
+ if (n <= 64) {
+ if (__rte_constant(n) && n == 64) {
+ rte_mov64((uint8_t *)dst, (const uint8_t *)src);
+ return dst;
+ }
+#if defined RTE_MEMCPY_AVX
+ rte_mov32((uint8_t *)dst, (const uint8_t *)src);
+ rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n);
+#else /* SSE implementation */
+ rte_mov16((uint8_t *)dst + 0 * 16, (const uint8_t *)src + 0 * 16);
+ rte_mov16((uint8_t *)dst + 1 * 16, (const uint8_t *)src + 1 * 16);
+ if (n > 48)
+ rte_mov16((uint8_t *)dst + 2 * 16, (const uint8_t *)src + 2 * 16);
+ rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n);
+#endif
+ return dst;
+ }
+
+ /* Implementation for size > 64 bytes depends on alignment with vector register size. */
if (!(((uintptr_t)dst | (uintptr_t)src) & ALIGNMENT_MASK))
- return rte_memcpy_aligned(dst, src, n);
+ return rte_memcpy_aligned_more_than_64(dst, src, n);
else
- return rte_memcpy_generic(dst, src, n);
+ return rte_memcpy_generic_more_than_64(dst, src, n);
}
#undef ALIGNMENT_MASK
-#if defined(RTE_TOOLCHAIN_GCC) && (GCC_VERSION >= 100000)
-#pragma GCC diagnostic pop
-#endif
-
#ifdef __cplusplus
}
#endif
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2 2/2] ethdev: add telemetry endpoint for list names
From: fengchengwen @ 2026-05-21 12:24 UTC (permalink / raw)
To: Bruce Richardson, Morten Brørup
Cc: thomas, stephen, dev, andrew.rybchenko
In-Reply-To: <ag3ME5EBdDSAhaW8@bricha3-mobl1.ger.corp.intel.com>
Thanks for the feedback.
I intend to keep the current dict format. This concise ID-name mapping is quite
helpful and easy to read especially when there are massive ports, which is exactly
the main purpose why I submitted this patch.
In my opinion, adopting OData-style query would require architecture-level
refactoring of telemetry framework, which is way too heavy for this simple requirement.
For complex query demands, we can implement them by extending the upper-layer Python
telemetry script instead.
So I suggest we keep this simple form here.
Thanks.
On 5/20/2026 10:58 PM, Bruce Richardson wrote:
> On Wed, May 20, 2026 at 03:29:36PM +0200, Morten Brørup wrote:
>>> From: Chengwen Feng [mailto:fengchengwen@huawei.com]
>>> Sent: Wednesday, 20 May 2026 11.38
>>>
>>> Add /ethdev/list_names telemetry endpoint which returns a dictionary
>>> keyed by port ID with device name as the value, so users can
>>> identify ports by name directly from the telemetry output.
>>>
>>> Original /ethdev/list output:
>>> {"/ethdev/list": [0, 1]}
>>>
>>> New /ethdev/list_names output:
>>> {"/ethdev/list_names": {"0": "0000:7d:00.0",
>>> "1": "0000:7d:00.1"}}
>>>
>>
>> <rant>
>>
>> Unfortunately, the telemetry protocol in DPDK is not using a common design, but takes parameters specific to each path.
>> It should have used OData or something similar, to standardize listing, filtering, etc.
>> Then we could have queried this like:
>> /ethdev/info?$select=port_id,name
>
> If you are up for implementing something like that, it should be possible
> to have syntax like the above work alongside our existing syntax too.
> The current telemetry scheme was set up with the overarching objective
> being simplicity.
>
>> And return something like:
>> [
>> {
>> "port_id": 0,
>> "name": "0000:7d:00.0"
>> },
>> {
>> "port_id": 1,
>> "name": "0000:7d:00.1"
>> }
>> ]
>> or:
>> [
>> {
>> 0,
>> "0000:7d:00.0"
>> },
>> {
>> 1,
>> "0000:7d:00.1"
>> }
>> ]
>>
>> But now we are stuck with what we have.
>>
>> </rant>
>>
>> So /etdev/list_names is OK.
>>
>> I'm not really familiar with the DPDK telemetry, so I wonder if indexed arrays are normally returned as an object, like in this patch?
>>
>> I would have expected a list function (such as list_names) to return an array.
>> Either a simple list:
>> {
>> "/ethdev/list_names":
>> [
>> "0000:7d:00.0",
>> "0000:7d:00.1"
>> ]
>> }
>>
>
> I think it would prefer this, but it does get a bit harder to read with a
> long list.
>
>> Or a list of objects:
>> {
>> "/ethdev/list_names":
>> [
>> {
>> "port_id": 0,
>> "name": "0000:7d:00.0"
>> },
>> {
>> "port_id": 1,
>> "name": "0000:7d:00.1"
>> }
>> ]
>> }
>>
>
> Agree that this also would be slightly better.
>
> However, a *completely* different approach would be to instead solve this
> issue by adding additional functionality to the interactive telemetry
> script itself. After all, the data for the list of names of ethdevs is
> already available from the telemetry endpoints already present in DPDK. All
> we need to do is to extend the python script to have "virtual endpoints" if
> you will, which do the necessary queries in the background and then present
> the data to the user. I think that would be a cleaner approach to things
> like this, rather than always adding more C code.
>
> /Bruce
>
^ permalink raw reply
* RE: [PATCH v2 2/2] ethdev: add telemetry endpoint for list names
From: Morten Brørup @ 2026-05-21 12:40 UTC (permalink / raw)
To: fengchengwen, Bruce Richardson; +Cc: thomas, stephen, dev, andrew.rybchenko
In-Reply-To: <cd4f1bcd-3d66-4ffe-91d7-7ac1fdfe8c2e@huawei.com>
> From: fengchengwen [mailto:fengchengwen@huawei.com]
> Sent: Thursday, 21 May 2026 14.25
>
> Thanks for the feedback.
>
> I intend to keep the current dict format. This concise ID-name mapping
> is quite
> helpful and easy to read especially when there are massive ports, which
> is exactly
> the main purpose why I submitted this patch.
>
> In my opinion, adopting OData-style query would require architecture-
> level
> refactoring of telemetry framework, which is way too heavy for this
> simple requirement.
Agree.
Refactoring the telemetry framework is different task, not related to this patch.
> For complex query demands, we can implement them by extending the
> upper-layer Python
> telemetry script instead.
>
> So I suggest we keep this simple form here.
If it is generally acceptable for DPDK telemetry that a request for a list does not return a list type, but returns an object type with "index": "value" fields instead, then
Series-acked-by: Morten Brørup <mb@smartsharesystems.com>
>
> Thanks.
>
> On 5/20/2026 10:58 PM, Bruce Richardson wrote:
> > On Wed, May 20, 2026 at 03:29:36PM +0200, Morten Brørup wrote:
> >>> From: Chengwen Feng [mailto:fengchengwen@huawei.com]
> >>> Sent: Wednesday, 20 May 2026 11.38
> >>>
> >>> Add /ethdev/list_names telemetry endpoint which returns a
> dictionary
> >>> keyed by port ID with device name as the value, so users can
> >>> identify ports by name directly from the telemetry output.
> >>>
> >>> Original /ethdev/list output:
> >>> {"/ethdev/list": [0, 1]}
> >>>
> >>> New /ethdev/list_names output:
> >>> {"/ethdev/list_names": {"0": "0000:7d:00.0",
> >>> "1": "0000:7d:00.1"}}
> >>>
> >>
> >> <rant>
> >>
> >> Unfortunately, the telemetry protocol in DPDK is not using a common
> design, but takes parameters specific to each path.
> >> It should have used OData or something similar, to standardize
> listing, filtering, etc.
> >> Then we could have queried this like:
> >> /ethdev/info?$select=port_id,name
> >
> > If you are up for implementing something like that, it should be
> possible
> > to have syntax like the above work alongside our existing syntax too.
> > The current telemetry scheme was set up with the overarching
> objective
> > being simplicity.
> >
> >> And return something like:
> >> [
> >> {
> >> "port_id": 0,
> >> "name": "0000:7d:00.0"
> >> },
> >> {
> >> "port_id": 1,
> >> "name": "0000:7d:00.1"
> >> }
> >> ]
> >> or:
> >> [
> >> {
> >> 0,
> >> "0000:7d:00.0"
> >> },
> >> {
> >> 1,
> >> "0000:7d:00.1"
> >> }
> >> ]
> >>
> >> But now we are stuck with what we have.
> >>
> >> </rant>
> >>
> >> So /etdev/list_names is OK.
> >>
> >> I'm not really familiar with the DPDK telemetry, so I wonder if
> indexed arrays are normally returned as an object, like in this patch?
> >>
> >> I would have expected a list function (such as list_names) to return
> an array.
> >> Either a simple list:
> >> {
> >> "/ethdev/list_names":
> >> [
> >> "0000:7d:00.0",
> >> "0000:7d:00.1"
> >> ]
> >> }
> >>
> >
> > I think it would prefer this, but it does get a bit harder to read
> with a
> > long list.
> >
> >> Or a list of objects:
> >> {
> >> "/ethdev/list_names":
> >> [
> >> {
> >> "port_id": 0,
> >> "name": "0000:7d:00.0"
> >> },
> >> {
> >> "port_id": 1,
> >> "name": "0000:7d:00.1"
> >> }
> >> ]
> >> }
> >>
> >
> > Agree that this also would be slightly better.
> >
> > However, a *completely* different approach would be to instead solve
> this
> > issue by adding additional functionality to the interactive telemetry
> > script itself. After all, the data for the list of names of ethdevs
> is
> > already available from the telemetry endpoints already present in
> DPDK. All
> > we need to do is to extend the python script to have "virtual
> endpoints" if
> > you will, which do the necessary queries in the background and then
> present
> > the data to the user. I think that would be a cleaner approach to
> things
> > like this, rather than always adding more C code.
> >
> > /Bruce
> >
^ permalink raw reply
* Re: [PATCH 1/2] bus/uacce: support driver forward compatibility
From: David Marchand @ 2026-05-21 13:06 UTC (permalink / raw)
To: Chengwen Feng; +Cc: thomas, dev, qianweili, liuyonglong
In-Reply-To: <20260302110206.49670-2-fengchengwen@huawei.com>
Hello Chengwen,
On Mon, 2 Mar 2026 at 12:02, Chengwen Feng <fengchengwen@huawei.com> wrote:
>
> As we know, the uacce driver (e.g. hisi_acc DMA driver) reads the API of
> the hardware device (through /sysfs/class/uacce/xxx/api) and compares it
> with the API supported by the driver to match the corresponding hardware
> device.
>
> Hardware devices will continue to evolve, which means their APIs will
> change, but business requirements demand that they support old
> programming interfaces as much as possible.
>
> To adapt to this situation, this commit supports forward compatibility
> of driver APIs. For example, if the driver supports the hisi_qm_v5 API,
> it can drive the hardware device that supports the hisi_qm_v6 or
> hisi_qm_v7 API.
>
> In addition, a driver flag (RTE_UACCE_DRV_FORWARD_COMPATIBILITY_DEV) is
> introduced. The driver supports forward compatibility of APIs only when
> this flag is defined.
>
> Signed-off-by: Chengwen Feng <fengchengwen@huawei.com>
> ---
> drivers/bus/uacce/bus_uacce_driver.h | 5 +++
> drivers/bus/uacce/uacce.c | 51 ++++++++++++++++++++++++++--
> 2 files changed, 53 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/bus/uacce/bus_uacce_driver.h b/drivers/bus/uacce/bus_uacce_driver.h
> index 618e0f9b76..c7445778a6 100644
> --- a/drivers/bus/uacce/bus_uacce_driver.h
> +++ b/drivers/bus/uacce/bus_uacce_driver.h
> @@ -50,6 +50,7 @@ struct rte_uacce_device {
> char dev_root[RTE_UACCE_DEV_PATH_SIZE]; /**< Sysfs path with device name. */
> char cdev_path[RTE_UACCE_DEV_PATH_SIZE]; /**< Device path in devfs. */
> char api[RTE_UACCE_API_NAME_SIZE]; /**< Device context type. */
> + uint32_t api_ver; /**< Device api version used for compatibility. */
I have some problem with this field.
See below.
> char algs[RTE_UACCE_ALGS_NAME_SIZE]; /**< Device supported algorithms. */
> uint32_t flags; /**< Device flags. */
> int numa_node; /**< NUMA node connection, -1 if unknown. */
> @@ -100,8 +101,12 @@ struct rte_uacce_driver {
> rte_uacce_probe_t *probe; /**< Device probe function. */
> rte_uacce_remove_t *remove; /**< Device remove function. */
> const struct rte_uacce_id *id_table; /**< ID table, NULL terminated. */
> + uint32_t drv_flags; /**< Flags RTE_UACCE_DRV_*. */
> };
>
> +/** Device driver supports forward compatibility device */
> +#define RTE_UACCE_DRV_FORWARD_COMPATIBILITY_DEV 0x1
> +
> /**
> * Get available queue number.
> *
> diff --git a/drivers/bus/uacce/uacce.c b/drivers/bus/uacce/uacce.c
> index 79f990c54c..a471edcad0 100644
> --- a/drivers/bus/uacce/uacce.c
> +++ b/drivers/bus/uacce/uacce.c
> @@ -2,6 +2,7 @@
> * Copyright(c) 2024 HiSilicon Limited
> */
>
> +#include <ctype.h>
> #include <dirent.h>
> #include <errno.h>
> #include <fcntl.h>
> @@ -324,19 +325,62 @@ uacce_scan(void)
> return -1;
> }
>
> +static uint32_t
> +uacce_calc_api_ver(const char *api, int *offset)
> +{
> + int len = strlen(api);
> + int end = len - 1;
> + unsigned long ver;
> +
> + while (end >= 0 && isdigit(api[end]))
> + end--;
> +
> + if (end <= 0 || end == len - 1 || api[end] != 'v')
> + return 0;
> +
> + ver = strtoul(api + end + 1, NULL, 10);
> + if (ver > UINT32_MAX)
> + return 0;
> +
> + if (offset != NULL)
> + *offset = end + 1;
> + return (uint32_t)ver;
> +}
> +
> +static bool
> +uacce_match_api(const struct rte_uacce_device *dev, bool forward_compat,
> + const struct rte_uacce_id *id_table)
> +{
> + int dev_ver_off = 0, id_ver_off = 0;
> + uint32_t dev_ver, id_ver;
> +
> + if (!forward_compat)
> + return strcmp(id_table->dev_api, dev->api) == 0;
> +
> + dev_ver = uacce_calc_api_ver(dev->api, &dev_ver_off);
> + id_ver = uacce_calc_api_ver(id_table->dev_api, &id_ver_off);
> + return dev_ver > 0 && id_ver > 0 && dev_ver_off == id_ver_off &&
> + strncmp(id_table->dev_api, dev->api, dev_ver_off) == 0 &&
> + dev_ver >= id_ver;
> +}
> +
> static bool
> -uacce_match(const struct rte_uacce_driver *dr, const struct rte_uacce_device *dev)
> +uacce_match(const struct rte_uacce_driver *dr, struct rte_uacce_device *dev)
A matching helper should only match, and not modify the object.
> {
> + bool forward_compat = !!(dr->drv_flags & RTE_UACCE_DRV_FORWARD_COMPATIBILITY_DEV);
> + uint32_t api_ver = uacce_calc_api_ver(dev->api, NULL);
This conversion from a string to integer could be placed in the scanning step.
Why place it here?
The dev->api_ver has no in-tree user.
This field was (silently?) dropped by my best AI friend in the bus
refactoring series I posted.
https://patchwork.dpdk.org/project/dpdk/patch/20260506155201.2709810-12-david.marchand@redhat.com/
Please advise if I can drop this field (it is just the integer value
extracted from dev->api afaiu), or if it should be moved to the
scanning step.
--
David Marchand
^ permalink raw reply
* Re: [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags
From: Kevin Traynor @ 2026-05-21 13:38 UTC (permalink / raw)
To: David Marchand, Robin Jarry; +Cc: dev
In-Reply-To: <CAJFAV8yTyFoWYCNhzcJUBM=DdTDz4qQaN7-=m9_HGQeTc3P0eg@mail.gmail.com>
On 5/20/26 1:42 PM, David Marchand wrote:
> On Wed, 20 May 2026 at 13:09, Robin Jarry <rjarry@redhat.com> wrote:
>>>> - } else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
>>>> - const struct rte_vlan_hdr *vh;
>>>> - struct rte_vlan_hdr vh_copy;
>>>> + if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN))
>>>> + pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
>>>> + else
>>>> + pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
>>>> +
>>>> + do {
>>>> + vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
>>>> + if (unlikely(vh == NULL))
>>>> + return pkt_type;
>>>
>>> Kevin noted that it is weird to report back some packet type when the
>>> packet is malformed.
>>> Maybe return RTE_PTYPE_UNKNOWN here so that the application is forced
>>> to validate the packet? (it should already be doing it, in any
>>> case..).
>>
>> If we do this, we need to fix it in the entire function. There are
>> several other places where the "current" value of pkt_type is returned
>> on error.
>
> There is this point and the code has been behaving like this for
> years, so some applications may have been relying on this behavior.
> I don't mind leaving as is.
>
>
I can agree with Hyrum's law :-) https://xkcd.com/1172/
^ permalink raw reply
* Re: [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags
From: Kevin Traynor @ 2026-05-21 13:38 UTC (permalink / raw)
To: Robin Jarry, dev; +Cc: David Marchand
In-Reply-To: <20260518132712.70913-10-rjarry@redhat.com>
On 5/18/26 2:27 PM, Robin Jarry wrote:
> The VLAN and QinQ code paths in rte_net_get_ptype handle at most two
> tags with duplicated logic. Replace them with a single loop that
> consumes all consecutive VLAN/QinQ headers regardless of depth.
>
> Bugzilla ID: 1941
> Suggested-by: David Marchand <david.marchand@redhat.com>
> Signed-off-by: Robin Jarry <rjarry@redhat.com>
> ---
> lib/net/rte_net.c | 35 ++++++++++++++++-------------------
> 1 file changed, 16 insertions(+), 19 deletions(-)
>
> diff --git a/lib/net/rte_net.c b/lib/net/rte_net.c
> index c70b57fdc0f8..d3cded961fb5 100644
> --- a/lib/net/rte_net.c
> +++ b/lib/net/rte_net.c
> @@ -349,29 +349,26 @@ uint32_t rte_net_get_ptype(const struct rte_mbuf *m,
> if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
> goto l3; /* fast path if packet is IPv4 */
>
> - if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) {
> + if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN)) ||
> + (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ))) {
> const struct rte_vlan_hdr *vh;
> struct rte_vlan_hdr vh_copy;
>
> - pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
> - vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
> - if (unlikely(vh == NULL))
> - return pkt_type;
> - off += sizeof(*vh);
> - hdr_lens->l2_len += sizeof(*vh);
> - proto = vh->eth_proto;
> - } else if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ)) {
> - const struct rte_vlan_hdr *vh;
> - struct rte_vlan_hdr vh_copy;
> + if (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN))
> + pkt_type = RTE_PTYPE_L2_ETHER_VLAN;
> + else
> + pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> +
> + do {
> + vh = rte_pktmbuf_read(m, off, sizeof(*vh), &vh_copy);
> + if (unlikely(vh == NULL))
> + return pkt_type;
> + off += sizeof(*vh);
> + hdr_lens->l2_len += sizeof(*vh);
> + proto = vh->eth_proto;
> + } while (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_VLAN) ||
> + proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_QINQ));
>
> - pkt_type = RTE_PTYPE_L2_ETHER_QINQ;
> - vh = rte_pktmbuf_read(m, off + sizeof(*vh), sizeof(*vh),
> - &vh_copy);
> - if (unlikely(vh == NULL))
> - return pkt_type;
> - off += 2 * sizeof(*vh);
> - hdr_lens->l2_len += 2 * sizeof(*vh);
> - proto = vh->eth_proto;
> } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
> (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
David's code snipped earlier in the thread suggested that this should be
change from an 'else if' to an 'if'. It seems to make sense to have it
as an 'if', not sure if it was deliberate to remove. Though it might not
be really part of this patch.
- } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
+ if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
(proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
unsigned int i;
> unsigned int i;
^ permalink raw reply
* Re: [PATCH dpdk v5 2/5] net: support multiple stacked VLAN tags
From: Robin Jarry @ 2026-05-21 13:40 UTC (permalink / raw)
To: Kevin Traynor, dev; +Cc: David Marchand
In-Reply-To: <0fc8a941-58ed-4901-8310-44c3a8d4b049@redhat.com>
Kevin Traynor, May 21, 2026 at 15:38:
>> } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
>> (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
>
> David's code snipped earlier in the thread suggested that this should be
> change from an 'else if' to an 'if'. It seems to make sense to have it
> as an 'if', not sure if it was deliberate to remove. Though it might not
> be really part of this patch.
>
> - } else if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
> + if ((proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLS)) ||
> (proto == rte_cpu_to_be_16(RTE_ETHER_TYPE_MPLSM))) {
> unsigned int i;
Yes, I deliberately kept this as it was. I can add another patch to
change to a simple if. It makes sense to support MPLS in VLAN.
--
Robin
> Place stamp here.
^ permalink raw reply
* Re: [PATCH v14 0/6] Add AGENTS.md and scripts for AI code review
From: Thomas Monjalon @ 2026-05-21 14:12 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, Aaron Conole
In-Reply-To: <20260414211012.951613-1-stephen@networkplumber.org>
14/04/2026 23:08, Stephen Hemminger:
> devtools/analyze-patch.py | 1509 ++++++++++++++++
> devtools/compare-reviews.sh | 263 +++
> devtools/review-doc.py | 1184 +++++++++++++
Should we move these scripts in a sub-directory devtools/ai/ ?
Still about the naming,
compare-reviews.sh is for analyze-patch.py,
so I suggest this consistent naming:
review-patch.py
compare-patch-reviews.sh
^ permalink raw reply
* Re: [PATCH v14 1/6] doc: add AGENTS.md for AI code review tools
From: Thomas Monjalon @ 2026-05-21 14:15 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, Aaron Conole
In-Reply-To: <20260414211012.951613-2-stephen@networkplumber.org>
14/04/2026 23:08, Stephen Hemminger:
> Provide structured guidelines for AI tools reviewing DPDK
> patches. Focuses on correctness bug detection (resource leaks,
> use-after-free, race conditions), C coding style, forbidden
> tokens, API conventions, and severity classifications.
>
> Mechanical checks already handled by checkpatches.sh (SPDX
> format, commit message formatting, tag ordering) are excluded
> to avoid redundant and potentially contradictory findings.
>
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> Acked-by: Aaron Conole <aconole@redhat.com>
> ---
> AGENTS.md | 2170 +++++++++++++++++++++++++++++++++++++++++++++++++++++
There are big chances that we are going to update this file in future.
Please help easy updates by following this guideline from
doc/guides/contributing/documentation.rst
* Each sentence should start on a new line.
Multiple sentences, which are not separated by a blank line,
are joined automatically into paragraphs.
* Wrap sentences at punctuation points, for example, at a comma.
If no punctuation, put the newline at a logical point in the sentence,
for example, at the end of a clause before an "and" or "but".
^ permalink raw reply
* Re: [PATCH v14 3/6] devtools: add compare-reviews.sh for multi-provider analysis
From: Thomas Monjalon @ 2026-05-21 14:17 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev, Aaron Conole
In-Reply-To: <20260414211012.951613-4-stephen@networkplumber.org>
14/04/2026 23:08, Stephen Hemminger:
> +#!/bin/bash
I'm not sure why bash is required for this script.
It could be just /bin/sh.
[...]
> + [[ -n "$ANTHROPIC_API_KEY" ]] && available="${available}anthropic,"
If you replace double brackets with single ones,
you should be close to a POSIX shell script.
> + [[ -z "${2:-}" || "$2" == -* ]] && error "$1 requires an argument"
and replace == with simple = in tests.
^ permalink raw reply
* Re: [PATCH] dma/ae4dma: add AMD AE4DMA DMA PMD
From: David Marchand @ 2026-05-21 14:28 UTC (permalink / raw)
To: Raghavendra Ningoji
Cc: dev, thomas, rjarry, Bhagyada.Modali, Selwin.Sebastian,
Chengwen Feng
In-Reply-To: <20260518181856.1228373-1-raghavendra.ningoji@amd.com>
Hello,
On Mon, 18 May 2026 at 20:19, Raghavendra Ningoji
<raghavendra.ningoji@amd.com> wrote:
>
> Add a new dmadev poll-mode driver for the AMD AE4DMA hardware DMA
> engine. An AE4DMA engine exposes 16 hardware command queues, each
> with a 32-entry descriptor ring; the PMD maps each hardware channel
> to its own dmadev with a single virtual channel, so a PCI function
> appears as 16 dmadevs named "<pci-bdf>-ch0" .. "<pci-bdf>-ch15".
>
> Driver characteristics:
>
> - Memory-to-memory copy operations only (RTE_DMA_CAPA_MEM_TO_MEM).
> - Completion is detected via the hardware's per-queue read_idx
> register, which the engine advances as it processes descriptors.
> The descriptor status / err_code bytes are read only to classify
> each drained slot as success or failure.
> - vchan_status reports IDLE/ACTIVE based on HW read_idx vs write_idx
> and HALTED_ERROR when the queue is not enabled.
> - depends on bus_pci and dmadev.
>
> Signed-off-by: Raghavendra Ningoji <raghavendra.ningoji@amd.com>
Cc: Chengwen Feng (DMAdev maintainer)
- The patch is big, splitting it into logical patches introducing one
feature at a time would help.
See for example how the latest DMA driver was submitted:
5a9c32a89c - dma/hisi_acc: introduce HiSilicon SoC accelerator driver
(7 months ago) <Chengwen Feng>
2557ad8f8a - dma/hisi_acc: add control path operations (7 months ago)
<Chengwen Feng>
b58c4435ea - dma/hisi_acc: add data path operations (7 months ago)
<Chengwen Feng>
- Please fix the below warnings raised by checkpatches.sh, and run
this script before submitting a new revision.
### [PATCH] dma/ae4dma: add AMD AE4DMA DMA PMD
WARNING:TYPO_SPELLING: 're-use' may be misspelled - perhaps 'reuse'?
#199: FILE: drivers/dma/ae4dma/ae4dma_dmadev.c:42:
+ AE4DMA_PMD_INFO("re-use memzone already "
^^^^^^
total: 0 errors, 1 warnings, 1160 lines checked
Warning in drivers/dma/ae4dma/ae4dma_internal.h:
Prefer RTE_LOG_LINE/RTE_LOG_DP_LINE
Warning in drivers/dma/ae4dma/ae4dma_internal.h:
Do not use variadic argument pack in macros
Please use __rte_cache_aligned only for struct or union types alignment.
- Please also checks the copyright years.
For new code (from upstream pov), this should be 2026.
- Globally in those changes, rte_iova_t should probably be used
instead of phys_addr_t.
> ---
> MAINTAINERS | 5 +
> doc/guides/dmadevs/ae4dma.rst | 75 +++
> doc/guides/dmadevs/index.rst | 1 +
> doc/guides/rel_notes/release_26_07.rst | 7 +
> drivers/dma/ae4dma/ae4dma_dmadev.c | 742 +++++++++++++++++++++++++
> drivers/dma/ae4dma/ae4dma_hw_defs.h | 164 ++++++
> drivers/dma/ae4dma/ae4dma_internal.h | 117 ++++
> drivers/dma/ae4dma/meson.build | 7 +
> drivers/dma/meson.build | 1 +
> usertools/dpdk-devbind.py | 5 +-
> 10 files changed, 1123 insertions(+), 1 deletion(-)
> create mode 100644 doc/guides/dmadevs/ae4dma.rst
> create mode 100644 drivers/dma/ae4dma/ae4dma_dmadev.c
> create mode 100644 drivers/dma/ae4dma/ae4dma_hw_defs.h
> create mode 100644 drivers/dma/ae4dma/ae4dma_internal.h
> create mode 100644 drivers/dma/ae4dma/meson.build
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9143d028bc..0b5a6e08d8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1361,6 +1361,11 @@ F: doc/guides/compressdevs/features/zsda.ini
> DMAdev Drivers
> --------------
>
> +AMD AE4DMA
> +M: Bhagyada Modali <Bhagyada.Modali@amd.com>
No capital letter in the mail address section.
> +F: drivers/dma/ae4dma/
> +F: doc/guides/dmadevs/ae4dma.rst
> +
> Intel IDXD - EXPERIMENTAL
> M: Bruce Richardson <bruce.richardson@intel.com>
> M: Kevin Laatz <kevin.laatz@intel.com>
Please also add an entry in the .mailmap file as it is your first contribution.
> diff --git a/drivers/dma/ae4dma/ae4dma_dmadev.c b/drivers/dma/ae4dma/ae4dma_dmadev.c
> new file mode 100644
> index 0000000000..eb6ea88f55
> --- /dev/null
> +++ b/drivers/dma/ae4dma/ae4dma_dmadev.c
> @@ -0,0 +1,742 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2021-2025 Advanced Micro Devices, Inc. All rights reserved.
> + */
> +
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include <rte_bus_pci.h>
> +#include <bus_pci_driver.h>
> +#include <rte_dmadev_pmd.h>
> +#include <rte_malloc.h>
> +
> +#include "ae4dma_internal.h"
> +
> +/*
> + * One dmadev per AE4DMA hardware channel; each dmadev has exactly one
> + * virtual channel. The HW's per-queue register block must be densely
> + * packed right after the engine-common config register at BAR0+0; the
> + * build-time check below catches an accidental layout change.
> + */
> +static_assert(sizeof(struct ae4dma_hwq_regs) == 32,
> + "ae4dma_hwq_regs stride changed; per-queue offset math will break");
> +
> +RTE_LOG_REGISTER_DEFAULT(ae4dma_pmd_logtype, INFO);
> +
> +#define AE4DMA_PMD_NAME dmadev_ae4dma
> +#define AE4DMA_PMD_NAME_STR RTE_STR(AE4DMA_PMD_NAME)
AE4DMA_PMD_NAME_STR is not used at all.
Avoid introducing the macro AE4DMA_PMD_NAME, the value is not supposed
to change.
> +
> +static const struct rte_memzone *
> +ae4dma_queue_dma_zone_reserve(const char *queue_name,
> + uint32_t queue_size, int socket_id)
> +{
> + const struct rte_memzone *mz;
> +
> + mz = rte_memzone_lookup(queue_name);
> + if (mz != 0) {
mz != NULL
> + if (((size_t)queue_size <= mz->len) &&
> + ((socket_id == SOCKET_ID_ANY) ||
> + (socket_id == mz->socket_id))) {
> + AE4DMA_PMD_INFO("re-use memzone already "
> + "allocated for %s", queue_name);
> + return mz;
> + }
> + AE4DMA_PMD_ERR("Incompatible memzone already "
> + "allocated %s, size %u, socket %d. "
> + "Requested size %u, socket %u",
> + queue_name, (uint32_t)mz->len,
> + mz->socket_id, queue_size, socket_id);
> + return NULL;
> + }
> + return rte_memzone_reserve_aligned(queue_name, queue_size,
> + socket_id, RTE_MEMZONE_IOVA_CONTIG, queue_size);
> +}
> +
> +/* Configure a device. */
> +static int
> +ae4dma_dev_configure(struct rte_dma_dev *dev __rte_unused,
> + const struct rte_dma_conf *dev_conf,
> + uint32_t conf_sz)
> +{
> + if (sizeof(struct rte_dma_conf) != conf_sz)
> + return -EINVAL;
> +
> + if (dev_conf->nb_vchans != 1)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +/* Setup a virtual channel for AE4DMA, only 1 vchan is supported per dmadev. */
> +static int
> +ae4dma_vchan_setup(struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
> + const struct rte_dma_vchan_conf *qconf, uint32_t qconf_sz)
> +{
> + struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint16_t max_desc = qconf->nb_desc;
> +
> + if (sizeof(struct rte_dma_vchan_conf) != qconf_sz)
> + return -EINVAL;
> +
> + if (max_desc < 2)
> + return -EINVAL;
> +
> + if (!rte_is_power_of_2(max_desc))
> + max_desc = rte_align32pow2(max_desc);
> +
> + if (max_desc > AE4DMA_DESCRIPTORS_PER_CMDQ) {
> + AE4DMA_PMD_DEBUG("DMA dev %u nb_desc clamped to %u",
> + dev->data->dev_id, AE4DMA_DESCRIPTORS_PER_CMDQ);
> + max_desc = AE4DMA_DESCRIPTORS_PER_CMDQ;
> + }
> +
> + cmd_q->qcfg = *qconf;
> + cmd_q->qcfg.nb_desc = max_desc;
> +
> + /* Ensure all counters are reset, if reconfiguring/restarting device. */
> + memset(&cmd_q->stats, 0, sizeof(cmd_q->stats));
> + return 0;
> +}
> +
> +/* Start a configured device. */
> +static int
> +ae4dma_dev_start(struct rte_dma_dev *dev)
> +{
> + struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> +
> + if (nb == 0)
> + return -EBUSY;
> +
> + /* Program ring depth expected by hardware. */
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->max_idx, nb);
> + return 0;
> +}
> +
> +/* Stop a configured device. */
> +static int
> +ae4dma_dev_stop(struct rte_dma_dev *dev)
> +{
> + struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> +
> + if (cmd_q->hwq_regs != NULL)
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
> + AE4DMA_CMD_QUEUE_DISABLE);
> + return 0;
> +}
> +
> +/* Get device information of a device. */
> +static int
> +ae4dma_dev_info_get(const struct rte_dma_dev *dev, struct rte_dma_info *info,
> + uint32_t size)
> +{
> + if (size < sizeof(*info))
> + return -EINVAL;
> + info->dev_name = dev->device->name;
> + info->dev_capa = RTE_DMA_CAPA_MEM_TO_MEM;
> + info->max_vchans = 1;
> + info->min_desc = 2;
> + info->max_desc = AE4DMA_DESCRIPTORS_PER_CMDQ;
> + info->nb_vchans = 1;
> + return 0;
> +}
> +
> +/* Close a configured device. */
> +static int
> +ae4dma_dev_close(struct rte_dma_dev *dev)
> +{
> + struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> +
> + if (cmd_q->hwq_regs != NULL)
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
> + AE4DMA_CMD_QUEUE_DISABLE);
> +
> + if (cmd_q->memz_name[0] != '\0') {
> + const struct rte_memzone *mz = rte_memzone_lookup(cmd_q->memz_name);
> +
> + if (mz != NULL)
> + rte_memzone_free(mz);
> + }
> + cmd_q->qbase_desc = NULL;
> + cmd_q->qbase_addr = NULL;
> + cmd_q->qbase_phys_addr = 0;
> + return 0;
> +}
> +
> +/* trigger h/w to process enqued desc:doorbell - by next_write */
> +static inline void
> +__submit(struct ae4dma_dmadev *ae4dma)
> +{
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint16_t write_idx = cmd_q->next_write;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> +
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->write_idx, write_idx);
> + if (nb != 0)
> + cmd_q->stats.submitted += (uint16_t)((cmd_q->next_write - cmd_q->last_write +
> + nb) % nb);
> + cmd_q->last_write = cmd_q->next_write;
> +}
> +
> +static int
> +ae4dma_submit(void *dev_private, uint16_t vchan __rte_unused)
> +{
> + struct ae4dma_dmadev *ae4dma = dev_private;
> +
> + __submit(ae4dma);
> + return 0;
> +}
> +
> +/* Write descriptor for enqueue (copy only). */
> +static inline int
> +__write_desc_copy(void *dev_private, rte_iova_t src, phys_addr_t dst,
> + uint32_t len, uint64_t flags)
> +{
> + struct ae4dma_dmadev *ae4dma = dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + struct ae4dma_desc *dma_desc;
> + uint16_t ret;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> + uint16_t write = cmd_q->next_write;
> +
> + if (nb == 0)
> + return -EINVAL;
> +
> + /* Reserve one slot to distinguish full from empty (power-of-two ring). */
> + if ((uint32_t)cmd_q->ring_buff_count >= (uint32_t)(nb - 1))
> + return -ENOSPC;
> +
> + dma_desc = &cmd_q->qbase_desc[write];
> + memset(dma_desc, 0, sizeof(*dma_desc));
> + dma_desc->length = len;
> + dma_desc->src_hi = upper_32_bits(src);
> + dma_desc->src_lo = lower_32_bits(src);
> + dma_desc->dst_hi = upper_32_bits(dst);
> + dma_desc->dst_lo = lower_32_bits(dst);
> + cmd_q->ring_buff_count++;
> + cmd_q->next_write = (uint16_t)((write + 1) % nb);
> + ret = write;
> + if (flags & RTE_DMA_OP_FLAG_SUBMIT)
> + __submit(ae4dma);
> + return ret;
> +}
> +
> +/* Enqueue a copy operation onto the ae4dma device. */
> +static int
> +ae4dma_enqueue_copy(void *dev_private, uint16_t vchan __rte_unused,
> + rte_iova_t src, rte_iova_t dst, uint32_t length, uint64_t flags)
> +{
> + return __write_desc_copy(dev_private, src, dst, length, flags);
> +}
> +
> +/* Dump DMA device info. */
> +static int
> +ae4dma_dev_dump(const struct rte_dma_dev *dev, FILE *f)
> +{
> + struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + struct ae4dma_cmd_queue *cmd_q;
> + void *ae4dma_mmio_base_addr = (uint8_t *)ae4dma->io_regs;
> +
> + cmd_q = &ae4dma->cmd_q;
> + fprintf(f, "cmd_q->id = %" PRIx64 "\n", cmd_q->id);
> + fprintf(f, "cmd_q->qidx = %" PRIx64 "\n", cmd_q->qidx);
> + fprintf(f, "cmd_q->qsize = %" PRIx64 "\n", cmd_q->qsize);
> + fprintf(f, "mmio_base_addr = %p\n", ae4dma_mmio_base_addr);
> + fprintf(f, "queues per ae4dma engine = %d\n", AE4DMA_READ_REG_OFFSET(
> + ae4dma_mmio_base_addr, AE4DMA_COMMON_CONFIG_OFFSET));
> + fprintf(f, "== Private Data ==\n");
> + fprintf(f, " Config: { ring_size: %u }\n", cmd_q->qcfg.nb_desc);
> + fprintf(f, " Ring virt: %p\tphys: %#" PRIx64 "\n",
> + (void *)cmd_q->qbase_desc,
> + (uint64_t)cmd_q->qbase_phys_addr);
> + fprintf(f, " Next write: %u\n", cmd_q->next_write);
> + fprintf(f, " Next read: %u\n", cmd_q->next_read);
> + fprintf(f, " current queue depth: %u\n", cmd_q->ring_buff_count);
> + fprintf(f, " }\n");
> + fprintf(f, " Key Stats { submitted: %" PRIu64 ", comp: %" PRIu64 ", failed: %" PRIu64 " }\n",
> + cmd_q->stats.submitted,
> + cmd_q->stats.completed,
> + cmd_q->stats.errors);
> + return 0;
> +}
> +
> +/* Translates AE4DMA ChanERRs to DMA error codes. */
> +static inline enum rte_dma_status_code
> +__translate_status_ae4dma_to_dma(enum ae4dma_dma_err status)
> +{
> + AE4DMA_PMD_DEBUG("ae4dma desc status = %d", status);
> +
> + switch (status) {
> + case AE4DMA_DMA_ERR_NO_ERR:
> + return RTE_DMA_STATUS_SUCCESSFUL;
> + case AE4DMA_DMA_ERR_INV_LEN:
> + return RTE_DMA_STATUS_INVALID_LENGTH;
> + case AE4DMA_DMA_ERR_INV_SRC:
> + return RTE_DMA_STATUS_INVALID_SRC_ADDR;
> + case AE4DMA_DMA_ERR_INV_DST:
> + return RTE_DMA_STATUS_INVALID_DST_ADDR;
> + case AE4DMA_DMA_ERR_INV_ALIGN:
> + /* Name matches DPDK public enum spelling. */
> + return RTE_DMA_STATUS_DATA_POISION;
> + case AE4DMA_DMA_ERR_INV_HEADER:
> + case AE4DMA_DMA_ERR_INV_STATUS:
> + return RTE_DMA_STATUS_ERROR_UNKNOWN;
> + default:
> + return RTE_DMA_STATUS_ERROR_UNKNOWN;
> + }
> +}
> +
> +/*
> + * Scan HW queue for completed descriptors (non-blocking).
> + *
> + * The AE4DMA engine signals completion by advancing the per-queue
> + * `read_idx` register; it does not (reliably) write a status value
> + * back into the descriptor. We therefore use the HW `read_idx`
> + * register as the source of truth and only inspect the descriptor's
> + * `dw1.err_code` byte to classify each completion as success or
> + * failure.
> + *
> + * @param cmd_q
> + * The AE4DMA command queue.
> + * @param max_ops
> + * Maximum descriptors to process this call.
> + * @param[out] failed_count
> + * Number of completed descriptors that did not report success.
> + * @return
> + * Number of descriptors completed (success + failure), <= max_ops.
> + */
> +static inline uint16_t
> +ae4dma_scan_hwq(struct ae4dma_cmd_queue *cmd_q, uint16_t max_ops,
> + uint16_t *failed_count)
> +{
> + volatile struct ae4dma_desc *hw_desc;
> + uint16_t events_count = 0, fails = 0;
> + uint16_t tail;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> + uint16_t mask;
> + uint16_t hw_read_idx;
> + uint16_t in_flight;
> + uint16_t scan_cap;
> +
> + if (nb == 0 || cmd_q->ring_buff_count == 0) {
> + *failed_count = 0;
> + return 0;
> + }
> + mask = nb - 1;
> +
> + hw_read_idx = (uint16_t)(AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx) & mask);
> + tail = cmd_q->next_read;
> +
> + /*
> + * Descriptors completed since our last visit live in the
> + * half-open ring range [tail, hw_read_idx). If HW hasn't
> + * moved we have nothing to do.
> + */
> + in_flight = (uint16_t)((hw_read_idx - tail) & mask);
> + if (in_flight == 0) {
> + *failed_count = 0;
> + return 0;
> + }
> +
> + scan_cap = max_ops;
> + if (scan_cap > AE4DMA_DESCRIPTORS_PER_CMDQ)
> + scan_cap = AE4DMA_DESCRIPTORS_PER_CMDQ;
> + if (scan_cap > in_flight)
> + scan_cap = in_flight;
> + if (scan_cap > cmd_q->ring_buff_count)
> + scan_cap = (uint16_t)cmd_q->ring_buff_count;
> +
> + while (events_count < scan_cap) {
> + uint8_t hw_status;
> + uint8_t hw_err;
> +
> + hw_desc = &cmd_q->qbase_desc[tail];
> + hw_status = hw_desc->dw1.status;
> + hw_err = hw_desc->dw1.err_code;
> +
> + /*
> + * read_idx advancing is the definitive completion
> + * signal. The per-descriptor status byte is informational
> + * and may not yet be written when we observe it:
> + *
> + * AE4DMA_DMA_DESC_ERROR (4)
> + * Hard failure - err_code names the precise cause.
> + * AE4DMA_DMA_DESC_COMPLETED (3) or 0
> + * Success.
> + * AE4DMA_DMA_DESC_VALIDATED (1) / _PROCESSED (2)
> + * Benign race: HW had not finished updating the
> + * status byte at the instant we read it. Since
> + * read_idx has moved past this slot, treat it as
> + * success unless err_code says otherwise.
> + *
> + * A non-zero err_code is treated as a failure regardless
> + * of the observed status value.
> + */
> + if (hw_status == AE4DMA_DMA_DESC_ERROR ||
> + hw_err != AE4DMA_DMA_ERR_NO_ERR) {
> + fails++;
> + AE4DMA_PMD_WARN("Desc failed: status=%u err=%u",
> + hw_status, hw_err);
> + }
> + cmd_q->status[events_count] = (enum ae4dma_dma_err)hw_err;
> + cmd_q->ring_buff_count--;
> + events_count++;
> + tail = (tail + 1) & mask;
> + }
> +
> + cmd_q->stats.completed += events_count;
> + cmd_q->stats.errors += fails;
> + cmd_q->next_read = tail;
> + *failed_count = fails;
> + return events_count;
> +}
> +
> +/* Returns successful operations count and sets error flag if any errors. */
> +static uint16_t
> +ae4dma_completed(void *dev_private, uint16_t vchan __rte_unused,
> + const uint16_t max_ops, uint16_t *last_idx, bool *has_error)
> +{
> + struct ae4dma_dmadev *ae4dma = dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint16_t cpl_count, sl_count;
> + uint16_t err_count = 0;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> +
> + *has_error = false;
> +
> + cpl_count = ae4dma_scan_hwq(cmd_q, max_ops, &err_count);
> +
> + if (cpl_count > max_ops)
> + cpl_count = max_ops;
> +
> + if (cpl_count > 0 && last_idx != NULL)
> + *last_idx = (uint16_t)((cmd_q->next_read - 1 + nb) % nb);
> +
> + sl_count = cpl_count - err_count;
> + if (err_count)
> + *has_error = true;
> +
> + return sl_count;
> +}
> +
> +static uint16_t
> +ae4dma_completed_status(void *dev_private, uint16_t vchan __rte_unused,
> + uint16_t max_ops, uint16_t *last_idx,
> + enum rte_dma_status_code *status)
> +{
> + struct ae4dma_dmadev *ae4dma = dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint16_t cpl_count;
> + uint16_t i;
> + uint16_t err_count = 0;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> +
> + cpl_count = ae4dma_scan_hwq(cmd_q, max_ops, &err_count);
> +
> + if (cpl_count > max_ops)
> + cpl_count = max_ops;
> +
> + if (cpl_count > 0 && last_idx != NULL)
> + *last_idx = (uint16_t)((cmd_q->next_read - 1 + nb) % nb);
> +
> + if (likely(err_count == 0)) {
> + for (i = 0; i < cpl_count; i++)
> + status[i] = RTE_DMA_STATUS_SUCCESSFUL;
> + } else {
> + for (i = 0; i < cpl_count; i++)
> + status[i] = __translate_status_ae4dma_to_dma(cmd_q->status[i]);
> + }
> +
> + return cpl_count;
> +}
> +
> +/* Get the remaining capacity of the ring. */
> +static uint16_t
> +ae4dma_burst_capacity(const void *dev_private, uint16_t vchan __rte_unused)
> +{
> + const struct ae4dma_dmadev *ae4dma = dev_private;
> + const struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint16_t nb = cmd_q->qcfg.nb_desc;
> + uint16_t mask;
> + uint16_t read_idx = cmd_q->next_read;
> + uint16_t write_idx = cmd_q->next_write;
> + uint16_t used;
> +
> + if (nb < 2 || !rte_is_power_of_2(nb))
> + return 0;
> +
> + mask = nb - 1;
> + used = (uint16_t)((write_idx - read_idx) & mask);
> + /* One slot reserved (same rule as enqueue). */
> + if (used >= nb - 1)
> + return 0;
> + return (uint16_t)(nb - 1 - used);
> +}
> +
> +/* Retrieve the generic stats of a DMA device. */
> +static int
> +ae4dma_stats_get(const struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
> + struct rte_dma_stats *rte_stats, uint32_t size)
> +{
> + const struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + const struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + const struct rte_dma_stats *stats = &cmd_q->stats;
> +
> + if (size < sizeof(*rte_stats))
> + return -EINVAL;
> + if (rte_stats == NULL)
> + return -EINVAL;
> +
> + *rte_stats = *stats;
> + return 0;
> +}
> +
> +/* Reset the generic stat counters for the DMA device. */
> +static int
> +ae4dma_stats_reset(struct rte_dma_dev *dev, uint16_t vchan __rte_unused)
> +{
> + struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> +
> + memset(&cmd_q->stats, 0, sizeof(cmd_q->stats));
> + return 0;
> +}
> +
> +/*
> + * Report channel state to the dmadev framework.
> + *
> + * RTE_DMA_VCHAN_HALTED_ERROR - HW queue is disabled (never started, or
> + * stopped via dev_stop()).
> + * RTE_DMA_VCHAN_IDLE - HW has caught up: read_idx == write_idx,
> + * no descriptors in flight.
> + * RTE_DMA_VCHAN_ACTIVE - HW still has descriptors to process.
> + */
> +static int
> +ae4dma_vchan_status(const struct rte_dma_dev *dev, uint16_t vchan __rte_unused,
> + enum rte_dma_vchan_status *status)
> +{
> + const struct ae4dma_dmadev *ae4dma = dev->fp_obj->dev_private;
> + const struct ae4dma_cmd_queue *cmd_q = &ae4dma->cmd_q;
> + uint32_t ctrl, hw_read, hw_write;
> +
> + if (cmd_q->hwq_regs == NULL) {
> + *status = RTE_DMA_VCHAN_HALTED_ERROR;
> + return 0;
> + }
> +
> + ctrl = AE4DMA_READ_REG(&cmd_q->hwq_regs->control_reg.control_raw);
> + if ((ctrl & AE4DMA_CMD_QUEUE_ENABLE) == 0) {
> + *status = RTE_DMA_VCHAN_HALTED_ERROR;
> + return 0;
> + }
> +
> + hw_read = AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx);
> + hw_write = AE4DMA_READ_REG(&cmd_q->hwq_regs->write_idx);
> +
> + *status = (hw_read == hw_write) ? RTE_DMA_VCHAN_IDLE
> + : RTE_DMA_VCHAN_ACTIVE;
> + return 0;
> +}
> +
> +static int
> +ae4dma_add_queue(struct ae4dma_dmadev *dev, uint8_t qn, const char *pci_name)
> +{
> + uint32_t dma_addr_lo, dma_addr_hi;
> + struct ae4dma_cmd_queue *cmd_q;
> + const struct rte_memzone *q_mz;
> +
> + if (dev == NULL)
> + return -EINVAL;
dev can't be NULL.
The only caller passes a pointer that was already dereferenced.
> +
> + dev->io_regs = dev->pci->mem_resource[AE4DMA_PCIE_BAR].addr;
> +
> + cmd_q = &dev->cmd_q;
> + cmd_q->id = qn;
> + cmd_q->qidx = 0;
> + cmd_q->qsize = AE4DMA_QUEUE_SIZE(AE4DMA_QUEUE_DESC_SIZE);
> + cmd_q->hwq_regs = (volatile struct ae4dma_hwq_regs *)dev->io_regs + (qn + 1);
> +
> + /*
> + * Memzone name must be globally unique. Embed PCI BDF so multiple
> + * PCI functions probed concurrently don't collide.
> + */
> + snprintf(cmd_q->memz_name, sizeof(cmd_q->memz_name),
> + "ae4dma_%s_q%u", pci_name, (unsigned int)qn);
> +
> + q_mz = ae4dma_queue_dma_zone_reserve(cmd_q->memz_name,
> + cmd_q->qsize, rte_socket_id());
> + if (q_mz == NULL) {
> + AE4DMA_PMD_ERR("memzone reserve failed for %s", cmd_q->memz_name);
> + return -ENOMEM;
> + }
> +
> + cmd_q->qbase_addr = (void *)q_mz->addr;
> + cmd_q->qbase_desc = (struct ae4dma_desc *)q_mz->addr;
> + cmd_q->qbase_phys_addr = q_mz->iova;
> +
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->max_idx, AE4DMA_DESCRIPTORS_PER_CMDQ);
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->control_reg.control_raw,
> + AE4DMA_CMD_QUEUE_ENABLE);
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->intr_status_reg.intr_status_raw,
> + AE4DMA_DISABLE_INTR);
> + cmd_q->next_write = (uint16_t)AE4DMA_READ_REG(&cmd_q->hwq_regs->write_idx);
> + cmd_q->next_read = (uint16_t)AE4DMA_READ_REG(&cmd_q->hwq_regs->read_idx);
> + cmd_q->ring_buff_count = 0;
> +
> + dma_addr_lo = low32_value(cmd_q->qbase_phys_addr);
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->qbase_lo, dma_addr_lo);
> + dma_addr_hi = high32_value(cmd_q->qbase_phys_addr);
> + AE4DMA_WRITE_REG(&cmd_q->hwq_regs->qbase_hi, dma_addr_hi);
> +
> + return 0;
> +}
> +
> +static void
> +ae4dma_channel_dev_name(char *out, size_t outlen, const char *pci_name,
> + unsigned int ch)
> +{
> + snprintf(out, outlen, "%s-ch%u", pci_name, ch);
> +}
> +
> +/* Create a dmadev(dpdk DMA device) */
> +static int
> +ae4dma_dmadev_create(const char *name, struct rte_pci_device *dev, uint8_t qn)
> +{
> + static const struct rte_dma_dev_ops ae4dma_dmadev_ops = {
> + .dev_close = ae4dma_dev_close,
> + .dev_configure = ae4dma_dev_configure,
> + .dev_dump = ae4dma_dev_dump,
> + .dev_info_get = ae4dma_dev_info_get,
> + .dev_start = ae4dma_dev_start,
> + .dev_stop = ae4dma_dev_stop,
> + .stats_get = ae4dma_stats_get,
> + .stats_reset = ae4dma_stats_reset,
> + .vchan_status = ae4dma_vchan_status,
> + .vchan_setup = ae4dma_vchan_setup,
> + };
> +
> + struct rte_dma_dev *dmadev = NULL;
> + struct ae4dma_dmadev *ae4dma = NULL;
> + char hwq_dev_name[RTE_DEV_NAME_MAX_LEN];
> +
> + if (!name) {
> + AE4DMA_PMD_ERR("Invalid name of the device!");
> + return -EINVAL;
> + }
> + memset(hwq_dev_name, 0, sizeof(hwq_dev_name));
> + ae4dma_channel_dev_name(hwq_dev_name, sizeof(hwq_dev_name), name, qn);
> +
> + dmadev = rte_dma_pmd_allocate(hwq_dev_name, dev->device.numa_node,
> + sizeof(struct ae4dma_dmadev));
> + if (dmadev == NULL) {
> + AE4DMA_PMD_ERR("Unable to allocate dma device");
> + return -ENOMEM;
> + }
> + dmadev->device = &dev->device;
> + dmadev->fp_obj->dev_private = dmadev->data->dev_private;
> + dmadev->dev_ops = &ae4dma_dmadev_ops;
> +
> + dmadev->fp_obj->burst_capacity = ae4dma_burst_capacity;
> + dmadev->fp_obj->completed = ae4dma_completed;
> + dmadev->fp_obj->completed_status = ae4dma_completed_status;
> + dmadev->fp_obj->copy = ae4dma_enqueue_copy;
> + dmadev->fp_obj->submit = ae4dma_submit;
> + /* fill capability not advertised: leave fp_obj->fill as zero-initialised. */
> +
> + ae4dma = dmadev->data->dev_private;
> + ae4dma->dmadev = dmadev;
> + ae4dma->pci = dev;
> +
> + if (ae4dma_add_queue(ae4dma, qn, name) != 0)
> + goto init_error;
> + return 0;
> +
> +init_error:
> + AE4DMA_PMD_ERR("driver %s(): failed", __func__);
> + rte_dma_pmd_release(hwq_dev_name);
> + return -EFAULT;
ENOMEM or the value returned from ae4dma_add_queue.
> +}
> +
[snip]
> diff --git a/drivers/dma/ae4dma/ae4dma_hw_defs.h b/drivers/dma/ae4dma/ae4dma_hw_defs.h
> new file mode 100644
> index 0000000000..235819778e
> --- /dev/null
> +++ b/drivers/dma/ae4dma/ae4dma_hw_defs.h
> @@ -0,0 +1,164 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2024 Advanced Micro Devices, Inc. All rights reserved.
> + */
> +
> +#ifndef __AE4DMA_HW_DEFS_H__
> +#define __AE4DMA_HW_DEFS_H__
> +
> +#include <rte_bus_pci.h>
> +#include <rte_byteorder.h>
> +#include <rte_io.h>
> +#include <rte_pci.h>
> +#include <rte_memzone.h>
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +#define AE4DMA_BIT(nr) (1UL << (nr))
> +
> +#define AE4DMA_BITS_PER_LONG (__SIZEOF_LONG__ * 8)
> +#define AE4DMA_GENMASK(h, l) \
> + (((~0UL) << (l)) & (~0UL >> (AE4DMA_BITS_PER_LONG - 1 - (h))))
We have rte_bitops.h macros for bit manipulations, please reuse.
> +
> +/* ae4dma device details */
> +#define AMD_VENDOR_ID 0x1022
> +#define AE4DMA_DEVICE_ID 0x149b
> +#define AE4DMA_PCIE_BAR 0
> +
[snip]
> diff --git a/usertools/dpdk-devbind.py b/usertools/dpdk-devbind.py
> index 93f2383dff..ec6d6713b4 100755
> --- a/usertools/dpdk-devbind.py
> +++ b/usertools/dpdk-devbind.py
> @@ -86,6 +86,9 @@
> cn9k_ree = {'Class': '08', 'Vendor': '177d', 'Device': 'a0f4',
> 'SVendor': None, 'SDevice': None}
>
> +amd_ae4dma = {'Class': '08', 'Vendor': '1022', 'Device': '149b',
> + 'SVendor': None, 'SDevice': None}
> +
Indent looks odd.
> virtio_blk = {'Class': '01', 'Vendor': "1af4", 'Device': '1001,1042',
> 'SVendor': None, 'SDevice': None}
>
> @@ -95,7 +98,7 @@
> network_devices = [network_class, cavium_pkx, avp_vnic, ifpga_class]
> baseband_devices = [acceleration_class]
> crypto_devices = [encryption_class, intel_processor_class]
> -dma_devices = [cnxk_dma, hisilicon_dma,
> +dma_devices = [amd_ae4dma, cnxk_dma, hisilicon_dma,
> intel_idxd_gnrd, intel_idxd_dmr, intel_idxd_spr,
> intel_ioat_bdw, intel_ioat_icx, intel_ioat_skx,
> odm_dma]
--
David Marchand
^ permalink raw reply
* Re: [PATCH v2 2/2] ethdev: add telemetry endpoint for list names
From: Stephen Hemminger @ 2026-05-21 14:37 UTC (permalink / raw)
To: Morten Brørup
Cc: fengchengwen, Bruce Richardson, thomas, dev, andrew.rybchenko
In-Reply-To: <98CBD80474FA8B44BF855DF32C47DC35F6588F@smartserver.smartshare.dk>
On Thu, 21 May 2026 14:40:47 +0200
Morten Brørup <mb@smartsharesystems.com> wrote:
> > From: fengchengwen [mailto:fengchengwen@huawei.com]
> > Sent: Thursday, 21 May 2026 14.25
> >
> > Thanks for the feedback.
> >
> > I intend to keep the current dict format. This concise ID-name mapping
> > is quite
> > helpful and easy to read especially when there are massive ports, which
> > is exactly
> > the main purpose why I submitted this patch.
> >
> > In my opinion, adopting OData-style query would require architecture-
> > level
> > refactoring of telemetry framework, which is way too heavy for this
> > simple requirement.
>
> Agree.
> Refactoring the telemetry framework is different task, not related to this patch.
>
> > For complex query demands, we can implement them by extending the
> > upper-layer Python
> > telemetry script instead.
> >
> > So I suggest we keep this simple form here.
>
> If it is generally acceptable for DPDK telemetry that a request for a list does not return a list type, but returns an object type with "index": "value" fields instead, then
> Series-acked-by: Morten Brørup <mb@smartsharesystems.com>
It is necessary since port list may have holes due to hotplug or the ownership API.
It would be good to have a more complete query function that returns more about each ethdev.
I wouldn't worry about the size of the response. This is JSON and it is meant to be read by scripts not directly by humans.
^ permalink raw reply
* Re: [PATCH] Windows: fix core count on NUMA with more than 64 cores per node
From: David Marchand @ 2026-05-21 14:51 UTC (permalink / raw)
To: Dmitry Kozlyuk, Andre Muezerie; +Cc: dev, Gena Tertychnyi
In-Reply-To: <20260513235518.408895-1-andremue@linux.microsoft.com>
Hello,
On Thu, 14 May 2026 at 01:56, Andre Muezerie
<andremue@linux.microsoft.com> wrote:
>
> From: Gena Tertychnyi <genter@microsoft.com>
>
> Fix specific to Windows NUMA machines with more than 64 cores per node
> (e.g. 2 NUMAs with 128 cores each):
>
> - NumaNode.GroupMasks[] array is used instead of NumaNode.GroupMask.
> - RelationAll is used instead of RelationNumaNode when calling
> GetLogicalProcessorInformationEx because RelationAll returns the full
> multi-group NUMA affinity as RelationNumaNode returns only the NUMA
> node's primary group.
Probably worth a Fixes: tag.
>
> Signed-off-by: Gena Tertychnyi <genter@microsoft.com>
> Signed-off-by: Andre Muezerie <andremue@linux.microsoft.com>
Dmitry, any objection?
--
David Marchand
^ permalink raw reply
* Re: [PATCH v6 3/3] test: add test for af_packet
From: Thomas Monjalon @ 2026-05-21 14:54 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
In-Reply-To: <20260429182055.398105-4-stephen@networkplumber.org>
29/04/2026 20:19, Stephen Hemminger:
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <inttypes.h>
> +#include <linux/if.h>
> +#include <linux/if_tun.h>
> +#include <linux/sockios.h>
> +#include <pthread.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/ioctl.h>
> +#include <sys/socket.h>
There is a compilation failure for ppc about sockaddr.
It seems sys/socket.h must be included before linux/if.h.
I'll do the change while pulling.
^ permalink raw reply
* Re: [PATCH v19 00/11]net/sxe2: fix logic errors and address feedback
From: Thomas Monjalon @ 2026-05-21 15:16 UTC (permalink / raw)
To: Jie Liu; +Cc: stephen, dev
In-Reply-To: <20260520021809.4019054-1-liujie5@linkdatatechnology.com>
Hello,
20/05/2026 04:17, liujie5@linkdatatechnology.com:
> common/sxe2: add sxe2 basic structures
Are you planning to add a crypto or compress driver?
This is usually the reason to have a common library.
If you don't intend to share some code between different driver,
then you should not have a common library.
^ permalink raw reply
* Re: [PATCH v2 2/2] ethdev: add telemetry endpoint for list names
From: Bruce Richardson @ 2026-05-21 15:21 UTC (permalink / raw)
To: Stephen Hemminger
Cc: Morten Brørup, fengchengwen, thomas, dev, andrew.rybchenko
In-Reply-To: <20260521073716.3a788175@phoenix.local>
On Thu, May 21, 2026 at 07:37:16AM -0700, Stephen Hemminger wrote:
> On Thu, 21 May 2026 14:40:47 +0200
> Morten Brørup <mb@smartsharesystems.com> wrote:
>
> > > From: fengchengwen [mailto:fengchengwen@huawei.com]
> > > Sent: Thursday, 21 May 2026 14.25
> > >
> > > Thanks for the feedback.
> > >
> > > I intend to keep the current dict format. This concise ID-name mapping
> > > is quite
> > > helpful and easy to read especially when there are massive ports, which
> > > is exactly
> > > the main purpose why I submitted this patch.
> > >
> > > In my opinion, adopting OData-style query would require architecture-
> > > level
> > > refactoring of telemetry framework, which is way too heavy for this
> > > simple requirement.
> >
> > Agree.
> > Refactoring the telemetry framework is different task, not related to this patch.
> >
> > > For complex query demands, we can implement them by extending the
> > > upper-layer Python
> > > telemetry script instead.
> > >
> > > So I suggest we keep this simple form here.
> >
> > If it is generally acceptable for DPDK telemetry that a request for a list does not return a list type, but returns an object type with "index": "value" fields instead, then
> > Series-acked-by: Morten Brørup <mb@smartsharesystems.com>
>
> It is necessary since port list may have holes due to hotplug or the ownership API.
> It would be good to have a more complete query function that returns more about each ethdev.
> I wouldn't worry about the size of the response. This is JSON and it is meant to be read by scripts not directly by humans.
That's where I think we have a difference - if the output is meant to be
read by scripts, we should not need extra commands like this, since the
information is already available via existing commands. The only compelling
reason that I can see for adding a new command to return a set of ethdev
names is for human interactive use.
Personally, I think the output should be just used by other scripts, but
it does appear that this quick-and-dirty telemetry script is being used
extensively for human interactive querying. Therefore, I'm working on
extending the script to make it more useful for such cases. I'd prefer to
add the extra smarts into the script rather than seeing a proliferation of
endpoints providing the same data in different formats.
/Bruce
^ permalink raw reply
* Re: [PATCH] Windows: fix core count on NUMA with more than 64 cores per node
From: Dmitry Kozlyuk @ 2026-05-21 15:26 UTC (permalink / raw)
To: David Marchand, Andre Muezerie; +Cc: dev, Gena Tertychnyi
In-Reply-To: <CAJFAV8yhmQ8hEpMmUWW77kAyuJXb-Ru6z1kBnHLSBhcZ_LfEhg@mail.gmail.com>
Fixes: b70a9b788625 ("eal: get/set thread affinity per thread identifier")
Cc: stable@dpdk.org
Acked-by: Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>
I believe it really fixes my commit b8a36b086625 ("eal/windows: improve
CPU and
NUMA node detection"), but the patch will only apply to Tyler's commit
and it's far enough for any LTS.
Nit: EAL_NUMA_GROUP_COUNT is not needed because GroupCount handling
is source-compatible between MinGW and Windows SDK,
and there is logic to handle GroupCount == 0 for older Windows anyway.
^ permalink raw reply
* [PATCH 0/2] extend interactive telemetry script
From: Bruce Richardson @ 2026-05-21 15:39 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
To simplify interactive telemetry script for general use, i.e. not from
other scripts, we can add two new features to it:
1. Support for FOREACH to allow gathering a set of output values across
a list of ports or devices, e.g. ethdevs or rawdevs.
2. Support having predefined aliases in a file in the user's home
directory to simplify the use of more complicated FOREACH commands.
Putting these together, we can create new commands such as "eth_names".
bruce@host:$ cat ~/.dpdk_telemetry_aliases
eth_names=FOREACH index /ethdev/list /ethdev/info,$index .name
bruce@host:$ echo eth_names | ./usertools/dpdk-telemetry.py | jq
[
{
"index": 0,
"name": "0000:16:00.0"
},
{
"index": 1,
"name": "0000:16:00.1"
}
]
Bruce Richardson (2):
usertools/telemetry: add a FOREACH command
usertools/telemetry: support using aliases for long commands
doc/guides/howto/telemetry.rst | 76 +++++++++++++
usertools/dpdk-telemetry.py | 201 ++++++++++++++++++++++++++++++++-
2 files changed, 271 insertions(+), 6 deletions(-)
--
2.53.0
^ permalink raw reply
* [PATCH 1/2] usertools/telemetry: add a FOREACH command
From: Bruce Richardson @ 2026-05-21 15:39 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
In-Reply-To: <20260521153913.82634-1-bruce.richardson@intel.com>
To simplify querying data from multiple devices, e.g. across ethdevs, or
dmadevs, add a FOREACH command to the python script, allowing you to
run, e.g. /ethdev/list, and then run a second command for each item in
the list, gathering the relevant output values, optionally including an
index counter.
Simple examples are given in the documentation:
--> FOREACH /ethdev/list /ethdev/stats .opackets
[0, 0]
--> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets
[{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}]
--> FOREACH i /ethdev/list /ethdev/info,$i .name
[{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}]
--> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets
[{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}]
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
doc/guides/howto/telemetry.rst | 42 +++++++++++
usertools/dpdk-telemetry.py | 128 ++++++++++++++++++++++++++++++++-
2 files changed, 167 insertions(+), 3 deletions(-)
diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst
index 0464c431fe..4bf48c635e 100644
--- a/doc/guides/howto/telemetry.rst
+++ b/doc/guides/howto/telemetry.rst
@@ -88,6 +88,48 @@ and query information using the telemetry client python script.
{"/help": {"/ethdev/xstats": "Returns the extended stats for a port.
Parameters: int port_id"}}
+ * Run a compound query using ``FOREACH``.
+
+ The ``FOREACH`` command runs a list command, iterates each returned item,
+ runs a second command for each item, and emits combined JSON output.
+
+ Start with the simplest form (no loop variable)::
+
+ FOREACH /<list_cmd> /<iter_cmd> .<field> [.<field> ...]
+
+ To include numbered output, use a loop variable::
+
+ FOREACH <var> /<list_cmd> /<iter_cmd_with_$var> .<field> [.<field> ...]
+
+ Notes:
+
+ - Field selectors are whitespace-separated tokens, each starting with ``.``.
+ - In no-variable mode, the iter command is called as ``/<iter_cmd>,<item>``.
+ - In loop-variable mode, use ``$<var>`` in the iter command where the
+ item value should be substituted.
+
+ Examples::
+
+ --> FOREACH /ethdev/list /ethdev/stats .opackets
+ [0, 0]
+
+ --> FOREACH /ethdev/list /ethdev/stats .ipackets .opackets
+ [{"ipackets": 0, "opackets": 0}, {"ipackets": 0, "opackets": 0}]
+
+ --> FOREACH i /ethdev/list /ethdev/info,$i .name
+ [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}]
+
+ --> FOREACH i /ethdev/list /ethdev/stats,$i .ipackets .opackets
+ [{"i": 0, "ipackets": 0, "opackets": 0}, {"i": 1, "ipackets": 0, "opackets": 0}]
+
+ Output behavior:
+
+ - Without loop variable and one field: returns an array of values.
+ - Without loop variable and multiple fields: returns an array of objects
+ containing named value fields.
+ - With loop variable: returns an array of objects containing the loop
+ variable field and requested value fields.
+
Connecting to Different DPDK Processes
--------------------------------------
diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py
index 09258a1f7e..2de10cff69 100755
--- a/usertools/dpdk-telemetry.py
+++ b/usertools/dpdk-telemetry.py
@@ -23,6 +23,130 @@
CMDS = []
+def send_command(sock, cmd, output_buf_len, echo=False, pretty=False):
+ """Send a telemetry command and return the parsed JSON reply"""
+ sock.send(cmd.encode())
+ return read_socket(sock, output_buf_len, echo, pretty)
+
+
+def get_cmd_payload(reply, cmd):
+ """Return the payload for a command response if present"""
+ if isinstance(reply, dict) and len(reply) == 1:
+ return next(iter(reply.values()))
+ return None
+
+
+def get_path_value(payload, path):
+ """Resolve a dotted path (e.g. '.name' or '.a.b') from a JSON payload"""
+ if not path:
+ return payload
+
+ keys = [k for k in path.lstrip(".").split(".") if k]
+ val = payload
+ for key in keys:
+ if not isinstance(val, dict) or key not in val:
+ return None
+ val = val[key]
+ return val
+
+
+def parse_selectors(selector_text):
+ """Parse whitespace-separated dotted selectors"""
+ selectors = selector_text.split()
+ if not selectors:
+ print("Invalid FOREACH syntax: missing selector")
+ return None
+ if any(not selector.startswith(".") for selector in selectors):
+ print("Invalid FOREACH syntax: selector must start with '.'")
+ return None
+ return selectors
+
+
+def parse_foreach(text):
+ """Parse FOREACH [<var>] /<cmd> /<parameterized cmd> .<value> [.<value> ...]"""
+ try:
+ tokens = text.split(None, 3)
+ except ValueError:
+ print("Invalid FOREACH syntax")
+ return None
+
+ if len(tokens) != 4:
+ print("Invalid FOREACH syntax")
+ return None
+
+ _, arg1, arg2, arg3 = tokens
+ if arg1.startswith("/"):
+ var_name = None
+ list_cmd = arg1
+ iter_cmd = arg2
+ selector_text = arg3
+ else:
+ var_name = arg1
+ list_cmd = arg2
+ try:
+ iter_cmd, selector_text = arg3.split(None, 1)
+ except ValueError:
+ print("Invalid FOREACH syntax")
+ return None
+
+ if not list_cmd.startswith("/") or not iter_cmd.startswith("/"):
+ print("Invalid FOREACH syntax: commands must start with '/'")
+ return None
+
+ selectors = parse_selectors(selector_text)
+ if selectors is None:
+ return None
+
+ return var_name, list_cmd, iter_cmd, selectors
+
+
+def build_foreach_result(item, var_name, payload, selectors):
+ """Build one FOREACH result entry based on selector count and index mode"""
+ values = {selector.lstrip("."): get_path_value(payload, selector) for selector in selectors}
+
+ if var_name is None and len(selectors) == 1:
+ return next(iter(values.values()))
+ if var_name is None:
+ return values
+
+ return {var_name: item, **values}
+
+
+def handle_foreach(sock, output_buf_len, text, pretty=False):
+ """Handle FOREACH queries and print telemetry-like JSON array output"""
+ parsed = parse_foreach(text)
+ if parsed is None:
+ return
+ var_name, list_cmd, iter_cmd, selectors = parsed
+
+ list_reply = send_command(sock, list_cmd, output_buf_len)
+ values = get_cmd_payload(list_reply, list_cmd)
+ if not isinstance(values, list):
+ print("FOREACH source command did not return a JSON array")
+ return
+
+ output = []
+ for item in values:
+ if var_name is None:
+ cmd = "{},{}".format(iter_cmd, item)
+ else:
+ cmd = iter_cmd.replace("$" + var_name, str(item))
+ item_reply = send_command(sock, cmd, output_buf_len)
+ item_payload = get_cmd_payload(item_reply, cmd)
+ output.append(build_foreach_result(item, var_name, item_payload, selectors))
+
+ indent = 2 if pretty else None
+ print(json.dumps(output, indent=indent))
+
+
+def handle_command(sock, output_buf_len, text, pretty=False):
+ """Execute a user command if recognized"""
+ if text.startswith("/"):
+ send_command(sock, text, output_buf_len, echo=True, pretty=pretty)
+ elif text.startswith("FOREACH "):
+ handle_foreach(sock, output_buf_len, text, pretty)
+
+
def read_socket(sock, buf_len, echo=True, pretty=False):
"""Read data from socket and return it in JSON format"""
reply = sock.recv(buf_len).decode()
@@ -140,9 +264,7 @@ def handle_socket(args, path):
try:
text = input(prompt).strip()
while text != "quit":
- if text.startswith("/"):
- sock.send(text.encode())
- read_socket(sock, output_buf_len, pretty=prompt)
+ handle_command(sock, output_buf_len, text, pretty=prompt)
text = input(prompt).strip()
except EOFError:
pass
--
2.53.0
^ permalink raw reply related
* [PATCH 2/2] usertools/telemetry: support using aliases for long commands
From: Bruce Richardson @ 2026-05-21 15:39 UTC (permalink / raw)
To: dev; +Cc: Bruce Richardson
In-Reply-To: <20260521153913.82634-1-bruce.richardson@intel.com>
Similarly to how shell aliases work, allow specifying of short alias
commands for dpdk-telemetry.py script. The aliases are read from
"$HOME/.dpdk_telemetry_aliases" at startup.
Some examples of use from the docs. Alias file contents:
# Basic shortcuts
ls=/ethdev/list
names=FOREACH i /ethdev/list /ethdev/info,$i .name
q=quit
Alias use:
--> ls
{"/ethdev/list": [0, 1]}
--> names
[{"i": 0, "name": "0000:
Signed-off-by: Bruce Richardson <bruce.richardson@intel.com>
---
doc/guides/howto/telemetry.rst | 34 +++++++++++++++
usertools/dpdk-telemetry.py | 75 ++++++++++++++++++++++++++++++++--
2 files changed, 105 insertions(+), 4 deletions(-)
diff --git a/doc/guides/howto/telemetry.rst b/doc/guides/howto/telemetry.rst
index 4bf48c635e..072289aec8 100644
--- a/doc/guides/howto/telemetry.rst
+++ b/doc/guides/howto/telemetry.rst
@@ -130,6 +130,40 @@ and query information using the telemetry client python script.
- With loop variable: returns an array of objects containing the loop
variable field and requested value fields.
+ * Use command aliases.
+
+ The telemetry script can load aliases at startup from::
+
+ $HOME/.dpdk_telemetry_aliases
+
+ Each alias entry must be in ``alias=command`` format.
+ Empty lines and lines starting with ``#`` are ignored.
+
+ Example alias file::
+
+ # Basic shortcuts
+ ls=/ethdev/list
+ names=FOREACH i /ethdev/list /ethdev/info,$i .name
+ q=quit
+
+ Alias behavior is intentionally similar to shell aliases:
+
+ - The first token of the entered input is checked for an alias match.
+ - If matched, that first token is replaced with its expansion.
+ - Alias expansion is recursive (aliases can expand to other aliases).
+ - Expansion has a safety limit to prevent infinite loops.
+
+ Examples::
+
+ --> ls
+ {"/ethdev/list": [0, 1]}
+
+ --> names
+ [{"i": 0, "name": "0000:16:00.0"}, {"i": 1, "name": "0000:16:00.1"}]
+
+ --> q
+ # exits the client
+
Connecting to Different DPDK Processes
--------------------------------------
diff --git a/usertools/dpdk-telemetry.py b/usertools/dpdk-telemetry.py
index 2de10cff69..8b976160e0 100755
--- a/usertools/dpdk-telemetry.py
+++ b/usertools/dpdk-telemetry.py
@@ -21,6 +21,70 @@
SOCKET_NAME = "dpdk_telemetry.{}".format(TELEMETRY_VERSION)
DEFAULT_PREFIX = "rte"
CMDS = []
+ALIASES = {}
+ALIAS_FILE = ".dpdk_telemetry_aliases"
+MAX_ALIAS_EXPANSIONS = 32
+
+
+def load_aliases():
+ """Load aliases from $HOME/.dpdk_telemetry_aliases"""
+ aliases = {}
+ home = os.environ.get("HOME")
+ if not home:
+ return aliases
+
+ alias_path = os.path.join(home, ALIAS_FILE)
+ if not os.path.isfile(alias_path):
+ return aliases
+
+ try:
+ with open(alias_path) as alias_file:
+ for line_num, line in enumerate(alias_file, start=1):
+ entry = line.strip()
+ if not entry or entry.startswith("#"):
+ continue
+ if "=" not in entry:
+ print(
+ "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num),
+ file=sys.stderr,
+ )
+ continue
+ name, command = entry.split("=", 1)
+ name = name.strip()
+ command = command.strip()
+ if not name or not command:
+ print(
+ "Warning: ignoring malformed alias at {}:{}".format(alias_path, line_num),
+ file=sys.stderr,
+ )
+ continue
+ aliases[name] = command
+ except OSError as e:
+ print("Warning: failed to read {}: {}".format(alias_path, e), file=sys.stderr)
+
+ return aliases
+
+
+def expand_aliases(text, aliases):
+ """Expand aliases similarly to shell aliases on the first token"""
+ expanded = text
+ for _ in range(MAX_ALIAS_EXPANSIONS):
+ stripped = expanded.lstrip()
+ if not stripped:
+ return expanded
+
+ parts = stripped.split(None, 1)
+ first = parts[0]
+ rest = parts[1] if len(parts) > 1 else ""
+
+ if first not in aliases:
+ return expanded
+
+ alias_value = aliases[first]
+ expanded = "{} {}".format(alias_value, rest).strip() if rest else alias_value
+
+ print("Warning: alias expansion limit reached", file=sys.stderr)
+ return expanded
def send_command(sock, cmd, output_buf_len, echo=False, pretty=False):
@@ -262,10 +326,12 @@ def handle_socket(args, path):
# interactive prompt
try:
- text = input(prompt).strip()
- while text != "quit":
- handle_command(sock, output_buf_len, text, pretty=prompt)
+ while True:
text = input(prompt).strip()
+ expanded = expand_aliases(text, ALIASES)
+ if expanded == "quit":
+ break
+ handle_command(sock, output_buf_len, expanded, pretty=prompt)
except EOFError:
pass
finally:
@@ -274,7 +340,7 @@ def handle_socket(args, path):
def readline_complete(text, state):
"""Find any matching commands from the list based on user input"""
- all_cmds = ["quit"] + CMDS
+ all_cmds = ["quit"] + list(ALIASES.keys()) + CMDS
if text:
matches = [c for c in all_cmds if c.startswith(text)]
else:
@@ -304,6 +370,7 @@ def readline_complete(text, state):
help="List all possible file-prefixes and exit",
)
args = parser.parse_args()
+ALIASES = load_aliases()
if args.list:
list_fp()
sys.exit(0)
--
2.53.0
^ permalink raw reply related
* Re: [RFC 2/7] eal: reimplement rte_smp_*mb with rte_atomic_thread_fence
From: Wathsala Vithanage @ 2026-05-21 15:43 UTC (permalink / raw)
To: Stephen Hemminger, dev
Cc: Bibo Mao, David Christensen, Sun Yuechi, Bruce Richardson,
Konstantin Ananyev
In-Reply-To: <20260521042043.1590536-3-stephen@networkplumber.org>
Hi Stephen,
Suggesting minor changes to comments on acquire and
release fences..
> +/** @name SMP Memory Barrier
> + */
> +///@{
> +/**
> + * General memory barrier between lcores
> + *
> + * Guarantees that the LOAD and STORE operations that precede the
> + * rte_smp_mb() call are globally visible across the lcores
> + * before the LOAD and STORE operations that follows it.
> + */
> +static __rte_always_inline void
> +rte_smp_mb(void)
> +{
> + rte_atomic_thread_fence(rte_memory_order_seq_cst);
> +}
> +
> +/**
> + * Write memory barrier between lcores
> + *
> + * Guarantees that the STORE operations that precede the
> + * rte_smp_wmb() call are globally visible across the lcores
> + * before the STORE operations that follows it.
> + */
> +static __rte_always_inline void
> +rte_smp_wmb(void)
> +{
> + rte_atomic_thread_fence(rte_memory_order_release);
> +}
Release fences order STORE | STORE, and LOAD | STORE.
Therefor, the comment should be "Guarantees that LOAD
and STORE operations that precede the rte_smp_wmb() call
are globally observed before STORE operations that follows it."
> +
> +/**
> + * Read memory barrier between lcores
> + *
> + * Guarantees that the LOAD operations that precede the
> + * rte_smp_rmb() call are globally visible across the lcores
> + * before the LOAD operations that follows it.
> + */
> +static __rte_always_inline void
> +rte_smp_rmb(void)
> +{
> + rte_atomic_thread_fence(rte_memory_order_acquire);
> +}
Acquire fences order LOAD | LOAD and LOAD | STORE.
Thus, the comment should be "Guarantees that the LOAD
operations that precede the rte_smp_rmb() call observe
global state before LOAD and STORE operations that
follows it"
--wathsala
^ permalink raw reply
* Re: [PATCH v2 2/2] ethdev: add telemetry endpoint for list names
From: Bruce Richardson @ 2026-05-21 15:44 UTC (permalink / raw)
To: Stephen Hemminger
Cc: Morten Brørup, fengchengwen, thomas, dev, andrew.rybchenko
In-Reply-To: <ag8jF4E2RnQcFrYu@bricha3-mobl1.ger.corp.intel.com>
On Thu, May 21, 2026 at 04:21:59PM +0100, Bruce Richardson wrote:
> On Thu, May 21, 2026 at 07:37:16AM -0700, Stephen Hemminger wrote:
> > On Thu, 21 May 2026 14:40:47 +0200 Morten Brørup
> > <mb@smartsharesystems.com> wrote:
> >
> > > > From: fengchengwen [mailto:fengchengwen@huawei.com] Sent: Thursday,
> > > > 21 May 2026 14.25
> > > >
> > > > Thanks for the feedback.
> > > >
> > > > I intend to keep the current dict format. This concise ID-name
> > > > mapping is quite helpful and easy to read especially when there are
> > > > massive ports, which is exactly the main purpose why I submitted
> > > > this patch.
> > > >
> > > > In my opinion, adopting OData-style query would require
> > > > architecture- level refactoring of telemetry framework, which is
> > > > way too heavy for this simple requirement.
> > >
> > > Agree. Refactoring the telemetry framework is different task, not
> > > related to this patch.
> > >
> > > > For complex query demands, we can implement them by extending the
> > > > upper-layer Python telemetry script instead.
> > > >
> > > > So I suggest we keep this simple form here.
> > >
> > > If it is generally acceptable for DPDK telemetry that a request for a
> > > list does not return a list type, but returns an object type with
> > > "index": "value" fields instead, then Series-acked-by: Morten Brørup
> > > <mb@smartsharesystems.com>
> >
> > It is necessary since port list may have holes due to hotplug or the
> > ownership API. It would be good to have a more complete query function
> > that returns more about each ethdev. I wouldn't worry about the size
> > of the response. This is JSON and it is meant to be read by scripts not
> > directly by humans.
>
> That's where I think we have a difference - if the output is meant to be
> read by scripts, we should not need extra commands like this, since the
> information is already available via existing commands. The only
> compelling reason that I can see for adding a new command to return a set
> of ethdev names is for human interactive use.
>
> Personally, I think the output should be just used by other scripts, but
> it does appear that this quick-and-dirty telemetry script is being used
> extensively for human interactive querying. Therefore, I'm working on
> extending the script to make it more useful for such cases. I'd prefer to
> add the extra smarts into the script rather than seeing a proliferation
> of endpoints providing the same data in different formats.
>
Here [1] is my proposed generalised solution, quickly prototyped with copilot,
composed of two parts: firstly, support for querying values across a range
of ports using a foreach command, and then secondly, support for aliases to
make the use of those foreach commands easier on the user. It's not
intended as a mergable set of patches as-is, but rather to demonstrate how
we might be able to come up with a more general solution (that keeps to the
DRY principle) entirely within the python script rather than extending the
C code.
/Bruce
[1] https://patches.dpdk.org/project/dpdk/list/?series=38197
^ permalink raw reply
* Re: [RFC 3/7] ring: use C11 atomic operations for MP/SP head/tail
From: Wathsala Vithanage @ 2026-05-21 15:57 UTC (permalink / raw)
To: Stephen Hemminger, dev; +Cc: Konstantin Ananyev
In-Reply-To: <20260521042043.1590536-4-stephen@networkplumber.org>
[-- Attachment #1: Type: text/plain, Size: 5398 bytes --]
Already looks good. I have one minor suggestion.
In |rte_ring_c11_pvt.h| (and in the MCS lock code as well), we introduced
a comment style that annotates load-acquire and store-release
operations as |An| and |Rm|, respectively. Each |An| comment refers to the
corresponding |Rm| it synchronizes with, and vice versa, while also
describing
the intent of the pairing.
--wathsala
On 5/20/26 23:17, Stephen Hemminger wrote:
> Last caller of rte_atomic32_cmpset() in lib/, blocking deprecation
> of the rte_atomicNN_*() family.
>
> Replace cmpset with rte_atomic_compare_exchange_weak_explicit(),
> and convert head/tail loads/stores from implicit seq_cst to explicit
> acquire/release. Matches the HTS/RTS pattern.
>
> Acquire-load of d->head orders the subsequent load of s->tail (was
> rte_smp_rmb()). Acquire-load of s->tail pairs with the release-store
> of the counterpart tail in __rte_ring_update_tail(), which subsumes
> the previous wmb/rmb barriers.
>
> Weak CAS avoids arm64's hidden inner retry; the outer do-while already
> loops. CAS orderings relaxed: no data published by the reservation.
>
> The now-unused 'enqueue' parameter of __rte_ring_update_tail() is
> removed; both call sites updated.
>
> Signed-off-by: Stephen Hemminger<stephen@networkplumber.org>
> ---
> lib/ring/rte_ring_generic_pvt.h | 64 +++++++++++++++++++++++----------
> 1 file changed, 45 insertions(+), 19 deletions(-)
>
> diff --git a/lib/ring/rte_ring_generic_pvt.h b/lib/ring/rte_ring_generic_pvt.h
> index affd2d5ba7..9497f6737b 100644
> --- a/lib/ring/rte_ring_generic_pvt.h
> +++ b/lib/ring/rte_ring_generic_pvt.h
> @@ -23,21 +23,25 @@
> */
> static __rte_always_inline void
> __rte_ring_update_tail(struct rte_ring_headtail *ht, uint32_t old_val,
> - uint32_t new_val, uint32_t single, uint32_t enqueue)
> + uint32_t new_val, uint32_t single,
> + uint32_t enqueue __rte_unused)
> {
> - if (enqueue)
> - rte_smp_wmb();
> - else
> - rte_smp_rmb();
> /*
> * If there are other enqueues/dequeues in progress that preceded us,
> * we need to wait for them to complete
> */
> if (!single)
> - rte_wait_until_equal_32((volatile uint32_t *)(uintptr_t)&ht->tail, old_val,
> - rte_memory_order_relaxed);
> + rte_wait_until_equal_32((volatile uint32_t *)(uintptr_t)&ht->tail,
> + old_val, rte_memory_order_relaxed);
>
> - ht->tail = new_val;
> + /*
> + * Release ordering on the tail store ensures that the slot reads
> + * (dequeue) or writes (enqueue) performed by this thread are visible
> + * to the other side before the new tail value is observed.
> + * Pairs with the acquire load of the counterpart's tail in
> + * __rte_ring_headtail_move_head().
> + */
> + rte_atomic_store_explicit(&ht->tail, new_val, rte_memory_order_release);
> }
>
> /**
> @@ -76,25 +80,35 @@ __rte_ring_headtail_move_head(struct rte_ring_headtail *d,
> {
> unsigned int max = n;
> int success;
> + uint32_t tail;
>
> do {
> /* Reset n to the initial burst count */
> n = max;
>
> - *old_head = d->head;
> + /*
> + * Acquire load: orders this load before the load of s->tail
> + * below (replaces rte_smp_rmb() in the previous version) and
> + * re-establishes ordering after a failed CAS on retry.
> + */
> + *old_head = rte_atomic_load_explicit(&d->head,
> + rte_memory_order_acquire);
>
> - /* add rmb barrier to avoid load/load reorder in weak
> - * memory model. It is noop on x86
> + /*
> + * Acquire load on the counterpart's tail pairs with the
> + * release store in __rte_ring_update_tail() on the other
> + * side, ensuring slot operations performed there are visible
> + * before the caller accesses the reserved slots.
> */
> - rte_smp_rmb();
> + tail = rte_atomic_load_explicit(&s->tail, rte_memory_order_acquire);
>
> /*
> * The subtraction is done between two unsigned 32bits value
> * (the result is always modulo 32 bits even if we have
> - * *old_head > s->tail). So 'entries' is always between 0
> + * *old_head > tail). So 'entries' is always between 0
> * and capacity (which is < size).
> */
> - *entries = (capacity + s->tail - *old_head);
> + *entries = (capacity + tail - *old_head);
>
> /* check that we have enough room in ring */
> if (unlikely(n > *entries))
> @@ -106,12 +120,24 @@ __rte_ring_headtail_move_head(struct rte_ring_headtail *d,
>
> *new_head = *old_head + n;
> if (is_st) {
> - d->head = *new_head;
> + rte_atomic_store_explicit(&d->head, *new_head, rte_memory_order_relaxed);
> success = 1;
> - } else
> - success = rte_atomic32_cmpset(
> - (uint32_t *)(uintptr_t)&d->head,
> - *old_head, *new_head);
> + } else {
> + /*
> + * Weak CAS: the outer do-while handles spurious
> + * failures, so we avoid the strong variant's
> + * internal retry (which on arm64 wraps the LL/SC
> + * pair in a hidden inner loop).
> + *
> + * Relaxed on both success and failure: this CAS
> + * does not publish data. Slot data visibility is
> + * provided by the acquire loads above and the
> + * release store of tail in __rte_ring_update_tail().
> + */
> + success = rte_atomic_compare_exchange_weak_explicit(
> + &d->head, old_head, *new_head,
> + rte_memory_order_relaxed, rte_memory_order_relaxed);
> + }
> } while (unlikely(success == 0));
> return n;
> }
[-- Attachment #2: Type: text/html, Size: 6093 bytes --]
^ permalink raw reply
* Re: [PATCH v2 0/6] net/gve: add hardware timestamping support
From: Joshua Washington @ 2026-05-21 16:10 UTC (permalink / raw)
To: Mark Blasko; +Cc: Stephen Hemminger, dev
In-Reply-To: <CANULgnJv8LyoLZhGfn7gPxo-=2Z3ZSD8jCv9jAf9bCu4RZCaiQ@mail.gmail.com>
On Mon, May 18, 2026 at 11:44 AM Mark Blasko <blasko@google.com> wrote:
>
> On Sun, May 17, 2026 at 4:15 PM Stephen Hemminger
> <stephen@networkplumber.org> wrote:
> >
> > Warning: (unchanged from v1) gve_read_clock and the periodic
> > gve_read_nic_clock alarm callback both issue
> > GVE_ADMINQ_REPORT_NIC_TIMESTAMP into the single shared DMA buffer
> > priv->nic_ts_report, then read it after gve_adminq_execute_cmd
> > has released adminq_lock. If gve_read_clock is preempted between
> > gve_adminq_report_nic_timestamp returning and the be64_to_cpu
> > read, the alarm callback can memset() and reissue its own
> > command, so the user thread will read either zero or another
> > command's response. The simplest fix is for gve_read_clock to
> > return the cached priv->last_read_nic_timestamp instead of
> > issuing a fresh adminq command - the 250ms periodic sync keeps
> > it fresh enough for .read_clock semantics. Once the dev_op
> > registration is restored this race becomes reachable.
>
> I want to make sure I fully understand the API contract here. Is the
> fact that .read_clock does not require a fresh device read documented
> in the DPDK specification, or is this based on typical application
> use cases? If the latter, what are those typical use cases?
Looking at the documentation for the user-exposed rte_eth_read_clock()
API, it seems that this API is intended to be quite accurate on a
read, or at least more accurate than 250ms of granularity would allow.
This is the example given in the documentation:
> * E.g, a simple heuristic to derivate the frequency would be:
> * uint64_t start, end;
> * rte_eth_read_clock(port, start);
> * rte_delay_ms(100);
> * rte_eth_read_clock(port, end);
> * double freq = (end - start) * 10;
> *
> * Compute a common reference with:
> * uint64_t base_time_sec = current_time();
> * uint64_t base_clock;
> * rte_eth_read_clock(port, base_clock);
> *
> * Then, convert the raw mbuf timestamp with:
> * base_time_sec + (double)(*timestamp_dynfield(mbuf) - base_clock) / freq;
read_clock() having 250ms granularity would completely nullify this
use case, as it is entirely possible for both clock reads to hold the
same value. And even if they _didn't_ hold the same value, they would
end up being 250ms-worth of NIC cycles apart, instead of a more
accurate representation of the delta over that period of time -- not
very useful.
--
Joshua Washington
^ permalink raw reply
* Re: [PATCH] net/mlx5: remove nonsensical flow action class_id checks
From: Dariusz Sosnowski @ 2026-05-21 16:23 UTC (permalink / raw)
To: Adrian Schollmeyer
Cc: Viacheslav Ovsiienko, Bing Zhao, Ori Kam, Suanming Mou,
Matan Azrad, Michael Baum, dev, Michael Pfeiffer, stable
In-Reply-To: <20260520132533.159996-1-a.schollmeyer@syseleven.de>
On Wed, May 20, 2026 at 03:25:32PM +0200, Adrian Schollmeyer wrote:
> From: Michael Pfeiffer <m.pfeiffer@syseleven.de>
>
> For a MODIFY_FIELD action, flow_hw_validate_action_modify_field() is
> invoked and enforces class_id == 0 in the action's source and
> destination, if the modified field is none of
> RTE_FLOW_FIELD_GENEVE_OPT_*, as the value is used solely for GENEVE
> fields.
>
> However, this check is flawed due to the way rte_flow_field_data is
> initialized. As it consists of unions and anonymous structs as members,
> empty initialization of this struct or initializing just the tag_index
> only guarantees initialization of the first union member, while the
> remaining member's default initialization behavior is unspecified.
> Therefore, depending on the compiler type, version and configuration,
> the remaining members may either be default-initialized as well or
> contain bytes from uninitialized memory. This causes the check to fail
> depending on how the struct is initialized wherever it is used.
>
> For example, rte_flow_configure() sometimes fails on mlx5 under these
> circumstances with an error "destination class id is not supported"
> during creation of representor tagging rules, as these internally use
> MODIFY_FIELD actions in the following call stack:
>
> 1. rte_flow_configure
> 2. mlx5_flow_port_configure
> 3. flow_hw_configure
> 4. __flow_hw_configure
> 5. flow_hw_setup_tx_repr_tagging
> 6. flow_hw_create_tx_repr_tag_jump_acts_tmpl
> --> various rte_flow_action_modify_field are initialized here, but
> class_id remains uninitialized
> 7. __flow_hw_actions_template_create
> 8. mlx5_flow_hw_actions_validate
> 9. flow_hw_validate_action_modify_field
> --> invoked with class_id containing uninitialized bytes and
> non-GENEVE field type
>
> Remove the two checks for class_id in the non-GENEVE case, as this field
> is unused for these actions and avoids additional implicit dependencies
> on the correct ordering of union members.
>
> Fixes: 1caa89ec1891 ("net/mlx5: support GENEVE options modification")
> Cc: stable@dpdk.org
>
> Signed-off-by: Michael Pfeiffer <m.pfeiffer@syseleven.de>
> Signed-off-by: Adrian Schollmeyer <a.schollmeyer@syseleven.de>
Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>
Thank you for the contribution.
Best regards,
Dariusz Sosnowski
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox