[2/5] net/enic: add minimal VF representor
diff mbox series

Message ID 20200909135656.18892-3-hyonkim@cisco.com
State Accepted
Delegated to: Ferruh Yigit
Headers show
Series
  • net/enic: add SR-IOV VF representor
Related show

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Hyong Youb Kim (hyonkim) Sept. 9, 2020, 1:56 p.m. UTC
Enable the minimal VF representor without Tx/Rx and flow API support.

1. Enable the standard devarg 'representor'
When the devarg is specified, create VF representor ports.

2. Initialize flowman early during PF probe
Representors require the flowman API from the firmware. Initialize it
before creating VF representors, so probe can detect the flowman
support and fail if not available.

3. Add enic_fm_allocate_switch_domain() to allocate switch domain ID
PFs and VFs on the same VIC adapter can forward packets to each other,
so the switch domain is the physical adapter.

4. Create a vnic_dev lock to serialize concurrent devcmd calls
PF and VF representor ports may invoke devcmd (e.g. dump stats)
simultaneously. As they all share a single PF devcmd instance in the
firmware, use a lock to serialize devcmd calls.

Signed-off-by: Hyong Youb Kim <hyonkim@cisco.com>
Reviewed-by: John Daley <johndale@cisco.com>
---
 drivers/net/enic/enic.h                |  34 ++
 drivers/net/enic/enic_ethdev.c         | 107 ++++++-
 drivers/net/enic/enic_fm_flow.c        |  55 +++-
 drivers/net/enic/enic_main.c           |  41 ++-
 drivers/net/enic/enic_vf_representor.c | 425 +++++++++++++++++++++++++
 drivers/net/enic/meson.build           |   1 +
 6 files changed, 652 insertions(+), 11 deletions(-)
 create mode 100644 drivers/net/enic/enic_vf_representor.c

Patch
diff mbox series

diff --git a/drivers/net/enic/enic.h b/drivers/net/enic/enic.h
index a9545c015..929ea90a9 100644
--- a/drivers/net/enic/enic.h
+++ b/drivers/net/enic/enic.h
@@ -210,8 +210,38 @@  struct enic {
 
 	/* Flow manager API */
 	struct enic_flowman *fm;
+	/* switchdev */
+	uint8_t switchdev_mode;
+	uint16_t switch_domain_id;
+	uint16_t max_vf_id;
+	/*
+	 * Lock to serialize devcmds from PF, VF representors as they all share
+	 * the same PF devcmd instance in firmware.
+	 */
+	rte_spinlock_t devcmd_lock;
+};
+
+struct enic_vf_representor {
+	struct enic enic;
+	struct vnic_enet_config config;
+	struct rte_eth_dev *eth_dev;
+	struct rte_ether_addr mac_addr;
+	struct rte_pci_addr bdf;
+	struct enic *pf;
+	uint16_t switch_domain_id;
+	uint16_t vf_id;
+	int allmulti;
+	int promisc;
 };
 
