diff --git a/doc/guides/nics/mlx5.rst b/doc/guides/nics/mlx5.rst
index 2e5274edb8..62fd27d859 100644
--- a/doc/guides/nics/mlx5.rst
+++ b/doc/guides/nics/mlx5.rst
@@ -337,12 +337,25 @@ Limitations
      - Length
      - Data
 
-  Only one Class/Type/Length Geneve TLV option is supported per shared device.
   Class/Type/Length fields must be specified as well as masks.
   Class/Type/Length specified masks must be full.
   Matching Geneve TLV option without specifying data is not supported.
   Matching Geneve TLV option with ``data & mask == 0`` is not supported.
 
+  In SW steering (``dv_flow_en`` = 1):
+
+     - Only one Class/Type/Length Geneve TLV option is supported per shared
+       device.
+     - Supported only when ``FLEX_PARSER_PROFILE_ENABLE`` = 0.
+
+  In HW steering (``dv_flow_en`` = 2):
+
+     - Multiple Class/Type/Length Geneve TLV option are supported per physical
+       device. See :ref:`geneve_parser_api` for more information.
+     - Multiple of same Geneve TLV option isn't supported at the same pattern
+       template.
+     - Supported only when ``FLEX_PARSER_PROFILE_ENABLE`` = 8.
+
 - VF: flow rules created on VF devices can only match traffic targeted at the
   configured MAC addresses (see ``rte_eth_dev_mac_addr_add()``).
 
diff --git a/doc/guides/rel_notes/release_24_03.rst b/doc/guides/rel_notes/release_24_03.rst
index a1dfea263c..0c8491ce37 100644
--- a/doc/guides/rel_notes/release_24_03.rst
+++ b/doc/guides/rel_notes/release_24_03.rst
@@ -77,6 +77,11 @@ New Features
 
   * Added support for ``RTE_FLOW_ITEM_TYPE_RANDOM`` flow item.
 
+  * Added HW steering support for ``RTE_FLOW_ITEM_TYPE_GENEVE`` flow item.
+
+  * Added HW steering support for ``RTE_FLOW_ITEM_TYPE_GENEVE_OPT`` flow item.
+
+
 Removed Items
 -------------
 
