diff mbox series

[08/10] net/sfc: support action VXLAN ENCAP in MAE backend

Message ID 20210312093143.28186-8-ivan.malov@oktetlabs.ru (mailing list archive)
State Superseded
Headers show
Series [01/10] ethdev: reuse header definition in flow pattern item ETH | expand

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

Ivan Malov March 12, 2021, 9:31 a.m. UTC
Provide necessary facilities for handling this action.

Signed-off-by: Ivan Malov <ivan.malov@oktetlabs.ru>
Reviewed-by: Andrew Rybchenko <andrew.rybchenko@oktetlabs.ru>
Reviewed-by: Andy Moreton <amoreton@xilinx.com>
---
 drivers/net/sfc/sfc_mae.c | 550 +++++++++++++++++++++++++++++++++++++-
 drivers/net/sfc/sfc_mae.h |  29 ++
 2 files changed, 572 insertions(+), 7 deletions(-)
diff mbox series

Patch

diff --git a/drivers/net/sfc/sfc_mae.c b/drivers/net/sfc/sfc_mae.c
index 50efd47ad..79a1bd91d 100644
--- a/drivers/net/sfc/sfc_mae.c
+++ b/drivers/net/sfc/sfc_mae.c
@@ -9,7 +9,9 @@ 
 
 #include <stdbool.h>
 
+#include <rte_bitops.h>
 #include <rte_common.h>
+#include <rte_vxlan.h>
 
 #include "efx.h"
 
@@ -35,6 +37,7 @@  sfc_mae_attach(struct sfc_adapter *sa)
 	const efx_nic_cfg_t *encp = efx_nic_cfg_get(sa->nic);
 	efx_mport_sel_t entity_mport;
 	struct sfc_mae *mae = &sa->mae;
+	struct sfc_mae_bounce_eh *bounce_eh = &mae->bounce_eh;
 	efx_mae_limits_t limits;
 	int rc;
 
@@ -80,17 +83,26 @@  sfc_mae_attach(struct sfc_adapter *sa)
 	if (rc != 0)
 		goto fail_mae_assign_switch_port;
 
+	sfc_log_init(sa, "allocate encap. header bounce buffer");
+	bounce_eh->buf_size = limits.eml_encap_header_size_limit;
+	bounce_eh->buf = rte_malloc("sfc_mae_bounce_eh",
+				    bounce_eh->buf_size, 0);
+	if (bounce_eh->buf == NULL)
+		goto fail_mae_alloc_bounce_eh;
+
 	mae->status = SFC_MAE_STATUS_SUPPORTED;
 	mae->nb_outer_rule_prios_max = limits.eml_max_n_outer_prios;
 	mae->nb_action_rule_prios_max = limits.eml_max_n_action_prios;
 	mae->encap_types_supported = limits.eml_encap_types_supported;
 	TAILQ_INIT(&mae->outer_rules);
+	TAILQ_INIT(&mae->encap_headers);
 	TAILQ_INIT(&mae->action_sets);
 
 	sfc_log_init(sa, "done");
 
 	return 0;
 
+fail_mae_alloc_bounce_eh:
 fail_mae_assign_switch_port:
 fail_mae_assign_switch_domain:
 fail_mae_assign_entity_mport:
@@ -117,6 +129,8 @@  sfc_mae_detach(struct sfc_adapter *sa)
 	if (status_prev != SFC_MAE_STATUS_SUPPORTED)
 		return;
 
+	rte_free(mae->bounce_eh.buf);
+
 	efx_mae_fini(sa->nic);
 
 	sfc_log_init(sa, "done");
@@ -254,8 +268,165 @@  sfc_mae_outer_rule_disable(struct sfc_adapter *sa,
 	return 0;
 }
 
