From: Randy L Tice <[email protected]>
Date: Thu, 03 Sep 2026 09:13:28 -0400

Add build-time support for optional cache-line-aligned dynamic-field
storage at the end of struct rte_mbuf.

The mbuf_dynfield3_size Meson option sets RTE_MBUF_DYNFIELD3_SIZE in
rte_build_config.h. A non-zero value enables the extra area and grows
every mbuf by the configured amount.

When enabled, dynfield3 is reserved for dynamic fields registered with
RTE_MBUF_DYNFIELD_F_NO_COPY. Such fields are allocated from dynfield3
and are not copied by the generic mbuf dynamic-field copy helper used by
mbuf copy and clone operations. Dynamic fields registered without this
flag continue to use the existing copied dynamic-field storage and are
prevented from overlapping dynfield3.

Disable the octeontx mempool driver when the option is enabled because
it requires sizeof(struct rte_mbuf) to remain at most 128 bytes.

Signed-off-by: Randy L Tice <[email protected]>
---
 app/test/test_mbuf.c                   | 72 ++++++++++++++++++++++++--
 config/meson.build                     |  5 ++
 devtools/test-meson-builds.sh          |  3 ++
 doc/guides/prog_guide/mbuf_lib.rst     |  9 ++++
 doc/guides/rel_notes/release_26_11.rst | 17 +++++-
 drivers/mempool/octeontx/meson.build   |  5 ++
 lib/mbuf/rte_mbuf_core.h               |  6 +++
 lib/mbuf/rte_mbuf_dyn.c                | 54 ++++++++++++++++---
 lib/mbuf/rte_mbuf_dyn.h                | 11 +++-
 meson_options.txt                      |  2 +
 10 files changed, 172 insertions(+), 12 deletions(-)

diff --git a/app/test/test_mbuf.c b/app/test/test_mbuf.c
index db23259745..c03878bd96 100644
--- a/app/test/test_mbuf.c
+++ b/app/test/test_mbuf.c
@@ -2569,7 +2569,7 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
        };
        const struct rte_mbuf_dynfield dynfield_fail_big = {
                .name = "test-dynfield-fail-big",
-               .size = 256,
+               .size = sizeof(struct rte_mbuf),
                .align = 1,
                .flags = 0,
        };
@@ -2583,8 +2583,28 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
                .name = "test-dynfield",
                .size = sizeof(uint8_t),
                .align = alignof(uint8_t),
-               .flags = 1,
+               .flags = RTE_MBUF_DYNFIELD_F_NO_COPY << 1,
+       };
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       const struct rte_mbuf_dynfield dynfield3_no_copy = {
+               .name = "test-dynfield3-no-copy",
+               .size = sizeof(uint64_t),
+               .align = alignof(uint64_t),
+               .flags = RTE_MBUF_DYNFIELD_F_NO_COPY,
+       };
+       const struct rte_mbuf_dynfield dynfield_no_copy_bad_offset = {
+               .name = "test-dynfield-no-copy-bad-offset",
+               .size = sizeof(uint64_t),
+               .align = alignof(uint64_t),
+               .flags = RTE_MBUF_DYNFIELD_F_NO_COPY,
+       };
+       const struct rte_mbuf_dynfield dynfield_copy_bad_offset = {
+               .name = "test-dynfield-copy-bad-offset",
+               .size = 2 * sizeof(uint64_t),
+               .align = alignof(uint64_t),
+               .flags = 0,
        };
+#endif
        const struct rte_mbuf_dynflag dynflag_fail_flag = {
                .name = "test-dynflag",
                .flags = 1,
@@ -2602,7 +2622,11 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
                .flags = 0,
        };
        struct rte_mbuf *m = NULL;
+       struct rte_mbuf *mc = NULL;
        int offset, offset2, offset3;
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       int dynfield3_no_copy_offset;
+#endif
        int flag, flag2, flag3;
        int ret;
 
