Add the 'create pinned-rxpool <seg-idx> <count> <elt-size>' testpmd
command to demonstrate zero-copy header/payload split receive using
pinned external-buffer mempools (RTE_PKTMBUF_POOL_F_PINNED_EXT_BUF).

Motivation: the DPDK buffer-split offload (RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT)
with two Rx segments allows an application to direct the NIC to place
the protocol header and the payload into separate mbufs. Combining it
with a pinned external-buffer pool for the payload segment lets the NIC
DMA the payload directly into application-owned hugepage memory without
any extra copy or new ethdev API.

This is the mechanism needed by streaming applications such as the
Media Transport Library (MTL) for SMPTE ST 2110-21 video reception,
where 1260-byte RTP payload chunks must land zero-copy in pre-allocated
per-frame video buffers. The existing rte_pktmbuf_pool_create_extbuf()
API covers this use case; no new callback or out-of-tree PMD hook is
required.

The command allocates a hugepage region via rte_zmalloc_socket(),
creates a pinned pool over it with rte_pktmbuf_pool_create_extbuf(),
and registers the pool with testpmd's pool index so that
rx_queue_setup() automatically selects it for the configured split
segment. The complete workflow and a worked video-streaming example are
documented in doc/guides/testpmd_app_ug/testpmd_funcs.rst.

Signed-off-by: Dawid Wesierski <[email protected]>
Signed-off-by: Marek Kasiewicz <[email protected]>
---
 app/test-pmd/cmdline.c                      | 123 ++++++++++++++++++++
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  82 +++++++++++++
 2 files changed, 205 insertions(+)

diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 3c39e27aa8..83ea6d2cd2 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -371,6 +371,11 @@ static void cmd_help_long_parsed(void *parsed_result,
                        "inner-ipv6-tcp|inner-ipv4-udp|inner-ipv6-udp|"
                        "inner-ipv4-sctp|inner-ipv6-sctp\n\n"
 
+                       "create pinned-rxpool (seg-idx) (count) (elt-size)\n"
+                       "    Create a pinned external-buffer Rx mempool for"
+                       " buffer-split segment <seg-idx>. Payloads DMA directly"
+                       " into application-owned hugepage memory without 
copy.\n\n"
+
                        "set txpkts (x[,y]*)\n"
                        "    Set the length of each segment of TXONLY"
                        " and optionally CSUM packets.\n\n"
@@ -4483,6 +4488,123 @@ static cmdline_parse_inst_t cmd_set_rxhdrs = {
        },
 };
 