+static struct sfc_mae_encap_header *
+sfc_mae_encap_header_attach(struct sfc_adapter *sa,
+			    const struct sfc_mae_bounce_eh *bounce_eh)
+{
+	struct sfc_mae_encap_header *encap_header;
+	struct sfc_mae *mae = &sa->mae;
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+
+	TAILQ_FOREACH(encap_header, &mae->encap_headers, entries) {
+		if (encap_header->size == bounce_eh->size &&
+		    memcmp(encap_header->buf, bounce_eh->buf,
+			   bounce_eh->size) == 0) {
+			++(encap_header->refcnt);
+			return encap_header;
+		}
+	}
+
+	return NULL;
+}
+
+static int
+sfc_mae_encap_header_add(struct sfc_adapter *sa,
+			 const struct sfc_mae_bounce_eh *bounce_eh,
+			 struct sfc_mae_encap_header **encap_headerp)
+{
+	struct sfc_mae_encap_header *encap_header;
+	struct sfc_mae *mae = &sa->mae;
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+
+	encap_header = rte_zmalloc("sfc_mae_encap_header",
+				   sizeof(*encap_header), 0);
+	if (encap_header == NULL)
+		return ENOMEM;
+
+	encap_header->size = bounce_eh->size;
+
+	encap_header->buf = rte_malloc("sfc_mae_encap_header_buf",
+				       encap_header->size, 0);
+	if (encap_header->buf == NULL) {
+		rte_free(encap_header);
+		return ENOMEM;
+	}
+
+	rte_memcpy(encap_header->buf, bounce_eh->buf, bounce_eh->size);
+
+	encap_header->refcnt = 1;
+	encap_header->type = bounce_eh->type;
+	encap_header->fw_rsrc.eh_id.id = EFX_MAE_RSRC_ID_INVALID;
+
+	TAILQ_INSERT_TAIL(&mae->encap_headers, encap_header, entries);
+
+	*encap_headerp = encap_header;
+
+	return 0;
+}
+
+static void
+sfc_mae_encap_header_del(struct sfc_adapter *sa,
+		       struct sfc_mae_encap_header *encap_header)
+{
+	struct sfc_mae *mae = &sa->mae;
+
+	if (encap_header == NULL)
+		return;
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+	SFC_ASSERT(encap_header->refcnt != 0);
+
+	--(encap_header->refcnt);
+
+	if (encap_header->refcnt != 0)
+		return;
+
+	SFC_ASSERT(encap_header->fw_rsrc.eh_id.id == EFX_MAE_RSRC_ID_INVALID);
+	SFC_ASSERT(encap_header->fw_rsrc.refcnt == 0);
+
+	TAILQ_REMOVE(&mae->encap_headers, encap_header, entries);
+	rte_free(encap_header->buf);
+	rte_free(encap_header);
+}
+
+static int
+sfc_mae_encap_header_enable(struct sfc_adapter *sa,
+			    struct sfc_mae_encap_header *encap_header,
+			    efx_mae_actions_t *action_set_spec)
+{
+	struct sfc_mae_fw_rsrc *fw_rsrc;
+	int rc;
+
+	if (encap_header == NULL)
+		return 0;
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+
+	fw_rsrc = &encap_header->fw_rsrc;
+
+	if (fw_rsrc->refcnt == 0) {
+		SFC_ASSERT(fw_rsrc->eh_id.id == EFX_MAE_RSRC_ID_INVALID);
+		SFC_ASSERT(encap_header->buf != NULL);
+		SFC_ASSERT(encap_header->size != 0);
+
+		rc = efx_mae_encap_header_alloc(sa->nic, encap_header->type,
+						encap_header->buf,
+						encap_header->size,
+						&fw_rsrc->eh_id);
+		if (rc != 0)
+			return rc;
+	}
+
+	rc = efx_mae_action_set_fill_in_eh_id(action_set_spec,
+					      &fw_rsrc->eh_id);
+	if (rc != 0) {
+		if (fw_rsrc->refcnt == 0) {
+			(void)efx_mae_encap_header_free(sa->nic,
+							&fw_rsrc->eh_id);
+		}
+		return rc;
+	}
+
+	++(fw_rsrc->refcnt);
+
+	return 0;
+}
+
+static int
+sfc_mae_encap_header_disable(struct sfc_adapter *sa,
+			     struct sfc_mae_encap_header *encap_header)
+{
+	struct sfc_mae_fw_rsrc *fw_rsrc;
+	int rc;
+
+	if (encap_header == NULL)
+		return 0;
+
+	SFC_ASSERT(sfc_adapter_is_locked(sa));
+
+	fw_rsrc = &encap_header->fw_rsrc;
+
+	SFC_ASSERT(fw_rsrc->eh_id.id != EFX_MAE_RSRC_ID_INVALID);
+	SFC_ASSERT(fw_rsrc->refcnt != 0);
+
+	if (fw_rsrc->refcnt == 1) {
+		rc = efx_mae_encap_header_free(sa->nic, &fw_rsrc->eh_id);
+		if (rc != 0)
+			return rc;
+
+		fw_rsrc->eh_id.id = EFX_MAE_RSRC_ID_INVALID;
+	}
+
+	--(fw_rsrc->refcnt);
+
+	return 0;
+}
+
 static struct sfc_mae_action_set *
 sfc_mae_action_set_attach(struct sfc_adapter *sa,
+			  const struct sfc_mae_encap_header *encap_header,
 			  const efx_mae_actions_t *spec)
 {
 	struct sfc_mae_action_set *action_set;
@@ -264,7 +435,8 @@  sfc_mae_action_set_attach(struct sfc_adapter *sa,
 	SFC_ASSERT(sfc_adapter_is_locked(sa));
 
 	TAILQ_FOREACH(action_set, &mae->action_sets, entries) {
-		if (efx_mae_action_set_specs_equal(action_set->spec, spec)) {
+		if (action_set->encap_header == encap_header &&
+		    efx_mae_action_set_specs_equal(action_set->spec, spec)) {
 			++(action_set->refcnt);
 			return action_set;
 		}
@@ -276,6 +448,7 @@  sfc_mae_action_set_attach(struct sfc_adapter *sa,
 static int
 sfc_mae_action_set_add(struct sfc_adapter *sa,
 		       efx_mae_actions_t *spec,
+		       struct sfc_mae_encap_header *encap_header,
 		       struct sfc_mae_action_set **action_setp)
 {
 	struct sfc_mae_action_set *action_set;
@@ -289,6 +462,7 @@  sfc_mae_action_set_add(struct sfc_adapter *sa,
 
 	action_set->refcnt = 1;
 	action_set->spec = spec;
+	action_set->encap_header = encap_header;
 
 	action_set->fw_rsrc.aset_id.id = EFX_MAE_RSRC_ID_INVALID;
 
@@ -317,6 +491,7 @@  sfc_mae_action_set_del(struct sfc_adapter *sa,
 	SFC_ASSERT(action_set->fw_rsrc.refcnt == 0);
 
 	efx_mae_action_set_spec_fini(sa->nic, action_set->spec);
+	sfc_mae_encap_header_del(sa, action_set->encap_header);
 	TAILQ_REMOVE(&mae->action_sets, action_set, entries);
 	rte_free(action_set);
 }
@@ -325,6 +500,7 @@  static int
 sfc_mae_action_set_enable(struct sfc_adapter *sa,
 			  struct sfc_mae_action_set *action_set)
 {
+	struct sfc_mae_encap_header *encap_header = action_set->encap_header;
 	struct sfc_mae_fw_rsrc *fw_rsrc = &action_set->fw_rsrc;
 	int rc;
 
@@ -334,10 +510,18 @@  sfc_mae_action_set_enable(struct sfc_adapter *sa,
 		SFC_ASSERT(fw_rsrc->aset_id.id == EFX_MAE_RSRC_ID_INVALID);
 		SFC_ASSERT(action_set->spec != NULL);
 
+		rc = sfc_mae_encap_header_enable(sa, encap_header,
+						 action_set->spec);
+		if (rc != 0)
+			return rc;
+
 		rc = efx_mae_action_set_alloc(sa->nic, action_set->spec,
 					      &fw_rsrc->aset_id);
-		if (rc != 0)
+		if (rc != 0) {
+			(void)sfc_mae_encap_header_disable(sa, encap_header);
+
 			return rc;
+		}
 	}
 
 	++(fw_rsrc->refcnt);
@@ -362,6 +546,10 @@  sfc_mae_action_set_disable(struct sfc_adapter *sa,
 			return rc;
 
 		fw_rsrc->aset_id.id = EFX_MAE_RSRC_ID_INVALID;
+
+		rc = sfc_mae_encap_header_disable(sa, action_set->encap_header);
+		if (rc != 0)
+			return rc;
 	}
 
 	--(fw_rsrc->refcnt);
@@ -1936,6 +2124,307 @@  sfc_mae_rule_parse_action_of_set_vlan_pcp(
 	bundle->vlan_push_tci |= rte_cpu_to_be_16(vlan_tci_pcp);
 }
 
+struct sfc_mae_parsed_item {
+	const struct rte_flow_item	*item;
+	size_t				proto_header_ofst;
+	size_t				proto_header_size;
+};
+
+/*
+ * For each 16-bit word of the given header, override
+ * bits enforced by the corresponding 16-bit mask.
+ */
+static void
+sfc_mae_header_force_item_masks(uint8_t *header_buf,
+				const struct sfc_mae_parsed_item *parsed_items,
+				unsigned int nb_parsed_items)
+{
+	unsigned int item_idx;
+
+	for (item_idx = 0; item_idx < nb_parsed_items; ++item_idx) {
+		const struct sfc_mae_parsed_item *parsed_item;
+		const struct rte_flow_item *item;
+		size_t proto_header_size;
+		size_t ofst;
+
+		parsed_item = &parsed_items[item_idx];
+		proto_header_size = parsed_item->proto_header_size;
+		item = parsed_item->item;
+
+		for (ofst = 0; ofst < proto_header_size;
+		     ofst += sizeof(rte_be16_t)) {
+			rte_be16_t *wp = RTE_PTR_ADD(header_buf, ofst);
+			const rte_be16_t *w_maskp;
+			const rte_be16_t *w_specp;
+
+			w_maskp = RTE_PTR_ADD(item->mask, ofst);
+			w_specp = RTE_PTR_ADD(item->spec, ofst);
+
+			*wp &= ~(*w_maskp);
+			*wp |= (*w_specp & *w_maskp);
+		}
+
+		header_buf += proto_header_size;
+	}
+}
+
+#define SFC_IPV4_TTL_DEF	0x40
+#define SFC_IPV6_VTC_FLOW_DEF	0x60000000
+#define SFC_IPV6_HOP_LIMITS_DEF	0xff
+#define SFC_VXLAN_FLAGS_DEF	0x08000000
+
+static int
+sfc_mae_rule_parse_action_vxlan_encap(
+			    struct sfc_mae *mae,
+			    const struct rte_flow_action_vxlan_encap *conf,
+			    efx_mae_actions_t *spec,
+			    struct rte_flow_error *error)
+{
+	struct sfc_mae_bounce_eh *bounce_eh = &mae->bounce_eh;
+	struct rte_flow_item *pattern = conf->definition;
+	uint8_t *buf = bounce_eh->buf;
+
+	/* This array will keep track of non-VOID pattern items. */
+	struct sfc_mae_parsed_item parsed_items[1 /* Ethernet */ +
+						2 /* VLAN tags */ +
+						1 /* IPv4 or IPv6 */ +
+						1 /* UDP */ +
+						1 /* VXLAN */];
+	unsigned int nb_parsed_items = 0;
+
+	size_t eth_ethertype_ofst = offsetof(struct rte_ether_hdr, ether_type);
+	uint8_t dummy_buf[RTE_MAX(sizeof(struct rte_ipv4_hdr),
+				  sizeof(struct rte_ipv6_hdr))];
+	struct rte_ipv4_hdr *ipv4 = (void *)dummy_buf;
+	struct rte_ipv6_hdr *ipv6 = (void *)dummy_buf;
+	struct rte_vxlan_hdr *vxlan = NULL;
+	struct rte_udp_hdr *udp = NULL;
+	unsigned int nb_vlan_tags = 0;
+	size_t next_proto_ofst = 0;
+	size_t ethertype_ofst = 0;
+	uint64_t exp_items;
+
+	if (pattern == NULL) {
+		return rte_flow_error_set(error, EINVAL,
+				RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+				"The encap. header definition is NULL");
+	}
+
+	bounce_eh->type = EFX_TUNNEL_PROTOCOL_VXLAN;
+	bounce_eh->size = 0;
+
+	/*
+	 * Process pattern items and remember non-VOID ones.
+	 * Defer applying masks until after the complete header
+	 * has been built from the pattern items.
+	 */
+	exp_items = RTE_BIT64(RTE_FLOW_ITEM_TYPE_ETH);
+
+	for (; pattern->type != RTE_FLOW_ITEM_TYPE_END; ++pattern) {
+		struct sfc_mae_parsed_item *parsed_item;
+		const uint64_t exp_items_extra_vlan[] = {
+			RTE_BIT64(RTE_FLOW_ITEM_TYPE_VLAN), 0
+		};
+		size_t proto_header_size;
+		rte_be16_t *ethertypep;
+		uint8_t *next_protop;
+		uint8_t *buf_cur;
+
+		if (pattern->spec == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"NULL item spec in the encap. header");
+		}
+
+		if (pattern->mask == NULL) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"NULL item mask in the encap. header");
+		}
+
+		if (pattern->last != NULL) {
+			/* This is not a match pattern, so disallow range. */
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"Range item in the encap. header");
+		}
+
+		if (pattern->type == RTE_FLOW_ITEM_TYPE_VOID) {
+			/* Handle VOID separately, for clarity. */
+			continue;
+		}
+
+		if ((exp_items & RTE_BIT64(pattern->type)) == 0) {
+			return rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"Unexpected item in the encap. header");
+		}
+
+		parsed_item = &parsed_items[nb_parsed_items];
+		buf_cur = buf + bounce_eh->size;
+
+		switch (pattern->type) {
+		case RTE_FLOW_ITEM_TYPE_ETH:
+			SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ITEM_TYPE_ETH,
+					       exp_items);
+			RTE_BUILD_BUG_ON(offsetof(struct rte_flow_item_eth,
+						  hdr) != 0);
+
+			proto_header_size = sizeof(struct rte_ether_hdr);
+
+			ethertype_ofst = eth_ethertype_ofst;
+
+			exp_items = RTE_BIT64(RTE_FLOW_ITEM_TYPE_VLAN) |
+				    RTE_BIT64(RTE_FLOW_ITEM_TYPE_IPV4) |
+				    RTE_BIT64(RTE_FLOW_ITEM_TYPE_IPV6);
+			break;
+		case RTE_FLOW_ITEM_TYPE_VLAN:
+			SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ITEM_TYPE_VLAN,
+					       exp_items);
+			RTE_BUILD_BUG_ON(offsetof(struct rte_flow_item_vlan,
+						  hdr) != 0);
+
+			proto_header_size = sizeof(struct rte_vlan_hdr);
+
+			ethertypep = RTE_PTR_ADD(buf, eth_ethertype_ofst);
+			*ethertypep = RTE_BE16(RTE_ETHER_TYPE_QINQ);
+
+			ethertypep = RTE_PTR_ADD(buf, ethertype_ofst);
+			*ethertypep = RTE_BE16(RTE_ETHER_TYPE_VLAN);
+
+			ethertype_ofst =
+			    bounce_eh->size +
+			    offsetof(struct rte_vlan_hdr, eth_proto);
+
+			exp_items = RTE_BIT64(RTE_FLOW_ITEM_TYPE_IPV4) |
+				    RTE_BIT64(RTE_FLOW_ITEM_TYPE_IPV6);
+			exp_items |= exp_items_extra_vlan[nb_vlan_tags];
+
+			++nb_vlan_tags;
+			break;
+		case RTE_FLOW_ITEM_TYPE_IPV4:
+			SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ITEM_TYPE_IPV4,
+					       exp_items);
+			RTE_BUILD_BUG_ON(offsetof(struct rte_flow_item_ipv4,
+						  hdr) != 0);
+
+			proto_header_size = sizeof(struct rte_ipv4_hdr);
+
+			ethertypep = RTE_PTR_ADD(buf, ethertype_ofst);
+			*ethertypep = RTE_BE16(RTE_ETHER_TYPE_IPV4);
+
+			next_proto_ofst =
+			    bounce_eh->size +
+			    offsetof(struct rte_ipv4_hdr, next_proto_id);
+
+			ipv4 = (struct rte_ipv4_hdr *)buf_cur;
+
+			exp_items = RTE_BIT64(RTE_FLOW_ITEM_TYPE_UDP);
+			break;
+		case RTE_FLOW_ITEM_TYPE_IPV6:
+			SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ITEM_TYPE_IPV6,
+					       exp_items);
+			RTE_BUILD_BUG_ON(offsetof(struct rte_flow_item_ipv6,
+						  hdr) != 0);
+
+			proto_header_size = sizeof(struct rte_ipv6_hdr);
+
+			ethertypep = RTE_PTR_ADD(buf, ethertype_ofst);
+			*ethertypep = RTE_BE16(RTE_ETHER_TYPE_IPV6);
+
+			next_proto_ofst = bounce_eh->size +
+					  offsetof(struct rte_ipv6_hdr, proto);
+
+			ipv6 = (struct rte_ipv6_hdr *)buf_cur;
+
+			exp_items = RTE_BIT64(RTE_FLOW_ITEM_TYPE_UDP);
+			break;
+		case RTE_FLOW_ITEM_TYPE_UDP:
+			SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ITEM_TYPE_UDP,
+					       exp_items);
+			RTE_BUILD_BUG_ON(offsetof(struct rte_flow_item_udp,
+						  hdr) != 0);
+
+			proto_header_size = sizeof(struct rte_udp_hdr);
+
+			next_protop = RTE_PTR_ADD(buf, next_proto_ofst);
+			*next_protop = IPPROTO_UDP;
+
+			udp = (struct rte_udp_hdr *)buf_cur;
+
+			exp_items = RTE_BIT64(RTE_FLOW_ITEM_TYPE_VXLAN);
+			break;
+		case RTE_FLOW_ITEM_TYPE_VXLAN:
+			SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ITEM_TYPE_VXLAN,
+					       exp_items);
+			RTE_BUILD_BUG_ON(offsetof(struct rte_flow_item_vxlan,
+						  hdr) != 0);
+
+			proto_header_size = sizeof(struct rte_vxlan_hdr);
+
+			vxlan = (struct rte_vxlan_hdr *)buf_cur;
+
+			udp->dst_port = RTE_BE16(RTE_VXLAN_DEFAULT_PORT);
+			udp->dgram_len = RTE_BE16(sizeof(*udp) +
+						  sizeof(*vxlan));
+			udp->dgram_cksum = 0;
+
+			exp_items = 0;
+			break;
+		default:
+			return rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"Unknown item in the encap. header");
+		}
+
+		if (bounce_eh->size + proto_header_size > bounce_eh->buf_size) {
+			return rte_flow_error_set(error, E2BIG,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"The encap. header is too big");
+		}
+
+		if ((proto_header_size & 1) != 0) {
+			return rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"Odd layer size in the encap. header");
+		}
+
+		rte_memcpy(buf_cur, pattern->spec, proto_header_size);
+		bounce_eh->size += proto_header_size;
+
+		parsed_item->item = pattern;
+		parsed_item->proto_header_size = proto_header_size;
+		++nb_parsed_items;
+	}
+
+	if (exp_items != 0) {
+		/* Parsing item VXLAN would have reset exp_items to 0. */
+		return rte_flow_error_set(error, ENOTSUP,
+					RTE_FLOW_ERROR_TYPE_ACTION_CONF, NULL,
+					"No item VXLAN in the encap. header");
+	}
+
+	/* One of the pointers (ipv4, ipv6) referes to a dummy area. */
+	ipv4->version_ihl = RTE_IPV4_VHL_DEF;
+	ipv4->time_to_live = SFC_IPV4_TTL_DEF;
+	ipv4->total_length = RTE_BE16(sizeof(*ipv4) + sizeof(*udp) +
+				      sizeof(*vxlan));
+	/* The HW cannot compute this checksum. */
+	ipv4->hdr_checksum = 0;
+	ipv4->hdr_checksum = rte_ipv4_cksum(ipv4);
+
+	ipv6->vtc_flow = RTE_BE32(SFC_IPV6_VTC_FLOW_DEF);
+	ipv6->hop_limits = SFC_IPV6_HOP_LIMITS_DEF;
+	ipv6->payload_len = udp->dgram_len;
+
+	vxlan->vx_flags = RTE_BE32(SFC_VXLAN_FLAGS_DEF);
+
+	/* Take care of the masks. */
+	sfc_mae_header_force_item_masks(buf, parsed_items, nb_parsed_items);
+
+	return (spec != NULL) ? efx_mae_action_set_populate_encap(spec) : 0;
+}
+
 static int
 sfc_mae_rule_parse_action_mark(const struct rte_flow_action_mark *conf,
 			       efx_mae_actions_t *spec)
