From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <dev-bounces@dpdk.org>
Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124])
	by inbox.dpdk.org (Postfix) with ESMTP id 88C7C4599D;
	Fri, 20 Sep 2024 15:48:58 +0200 (CEST)
Received: from mails.dpdk.org (localhost [127.0.0.1])
	by mails.dpdk.org (Postfix) with ESMTP id 513B94067E;
	Fri, 20 Sep 2024 15:48:58 +0200 (CEST)
Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.15])
 by mails.dpdk.org (Postfix) with ESMTP id 9D99D40669
 for <dev@dpdk.org>; Fri, 20 Sep 2024 15:48:55 +0200 (CEST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple;
 d=intel.com; i=@intel.com; q=dns/txt; s=Intel;
 t=1726840136; x=1758376136;
 h=from:to:cc:subject:date:message-id:references:
 in-reply-to:content-transfer-encoding:mime-version;
 bh=Z02gH8GZbBD+qswm0+T0Vm1JlcEYR51IpCfor/NvgL0=;
 b=KtJj9YXJqJLiGwLo74rK9erh+U6k5Zxp2DtCEF01E5L3vcSN5TqbzXAB
 pugyFlgRh7tF3DV2cUTOslrsB23ErHdpcwzN2AsABBcOM6vBz6nUL8K9D
 nWw1j+hbMPmUi/q0fEBt3TzWCxipZ2099IcpIKtvY67xjYPbeZdZJWv2L
 bkIhRpDiKXagXENL8wbNX6gmA57YzupPBWC/c8JJ0LcxC1DwrAoAVJztf
 wVLSBd5yxBzQ59AB4Psodl36PDlVinKSzxMr4AWSPUukt3bb9HHA5NpjI
 G7EVklqNocPbbsUxHUGDk/NIXvEgyRd48UYWAq7Ue6zc3LsbWcRF8ngyP Q==;
X-CSE-ConnectionGUID: ZGFWV/07RBuh3msSyp1ugg==
X-CSE-MsgGUID: Vs27g72YQiGWHtt/GvJlug==
X-IronPort-AV: E=McAfee;i="6700,10204,11200"; a="25993673"
X-IronPort-AV: E=Sophos;i="6.10,244,1719903600"; d="scan'208";a="25993673"
Received: from orviesa001.jf.intel.com ([10.64.159.141])
 by fmvoesa109.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;
 20 Sep 2024 06:48:55 -0700
X-CSE-ConnectionGUID: rkF++2XtRnq3KjuXcJ95eg==
X-CSE-MsgGUID: jMlxnBcXQaqH/sfw0JC0rw==
X-ExtLoop1: 1
X-IronPort-AV: E=Sophos;i="6.10,244,1719903600"; d="scan'208";a="107760258"
Received: from orsmsx602.amr.corp.intel.com ([10.22.229.15])
 by orviesa001.jf.intel.com with ESMTP/TLS/AES256-GCM-SHA384;
 20 Sep 2024 06:48:55 -0700
Received: from orsmsx610.amr.corp.intel.com (10.22.229.23) by
 ORSMSX602.amr.corp.intel.com (10.22.229.15) with Microsoft SMTP Server
 (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id
 15.1.2507.39; Fri, 20 Sep 2024 06:48:54 -0700
Received: from ORSEDG601.ED.cps.intel.com (10.7.248.6) by
 orsmsx610.amr.corp.intel.com (10.22.229.23) with Microsoft SMTP Server
 (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id
 15.1.2507.39 via Frontend Transport; Fri, 20 Sep 2024 06:48:54 -0700
Received: from NAM12-MW2-obe.outbound.protection.outlook.com (104.47.66.41) by
 edgegateway.intel.com (134.134.137.102) with Microsoft SMTP Server
 (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id
 15.1.2507.39; Fri, 20 Sep 2024 06:48:53 -0700
ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;
 b=ZGVV70HBEWBolkdv7aaXwtyXy//5T7Crn77BHrm+p/f9/lp/nvYTnYn37S8lQO75QzMu23yu9PRZ9MoYo+aoaDPnmpvFJZ2yFLiLBmmYSGc6k0B5rNVdeZPCgFK5A9lmmJiOr9McrJNbFdHtPDj2QECHcJsLGapIZI4M8Ysm33qnT0w0Zw1Hue79l/oX5OMFT73owl/c6APUzibliHH6gIc/U6svqm1ToJyCo0j5gusLAM3cB9Kwd3Owg2cngabkRrRFcAwxxqqoGJnk/5bYy1rJVqHRMUwNRm/CGRuU+0u+dN3W0phuQaYqNO9dhzfJAwiIX6O9Ob+0DVYlML9N5A==
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; 
 s=arcselector10001;
 h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;
 bh=s3STmH39ZXoocjB69uqhG4yR2u4asnPsK0d2+NknACI=;
 b=AznSyGYZRvZELXAcFqEOv5YOPCHjrWikN/an7FUIaNHHEWQtqz3D8vIqnAuJrDYkkZ4O6gPbywjYf2Z9vM7s9mdkouj3D7KWPkCr3xt+7fn2QL+fx1VgMtYipWptNyRIyok9O7ZIsVtVfOCfbNIPeuPUsBHkKOHcBxtj8UlB4eSg0GDv84S5U3oyf7TFO7E0eIKosp5pnI92i5m2L5xkQDgWyqZUVckJtCqyUQvAg36F6NUElLRd7x3xzBnhYHfOdQZyO4KrzxsfS38qVGKlmKTKekSGXMFbnh7UXdz8Ddm8HdyWNu4J+68CZtdPsvaePSRP/lnHyxmzDS/TCPhI7Q==
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass
 smtp.mailfrom=intel.com; dmarc=pass action=none header.from=intel.com;
 dkim=pass header.d=intel.com; arc=none
Received: from IA1PR11MB6074.namprd11.prod.outlook.com (2603:10b6:208:3d6::14)
 by DM6PR11MB4594.namprd11.prod.outlook.com (2603:10b6:5:2a0::12) with
 Microsoft SMTP Server (version=TLS1_2,
 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7982.23; Fri, 20 Sep
 2024 13:48:51 +0000
Received: from IA1PR11MB6074.namprd11.prod.outlook.com
 ([fe80::37f8:8ecb:2c75:3092]) by IA1PR11MB6074.namprd11.prod.outlook.com
 ([fe80::37f8:8ecb:2c75:3092%4]) with mapi id 15.20.7982.018; Fri, 20 Sep 2024
 13:48:51 +0000
From: "Singh, Aman Deep" <aman.deep.singh@intel.com>
To: Gregory Etelson <getelson@nvidia.com>, "dev@dpdk.org" <dev@dpdk.org>
CC: "thomas@monjalon.net" <thomas@monjalon.net>, "yuying.zhang@intel.com"
 <yuying.zhang@intel.com>, Dariusz Sosnowski <dsosnowski@nvidia.com>
Subject: RE: [PATCH v4 1/1] testpmd: add hairpin-map parameter
Thread-Topic: [PATCH v4 1/1] testpmd: add hairpin-map parameter
Thread-Index: AQHbBAjszXBOFXp0b0W00BOhezW93rJgu/Bw
Date: Fri, 20 Sep 2024 13:48:51 +0000
Message-ID: <IA1PR11MB6074AEC8165D83762A9DA10BA36C2@IA1PR11MB6074.namprd11.prod.outlook.com>
References: <20230928153605.759397-1-getelson@nvidia.com>
 <20240911050833.202698-1-getelson@nvidia.com>
In-Reply-To: <20240911050833.202698-1-getelson@nvidia.com>
Accept-Language: en-US
Content-Language: en-US
X-MS-Has-Attach: 
X-MS-TNEF-Correlator: 
authentication-results: dkim=none (message not signed)
 header.d=none;dmarc=none action=none header.from=intel.com;
x-ms-publictraffictype: Email
x-ms-traffictypediagnostic: IA1PR11MB6074:EE_|DM6PR11MB4594:EE_
x-ms-office365-filtering-correlation-id: 31515c92-4611-4387-cb8d-08dcd97af5ed
x-ms-exchange-senderadcheck: 1
x-ms-exchange-antispam-relay: 0
x-microsoft-antispam: BCL:0; ARA:13230040|376014|366016|1800799024|38070700018;
x-microsoft-antispam-message-info: =?us-ascii?Q?j7lrr0j9YGMNnB6iSV4ohV3vaqG+Qs44WPntVEAyn3CbYEDdKYJ+ySbH+jKG?=
 =?us-ascii?Q?DdyXa0U+8wDT90JM/r/wJ8ff7CNWZ0gPCM5jRT3zQbEPr61IJnU+bKtpL0AW?=
 =?us-ascii?Q?6uXVkzZl6CkpOChGk0ehpDNyU+nv7+C70KYHLpY8gGYRs+MwZeumtQrVl/cD?=
 =?us-ascii?Q?1r9j6Tr012qCOMSn7uvwYjKH+tqUzZnZYpU9ko2xCMDFIHr9L/9ccH99UWCp?=
 =?us-ascii?Q?pLCo2XVA5PQF5gou1VSDorP5IGDMcVRhpzzB6Hv8OuBpQsszUWASLZcGErLy?=
 =?us-ascii?Q?ys3jYlc8dnJCzTgW9Nry0hVF0RSL7dB3POzZ8odXSC3nJMcdwP5vf6xQXGVR?=
 =?us-ascii?Q?8nXNjtFgLsnumdlHhubZp2wZTbOyWCYvkxCPbUjkIH7nkkygnFjoCJ9ZtGHg?=
 =?us-ascii?Q?Sujnn69ZfvnAiz9Zhp4M4iDR4vU45S5k2rg1kHwXoVFz6s3O91kBd1UkLW+r?=
 =?us-ascii?Q?wat0Ivr7IGpIxm2e135uEdjXrgfqAN3cVknfjQu1FArvVw1o9+v9S8aR3cIR?=
 =?us-ascii?Q?5hxigaaf+A9gUPjh+L+eduxg+gc+VMlG8lyrZF05nnd7vtzrp25ZyQrpyPRq?=
 =?us-ascii?Q?VpUofjsrI/vJdsA56HDTEHIfJQMPNSBbmOe9uds67W6qxadgEwKW/Q+S9Z6U?=
 =?us-ascii?Q?xzbl9iBw6zRR2n2X4goQoEu0QJaoG1ncMM3TY3cBNqgYaAwleoVj2+gpHI0u?=
 =?us-ascii?Q?iYSMhUKJKf2ynZ0ahuwzy3blsVNhw8wHvn66NaT2UaPMnp3LAKMXh4CP2Muw?=
 =?us-ascii?Q?qZVUc7gsHomyQ8NAdMcX0BGnexUa0Xnh9BugoxJSUw3QPgiCPv85aM9xfTO+?=
 =?us-ascii?Q?CZnzQTN1o0scobnaRJIgClB+Tci2F2KWxJrjQF4YN/1mqIyvXLWOGqNTEZ1f?=
 =?us-ascii?Q?/XoyX2h3lQ46Q/rmYvybf6x9uI1Nd3KlDCpjcIgq0zHImNcy7Df4aw5Eus5R?=
 =?us-ascii?Q?6TdB57mlakHO22MoEzh6sniHjulHZ9lmmfipxuxXA8hEdLLbjPaHABjIXvND?=
 =?us-ascii?Q?rGu2RbOw+4gkzuJrvJwhC0Swxj+HHCkQlJfF57FqQa9UIJvQ8Sr3MgDCkh5J?=
 =?us-ascii?Q?5falojXjLVp8n5dLc2dsNwSxKyJ5jW/Vg2oZHx1oJj0zZAuTmW2I8adaXRoe?=
 =?us-ascii?Q?hSvxTCRwb0zbb3deSNvIIR4VvYAdbaM+WNg04tN+GyaknCadnUh/SBugXluD?=
 =?us-ascii?Q?pJcfy8bw84w5hj8vQOQOnKyFsg9UylrUUGpEejHGeRqE0dcYEavGFxzUZZ3Z?=
 =?us-ascii?Q?0T3LYMdiLfs4AsDrxCIHF8BJzojU2AVhYWcCoSf5HTIaqFg3P009zbom0ncC?=
 =?us-ascii?Q?mUdG32yJAauJvNl4LQIC3VxI4fJs8LtL9eLGnlrp+Pd5zw=3D=3D?=
x-forefront-antispam-report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;
 IPV:NLI; SFV:NSPM; H:IA1PR11MB6074.namprd11.prod.outlook.com; PTR:; CAT:NONE;
 SFS:(13230040)(376014)(366016)(1800799024)(38070700018); DIR:OUT; SFP:1101; 
x-ms-exchange-antispam-messagedata-chunkcount: 1
x-ms-exchange-antispam-messagedata-0: =?us-ascii?Q?wcEu0a8yhV59FF8ujUtl+N7OlQVB8RhpUXnmtLxMk4Yms4mi9DkoHFIy9MNx?=
 =?us-ascii?Q?N9jOanaatOOAodqlIYXChg4/94p3iFOHqkFWrH2BBUHKCobrvcI1tIiorrUm?=
 =?us-ascii?Q?N3lNmBfr7Q1T+S2tUzxQoRgvUi+EKpf+Bq6sEukBGxPWN2DRd+uB+nV7cqgB?=
 =?us-ascii?Q?mfSNNwSGTPF0OvGFrqY1P4KOpRqQmj2j6QI1M3bfPrWVKC8fWmkuwlCpUlPE?=
 =?us-ascii?Q?Fx/c6W6mjfh43P2y8/fG9sD3Qmjpcnyx9EB9fJljyT+nTheBp3GE3D/17PBp?=
 =?us-ascii?Q?BrWmL8irkn8gy3HjoN/qkjOq4+6QQeeicd6/cOdhzEGprI5MV1Ejh3Zx2BoN?=
 =?us-ascii?Q?2qc32PIWHlLt50w1T5CHrU6wXHu8gkuGVvuMe3dcnvmFjJ3yD90BgW+BpBz7?=
 =?us-ascii?Q?HPCwyIqcGQQj26681laH+HWFykmofJDvNOrsdFAtynIDGmWfmyTw8PrQSORv?=
 =?us-ascii?Q?6PINeZ6luZ0g719QwOE93aAU+uJcrNbDm9+166rCtThO7Dl8fzLU/DbpFG6L?=
 =?us-ascii?Q?IxKirgvu4zuB3tjs9/YtRdSkDPF/R/3rPp9kk53+hwk4bdyvZnFs6BXfvDVB?=
 =?us-ascii?Q?TognX1hszn+p8rtfJ2w71j7+/mfo1owB7JXB/D1Hdf2T/TlMzF/f5s4qq59C?=
 =?us-ascii?Q?bM8QaXEEwiJVRShcUBQGdSwbvfXKJUSxd4kr4Y3NfU+WI3muNO9Q5fRM74Bg?=
 =?us-ascii?Q?LitW3uwSi6tZOkLCp5i+Tpjb7q9KYUSuXpCajJBY5TEJY5h0t1gOIg960lx0?=
 =?us-ascii?Q?3YHBA9tYRRqKfRgpHxu/5AhEWhtUT3R6Qstoh2P6qczTJgKw5Ve1SE7prQ9V?=
 =?us-ascii?Q?7E3BTRBLWDHBu+Jtwsef9AeQCHpLF7++erwR3mIbyOFSlADy/y72bxufPjF5?=
 =?us-ascii?Q?BsVDO+g0HIglmOoPoikoOa7eMODCqkLeFsIhyYwPU3AMQS6b0n+mRXhuCOTi?=
 =?us-ascii?Q?uKgbiAXooVtQ8ptFX1d9GEx5HCZqpbErypuIx4c/PiB7NDqq3SlRqmbMzaM2?=
 =?us-ascii?Q?fo8+ZEYoMLvHBJWhse3xMW0FQ9feHcaxOOXwWHEkrTr3awl9Bf0CQwG2nTyt?=
 =?us-ascii?Q?653ss/DBWxzj+n64sPSWqDnJZP0H0MdJ2wSNRZAKhXw7UJXwkZ8Gp3IeBS02?=
 =?us-ascii?Q?Yz+NqUd4VzBAKzVCOsNm1kXwVyNiqLHniGcJV+toZ1E0U55TuQIyBLSl3Q8O?=
 =?us-ascii?Q?ooqebEEC9/4KYK2iHRYI3W6dWzmE+55Ov2QGPHVtjvEV2AJPyaCeISfmI6My?=
 =?us-ascii?Q?uSfk8/wzLMZzHLXALE+UY3YC/9qnz4Wd0P5581tkoa9qNPJrCn5hlmVg8RYP?=
 =?us-ascii?Q?1IiO3e7iLSpzF9NA8WkQPM22e95fOTClfCF3biSArgzm0/wcz8+ufgNOk4Pb?=
 =?us-ascii?Q?sVoOIUHkfkKhGNls1OYe/0Vj2G8gVEF6P1WEr7Rn+V2rkevpuIWRqnvQOR2f?=
 =?us-ascii?Q?tIRLpfNbe/gH31JICwh22QNl6Tk/imSZuG7uncpPUNqEEtI5H5OMZXS36C9d?=
 =?us-ascii?Q?+NRoqRuQja7zbYu6pXFgUA1WR+xMXYdQsHsZQIHAoZmWAl+bU5y4a0k02vbz?=
 =?us-ascii?Q?ULr5F82HSbu0NLXxCxfiKe2m1hjVfhb5vdL5/t58h6Y1AYwYYmnS8FD0dv+9?=
 =?us-ascii?Q?lw=3D=3D?=
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0
X-MS-Exchange-CrossTenant-AuthAs: Internal
X-MS-Exchange-CrossTenant-AuthSource: IA1PR11MB6074.namprd11.prod.outlook.com
X-MS-Exchange-CrossTenant-Network-Message-Id: 31515c92-4611-4387-cb8d-08dcd97af5ed
X-MS-Exchange-CrossTenant-originalarrivaltime: 20 Sep 2024 13:48:51.4951 (UTC)
X-MS-Exchange-CrossTenant-fromentityheader: Hosted
X-MS-Exchange-CrossTenant-id: 46c98d88-e344-4ed4-8496-4ed7712e255d
X-MS-Exchange-CrossTenant-mailboxtype: HOSTED
X-MS-Exchange-CrossTenant-userprincipalname: CXLABVoguDfbSSp46JoozOXV2b6kpNLNmNLgLDAKabq5AIU2aXhMiTFM1X01ziN8xfWbHOGb+tA6Ez1Tp5oED7lCK9t0B/ai8e6x2/ZCB10=
X-MS-Exchange-Transport-CrossTenantHeadersStamped: DM6PR11MB4594
X-OriginatorOrg: intel.com
X-BeenThere: dev@dpdk.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: DPDK patches and discussions <dev.dpdk.org>
List-Unsubscribe: <https://mails.dpdk.org/options/dev>,
 <mailto:dev-request@dpdk.org?subject=unsubscribe>
List-Archive: <http://mails.dpdk.org/archives/dev/>
List-Post: <mailto:dev@dpdk.org>
List-Help: <mailto:dev-request@dpdk.org?subject=help>
List-Subscribe: <https://mails.dpdk.org/listinfo/dev>,
 <mailto:dev-request@dpdk.org?subject=subscribe>
Errors-To: dev-bounces@dpdk.org

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 po=
rt <tp> Tx queue <tq>.

Testpmd implements a static hairpin configuration scheme.

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

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

Can we rephrase the parameter "queues number " to something like "total que=
ue pairs"

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

dpdk-testpmd <EAL params> -- \
  <testpmd params> \
  --rxq=3D2 --txq=3D2 --hairpinq=3D2 --hairpin-mode=3D0x12 \
  --hairpin-map=3D0:2:1:2:1 \ # [1]
  --hairpin-map=3D0: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.

Signed-off-by: Gregory Etelson <getelson@nvidia.com>
Acked-by: Dariusz Sosnowski <dsosnowski@nvidia.com>
---
 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 a=
pp/test-pmd/hairpin.c

diff --git a/app/test-pmd/hairpin.c b/app/test-pmd/hairpin.c new file mode =
100644 index 0000000000..de54e8bac8
--- /dev/null
+++ b/app/test-pmd/hairpin.c
@@ -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 =3D false;
+
+queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */
+
+static LIST_HEAD(, hairpin_map) hairpin_map_head =3D=20
+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 =3D 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) !=3D 0) {
+			*pid =3D pi;
+			return 0;
+		}
+		if (cap.max_nb_queues < allowed_max_hairpinq) {
+			allowed_max_hairpinq =3D cap.max_nb_queues;
+			*pid =3D 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 =3D 0;
+
+	allowed_max_hairpinq =3D 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=20
+HAIRPIN_MODE_TX_FORCE_MEMORY RTE_BIT32(9)
+
+#define HAIRPIN_MODE_RX_LOCKED_MEMORY RTE_BIT32(12) #define=20
+HAIRPIN_MODE_RX_RTE_MEMORY RTE_BIT32(13)
+
+#define HAIRPIN_MODE_TX_LOCKED_MEMORY RTE_BIT32(16) #define=20
+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 =3D !!(hairpin_mode & 0x10);
+	uint32_t force_mem =3D !!(hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY);
+	uint32_t locked_mem =3D !!(hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMORY);
+	uint32_t rte_mem =3D !!(hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY);
+	struct rte_port *port =3D &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf =3D {
+		.peer_count =3D 1,
+	};
+
+	for (qi =3D rxq_head, i =3D 0; qi < rxq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port =3D peer_tx_port;
+		hairpin_conf.peers[0].queue =3D i + txq_head;
+		hairpin_conf.manual_bind =3D manual_bind;
+		hairpin_conf.tx_explicit =3D tx_explicit;
+		hairpin_conf.force_memory =3D force_mem;
+		hairpin_conf.use_locked_device_memory =3D locked_mem;
+		hairpin_conf.use_rte_memory =3D rte_mem;
+		diag =3D rte_eth_rx_hairpin_queue_setup
+			(pi, qi, nb_rxd, &hairpin_conf);
+		i++;
+		if (diag =3D=3D 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status =3D=3D RTE_PORT_HANDLING)
+			port->port_status =3D 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 =3D 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 =3D !!(hairpin_mode & 0x10);
+	uint32_t force_mem =3D !!(hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY);
+	uint32_t locked_mem =3D !!(hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMORY);
+	uint32_t rte_mem =3D !!(hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY);
+	struct rte_port *port =3D &ports[pi];
+	struct rte_eth_hairpin_conf hairpin_conf =3D {
+		.peer_count =3D 1,
+	};
+
+	for (qi =3D txq_head, i =3D 0; qi < txq_head + qcount; qi++) {
+		hairpin_conf.peers[0].port =3D peer_rx_port;
+		hairpin_conf.peers[0].queue =3D i + rxq_head;
+		hairpin_conf.manual_bind =3D manual_bind;
+		hairpin_conf.tx_explicit =3D tx_explicit;
+		hairpin_conf.force_memory =3D force_mem;
+		hairpin_conf.use_locked_device_memory =3D locked_mem;
+		hairpin_conf.use_rte_memory =3D rte_mem;
+		diag =3D rte_eth_tx_hairpin_queue_setup
+			(pi, qi, nb_txd, &hairpin_conf);
+		i++;
+		if (diag =3D=3D 0)
+			continue;
+
+		/* Fail to setup rx queue, return */
+		if (port->port_status =3D=3D RTE_PORT_HANDLING)
+			port->port_status =3D 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 =3D 1;
+		return -1;
+	}
+	return 0;
+}
+
+static int
+setup_legacy_hairpin_queues(portid_t pi, portid_t p_pi, uint16_t=20
+cnt_pi) {
+	int diag;
+	uint16_t peer_rx_port =3D pi;
+	uint16_t peer_tx_port =3D pi;
+	uint32_t manual =3D 1;
+
+	if (!(hairpin_mode & 0xf)) {
+		peer_rx_port =3D pi;
+		peer_tx_port =3D pi;
+		manual =3D 0;
+	} else if (hairpin_mode & 0x1) {
+		peer_tx_port =3D rte_eth_find_next_owned_by(pi + 1,
+							  RTE_ETH_DEV_NO_OWNER);
+		if (peer_tx_port >=3D RTE_MAX_ETHPORTS)
+			peer_tx_port =3D rte_eth_find_next_owned_by(0,
+								  RTE_ETH_DEV_NO_OWNER);
+		if (p_pi !=3D RTE_MAX_ETHPORTS) {
+			peer_rx_port =3D 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 =3D next_pi;
+		}
+		manual =3D 1;
+	} else if (hairpin_mode & 0x2) {
+		if (cnt_pi & 0x1) {
+			peer_rx_port =3D p_pi;
+		} else {
+			peer_rx_port =3D rte_eth_find_next_owned_by(pi + 1,
+								  RTE_ETH_DEV_NO_OWNER);
+			if (peer_rx_port >=3D RTE_MAX_ETHPORTS)
+				peer_rx_port =3D pi;
+		}
+		peer_tx_port =3D peer_rx_port;
+		manual =3D 1;
+	}
+	diag =3D port_config_hairpin_txq(pi, peer_rx_port, nb_rxq, nb_txq,
+				       nb_hairpinq, manual);
+	if (diag)
+		return diag;
+	diag =3D 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 =3D 0;
+	struct hairpin_map *map;
+
+	LIST_FOREACH(map, &hairpin_map_head, entry) {
+		if (map->rx_port =3D=3D pi) {
+			ret =3D 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 =3D=3D pi) {
+			ret =3D 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=20
+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 =3D 0; i < cfg_pi; i++) {
+		pi =3D pl[i];
+		/* bind current Tx to all peer Rx */
+		peer_pi =3D rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 1);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j =3D 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag =3D 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 =3D rte_eth_hairpin_get_peer_ports(pi, peer_pl,
+							 RTE_MAX_ETHPORTS, 0);
+		if (peer_pi < 0)
+			return peer_pi;
+		for (j =3D 0; j < peer_pi; j++) {
+			if (!port_is_started(peer_pl[j]))
+				continue;
+			diag =3D 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=3Drxpi: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 =3D strchr(input, ':');
+
+	if (!tail)
+		return NULL;
+	tail[0] =3D '\0';
+	*next =3D 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 =3D (char *)(uintptr_t)hpmap;
+	struct hairpin_map *map =3D calloc(1, sizeof(*map));
Can use rte_calloc() here.
+
+	if (!map)
+		return -ENOMEM;
+
+	head =3D parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rx_port =3D atoi(head);
+	head =3D parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->rxq_head =3D atoi(head);
+	head =3D parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->tx_port =3D atoi(head);
+	head =3D parse_hairpin_map_entry(next, &next);
+	if (!head)
+		goto err;
+	map->txq_head =3D atoi(head);
+	map->qnum =3D atoi(next);
+	hairpin_add_multiport_map(map);
+	return 0;
+err:
+	free(map);
Can use rte_free()
+	return -EINVAL;
+}
+
+
diff --git a/app/test-pmd/meson.build b/app/test-pmd/meson.build index 719f=
875be0..f1c36529b4 100644
--- a/app/test-pmd/meson.build
+++ b/app/test-pmd/meson.build
@@ -15,6 +15,7 @@ sources =3D files(
         'config.c',
         'csumonly.c',
         'flowgen.c',
+        'hairpin.c',
         'icmpecho.c',
         'ieee1588fwd.c',
         'iofwd.c',
diff --git a/app/test-pmd/parameters.c b/app/test-pmd/parameters.c index 22=
364e09ab..7b31b94542 100644
--- a/app/test-pmd/parameters.c
+++ b/app/test-pmd/parameters.c
@@ -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[] =3D {
 	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=3D0xXX: 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();
 }
=20
 static int
@@ -1317,6 +1321,12 @@ launch_args_parse(int argc, char** argv)
 				hairpin_mode =3D (uint32_t)n;
 			break;
 		}
+		case TESTPMD_OPT_HAIRPIN_MAP_NUM:
+			hairpin_multiport_mode =3D true;
+			ret =3D parse_hairpin_map(optarg);
+			if (ret)
+				rte_exit(EXIT_FAILURE, "invalid hairpin map\n");
+			break;
 		case TESTPMD_OPT_BURST_NUM:
 			n =3D atoi(optarg);
 			if (n =3D=3D 0) {
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c index b1401136=
e4..f487769578 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -284,7 +284,6 @@ uint8_t dcb_config =3D 0;
 /*
  * Configurable number of RX/TX queues.
  */
-queueid_t nb_hairpinq; /**< Number of hairpin queues per port. */  queueid=
_t nb_rxq =3D 1; /**< Number of RX queues per port. */  queueid_t nb_txq =
=3D 1; /**< Number of TX queues per port. */
=20
@@ -431,9 +430,6 @@ bool setup_on_probe_event =3D true;
 /* Clear ptypes on port initialization. */  uint8_t clear_ptypes =3D true;
=20
-/* Hairpin ports configuration mode. */ -uint32_t hairpin_mode;
-
 /* Pretty printing of ethdev events */
 static const char * const eth_event_desc[] =3D {
 	[RTE_ETH_EVENT_UNKNOWN] =3D "unknown",
@@ -1555,54 +1551,6 @@ check_nb_txd(queueid_t txd)
 	return 0;
 }
=20
-
-/*
- * 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 =3D 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) !=3D 0) {
-			*pid =3D pi;
-			return 0;
-		}
-		if (cap.max_nb_queues < allowed_max_hairpinq) {
-			allowed_max_hairpinq =3D cap.max_nb_queues;
-			*pid =3D 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 =3D 0;
-
-	allowed_max_hairpinq =3D 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;
-}
-
 static int
 get_eth_overhead(struct rte_eth_dev_info *dev_info)  { @@ -2684,126 +2632,=
6 @@ port_is_started(portid_t port_id)
 	return 1;
 }
=20
-#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. */ -stati=
c 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 =3D {
-		.peer_count =3D 1,
-	};
-	int i;
-	int diag;
-	struct rte_port *port =3D &ports[pi];
-	uint16_t peer_rx_port =3D pi;
-	uint16_t peer_tx_port =3D pi;
-	uint32_t manual =3D 1;
-	uint32_t tx_exp =3D hairpin_mode & 0x10;
-	uint32_t rx_force_memory =3D hairpin_mode & HAIRPIN_MODE_RX_FORCE_MEMORY;
-	uint32_t rx_locked_memory =3D hairpin_mode & HAIRPIN_MODE_RX_LOCKED_MEMOR=
Y;
-	uint32_t rx_rte_memory =3D hairpin_mode & HAIRPIN_MODE_RX_RTE_MEMORY;
-	uint32_t tx_force_memory =3D hairpin_mode & HAIRPIN_MODE_TX_FORCE_MEMORY;
-	uint32_t tx_locked_memory =3D hairpin_mode & HAIRPIN_MODE_TX_LOCKED_MEMOR=
Y;
-	uint32_t tx_rte_memory =3D hairpin_mode & HAIRPIN_MODE_TX_RTE_MEMORY;
-
-	if (!(hairpin_mode & 0xf)) {
-		peer_rx_port =3D pi;
-		peer_tx_port =3D pi;
-		manual =3D 0;
-	} else if (hairpin_mode & 0x1) {
-		peer_tx_port =3D rte_eth_find_next_owned_by(pi + 1,
-						       RTE_ETH_DEV_NO_OWNER);
-		if (peer_tx_port >=3D RTE_MAX_ETHPORTS)
-			peer_tx_port =3D rte_eth_find_next_owned_by(0,
-						RTE_ETH_DEV_NO_OWNER);
-		if (p_pi !=3D RTE_MAX_ETHPORTS) {
-			peer_rx_port =3D 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 =3D next_pi;
-		}
-		manual =3D 1;
-	} else if (hairpin_mode & 0x2) {
-		if (cnt_pi & 0x1) {
-			peer_rx_port =3D p_pi;
-		} else {
-			peer_rx_port =3D rte_eth_find_next_owned_by(pi + 1,
-						RTE_ETH_DEV_NO_OWNER);
-			if (peer_rx_port >=3D RTE_MAX_ETHPORTS)
-				peer_rx_port =3D pi;
-		}
-		peer_tx_port =3D peer_rx_port;
-		manual =3D 1;
-	}
-
-	for (qi =3D nb_txq, i =3D 0; qi < nb_hairpinq + nb_txq; qi++) {
-		hairpin_conf.peers[0].port =3D peer_rx_port;
-		hairpin_conf.peers[0].queue =3D i + nb_rxq;
-		hairpin_conf.manual_bind =3D !!manual;
-		hairpin_conf.tx_explicit =3D !!tx_exp;
-		hairpin_conf.force_memory =3D !!tx_force_memory;
-		hairpin_conf.use_locked_device_memory =3D !!tx_locked_memory;
-		hairpin_conf.use_rte_memory =3D !!tx_rte_memory;
-		diag =3D rte_eth_tx_hairpin_queue_setup
-			(pi, qi, nb_txd, &hairpin_conf);
-		i++;
-		if (diag =3D=3D 0)
-			continue;
-
-		/* Fail to setup rx queue, return */
-		if (port->port_status =3D=3D RTE_PORT_HANDLING)
-			port->port_status =3D 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 =3D 1;
-		return -1;
-	}
-	for (qi =3D nb_rxq, i =3D 0; qi < nb_hairpinq + nb_rxq; qi++) {
-		hairpin_conf.peers[0].port =3D peer_tx_port;
-		hairpin_conf.peers[0].queue =3D i + nb_txq;
-		hairpin_conf.manual_bind =3D !!manual;
-		hairpin_conf.tx_explicit =3D !!tx_exp;
-		hairpin_conf.force_memory =3D !!rx_force_memory;
-		hairpin_conf.use_locked_device_memory =3D !!rx_locked_memory;
-		hairpin_conf.use_rte_memory =3D !!rx_rte_memory;
-		diag =3D rte_eth_rx_hairpin_queue_setup
-			(pi, qi, nb_rxd, &hairpin_conf);
-		i++;
-		if (diag =3D=3D 0)
-			continue;
-
-		/* Fail to setup rx queue, return */
-		if (port->port_status =3D=3D RTE_PORT_HANDLING)
-			port->port_status =3D 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 =3D 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 pi=
d)
 	portid_t peer_pl[RTE_MAX_ETHPORTS];
 	uint16_t cnt_pi =3D 0;
 	uint16_t cfg_pi =3D 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");
=20
 	if (hairpin_mode & 0xf) {
-		uint16_t i;
-		int j;
-
-		/* bind all started hairpin ports */
-		for (i =3D 0; i < cfg_pi; i++) {
-			pi =3D pl[i];
-			/* bind current Tx to all peer Rx */
-			peer_pi =3D rte_eth_hairpin_get_peer_ports(pi, peer_pl,
-							RTE_MAX_ETHPORTS, 1);
-			if (peer_pi < 0)
-				return peer_pi;
-			for (j =3D 0; j < peer_pi; j++) {
-				if (!port_is_started(peer_pl[j]))
-					continue;
-				diag =3D 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 =3D rte_eth_hairpin_get_peer_ports(pi, peer_pl,
-							RTE_MAX_ETHPORTS, 0);
-			if (peer_pi < 0)
-				return peer_pi;
-			for (j =3D 0; j < peer_pi; j++) {
-				if (!port_is_started(peer_pl[j]))
-					continue;
-				diag =3D 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 =3D hairpin_bind(cfg_pi, pl, peer_pl);
+		if (diag < 0)
+			return -1;
 	}
=20
 	fill_xstats_display_info_for_port(pid);
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h index 9facd7f2=
81..659c5b4fdc 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -126,6 +126,16 @@ enum noisy_fwd_mode {
 	NOISY_FWD_MODE_MAX,
 };
=20
+/**
+ * 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);=20
+int hairpin_bind(uint16_t cfg_pi, portid_t *pl, portid_t *peer_pl);=20
+void hairpin_map_usage(void); int parse_hairpin_map(const char *hpmap);
=20
 uint64_t str_to_rsstypes(const char *str);  const char *rsstypes_to_str(ui=
nt64_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
--- a/doc/guides/testpmd_app_ug/run_app.rst
+++ b/doc/guides/testpmd_app_ug/run_app.rst
@@ -571,6 +571,9 @@ The command line options are:
=20
     The default value is 0. Hairpin will use single port mode and implicit=
 Tx flow mode.
=20
+*   ``--hairpin-map=3DRx port id:Rx queue:Tx port id:Tx queue:queues numbe=
r``
+
+    Set explicit hairpin configuration.
=20
 Testpmd Multi-Process Command-line Options  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=
~~~~~~~~~~~~
--
2.43.0