Skip to contentSkip to conversation

testpmd: add hairpin-map parameter

PATCH Superseded Gregory Etelson 5 participants 20 messages Sep 19, 2023 – Nov 1, 2024 Arm/Amlogic Meson SoC support

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 testpmd: add hairpin-map parameter

Testpmd hairpin implementation always sets the next valid port to complete hairpin binding. That limits hairpin configuration options.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
 app/test-pmd/parameters.c             |  63 ++++++++
 app/test-pmd/testpmd.c                | 212 ++++++++++++++++++--------
 app/test-pmd/testpmd.h                |  18 +++
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 4 files changed, 230 insertions(+), 66 deletions(-)
app/test-pmd/parameters.c +63 -0
index a9ca58339d..675de81861 100644
@@ -206,6 +206,12 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
 }
 
 #ifdef RTE_LIB_CMDLINE
@@ -588,6 +594,55 @@ parse_link_speed(int n)
 	return speed;
 }
 
+static __rte_always_inline
+char *parse_hairpin_map_entry(char *input, char **next)
+{
+	char *tail = strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] = '\0';
+	*next = tail + 1;
+	return input;
+}
+
+static int
+parse_hairpin_map(char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	char *head, *next = hpmap;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rxq_head = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->tx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->txq_head = atoi(head);
+	map->qnum = atoi(next);
+	hairpin_add_multiport_map(map);
+	return 0;
+err:
+	free(map);
+	return -EINVAL;
+}
+
 void
 launch_args_parse(int argc, char** argv)
 {
@@ -663,6 +718,7 @@ launch_args_parse(int argc, char** argv)
 		{ "txd",			1, 0, 0 },
 		{ "hairpinq",			1, 0, 0 },
 		{ "hairpin-mode",		1, 0, 0 },
+		{ "hairpin-map",                1, 0, 0 },
 		{ "burst",			1, 0, 0 },
 		{ "flowgen-clones",		1, 0, 0 },
 		{ "flowgen-flows",		1, 0, 0 },
@@ -1111,6 +1167,13 @@ launch_args_parse(int argc, char** argv)
 				else
 					hairpin_mode = (uint32_t)n;
 			}
+			if (!strcmp(lgopts[opt_idx].name, "hairpin-map")) {
+				hairpin_multiport_mode = true;
+				ret = parse_hairpin_map(optarg);
+				if (ret)
+					rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+
+			}
 			if (!strcmp(lgopts[opt_idx].name, "burst")) {
 				n = atoi(optarg);
 				if (n == 0) {
app/test-pmd/testpmd.c +146 -66

259 lines not on this page · raw message

app/test-pmd/testpmd.h +18 -0
index f1df6a8faf..208e8e9514 100644
@@ -125,6 +125,24 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 6e9c552e76..a202c98b4c 100644
@@ -566,6 +566,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.39.2
#1 lore raw

PATCH v2 1/1 testpmd: add hairpin-map parameter

Testpmd hairpin implementation always sets the next valid port to complete hairpin binding. That limits hairpin configuration options.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
v2: fix Windows Server 2019 compilation failure.

---
 app/test-pmd/parameters.c             |  63 ++++++++
 app/test-pmd/testpmd.c                | 212 ++++++++++++++++++--------
 app/test-pmd/testpmd.h                |  18 +++
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 4 files changed, 230 insertions(+), 66 deletions(-)
app/test-pmd/parameters.c +63 -0
index a9ca58339d..c6bdfdf06f 100644
@@ -206,6 +206,12 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
 }
 
 #ifdef RTE_LIB_CMDLINE
@@ -588,6 +594,55 @@ parse_link_speed(int n)
 	return speed;
 }
 
+static __rte_always_inline
+char *parse_hairpin_map_entry(char *input, char **next)
+{
+	char *tail = strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] = '\0';
+	*next = tail + 1;
+	return input;
+}
+
+static int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	char *head, *next = (char *)(uintptr_t)hpmap;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rxq_head = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->tx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->txq_head = atoi(head);
+	map->qnum = atoi(next);
+	hairpin_add_multiport_map(map);
+	return 0;
+err:
+	free(map);
+	return -EINVAL;
+}
+
 void
 launch_args_parse(int argc, char** argv)
 {
@@ -663,6 +718,7 @@ launch_args_parse(int argc, char** argv)
 		{ "txd",			1, 0, 0 },
 		{ "hairpinq",			1, 0, 0 },
 		{ "hairpin-mode",		1, 0, 0 },
+		{ "hairpin-map",                1, 0, 0 },
 		{ "burst",			1, 0, 0 },
 		{ "flowgen-clones",		1, 0, 0 },
 		{ "flowgen-flows",		1, 0, 0 },
@@ -1111,6 +1167,13 @@ launch_args_parse(int argc, char** argv)
 				else
 					hairpin_mode = (uint32_t)n;
 			}
+			if (!strcmp(lgopts[opt_idx].name, "hairpin-map")) {
+				hairpin_multiport_mode = true;
+				ret = parse_hairpin_map(optarg);
+				if (ret)
+					rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+
+			}
 			if (!strcmp(lgopts[opt_idx].name, "burst")) {
 				n = atoi(optarg);
 				if (n == 0) {
app/test-pmd/testpmd.c +146 -66

259 lines not on this page · raw message

app/test-pmd/testpmd.h +18 -0
index f1df6a8faf..208e8e9514 100644
@@ -125,6 +125,24 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 6e9c552e76..a202c98b4c 100644
@@ -566,6 +566,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.39.2
#2 lore raw

PATCH v3 1/1 testpmd: add hairpin-map parameter

In reply to Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is delivered to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme. The scheme implicitly matches next valid port for given <rp> or <tp>. That approach can be used in a single or double port setups only.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
v2: Fix Windows Server 2019 compilation failure.
v3: Update the patch comment.
Fix typo.

---
 app/test-pmd/parameters.c             |  63 ++++++++
 app/test-pmd/testpmd.c                | 212 ++++++++++++++++++--------
 app/test-pmd/testpmd.h                |  18 +++
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 4 files changed, 230 insertions(+), 66 deletions(-)
app/test-pmd/parameters.c +63 -0
index a9ca58339d..c6bdfdf06f 100644
@@ -206,6 +206,12 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
 }
 
 #ifdef RTE_LIB_CMDLINE
@@ -588,6 +594,55 @@ parse_link_speed(int n)
 	return speed;
 }
 
+static __rte_always_inline
+char *parse_hairpin_map_entry(char *input, char **next)
+{
+	char *tail = strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] = '\0';
+	*next = tail + 1;
+	return input;
+}
+
+static int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	char *head, *next = (char *)(uintptr_t)hpmap;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rxq_head = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->tx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->txq_head = atoi(head);
+	map->qnum = atoi(next);
+	hairpin_add_multiport_map(map);
+	return 0;
+err:
+	free(map);
+	return -EINVAL;
+}
+
 void
 launch_args_parse(int argc, char** argv)
 {
@@ -663,6 +718,7 @@ launch_args_parse(int argc, char** argv)
 		{ "txd",			1, 0, 0 },
 		{ "hairpinq",			1, 0, 0 },
 		{ "hairpin-mode",		1, 0, 0 },
+		{ "hairpin-map",                1, 0, 0 },
 		{ "burst",			1, 0, 0 },
 		{ "flowgen-clones",		1, 0, 0 },
 		{ "flowgen-flows",		1, 0, 0 },
@@ -1111,6 +1167,13 @@ launch_args_parse(int argc, char** argv)
 				else
 					hairpin_mode = (uint32_t)n;
 			}
+			if (!strcmp(lgopts[opt_idx].name, "hairpin-map")) {
+				hairpin_multiport_mode = true;
+				ret = parse_hairpin_map(optarg);
+				if (ret)
+					rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+
+			}
 			if (!strcmp(lgopts[opt_idx].name, "burst")) {
 				n = atoi(optarg);
 				if (n == 0) {
app/test-pmd/testpmd.c +146 -66

259 lines not on this page · raw message

app/test-pmd/testpmd.h +18 -0
index f1df6a8faf..208e8e9514 100644
@@ -125,6 +125,24 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 6e9c552e76..a202c98b4c 100644
@@ -566,6 +566,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.39.2
Acked-by on v3 1/1
#3 lore raw
Quoted 33 lines

Subject: [PATCH v3] testpmd: add hairpin-map parameter

Hairpin offloads packet forwarding between ports.
Packet is expected on Rx port <rp>, Rx queue <rq> and is delivered to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme. The scheme implicitly matches next valid port for given <rp> or <tp>. That approach can be used in a single or double port setups only.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

Quoted 4 lines

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

Best regards,
Dariusz Sosnowski

#4 lore raw
In reply to Gregory Etelson
Quoted 25 lines · Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is delivered to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme. The scheme implicitly matches next valid port for given <rp> or <tp>. That approach can be used in a single or double port setups only.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Quoted 9 lines

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

Hi Gregory,

This patch is waiting for a while in the patchwork, not because there is a problem with the implementation, but I have a feeling that it is bloating the testpmd code with the hairpin feature which is not commonly supported, and I don't know what to do.

Also there was no review on the code ...

As a brainstorm, what do you think move hairpin related code to its own file, and have a kind of register logic to the commands, argument parsing and usage() functions etc.

@David did something similar to move PMD specific testpmd code to the drivers [1].

This can be initial steps for the more modular testpmd design and we can introduce more features with this approach in the future, I think hairpin support is a good instance to start this work, because of its limited impact to users.

[1]
Commit 592ab76f9f0f ("app/testpmd: register driver specific commands") Commit 94b3c1a72507 ("net/i40e: move testpmd commands")

Quoted 17 lines

---
v2: Fix Windows Server 2019 compilation failure.
v3: Update the patch comment.
Fix typo.

---
 app/test-pmd/parameters.c             |  63 ++++++++
 app/test-pmd/testpmd.c                | 212 ++++++++++++++++++--------
 app/test-pmd/testpmd.h                |  18 +++
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 4 files changed, 230 insertions(+), 66 deletions(-)
app/test-pmd/parameters.c +0 -0
index a9ca58339d..c6bdfdf06f 100644

app/test-pmd/parameters.c usage(char* progname) · line 215

Quoted 10 lines
app/test-pmd/parameters.c +6 -0
@@ -206,6 +206,12 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
 

'rxpi' or 'port index' are not common usage in DPDK, what about 'rx_port' & 'Rx port id' or similar?

app/test-pmd/parameters.c parse_link_speed(int n) · line 600

Quoted 11 lines
app/test-pmd/parameters.c +0 -0
 }
 

#ifdef RTE_LIB_CMDLINE

app/test-pmd/parameters.c +3 -0
@@ -588,6 +594,55 @@ parse_link_speed(int n)
 	return speed;
 }
 