@@ -2016,6 +2505,7 @@  sfc_mae_rule_parse_action(struct sfc_adapter *sa,
 			  efx_mae_actions_t *spec,
 			  struct rte_flow_error *error)
 {
+	bool custom_error = B_FALSE;
 	int rc = 0;
 
 	switch (action->type) {
@@ -2039,6 +2529,14 @@  sfc_mae_rule_parse_action(struct sfc_adapter *sa,
 				       bundle->actions_mask);
 		sfc_mae_rule_parse_action_of_set_vlan_pcp(action->conf, bundle);
 		break;
+	case RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP:
+		SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ACTION_TYPE_VXLAN_ENCAP,
+				       bundle->actions_mask);
+		rc = sfc_mae_rule_parse_action_vxlan_encap(&sa->mae,
+							   action->conf,
+							   spec, error);
+		custom_error = B_TRUE;
+		break;
 	case RTE_FLOW_ACTION_TYPE_FLAG:
 		SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ACTION_TYPE_FLAG,
 				       bundle->actions_mask);
@@ -2080,24 +2578,49 @@  sfc_mae_rule_parse_action(struct sfc_adapter *sa,
 				"Unsupported action");
 	}
 
-	if (rc != 0) {
+	if (rc == 0) {
+		bundle->actions_mask |= (1ULL << action->type);
+	} else if (!custom_error) {
 		rc = rte_flow_error_set(error, rc, RTE_FLOW_ERROR_TYPE_ACTION,
 				NULL, "Failed to request the action");
-	} else {
-		bundle->actions_mask |= (1ULL << action->type);
 	}
 
 	return rc;
 }
 