@@ -2654,6 +2678,29 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
        if (ret != -1)
                GOTO_FAIL("dynamic field creation should fail (invalid flag)");
 
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       dynfield3_no_copy_offset = 
rte_mbuf_dynfield_register_offset(&dynfield3_no_copy,
+                       offsetof(struct rte_mbuf, dynfield3));
+       if (dynfield3_no_copy_offset != offsetof(struct rte_mbuf, dynfield3))
+               GOTO_FAIL("failed to register no-copy dynfield3 field, 
offset=%d: %s",
+                       dynfield3_no_copy_offset, strerror(errno));
+
+       ret = rte_mbuf_dynfield_register_offset(&dynfield_no_copy_bad_offset,
+                       offsetof(struct rte_mbuf, dynfield1[0]));
+       if (ret != -1)
+               GOTO_FAIL("no-copy dynamic field creation should fail outside 
dynfield3");
+
+       ret = rte_mbuf_dynfield_register_offset(&dynfield_copy_bad_offset,
+                       offsetof(struct rte_mbuf, dynfield3));
+       if (ret != -1)
+               GOTO_FAIL("copied dynamic field creation should fail in 
dynfield3");
+
+       ret = rte_mbuf_dynfield_register_offset(&dynfield_copy_bad_offset,
+                       offsetof(struct rte_mbuf, dynfield3) - 
sizeof(uint64_t));
+       if (ret != -1)
+               GOTO_FAIL("copied dynamic field creation should fail when 
straddling dynfield3");
+#endif
+
        ret = rte_mbuf_dynflag_register(&dynflag_fail_flag);
        if (ret != -1)
                GOTO_FAIL("dynamic flag creation should fail (invalid flag)");
@@ -2693,13 +2740,29 @@ test_mbuf_dyn(struct rte_mempool *pktmbuf_pool)
        if (*RTE_MBUF_DYNFIELD(m, offset2, uint16_t *) != 1000)
                GOTO_FAIL("failed to read dynamic field");
 
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       *RTE_MBUF_DYNFIELD(m, dynfield3_no_copy_offset, uint64_t *) =
+               UINT64_C(0x8877665544332211);
+       mc = rte_pktmbuf_alloc(pktmbuf_pool);
+       if (mc == NULL)
+               GOTO_FAIL("Cannot allocate mbuf for dynamic field copy test");
+       *RTE_MBUF_DYNFIELD(mc, dynfield3_no_copy_offset, uint64_t *) =
+               UINT64_C(0xa5a5a5a5a5a5a5a5);
+       rte_mbuf_dynfield_copy(mc, m);
+       if (*RTE_MBUF_DYNFIELD(mc, dynfield3_no_copy_offset, uint64_t *) !=
+                       UINT64_C(0xa5a5a5a5a5a5a5a5))
+               GOTO_FAIL("copied no-copy dynfield3 dynamic field");
+#endif
+
        /* set a dynamic flag */
        m->ol_flags |= (1ULL << flag);
 
        rte_mbuf_dyn_dump(stdout);
+       rte_pktmbuf_free(mc);
        rte_pktmbuf_free(m);
        return 0;
 fail:
+       rte_pktmbuf_free(mc);
        rte_pktmbuf_free(m);
        return -1;
 }
@@ -2776,8 +2839,9 @@ test_mbuf(void)
        struct rte_mempool *pktmbuf_pool = NULL;
        struct rte_mempool *pktmbuf_pool2 = NULL;
 
-
-       RTE_BUILD_BUG_ON(sizeof(struct rte_mbuf) != RTE_CACHE_LINE_MIN_SIZE * 
2);
+       RTE_BUILD_BUG_ON(sizeof(struct rte_mbuf) !=
+               RTE_CACHE_LINE_MIN_SIZE * 2 +
+               RTE_MBUF_DYNFIELD3_SIZE);
 
        /* create pktmbuf pool if it does not exist */
        pktmbuf_pool = rte_pktmbuf_pool_create("test_pktmbuf_pool",
diff --git a/config/meson.build b/config/meson.build
index 344f68822b..a6bd25b9ac 100644
--- a/config/meson.build
+++ b/config/meson.build
@@ -384,6 +384,8 @@ dpdk_conf.set('RTE_LIBEAL_USE_HPET', get_option('use_hpet'))
 dpdk_conf.set('RTE_ENABLE_STDATOMIC', get_option('enable_stdatomic'))
 dpdk_conf.set('RTE_ENABLE_TRACE_FP', get_option('enable_trace_fp'))
 dpdk_conf.set('RTE_PKTMBUF_HEADROOM', get_option('pkt_mbuf_headroom'))
+mbuf_dynfield3_size = get_option('mbuf_dynfield3_size')
+dpdk_conf.set('RTE_MBUF_DYNFIELD3_SIZE', mbuf_dynfield3_size)
 # values which have defaults which may be overridden
 dpdk_conf.set('RTE_MAX_VFIO_GROUPS', 64)
 dpdk_conf.set('RTE_DRIVER_MEMPOOL_BUCKET_SIZE_KB', 64)
@@ -395,6 +397,9 @@ dpdk_conf.set10('RTE_IOVA_IN_MBUF', 
get_option('enable_iova_as_pa'))
 
 compile_time_cpuflags = []
 subdir(arch_subdir)
+if mbuf_dynfield3_size % dpdk_conf.get('RTE_CACHE_LINE_SIZE') != 0
+    error('mbuf_dynfield3_size must be a multiple of RTE_CACHE_LINE_SIZE')
+endif
 dpdk_conf.set('RTE_COMPILE_TIME_CPUFLAGS', ','.join(compile_time_cpuflags))
 
 # apply cross-specific options
diff --git a/devtools/test-meson-builds.sh b/devtools/test-meson-builds.sh
index 11e4be3f88..23115def8c 100755
--- a/devtools/test-meson-builds.sh
+++ b/devtools/test-meson-builds.sh
@@ -259,6 +259,9 @@ fi
 build build-x86-generic cc skipABI --buildtype=debug -Dcheck_includes=true \
        -Dlibdir=lib -Dcpu_instruction_set=$generic_isa $use_shared
 
+build build-mbuf-dynfield3 cc skipABI --buildtype=debug \
+       -Dmbuf_dynfield3_size=256 $use_shared
+
 # 32-bit with default compiler
 if check_cc_flags '-m32' ; then
        target_override='i386-pc-linux-gnu'
diff --git a/doc/guides/prog_guide/mbuf_lib.rst 
b/doc/guides/prog_guide/mbuf_lib.rst
index cf64add109..ca0efb99c1 100644
--- a/doc/guides/prog_guide/mbuf_lib.rst
+++ b/doc/guides/prog_guide/mbuf_lib.rst
@@ -234,6 +234,15 @@ The dynamic fields and flags are managed with the 
functions ``rte_mbuf_dyn*``.
 
 It is not possible to unregister fields or flags.
 
+The build option ``mbuf_dynfield3_size`` can add extra cache-line-aligned
+dynamic field storage to ``struct rte_mbuf``.  This increases every mbuf by
+the configured amount and changes the mbuf layout, so applications and
+secondary processes must be built with the same value as the primary process.
+The option defaults to ``0``.  The extra storage is reserved for dynamic
+fields registered with ``RTE_MBUF_DYNFIELD_F_NO_COPY``.  These fields are not
+copied by mbuf copy and clone operations.  Dynamic fields registered without
+this flag continue to use the existing copied dynamic-field storage.
+
 .. _direct_indirect_buffer:
 
 Direct and Indirect Buffers
diff --git a/doc/guides/rel_notes/release_26_11.rst 
b/doc/guides/rel_notes/release_26_11.rst
index dec96ccbc7..cff68d7034 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -60,6 +60,22 @@ New Features
   Added the experimental ``rte_cpu_socket_id()`` function
   to map an OS logical CPU ID to the NUMA socket containing that CPU.
 
+* **Added optional extra mbuf dynamic field storage.**
+
+  Added ``mbuf_dynfield3_size`` build option to enable a
+  cache-line-aligned ``dynfield3`` area in ``struct rte_mbuf``.
+  The configured size is defined as ``RTE_MBUF_DYNFIELD3_SIZE``
+  in ``rte_build_config.h``.
+  The extra storage is reserved for dynamic fields registered with
+  ``RTE_MBUF_DYNFIELD_F_NO_COPY``.
+  These fields are not copied by generic mbuf copy and clone operations.
+  Dynamic fields registered without this flag continue to use the existing
+  copied dynamic-field storage.
+  Applications and secondary processes must be built with the same
+  non-zero value as the primary process.
+  The ``mempool/octeontx`` driver is disabled when this option is
+  enabled because it requires a fixed 128-byte mbuf header.
+
 * **Added TPID support to VLAN tag insertion.**
 
   Added ``rte_vlan_insert_tpid()`` to the net library.
@@ -338,7 +354,6 @@ Known Issues
    Also, make sure to start the actual text at the margin.
    =======================================================
 
-
 Tested Platforms
 ----------------
 
diff --git a/drivers/mempool/octeontx/meson.build 
b/drivers/mempool/octeontx/meson.build
index 3ccecac75d..01aece94be 100644
--- a/drivers/mempool/octeontx/meson.build
+++ b/drivers/mempool/octeontx/meson.build
@@ -6,6 +6,11 @@ if not is_linux or not dpdk_conf.get('RTE_ARCH_64')
     reason = 'only supported on 64-bit Linux'
     subdir_done()
 endif
+if get_option('mbuf_dynfield3_size') != 0
+    build = false
+    reason = 'requires sizeof(struct rte_mbuf) <= 128'
+    subdir_done()
+endif
 
 sources = files(
         'octeontx_fpavf.c',
diff --git a/lib/mbuf/rte_mbuf_core.h b/lib/mbuf/rte_mbuf_core.h
index 98b0bd9ca7..b4f204b268 100644
--- a/lib/mbuf/rte_mbuf_core.h
+++ b/lib/mbuf/rte_mbuf_core.h
@@ -686,6 +686,12 @@ struct __rte_cache_aligned rte_mbuf {
        uint16_t timesync;
 
        uint32_t dynfield1[9]; /**< Reserved for dynamic fields. */
+
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       alignas(RTE_CACHE_LINE_SIZE)
+       uint64_t dynfield3[RTE_MBUF_DYNFIELD3_SIZE / sizeof(uint64_t)];
+       /**< Reserved cache-line-aligned space for dynamic fields. */
+#endif /* RTE_MBUF_DYNFIELD3_SIZE > 0 */
 };
 
 /**
diff --git a/lib/mbuf/rte_mbuf_dyn.c b/lib/mbuf/rte_mbuf_dyn.c
index 5987c9dee8..7cecbc9ee6 100644
--- a/lib/mbuf/rte_mbuf_dyn.c
+++ b/lib/mbuf/rte_mbuf_dyn.c
@@ -3,6 +3,7 @@
  */
 
 #include <stdalign.h>
+#include <stddef.h>
 #include <sys/queue.h>
 #include <stdint.h>
 #include <limits.h>
@@ -51,7 +52,7 @@ struct mbuf_dyn_shm {
         * The value is the size of the biggest aligned element that
         * can fit in the zone.
         */
-       uint8_t free_space[sizeof(struct rte_mbuf)];
+       uint16_t free_space[sizeof(struct rte_mbuf)];
        /** Bitfield of available flags. */
        uint64_t free_flags;
 };
@@ -135,6 +136,9 @@ init_shared_mem(void)
 #if !RTE_IOVA_IN_MBUF
                mark_free(dynfield2);
 #endif
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+               mark_free(dynfield3);
+#endif
 
                /* init free_flags */
                for (mask = RTE_MBUF_F_FIRST_FREE; mask <= 
RTE_MBUF_F_LAST_FREE; mask <<= 1)
@@ -147,11 +151,48 @@ init_shared_mem(void)
 }
 
 /* check if this offset can be used */
+static bool
+dynfield_in_dynfield3(size_t offset, size_t size)
+{
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       size_t dynfield3_offset = offsetof(struct rte_mbuf, dynfield3);
+
+       return offset >= dynfield3_offset &&
+               size <= sizeof(((struct rte_mbuf *)0)->dynfield3) &&
+               offset - dynfield3_offset <= sizeof(((struct rte_mbuf 
*)0)->dynfield3) - size;
+#else
+       RTE_SET_USED(offset);
+       RTE_SET_USED(size);
+       return false;
+#endif
+}
+
+static bool
+dynfield_overlaps_dynfield3(size_t offset, size_t size)
+{
+#if RTE_MBUF_DYNFIELD3_SIZE > 0
+       size_t dynfield3_offset = offsetof(struct rte_mbuf, dynfield3);
+       size_t dynfield3_end = dynfield3_offset + sizeof(((struct rte_mbuf 
*)0)->dynfield3);
+
+       return offset < dynfield3_end && offset + size > dynfield3_offset;
+#else
+       RTE_SET_USED(offset);
+       RTE_SET_USED(size);
+       return false;
+#endif
+}
+
 static int
-check_offset(size_t offset, size_t size, size_t align)
+check_offset(size_t offset, size_t size, size_t align, unsigned int flags)
 {
        size_t i;
 
+       if ((flags & RTE_MBUF_DYNFIELD_F_NO_COPY) != 0 &&
+                       !dynfield_in_dynfield3(offset, size))
+               return -1;
+       if ((flags & RTE_MBUF_DYNFIELD_F_NO_COPY) == 0 &&
+                       dynfield_overlaps_dynfield3(offset, size))
+               return -1;
        if ((offset & (align - 1)) != 0)
                return -1;
        if (offset + size > sizeof(struct rte_mbuf))
@@ -268,7 +309,7 @@ __rte_mbuf_dynfield_register_offset(const struct 
rte_mbuf_dynfield *params,
                     offset < sizeof(struct rte_mbuf);
                     offset++) {
                        if (check_offset(offset, params->size,
-                                               params->align) == 0 &&
+                                               params->align, params->flags) 
== 0 &&
                                        shm->free_space[offset] < best_zone) {
                                best_zone = shm->free_space[offset];
                                req = offset;
@@ -279,7 +320,8 @@ __rte_mbuf_dynfield_register_offset(const struct 
rte_mbuf_dynfield *params,
                        return -1;
                }
        } else {
-               if (check_offset(req, params->size, params->align) < 0) {
+               if (check_offset(req, params->size, params->align,
+                               params->flags) < 0) {
                        rte_errno = EBUSY;
                        return -1;
                }
@@ -342,7 +384,7 @@ rte_mbuf_dynfield_register_offset(const struct 
rte_mbuf_dynfield *params,
                rte_errno = EINVAL;
                return -1;
        }
-       if (params->flags != 0) {
+       if ((params->flags & ~RTE_MBUF_DYNFIELD_F_NO_COPY) != 0) {
                rte_errno = EINVAL;
                return -1;
        }
@@ -573,7 +615,7 @@ void rte_mbuf_dyn_dump(FILE *out)
        for (i = 0; i < sizeof(struct rte_mbuf); i++) {
                if ((i % 8) == 0)
                        fprintf(out, "  %4.4zx: ", i);
-               fprintf(out, "%2.2x%s", shm->free_space[i],
+               fprintf(out, "%4.4x%s", shm->free_space[i],
                        (i % 8 != 7) ? " " : "\n");
        }
        fprintf(out, "Free bit in mbuf->ol_flags (0 = occupied, 1 = free):\n");
diff --git a/lib/mbuf/rte_mbuf_dyn.h b/lib/mbuf/rte_mbuf_dyn.h
index 20ce505bb4..d9a46b3c33 100644
--- a/lib/mbuf/rte_mbuf_dyn.h
+++ b/lib/mbuf/rte_mbuf_dyn.h
@@ -69,6 +69,7 @@
 #include <stdio.h>
 #include <stdint.h>
 
+#include <rte_bitops.h>
 #include <rte_stdatomic.h>
 
 #ifdef __cplusplus
@@ -80,6 +81,14 @@ extern "C" {
  */
 #define RTE_MBUF_DYN_NAMESIZE 64
 
+/**
+ * Do not copy this dynamic field during mbuf clone or copy.
+ *
+ * Fields using this flag are allocated from the optional dynfield3 area
+ * configured by the mbuf_dynfield3_size build option.
+ */
+#define RTE_MBUF_DYNFIELD_F_NO_COPY RTE_BIT32(0)
+
 /**
  * Structure describing the parameters of a mbuf dynamic field.
  */
@@ -87,7 +96,7 @@ struct rte_mbuf_dynfield {
        char name[RTE_MBUF_DYN_NAMESIZE]; /**< Name of the field. */
        size_t size;        /**< The number of bytes to reserve. */
        size_t align;       /**< The alignment constraint (power of 2). */
-       unsigned int flags; /**< Reserved for future use, must be 0. */
+       unsigned int flags; /**< Dynamic field flags. */
 };
 
 /**
diff --git a/meson_options.txt b/meson_options.txt
index e28d24054c..1248498a3d 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -44,6 +44,8 @@ option('max_numa_nodes', type: 'string', value: 'default', 
description:
        'Set the highest NUMA node supported by EAL; "default" is different 
per-arch, "detect" detects the highest NUMA node on the build machine.')
 option('enable_iova_as_pa', type: 'boolean', value: true, description:
        'Support the use of physical addresses for IO addresses, such as used 
by UIO or VFIO in no-IOMMU mode. When disabled, DPDK can only run with IOMMU 
support for address mappings, but will have more space available in the mbuf 
structure.')
+option('mbuf_dynfield3_size', type: 'integer', min: 0, value: 0, description:
+       'Size of optional extra mbuf dynamic field area, in bytes.')
 option('mbuf_refcnt_atomic', type: 'boolean', value: true, description:
        'Atomically access the mbuf refcnt.')
 option('platform', type: 'string', value: 'native', description:
-- 
2.35.6

Reply via email to