diff --git a/drivers/net/mlx5/linux/mlx5_os.c b/drivers/net/mlx5/linux/mlx5_os.c
index d1e7bcce57..12f503474a 100644
--- a/drivers/net/mlx5/linux/mlx5_os.c
+++ b/drivers/net/mlx5/linux/mlx5_os.c
@@ -1558,6 +1558,8 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 #ifdef HAVE_IBV_FLOW_DV_SUPPORT
 		if (priv->vport_meta_mask)
 			flow_hw_set_port_info(eth_dev);
+		/* Only HWS requires this information. */
+		flow_hw_init_tags_set(eth_dev);
 		return eth_dev;
 #else
 		DRV_LOG(ERR, "DV support is missing for HWS.");
diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index 1d10932619..b39ef1ecbe 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -1945,6 +1945,8 @@ mlx5_dev_close(struct rte_eth_dev *dev)
 #if defined(HAVE_IBV_FLOW_DV_SUPPORT) || !defined(HAVE_INFINIBAND_VERBS_H)
 	flow_hw_resource_release(dev);
 	flow_hw_clear_port_info(dev);
+	if (priv->sh->config.dv_flow_en == 2)
+		flow_hw_clear_tags_set(dev);
 #endif
 	if (priv->rxq_privs != NULL) {
 		/* XXX race condition if mlx5_rx_burst() is still running. */
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index c2c3ed81fa..aa328c3bc9 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -1205,6 +1205,7 @@ struct mlx5_dev_ctx_shared {
 	uint32_t drop_action_check_flag:1; /* Check Flag for drop action. */
 	uint32_t flow_priority_check_flag:1; /* Check Flag for flow priority. */
 	uint32_t metadata_regc_check_flag:1; /* Check Flag for metadata REGC. */
+	uint32_t hws_tags:1; /* Check if tags info for HWS initialized. */
 	uint32_t max_port; /* Maximal IB device port index. */
 	struct mlx5_bond_info bond; /* Bonding information. */
 	struct mlx5_common_device *cdev; /* Backend mlx5 device. */
diff --git a/drivers/net/mlx5/mlx5_defs.h b/drivers/net/mlx5/mlx5_defs.h
index 018d3f0f0c..585afb0a98 100644
--- a/drivers/net/mlx5/mlx5_defs.h
+++ b/drivers/net/mlx5/mlx5_defs.h
@@ -139,6 +139,8 @@
 #define MLX5_XMETA_MODE_META32 2
 /* Provide info on patrial hw miss. Implies MLX5_XMETA_MODE_META16 */
 #define MLX5_XMETA_MODE_MISS_INFO 3
+/* Only valid in HWS, 32bits extended META without MARK support in FDB. */
+#define MLX5_XMETA_MODE_META32_HWS 4
 
 /* Tx accurate scheduling on timestamps parameters. */
 #define MLX5_TXPP_WAIT_INIT_TS 1000ul /* How long to wait timestamp. */
diff --git a/drivers/net/mlx5/mlx5_flow.c b/drivers/net/mlx5/mlx5_flow.c
index 72f4374c07..1543d7f75e 100644
--- a/drivers/net/mlx5/mlx5_flow.c
+++ b/drivers/net/mlx5/mlx5_flow.c
@@ -39,6 +39,17 @@
  */
 struct flow_hw_port_info mlx5_flow_hw_port_infos[RTE_MAX_ETHPORTS];
 
+/*
+ * A global structure to save the available REG_C_x for tags usage.
+ * The Meter color REG (ASO) and the last available one will be reserved
+ * for PMD internal usage.
+ * Since there is no "port" concept in the driver, it is assumed that the
+ * available tags set will be the minimum intersection.
+ * 3 - in FDB mode / 5 - in legacy mode
+ */
+uint32_t mlx5_flow_hw_avl_tags_init_cnt;
+enum modify_reg mlx5_flow_hw_avl_tags[MLX5_FLOW_HW_TAGS_MAX] = {REG_NON};
+
 struct tunnel_default_miss_ctx {
 	uint16_t *queue;
 	__extension__
diff --git a/drivers/net/mlx5/mlx5_flow.h b/drivers/net/mlx5/mlx5_flow.h
index c0c719dd8b..98ae7c6bda 100644
--- a/drivers/net/mlx5/mlx5_flow.h
+++ b/drivers/net/mlx5/mlx5_flow.h
@@ -1334,6 +1334,10 @@ struct flow_hw_port_info {
 
 extern struct flow_hw_port_info mlx5_flow_hw_port_infos[RTE_MAX_ETHPORTS];
 
+#define MLX5_FLOW_HW_TAGS_MAX 8
+extern uint32_t mlx5_flow_hw_avl_tags_init_cnt;
+extern enum modify_reg mlx5_flow_hw_avl_tags[];
+
 /*
  * Get metadata match tag and mask for given rte_eth_dev port.
  * Used in HWS rule creation.
@@ -1375,9 +1379,32 @@ flow_hw_get_wire_port(struct ibv_context *ibctx)
 }
 #endif
 
+/*
+ * Convert metadata or tag to the actual register.
+ * META: Can only be used to match in the FDB in this stage, fixed C_1.
+ * TAG: C_x expect meter color reg and the reserved ones.
+ * TODO: Per port / device, FDB or NIC for Meta matching.
+ */
+static __rte_always_inline int
+flow_hw_get_reg_id(enum rte_flow_item_type type, uint32_t id)
+{
+	switch (type) {
+	case RTE_FLOW_ITEM_TYPE_META:
+		return REG_C_1;
+	case RTE_FLOW_ITEM_TYPE_TAG:
+		MLX5_ASSERT(id < MLX5_FLOW_HW_TAGS_MAX);
+		return mlx5_flow_hw_avl_tags[id];
+	default:
+		return REG_NON;
+	}
+}
+
 void flow_hw_set_port_info(struct rte_eth_dev *dev);
 void flow_hw_clear_port_info(struct rte_eth_dev *dev);
 
+void flow_hw_init_tags_set(struct rte_eth_dev *dev);
+void flow_hw_clear_tags_set(struct rte_eth_dev *dev);
+
 typedef int (*mlx5_flow_validate_t)(struct rte_eth_dev *dev,
 				    const struct rte_flow_attr *attr,
 				    const struct rte_flow_item items[],
diff --git a/drivers/net/mlx5/mlx5_flow_hw.c b/drivers/net/mlx5/mlx5_flow_hw.c
index 765e5164cb..03725649c8 100644
--- a/drivers/net/mlx5/mlx5_flow_hw.c
+++ b/drivers/net/mlx5/mlx5_flow_hw.c
@@ -2240,6 +2240,82 @@ flow_hw_clear_port_info(struct rte_eth_dev *dev)
 	info->is_wire = 0;
 }
 
+/*
+ * Initialize the information of available tag registers and an intersection
+ * of all the probed devices' REG_C_Xs.
+ * PS. No port concept in steering part, right now it cannot be per port level.
+ *
+ * @param[in] dev
+ *   Pointer to the rte_eth_dev structure.
+ */
+void flow_hw_init_tags_set(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+	uint32_t meta_mode = priv->sh->config.dv_xmeta_en;
+	uint8_t masks = (uint8_t)priv->sh->cdev->config.hca_attr.set_reg_c;
+	uint32_t i, j;
+	enum modify_reg copy[MLX5_FLOW_HW_TAGS_MAX] = {REG_NON};
+	uint8_t unset = 0;
+	uint8_t copy_masks = 0;
+
+	/*
+	 * The CAPA is global for common device but only used in net.
+	 * It is shared per eswitch domain.
+	 */
+	if (!!priv->sh->hws_tags)
+		return;
+	unset |= 1 << (priv->mtr_color_reg - REG_C_0);
+	unset |= 1 << (REG_C_6 - REG_C_0);
+	if (meta_mode == MLX5_XMETA_MODE_META32_HWS) {
+		unset |= 1 << (REG_C_1 - REG_C_0);
+		unset |= 1 << (REG_C_0 - REG_C_0);
+	}
+	masks &= ~unset;
+	if (mlx5_flow_hw_avl_tags_init_cnt) {
+		for (i = 0; i < MLX5_FLOW_HW_TAGS_MAX; i++) {
+			if (mlx5_flow_hw_avl_tags[i] != REG_NON && !!((1 << i) & masks)) {
+				copy[mlx5_flow_hw_avl_tags[i] - REG_C_0] =
+						mlx5_flow_hw_avl_tags[i];
+				copy_masks |= (1 << i);
+			}
+		}
+		if (copy_masks != masks) {
+			j = 0;
+			for (i = 0; i < MLX5_FLOW_HW_TAGS_MAX; i++)
+				if (!!((1 << i) & copy_masks))
+					mlx5_flow_hw_avl_tags[j++] = copy[i];
+		}
+	} else {
+		j = 0;
+		for (i = 0; i < MLX5_FLOW_HW_TAGS_MAX; i++) {
+			if (!!((1 << i) & masks))
+				mlx5_flow_hw_avl_tags[j++] =
+					(enum modify_reg)(i + (uint32_t)REG_C_0);
+		}
+	}
+	priv->sh->hws_tags = 1;
+	mlx5_flow_hw_avl_tags_init_cnt++;
+}
+
+/*
+ * Reset the available tag registers information to NONE.
+ *
+ * @param[in] dev
+ *   Pointer to the rte_eth_dev structure.
+ */
+void flow_hw_clear_tags_set(struct rte_eth_dev *dev)
+{
+	struct mlx5_priv *priv = dev->data->dev_private;
+
+	if (!priv->sh->hws_tags)
+		return;
+	priv->sh->hws_tags = 0;
+	mlx5_flow_hw_avl_tags_init_cnt--;
+	if (!mlx5_flow_hw_avl_tags_init_cnt)
+		memset(mlx5_flow_hw_avl_tags, REG_NON,
+		       sizeof(enum modify_reg) * MLX5_FLOW_HW_TAGS_MAX);
+}
+
 /**
  * Create shared action.
  *