+static void
+sfc_mae_bounce_eh_invalidate(struct sfc_mae_bounce_eh *bounce_eh)
+{
+	bounce_eh->type = EFX_TUNNEL_PROTOCOL_NONE;
+}
+
+static int
+sfc_mae_process_encap_header(struct sfc_adapter *sa,
+			     const struct sfc_mae_bounce_eh *bounce_eh,
+			     struct sfc_mae_encap_header **encap_headerp)
+{
+	if (bounce_eh->type == EFX_TUNNEL_PROTOCOL_NONE) {
+		encap_headerp = NULL;
+		return 0;
+	}
+
+	*encap_headerp = sfc_mae_encap_header_attach(sa, bounce_eh);
+	if (*encap_headerp != NULL)
+		return 0;
+
+	return sfc_mae_encap_header_add(sa, bounce_eh, encap_headerp);
+}
+
 int
 sfc_mae_rule_parse_actions(struct sfc_adapter *sa,
 			   const struct rte_flow_action actions[],
 			   struct sfc_flow_spec_mae *spec_mae,
 			   struct rte_flow_error *error)
 {
+	struct sfc_mae_encap_header *encap_header = NULL;
 	struct sfc_mae_actions_bundle bundle = {0};
 	const struct rte_flow_action *action;
+	struct sfc_mae *mae = &sa->mae;
 	efx_mae_actions_t *spec;
 	int rc;
 
@@ -2111,6 +2634,9 @@  sfc_mae_rule_parse_actions(struct sfc_adapter *sa,
 	if (rc != 0)
 		goto fail_action_set_spec_init;
 
+	/* Cleanup after previous encap. header bounce buffer usage. */
+	sfc_mae_bounce_eh_invalidate(&mae->bounce_eh);
+
 	for (action = actions;
 	     action->type != RTE_FLOW_ACTION_TYPE_END; ++action) {
 		rc = sfc_mae_actions_bundle_sync(action, &bundle, spec, error);
@@ -2127,19 +2653,29 @@  sfc_mae_rule_parse_actions(struct sfc_adapter *sa,
 	if (rc != 0)
 		goto fail_rule_parse_action;
 
-	spec_mae->action_set = sfc_mae_action_set_attach(sa, spec);
+	rc = sfc_mae_process_encap_header(sa, &mae->bounce_eh, &encap_header);
+	if (rc != 0)
+		goto fail_process_encap_header;
+
+	spec_mae->action_set = sfc_mae_action_set_attach(sa, encap_header,
+							 spec);
 	if (spec_mae->action_set != NULL) {
+		sfc_mae_encap_header_del(sa, encap_header);
 		efx_mae_action_set_spec_fini(sa->nic, spec);
 		return 0;
 	}
 
-	rc = sfc_mae_action_set_add(sa, spec, &spec_mae->action_set);
+	rc = sfc_mae_action_set_add(sa, spec, encap_header,
+				    &spec_mae->action_set);
 	if (rc != 0)
 		goto fail_action_set_add;
 
 	return 0;
 
 fail_action_set_add:
+	sfc_mae_encap_header_del(sa, encap_header);
+
+fail_process_encap_header:
 fail_rule_parse_action:
 	efx_mae_action_set_spec_fini(sa->nic, spec);
 
diff --git a/drivers/net/sfc/sfc_mae.h b/drivers/net/sfc/sfc_mae.h
index 00987af61..c08fa545b 100644
--- a/drivers/net/sfc/sfc_mae.h
+++ b/drivers/net/sfc/sfc_mae.h
@@ -27,6 +27,7 @@  struct sfc_mae_fw_rsrc {
 	union {
 		efx_mae_aset_id_t	aset_id;
 		efx_mae_rule_id_t	rule_id;
+		efx_mae_eh_id_t		eh_id;
 	};
 };
 
@@ -41,11 +42,24 @@  struct sfc_mae_outer_rule {
 
 TAILQ_HEAD(sfc_mae_outer_rules, sfc_mae_outer_rule);
 
+/** Encap. header registry entry */
+struct sfc_mae_encap_header {
+	TAILQ_ENTRY(sfc_mae_encap_header)	entries;
+	unsigned int				refcnt;
+	uint8_t					*buf;
+	size_t					size;
+	efx_tunnel_protocol_t			type;
+	struct sfc_mae_fw_rsrc			fw_rsrc;
+};
+
+TAILQ_HEAD(sfc_mae_encap_headers, sfc_mae_encap_header);
+
 /** Action set registry entry */
 struct sfc_mae_action_set {
 	TAILQ_ENTRY(sfc_mae_action_set)	entries;
 	unsigned int			refcnt;
 	efx_mae_actions_t		*spec;
+	struct sfc_mae_encap_header	*encap_header;
 	struct sfc_mae_fw_rsrc		fw_rsrc;
 };
 
@@ -58,6 +72,17 @@  enum sfc_mae_status {
 	SFC_MAE_STATUS_SUPPORTED
 };
 
+/*
+ * Encap. header bounce buffer. It is used to store header data
+ * when parsing the header definition in the action VXLAN_ENCAP.
+ */
+struct sfc_mae_bounce_eh {
+	uint8_t				*buf;
+	size_t				buf_size;
+	size_t				size;
+	efx_tunnel_protocol_t		type;
+};
+
 struct sfc_mae {
 	/** Assigned switch domain identifier */
 	uint16_t			switch_domain_id;
@@ -73,8 +98,12 @@  struct sfc_mae {
 	uint32_t			encap_types_supported;
 	/** Outer rule registry */
 	struct sfc_mae_outer_rules	outer_rules;
+	/** Encap. header registry */
+	struct sfc_mae_encap_headers	encap_headers;
 	/** Action set registry */
 	struct sfc_mae_action_sets	action_sets;
+	/** Encap. header bounce buffer */
+	struct sfc_mae_bounce_eh	bounce_eh;
 };
 
 struct sfc_adapter;