/* SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright(c) 2019-2021 Xilinx, Inc.
 * Copyright(c) 2019 Solarflare Communications Inc.
 *
 * This software was jointly developed between OKTET Labs (under contract
 * for Solarflare) and Solarflare Communications, Inc.
 */

#include <rte_service.h>
#include <rte_service_component.h>

#include "sfc_log.h"
#include "sfc_service.h"
#include "sfc_repr_proxy.h"
#include "sfc_repr_proxy_api.h"
#include "sfc.h"
#include "sfc_ev.h"
#include "sfc_rx.h"
#include "sfc_tx.h"
#include "sfc_dp_rx.h"

/**
 * Amount of time to wait for the representor proxy routine (which is
 * running on a service core) to handle a request sent via mbox.
 */
#define SFC_REPR_PROXY_MBOX_POLL_TIMEOUT_MS	1000

/**
 * Amount of time to wait for the representor proxy routine (which is
 * running on a service core) to terminate after service core is stopped.
 */
#define SFC_REPR_PROXY_ROUTINE_TERMINATE_TIMEOUT_MS	10000

#define SFC_REPR_INVALID_ROUTE_PORT_ID  (UINT16_MAX)

static struct sfc_repr_proxy *
sfc_repr_proxy_by_adapter(struct sfc_adapter *sa)
{
	return &sa->repr_proxy;
}

static struct sfc_adapter *
sfc_get_adapter_by_pf_port_id(uint16_t pf_port_id)
{
	struct rte_eth_dev *dev;
	struct sfc_adapter *sa;

	SFC_ASSERT(pf_port_id < RTE_MAX_ETHPORTS);

	dev = &rte_eth_devices[pf_port_id];
	sa = sfc_adapter_by_eth_dev(dev);

	sfc_adapter_lock(sa);

	return sa;
}

static void
sfc_put_adapter(struct sfc_adapter *sa)
{
	sfc_adapter_unlock(sa);
}

static struct sfc_repr_proxy_port *
sfc_repr_proxy_find_port(struct sfc_repr_proxy *rp, uint16_t repr_id)
{
	struct sfc_repr_proxy_port *port;

	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (port->repr_id == repr_id)
			return port;
	}

	return NULL;
}

static int
sfc_repr_proxy_mbox_send(struct sfc_repr_proxy_mbox *mbox,
			 struct sfc_repr_proxy_port *port,
			 enum sfc_repr_proxy_mbox_op op)
{
	const unsigned int wait_ms = SFC_REPR_PROXY_MBOX_POLL_TIMEOUT_MS;
	unsigned int i;

	mbox->op = op;
	mbox->port = port;
	mbox->ack = false;

	/*
	 * Release ordering enforces marker set after data is populated.
	 * Paired with acquire ordering in sfc_repr_proxy_mbox_handle().
	 */
	__atomic_store_n(&mbox->write_marker, true, __ATOMIC_RELEASE);

	/*
	 * Wait for the representor routine to process the request.
	 * Give up on timeout.
	 */
	for (i = 0; i < wait_ms; i++) {
		/*
		 * Paired with release ordering in sfc_repr_proxy_mbox_handle()
		 * on acknowledge write.
		 */
		if (__atomic_load_n(&mbox->ack, __ATOMIC_ACQUIRE))
			break;

		rte_delay_ms(1);
	}

	if (i == wait_ms) {
		SFC_GENERIC_LOG(ERR,
			"%s() failed to wait for representor proxy routine ack",
			__func__);
		return ETIMEDOUT;
	}

	return 0;
}

static void
sfc_repr_proxy_mbox_handle(struct sfc_repr_proxy *rp)
{
	struct sfc_repr_proxy_mbox *mbox = &rp->mbox;

	/*
	 * Paired with release ordering in sfc_repr_proxy_mbox_send()
	 * on marker set.
	 */
	if (!__atomic_load_n(&mbox->write_marker, __ATOMIC_ACQUIRE))
		return;

	mbox->write_marker = false;

	switch (mbox->op) {
	case SFC_REPR_PROXY_MBOX_ADD_PORT:
		TAILQ_INSERT_TAIL(&rp->ports, mbox->port, entries);
		break;
	case SFC_REPR_PROXY_MBOX_DEL_PORT:
		TAILQ_REMOVE(&rp->ports, mbox->port, entries);
		break;
	case SFC_REPR_PROXY_MBOX_START_PORT:
		mbox->port->started = true;
		break;
	case SFC_REPR_PROXY_MBOX_STOP_PORT:
		mbox->port->started = false;
		break;
	default:
		SFC_ASSERT(0);
		return;
	}

	/*
	 * Paired with acquire ordering in sfc_repr_proxy_mbox_send()
	 * on acknowledge read.
	 */
	__atomic_store_n(&mbox->ack, true, __ATOMIC_RELEASE);
}

static void
sfc_repr_proxy_handle_tx(struct sfc_repr_proxy_dp_txq *rp_txq,
			 struct sfc_repr_proxy_txq *repr_txq)
{
	/*
	 * With multiple representor proxy queues configured it is
	 * possible that not all of the corresponding representor
	 * queues were created. Skip the queues that do not exist.
	 */
	if (repr_txq->ring == NULL)
		return;

	if (rp_txq->available < RTE_DIM(rp_txq->tx_pkts)) {
		rp_txq->available +=
			rte_ring_sc_dequeue_burst(repr_txq->ring,
				(void **)(&rp_txq->tx_pkts[rp_txq->available]),
				RTE_DIM(rp_txq->tx_pkts) - rp_txq->available,
				NULL);

		if (rp_txq->available == rp_txq->transmitted)
			return;
	}