+#define VF_ENIC_TO_VF_REP(vf_enic) \
+	container_of(vf_enic, struct enic_vf_representor, enic)
+
+static inline int enic_is_vf_rep(struct enic *enic)
+{
+	return !!(enic->rte_dev->data->dev_flags & RTE_ETH_DEV_REPRESENTOR);
+}
+
 /* Compute ethdev's max packet size from MTU */
 static inline uint32_t enic_mtu_to_max_rx_pktlen(uint32_t mtu)
 {
@@ -364,6 +394,10 @@  void enic_pick_rx_handler(struct rte_eth_dev *eth_dev);
 void enic_pick_tx_handler(struct rte_eth_dev *eth_dev);
 void enic_fdir_info(struct enic *enic);
 void enic_fdir_info_get(struct enic *enic, struct rte_eth_fdir_info *stats);
+int enic_vf_representor_init(struct rte_eth_dev *eth_dev, void *init_params);
+int enic_vf_representor_uninit(struct rte_eth_dev *ethdev);
+int enic_fm_allocate_switch_domain(struct enic *pf);
 extern const struct rte_flow_ops enic_flow_ops;
 extern const struct rte_flow_ops enic_fm_flow_ops;
+
 #endif /* _ENIC_H_ */
diff --git a/drivers/net/enic/enic_ethdev.c b/drivers/net/enic/enic_ethdev.c
index ca75919ee..780f746a2 100644
--- a/drivers/net/enic/enic_ethdev.c
+++ b/drivers/net/enic/enic_ethdev.c
@@ -68,6 +68,7 @@  static const struct vic_speed_capa {
 #define ENIC_DEVARG_ENABLE_AVX2_RX "enable-avx2-rx"
 #define ENIC_DEVARG_GENEVE_OPT "geneve-opt"
 #define ENIC_DEVARG_IG_VLAN_REWRITE "ig-vlan-rewrite"
+#define ENIC_DEVARG_REPRESENTOR "representor"
 
 RTE_LOG_REGISTER(enic_pmd_logtype, pmd.net.enic, INFO);
 
@@ -1236,6 +1237,7 @@  static int enic_check_devargs(struct rte_eth_dev *dev)
 		ENIC_DEVARG_ENABLE_AVX2_RX,
 		ENIC_DEVARG_GENEVE_OPT,
 		ENIC_DEVARG_IG_VLAN_REWRITE,
+		ENIC_DEVARG_REPRESENTOR,
 		NULL};
 	struct enic *enic = pmd_priv(dev);
 	struct rte_kvargs *kvlist;
@@ -1266,10 +1268,9 @@  static int enic_check_devargs(struct rte_eth_dev *dev)
 	return 0;
 }
 