+static __rte_always_inline
+char *parse_hairpin_map_entry(char *input, char **next)
+{
 

Why forcing this function to be inline, can make it static and left decision to compiler.

<...>

doc/guides/testpmd_app_ug/run_app.rst The command line options are: · line 572

Quoted 12 lines
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 6e9c552e76..a202c98b4c 100644
@@ -566,6 +566,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 

What 'queues number' does is not intuitive, sample in the commit log makes it easier to understand.
Can you please either explain or provide sample to clarify what "queues number" is for?

#5 lore raw

Hello Ferruh,

As a brainstorm, what do you think move hairpin related code to its own file, and have a kind of register logic to the commands, argument parsing and usage() functions etc.

Please consider an option for the testpmd to dynamically add supported commands. Testpmd can still provide some build-in core functionality, but custom test commands can be added and removed on demand.
In addition, the custom command code does not have to be part of the testpmd. That will reduce maintanance.

Regards,
Gregory

________________________________
From: Ferruh Yigit <[email protected]>
Sent: Friday, March 1, 2024 20:54
To: Gregory Etelson <[email protected]>; [email protected] <[email protected]>
Cc: Maayan Kashani <[email protected]>; NBU-Contact-Thomas Monjalon (EXTERNAL) <[email protected]>; Aman Singh <[email protected]>; Yuying Zhang <[email protected]>; David Marchand <[email protected]>
Subject: Re: [PATCH v3] testpmd: add hairpin-map parameter

External email: Use caution opening links or attachments

Quoted 25 lines · Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is delivered to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme. The scheme implicitly matches next valid port for given <rp> or <tp>. That approach can be used in a single or double port setups only.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Quoted 9 lines

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

Hi Gregory,

This patch is waiting for a while in the patchwork, not because there is a problem with the implementation, but I have a feeling that it is bloating the testpmd code with the hairpin feature which is not commonly supported, and I don't know what to do.

Also there was no review on the code ...

As a brainstorm, what do you think move hairpin related code to its own file, and have a kind of register logic to the commands, argument parsing and usage() functions etc.

@David did something similar to move PMD specific testpmd code to the drivers [1].

This can be initial steps for the more modular testpmd design and we can introduce more features with this approach in the future, I think hairpin support is a good instance to start this work, because of its limited impact to users.

[1]
Commit 592ab76f9f0f ("app/testpmd: register driver specific commands") Commit 94b3c1a72507 ("net/i40e: move testpmd commands")

Quoted 17 lines

---
v2: Fix Windows Server 2019 compilation failure.
v3: Update the patch comment.
Fix typo.

---
 app/test-pmd/parameters.c             |  63 ++++++++
 app/test-pmd/testpmd.c                | 212 ++++++++++++++++++--------
 app/test-pmd/testpmd.h                |  18 +++
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 4 files changed, 230 insertions(+), 66 deletions(-)
app/test-pmd/parameters.c +0 -0
index a9ca58339d..c6bdfdf06f 100644

app/test-pmd/parameters.c usage(char* progname) · line 215

Quoted 10 lines
app/test-pmd/parameters.c +6 -0
@@ -206,6 +206,12 @@ usage(char* progname)
      printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
             "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
             "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+     printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+            "    rxpi - Rx port index.\n"
+            "    rxq  - Rx queue.\n"
+            "    txpi - Tx port index.\n"
+            "    txq  - Tx queue.\n"
+            "    n    - hairpin queues number.\n");
 

'rxpi' or 'port index' are not common usage in DPDK, what about 'rx_port' & 'Rx port id' or similar?

app/test-pmd/parameters.c parse_link_speed(int n) · line 600

Quoted 11 lines
app/test-pmd/parameters.c +0 -0
 }
 

#ifdef RTE_LIB_CMDLINE

app/test-pmd/parameters.c +3 -0
@@ -588,6 +594,55 @@ parse_link_speed(int n)
      return speed;
 }
 