	rp_txq->transmitted += rp_txq->pkt_burst(rp_txq->dp,
				&rp_txq->tx_pkts[rp_txq->transmitted],
				rp_txq->available - rp_txq->transmitted);

	if (rp_txq->available == rp_txq->transmitted) {
		rp_txq->available = 0;
		rp_txq->transmitted = 0;
	}
}

static struct sfc_repr_proxy_port *
sfc_repr_proxy_rx_route_mbuf(struct sfc_repr_proxy *rp, struct rte_mbuf *m)
{
	struct sfc_repr_proxy_port *port;
	efx_mport_id_t mport_id;

	mport_id.id = *RTE_MBUF_DYNFIELD(m, sfc_dp_mport_offset,
					 typeof(&((efx_mport_id_t *)0)->id));

	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (port->egress_mport.id == mport_id.id) {
			m->port = port->rte_port_id;
			m->ol_flags &= ~sfc_dp_mport_override;
			return port;
		}
	}

	return NULL;
}

/*
 * Returns true if a packet is encountered which should be forwarded to a
 * port which is different from the one that is currently routed.
 */
static bool
sfc_repr_proxy_rx_route(struct sfc_repr_proxy *rp,
			struct sfc_repr_proxy_dp_rxq *rp_rxq)
{
	unsigned int i;

	for (i = rp_rxq->routed;
	     i < rp_rxq->available && !rp_rxq->stop_route;
	     i++, rp_rxq->routed++) {
		struct sfc_repr_proxy_port *port;
		struct rte_mbuf *m = rp_rxq->pkts[i];

		port = sfc_repr_proxy_rx_route_mbuf(rp, m);
		/* Cannot find destination representor */
		if (port == NULL) {
			/* Effectively drop the packet */
			rp_rxq->forwarded++;
			continue;
		}

		/* Currently routed packets are mapped to a different port */
		if (port->repr_id != rp_rxq->route_port_id &&
		    rp_rxq->route_port_id != SFC_REPR_INVALID_ROUTE_PORT_ID)
			return true;

		rp_rxq->route_port_id = port->repr_id;
	}

	return false;
}

static void
sfc_repr_proxy_rx_forward(struct sfc_repr_proxy *rp,
			  struct sfc_repr_proxy_dp_rxq *rp_rxq)
{
	struct sfc_repr_proxy_port *port;

	if (rp_rxq->route_port_id != SFC_REPR_INVALID_ROUTE_PORT_ID) {
		port = sfc_repr_proxy_find_port(rp, rp_rxq->route_port_id);

		if (port != NULL && port->started) {
			rp_rxq->forwarded +=
			    rte_ring_sp_enqueue_burst(port->rxq[0].ring,
				(void **)(&rp_rxq->pkts[rp_rxq->forwarded]),
				rp_rxq->routed - rp_rxq->forwarded, NULL);
		} else {
			/* Drop all routed packets if the port is not started */
			rp_rxq->forwarded = rp_rxq->routed;
		}
	}

	if (rp_rxq->forwarded == rp_rxq->routed) {
		rp_rxq->route_port_id = SFC_REPR_INVALID_ROUTE_PORT_ID;
		rp_rxq->stop_route = false;
	} else {
		/* Stall packet routing if not all packets were forwarded */
		rp_rxq->stop_route = true;
	}

	if (rp_rxq->available == rp_rxq->forwarded)
		rp_rxq->available = rp_rxq->forwarded = rp_rxq->routed = 0;
}

static void
sfc_repr_proxy_handle_rx(struct sfc_repr_proxy *rp,
			 struct sfc_repr_proxy_dp_rxq *rp_rxq)
{
	bool route_again;

	if (rp_rxq->available < RTE_DIM(rp_rxq->pkts)) {
		rp_rxq->available += rp_rxq->pkt_burst(rp_rxq->dp,
				&rp_rxq->pkts[rp_rxq->available],
				RTE_DIM(rp_rxq->pkts) - rp_rxq->available);
		if (rp_rxq->available == rp_rxq->forwarded)
			return;
	}

	do {
		route_again = sfc_repr_proxy_rx_route(rp, rp_rxq);
		sfc_repr_proxy_rx_forward(rp, rp_rxq);
	} while (route_again && !rp_rxq->stop_route);
}

static int32_t
sfc_repr_proxy_routine(void *arg)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy *rp = arg;
	unsigned int i;

	sfc_repr_proxy_mbox_handle(rp);

	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (!port->started)
			continue;

		for (i = 0; i < rp->nb_txq; i++)
			sfc_repr_proxy_handle_tx(&rp->dp_txq[i], &port->txq[i]);
	}

	for (i = 0; i < rp->nb_rxq; i++)
		sfc_repr_proxy_handle_rx(rp, &rp->dp_rxq[i]);

	return 0;
}

static struct sfc_txq_info *
sfc_repr_proxy_txq_info_get(struct sfc_adapter *sa, unsigned int repr_queue_id)
{
	struct sfc_adapter_shared *sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy_dp_txq *dp_txq;

	SFC_ASSERT(repr_queue_id < sfc_repr_nb_txq(sas));
	dp_txq = &sa->repr_proxy.dp_txq[repr_queue_id];

	return &sas->txq_info[dp_txq->sw_index];
}

static int
sfc_repr_proxy_txq_attach(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	unsigned int i;

	sfc_log_init(sa, "entry");

	for (i = 0; i < sfc_repr_nb_txq(sas); i++) {
		sfc_sw_index_t sw_index = sfc_repr_txq_sw_index(sas, i);

		rp->dp_txq[i].sw_index = sw_index;
	}

	sfc_log_init(sa, "done");

	return 0;
}

