diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 742bedd..33cfc5b 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -191,6 +191,7 @@ struct mlx5_hca_attr {
 	uint32_t log_max_hairpin_queues:5;
 	uint32_t log_max_hairpin_wq_data_sz:5;
 	uint32_t log_max_hairpin_num_packets:5;
+	uint32_t vhca_id:16;
 };
 
 /* Flow list . */
diff --git a/drivers/net/mlx5/mlx5_devx_cmds.c b/drivers/net/mlx5/mlx5_devx_cmds.c
index a501f1f..3471a9b 100644
--- a/drivers/net/mlx5/mlx5_devx_cmds.c
+++ b/drivers/net/mlx5/mlx5_devx_cmds.c
@@ -334,6 +334,7 @@ struct mlx5_devx_obj *
 						    log_max_hairpin_wq_data_sz);
 	attr->log_max_hairpin_num_packets = MLX5_GET
 		(cmd_hca_cap, hcattr, log_min_hairpin_wq_data_sz);
+	attr->vhca_id = MLX5_GET(cmd_hca_cap, hcattr, vhca_id);
 	attr->eth_net_offloads = MLX5_GET(cmd_hca_cap, hcattr,
 					  eth_net_offloads);
 	attr->eth_virt = MLX5_GET(cmd_hca_cap, hcattr, eth_virt);
diff --git a/drivers/net/mlx5/mlx5_prm.h b/drivers/net/mlx5/mlx5_prm.h
index c687cfb..e4b19f8 100644
--- a/drivers/net/mlx5/mlx5_prm.h
+++ b/drivers/net/mlx5/mlx5_prm.h
@@ -1628,6 +1628,12 @@ struct mlx5_ifc_create_rqt_in_bits {
 #pragma GCC diagnostic error "-Wpedantic"
 #endif
 
+enum {
+	MLX5_SQC_STATE_RST  = 0x0,
+	MLX5_SQC_STATE_RDY  = 0x1,
+	MLX5_SQC_STATE_ERR  = 0x3,
+};
+
 struct mlx5_ifc_sqc_bits {
 	u8 rlky[0x1];
 	u8 cd_master[0x1];
diff --git a/drivers/net/mlx5/mlx5_trigger.c b/drivers/net/mlx5/mlx5_trigger.c
index 3ec86c4..a4fcdb3 100644
--- a/drivers/net/mlx5/mlx5_trigger.c
+++ b/drivers/net/mlx5/mlx5_trigger.c
@@ -162,6 +162,96 @@
 }
 
 /**
+ * Binds Tx queues to Rx queues for hairpin.
+ *
+ * Binds Tx queues to the target Rx queues.
+ *
+ * @param dev
+ *   Pointer to Ethernet device structure.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+static int
+mlx5_hairpin_bind(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	struct mlx5_devx_modify_sq_attr sq_attr = { 0 };
+	struct mlx5_devx_modify_rq_attr rq_attr = { 0 };
+	struct mlx5_txq_ctrl *txq_ctrl;
+	struct mlx5_rxq_ctrl *rxq_ctrl;
+	struct mlx5_devx_obj *sq;
+	struct mlx5_devx_obj *rq;
+	unsigned int i;
+	int ret = 0;
+
+	for (i = 0; i != priv->txqs_n; ++i) {
+		txq_ctrl = mlx5_txq_get(dev, i);
+		if (!txq_ctrl)
+			continue;
+		if (txq_ctrl->type != MLX5_TXQ_TYPE_HAIRPIN) {
+			mlx5_txq_release(dev, i);
+			continue;
+		}
+		if (!txq_ctrl->obj) {
+			rte_errno = ENOMEM;
+			DRV_LOG(ERR, "port %u no txq object found: %d",
+				dev->data->port_id, i);
+			mlx5_txq_release(dev, i);
+			return -rte_errno;
+		}
+		sq = txq_ctrl->obj->sq;
+		rxq_ctrl = mlx5_rxq_get(dev,
+					txq_ctrl->hairpin_conf.peers[0].queue);
+		if (!rxq_ctrl) {
+			mlx5_txq_release(dev, i);
+			rte_errno = EINVAL;
+			DRV_LOG(ERR, "port %u no rxq object found: %d",
+				dev->data->port_id,
+				txq_ctrl->hairpin_conf.peers[0].queue);
+			return -rte_errno;
+		}
+		if (rxq_ctrl->type != MLX5_RXQ_TYPE_HAIRPIN ||
+		    rxq_ctrl->hairpin_conf.peers[0].queue != i) {
+			rte_errno = ENOMEM;
+			DRV_LOG(ERR, "port %u Tx queue %d can't be binded to "
+				"Rx queue %d", dev->data->port_id,
+				i, txq_ctrl->hairpin_conf.peers[0].queue);
+			goto error;
+		}
+		rq = rxq_ctrl->obj->rq;
+		if (!rq) {
+			rte_errno = ENOMEM;
+			DRV_LOG(ERR, "port %u hairpin no matching rxq: %d",
+				dev->data->port_id,
+				txq_ctrl->hairpin_conf.peers[0].queue);
+			goto error;
+		}
+		sq_attr.state = MLX5_SQC_STATE_RDY;
+		sq_attr.sq_state = MLX5_SQC_STATE_RST;
+		sq_attr.hairpin_peer_rq = rq->id;
+		sq_attr.hairpin_peer_vhca = priv->config.hca_attr.vhca_id;
+		ret = mlx5_devx_cmd_modify_sq(sq, &sq_attr);
+		if (ret)
+			goto error;
+		rq_attr.state = MLX5_SQC_STATE_RDY;
+		rq_attr.rq_state = MLX5_SQC_STATE_RST;
+		rq_attr.hairpin_peer_sq = sq->id;
+		rq_attr.hairpin_peer_vhca = priv->config.hca_attr.vhca_id;
+		ret = mlx5_devx_cmd_modify_rq(rq, &rq_attr);
+		if (ret)
+			goto error;
+		mlx5_txq_release(dev, i);
+		mlx5_rxq_release(dev, txq_ctrl->hairpin_conf.peers[0].queue);
+	}
+	return 0;
+error:
+	mlx5_txq_release(dev, i);
+	mlx5_rxq_release(dev, txq_ctrl->hairpin_conf.peers[0].queue);
+	return -rte_errno;
+}
+
+/**
  * DPDK callback to start the device.
  *
  * Simulate device start by attaching all configured flows.
@@ -192,6 +282,13 @@
 		mlx5_txq_stop(dev);
 		return -rte_errno;
 	}
+	ret = mlx5_hairpin_bind(dev);
+	if (ret) {
+		DRV_LOG(ERR, "port %u hairpin binding failed: %s",
+			dev->data->port_id, strerror(rte_errno));
+		mlx5_txq_stop(dev);
+		return -rte_errno;
+	}
 	dev->data->dev_started = 1;
 	ret = mlx5_rx_intr_vec_enable(dev);
 	if (ret) {