+static __rte_always_inline
+char *parse_hairpin_map_entry(char *input, char **next)
+{
 

Why forcing this function to be inline, can make it static and left decision to compiler.

<...>

doc/guides/testpmd_app_ug/run_app.rst The command line options are: · line 572

Quoted 12 lines
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 6e9c552e76..a202c98b4c 100644
@@ -566,6 +566,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 

What 'queues number' does is not intuitive, sample in the commit log makes it easier to understand.
Can you please either explain or provide sample to clarify what "queues number" is for?

#6 lore raw

PATCH v5 1/1 testpmd: add hairpin-map parameter

In reply to Ferruh Yigit

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
 app/test-pmd/hairpin.c                | 386 ++++++++++++++++++++++++++
 app/test-pmd/meson.build              |   1 +
 app/test-pmd/parameters.c             |  10 +
 app/test-pmd/testpmd.c                | 217 +--------------
 app/test-pmd/testpmd.h                |  14 +
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 6 files changed, 417 insertions(+), 214 deletions(-)
 create mode 100644 app/test-pmd/hairpin.c
app/test-pmd/hairpin.c new file +386 -0
new file mode 100644
index 0000000000..445d543851
@@ -0,0 +1,386 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 NVIDIA Corporation & Affiliates
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <sys/queue.h>
+
+#include "testpmd.h"
+
+/* Hairpin ports configuration mode. */
+uint32_t hairpin_mode;
+
+bool hairpin_multiport_mode = false;
+
+queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */
+
+static LIST_HEAD(, hairpin_map) hairpin_map_head = LIST_HEAD_INITIALIZER();
+
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+void
+hairpin_add_multiport_map(struct hairpin_map *map)
+{
+	LIST_INSERT_HEAD(&hairpin_map_head, map, entry);
+}
+
+/*
+ * Get the allowed maximum number of hairpin queues.
+ * *pid return the port id which has minimal value of
+ * max_hairpin_queues in all ports.
+ */
+queueid_t
+get_allowed_max_nb_hairpinq(portid_t *pid)
+{
+	queueid_t allowed_max_hairpinq = RTE_MAX_QUEUES_PER_PORT;
+	portid_t pi;
+	struct rte_eth_hairpin_cap cap;
+
+	RTE_ETH_FOREACH_DEV(pi) {
+		if (rte_eth_dev_hairpin_capability_get(pi, &cap) != 0) {
+			*pid = pi;
+			return 0;
+		}
+		if (cap.max_nb_queues < allowed_max_hairpinq) {
+			allowed_max_hairpinq = cap.max_nb_queues;
+			*pid = pi;
+		}
+	}
+	return allowed_max_hairpinq;
+}
+
+/*
+ * Check input hairpin is valid or not.
+ * If input hairpin is not greater than any of maximum number
+ * of hairpin queues of all ports, it is valid.
+ * if valid, return 0, else return -1
+ */
+int
+check_nb_hairpinq(queueid_t hairpinq)
+{
+	queueid_t allowed_max_hairpinq;
+	portid_t pid = 0;
+
+	allowed_max_hairpinq = get_allowed_max_nb_hairpinq(&pid);
+	if (hairpinq > allowed_max_hairpinq) {
+		fprintf(stderr,
+			"Fail: input hairpin (%u) can't be greater than max_hairpin_queues (%u) of port %u\n",
+			hairpinq, allowed_max_hairpinq, pid);
+		return -1;
+	}
+	return 0;
+}
+
+#define HAIRPIN_MODE_RX_FORCE_MEMORY RTE_BIT32(8)
+#define HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
+
+#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12)
+#define HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
+
+#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16)
+#define HAIRPIN_MODE_TX_RTE_MEMORY RTE_BIT32(17)
+
+static int
+port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_tx_port;
+		hairpin_conf.peers[0].queue = i + txq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_rx_hairpin_queue_setup
+			(pi, qi, nb_rxd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on rxq %u.\n"
+			"Peer port: %u peer txq: %u\n",
+			pi, qi, peer_tx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+port_config_hairpin_txq(portid_t pi, uint16_t peer_rx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = txq_head, i = 0; qi < txq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_rx_port;
+		hairpin_conf.peers[0].queue = i + rxq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_tx_hairpin_queue_setup
+			(pi, qi, nb_txd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on txq %u.\n"
+			"Peer port: %u peer rxq: %u\n",
+			pi, qi, peer_rx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+setup_legacy_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	int diag;
+	uint16_t peer_rx_port = pi;
+	uint16_t peer_tx_port = pi;
+	uint32_t manual = 1;
+
+	if (!(hairpin_mode & 0xf)) {
+		peer_rx_port = pi;
+		peer_tx_port = pi;
+		manual = 0;
+	} else if (hairpin_mode & 0x1) {
+		peer_tx_port = rte_eth_find_next_owned_by(pi + 1,
+							  RTE_ETH_DEV_NO_OWNER);
+		if (peer_tx_port >= RTE_MAX_ETHPORTS)
+			peer_tx_port = rte_eth_find_next_owned_by(0,
+								  RTE_ETH_DEV_NO_OWNER);
+		if (p_pi != RTE_MAX_ETHPORTS) {
+			peer_rx_port = p_pi;
+		} else {
+			uint16_t next_pi;
+
+			/* Last port will be the peer RX port of the first. */
+			RTE_ETH_FOREACH_DEV(next_pi)
+				peer_rx_port = next_pi;
+		}
+		manual = 1;
+	} else if (hairpin_mode & 0x2) {
+		if (cnt_pi & 0x1) {
+			peer_rx_port = p_pi;
+		} else {
+			peer_rx_port = rte_eth_find_next_owned_by(pi + 1,
+								  RTE_ETH_DEV_NO_OWNER);
+			if (peer_rx_port >= RTE_MAX_ETHPORTS)
+				peer_rx_port = pi;
+		}
+		peer_tx_port = peer_rx_port;
+		manual = 1;
+	}
+	diag = port_config_hairpin_txq(pi, peer_rx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	diag = port_config_hairpin_rxq(pi, peer_tx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	return 0;
+}
+
+static int
+setup_mapped_harpin_queues(portid_t pi)
+{
+	int ret = 0;
+	struct hairpin_map *map;
+
+	LIST_FOREACH(map, &hairpin_map_head, entry) {
+		if (map->rx_port == pi) {
+			ret = port_config_hairpin_rxq(pi, map->tx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+		if (map->tx_port == pi) {
+			ret = port_config_hairpin_txq(pi, map->rx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+/* Configure the Rx and Tx hairpin queues for the selected port. */
+int
+setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	return !hairpin_multiport_mode ?
+	       setup_legacy_hairpin_queues(pi, p_pi, cnt_pi) :
+	       setup_mapped_harpin_queues(pi);
+}
+
+int
+hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl)
+{
+	uint16_t i;
+	portid_t pi;
+	int peer_pi;
+	int diag;
+	int j;
+
+	/* bind all started hairpin ports */
+	for (i = 0; i < cfg_pi; i++) {
+		pi = pl[i];
+		/* bind current Tx to all peer Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 1);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(pi, peer_pl[j]);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					pi, peer_pl[j],
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+		/* bind all peer Tx to current Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 0);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(peer_pl[j], pi);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					peer_pl[j], pi,
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+hairpin_map_usage(void)
+{
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
+}
+
+static char
+*parse_hairpin_map_entry(char *input, char **next)
+{
+	char *tail = strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] = '\0';
+	*next = tail + 1;
+	return input;
+}
+
+int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	char *head, *next = (char *)(uintptr_t)hpmap;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rxq_head = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->tx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->txq_head = atoi(head);
+	map->qnum = atoi(next);
+	hairpin_add_multiport_map(map);
+	return 0;
+err:
+	free(map);
+	return -EINVAL;
+}
+
+
app/test-pmd/meson.build +1 -0
index 719f875be0..f1c36529b4 100644
@@ -15,6 +15,7 @@ sources = files(
         'config.c',
         'csumonly.c',
         'flowgen.c',
+        'hairpin.c',
         'icmpecho.c',
         'ieee1588fwd.c',
         'iofwd.c',
app/test-pmd/parameters.c +10 -0
index 22364e09ab..7b31b94542 100644
@@ -143,6 +143,8 @@ enum {
 	TESTPMD_OPT_HAIRPINQ_NUM,
 #define TESTPMD_OPT_HAIRPIN_MODE "hairpin-mode"
 	TESTPMD_OPT_HAIRPIN_MODE_NUM,
+#define TESTPMD_OPT_HAIRPIN_MAP "hairpin-map"
+	TESTPMD_OPT_HAIRPIN_MAP_NUM,
 #define TESTPMD_OPT_BURST "burst"
 	TESTPMD_OPT_BURST_NUM,
 #define TESTPMD_OPT_FLOWGEN_CLONES "flowgen-clones"
@@ -317,6 +319,7 @@ static const struct option long_options[] = {
 	REQUIRED_ARG(TESTPMD_OPT_TXD),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPINQ),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MODE),
+	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MAP),
 	REQUIRED_ARG(TESTPMD_OPT_BURST),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_CLONES),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_FLOWS),
@@ -542,6 +545,7 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	hairpin_map_usage();
 }
 
 static int
@@ -1317,6 +1321,12 @@ launch_args_parse(int argc, char** argv)
 				hairpin_mode = (uint32_t)n;
 			break;
 		}
+		case TESTPMD_OPT_HAIRPIN_MAP_NUM:
+			hairpin_multiport_mode = true;
+			ret = parse_hairpin_map(optarg);
+			if (ret)
+				rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+			break;
 		case TESTPMD_OPT_BURST_NUM:
 			n = atoi(optarg);
 			if (n == 0) {
app/test-pmd/testpmd.c +3 -214

253 lines not on this page · raw message

app/test-pmd/testpmd.h +14 -0
index 9facd7f281..659c5b4fdc 100644
@@ -126,6 +126,16 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+struct hairpin_map;
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
@@ -1252,6 +1262,10 @@ extern int flow_parse(const char *src, void *result, unsigned int size,
 		      struct rte_flow_attr **attr,
 		      struct rte_flow_item **pattern,
 		      struct rte_flow_action **actions);
+int setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi);
+int hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl);
+void hairpin_map_usage(void);
+int parse_hairpin_map(const char *hpmap);
 
 uint64_t str_to_rsstypes(const char *str);
 const char *rsstypes_to_str(uint64_t rss_type);
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 1a9b812a7f..48717707a7 100644
@@ -571,6 +571,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.43.0
#7 lore raw
Quoted 29 lines · Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

Quoted 6 lines

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

Hi Aman, reminder of this patch.

#9 lore raw
In reply to Gregory Etelson
Quoted 29 lines · Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

Quoted 6 lines

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

+Praveen
cpfl is only other driver that implements hairpin, @Praveen can you please help reviewing this patch, and confirm change works fine with your changes.

@Stephen, if you have any free cycles, can you please help on this patch too?

Thanks,
ferruh

#10 lore raw
Quoted 42 lines · On Tue, 29 Oct 2024 21:51:40 +0000 Ferruh Yigit <[email protected]> wrote:
Quoted 29 lines · Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

Quoted 6 lines

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

+Praveen
cpfl is only other driver that implements hairpin, @Praveen can you please help reviewing this patch, and confirm change works fine with your changes.

Quoted 5 lines

@Stephen, if you have any free cycles, can you please help on this patch too?

Thanks,
ferruh

Ok, can do semantic review but do not have any working HW with hairpin capability.

#11 lore raw

PATCH v6 1/1 testpmd: add hairpin map parameter

In reply to Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
 app/test-pmd/hairpin.c                | 360 ++++++++++++++++++++++++++
 app/test-pmd/meson.build              |   1 +
 app/test-pmd/parameters.c             |  10 +
 app/test-pmd/testpmd.c                | 217 +---------------
 app/test-pmd/testpmd.h                |  14 +
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 6 files changed, 391 insertions(+), 214 deletions(-)
 create mode 100644 app/test-pmd/hairpin.c
app/test-pmd/hairpin.c new file +360 -0
new file mode 100644
index 0000000000..6d42fdc088
@@ -0,0 +1,360 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 NVIDIA Corporation & Affiliates
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <sys/queue.h>
+
+#include "testpmd.h"
+
+/* Hairpin ports configuration mode. */
+uint32_t hairpin_mode;
+
+bool hairpin_multiport_mode = false;
+
+queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */
+
+static LIST_HEAD(, hairpin_map) hairpin_map_head = LIST_HEAD_INITIALIZER();
+
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+void
+hairpin_add_multiport_map(struct hairpin_map *map)
+{
+	LIST_INSERT_HEAD(&hairpin_map_head, map, entry);
+}
+
+/*
+ * Get the allowed maximum number of hairpin queues.
+ * *pid return the port id which has minimal value of
+ * max_hairpin_queues in all ports.
+ */
+queueid_t
+get_allowed_max_nb_hairpinq(portid_t *pid)
+{
+	queueid_t allowed_max_hairpinq = RTE_MAX_QUEUES_PER_PORT;
+	portid_t pi;
+	struct rte_eth_hairpin_cap cap;
+
+	RTE_ETH_FOREACH_DEV(pi) {
+		if (rte_eth_dev_hairpin_capability_get(pi, &cap) != 0) {
+			*pid = pi;
+			return 0;
+		}
+		if (cap.max_nb_queues < allowed_max_hairpinq) {
+			allowed_max_hairpinq = cap.max_nb_queues;
+			*pid = pi;
+		}
+	}
+	return allowed_max_hairpinq;
+}
+
+/*
+ * Check input hairpin is valid or not.
+ * If input hairpin is not greater than any of maximum number
+ * of hairpin queues of all ports, it is valid.
+ * if valid, return 0, else return -1
+ */
+int
+check_nb_hairpinq(queueid_t hairpinq)
+{
+	queueid_t allowed_max_hairpinq;
+	portid_t pid = 0;
+
+	allowed_max_hairpinq = get_allowed_max_nb_hairpinq(&pid);
+	if (hairpinq > allowed_max_hairpinq) {
+		fprintf(stderr,
+			"Fail: input hairpin (%u) can't be greater than max_hairpin_queues (%u) of port %u\n",
+			hairpinq, allowed_max_hairpinq, pid);
+		return -1;
+	}
+	return 0;
+}
+
+#define HAIRPIN_MODE_RX_FORCE_MEMORY RTE_BIT32(8)
+#define HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
+
+#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12)
+#define HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
+
+#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16)
+#define HAIRPIN_MODE_TX_RTE_MEMORY RTE_BIT32(17)
+
+static int
+port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_tx_port;
+		hairpin_conf.peers[0].queue = i + txq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_rx_hairpin_queue_setup
+			(pi, qi, nb_rxd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %u failed to configure hairpin on rxq %u.\n"
+			"Peer port: %u peer txq: %u\n",
+			pi, qi, peer_tx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+port_config_hairpin_txq(portid_t pi, uint16_t peer_rx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = txq_head, i = 0; qi < txq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_rx_port;
+		hairpin_conf.peers[0].queue = i + rxq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_tx_hairpin_queue_setup
+			(pi, qi, nb_txd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on txq %u.\n"
+			"Peer port: %u peer rxq: %u\n",
+			pi, qi, peer_rx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+setup_legacy_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	int diag;
+	uint16_t peer_rx_port = pi;
+	uint16_t peer_tx_port = pi;
+	uint32_t manual = 1;
+
+	if (!(hairpin_mode & 0xf)) {
+		peer_rx_port = pi;
+		peer_tx_port = pi;
+		manual = 0;
+	} else if (hairpin_mode & 0x1) {
+		peer_tx_port = rte_eth_find_next_owned_by(pi + 1,
+							  RTE_ETH_DEV_NO_OWNER);
+		if (peer_tx_port >= RTE_MAX_ETHPORTS)
+			peer_tx_port = rte_eth_find_next_owned_by(0,
+								  RTE_ETH_DEV_NO_OWNER);
+		if (p_pi != RTE_MAX_ETHPORTS) {
+			peer_rx_port = p_pi;
+		} else {
+			uint16_t next_pi;
+
+			/* Last port will be the peer RX port of the first. */
+			RTE_ETH_FOREACH_DEV(next_pi)
+				peer_rx_port = next_pi;
+		}
+		manual = 1;
+	} else if (hairpin_mode & 0x2) {
+		if (cnt_pi & 0x1) {
+			peer_rx_port = p_pi;
+		} else {
+			peer_rx_port = rte_eth_find_next_owned_by(pi + 1,
+								  RTE_ETH_DEV_NO_OWNER);
+			if (peer_rx_port >= RTE_MAX_ETHPORTS)
+				peer_rx_port = pi;
+		}
+		peer_tx_port = peer_rx_port;
+		manual = 1;
+	}
+	diag = port_config_hairpin_txq(pi, peer_rx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	diag = port_config_hairpin_rxq(pi, peer_tx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	return 0;
+}
+
+static int
+setup_mapped_harpin_queues(portid_t pi)
+{
+	int ret = 0;
+	struct hairpin_map *map;
+
+	LIST_FOREACH(map, &hairpin_map_head, entry) {
+		if (map->rx_port == pi) {
+			ret = port_config_hairpin_rxq(pi, map->tx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+		if (map->tx_port == pi) {
+			ret = port_config_hairpin_txq(pi, map->rx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+/* Configure the Rx and Tx hairpin queues for the selected port. */
+int
+setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	if (hairpin_multiport_mode)
+		return setup_mapped_harpin_queues(pi);
+
+	return setup_legacy_hairpin_queues(pi, p_pi, cnt_pi);
+}
+
+int
+hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl)
+{
+	uint16_t i;
+	portid_t pi;
+	int peer_pi;
+	int diag;
+	int j;
+
+	/* bind all started hairpin ports */
+	for (i = 0; i < cfg_pi; i++) {
+		pi = pl[i];
+		/* bind current Tx to all peer Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 1);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(pi, peer_pl[j]);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					pi, peer_pl[j],
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+		/* bind all peer Tx to current Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 0);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(peer_pl[j], pi);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					peer_pl[j], pi,
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+hairpin_map_usage(void)
+{
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
+}
+
+int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	int ret;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	ret = sscanf(hpmap, "%hu:%hu:%hu:%hu:%hu",
+		     &map->rx_port, &map->rxq_head,
+		     &map->tx_port, &map->txq_head, &map->qnum);
+	if (ret != 5) {
+		free(map);
+		return -EINVAL;
+	}
+	hairpin_add_multiport_map(map);
+	return 0;
+}
app/test-pmd/meson.build +1 -0
index 719f875be0..f1c36529b4 100644
@@ -15,6 +15,7 @@ sources = files(
         'config.c',
         'csumonly.c',
         'flowgen.c',
+        'hairpin.c',
         'icmpecho.c',
         'ieee1588fwd.c',
         'iofwd.c',
app/test-pmd/parameters.c +10 -0
index 22364e09ab..7b31b94542 100644
@@ -143,6 +143,8 @@ enum {
 	TESTPMD_OPT_HAIRPINQ_NUM,
 #define TESTPMD_OPT_HAIRPIN_MODE "hairpin-mode"
 	TESTPMD_OPT_HAIRPIN_MODE_NUM,
+#define TESTPMD_OPT_HAIRPIN_MAP "hairpin-map"
+	TESTPMD_OPT_HAIRPIN_MAP_NUM,
 #define TESTPMD_OPT_BURST "burst"
 	TESTPMD_OPT_BURST_NUM,
 #define TESTPMD_OPT_FLOWGEN_CLONES "flowgen-clones"
@@ -317,6 +319,7 @@ static const struct option long_options[] = {
 	REQUIRED_ARG(TESTPMD_OPT_TXD),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPINQ),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MODE),
+	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MAP),
 	REQUIRED_ARG(TESTPMD_OPT_BURST),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_CLONES),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_FLOWS),
@@ -542,6 +545,7 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	hairpin_map_usage();
 }
 
 static int
@@ -1317,6 +1321,12 @@ launch_args_parse(int argc, char** argv)
 				hairpin_mode = (uint32_t)n;
 			break;
 		}
+		case TESTPMD_OPT_HAIRPIN_MAP_NUM:
+			hairpin_multiport_mode = true;
+			ret = parse_hairpin_map(optarg);
+			if (ret)
+				rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+			break;
 		case TESTPMD_OPT_BURST_NUM:
 			n = atoi(optarg);
 			if (n == 0) {
app/test-pmd/testpmd.c +3 -214

253 lines not on this page · raw message

app/test-pmd/testpmd.h +14 -0
index 131ea53f84..314482e69c 100644
@@ -126,6 +126,16 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+struct hairpin_map;
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
@@ -1255,6 +1265,10 @@ extern int flow_parse(const char *src, void *result, unsigned int size,
 		      struct rte_flow_attr **attr,
 		      struct rte_flow_item **pattern,
 		      struct rte_flow_action **actions);
+int setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi);
+int hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl);
+void hairpin_map_usage(void);
+int parse_hairpin_map(const char *hpmap);
 
 uint64_t str_to_rsstypes(const char *str);
 const char *rsstypes_to_str(uint64_t rss_type);
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 1a9b812a7f..48717707a7 100644
@@ -571,6 +571,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.43.0
#12 lore raw

Re: [PATCH v6] testpmd: add hairpin map parameter

Quoted 14 lines · On Wed, 30 Oct 2024 09:37:24 +0200 Gregory Etelson <[email protected]> wrote:
app/test-pmd/hairpin.c new file +14 -0
+static int
+port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,

app/test-pmd/hairpin.c line 118

Quoted 10 lines
app/test-pmd/hairpin.c new file +10 -0
+	};
+
+	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_tx_port;
+		hairpin_conf.peers[0].queue = i + txq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;

Did you consider that most of the hairpin_conf values could just be set directly, avoiding intermediate variables.

app/test-pmd/hairpin.c line 120

Quoted 2 lines
app/test-pmd/hairpin.c new file +2 -0
+		diag = rte_eth_rx_hairpin_queue_setup
+			(pi, qi, nb_rxd, &hairpin_conf);

Unnecessary line break.

Suggestion:

static int
port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
queueid_t rxq_head, queueid_t txq_head, uint16_t qcount, uint32_t manual_bind)

{
	int diag;
	queueid_t i, qi;
	struct rte_port *port = &ports[pi];
	struct rte_eth_hairpin_conf hairpin_conf = {
		.peer_count = 1,
		.manual_bind = manual_bind,
		.tx_explicit = !!(hairpin_mode & 0x10),
		.force_memory = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY),
		.use_locked_device_memory = !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY),
		.use_rte_memory = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY),
	};

	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++) {
		hairpin_conf.peers[0].port = peer_tx_port;
		hairpin_conf.peers[0].queue = i + txq_head;

		diag = rte_eth_rx_hairpin_queue_setup(pi, qi, nb_rxd, &hairpin_conf);
		i++;
		if (diag == 0)
			continue;
#13 lore raw

Re: [PATCH v6] testpmd: add hairpin map parameter

Hello Stephen,

Quoted 4 lines

Did you consider that most of the hairpin_conf values could just be set directly, avoiding intermediate variables.

fixed.

Quoted 4 lines

app/test-pmd/hairpin.c line 120

Quoted 3 lines
app/test-pmd/hairpin.c new file +2 -0
+             diag = rte_eth_rx_hairpin_queue_setup
+                     (pi, qi, nb_rxd, &hairpin_conf);

fixed.

#14 lore raw

PATCH v7 1/1 testpmd: add hairpin map parameter

In reply to Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
 app/test-pmd/hairpin.c                | 350 ++++++++++++++++++++++++++
 app/test-pmd/meson.build              |   1 +
 app/test-pmd/parameters.c             |  10 +
 app/test-pmd/testpmd.c                | 217 +---------------
 app/test-pmd/testpmd.h                |  14 ++
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 6 files changed, 381 insertions(+), 214 deletions(-)
 create mode 100644 app/test-pmd/hairpin.c
app/test-pmd/hairpin.c new file +350 -0
new file mode 100644
index 0000000000..55ee69c653
@@ -0,0 +1,350 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 NVIDIA Corporation & Affiliates
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <sys/queue.h>
+
+#include "testpmd.h"
+
+/* Hairpin ports configuration mode. */
+uint32_t hairpin_mode;
+
+bool hairpin_multiport_mode = false;
+
+queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */
+
+static LIST_HEAD(, hairpin_map) hairpin_map_head = LIST_HEAD_INITIALIZER();
+
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+void
+hairpin_add_multiport_map(struct hairpin_map *map)
+{
+	LIST_INSERT_HEAD(&hairpin_map_head, map, entry);
+}
+
+/*
+ * Get the allowed maximum number of hairpin queues.
+ * *pid return the port id which has minimal value of
+ * max_hairpin_queues in all ports.
+ */
+queueid_t
+get_allowed_max_nb_hairpinq(portid_t *pid)
+{
+	queueid_t allowed_max_hairpinq = RTE_MAX_QUEUES_PER_PORT;
+	portid_t pi;
+	struct rte_eth_hairpin_cap cap;
+
+	RTE_ETH_FOREACH_DEV(pi) {
+		if (rte_eth_dev_hairpin_capability_get(pi, &cap) != 0) {
+			*pid = pi;
+			return 0;
+		}
+		if (cap.max_nb_queues < allowed_max_hairpinq) {
+			allowed_max_hairpinq = cap.max_nb_queues;
+			*pid = pi;
+		}
+	}
+	return allowed_max_hairpinq;
+}
+
+/*
+ * Check input hairpin is valid or not.
+ * If input hairpin is not greater than any of maximum number
+ * of hairpin queues of all ports, it is valid.
+ * if valid, return 0, else return -1
+ */
+int
+check_nb_hairpinq(queueid_t hairpinq)
+{
+	queueid_t allowed_max_hairpinq;
+	portid_t pid = 0;
+
+	allowed_max_hairpinq = get_allowed_max_nb_hairpinq(&pid);
+	if (hairpinq > allowed_max_hairpinq) {
+		fprintf(stderr,
+			"Fail: input hairpin (%u) can't be greater than max_hairpin_queues (%u) of port %u\n",
+			hairpinq, allowed_max_hairpinq, pid);
+		return -1;
+	}
+	return 0;
+}
+
+#define HAIRPIN_MODE_RX_FORCE_MEMORY RTE_BIT32(8)
+#define HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
+
+#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12)
+#define HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
+
+#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16)
+#define HAIRPIN_MODE_TX_RTE_MEMORY RTE_BIT32(17)
+
+static int
+port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+		.peers[0].port = peer_tx_port,
+		.manual_bind = manual_bind,
+		.tx_explicit = !!(hairpin_mode & 0x10),
+		.force_memory = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY),
+		.use_locked_device_memory =
+			!!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY),
+		.use_rte_memory = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY),
+	};
+
+	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++, i++) {
+		hairpin_conf.peers[0].queue = i + txq_head;
+		diag = rte_eth_rx_hairpin_queue_setup(pi, qi, nb_rxd, &hairpin_conf);
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %u failed to configure hairpin on rxq %u.\n"
+			"Peer port: %u peer txq: %u\n",
+			pi, qi, peer_tx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+port_config_hairpin_txq(portid_t pi, uint16_t peer_rx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+		.peers[0].port = peer_rx_port,
+		.manual_bind = manual_bind,
+		.tx_explicit = !!(hairpin_mode & 0x10),
+		.force_memory = !!(hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY),
+		.use_locked_device_memory =
+			!!(hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY),
+		.use_rte_memory = !!(hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY),
+	};
+
+	for (qi = txq_head, i = 0; qi < txq_head + qcount; qi++, i++) {
+		hairpin_conf.peers[0].queue = i + rxq_head;
+		diag = rte_eth_tx_hairpin_queue_setup(pi, qi, nb_txd, &hairpin_conf);
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on txq %u.\n"
+			"Peer port: %u peer rxq: %u\n",
+			pi, qi, peer_rx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+setup_legacy_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	int diag;
+	uint16_t peer_rx_port = pi;
+	uint16_t peer_tx_port = pi;
+	uint32_t manual = 1;
+
+	if (!(hairpin_mode & 0xf)) {
+		peer_rx_port = pi;
+		peer_tx_port = pi;
+		manual = 0;
+	} else if (hairpin_mode & 0x1) {
+		peer_tx_port = rte_eth_find_next_owned_by(pi + 1,
+							  RTE_ETH_DEV_NO_OWNER);
+		if (peer_tx_port >= RTE_MAX_ETHPORTS)
+			peer_tx_port = rte_eth_find_next_owned_by(0,
+								  RTE_ETH_DEV_NO_OWNER);
+		if (p_pi != RTE_MAX_ETHPORTS) {
+			peer_rx_port = p_pi;
+		} else {
+			uint16_t next_pi;
+
+			/* Last port will be the peer RX port of the first. */
+			RTE_ETH_FOREACH_DEV(next_pi)
+				peer_rx_port = next_pi;
+		}
+		manual = 1;
+	} else if (hairpin_mode & 0x2) {
+		if (cnt_pi & 0x1) {
+			peer_rx_port = p_pi;
+		} else {
+			peer_rx_port = rte_eth_find_next_owned_by(pi + 1,
+								  RTE_ETH_DEV_NO_OWNER);
+			if (peer_rx_port >= RTE_MAX_ETHPORTS)
+				peer_rx_port = pi;
+		}
+		peer_tx_port = peer_rx_port;
+		manual = 1;
+	}
+	diag = port_config_hairpin_txq(pi, peer_rx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	diag = port_config_hairpin_rxq(pi, peer_tx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	return 0;
+}
+
+static int
+setup_mapped_harpin_queues(portid_t pi)
+{
+	int ret = 0;
+	struct hairpin_map *map;
+
+	LIST_FOREACH(map, &hairpin_map_head, entry) {
+		if (map->rx_port == pi) {
+			ret = port_config_hairpin_rxq(pi, map->tx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+		if (map->tx_port == pi) {
+			ret = port_config_hairpin_txq(pi, map->rx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+/* Configure the Rx and Tx hairpin queues for the selected port. */
+int
+setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	if (hairpin_multiport_mode)
+		return setup_mapped_harpin_queues(pi);
+
+	return setup_legacy_hairpin_queues(pi, p_pi, cnt_pi);
+}
+
+int
+hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl)
+{
+	uint16_t i;
+	portid_t pi;
+	int peer_pi;
+	int diag;
+	int j;
+
+	/* bind all started hairpin ports */
+	for (i = 0; i < cfg_pi; i++) {
+		pi = pl[i];
+		/* bind current Tx to all peer Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 1);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(pi, peer_pl[j]);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					pi, peer_pl[j],
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+		/* bind all peer Tx to current Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 0);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(peer_pl[j], pi);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					peer_pl[j], pi,
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+hairpin_map_usage(void)
+{
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
+}
+
+int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	int ret;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	ret = sscanf(hpmap, "%hu:%hu:%hu:%hu:%hu",
+		     &map->rx_port, &map->rxq_head,
+		     &map->tx_port, &map->txq_head, &map->qnum);
+	if (ret != 5) {
+		free(map);
+		return -EINVAL;
+	}
+	hairpin_add_multiport_map(map);
+	return 0;
+}
app/test-pmd/meson.build +1 -0
index 719f875be0..f1c36529b4 100644
@@ -15,6 +15,7 @@ sources = files(
         'config.c',
         'csumonly.c',
         'flowgen.c',
+        'hairpin.c',
         'icmpecho.c',
         'ieee1588fwd.c',
         'iofwd.c',
app/test-pmd/parameters.c +10 -0
index 22364e09ab..7b31b94542 100644
@@ -143,6 +143,8 @@ enum {
 	TESTPMD_OPT_HAIRPINQ_NUM,
 #define TESTPMD_OPT_HAIRPIN_MODE "hairpin-mode"
 	TESTPMD_OPT_HAIRPIN_MODE_NUM,
+#define TESTPMD_OPT_HAIRPIN_MAP "hairpin-map"
+	TESTPMD_OPT_HAIRPIN_MAP_NUM,
 #define TESTPMD_OPT_BURST "burst"
 	TESTPMD_OPT_BURST_NUM,
 #define TESTPMD_OPT_FLOWGEN_CLONES "flowgen-clones"
@@ -317,6 +319,7 @@ static const struct option long_options[] = {
 	REQUIRED_ARG(TESTPMD_OPT_TXD),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPINQ),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MODE),
+	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MAP),
 	REQUIRED_ARG(TESTPMD_OPT_BURST),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_CLONES),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_FLOWS),
@@ -542,6 +545,7 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	hairpin_map_usage();
 }
 
 static int
@@ -1317,6 +1321,12 @@ launch_args_parse(int argc, char** argv)
 				hairpin_mode = (uint32_t)n;
 			break;
 		}
+		case TESTPMD_OPT_HAIRPIN_MAP_NUM:
+			hairpin_multiport_mode = true;
+			ret = parse_hairpin_map(optarg);
+			if (ret)
+				rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+			break;
 		case TESTPMD_OPT_BURST_NUM:
 			n = atoi(optarg);
 			if (n == 0) {
app/test-pmd/testpmd.c +3 -214

253 lines not on this page · raw message

app/test-pmd/testpmd.h +14 -0
index 131ea53f84..314482e69c 100644
@@ -126,6 +126,16 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+struct hairpin_map;
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
@@ -1255,6 +1265,10 @@ extern int flow_parse(const char *src, void *result, unsigned int size,
 		      struct rte_flow_attr **attr,
 		      struct rte_flow_item **pattern,
 		      struct rte_flow_action **actions);
+int setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi);
+int hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl);
+void hairpin_map_usage(void);
+int parse_hairpin_map(const char *hpmap);
 
 uint64_t str_to_rsstypes(const char *str);
 const char *rsstypes_to_str(uint64_t rss_type);
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 1a9b812a7f..48717707a7 100644
@@ -571,6 +571,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.43.0
Acked-by on v7 1/1
#15 lore raw

Re: [PATCH v7] testpmd: add hairpin map parameter

Quoted 29 lines · On Thu, 31 Oct 2024 06:58:17 +0200 Gregory Etelson <[email protected]> wrote:

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

Quoted 5 lines

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

Applied
#16 lore raw

Re: [PATCH v7] testpmd: add hairpin map parameter

Quoted 32 lines · Stephen Hemminger
Quoted 29 lines · On Thu, 31 Oct 2024 06:58:17 +0200 Gregory Etelson <[email protected]> wrote:

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

Quoted 8 lines
Quoted 5 lines

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

Applied to dpdk-next-net/main, thanks.

#17 lore raw

PATCH v4 1/1 testpmd: add hairpin-map parameter

In reply to Gregory Etelson

Hairpin offloads packet forwarding between ports. Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration.
The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Testpmd operator can provide several hairpin-map parameters for different hairpin maps.
Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
 app/test-pmd/hairpin.c                | 385 ++++++++++++++++++++++++++
 app/test-pmd/meson.build              |   1 +
 app/test-pmd/parameters.c             |  10 +
 app/test-pmd/testpmd.c                | 217 +--------------
 app/test-pmd/testpmd.h                |  14 +
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 6 files changed, 416 insertions(+), 214 deletions(-)
 create mode 100644 app/test-pmd/hairpin.c
app/test-pmd/hairpin.c new file +385 -0
new file mode 100644
index 0000000000..de54e8bac8
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 NVIDIA Corporation & Affiliates
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <sys/queue.h>
+
+#include "testpmd.h"
+
+/* Hairpin ports configuration mode. */
+uint32_t hairpin_mode;
+
+bool hairpin_multiport_mode = false;
+
+queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */
+
+static LIST_HEAD(, hairpin_map) hairpin_map_head = LIST_HEAD_INITIALIZER();
+
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */
+};
+
+void
+hairpin_add_multiport_map(struct hairpin_map *map)
+{
+	LIST_INSERT_HEAD(&hairpin_map_head, map, entry);
+}
+
+/*
+ * Get the allowed maximum number of hairpin queues.
+ * *pid return the port id which has minimal value of
+ * max_hairpin_queues in all ports.
+ */
+queueid_t
+get_allowed_max_nb_hairpinq(portid_t *pid)
+{
+	queueid_t allowed_max_hairpinq = RTE_MAX_QUEUES_PER_PORT;
+	portid_t pi;
+	struct rte_eth_hairpin_cap cap;
+
+	RTE_ETH_FOREACH_DEV(pi) {
+		if (rte_eth_dev_hairpin_capability_get(pi, &cap) != 0) {
+			*pid = pi;
+			return 0;
+		}
+		if (cap.max_nb_queues < allowed_max_hairpinq) {
+			allowed_max_hairpinq = cap.max_nb_queues;
+			*pid = pi;
+		}
+	}
+	return allowed_max_hairpinq;
+}
+
+/*
+ * Check input hairpin is valid or not.
+ * If input hairpin is not greater than any of maximum number
+ * of hairpin queues of all ports, it is valid.
+ * if valid, return 0, else return -1
+ */
+int
+check_nb_hairpinq(queueid_t hairpinq)
+{
+	queueid_t allowed_max_hairpinq;
+	portid_t pid = 0;
+
+	allowed_max_hairpinq = get_allowed_max_nb_hairpinq(&pid);
+	if (hairpinq > allowed_max_hairpinq) {
+		fprintf(stderr,
+			"Fail: input hairpin (%u) can't be greater than max_hairpin_queues (%u) of port %u\n",
+			hairpinq, allowed_max_hairpinq, pid);
+		return -1;
+	}
+	return 0;
+}
+
+#define HAIRPIN_MODE_RX_FORCE_MEMORY RTE_BIT32(8)
+#define HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
+
+#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12)
+#define HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
+
+#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16)
+#define HAIRPIN_MODE_TX_RTE_MEMORY RTE_BIT32(17)
+
+static int
+port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_tx_port;
+		hairpin_conf.peers[0].queue = i + txq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_rx_hairpin_queue_setup
+			(pi, qi, nb_rxd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on rxq %u.\n"
+			"Peer port: %u peer txq: %u\n",
+			pi, qi, peer_tx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+port_config_hairpin_txq(portid_t pi, uint16_t peer_rx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind)
+{
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = txq_head, i = 0; qi < txq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_rx_port;
+		hairpin_conf.peers[0].queue = i + rxq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_tx_hairpin_queue_setup
+			(pi, qi, nb_txd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on txq %u.\n"
+			"Peer port: %u peer rxq: %u\n",
+			pi, qi, peer_rx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+setup_legacy_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	int diag;
+	uint16_t peer_rx_port = pi;
+	uint16_t peer_tx_port = pi;
+	uint32_t manual = 1;
+
+	if (!(hairpin_mode & 0xf)) {
+		peer_rx_port = pi;
+		peer_tx_port = pi;
+		manual = 0;
+	} else if (hairpin_mode & 0x1) {
+		peer_tx_port = rte_eth_find_next_owned_by(pi + 1,
+							  RTE_ETH_DEV_NO_OWNER);
+		if (peer_tx_port >= RTE_MAX_ETHPORTS)
+			peer_tx_port = rte_eth_find_next_owned_by(0,
+								  RTE_ETH_DEV_NO_OWNER);
+		if (p_pi != RTE_MAX_ETHPORTS) {
+			peer_rx_port = p_pi;
+		} else {
+			uint16_t next_pi;
+
+			/* Last port will be the peer RX port of the first. */
+			RTE_ETH_FOREACH_DEV(next_pi)
+				peer_rx_port = next_pi;
+		}
+		manual = 1;
+	} else if (hairpin_mode & 0x2) {
+		if (cnt_pi & 0x1) {
+			peer_rx_port = p_pi;
+		} else {
+			peer_rx_port = rte_eth_find_next_owned_by(pi + 1,
+								  RTE_ETH_DEV_NO_OWNER);
+			if (peer_rx_port >= RTE_MAX_ETHPORTS)
+				peer_rx_port = pi;
+		}
+		peer_tx_port = peer_rx_port;
+		manual = 1;
+	}
+	diag = port_config_hairpin_txq(pi, peer_rx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	diag = port_config_hairpin_rxq(pi, peer_tx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	return 0;
+}
+
+static int
+setup_mapped_harpin_queues(portid_t pi)
+{
+	int ret = 0;
+	struct hairpin_map *map;
+
+	LIST_FOREACH(map, &hairpin_map_head, entry) {
+		if (map->rx_port == pi) {
+			ret = port_config_hairpin_rxq(pi, map->tx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+		if (map->tx_port == pi) {
+			ret = port_config_hairpin_txq(pi, map->rx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+/* Configure the Rx and Tx hairpin queues for the selected port. */
+int
+setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi)
+{
+	return !hairpin_multiport_mode ?
+	       setup_legacy_hairpin_queues(pi, p_pi, cnt_pi) :
+	       setup_mapped_harpin_queues(pi);
+}
+
+int
+hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl)
+{
+	uint16_t i;
+	portid_t pi;
+	int peer_pi;
+	int diag;
+	int j;
+
+	/* bind all started hairpin ports */
+	for (i = 0; i < cfg_pi; i++) {
+		pi = pl[i];
+		/* bind current Tx to all peer Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 1);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(pi, peer_pl[j]);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					pi, peer_pl[j],
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+		/* bind all peer Tx to current Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 0);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(peer_pl[j], pi);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					peer_pl[j], pi,
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+hairpin_map_usage(void)
+{
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
+}
+
+static char
+*parse_hairpin_map_entry(char *input, char **next)
+{
+	char *tail = strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] = '\0';
+	*next = tail + 1;
+	return input;
+}
+
+int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	char *head, *next = (char *)(uintptr_t)hpmap;
+	struct hairpin_map *map = calloc(1, sizeof(*map));
+
+	if (!map)
+		return -ENOMEM;
+
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rxq_head = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->tx_port = atoi(head);
+	head = parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->txq_head = atoi(head);
+	map->qnum = atoi(next);
+	hairpin_add_multiport_map(map);
+	return 0;
+err:
+	free(map);
+	return -EINVAL;
+}
+
+
app/test-pmd/meson.build +1 -0
index 719f875be0..f1c36529b4 100644
@@ -15,6 +15,7 @@ sources = files(
         'config.c',
         'csumonly.c',
         'flowgen.c',
+        'hairpin.c',
         'icmpecho.c',
         'ieee1588fwd.c',
         'iofwd.c',
app/test-pmd/parameters.c +10 -0
index 22364e09ab..7b31b94542 100644
@@ -143,6 +143,8 @@ enum {
 	TESTPMD_OPT_HAIRPINQ_NUM,
 #define TESTPMD_OPT_HAIRPIN_MODE "hairpin-mode"
 	TESTPMD_OPT_HAIRPIN_MODE_NUM,
+#define TESTPMD_OPT_HAIRPIN_MAP "hairpin-map"
+	TESTPMD_OPT_HAIRPIN_MAP_NUM,
 #define TESTPMD_OPT_BURST "burst"
 	TESTPMD_OPT_BURST_NUM,
 #define TESTPMD_OPT_FLOWGEN_CLONES "flowgen-clones"
@@ -317,6 +319,7 @@ static const struct option long_options[] = {
 	REQUIRED_ARG(TESTPMD_OPT_TXD),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPINQ),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MODE),
+	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MAP),
 	REQUIRED_ARG(TESTPMD_OPT_BURST),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_CLONES),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_FLOWS),
@@ -542,6 +545,7 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	hairpin_map_usage();
 }
 
 static int
@@ -1317,6 +1321,12 @@ launch_args_parse(int argc, char** argv)
 				hairpin_mode = (uint32_t)n;
 			break;
 		}
+		case TESTPMD_OPT_HAIRPIN_MAP_NUM:
+			hairpin_multiport_mode = true;
+			ret = parse_hairpin_map(optarg);
+			if (ret)
+				rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+			break;
 		case TESTPMD_OPT_BURST_NUM:
 			n = atoi(optarg);
 			if (n == 0) {
app/test-pmd/testpmd.c +3 -214

253 lines not on this page · raw message

app/test-pmd/testpmd.h +14 -0
index 9facd7f281..659c5b4fdc 100644
@@ -126,6 +126,16 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+struct hairpin_map;
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
@@ -1252,6 +1262,10 @@ extern int flow_parse(const char *src, void *result, unsigned int size,
 		      struct rte_flow_attr **attr,
 		      struct rte_flow_item **pattern,
 		      struct rte_flow_action **actions);
+int setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi);
+int hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl);
+void hairpin_map_usage(void);
+int parse_hairpin_map(const char *hpmap);
 
 uint64_t str_to_rsstypes(const char *str);
 const char *rsstypes_to_str(uint64_t rss_type);
doc/guides/testpmd_app_ug/run_app.rst +3 -0
index 1a9b812a7f..48717707a7 100644
@@ -571,6 +571,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Signature · 1 line
2.43.0
Acked-by Dariusz Sosnowski on v4 1/1
#18 lore raw

Hi Gregory,
Can you please resolve the compilation issues seen with this patch.

Hairpin offloads packet forwarding between ports.
Packet is expected on Rx port <rp>, Rx queue <rq> and is forwarded to Tx port <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

The new parameter allows explicit selection of Rx and Tx ports and queues in hairpin configuration. The new hairpin-map parameter is provided with 5 parameters, separated by :

--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number

Can we rephrase the parameter "queues number " to something like "total queue pairs"

Testpmd operator can provide several hairpin-map parameters for different hairpin maps. Example:

dpdk-testpmd <EAL params> -- \
<testpmd params> \
--rxq=2 --txq=2 --hairpinq=2 --hairpin-mode=0x12 \
--hairpin-map=0:2:1:2:1 \ # [1]
--hairpin-map=0:3:2:2:3 # [2]

Hairpin map [1] binds Rx port 0, queue 2 with Tx port 1, queue 2. Hairpin map [2] binds
Rx port 0, queue 3 with Tx port 2, queue 2,
Rx port 0, queue 4 with Tx port 2, queue 3,
Rx port 0, queue 5 with Tx port 2, queue 4.

The new hairpin-map parameter is optional. If omitted, testpmd will create "default" hairpin maps.

---
 app/test-pmd/hairpin.c                | 385 ++++++++++++++++++++++++++
 app/test-pmd/meson.build              |   1 +
 app/test-pmd/parameters.c             |  10 +
 app/test-pmd/testpmd.c                | 217 +--------------
 app/test-pmd/testpmd.h                |  14 +
 doc/guides/testpmd_app_ug/run_app.rst |   3 +
 6 files changed, 416 insertions(+), 214 deletions(-)  create mode 100644 app/test-pmd/hairpin.c
app/test-pmd/hairpin.c new file +343 -0
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2022 NVIDIA Corporation & Affiliates  */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <sys/queue.h>
+
+#include "testpmd.h"
+
+/* Hairpin ports configuration mode. */ uint32_t hairpin_mode;
+
+bool hairpin_multiport_mode = false;
+
+queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */
+
+static LIST_HEAD(, hairpin_map) hairpin_map_head = 
+LIST_HEAD_INITIALIZER();
+
+struct hairpin_map {
+	LIST_ENTRY(hairpin_map) entry; /**< List entry. */
+	portid_t rx_port; /**< Hairpin Rx port ID. */
+	portid_t tx_port; /**< Hairpin Tx port ID. */
+	uint16_t rxq_head; /**< Hairpin Rx queue head. */
+	uint16_t txq_head; /**< Hairpin Tx queue head. */
+	uint16_t qnum; /**< Hairpin queues number. */ };
+
+void
+hairpin_add_multiport_map(struct hairpin_map *map) {
+	LIST_INSERT_HEAD(&hairpin_map_head, map, entry); }
+
+/*
+ * Get the allowed maximum number of hairpin queues.
+ * *pid return the port id which has minimal value of
+ * max_hairpin_queues in all ports.
+ */
+queueid_t
+get_allowed_max_nb_hairpinq(portid_t *pid) {
+	queueid_t allowed_max_hairpinq = RTE_MAX_QUEUES_PER_PORT;
+	portid_t pi;
+	struct rte_eth_hairpin_cap cap;
+
+	RTE_ETH_FOREACH_DEV(pi) {
+		if (rte_eth_dev_hairpin_capability_get(pi, &cap) != 0) {
+			*pid = pi;
+			return 0;
+		}
+		if (cap.max_nb_queues < allowed_max_hairpinq) {
+			allowed_max_hairpinq = cap.max_nb_queues;
+			*pid = pi;
+		}
+	}
+	return allowed_max_hairpinq;
+}
+
+/*
+ * Check input hairpin is valid or not.
+ * If input hairpin is not greater than any of maximum number
+ * of hairpin queues of all ports, it is valid.
+ * if valid, return 0, else return -1
+ */
+int
+check_nb_hairpinq(queueid_t hairpinq)
+{
+	queueid_t allowed_max_hairpinq;
+	portid_t pid = 0;
+
+	allowed_max_hairpinq = get_allowed_max_nb_hairpinq(&pid);
+	if (hairpinq > allowed_max_hairpinq) {
+		fprintf(stderr,
+			"Fail: input hairpin (%u) can't be greater than max_hairpin_queues (%u) of port %u\n",
+			hairpinq, allowed_max_hairpinq, pid);
+		return -1;
+	}
+	return 0;
+}
+
+#define HAIRPIN_MODE_RX_FORCE_MEMORY RTE_BIT32(8) #define 
+HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
+
+#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12) #define 
+HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
+
+#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16) #define 
+HAIRPIN_MODE_TX_RTE_MEMORY RTE_BIT32(17)
+
+static int
+port_config_hairpin_rxq(portid_t pi, uint16_t peer_tx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind) {
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = rxq_head, i = 0; qi < rxq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_tx_port;
+		hairpin_conf.peers[0].queue = i + txq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_rx_hairpin_queue_setup
+			(pi, qi, nb_rxd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on rxq %u.\n"
+			"Peer port: %u peer txq: %u\n",
+			pi, qi, peer_tx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+port_config_hairpin_txq(portid_t pi, uint16_t peer_rx_port,
+			queueid_t rxq_head, queueid_t txq_head,
+			uint16_t qcount, uint32_t manual_bind) {
+	int diag;
+	queueid_t i, qi;
+	uint32_t tx_explicit = !!(hairpin_mode & 0x10);
+	uint32_t force_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY);
+	uint32_t locked_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY);
+	uint32_t rte_mem = !!(hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY);
+	struct rte_port *port = &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf = {
+		.peer_count = 1,
+	};
+
+	for (qi = txq_head, i = 0; qi < txq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port = peer_rx_port;
+		hairpin_conf.peers[0].queue = i + rxq_head;
+		hairpin_conf.manual_bind = manual_bind;
+		hairpin_conf.tx_explicit = tx_explicit;
+		hairpin_conf.force_memory = force_mem;
+		hairpin_conf.use_locked_device_memory = locked_mem;
+		hairpin_conf.use_rte_memory = rte_mem;
+		diag = rte_eth_tx_hairpin_queue_setup
+			(pi, qi, nb_txd, &hairpin_conf);
+		i++;
+		if (diag == 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status == RTE_PORT_HANDLING)
+			port->port_status = RTE_PORT_STOPPED;
+		else
+			fprintf(stderr,
+				"Port %d can not be set back to stopped\n", pi);
+		fprintf(stderr,
+			"Port %d failed to configure hairpin on txq %u.\n"
+			"Peer port: %u peer rxq: %u\n",
+			pi, qi, peer_rx_port, i);
+		/* try to reconfigure queues next time */
+		port->need_reconfig_queues = 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+setup_legacy_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t 
+cnt_pi) {
+	int diag;
+	uint16_t peer_rx_port = pi;
+	uint16_t peer_tx_port = pi;
+	uint32_t manual = 1;
+
+	if (!(hairpin_mode & 0xf)) {
+		peer_rx_port = pi;
+		peer_tx_port = pi;
+		manual = 0;
+	} else if (hairpin_mode & 0x1) {
+		peer_tx_port = rte_eth_find_next_owned_by(pi + 1,
+							  RTE_ETH_DEV_NO_OWNER);
+		if (peer_tx_port >= RTE_MAX_ETHPORTS)
+			peer_tx_port = rte_eth_find_next_owned_by(0,
+								  RTE_ETH_DEV_NO_OWNER);
+		if (p_pi != RTE_MAX_ETHPORTS) {
+			peer_rx_port = p_pi;
+		} else {
+			uint16_t next_pi;
+
+			/* Last port will be the peer RX port of the first. */
+			RTE_ETH_FOREACH_DEV(next_pi)
+				peer_rx_port = next_pi;
+		}
+		manual = 1;
+	} else if (hairpin_mode & 0x2) {
+		if (cnt_pi & 0x1) {
+			peer_rx_port = p_pi;
+		} else {
+			peer_rx_port = rte_eth_find_next_owned_by(pi + 1,
+								  RTE_ETH_DEV_NO_OWNER);
+			if (peer_rx_port >= RTE_MAX_ETHPORTS)
+				peer_rx_port = pi;
+		}
+		peer_tx_port = peer_rx_port;
+		manual = 1;
+	}
+	diag = port_config_hairpin_txq(pi, peer_rx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	diag = port_config_hairpin_rxq(pi, peer_tx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	return 0;
+}
+
+static int
+setup_mapped_harpin_queues(portid_t pi) {
+	int ret = 0;
+	struct hairpin_map *map;
+
+	LIST_FOREACH(map, &hairpin_map_head, entry) {
+		if (map->rx_port == pi) {
+			ret = port_config_hairpin_rxq(pi, map->tx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+		if (map->tx_port == pi) {
+			ret = port_config_hairpin_txq(pi, map->rx_port,
+						      map->rxq_head,
+						      map->txq_head,
+						      map->qnum, true);
+			if (ret)
+				return ret;
+		}
+	}
+	return 0;
+}
+
+/* Configure the Rx and Tx hairpin queues for the selected port. */ int 
+setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi) {
+	return !hairpin_multiport_mode ?
+	       setup_legacy_hairpin_queues(pi, p_pi, cnt_pi) :
+	       setup_mapped_harpin_queues(pi); }
+
+int
+hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl) {
+	uint16_t i;
+	portid_t pi;
+	int peer_pi;
+	int diag;
+	int j;
+
+	/* bind all started hairpin ports */
+	for (i = 0; i < cfg_pi; i++) {
+		pi = pl[i];
+		/* bind current Tx to all peer Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 1);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(pi, peer_pl[j]);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					pi, peer_pl[j],
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+		/* bind all peer Tx to current Rx */
+		peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 0);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j = 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag = rte_eth_hairpin_bind(peer_pl[j], pi);
+			if (diag < 0) {
+				fprintf(stderr,
+					"Error during binding hairpin Tx port %u to %u: %s\n",
+					peer_pl[j], pi,
+					rte_strerror(-diag));
+				return -1;
+			}
+		}
+	}
+	return 0;
+}
+
+void
+hairpin_map_usage(void)
+{
+	printf("  --hairpin-map=rxpi:rxq:txpi:txq:n: hairpin map.\n"
+	       "    rxpi - Rx port index.\n"
+	       "    rxq  - Rx queue.\n"
+	       "    txpi - Tx port index.\n"
+	       "    txq  - Tx queue.\n"
+	       "    n    - hairpin queues number.\n");
+}
+
+static char
+*parse_hairpin_map_entry(char *input, char **next) {
+	char *tail = strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] = '\0';
+	*next = tail + 1;
+	return input;
+}
+
+int
+parse_hairpin_map(const char *hpmap)
+{
+	/*
+	 * Testpmd hairpin map format:
+	 * <Rx port id:First Rx queue:Tx port id:First Tx queue:queues number>
+	 */
+	char *head, *next = (char *)(uintptr_t)hpmap;
+	struct hairpin_map *map = calloc(1, sizeof(*map));

Can use rte_calloc() here.
+
+ if (!map)
+ return -ENOMEM;
+
+ head = parse_hairpin_map_entry(next, &next);
+ if (!head)
+ goto err;
+ map->rx_port = atoi(head);
+ head = parse_hairpin_map_entry(next, &next);
+ if (!head)
+ goto err;
+ map->rxq_head = atoi(head);
+ head = parse_hairpin_map_entry(next, &next);
+ if (!head)
+ goto err;
+ map->tx_port = atoi(head);
+ head = parse_hairpin_map_entry(next, &next);
+ if (!head)
+ goto err;
+ map->txq_head = atoi(head);
+ map->qnum = atoi(next);
+ hairpin_add_multiport_map(map);
+ return 0;
+err:
+ free(map);
Can use rte_free()
+ return -EINVAL;
+}
+
+

app/test-pmd/meson.build +1 -0
@@ -15,6 +15,7 @@ sources = files(
         'config.c',
         'csumonly.c',
         'flowgen.c',
+        'hairpin.c',
         'icmpecho.c',
         'ieee1588fwd.c',
         'iofwd.c',
app/test-pmd/parameters.c +10 -0
@@ -143,6 +143,8 @@ enum {
 	TESTPMD_OPT_HAIRPINQ_NUM,
 #define TESTPMD_OPT_HAIRPIN_MODE "hairpin-mode"
 	TESTPMD_OPT_HAIRPIN_MODE_NUM,
+#define TESTPMD_OPT_HAIRPIN_MAP "hairpin-map"
+	TESTPMD_OPT_HAIRPIN_MAP_NUM,
 #define TESTPMD_OPT_BURST "burst"
 	TESTPMD_OPT_BURST_NUM,
 #define TESTPMD_OPT_FLOWGEN_CLONES "flowgen-clones"
@@ -317,6 +319,7 @@ static const struct option long_options[] = {
 	REQUIRED_ARG(TESTPMD_OPT_TXD),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPINQ),
 	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MODE),
+	REQUIRED_ARG(TESTPMD_OPT_HAIRPIN_MAP),
 	REQUIRED_ARG(TESTPMD_OPT_BURST),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_CLONES),
 	REQUIRED_ARG(TESTPMD_OPT_FLOWGEN_FLOWS),
@@ -542,6 +545,7 @@ usage(char* progname)
 	printf("  --hairpin-mode=0xXX: bitmask set the hairpin port mode.\n"
 	       "    0x10 - explicit Tx rule, 0x02 - hairpin ports paired\n"
 	       "    0x01 - hairpin ports loop, 0x00 - hairpin port self\n");
+	hairpin_map_usage();
 }
 
 static int
@@ -1317,6 +1321,12 @@ launch_args_parse(int argc, char** argv)
 				hairpin_mode = (uint32_t)n;
 			break;
 		}
+		case TESTPMD_OPT_HAIRPIN_MAP_NUM:
+			hairpin_multiport_mode = true;
+			ret = parse_hairpin_map(optarg);
+			if (ret)
+				rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+			break;
 		case TESTPMD_OPT_BURST_NUM:
 			n = atoi(optarg);
 			if (n == 0) {
app/test-pmd/testpmd.c +0 -50

65 lines not on this page · raw message

}

-#define HAIRPIN_MODE_RX_FORCE_MEMORY RTE_BIT32(8) -#define HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
-
-#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12) -#define HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
-
-#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16) -#define HAIRPIN_MODE_TX_RTE_MEMORY RTE_BIT32(17)
-
-
-/* Configure the Rx and Tx hairpin queues for the selected port. */ -static int -setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi) -{
- queueid_t qi;
- struct rte_eth_hairpin_conf hairpin_conf = {
- .peer_count = 1,
- };
- int i;
- int diag;
- struct rte_port *port = &ports[pi];
- uint16_t peer_rx_port = pi;
- uint16_t peer_tx_port = pi;
- uint32_t manual = 1;
- uint32_t tx_exp = hairpin_mode & 0x10;
- uint32_t rx_force_memory = hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY;
- uint32_t rx_locked_memory = hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY;
- uint32_t rx_rte_memory = hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY;
- uint32_t tx_force_memory = hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY;
- uint32_t tx_locked_memory = hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY;
- uint32_t tx_rte_memory = hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY;
-
- if (!(hairpin_mode & 0xf)) {
- peer_rx_port = pi;
- peer_tx_port = pi;
- manual = 0;
- } else if (hairpin_mode & 0x1) {
- peer_tx_port = rte_eth_find_next_owned_by(pi + 1,
- RTE_ETH_DEV_NO_OWNER);
- if (peer_tx_port >= RTE_MAX_ETHPORTS)
- peer_tx_port = rte_eth_find_next_owned_by(0,
- RTE_ETH_DEV_NO_OWNER);
- if (p_pi != RTE_MAX_ETHPORTS) {
- peer_rx_port = p_pi;
- } else {
- uint16_t next_pi;
-
- /* Last port will be the peer RX port of the first. */
- RTE_ETH_FOREACH_DEV(next_pi)
- peer_rx_port = next_pi;
- }
- manual = 1;
- } else if (hairpin_mode & 0x2) {
- if (cnt_pi & 0x1) {
- peer_rx_port = p_pi;
- } else {
- peer_rx_port = rte_eth_find_next_owned_by(pi + 1,
- RTE_ETH_DEV_NO_OWNER);
- if (peer_rx_port >= RTE_MAX_ETHPORTS)
- peer_rx_port = pi;
- }
- peer_tx_port = peer_rx_port;
- manual = 1;
- }
-
- for (qi = nb_txq, i = 0; qi < nb_hairpinq + nb_txq; qi++) {
- hairpin_conf.peers[0].port = peer_rx_port;
- hairpin_conf.peers[0].queue = i + nb_rxq;
- hairpin_conf.manual_bind = !!manual;
- hairpin_conf.tx_explicit = !!tx_exp;
- hairpin_conf.force_memory = !!tx_force_memory;
- hairpin_conf.use_locked_device_memory = !!tx_locked_memory;
- hairpin_conf.use_rte_memory = !!tx_rte_memory;
- diag = rte_eth_tx_hairpin_queue_setup
- (pi, qi, nb_txd, &hairpin_conf);
- i++;
- if (diag == 0)
- continue;
-
- /* Fail to setup rx queue, return */
- if (port->port_status == RTE_PORT_HANDLING)
- port->port_status = RTE_PORT_STOPPED;
- else
- fprintf(stderr,
- "Port %d can not be set back to stopped\n", pi);
- fprintf(stderr, "Fail to configure port %d hairpin queues\n",
- pi);
- /* try to reconfigure queues next time */
- port->need_reconfig_queues = 1;
- return -1;
- }
- for (qi = nb_rxq, i = 0; qi < nb_hairpinq + nb_rxq; qi++) {
- hairpin_conf.peers[0].port = peer_tx_port;
- hairpin_conf.peers[0].queue = i + nb_txq;
- hairpin_conf.manual_bind = !!manual;
- hairpin_conf.tx_explicit = !!tx_exp;
- hairpin_conf.force_memory = !!rx_force_memory;
- hairpin_conf.use_locked_device_memory = !!rx_locked_memory;
- hairpin_conf.use_rte_memory = !!rx_rte_memory;
- diag = rte_eth_rx_hairpin_queue_setup
- (pi, qi, nb_rxd, &hairpin_conf);
- i++;
- if (diag == 0)
- continue;
-
- /* Fail to setup rx queue, return */
- if (port->port_status == RTE_PORT_HANDLING)
- port->port_status = RTE_PORT_STOPPED;
- else
- fprintf(stderr,
- "Port %d can not be set back to stopped\n", pi);
- fprintf(stderr, "Fail to configure port %d hairpin queues\n",
- pi);
- /* try to reconfigure queues next time */
- port->need_reconfig_queues = 1;
- return -1;
- }
- return 0;
-}
-
/* Configure the Rx with optional split. */ int rx_queue_setup(uint16_t port_id, uint16_t rx_queue_id, @@ -3043,7 +2871,6 @@ start_port(portid_t pid)
portid_t peer_pl[RTE_MAX_ETHPORTS];
uint16_t cnt_pi = 0;
uint16_t cfg_pi = 0;
- int peer_pi;
queueid_t qi;
struct rte_port *port;
struct rte_eth_hairpin_cap cap;
@@ -3304,47 +3131,9 @@ start_port(portid_t pid)
fprintf(stderr, "Please stop the ports first\n");

if (hairpin_mode & 0xf) {
- uint16_t i;
- int j;
-
- /* bind all started hairpin ports */
- for (i = 0; i < cfg_pi; i++) {
- pi = pl[i];
- /* bind current Tx to all peer Rx */
- peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
- RTE_MAX_ETHPORTS, 1);
- if (peer_pi < 0)
- return peer_pi;
- for (j = 0; j < peer_pi; j++) {
- if (!port_is_started(peer_pl[j]))
- continue;
- diag = rte_eth_hairpin_bind(pi, peer_pl[j]);
- if (diag < 0) {
- fprintf(stderr,
- "Error during binding hairpin Tx port %u to %u: %s\n",
- pi, peer_pl[j],
- rte_strerror(-diag));
- return -1;
- }
- }
- /* bind all peer Tx to current Rx */
- peer_pi = rte_eth_hairpin_get_peer_ports(pi, peer_pl,
- RTE_MAX_ETHPORTS, 0);
- if (peer_pi < 0)
- return peer_pi;
- for (j = 0; j < peer_pi; j++) {
- if (!port_is_started(peer_pl[j]))
- continue;
- diag = rte_eth_hairpin_bind(peer_pl[j], pi);
- if (diag < 0) {
- fprintf(stderr,
- "Error during binding hairpin Tx port %u to %u: %s\n",
- peer_pl[j], pi,
- rte_strerror(-diag));
- return -1;
- }
- }
- }
+ diag = hairpin_bind(cfg_pi, pl, peer_pl);
+ if (diag < 0)
+ return -1;
}

fill_xstats_display_info_for_port(pid);

app/test-pmd/testpmd.h +13 -0
@@ -126,6 +126,16 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
 
+/**
+ * Command line arguments parser sets `hairpin_multiport_mode` to True
+ * if explicit hairpin map configuration mode was used.
+ */
+extern bool hairpin_multiport_mode;
+
+/** Hairpin maps list. */
+struct hairpin_map;
+extern void hairpin_add_multiport_map(struct hairpin_map *map);
+
 /**
  * The data structure associated with RX and TX packet burst statistics
  * that are recorded for each forwarding stream.
@@ -1252,6 +1262,10 @@ extern int flow_parse(const char *src, void *result, unsigned int size,
 		      struct rte_flow_attr **attr,
 		      struct rte_flow_item **pattern,
 		      struct rte_flow_action **actions);
+int setup_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t cnt_pi); 
+int hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl); 
+void hairpin_map_usage(void); int parse_hairpin_map(const char *hpmap);
 
 uint64_t str_to_rsstypes(const char *str);  const char *rsstypes_to_str(uint64_t rss_type); diff --git a/doc/guides/testpmd_app_ug/run_app.rst b/doc/guides/testpmd_app_ug/run_app.rst

index 1a9b812a7f..48717707a7 100644

doc/guides/testpmd_app_ug/run_app.rst +3 -1
@@ -571,6 +571,9 @@ The command line options are:
 
     The default value is 0. Hairpin will use single port mode and implicit Tx flow mode.
 
+*   ``--hairpin-map=Rx port id:Rx queue:Tx port id:Tx queue:queues number``
+
+    Set explicit hairpin configuration.
 
 Testpmd Multi-Process Command-line Options  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--

2.43.0

#19 lore raw
Quoted 6 lines · Aman Deep

External email: Use caution opening links or attachments

Hi Gregory,
Can you please resolve the compilation issues seen with this patch.

Hello,

I've posted v5.

Regards,
Gregory