static void
sfc_repr_proxy_txq_detach(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	unsigned int i;

	sfc_log_init(sa, "entry");

	for (i = 0; i < sfc_repr_nb_txq(sas); i++)
		rp->dp_txq[i].sw_index = 0;

	sfc_log_init(sa, "done");
}

int
sfc_repr_proxy_txq_init(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	const struct rte_eth_txconf tx_conf = {
		.tx_free_thresh = SFC_REPR_PROXY_TXQ_FREE_THRESH,
	};
	struct sfc_txq_info *txq_info;
	unsigned int init_i;
	unsigned int i;
	int rc;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return 0;
	}

	for (init_i = 0; init_i < sfc_repr_nb_txq(sas); init_i++) {
		struct sfc_repr_proxy_dp_txq *txq = &rp->dp_txq[init_i];

		txq_info = &sfc_sa2shared(sa)->txq_info[txq->sw_index];
		if (txq_info->state == SFC_TXQ_INITIALIZED) {
			sfc_log_init(sa,
				"representor proxy TxQ %u is already initialized - skip",
				init_i);
			continue;
		}

		sfc_tx_qinit_info(sa, txq->sw_index);

		rc = sfc_tx_qinit(sa, txq->sw_index,
				  SFC_REPR_PROXY_TX_DESC_COUNT, sa->socket_id,
				  &tx_conf);

		if (rc != 0) {
			sfc_err(sa, "failed to init representor proxy TxQ %u",
				init_i);
			goto fail_init;
		}
	}

	sfc_log_init(sa, "done");

	return 0;

fail_init:
	for (i = 0; i < init_i; i++) {
		struct sfc_repr_proxy_dp_txq *txq = &rp->dp_txq[i];

		txq_info = &sfc_sa2shared(sa)->txq_info[txq->sw_index];
		if (txq_info->state == SFC_TXQ_INITIALIZED)
			sfc_tx_qfini(sa, txq->sw_index);
	}
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));

	return rc;
}

void
sfc_repr_proxy_txq_fini(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct sfc_txq_info *txq_info;
	unsigned int i;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return;
	}

	for (i = 0; i < sfc_repr_nb_txq(sas); i++) {
		struct sfc_repr_proxy_dp_txq *txq = &rp->dp_txq[i];

		txq_info = &sfc_sa2shared(sa)->txq_info[txq->sw_index];
		if (txq_info->state != SFC_TXQ_INITIALIZED) {
			sfc_log_init(sa,
				"representor proxy TxQ %u is already finalized - skip",
				i);
			continue;
		}

		sfc_tx_qfini(sa, txq->sw_index);
	}

	sfc_log_init(sa, "done");
}

static int
sfc_repr_proxy_txq_start(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	unsigned int i;

	sfc_log_init(sa, "entry");

	for (i = 0; i < sfc_repr_nb_txq(sas); i++) {
		struct sfc_repr_proxy_dp_txq *txq = &rp->dp_txq[i];

		txq->dp = sfc_repr_proxy_txq_info_get(sa, i)->dp;
		txq->pkt_burst = sa->eth_dev->tx_pkt_burst;
		txq->available = 0;
		txq->transmitted = 0;
	}

	sfc_log_init(sa, "done");

	return 0;
}

static void
sfc_repr_proxy_txq_stop(struct sfc_adapter *sa)
{
	sfc_log_init(sa, "entry");
	sfc_log_init(sa, "done");
}

static int
sfc_repr_proxy_rxq_attach(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	unsigned int i;

	sfc_log_init(sa, "entry");

	for (i = 0; i < sfc_repr_nb_rxq(sas); i++) {
		sfc_sw_index_t sw_index = sfc_repr_rxq_sw_index(sas, i);

		rp->dp_rxq[i].sw_index = sw_index;
	}

	sfc_log_init(sa, "done");

	return 0;
}

static void
sfc_repr_proxy_rxq_detach(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	unsigned int i;

	sfc_log_init(sa, "entry");

	for (i = 0; i < sfc_repr_nb_rxq(sas); i++)
		rp->dp_rxq[i].sw_index = 0;

	sfc_log_init(sa, "done");
}

static struct sfc_rxq_info *
sfc_repr_proxy_rxq_info_get(struct sfc_adapter *sa, unsigned int repr_queue_id)
{
	struct sfc_adapter_shared *sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy_dp_rxq *dp_rxq;

	SFC_ASSERT(repr_queue_id < sfc_repr_nb_rxq(sas));
	dp_rxq = &sa->repr_proxy.dp_rxq[repr_queue_id];

	return &sas->rxq_info[dp_rxq->sw_index];
}

static int
sfc_repr_proxy_rxq_init(struct sfc_adapter *sa,
			struct sfc_repr_proxy_dp_rxq *rxq)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	uint16_t nb_rx_desc = SFC_REPR_PROXY_RX_DESC_COUNT;
	struct sfc_rxq_info *rxq_info;
	struct rte_eth_rxconf rxconf = {
		.rx_free_thresh = SFC_REPR_PROXY_RXQ_REFILL_LEVEL,
		.rx_drop_en = 1,
	};
	int rc;

	sfc_log_init(sa, "entry");

	rxq_info = &sas->rxq_info[rxq->sw_index];
	if (rxq_info->state & SFC_RXQ_INITIALIZED) {
		sfc_log_init(sa, "RxQ is already initialized - skip");
		return 0;
	}

	nb_rx_desc = RTE_MIN(nb_rx_desc, sa->rxq_max_entries);
	nb_rx_desc = RTE_MAX(nb_rx_desc, sa->rxq_min_entries);

	rc = sfc_rx_qinit_info(sa, rxq->sw_index, EFX_RXQ_FLAG_INGRESS_MPORT);
	if (rc != 0) {
		sfc_err(sa, "failed to init representor proxy RxQ info");
		goto fail_repr_rxq_init_info;
	}

	rc = sfc_rx_qinit(sa, rxq->sw_index, nb_rx_desc, sa->socket_id, &rxconf,
			  rxq->mp);
	if (rc != 0) {
		sfc_err(sa, "failed to init representor proxy RxQ");
		goto fail_repr_rxq_init;
	}

	sfc_log_init(sa, "done");

	return 0;

