diff --git a/doc/guides/nics/sfc_efx.rst b/doc/guides/nics/sfc_efx.rst
index ba73391d5f..7b8c1c8527 100644
--- a/doc/guides/nics/sfc_efx.rst
+++ b/doc/guides/nics/sfc_efx.rst
@@ -218,6 +218,8 @@ Supported actions (***transfer*** rules):
 
 - VF
 
+- PORT_ID
+
 - DROP
 
 Validating flow rules depends on the firmware variant.
diff --git a/drivers/net/sfc/sfc_mae.c b/drivers/net/sfc/sfc_mae.c
index 64cd6b0e9b..f309efa2cf 100644
--- a/drivers/net/sfc/sfc_mae.c
+++ b/drivers/net/sfc/sfc_mae.c
@@ -30,6 +30,7 @@ sfc_mae_assign_entity_mport(struct sfc_adapter *sa,
 int
 sfc_mae_attach(struct sfc_adapter *sa)
 {
+	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
 	struct sfc_mae_switch_port_request switch_port_request = {0};
 	const efx_nic_cfg_t *encp = efx_nic_cfg_get(sa->nic);
 	efx_mport_sel_t entity_mport;
@@ -67,6 +68,12 @@ sfc_mae_attach(struct sfc_adapter *sa)
 	sfc_log_init(sa, "assign RTE switch port");
 	switch_port_request.type = SFC_MAE_SWITCH_PORT_INDEPENDENT;
 	switch_port_request.entity_mportp = &entity_mport;
+	/*
+	 * As of now, the driver does not support representors, so
+	 * RTE ethdev MPORT simply matches that of the entity.
+	 */
+	switch_port_request.ethdev_mportp = &entity_mport;
+	switch_port_request.ethdev_port_id = sas->port_id;
 	rc = sfc_mae_assign_switch_port(mae->switch_domain_id,
 					&switch_port_request,
 					&mae->switch_port_id);
@@ -793,6 +800,27 @@ sfc_mae_rule_parse_action_pf_vf(struct sfc_adapter *sa,
 	return efx_mae_action_set_populate_deliver(spec, &mport);
 }
 
+static int
+sfc_mae_rule_parse_action_port_id(struct sfc_adapter *sa,
+				  const struct rte_flow_action_port_id *conf,
+				  efx_mae_actions_t *spec)
+{
+	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
+	struct sfc_mae *mae = &sa->mae;
+	efx_mport_sel_t mport;
+	uint16_t port_id;
+	int rc;
+
+	port_id = (conf->original != 0) ? sas->port_id : conf->id;
+
+	rc = sfc_mae_switch_port_by_ethdev(mae->switch_domain_id,
+					   port_id, &mport);
+	if (rc != 0)
+		return rc;
+
+	return efx_mae_action_set_populate_deliver(spec, &mport);
+}
+
 static int
 sfc_mae_rule_parse_action(struct sfc_adapter *sa,
 			  const struct rte_flow_action *action,
@@ -848,6 +876,11 @@ sfc_mae_rule_parse_action(struct sfc_adapter *sa,
 				       bundle->actions_mask);
 		rc = sfc_mae_rule_parse_action_pf_vf(sa, action->conf, spec);
 		break;
+	case RTE_FLOW_ACTION_TYPE_PORT_ID:
+		SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ACTION_TYPE_PORT_ID,
+				       bundle->actions_mask);
+		rc = sfc_mae_rule_parse_action_port_id(sa, action->conf, spec);
+		break;
 	case RTE_FLOW_ACTION_TYPE_DROP:
 		SFC_BUILD_SET_OVERFLOW(RTE_FLOW_ACTION_TYPE_DROP,
 				       bundle->actions_mask);
diff --git a/drivers/net/sfc/sfc_switch.c b/drivers/net/sfc/sfc_switch.c
index 395fc40263..bdea2a2446 100644
--- a/drivers/net/sfc/sfc_switch.c
+++ b/drivers/net/sfc/sfc_switch.c
@@ -41,10 +41,22 @@
  * This mapping comprises a port type to ensure that RTE switch port ID
  * of a represented entity and that of its representor are different in
  * the case when the entity gets plugged into DPDK and not into a guest.
+ *
+ * Entry data also comprises RTE ethdev's own MPORT. This value
+ * coincides with the entity MPORT in the case of independent ports.
+ * In the case of representors, this ID is not a selector and refers
+ * to an allocatable object (that is, it's likely to change on RTE
+ * ethdev replug). Flow API backend must use this value rather
+ * than entity_mport to support flow rule action PORT_ID.
  */
 struct sfc_mae_switch_port {
 	TAILQ_ENTRY(sfc_mae_switch_port)	switch_domain_ports;
 
+	/** RTE ethdev MPORT */
+	efx_mport_sel_t				ethdev_mport;
+	/** RTE ethdev port ID */
+	uint16_t				ethdev_port_id;
+
 	/** Entity (PCIe function) MPORT selector */
 	efx_mport_sel_t				entity_mport;
 	/** Port type (independent/representor) */
@@ -263,6 +275,9 @@ sfc_mae_assign_switch_port(uint16_t switch_domain_id,
 	TAILQ_INSERT_TAIL(&domain->ports, port, switch_domain_ports);
 
 done:
+	port->ethdev_mport = *req->ethdev_mportp;
+	port->ethdev_port_id = req->ethdev_port_id;
+
 	*switch_port_id = port->id;
 
 	rte_spinlock_unlock(&sfc_mae_switch.lock);
@@ -274,3 +289,46 @@ sfc_mae_assign_switch_port(uint16_t switch_domain_id,
 	rte_spinlock_unlock(&sfc_mae_switch.lock);
 	return rc;
 }
+
+/* This function expects to be called only when the lock is held */
+static int
+sfc_mae_find_switch_port_by_ethdev(uint16_t switch_domain_id,
+				   uint16_t ethdev_port_id,
+				   efx_mport_sel_t *mport_sel)
+{
+	struct sfc_mae_switch_domain *domain;
+	struct sfc_mae_switch_port *port;
+
+	SFC_ASSERT(rte_spinlock_is_locked(&sfc_mae_switch.lock));
+
+	if (ethdev_port_id == RTE_MAX_ETHPORTS)
+		return EINVAL;
+
+	domain = sfc_mae_find_switch_domain_by_id(switch_domain_id);
+	if (domain == NULL)
+		return EINVAL;
+
+	TAILQ_FOREACH(port, &domain->ports, switch_domain_ports) {
+		if (port->ethdev_port_id == ethdev_port_id) {
+			*mport_sel = port->ethdev_mport;
+			return 0;
+		}
+	}
+
+	return ENOENT;
+}
+
+int
+sfc_mae_switch_port_by_ethdev(uint16_t switch_domain_id,
+			      uint16_t ethdev_port_id,
+			      efx_mport_sel_t *mport_sel)
+{
+	int rc;
+
+	rte_spinlock_lock(&sfc_mae_switch.lock);
+	rc = sfc_mae_find_switch_port_by_ethdev(switch_domain_id,
+						ethdev_port_id, mport_sel);
+	rte_spinlock_unlock(&sfc_mae_switch.lock);
+
+	return rc;
+}
diff --git a/drivers/net/sfc/sfc_switch.h b/drivers/net/sfc/sfc_switch.h
index 9845ac8801..96ece95654 100644
--- a/drivers/net/sfc/sfc_switch.h
+++ b/drivers/net/sfc/sfc_switch.h
@@ -32,6 +32,8 @@ enum sfc_mae_switch_port_type {
 struct sfc_mae_switch_port_request {
 	enum sfc_mae_switch_port_type		type;
 	const efx_mport_sel_t			*entity_mportp;
+	const efx_mport_sel_t			*ethdev_mportp;
+	uint16_t				ethdev_port_id;
 };
 
 int sfc_mae_assign_switch_domain(struct sfc_adapter *sa,
@@ -41,6 +43,10 @@ int sfc_mae_assign_switch_port(uint16_t switch_domain_id,
 			       const struct sfc_mae_switch_port_request *req,
 			       uint16_t *switch_port_id);
 
+int sfc_mae_switch_port_by_ethdev(uint16_t switch_domain_id,
+				  uint16_t ethdev_port_id,
+				  efx_mport_sel_t *mport_sel);
+
 #ifdef __cplusplus
 }
 #endif