+/* *** CREATE PINNED EXTERNAL BUFFER POOL FOR RX SPLIT SEGMENT *** */
+struct cmd_create_pinned_rxpool_result {
+       cmdline_fixed_string_t create;
+       cmdline_fixed_string_t pinned_rxpool;
+       uint16_t seg_idx;
+       uint32_t count;
+       uint16_t elt_size;
+};
+
+static void
+cmd_create_pinned_rxpool_parsed(void *parsed_result,
+                               __rte_unused struct cmdline *cl,
+                               __rte_unused void *data)
+{
+       struct cmd_create_pinned_rxpool_result *res = parsed_result;
+       char pool_name[RTE_MEMPOOL_NAMESIZE];
+       struct rte_pktmbuf_extmem ext_mem;
+       struct rte_mempool *mp;
+       unsigned int socket_id;
+       size_t mem_size;
+       void *frames;
+
+       if (res->seg_idx >= MAX_SEGS_BUFFER_SPLIT) {
+               fprintf(stderr, "seg-idx must be less than %u\n",
+                       MAX_SEGS_BUFFER_SPLIT);
+               return;
+       }
+
+       socket_id = (num_sockets > 0) ? (unsigned int)socket_ids[0] : 0;
+       mbuf_poolname_build(socket_id, pool_name, sizeof(pool_name),
+                           res->seg_idx);
+
+       if (mbuf_pool_find(socket_id, res->seg_idx) != NULL) {
+               fprintf(stderr,
+                       "Pool '%s' already exists; stop/close port before 
recreating\n",
+                       pool_name);
+               return;
+       }
+
+       mem_size = (size_t)res->count * res->elt_size;
+       frames = rte_zmalloc_socket("pinned_rxpool_mem", mem_size,
+                                   RTE_CACHE_LINE_SIZE, socket_id);
+       if (frames == NULL) {
+               fprintf(stderr,
+                       "Failed to allocate %zu bytes for pinned pool\n",
+                       mem_size);
+               return;
+       }
+
+       ext_mem.buf_ptr  = frames;
+       ext_mem.buf_iova = rte_malloc_virt2iova(frames);
+       if (ext_mem.buf_iova == RTE_BAD_IOVA) {
+               fprintf(stderr,
+                       "No IOVA mapping for pinned pool (VFIO/IOMMU 
required)\n");
+               rte_free(frames);
+               return;
+       }
+       ext_mem.buf_len  = mem_size;
+       ext_mem.elt_size = res->elt_size;
+
+       mp = rte_pktmbuf_pool_create_extbuf(pool_name, res->count,
+                                           0, 0, res->elt_size,
+                                           socket_id, &ext_mem, 1);
+       if (mp == NULL) {
+               fprintf(stderr, "Failed to create pinned pool '%s': %s\n",
+                       pool_name, rte_strerror(rte_errno));
+               rte_free(frames);
+               return;
+       }
+
+       /* Register with testpmd so rx_queue_setup() uses this pool for
+        * segment <seg_idx> when buffer-split is configured.
+        */
+       mbuf_data_size[res->seg_idx] = res->elt_size;
+       if ((uint32_t)(res->seg_idx + 1) > mbuf_data_size_n)
+               mbuf_data_size_n = res->seg_idx + 1;
+
+       printf("Created pinned ext-buf pool '%s':\n"
+              "  socket=%u seg-idx=%u count=%u elt-size=%u "
+              "mem=%p iova=0x%" PRIx64 "\n",
+              pool_name, socket_id, res->seg_idx, res->count,
+              res->elt_size, frames, ext_mem.buf_iova);
+}
+
+static cmdline_parse_token_string_t cmd_create_pinned_rxpool_create =
+       TOKEN_STRING_INITIALIZER(struct cmd_create_pinned_rxpool_result,
+                                create, "create");
+static cmdline_parse_token_string_t cmd_create_pinned_rxpool_kw =
+       TOKEN_STRING_INITIALIZER(struct cmd_create_pinned_rxpool_result,
+                                pinned_rxpool, "pinned-rxpool");
+static cmdline_parse_token_num_t cmd_create_pinned_rxpool_seg_idx =
+       TOKEN_NUM_INITIALIZER(struct cmd_create_pinned_rxpool_result,
+                             seg_idx, RTE_UINT16);
+static cmdline_parse_token_num_t cmd_create_pinned_rxpool_count =
+       TOKEN_NUM_INITIALIZER(struct cmd_create_pinned_rxpool_result,
+                             count, RTE_UINT32);
+static cmdline_parse_token_num_t cmd_create_pinned_rxpool_elt_size =
+       TOKEN_NUM_INITIALIZER(struct cmd_create_pinned_rxpool_result,
+                             elt_size, RTE_UINT16);
+
+static cmdline_parse_inst_t cmd_create_pinned_rxpool = {
+       .f = cmd_create_pinned_rxpool_parsed,
+       .data = NULL,
+       .help_str = "create pinned-rxpool <seg-idx> <count> <elt-size>: "
+                   "create a pinned external-buffer Rx mempool for split "
+                   "segment <seg-idx>; payloads DMA directly into hugepage "
+                   "memory owned by the application without an extra copy",
+       .tokens = {
+               (void *)&cmd_create_pinned_rxpool_create,
+               (void *)&cmd_create_pinned_rxpool_kw,
+               (void *)&cmd_create_pinned_rxpool_seg_idx,
+               (void *)&cmd_create_pinned_rxpool_count,
+               (void *)&cmd_create_pinned_rxpool_elt_size,
+               NULL,
+       },
+};
+
 /* *** SET SEGMENT LENGTHS OF TXONLY PACKETS *** */
 
 struct cmd_set_txpkts_result {
@@ -14238,6 +14360,7 @@ static cmdline_parse_ctx_t builtin_ctx[] = {
        &cmd_set_rxoffs,
        &cmd_set_rxpkts,
        &cmd_set_rxhdrs,
+       &cmd_create_pinned_rxpool,
        &cmd_set_txflows,
        &cmd_set_txpkts,
        &cmd_set_txsplit,
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst 
b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index f0f2b0758b..0ca263cc4e 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -867,6 +867,88 @@ Where eth[,ipv4]* represents a CSV list of values, without 
white space.
 If the list of offsets is shorter than the list of segments,
 zero offsets will be used for the remaining segments.
 
+create pinned-rxpool
+~~~~~~~~~~~~~~~~~~~~
+
+Create a pinned external-buffer Rx mempool for a buffer-split payload
+segment. Each mbuf in the pool is permanently bound to a fixed slot in
+a contiguous hugepage region allocated by testpmd. When the NIC
+performs a buffer-split receive, the payload DMA-writes directly into
+that hugepage memory without any extra copy from a normal mbuf.
+
+.. code-block:: none
+
+   testpmd> create pinned-rxpool (seg-idx) (count) (elt-size)
+
+Where:
+
+seg-idx
+   Index of the Rx split segment that will use the pinned pool.
+   0 is the header segment (usually left as the default pool);
+   1 is the first payload segment and is the typical target.
+
+count
+   Number of mbufs in the pool. Should be at least as large as the
+   number of Rx descriptors on all queues using this pool, plus a
+   safety margin (e.g. ``nb_rxd * nb_rxq * 2``).
+
+elt-size
+   Size of each pool element in bytes. This is the total slot stride,
+   including ``RTE_PKTMBUF_HEADROOM`` (default 128 B). For example,
+   to hold 1260-byte video payloads (SMPTE ST 2110-21 BPM size) with
+   a standard headroom, set ``elt-size`` to 1388 (= 128 + 1260).
+
+The pool is named according to testpmd's internal convention so that
+``rx_queue_setup()`` automatically selects it for segment ``seg-idx``
+when ``RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT`` is enabled.
+
+.. note::
+
+   VFIO or another IOMMU driver is required so that hugepage memory
+   has a valid IOVA. The backing hugepage allocation is not freed when
+   the port is stopped or the pool is re-created; restart testpmd to
+   reclaim it.
+
+**Example — zero-copy header/payload split for video streaming**
+
+This example mirrors the use case in the Media Transport Library (MTL)
+for SMPTE ST 2110-21 video reception. Headers land in the default
+pool; 1260-byte video payloads DMA directly into a pinned hugepage
+region without any copy. The pool holds enough slots for 4 K
+descriptors on 4 queues with headroom:
+
+.. code-block:: console
+
+   # Start testpmd with a default header pool (segment 0)
+   dpdk-testpmd -a 0000:31:00.0,enable-rx-timestamp=0 \
+     --mbuf-size=256 -- -i --rxq=4 --txq=4 --rxd=4096
+
+   # Inside testpmd:
+
+   # 1. Create the pinned payload pool for segment 1 (4*4096 mbufs,
+   #    1388 bytes each: 128 B headroom + 1260 B payload)
+   testpmd> create pinned-rxpool 1 32768 1388
+
+   # 2. Configure buffer split: segment 0 = UDP+lower headers,
+   #    segment 1 = payload (length 0 means "rest of packet")
+   testpmd> set rxhdrs ipv4-udp
+   testpmd> set rxpkts 0,0
+
+   # 3. Enable buffer split on the port
+   testpmd> port config 0 rx_offload buffer_split on
+
+   # 4. Restart the queues so the new configuration takes effect
+   testpmd> stop
+   testpmd> port stop 0
+   testpmd> port start 0
+   testpmd> start
+
+After this sequence, each received UDP packet is split by the NIC:
+the header mbuf (chain head) comes from the default pool, and the
+payload mbuf (chain next) has its ``buf_addr`` pointing into the
+pinned hugepage region owned by the application — no copy, no
+callback, no new ethdev API.
+
 set txpkts
 ~~~~~~~~~~
 
-- 
2.47.3

---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial 
Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | 
Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z 
dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach 
handlowych.

Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i 
moze zawierac informacje poufne. W razie przypadkowego otrzymania tej 
wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; 
jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole 
use of the intended recipient(s). If you are not the intended recipient, please 
contact the sender and delete all copies; any review or distribution by others 
is strictly prohibited.

Reply via email to