fail_repr_rxq_init:
fail_repr_rxq_init_info:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));

	return rc;
}

static void
sfc_repr_proxy_rxq_fini(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct sfc_rxq_info *rxq_info;
	unsigned int i;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return;
	}

	for (i = 0; i < sfc_repr_nb_rxq(sas); i++) {
		struct sfc_repr_proxy_dp_rxq *rxq = &rp->dp_rxq[i];

		rxq_info = &sas->rxq_info[rxq->sw_index];
		if (rxq_info->state != SFC_RXQ_INITIALIZED) {
			sfc_log_init(sa,
				"representor RxQ %u is already finalized - skip",
				i);
			continue;
		}

		sfc_rx_qfini(sa, rxq->sw_index);
	}

	sfc_log_init(sa, "done");
}

static void
sfc_repr_proxy_rxq_stop(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	unsigned int i;

	sfc_log_init(sa, "entry");

	for (i = 0; i < sfc_repr_nb_rxq(sas); i++)
		sfc_rx_qstop(sa, sa->repr_proxy.dp_rxq[i].sw_index);

	sfc_repr_proxy_rxq_fini(sa);

	sfc_log_init(sa, "done");
}

static int
sfc_repr_proxy_rxq_start(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	unsigned int i;
	int rc;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return 0;
	}

	for (i = 0; i < sfc_repr_nb_rxq(sas); i++) {
		struct sfc_repr_proxy_dp_rxq *rxq = &rp->dp_rxq[i];

		rc = sfc_repr_proxy_rxq_init(sa, rxq);
		if (rc != 0) {
			sfc_err(sa, "failed to init representor proxy RxQ %u",
				i);
			goto fail_init;
		}

		rc = sfc_rx_qstart(sa, rxq->sw_index);
		if (rc != 0) {
			sfc_err(sa, "failed to start representor proxy RxQ %u",
				i);
			goto fail_start;
		}

		rxq->dp = sfc_repr_proxy_rxq_info_get(sa, i)->dp;
		rxq->pkt_burst = sa->eth_dev->rx_pkt_burst;
		rxq->available = 0;
		rxq->routed = 0;
		rxq->forwarded = 0;
		rxq->stop_route = false;
		rxq->route_port_id = SFC_REPR_INVALID_ROUTE_PORT_ID;
	}

	sfc_log_init(sa, "done");

	return 0;

fail_start:
fail_init:
	sfc_repr_proxy_rxq_stop(sa);
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	return rc;
}

static int
sfc_repr_proxy_mae_rule_insert(struct sfc_adapter *sa,
			       struct sfc_repr_proxy_port *port)
{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	efx_mport_sel_t mport_alias_selector;
	efx_mport_sel_t mport_vf_selector;
	struct sfc_mae_rule *mae_rule;
	int rc;

	sfc_log_init(sa, "entry");

	rc = efx_mae_mport_by_id(&port->egress_mport,
				 &mport_vf_selector);
	if (rc != 0) {
		sfc_err(sa, "failed to get VF mport for repr %u",
			port->repr_id);
		goto fail_get_vf;
	}

	rc = efx_mae_mport_by_id(&rp->mport_alias, &mport_alias_selector);
	if (rc != 0) {
		sfc_err(sa, "failed to get mport selector for repr %u",
			port->repr_id);
		goto fail_get_alias;
	}

	rc = sfc_mae_rule_add_mport_match_deliver(sa, &mport_vf_selector,
						  &mport_alias_selector, -1,
						  &mae_rule);
	if (rc != 0) {
		sfc_err(sa, "failed to insert MAE rule for repr %u",
			port->repr_id);
		goto fail_rule_add;
	}

	port->mae_rule = mae_rule;

	sfc_log_init(sa, "done");

	return 0;

fail_rule_add:
fail_get_alias:
fail_get_vf:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	return rc;
}

static void
sfc_repr_proxy_mae_rule_remove(struct sfc_adapter *sa,
			       struct sfc_repr_proxy_port *port)
{
	struct sfc_mae_rule *mae_rule = port->mae_rule;

	sfc_mae_rule_del(sa, mae_rule);
}