diff --git a/drivers/net/mlx5/mlx5_flow.h b/drivers/net/mlx5/mlx5_flow.h
index 14806fa78e..0459472fe4 100644
--- a/drivers/net/mlx5/mlx5_flow.h
+++ b/drivers/net/mlx5/mlx5_flow.h
@@ -1338,6 +1338,15 @@ struct mlx5_action_construct_data {
 
 #define MAX_GENEVE_OPTIONS_RESOURCES 7
 
+/* GENEVE TLV options manager structure. */
+struct mlx5_geneve_tlv_options_mng {
+	uint8_t nb_options; /* Number of options inside the template. */
+	struct {
+		uint8_t opt_type;
+		uint16_t opt_class;
+	} options[MAX_GENEVE_OPTIONS_RESOURCES];
+};
+
 /* Flow item template struct. */
 struct rte_flow_pattern_template {
 	LIST_ENTRY(rte_flow_pattern_template) next;
@@ -1357,6 +1366,8 @@ struct rte_flow_pattern_template {
 	 * tag pattern item for representor matching.
 	 */
 	bool implicit_tag;
+	/* Manages all GENEVE TLV options used by this pattern template. */
+	struct mlx5_geneve_tlv_options_mng geneve_opt_mng;
 	uint8_t flex_item; /* flex item index. */
 };
 
@@ -1805,6 +1816,16 @@ mlx5_geneve_tlv_parser_create(uint16_t port_id,
 			      const struct rte_pmd_mlx5_geneve_tlv tlv_list[],
 			      uint8_t nb_options);
 int mlx5_geneve_tlv_parser_destroy(void *handle);
+int mlx5_flow_geneve_tlv_option_validate(struct mlx5_priv *priv,
+					 const struct rte_flow_item *geneve_opt,
+					 struct rte_flow_error *error);
+
+struct mlx5_geneve_tlv_options_mng;
+int mlx5_geneve_tlv_option_register(struct mlx5_priv *priv,
+				    const struct rte_flow_item_geneve_opt *spec,
+				    struct mlx5_geneve_tlv_options_mng *mng);
+void mlx5_geneve_tlv_options_unregister(struct mlx5_priv *priv,
+					struct mlx5_geneve_tlv_options_mng *mng);
 
 void flow_hw_set_port_info(struct rte_eth_dev *dev);
 void flow_hw_clear_port_info(struct rte_eth_dev *dev);
diff --git a/drivers/net/mlx5/mlx5_flow_geneve.c b/drivers/net/mlx5/mlx5_flow_geneve.c
index 2d593b70ba..2c8dc39e74 100644
--- a/drivers/net/mlx5/mlx5_flow_geneve.c
+++ b/drivers/net/mlx5/mlx5_flow_geneve.c
@@ -152,6 +152,106 @@ mlx5_get_geneve_hl_data(const void *dr_ctx, uint8_t type, uint16_t class,
 	return -EINVAL;
 }
 
+/**
+ * Calculate total data size.
+ *
+ * @param[in] priv
+ *   Pointer to port's private data.
+ * @param[in] geneve_opt
+ *   Pointer to GENEVE option item structure.
+ * @param[out] error
+ *   Pointer to error structure.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+int
+mlx5_flow_geneve_tlv_option_validate(struct mlx5_priv *priv,
+				     const struct rte_flow_item *geneve_opt,
+				     struct rte_flow_error *error)
+{
+	const struct rte_flow_item_geneve_opt *spec = geneve_opt->spec;
+	const struct rte_flow_item_geneve_opt *mask = geneve_opt->mask;
+	struct mlx5_geneve_tlv_option *option;
+
+	option = mlx5_geneve_tlv_option_get(priv, spec->option_type, spec->option_class);
+	if (option == NULL)
+		return rte_flow_error_set(error, rte_errno,
+					  RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					  "Unregistered GENEVE option");
+	if (mask->option_type != UINT8_MAX)
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					  "GENEVE option type must be fully masked");
+	if (option->class_mode == 1 && mask->option_class != UINT16_MAX)
+		return rte_flow_error_set(error, EINVAL,
+					  RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+					  "GENEVE option class must be fully masked");
+	return 0;
+}
+
+/**
+ * Register single GENEVE TLV option as used by pattern template.
+ *
+ * @param[in] priv
+ *   Pointer to port's private data.
+ * @param[in] spec
+ *   Pointer to GENEVE option item structure.
+ * @param[out] mng
+ *   Pointer to GENEVE option manager.
+ *
+ * @return
+ *   0 on success, a negative errno value otherwise and rte_errno is set.
+ */
+int
+mlx5_geneve_tlv_option_register(struct mlx5_priv *priv,
+				const struct rte_flow_item_geneve_opt *spec,
+				struct mlx5_geneve_tlv_options_mng *mng)
+{
+	struct mlx5_geneve_tlv_option *option;
+
+	option = mlx5_geneve_tlv_option_get(priv, spec->option_type, spec->option_class);
+	if (option == NULL)
+		return -rte_errno;
+	/* Increase the option reference counter. */
+	rte_atomic_fetch_add_explicit(&option->refcnt, 1,
+				      rte_memory_order_relaxed);
+	/* Update the manager with option information. */
+	mng->options[mng->nb_options].opt_type = spec->option_type;
+	mng->options[mng->nb_options].opt_class = spec->option_class;
+	mng->nb_options++;
+	return 0;
+}
+
+/**
+ * Unregister all GENEVE TLV options used by pattern template.
+ *
+ * @param[in] priv
+ *   Pointer to port's private data.
+ * @param[in] mng
+ *   Pointer to GENEVE option manager.
+ */
+void
+mlx5_geneve_tlv_options_unregister(struct mlx5_priv *priv,
+				   struct mlx5_geneve_tlv_options_mng *mng)
+{
+	struct mlx5_geneve_tlv_option *option;
+	uint8_t i;
+
+	for (i = 0; i < mng->nb_options; ++i) {
+		option = mlx5_geneve_tlv_option_get(priv,
+						    mng->options[i].opt_type,
+						    mng->options[i].opt_class);
+		MLX5_ASSERT(option != NULL);
+		/* Decrease the option reference counter. */
+		rte_atomic_fetch_sub_explicit(&option->refcnt, 1,
+					      rte_memory_order_relaxed);
+		mng->options[i].opt_type = 0;
+		mng->options[i].opt_class = 0;
+	}
+	mng->nb_options = 0;
+}
+
 /**
  * Create single GENEVE TLV option sample.
  *
@@ -208,6 +308,24 @@ mlx5_geneve_tlv_option_destroy_sample(struct mlx5_geneve_tlv_resource *resource)
 	resource->obj = NULL;
 }
 
+/*
+ * Sample for DW0 are created when one of two conditions is met:
+ * 1. Header is matchable.
+ * 2. This option doesn't configure any data DW.
+ */
+static bool
+should_configure_sample_for_dw0(const struct rte_pmd_mlx5_geneve_tlv *spec)
+{
+	uint8_t i;
+
+	if (spec->match_on_class_mode == 2)
+		return true;
+	for (i = 0; i < spec->sample_len; ++i)
+		if (spec->match_data_mask[i] != 0)
+			return false;
+	return true;
+}
+
 /**
  * Create single GENEVE TLV option.
  *
@@ -237,8 +355,7 @@ mlx5_geneve_tlv_option_create(void *ctx, const struct rte_pmd_mlx5_geneve_tlv *s
 	uint8_t i, resource_id = 0;
 	int ret;
 
-	if (spec->match_on_class_mode == 2) {
-		/* Header is matchable, create sample for DW0. */
+	if (should_configure_sample_for_dw0(spec)) {
 		attr.sample_offset = 0;
 		resource = &option->resources[resource_id];
 		ret = mlx5_geneve_tlv_option_create_sample(ctx, &attr,
diff --git a/drivers/net/mlx5/mlx5_flow_hw.c b/drivers/net/mlx5/mlx5_flow_hw.c
index f06d2ce273..00dc9bc890 100644
--- a/drivers/net/mlx5/mlx5_flow_hw.c
+++ b/drivers/net/mlx5/mlx5_flow_hw.c
@@ -6828,6 +6828,17 @@ flow_hw_pattern_validate(struct rte_eth_dev *dev,
 							  " attribute");
 			break;
 		}
+		case RTE_FLOW_ITEM_TYPE_GENEVE_OPT:
+		{
+			int ret;
+
+			ret = mlx5_flow_geneve_tlv_option_validate(priv,
+								   &items[i],
+								   error);
+			if (ret < 0)
+				return ret;
+			break;
+		}
 		case RTE_FLOW_ITEM_TYPE_VOID:
 		case RTE_FLOW_ITEM_TYPE_ETH:
 		case RTE_FLOW_ITEM_TYPE_VLAN:
@@ -6840,6 +6851,7 @@ flow_hw_pattern_validate(struct rte_eth_dev *dev,
 		case RTE_FLOW_ITEM_TYPE_VXLAN:
 		case RTE_FLOW_ITEM_TYPE_VXLAN_GPE:
 		case RTE_FLOW_ITEM_TYPE_MPLS:
+		case RTE_FLOW_ITEM_TYPE_GENEVE:
 		case MLX5_RTE_FLOW_ITEM_TYPE_SQ:
 		case RTE_FLOW_ITEM_TYPE_GRE:
 		case RTE_FLOW_ITEM_TYPE_GRE_KEY:
@@ -7008,24 +7020,45 @@ flow_hw_pattern_template_create(struct rte_eth_dev *dev,
 		}
 	}
 	for (i = 0; items[i].type != RTE_FLOW_ITEM_TYPE_END; ++i) {
-		if (items[i].type == RTE_FLOW_ITEM_TYPE_FLEX) {
+		switch (items[i].type) {
+		case RTE_FLOW_ITEM_TYPE_FLEX: {
 			const struct rte_flow_item_flex *spec =
 				(const struct rte_flow_item_flex *)items[i].spec;
 			struct rte_flow_item_flex_handle *handle = spec->handle;
 
 			if (flow_hw_flex_item_acquire(dev, handle, &it->flex_item)) {
-				claim_zero(mlx5dr_match_template_destroy(it->mt));
-				mlx5_free(it);
 				rte_flow_error_set(error, rte_errno,
 						   RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 						   "Failed to acquire flex item");
-				return NULL;
+				goto error;
 			}
+			break;
+		}
+		case RTE_FLOW_ITEM_TYPE_GENEVE_OPT: {
+			const struct rte_flow_item_geneve_opt *spec = items[i].spec;
+
+			if (mlx5_geneve_tlv_option_register(priv, spec,
+							    &it->geneve_opt_mng)) {
+				rte_flow_error_set(error, rte_errno,
+						   RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+						   "Failed to register GENEVE TLV option");
+				goto error;
+			}
+			break;
+		}
+		default:
+			break;
 		}
 	}
 	__atomic_fetch_add(&it->refcnt, 1, __ATOMIC_RELAXED);
 	LIST_INSERT_HEAD(&priv->flow_hw_itt, it, next);
 	return it;
+error:
+	flow_hw_flex_item_release(dev, &it->flex_item);
+	mlx5_geneve_tlv_options_unregister(priv, &it->geneve_opt_mng);
+	claim_zero(mlx5dr_match_template_destroy(it->mt));
+	mlx5_free(it);
+	return NULL;
 }
 
 /**
@@ -7046,6 +7079,8 @@ flow_hw_pattern_template_destroy(struct rte_eth_dev *dev,
 			      struct rte_flow_pattern_template *template,
 			      struct rte_flow_error *error __rte_unused)
 {
+	struct mlx5_priv *priv = dev->data->dev_private;
+
 	if (__atomic_load_n(&template->refcnt, __ATOMIC_RELAXED) > 1) {
 		DRV_LOG(WARNING, "Item template %p is still in use.",
 			(void *)template);
@@ -7059,6 +7094,7 @@ flow_hw_pattern_template_destroy(struct rte_eth_dev *dev,
 		mlx5_free_srh_flex_parser(dev);
 	LIST_REMOVE(template, next);
 	flow_hw_flex_item_release(dev, &template->flex_item);
+	mlx5_geneve_tlv_options_unregister(priv, &template->geneve_opt_mng);
 	claim_zero(mlx5dr_match_template_destroy(template->mt));
 	mlx5_free(template);
 	return 0;