-/* Initialize the driver
- * It returns 0 on success.
- */
-static int eth_enicpmd_dev_init(struct rte_eth_dev *eth_dev)
+/* Initialize the driver for PF */
+static int eth_enic_dev_init(struct rte_eth_dev *eth_dev,
+			     void *init_params __rte_unused)
 {
 	struct rte_pci_device *pdev;
 	struct rte_pci_addr *addr;
@@ -1277,7 +1278,6 @@  static int eth_enicpmd_dev_init(struct rte_eth_dev *eth_dev)
 	int err;
 
 	ENICPMD_FUNC_TRACE();
-
 	eth_dev->dev_ops = &enicpmd_eth_dev_ops;
 	eth_dev->rx_pkt_burst = &enic_recv_pkts;
 	eth_dev->tx_pkt_burst = &enic_xmit_pkts;
@@ -1305,19 +1305,108 @@  static int eth_enicpmd_dev_init(struct rte_eth_dev *eth_dev)
 	err = enic_check_devargs(eth_dev);
 	if (err)
 		return err;
-	return enic_probe(enic);
+	err = enic_probe(enic);
+	if (!err && enic->fm) {
+		err = enic_fm_allocate_switch_domain(enic);
+		if (err)
+			ENICPMD_LOG(ERR, "failed to allocate switch domain id");
+	}
+	return err;
+}
+
+static int eth_enic_dev_uninit(struct rte_eth_dev *eth_dev)
+{
+	struct enic *enic = pmd_priv(eth_dev);
+	int err;
+
+	ENICPMD_FUNC_TRACE();
+	eth_dev->device = NULL;
+	eth_dev->intr_handle = NULL;
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return 0;
+	err = rte_eth_switch_domain_free(enic->switch_domain_id);
+	if (err)
+		ENICPMD_LOG(WARNING, "failed to free switch domain: %d", err);
+	return 0;
 }
 
 static int eth_enic_pci_probe(struct rte_pci_driver *pci_drv __rte_unused,
 	struct rte_pci_device *pci_dev)
 {
-	return rte_eth_dev_pci_generic_probe(pci_dev, sizeof(struct enic),
-		eth_enicpmd_dev_init);
+	char name[RTE_ETH_NAME_MAX_LEN];
+	struct rte_eth_devargs eth_da = { .nb_representor_ports = 0 };
+	struct rte_eth_dev *pf_ethdev;
+	struct enic *pf_enic;
+	int i, retval;
+
+	ENICPMD_FUNC_TRACE();
+	if (pci_dev->device.devargs) {
+		retval = rte_eth_devargs_parse(pci_dev->device.devargs->args,
+				&eth_da);
+		if (retval)
+			return retval;
+	}
+	retval = rte_eth_dev_create(&pci_dev->device, pci_dev->device.name,
+		sizeof(struct enic),
+		eth_dev_pci_specific_init, pci_dev,
+		eth_enic_dev_init, NULL);
+	if (retval || eth_da.nb_representor_ports < 1)
+		return retval;
+
+	/* Probe VF representor */
+	pf_ethdev = rte_eth_dev_allocated(pci_dev->device.name);
+	if (pf_ethdev == NULL)
+		return -ENODEV;
+	/* Representors require flowman */
+	pf_enic = pmd_priv(pf_ethdev);
+	if (pf_enic->fm == NULL) {
+		ENICPMD_LOG(ERR, "VF representors require flowman");
+		return -ENOTSUP;
+	}
+	/*
+	 * For now representors imply switchdev, as firmware does not support
+	 * legacy mode SR-IOV
+	 */
+	pf_enic->switchdev_mode = 1;
+	/* Calculate max VF ID before initializing representor*/
+	pf_enic->max_vf_id = 0;
+	for (i = 0; i < eth_da.nb_representor_ports; i++) {
+		pf_enic->max_vf_id = RTE_MAX(pf_enic->max_vf_id,
+					     eth_da.representor_ports[i]);
+	}
+	for (i = 0; i < eth_da.nb_representor_ports; i++) {
+		struct enic_vf_representor representor;
+
+		representor.vf_id = eth_da.representor_ports[i];
+				representor.switch_domain_id =
+			pmd_priv(pf_ethdev)->switch_domain_id;
+		representor.pf = pmd_priv(pf_ethdev);
+		snprintf(name, sizeof(name), "net_%s_representor_%d",
+			pci_dev->device.name, eth_da.representor_ports[i]);
+		retval = rte_eth_dev_create(&pci_dev->device, name,
+			sizeof(struct enic_vf_representor), NULL, NULL,
+			enic_vf_representor_init, &representor);
+		if (retval) {
+			ENICPMD_LOG(ERR, "failed to create enic vf representor %s",
+				    name);
+			return retval;
+		}
+	}
+	return 0;
 }
 
 static int eth_enic_pci_remove(struct rte_pci_device *pci_dev)
 {
-	return rte_eth_dev_pci_generic_remove(pci_dev, NULL);
+	struct rte_eth_dev *ethdev;
+
+	ENICPMD_FUNC_TRACE();
+	ethdev = rte_eth_dev_allocated(pci_dev->device.name);
+	if (!ethdev)
+		return -ENODEV;
+	if (ethdev->data->dev_flags & RTE_ETH_DEV_REPRESENTOR)
+		return rte_eth_dev_destroy(ethdev, enic_vf_representor_uninit);
+	else
+		return rte_eth_dev_destroy(ethdev, eth_enic_dev_uninit);
 }
 
 static struct rte_pci_driver rte_enic_pmd = {
diff --git a/drivers/net/enic/enic_fm_flow.c b/drivers/net/enic/enic_fm_flow.c
index cb08a9317..49eaefdec 100644
--- a/drivers/net/enic/enic_fm_flow.c
+++ b/drivers/net/enic/enic_fm_flow.c
@@ -1073,7 +1073,8 @@  enic_fm_find_vnic(struct enic *enic, const struct rte_pci_addr *addr,
 	args[1] = bdf;
 	rc = vnic_dev_flowman_cmd(enic->vdev, args, 2);
 	if (rc != 0) {
-		ENICPMD_LOG(ERR, "allocating counters rc=%d", rc);
+		/* Expected to fail if BDF is not on the adapter */
+		ENICPMD_LOG(DEBUG, "cannot find vnic handle: rc=%d", rc);
 		return rc;
 	}
 	*handle = args[0];
@@ -2522,6 +2523,58 @@  enic_fm_destroy(struct enic *enic)
 	enic->fm = NULL;
 }
 
+int
+enic_fm_allocate_switch_domain(struct enic *pf)
+{
+	const struct rte_pci_addr *cur_a, *prev_a;
+	struct rte_eth_dev *dev;
+	struct enic *cur, *prev;
+	uint16_t domain_id;
+	uint64_t vnic_h;
+	uint16_t pid;
+	int ret;
+
+	ENICPMD_FUNC_TRACE();
+	if (enic_is_vf_rep(pf))
+		return -EINVAL;
+	cur = pf;
+	cur_a = &RTE_ETH_DEV_TO_PCI(cur->rte_dev)->addr;
+	/* Go through ports and find another PF that is on the same adapter */
+	RTE_ETH_FOREACH_DEV(pid) {
+		dev = &rte_eth_devices[pid];
+		if (!dev_is_enic(dev))
+			continue;
+		if (dev->data->dev_flags & RTE_ETH_DEV_REPRESENTOR)
+			continue;
+		if (dev == cur->rte_dev)
+			continue;
+		/* dev is another PF. Is it on the same adapter? */
+		prev = pmd_priv(dev);
+		prev_a = &RTE_ETH_DEV_TO_PCI(dev)->addr;
+		if (!enic_fm_find_vnic(cur, prev_a, &vnic_h)) {
+			ENICPMD_LOG(DEBUG, "Port %u (PF BDF %x:%x:%x) and port %u (PF BDF %x:%x:%x domain %u) are on the same VIC",
+				cur->rte_dev->data->port_id,
+				cur_a->bus, cur_a->devid, cur_a->function,
+				dev->data->port_id,
+				prev_a->bus, prev_a->devid, prev_a->function,
+				prev->switch_domain_id);
+			cur->switch_domain_id = prev->switch_domain_id;
+			return 0;
+		}
+	}
+	ret = rte_eth_switch_domain_alloc(&domain_id);
+	if (ret) {
+		ENICPMD_LOG(WARNING, "failed to allocate switch domain for device %d",
+			    ret);
+	}
+	cur->switch_domain_id = domain_id;
+	ENICPMD_LOG(DEBUG, "Port %u (PF BDF %x:%x:%x) is the 1st PF on the VIC. Allocated switch domain id %u",
+		    cur->rte_dev->data->port_id,
+		    cur_a->bus, cur_a->devid, cur_a->function,
+		    domain_id);
+	return ret;
+}
+
 const struct rte_flow_ops enic_fm_flow_ops = {
 	.validate = enic_fm_flow_validate,
 	.create = enic_fm_flow_create,
diff --git a/drivers/net/enic/enic_main.c b/drivers/net/enic/enic_main.c
index 7942b0df6..9865642b2 100644
--- a/drivers/net/enic/enic_main.c
+++ b/drivers/net/enic/enic_main.c
@@ -608,7 +608,8 @@  int enic_enable(struct enic *enic)
 		dev_warning(enic, "Init of hash table for clsf failed."\
 			"Flow director feature will not work\n");
 
-	if (enic_fm_init(enic))
+	/* Initialize flowman if not already initialized during probe */
+	if (enic->fm == NULL && enic_fm_init(enic))
 		dev_warning(enic, "Init of flowman failed.\n");
 
 	for (index = 0; index < enic->rq_count; index++) {
@@ -1268,6 +1269,18 @@  int enic_setup_finish(struct enic *enic)
 {
 	enic_init_soft_stats(enic);
 
+	/* switchdev: enable promisc mode on PF */
+	if (enic->switchdev_mode) {
+		vnic_dev_packet_filter(enic->vdev,
+				       0 /* directed  */,
+				       0 /* multicast */,
+				       0 /* broadcast */,
+				       1 /* promisc   */,
+				       0 /* allmulti  */);
+		enic->promisc = 1;
+		enic->allmulti = 0;
+		return 0;
+	}
 	/* Default conf */
 	vnic_dev_packet_filter(enic->vdev,
 		1 /* directed  */,
@@ -1393,6 +1406,11 @@  int enic_set_vlan_strip(struct enic *enic)
 
 int enic_add_packet_filter(struct enic *enic)
 {
+	/* switchdev ignores packet filters */
+	if (enic->switchdev_mode) {
+		ENICPMD_LOG(DEBUG, " switchdev: ignore packet filter");
+		return 0;
+	}
 	/* Args -> directed, multicast, broadcast, promisc, allmulti */
 	return vnic_dev_packet_filter(enic->vdev, 1, 1, 1,
 		enic->promisc, enic->allmulti);
@@ -1785,10 +1803,26 @@  static int enic_dev_init(struct enic *enic)
 		}
 	}
 
+	if (enic_fm_init(enic))
+		dev_warning(enic, "Init of flowman failed.\n");
 	return 0;
 
 }
 
+static void lock_devcmd(void *priv)
+{
+	struct enic *enic = priv;
+
+	rte_spinlock_lock(&enic->devcmd_lock);
+}
+
+static void unlock_devcmd(void *priv)
+{
+	struct enic *enic = priv;
+
+	rte_spinlock_unlock(&enic->devcmd_lock);
+}
+
 int enic_probe(struct enic *enic)
 {
 	struct rte_pci_device *pdev = enic->pdev;
@@ -1864,6 +1898,11 @@  int enic_probe(struct enic *enic)
 		goto err_out_dev_close;
 	}
 
+	/* Use a PF spinlock to serialize devcmd from PF and VF representors */
+	if (enic->switchdev_mode) {
+		rte_spinlock_init(&enic->devcmd_lock);
+		vnic_register_lock(enic->vdev, lock_devcmd, unlock_devcmd);
+	}
 	return 0;
 
 err_out_dev_close:
diff --git a/drivers/net/enic/enic_vf_representor.c b/drivers/net/enic/enic_vf_representor.c
new file mode 100644
index 000000000..bc2d8868e
--- /dev/null
+++ b/drivers/net/enic/enic_vf_representor.c
@@ -0,0 +1,425 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2008-2019 Cisco Systems, Inc.  All rights reserved.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include <rte_bus_pci.h>
+#include <rte_common.h>
+#include <rte_dev.h>
+#include <rte_ethdev_driver.h>
+#include <rte_ethdev_pci.h>
+#include <rte_flow_driver.h>
+#include <rte_kvargs.h>
+#include <rte_pci.h>
+#include <rte_string_fns.h>
+
+#include "enic_compat.h"
+#include "enic.h"
+#include "vnic_dev.h"
+#include "vnic_enet.h"
+#include "vnic_intr.h"
+#include "vnic_cq.h"
+#include "vnic_wq.h"
+#include "vnic_rq.h"
+
+static uint16_t enic_vf_recv_pkts(void *rx_queue __rte_unused,
+				  struct rte_mbuf **rx_pkts __rte_unused,
+				  uint16_t nb_pkts __rte_unused)
+{
+	return 0;
+}
+
+static uint16_t enic_vf_xmit_pkts(void *tx_queue __rte_unused,
+				  struct rte_mbuf **tx_pkts __rte_unused,
+				  uint16_t nb_pkts __rte_unused)
+{
+	return 0;
+}
+
+static int enic_vf_dev_tx_queue_setup(struct rte_eth_dev *eth_dev __rte_unused,
+	uint16_t queue_idx __rte_unused,
+	uint16_t nb_desc __rte_unused,
+	unsigned int socket_id __rte_unused,
+	const struct rte_eth_txconf *tx_conf __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	return 0;
+}
+
+static void enic_vf_dev_tx_queue_release(void *txq __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return;
+}
+
+static int enic_vf_dev_rx_queue_setup(struct rte_eth_dev *eth_dev __rte_unused,
+	uint16_t queue_idx __rte_unused,
+	uint16_t nb_desc __rte_unused,
+	unsigned int socket_id __rte_unused,
+	const struct rte_eth_rxconf *rx_conf __rte_unused,
+	struct rte_mempool *mp __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	return 0;
+}
+
+static void enic_vf_dev_rx_queue_release(void *rxq __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return;
+}
+
+static int enic_vf_dev_configure(struct rte_eth_dev *eth_dev __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	return 0;
+}
+
+static int enic_vf_dev_start(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+	int ret;
+
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+
+	vf = eth_dev->data->dev_private;
+	/* Remove all packet filters so no ingress packets go to VF.
+	 * When PF enables switchdev, it will ensure packet filters
+	 * are removed.  So, this is not technically needed.
+	 */
+	ENICPMD_LOG(DEBUG, "Clear packet filters");
+	ret = vnic_dev_packet_filter(vf->enic.vdev, 0, 0, 0, 0, 0);
+	if (ret) {
+		ENICPMD_LOG(ERR, "Cannot clear packet filters");
+		return ret;
+	}
+	return 0;
+}
+
+static void enic_vf_dev_stop(struct rte_eth_dev *eth_dev __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return;
+}
+
+/*
+ * "close" is no-op for now and solely exists so that rte_eth_dev_close()
+ * can finish its own cleanup without errors.
+ */
+static void enic_vf_dev_close(struct rte_eth_dev *eth_dev __rte_unused)
+{
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return;
+}
+
+static int enic_vf_link_update(struct rte_eth_dev *eth_dev,
+	int wait_to_complete __rte_unused)
+{
+	struct enic_vf_representor *vf;
+	struct rte_eth_link link;
+	struct enic *pf;
+
+	ENICPMD_FUNC_TRACE();
+	vf = eth_dev->data->dev_private;
+	pf = vf->pf;
+	/*
+	 * Link status and speed are same as PF. Update PF status and then
+	 * copy it to VF.
+	 */
+	enic_link_update(pf->rte_dev);
+	rte_eth_linkstatus_get(pf->rte_dev, &link);
+	rte_eth_linkstatus_set(eth_dev, &link);
+	return 0;
+}
+
+static int enic_vf_stats_get(struct rte_eth_dev *eth_dev,
+	struct rte_eth_stats *stats)
+{
+	struct enic_vf_representor *vf;
+	struct vnic_stats *vs;
+	int err;
+
+	ENICPMD_FUNC_TRACE();
+	vf = eth_dev->data->dev_private;
+	/* Get VF stats via PF */
+	err = vnic_dev_stats_dump(vf->enic.vdev, &vs);
+	if (err) {
+		ENICPMD_LOG(ERR, "error in getting stats\n");
+		return err;
+	}
+	stats->ipackets = vs->rx.rx_frames_ok;
+	stats->opackets = vs->tx.tx_frames_ok;
+	stats->ibytes = vs->rx.rx_bytes_ok;
+	stats->obytes = vs->tx.tx_bytes_ok;
+	stats->ierrors = vs->rx.rx_errors + vs->rx.rx_drop;
+	stats->oerrors = vs->tx.tx_errors;
+	stats->imissed = vs->rx.rx_no_bufs;
+	return 0;
+}
+
+static int enic_vf_stats_reset(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+	int err;
+
+	ENICPMD_FUNC_TRACE();
+	vf = eth_dev->data->dev_private;
+	/* Ask PF to clear VF stats */
+	err = vnic_dev_stats_clear(vf->enic.vdev);
+	if (err)
+		ENICPMD_LOG(ERR, "error in clearing stats\n");
+	return err;
+}
+
+static int enic_vf_dev_infos_get(struct rte_eth_dev *eth_dev,
+	struct rte_eth_dev_info *device_info)
+{
+	struct enic_vf_representor *vf;
+	struct enic *pf;
+
+	ENICPMD_FUNC_TRACE();
+	vf = eth_dev->data->dev_private;
+	pf = vf->pf;
+	device_info->max_rx_queues = eth_dev->data->nb_rx_queues;
+	device_info->max_tx_queues = eth_dev->data->nb_tx_queues;
+	device_info->min_rx_bufsize = ENIC_MIN_MTU;
+	/* Max packet size is same as PF */
+	device_info->max_rx_pktlen = enic_mtu_to_max_rx_pktlen(pf->max_mtu);
+	device_info->max_mac_addrs = ENIC_UNICAST_PERFECT_FILTERS;
+	/* No offload capa, RSS, etc. until Tx/Rx handlers are added */
+	device_info->rx_offload_capa = 0;
+	device_info->tx_offload_capa = 0;
+	device_info->switch_info.name =	pf->rte_dev->device->name;
+	device_info->switch_info.domain_id = vf->switch_domain_id;
+	device_info->switch_info.port_id = vf->vf_id;
+	return 0;
+}
+
+static void set_vf_packet_filter(struct enic_vf_representor *vf)
+{
+	/* switchdev: packet filters are ignored */
+	if (vf->enic.switchdev_mode)
+		return;
+	/* Ask PF to apply filters on VF */
+	vnic_dev_packet_filter(vf->enic.vdev, 1 /* unicast */, 1 /* mcast */,
+		1 /* bcast */, vf->promisc, vf->allmulti);
+}
+
+static int enic_vf_promiscuous_enable(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	vf = eth_dev->data->dev_private;
+	vf->promisc = 1;
+	set_vf_packet_filter(vf);
+	return 0;
+}
+
+static int enic_vf_promiscuous_disable(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	vf = eth_dev->data->dev_private;
+	vf->promisc = 0;
+	set_vf_packet_filter(vf);
+	return 0;
+}
+
+static int enic_vf_allmulticast_enable(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	vf = eth_dev->data->dev_private;
+	vf->allmulti = 1;
+	set_vf_packet_filter(vf);
+	return 0;
+}
+
+static int enic_vf_allmulticast_disable(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+
+	ENICPMD_FUNC_TRACE();
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return -E_RTE_SECONDARY;
+	vf = eth_dev->data->dev_private;
+	vf->allmulti = 0;
+	set_vf_packet_filter(vf);
+	return 0;
+}
+
+/*
+ * A minimal set of handlers.
+ * The representor can get/set a small set of VF settings via "proxy" devcmd.
+ * With proxy devcmd, the PF driver basically tells the VIC firmware to
+ * "perform this devcmd on that VF".
+ */
+static const struct eth_dev_ops enic_vf_representor_dev_ops = {
+	.allmulticast_enable  = enic_vf_allmulticast_enable,
+	.allmulticast_disable = enic_vf_allmulticast_disable,
+	.dev_configure        = enic_vf_dev_configure,
+	.dev_infos_get        = enic_vf_dev_infos_get,
+	.dev_start            = enic_vf_dev_start,
+	.dev_stop             = enic_vf_dev_stop,
+	.dev_close            = enic_vf_dev_close,
+	.link_update          = enic_vf_link_update,
+	.promiscuous_enable   = enic_vf_promiscuous_enable,
+	.promiscuous_disable  = enic_vf_promiscuous_disable,
+	.stats_get            = enic_vf_stats_get,
+	.stats_reset          = enic_vf_stats_reset,
+	.rx_queue_setup	      = enic_vf_dev_rx_queue_setup,
+	.rx_queue_release     = enic_vf_dev_rx_queue_release,
+	.tx_queue_setup	      = enic_vf_dev_tx_queue_setup,
+	.tx_queue_release     = enic_vf_dev_tx_queue_release,
+};
+
+static int get_vf_config(struct enic_vf_representor *vf)
+{
+	struct vnic_enet_config *c;
+	struct enic *pf;
+	int switch_mtu;
+	int err;
+
+	c = &vf->config;
+	pf = vf->pf;
+	/* VF MAC */
+	err = vnic_dev_get_mac_addr(vf->enic.vdev, vf->mac_addr.addr_bytes);
+	if (err) {
+		ENICPMD_LOG(ERR, "error in getting MAC address\n");
+		return err;
+	}
+	rte_ether_addr_copy(&vf->mac_addr, vf->eth_dev->data->mac_addrs);
+
+	/* VF MTU per its vNIC setting */
+	err = vnic_dev_spec(vf->enic.vdev,
+			    offsetof(struct vnic_enet_config, mtu),
+			    sizeof(c->mtu), &c->mtu);
+	if (err) {
+		ENICPMD_LOG(ERR, "error in getting MTU\n");
+		return err;
+	}
+	/*
+	 * Blade switch (fabric interconnect) port's MTU. Assume the kernel
+	 * enic driver runs on VF. That driver automatically adjusts its MTU
+	 * according to the switch MTU.
+	 */
+	switch_mtu = vnic_dev_mtu(pf->vdev);
+	vf->eth_dev->data->mtu = c->mtu;
+	if (switch_mtu > c->mtu)
+		vf->eth_dev->data->mtu = RTE_MIN(ENIC_MAX_MTU, switch_mtu);
+	return 0;
+}
+
+int enic_vf_representor_init(struct rte_eth_dev *eth_dev, void *init_params)
+{
+	struct enic_vf_representor *vf, *params;
+	struct rte_pci_device *pdev;
+	struct enic *pf, *vf_enic;
+	struct rte_pci_addr *addr;
+	int ret;
+
+	ENICPMD_FUNC_TRACE();
+	params = init_params;
+	vf = eth_dev->data->dev_private;
+	vf->switch_domain_id = params->switch_domain_id;
+	vf->vf_id = params->vf_id;
+	vf->eth_dev = eth_dev;
+	vf->pf = params->pf;
+	vf->allmulti = 1;
+	vf->promisc = 0;
+	pf = vf->pf;
+	vf->enic.switchdev_mode = pf->switchdev_mode;
+	/* Only switchdev is supported now */
+	RTE_ASSERT(vf->enic.switchdev_mode);
+
+	/* Check for non-existent VFs */
+	pdev = RTE_ETH_DEV_TO_PCI(pf->rte_dev);
+	if (vf->vf_id >= pdev->max_vfs) {
+		ENICPMD_LOG(ERR, "VF ID is invalid. vf_id %u max_vfs %u",
+			    vf->vf_id, pdev->max_vfs);
+		return -ENODEV;
+	}
+
+	eth_dev->device->driver = pf->rte_dev->device->driver;
+	eth_dev->dev_ops = &enic_vf_representor_dev_ops;
+	eth_dev->data->dev_flags |= RTE_ETH_DEV_REPRESENTOR
+		| RTE_ETH_DEV_CLOSE_REMOVE;
+	eth_dev->data->representor_id = vf->vf_id;
+	eth_dev->data->mac_addrs = rte_zmalloc("enic_mac_addr_vf",
+		sizeof(struct rte_ether_addr) *
+		ENIC_UNICAST_PERFECT_FILTERS, 0);
+	if (eth_dev->data->mac_addrs == NULL)
+		return -ENOMEM;
+	/* Use 1 RX queue and 1 TX queue for representor path */
+	eth_dev->data->nb_rx_queues = 1;
+	eth_dev->data->nb_tx_queues = 1;
+	eth_dev->rx_pkt_burst = &enic_vf_recv_pkts;
+	eth_dev->tx_pkt_burst = &enic_vf_xmit_pkts;
+	/* Initial link state copied from PF */
+	eth_dev->data->dev_link = pf->rte_dev->data->dev_link;
+	/* Representor vdev to perform devcmd */
+	vf->enic.vdev = vnic_vf_rep_register(&vf->enic, pf->vdev, vf->vf_id);
+	if (vf->enic.vdev == NULL)
+		return -ENOMEM;
+	ret = vnic_dev_alloc_stats_mem(vf->enic.vdev);
+	if (ret)
+		return ret;
+	/* Get/copy VF vNIC MAC, MTU, etc. into eth_dev */
+	ret = get_vf_config(vf);
+	if (ret)
+		return ret;
+
+	/*
+	 * Calculate VF BDF. The firmware ensures that PF BDF is always
+	 * bus:dev.0, and VF BDFs are dev.1, dev.2, and so on.
+	 */
+	vf->bdf = pdev->addr;
+	vf->bdf.function += vf->vf_id + 1;
+
+	/* Copy a few fields used by enic_fm_flow */
+	vf_enic = &vf->enic;
+	vf_enic->switch_domain_id = vf->switch_domain_id;
+	vf_enic->flow_filter_mode = pf->flow_filter_mode;
+	vf_enic->rte_dev = eth_dev;
+	vf_enic->dev_data = eth_dev->data;
+	LIST_INIT(&vf_enic->flows);
+	LIST_INIT(&vf_enic->memzone_list);
+	rte_spinlock_init(&vf_enic->memzone_list_lock);
+	addr = &vf->bdf;
+	snprintf(vf_enic->bdf_name, ENICPMD_BDF_LENGTH, "%04x:%02x:%02x.%x",
+		 addr->domain, addr->bus, addr->devid, addr->function);
+	return 0;
+}
+
+int enic_vf_representor_uninit(struct rte_eth_dev *eth_dev)
+{
+	struct enic_vf_representor *vf;
+
+	ENICPMD_FUNC_TRACE();
+	vf = eth_dev->data->dev_private;
+	vnic_dev_unregister(vf->enic.vdev);
+	return 0;
+}
diff --git a/drivers/net/enic/meson.build b/drivers/net/enic/meson.build
index 1bd7cc7e1..7f4836d0f 100644
--- a/drivers/net/enic/meson.build
+++ b/drivers/net/enic/meson.build
@@ -14,6 +14,7 @@  sources = files(
 	'enic_main.c',
 	'enic_res.c',
 	'enic_rxtx.c',
+	'enic_vf_representor.c',
 	)
 deps += ['hash']
 includes += include_directories('base')