static int
sfc_repr_proxy_mport_filter_insert(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct sfc_rxq *rxq_ctrl;
	struct sfc_repr_proxy_filter *filter = &rp->mport_filter;
	efx_mport_sel_t mport_alias_selector;
	static const efx_filter_match_flags_t flags[RTE_DIM(filter->specs)] = {
		EFX_FILTER_MATCH_UNKNOWN_UCAST_DST,
		EFX_FILTER_MATCH_UNKNOWN_MCAST_DST };
	unsigned int i;
	int rc;

	sfc_log_init(sa, "entry");

	if (sfc_repr_nb_rxq(sas) == 1) {
		rxq_ctrl = &sa->rxq_ctrl[rp->dp_rxq[0].sw_index];
	} else {
		sfc_err(sa, "multiple representor proxy RxQs not supported");
		rc = ENOTSUP;
		goto fail_multiple_queues;
	}

	rc = efx_mae_mport_by_id(&rp->mport_alias, &mport_alias_selector);
	if (rc != 0) {
		sfc_err(sa, "failed to get repr proxy mport by ID");
		goto fail_get_selector;
	}

	memset(filter->specs, 0, sizeof(filter->specs));
	for (i = 0; i < RTE_DIM(filter->specs); i++) {
		filter->specs[i].efs_priority = EFX_FILTER_PRI_MANUAL;
		filter->specs[i].efs_flags = EFX_FILTER_FLAG_RX;
		filter->specs[i].efs_dmaq_id = rxq_ctrl->hw_index;
		filter->specs[i].efs_match_flags = flags[i] |
				EFX_FILTER_MATCH_MPORT;
		filter->specs[i].efs_ingress_mport = mport_alias_selector.sel;

		rc = efx_filter_insert(sa->nic, &filter->specs[i]);
		if (rc != 0) {
			sfc_err(sa, "failed to insert repr proxy filter");
			goto fail_insert;
		}
	}

	sfc_log_init(sa, "done");

	return 0;

fail_insert:
	while (i-- > 0)
		efx_filter_remove(sa->nic, &filter->specs[i]);

fail_get_selector:
fail_multiple_queues:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	return rc;
}

static void
sfc_repr_proxy_mport_filter_remove(struct sfc_adapter *sa)
{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct sfc_repr_proxy_filter *filter = &rp->mport_filter;
	unsigned int i;

	for (i = 0; i < RTE_DIM(filter->specs); i++)
		efx_filter_remove(sa->nic, &filter->specs[i]);
}

static int
sfc_repr_proxy_port_rule_insert(struct sfc_adapter *sa,
				struct sfc_repr_proxy_port *port)
{
	int rc;

	rc = sfc_repr_proxy_mae_rule_insert(sa, port);
	if (rc != 0)
		goto fail_mae_rule_insert;

	return 0;

fail_mae_rule_insert:
	return rc;
}

static void
sfc_repr_proxy_port_rule_remove(struct sfc_adapter *sa,
				struct sfc_repr_proxy_port *port)
{
	sfc_repr_proxy_mae_rule_remove(sa, port);
}

static int
sfc_repr_proxy_ports_init(struct sfc_adapter *sa)
{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	int rc;

	sfc_log_init(sa, "entry");

	rc = efx_mcdi_mport_alloc_alias(sa->nic, &rp->mport_alias, NULL);
	if (rc != 0) {
		sfc_err(sa, "failed to alloc mport alias: %s",
			rte_strerror(rc));
		goto fail_alloc_mport_alias;
	}

	TAILQ_INIT(&rp->ports);

	sfc_log_init(sa, "done");

	return 0;

fail_alloc_mport_alias:

	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	return rc;
}

void
sfc_repr_proxy_pre_detach(struct sfc_adapter *sa)
{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	bool close_ports[RTE_MAX_ETHPORTS] = {0};
	struct sfc_repr_proxy_port *port;
	unsigned int i;

	SFC_ASSERT(!sfc_adapter_is_locked(sa));

	sfc_adapter_lock(sa);

	if (sfc_repr_available(sfc_sa2shared(sa))) {
		TAILQ_FOREACH(port, &rp->ports, entries)
			close_ports[port->rte_port_id] = true;
	} else {
		sfc_log_init(sa, "representors not supported - skip");
	}

	sfc_adapter_unlock(sa);

	for (i = 0; i < RTE_DIM(close_ports); i++) {
		if (close_ports[i]) {
			rte_eth_dev_stop(i);
			rte_eth_dev_close(i);
		}
	}
}

static void
sfc_repr_proxy_ports_fini(struct sfc_adapter *sa)
{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;

	efx_mae_mport_free(sa->nic, &rp->mport_alias);
}

int
sfc_repr_proxy_attach(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct rte_service_spec service;
	uint32_t cid;
	uint32_t sid;
	int rc;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return 0;
	}

	rc = sfc_repr_proxy_rxq_attach(sa);
	if (rc != 0)
		goto fail_rxq_attach;

	rc = sfc_repr_proxy_txq_attach(sa);
	if (rc != 0)
		goto fail_txq_attach;

	rc = sfc_repr_proxy_ports_init(sa);
	if (rc != 0)
		goto fail_ports_init;

	cid = sfc_get_service_lcore(sa->socket_id);
	if (cid == RTE_MAX_LCORE && sa->socket_id != SOCKET_ID_ANY) {
		/* Warn and try to allocate on any NUMA node */
		sfc_warn(sa,
			"repr proxy: unable to get service lcore at socket %d",
			sa->socket_id);

		cid = sfc_get_service_lcore(SOCKET_ID_ANY);
	}
	if (cid == RTE_MAX_LCORE) {
		rc = ENOTSUP;
		sfc_err(sa, "repr proxy: failed to get service lcore");
		goto fail_get_service_lcore;
	}

	memset(&service, 0, sizeof(service));
	snprintf(service.name, sizeof(service.name),
		 "net_sfc_%hu_repr_proxy", sfc_sa2shared(sa)->port_id);
	service.socket_id = rte_lcore_to_socket_id(cid);
	service.callback = sfc_repr_proxy_routine;
	service.callback_userdata = rp;

	rc = rte_service_component_register(&service, &sid);
	if (rc != 0) {
		rc = ENOEXEC;
		sfc_err(sa, "repr proxy: failed to register service component");
		goto fail_register;
	}

	rc = rte_service_map_lcore_set(sid, cid, 1);
	if (rc != 0) {
		rc = -rc;
		sfc_err(sa, "repr proxy: failed to map lcore");
		goto fail_map_lcore;
	}

	rp->service_core_id = cid;
	rp->service_id = sid;

	sfc_log_init(sa, "done");

	return 0;

fail_map_lcore:
	rte_service_component_unregister(sid);

fail_register:
	/*
	 * No need to rollback service lcore get since
	 * it just makes socket_id based search and remembers it.
	 */

fail_get_service_lcore:
	sfc_repr_proxy_ports_fini(sa);

fail_ports_init:
	sfc_repr_proxy_txq_detach(sa);

fail_txq_attach:
	sfc_repr_proxy_rxq_detach(sa);

fail_rxq_attach:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	return rc;
}

void
sfc_repr_proxy_detach(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return;
	}

	rte_service_map_lcore_set(rp->service_id, rp->service_core_id, 0);
	rte_service_component_unregister(rp->service_id);
	sfc_repr_proxy_ports_fini(sa);
	sfc_repr_proxy_rxq_detach(sa);
	sfc_repr_proxy_txq_detach(sa);

	sfc_log_init(sa, "done");
}

static int
sfc_repr_proxy_do_start_port(struct sfc_adapter *sa,
			   struct sfc_repr_proxy_port *port)
{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	int rc;

	rc = sfc_repr_proxy_port_rule_insert(sa, port);
	if (rc != 0)
		goto fail_filter_insert;

	if (rp->started) {
		rc = sfc_repr_proxy_mbox_send(&rp->mbox, port,
					      SFC_REPR_PROXY_MBOX_START_PORT);
		if (rc != 0) {
			sfc_err(sa, "failed to start proxy port %u",
				port->repr_id);
			goto fail_port_start;
		}
	} else {
		port->started = true;
	}

	return 0;

fail_port_start:
	sfc_repr_proxy_port_rule_remove(sa, port);
fail_filter_insert:
	sfc_err(sa, "%s() failed %s", __func__, rte_strerror(rc));

	return rc;
}

static int
sfc_repr_proxy_do_stop_port(struct sfc_adapter *sa,
			  struct sfc_repr_proxy_port *port)

{
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	int rc;

	if (rp->started) {
		rc = sfc_repr_proxy_mbox_send(&rp->mbox, port,
					      SFC_REPR_PROXY_MBOX_STOP_PORT);
		if (rc != 0) {
			sfc_err(sa, "failed to stop proxy port %u: %s",
				port->repr_id, rte_strerror(rc));
			return rc;
		}
	} else {
		port->started = false;
	}

	sfc_repr_proxy_port_rule_remove(sa, port);

	return 0;
}

static bool
sfc_repr_proxy_port_enabled(struct sfc_repr_proxy_port *port)
{
	return port->rte_port_id != RTE_MAX_ETHPORTS && port->enabled;
}

static bool
sfc_repr_proxy_ports_disabled(struct sfc_repr_proxy *rp)
{
	struct sfc_repr_proxy_port *port;

	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (sfc_repr_proxy_port_enabled(port))
			return false;
	}

	return true;
}

int
sfc_repr_proxy_start(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct sfc_repr_proxy_port *last_port = NULL;
	struct sfc_repr_proxy_port *port;
	int rc;

	sfc_log_init(sa, "entry");

	/* Representor proxy is not started when no representors are started */
	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return 0;
	}

	if (sfc_repr_proxy_ports_disabled(rp)) {
		sfc_log_init(sa, "no started representor ports - skip");
		return 0;
	}

	rc = sfc_repr_proxy_rxq_start(sa);
	if (rc != 0)
		goto fail_rxq_start;

	rc = sfc_repr_proxy_txq_start(sa);
	if (rc != 0)
		goto fail_txq_start;

	rp->nb_txq = sfc_repr_nb_txq(sas);
	rp->nb_rxq = sfc_repr_nb_rxq(sas);

	/* Service core may be in "stopped" state, start it */
	rc = rte_service_lcore_start(rp->service_core_id);
	if (rc != 0 && rc != -EALREADY) {
		rc = -rc;
		sfc_err(sa, "failed to start service core for %s: %s",
			rte_service_get_name(rp->service_id),
			rte_strerror(rc));
		goto fail_start_core;
	}

	/* Run the service */
	rc = rte_service_component_runstate_set(rp->service_id, 1);
	if (rc < 0) {
		rc = -rc;
		sfc_err(sa, "failed to run %s component: %s",
			rte_service_get_name(rp->service_id),
			rte_strerror(rc));
		goto fail_component_runstate_set;
	}
	rc = rte_service_runstate_set(rp->service_id, 1);
	if (rc < 0) {
		rc = -rc;
		sfc_err(sa, "failed to run %s: %s",
			rte_service_get_name(rp->service_id),
			rte_strerror(rc));
		goto fail_runstate_set;
	}

	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (sfc_repr_proxy_port_enabled(port)) {
			rc = sfc_repr_proxy_do_start_port(sa, port);
			if (rc != 0)
				goto fail_start_id;

			last_port = port;
		}
	}

	rc = sfc_repr_proxy_mport_filter_insert(sa);
	if (rc != 0)
		goto fail_mport_filter_insert;

	rp->started = true;

	sfc_log_init(sa, "done");

	return 0;

fail_mport_filter_insert:
fail_start_id:
	if (last_port != NULL) {
		TAILQ_FOREACH(port, &rp->ports, entries) {
			if (sfc_repr_proxy_port_enabled(port)) {
				(void)sfc_repr_proxy_do_stop_port(sa, port);
				if (port == last_port)
					break;
			}
		}
	}

	rte_service_runstate_set(rp->service_id, 0);

fail_runstate_set:
	rte_service_component_runstate_set(rp->service_id, 0);

fail_component_runstate_set:
	/* Service lcore may be shared and we never stop it */

fail_start_core:
	sfc_repr_proxy_txq_stop(sa);

fail_txq_start:
	sfc_repr_proxy_rxq_stop(sa);

fail_rxq_start:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	return rc;
}

void
sfc_repr_proxy_stop(struct sfc_adapter *sa)
{
	struct sfc_adapter_shared * const sas = sfc_sa2shared(sa);
	struct sfc_repr_proxy *rp = &sa->repr_proxy;
	struct sfc_repr_proxy_port *port;
	const unsigned int wait_ms_total =
		SFC_REPR_PROXY_ROUTINE_TERMINATE_TIMEOUT_MS;
	unsigned int i;
	int rc;

	sfc_log_init(sa, "entry");

	if (!sfc_repr_available(sas)) {
		sfc_log_init(sa, "representors not supported - skip");
		return;
	}

	if (sfc_repr_proxy_ports_disabled(rp)) {
		sfc_log_init(sa, "no started representor ports - skip");
		return;
	}

	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (sfc_repr_proxy_port_enabled(port)) {
			rc = sfc_repr_proxy_do_stop_port(sa, port);
			if (rc != 0) {
				sfc_err(sa,
					"failed to stop representor proxy port %u: %s",
					port->repr_id, rte_strerror(rc));
			}
		}
	}

	sfc_repr_proxy_mport_filter_remove(sa);

	rc = rte_service_runstate_set(rp->service_id, 0);
	if (rc < 0) {
		sfc_err(sa, "failed to stop %s: %s",
			rte_service_get_name(rp->service_id),
			rte_strerror(-rc));
	}

	rc = rte_service_component_runstate_set(rp->service_id, 0);
	if (rc < 0) {
		sfc_err(sa, "failed to stop %s component: %s",
			rte_service_get_name(rp->service_id),
			rte_strerror(-rc));
	}

	/* Service lcore may be shared and we never stop it */

	/*
	 * Wait for the representor proxy routine to finish the last iteration.
	 * Give up on timeout.
	 */
	for (i = 0; i < wait_ms_total; i++) {
		if (rte_service_may_be_active(rp->service_id) == 0)
			break;

		rte_delay_ms(1);
	}

	sfc_repr_proxy_rxq_stop(sa);
	sfc_repr_proxy_txq_stop(sa);

	rp->started = false;

	sfc_log_init(sa, "done");
}

int
sfc_repr_proxy_add_port(uint16_t pf_port_id, uint16_t repr_id,
			uint16_t rte_port_id, const efx_mport_sel_t *mport_sel,
			efx_pcie_interface_t intf, uint16_t pf, uint16_t vf)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;
	int rc;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");
	TAILQ_FOREACH(port, &rp->ports, entries) {
		if (port->rte_port_id == rte_port_id) {
			rc = EEXIST;
			sfc_err(sa, "%s() failed: port exists", __func__);
			goto fail_port_exists;
		}
	}

	port = rte_zmalloc("sfc-repr-proxy-port", sizeof(*port),
			   sa->socket_id);
	if (port == NULL) {
		rc = ENOMEM;
		sfc_err(sa, "failed to alloc memory for proxy port");
		goto fail_alloc_port;
	}

	rc = efx_mae_mport_id_by_selector(sa->nic, mport_sel,
					  &port->egress_mport);
	if (rc != 0) {
		sfc_err(sa,
			"failed get MAE mport id by selector (repr_id %u): %s",
			repr_id, rte_strerror(rc));
		goto fail_mport_id;
	}

	port->rte_port_id = rte_port_id;
	port->repr_id = repr_id;

	rc = efx_mcdi_get_client_handle(sa->nic, intf, pf, vf,
					&port->remote_vnic_mcdi_client_handle);
	if (rc != 0) {
		sfc_err(sa, "failed to get the represented VNIC's MCDI handle (repr_id=%u): %s",
			repr_id, rte_strerror(rc));
		goto fail_client_handle;
	}

	if (rp->started) {
		rc = sfc_repr_proxy_mbox_send(&rp->mbox, port,
					      SFC_REPR_PROXY_MBOX_ADD_PORT);
		if (rc != 0) {
			sfc_err(sa, "failed to add proxy port %u",
				port->repr_id);
			goto fail_port_add;
		}
	} else {
		TAILQ_INSERT_TAIL(&rp->ports, port, entries);
	}

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);

	return 0;

fail_port_add:
fail_client_handle:
fail_mport_id:
	rte_free(port);
fail_alloc_port:
fail_port_exists:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	sfc_put_adapter(sa);

	return rc;
}

int
sfc_repr_proxy_del_port(uint16_t pf_port_id, uint16_t repr_id)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;
	int rc;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "failed: no such port");
		rc = ENOENT;
		goto fail_no_port;
	}

	if (rp->started) {
		rc = sfc_repr_proxy_mbox_send(&rp->mbox, port,
					      SFC_REPR_PROXY_MBOX_DEL_PORT);
		if (rc != 0) {
			sfc_err(sa, "failed to remove proxy port %u",
				port->repr_id);
			goto fail_port_remove;
		}
	} else {
		TAILQ_REMOVE(&rp->ports, port, entries);
	}

	rte_free(port);

	sfc_log_init(sa, "done");

	sfc_put_adapter(sa);

	return 0;

fail_port_remove:
fail_no_port:
	sfc_log_init(sa, "failed: %s", rte_strerror(rc));
	sfc_put_adapter(sa);

	return rc;
}

int
sfc_repr_proxy_add_rxq(uint16_t pf_port_id, uint16_t repr_id,
		       uint16_t queue_id, struct rte_ring *rx_ring,
		       struct rte_mempool *mp)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy_rxq *rxq;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port", __func__);
		sfc_put_adapter(sa);
		return ENOENT;
	}

	rxq = &port->rxq[queue_id];
	if (rp->dp_rxq[queue_id].mp != NULL && rp->dp_rxq[queue_id].mp != mp) {
		sfc_err(sa, "multiple mempools per queue are not supported");
		sfc_put_adapter(sa);
		return ENOTSUP;
	}

	rxq->ring = rx_ring;
	rxq->mb_pool = mp;
	rp->dp_rxq[queue_id].mp = mp;
	rp->dp_rxq[queue_id].ref_count++;

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);

	return 0;
}

void
sfc_repr_proxy_del_rxq(uint16_t pf_port_id, uint16_t repr_id,
		       uint16_t queue_id)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy_rxq *rxq;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port", __func__);
		sfc_put_adapter(sa);
		return;
	}

	rxq = &port->rxq[queue_id];

	rxq->ring = NULL;
	rxq->mb_pool = NULL;
	rp->dp_rxq[queue_id].ref_count--;
	if (rp->dp_rxq[queue_id].ref_count == 0)
		rp->dp_rxq[queue_id].mp = NULL;

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);
}

int
sfc_repr_proxy_add_txq(uint16_t pf_port_id, uint16_t repr_id,
		       uint16_t queue_id, struct rte_ring *tx_ring,
		       efx_mport_id_t *egress_mport)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy_txq *txq;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port", __func__);
		sfc_put_adapter(sa);
		return ENOENT;
	}

	txq = &port->txq[queue_id];

	txq->ring = tx_ring;

	*egress_mport = port->egress_mport;

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);

	return 0;
}

void
sfc_repr_proxy_del_txq(uint16_t pf_port_id, uint16_t repr_id,
		       uint16_t queue_id)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy_txq *txq;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port", __func__);
		sfc_put_adapter(sa);
		return;
	}

	txq = &port->txq[queue_id];

	txq->ring = NULL;

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);
}

int
sfc_repr_proxy_start_repr(uint16_t pf_port_id, uint16_t repr_id)
{
	bool proxy_start_required = false;
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;
	int rc;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port", __func__);
		rc = ENOENT;
		goto fail_not_found;
	}

	if (port->enabled) {
		rc = EALREADY;
		sfc_err(sa, "failed: repr %u proxy port already started",
			repr_id);
		goto fail_already_started;
	}

	if (sa->state == SFC_ETHDEV_STARTED) {
		if (sfc_repr_proxy_ports_disabled(rp)) {
			proxy_start_required = true;
		} else {
			rc = sfc_repr_proxy_do_start_port(sa, port);
			if (rc != 0) {
				sfc_err(sa,
					"failed to start repr %u proxy port",
					repr_id);
				goto fail_start_id;
			}
		}
	}

	port->enabled = true;

	if (proxy_start_required) {
		rc = sfc_repr_proxy_start(sa);
		if (rc != 0) {
			sfc_err(sa, "failed to start proxy");
			goto fail_proxy_start;
		}
	}

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);

	return 0;

fail_proxy_start:
	port->enabled = false;

fail_start_id:
fail_already_started:
fail_not_found:
	sfc_err(sa, "failed to start repr %u proxy port: %s", repr_id,
		rte_strerror(rc));
	sfc_put_adapter(sa);

	return rc;
}

int
sfc_repr_proxy_stop_repr(uint16_t pf_port_id, uint16_t repr_id)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy_port *p;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;
	int rc;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	sfc_log_init(sa, "entry");

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port", __func__);
		sfc_put_adapter(sa);
		return ENOENT;
	}

	if (!port->enabled) {
		sfc_log_init(sa, "repr %u proxy port is not started - skip",
			     repr_id);
		sfc_put_adapter(sa);
		return 0;
	}

	if (sa->state == SFC_ETHDEV_STARTED) {
		bool last_enabled = true;

		TAILQ_FOREACH(p, &rp->ports, entries) {
			if (p == port)
				continue;

			if (sfc_repr_proxy_port_enabled(p)) {
				last_enabled = false;
				break;
			}
		}

		rc = 0;
		if (last_enabled)
			sfc_repr_proxy_stop(sa);
		else
			rc = sfc_repr_proxy_do_stop_port(sa, port);

		if (rc != 0) {
			sfc_err(sa,
				"failed to stop representor proxy TxQ %u: %s",
				repr_id, rte_strerror(rc));
			sfc_put_adapter(sa);
			return rc;
		}
	}

	port->enabled = false;

	sfc_log_init(sa, "done");
	sfc_put_adapter(sa);

	return 0;
}

int
sfc_repr_proxy_repr_entity_mac_addr_set(uint16_t pf_port_id, uint16_t repr_id,
					const struct rte_ether_addr *mac_addr)
{
	struct sfc_repr_proxy_port *port;
	struct sfc_repr_proxy *rp;
	struct sfc_adapter *sa;
	int rc;

	sa = sfc_get_adapter_by_pf_port_id(pf_port_id);
	rp = sfc_repr_proxy_by_adapter(sa);

	port = sfc_repr_proxy_find_port(rp, repr_id);
	if (port == NULL) {
		sfc_err(sa, "%s() failed: no such port (repr_id=%u)",
			__func__, repr_id);
		sfc_put_adapter(sa);
		return ENOENT;
	}

	rc = efx_mcdi_client_mac_addr_set(sa->nic,
					  port->remote_vnic_mcdi_client_handle,
					  mac_addr->addr_bytes);
	if (rc != 0) {
		sfc_err(sa, "%s() failed: cannot set MAC address (repr_id=%u): %s",
			__func__, repr_id, rte_strerror(rc));
	}

	sfc_put_adapter(sa);

	return rc;
}
