[nss-clients] Add qca-nss-vlan-manager

Change-Id: I977b26f6d7853735355018e3c9ac35be8d12f688
Signed-off-by: Xiaoping Fan <xfan@codeaurora.org>
diff --git a/Makefile b/Makefile
index 4b4e376..53243f9 100644
--- a/Makefile
+++ b/Makefile
@@ -63,4 +63,7 @@
 obj-y += bridge/
 endif
 
+# Vlan manager
+obj-y += vlan/
+
 obj ?= .
diff --git a/exports/nss_vlan_mgr.h b/exports/nss_vlan_mgr.h
new file mode 100644
index 0000000..2665cde
--- /dev/null
+++ b/exports/nss_vlan_mgr.h
@@ -0,0 +1,44 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+/*
+ * nss_vlan_mgr.h
+ *	vlan manager interface definitions.
+ */
+#ifndef _NSS_VLAN_MGR_H_
+#define _NSS_VLAN_MGR_H_
+
+/*
+ * nss_vlan_mgr_join_bridge()
+ *	update ingress and egress vlan translation rule to use bridge VSI
+ *
+ * @param dev[IN] vlan device which joined in bridge
+ * @param bridge_vsi[IN] bridge VSI to attach to
+ * @return 0 for success, -1 for failure
+ */
+int nss_vlan_mgr_join_bridge(struct net_device *dev, uint32_t bridge_vsi);
+
+/*
+ * nss_vlan_mgr_leave_bridge()
+ *	update ingress and egress vlan translation rule to restore vlan VSI
+ *
+ * @param dev[IN] vlan device which left from bridge
+ * @param bridge_vsi[IN] bridge VSI to detach from
+ * @return 0 for success, -1 for failure
+ */
+int nss_vlan_mgr_leave_bridge(struct net_device *dev, uint32_t bridge_vsi);
+
+#endif /* _NSS_VLAN_MGR_H_ */
diff --git a/vlan/Makefile b/vlan/Makefile
new file mode 100644
index 0000000..18c4317
--- /dev/null
+++ b/vlan/Makefile
@@ -0,0 +1,11 @@
+ccflags-y := -I$(obj)/../exports -I$(obj)/..  -I$(obj)/nss_hal/include
+ccflags-y += -DNSS_CLIENT_BUILD_ID="$(BUILD_ID)"
+
+obj-m += qca-nss-vlan.o
+qca-nss-vlan-objs := nss_vlan_mgr.o
+
+ifeq ($(SoC), ipq807x)
+ccflags-y += -DNSS_VLAN_MGR_PPE_SUPPORT
+endif
+
+ccflags-y += -DNSS_VLAN_MGR_DEBUG_LEVEL=0
diff --git a/vlan/nss_vlan_mgr.c b/vlan/nss_vlan_mgr.c
new file mode 100644
index 0000000..335784c
--- /dev/null
+++ b/vlan/nss_vlan_mgr.c
@@ -0,0 +1,976 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+/*
+ * nss_vlan_mgr.c
+ *	NSS to HLOS vlan Interface manager
+ */
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/proc_fs.h>
+#include <linux/sysctl.h>
+#include <nss_api_if.h>
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+#include <ref/ref_vsi.h>
+#include <fal/fal_portvlan.h>
+#endif
+
+#if (NSS_VLAN_MGR_DEBUG_LEVEL < 1)
+#define nss_vlan_mgr_assert(fmt, args...)
+#else
+#define nss_vlan_mgr_assert(c) BUG_ON(!(c))
+#endif /* NSS_VLAN_MGR_DEBUG_LEVEL */
+
+/*
+ * Compile messages for dynamic enable/disable
+ */
+#if defined(CONFIG_DYNAMIC_DEBUG)
+#define nss_vlan_mgr_warn(s, ...) \
+		pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
+#define nss_vlan_mgr_info(s, ...) \
+		pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
+#define nss_vlan_mgr_trace(s, ...) \
+		pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
+#else /* CONFIG_DYNAMIC_DEBUG */
+/*
+ * Statically compile messages at different levels
+ */
+#if (NSS_VLAN_MGR_DEBUG_LEVEL < 2)
+#define nss_vlan_mgr_warn(s, ...)
+#else
+#define nss_vlan_mgr_warn(s, ...) \
+		pr_warn("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
+#endif
+
+#if (NSS_VLAN_MGR_DEBUG_LEVEL < 3)
+#define nss_vlan_mgr_info(s, ...)
+#else
+#define nss_vlan_mgr_info(s, ...) \
+		pr_notice("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
+#endif
+
+#if (NSS_VLAN_MGR_DEBUG_LEVEL < 4)
+#define nss_vlan_mgr_trace(s, ...)
+#else
+#define nss_vlan_mgr_trace(s, ...) \
+		pr_info("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
+#endif
+#endif /* CONFIG_DYNAMIC_DEBUG */
+
+#define NSS_VLAN_PHY_PORT_MIN 1
+#define NSS_VLAN_PHY_PORT_MAX 6
+#define NSS_VLAN_PHY_PORT_NUM 8
+#define NSS_VLAN_PHY_PORT_CHK(n) ((n) >= NSS_VLAN_PHY_PORT_MIN && (n) <= NSS_VLAN_PHY_PORT_MAX)
+#define NSS_VLAN_MGR_TAG_CNT(v) ((v->parent) ? NSS_VLAN_TYPE_DOUBLE : NSS_VLAN_TYPE_SINGLE)
+#define NSS_VLAN_TPID_SHIFT 16
+#define NSS_VLAN_PORT_ROLE_CHANGED 1
+
+/*
+ * vlan client context
+ */
+struct nss_vlan_mgr_context {
+	int ctpid;				/* Customer TPID */
+	int stpid;				/* Service TPID */
+	int port_role[NSS_VLAN_PHY_PORT_NUM];	/* Role of physical ports */
+	struct list_head list;			/* List of vlan private instance */
+	spinlock_t lock;			/* Lock to protect vlan private instance */
+	struct ctl_table_header *sys_hdr;	/* "/pro/sys/nss/vlan_client" directory */
+} vlan_mgr_ctx;
+
+/*
+ * vlan manager private structure
+ */
+struct nss_vlan_pvt {
+	struct list_head list;			/* list head */
+	struct nss_vlan_pvt *parent;		/* parent vlan instance */
+	uint32_t nss_if;			/* nss interface number of this vlan device */
+
+	/*
+	 * Fields for Linux information
+	 */
+	int ifindex;				/* netdev ifindex */
+	int32_t port;				/* real physical port of this vlan device */
+	uint32_t vid;				/* vid info */
+	uint32_t tpid;				/* tpid info */
+	uint32_t mtu;				/* mtu info */
+	uint8_t dev_addr[ETH_ALEN];		/* mac address */
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	/*
+	 * Fields for PPE information
+	 */
+	uint32_t ppe_vsi;			/* VLAN VSI info */
+	uint32_t bridge_vsi;			/* Bridge's VSI when vlan is a member of a bridge */
+	uint32_t ppe_cvid;			/* ppe_cvid info */
+	uint32_t ppe_svid;			/* ppe_svid info */
+	fal_vlan_trans_entry_t eg_xlt_entry;	/* VLAN translation entry */
+#endif
+	int refs;				/* reference count */
+};
+
+/*
+ * nss_vlan_mgr_instance_find_and_ref()
+ */
+static struct nss_vlan_pvt *nss_vlan_mgr_instance_find_and_ref(
+						struct net_device *dev)
+{
+	struct nss_vlan_pvt *v;
+
+	if (!is_vlan_dev(dev)) {
+		return NULL;
+	}
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	list_for_each_entry(v, &vlan_mgr_ctx.list, list) {
+		if (v->ifindex == dev->ifindex) {
+			v->refs++;
+			spin_unlock(&vlan_mgr_ctx.lock);
+			return v;
+		}
+	}
+	spin_unlock(&vlan_mgr_ctx.lock);
+
+	return NULL;
+}
+
+/*
+ * nss_vlan_mgr_instance_deref()
+ */
+static void nss_vlan_mgr_instance_deref(struct nss_vlan_pvt *v)
+{
+	spin_lock(&vlan_mgr_ctx.lock);
+	BUG_ON(!(--v->refs));
+	spin_unlock(&vlan_mgr_ctx.lock);
+}
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+/*
+ * nss_vlan_mgr_calculate_new_port_role()
+ *	check if we can change this port to edge port
+ */
+static bool nss_vlan_mgr_calculate_new_port_role(int32_t port)
+{
+	struct nss_vlan_pvt *vif;
+	bool to_edge_port = true;
+
+	if (vlan_mgr_ctx.port_role[port] == FAL_QINQ_EDGE_PORT)
+		return false;
+
+	if (vlan_mgr_ctx.ctpid != vlan_mgr_ctx.stpid)
+		return false;
+
+	/*
+	 * If no other double VLAN interface on the same physcial port,
+	 * we set physical port as edge port
+	 */
+	spin_lock(&vlan_mgr_ctx.lock);
+	list_for_each_entry(vif, &vlan_mgr_ctx.list, list) {
+		if ((vif->port == port) && (vif->parent)) {
+			to_edge_port = false;
+			break;
+		}
+	}
+	spin_unlock(&vlan_mgr_ctx.lock);
+
+	if (to_edge_port) {
+		fal_port_qinq_role_t mode;
+
+		mode.mask = FAL_PORT_QINQ_ROLE_INGRESS_EN | FAL_PORT_QINQ_ROLE_EGRESS_EN;
+		mode.ingress_port_role = FAL_QINQ_EDGE_PORT;
+		mode.egress_port_role = FAL_QINQ_EDGE_PORT;
+
+		if (fal_port_qinq_mode_set(0, port, &mode)) {
+			nss_vlan_mgr_warn("failed to set %d as edge port\n", port);
+			return false;
+		}
+
+		vlan_mgr_ctx.port_role[port] = FAL_QINQ_EDGE_PORT;
+	}
+
+	return to_edge_port;
+}
+
+/*
+ * nss_vlan_mgr_port_role_update()
+ */
+static void nss_vlan_mgr_port_role_update(struct nss_vlan_pvt *vif, uint32_t new_ppe_cvid, uint32_t new_ppe_svid)
+{
+	/*
+	 * Delete old ingress vlan translation rule
+	 */
+	if (ppe_port_vlan_vsi_set(0, vif->port, vif->ppe_svid, vif->ppe_cvid, 0xffff)) {
+		nss_vlan_mgr_warn("Failed to delete old ingress vlan translation rule of port %d\n", vif->port);
+		return;
+	}
+
+	/*
+	 * Delete old egress vlan translation rule
+	 */
+	if (fal_port_vlan_trans_del(0, vif->port, &vif->eg_xlt_entry)) {
+		nss_vlan_mgr_warn("Failed to delete old egress vlan translation of port %d\n", vif->port);
+		return;
+	}
+
+	/*
+	 * Update ppe_civd and ppe_svid
+	 */
+	vif->ppe_cvid = new_ppe_cvid;
+	vif->ppe_svid = new_ppe_svid;
+
+	/*
+	 * Add new ingress vlan translation rule
+	 */
+	if (ppe_port_vlan_vsi_set(0, vif->port, vif->ppe_svid, vif->ppe_cvid,
+				  (vif->bridge_vsi ? vif->bridge_vsi : vif->ppe_vsi))) {
+		nss_vlan_mgr_warn("Failed to update ingress vlan translation of port %d\n", vif->port);
+		return;
+	}
+
+	/*
+	 * Add new egress vlan translation rule
+	 */
+	vif->eg_xlt_entry.cvid_xlt_cmd = (vif->ppe_cvid == 0xffff) ? 0 : FAL_VID_XLT_CMD_ADDORREPLACE;
+	vif->eg_xlt_entry.cvid_xlt = vif->ppe_cvid;
+	vif->eg_xlt_entry.svid_xlt_cmd = (vif->ppe_svid == 0xffff) ? 0 : FAL_VID_XLT_CMD_ADDORREPLACE;
+	vif->eg_xlt_entry.svid_xlt = vif->ppe_svid;
+
+	if (fal_port_vlan_trans_add(0, vif->port, &vif->eg_xlt_entry)) {
+		nss_vlan_mgr_warn("Failed to update egress vlan translation of port %d\n", vif->port);
+	}
+}
+
+/*
+ * nss_vlan_mgr_port_role_event()
+ */
+static void nss_vlan_mgr_port_role_event(int32_t port)
+{
+	struct nss_vlan_pvt *vif;
+
+	if (vlan_mgr_ctx.ctpid != vlan_mgr_ctx.stpid)
+		return;
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	list_for_each_entry(vif, &vlan_mgr_ctx.list, list) {
+		if ((vif->port == port) && (!vif->parent)) {
+			if ((vlan_mgr_ctx.port_role[port] == FAL_QINQ_EDGE_PORT) &&
+			    (vif->vid != vif->ppe_cvid)) {
+				nss_vlan_mgr_port_role_update(vif, vif->vid, 0xffff);
+			}
+
+			if ((vlan_mgr_ctx.port_role[port] == FAL_QINQ_CORE_PORT) &&
+			    (vif->vid != vif->ppe_svid)) {
+				nss_vlan_mgr_port_role_update(vif, 0xffff, vif->vid);
+			}
+		}
+	}
+	spin_unlock(&vlan_mgr_ctx.lock);
+}
+
+/*
+ * nss_vlan_mgr_configure_ppe()
+ */
+static int nss_vlan_mgr_configure_ppe(struct nss_vlan_pvt *v, struct net_device *dev)
+{
+	uint32_t vsi;
+	int ret = 0;
+
+	if (ppe_vsi_alloc(0, &vsi)) {
+		nss_vlan_mgr_warn("%s: failed to allocate VSI for vlan device", dev->name);
+		return -1;
+	}
+
+	if (nss_vlan_tx_vsi_attach_msg(v->nss_if, vsi) != NSS_TX_SUCCESS) {
+		nss_vlan_mgr_warn("%s: failed to attach VSI to vlan interface\n", dev->name);
+		goto free_vsi;
+	}
+
+	/*
+	 * Calculate ppe cvid and svid
+	 */
+	if (NSS_VLAN_MGR_TAG_CNT(v) == NSS_VLAN_TYPE_DOUBLE) {
+		v->ppe_cvid = v->vid;
+		v->ppe_svid = v->parent->vid;
+	} else {
+		if (((vlan_mgr_ctx.ctpid != vlan_mgr_ctx.stpid) && (v->tpid == vlan_mgr_ctx.ctpid)) ||
+		    ((vlan_mgr_ctx.ctpid == vlan_mgr_ctx.stpid) && (vlan_mgr_ctx.port_role[v->port] == FAL_QINQ_EDGE_PORT))) {
+			v->ppe_cvid = v->vid;
+			v->ppe_svid = 0xffff;
+		} else {
+			v->ppe_cvid = 0xffff;
+			v->ppe_svid = v->vid;
+		}
+	}
+
+	/*
+	 * Add ingress vlan translation rule
+	 */
+	if (ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, vsi)) {
+		nss_vlan_mgr_warn("%s: failed to set ingress vlan translation\n", dev->name);
+		goto detach_vsi;
+	}
+
+	/*
+	 * Add egress vlan translation rule
+	 */
+	memset(&v->eg_xlt_entry, 0, sizeof(v->eg_xlt_entry));
+
+	/*
+	 * Fields for match
+	 */
+	v->eg_xlt_entry.trans_direction = 1;		/* Egress vlan translation rule*/
+	v->eg_xlt_entry.vsi_valid = true;		/* Use vsi as search key*/
+	v->eg_xlt_entry.vsi_enable = true;		/* Use vsi as search key*/
+	v->eg_xlt_entry.vsi = vsi;			/* Use vsi as search key*/
+	v->eg_xlt_entry.s_tagged = 0x7;			/* Accept tagged/untagged/priority tagged svlan */
+	v->eg_xlt_entry.c_tagged = 0x7;			/* Accept tagged/untagged/priority tagged cvlan */
+	v->eg_xlt_entry.port_bitmap = (1 << v->port);	/* Use port as search key*/
+
+	/*
+	 * Fields for action
+	 */
+	v->eg_xlt_entry.cvid_xlt_cmd = (v->ppe_cvid == 0xffff) ? 0 : FAL_VID_XLT_CMD_ADDORREPLACE;
+	v->eg_xlt_entry.cvid_xlt = v->ppe_cvid;
+	v->eg_xlt_entry.svid_xlt_cmd = (v->ppe_svid == 0xffff) ? 0 : FAL_VID_XLT_CMD_ADDORREPLACE;
+	v->eg_xlt_entry.svid_xlt = v->ppe_svid;
+
+	if (fal_port_vlan_trans_add(0, v->port, &v->eg_xlt_entry)) {
+		nss_vlan_mgr_warn("%s: failed to set egress vlan translation\n", dev->name);
+		goto delete_ingress_rule;
+	}
+
+	if ((v->ppe_svid != 0xffff) && (vlan_mgr_ctx.port_role[v->port] != FAL_QINQ_CORE_PORT)) {
+		fal_port_qinq_role_t mode;
+
+		/*
+		 * If double tag, we should set physical port as core port
+		 */
+		vlan_mgr_ctx.port_role[v->port] = FAL_QINQ_CORE_PORT;
+
+		/*
+		 * Update port role in PPE
+		 */
+		mode.mask = 0x3;
+		mode.ingress_port_role = FAL_QINQ_CORE_PORT;
+		mode.egress_port_role = FAL_QINQ_CORE_PORT;
+
+		if (fal_port_qinq_mode_set(0, v->port, &mode)) {
+			nss_vlan_mgr_warn("%s: failed to set %d as core port\n", dev->name, v->port);
+			goto delete_egress_rule;
+		}
+
+		ret = NSS_VLAN_PORT_ROLE_CHANGED;
+	}
+
+	v->ppe_vsi = vsi;
+	return ret;
+
+delete_egress_rule:
+	if (fal_port_vlan_trans_del(0, v->port, &v->eg_xlt_entry)) {
+		nss_vlan_mgr_warn("%p: Failed to delete egress translation rule\n", v);
+	}
+
+delete_ingress_rule:
+	if (ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, 0xffff)) {
+		nss_vlan_mgr_warn("%p: Failed to delete ingress translation rule\n", v);
+	}
+
+detach_vsi:
+	if (nss_vlan_tx_vsi_detach_msg(v->nss_if, vsi)) {
+		nss_vlan_mgr_warn("%p: Failed to detach vsi %d\n", v, vsi);
+	}
+
+free_vsi:
+	if (ppe_vsi_free(0, vsi)) {
+		nss_vlan_mgr_warn("%p: Failed to free VLAN VSI\n", v);
+	}
+
+	return -1;
+}
+#endif
+
+/*
+ * nss_vlan_mgr_create_instance()
+ */
+static struct nss_vlan_pvt *nss_vlan_mgr_create_instance(
+						struct net_device *dev)
+{
+	struct nss_vlan_pvt *v;
+	struct vlan_dev_priv *vlan;
+	struct net_device *real_dev;
+
+	if (!is_vlan_dev(dev)) {
+		return NULL;
+	}
+
+	v = kzalloc(sizeof(*v), GFP_KERNEL);
+	if (!v) {
+		return NULL;
+	}
+
+	INIT_LIST_HEAD(&v->list);
+
+	vlan = vlan_dev_priv(dev);
+	real_dev = vlan->real_dev;
+	v->vid = vlan->vlan_id;
+	v->tpid = ntohs(vlan->vlan_proto);
+	v->parent = nss_vlan_mgr_instance_find_and_ref(real_dev);
+	if (!v->parent) {
+		v->port = nss_cmn_get_interface_number_by_dev(real_dev);
+		if (!NSS_VLAN_PHY_PORT_CHK(v->port)) {
+			nss_vlan_mgr_warn("%s: %d is not valid physical port\n", dev->name, v->port);
+			kfree(v);
+			return NULL;
+		}
+	} else if (!v->parent->parent) {
+		v->port = v->parent->port;
+	} else {
+		nss_vlan_mgr_warn("%s: don't support more than 2 vlans\n", dev->name);
+		nss_vlan_mgr_instance_deref(v->parent);
+		kfree(v);
+		return NULL;
+	}
+
+	/*
+	 * Check if TPID is permited
+	 */
+	if ((NSS_VLAN_MGR_TAG_CNT(v) == NSS_VLAN_TYPE_DOUBLE) &&
+	    ((v->tpid != vlan_mgr_ctx.ctpid) || (v->parent->tpid != vlan_mgr_ctx.stpid))) {
+		nss_vlan_mgr_warn("%s: double tag: tpid %04x not match global tpid(%04x, %04x)\n",
+				  dev->name, v->tpid, vlan_mgr_ctx.ctpid, vlan_mgr_ctx.stpid);
+		nss_vlan_mgr_instance_deref(v->parent);
+		kfree(v);
+		return NULL;
+	}
+
+	if ((NSS_VLAN_MGR_TAG_CNT(v) == NSS_VLAN_TYPE_SINGLE) &&
+	    ((v->tpid != vlan_mgr_ctx.ctpid) && (v->tpid != vlan_mgr_ctx.stpid))) {
+		nss_vlan_mgr_warn("%s: single tag: tpid %04x not match global tpid(%04x, %04x)\n",
+				  dev->name, v->tpid, vlan_mgr_ctx.ctpid, vlan_mgr_ctx.stpid);
+		kfree(v);
+		return NULL;
+	}
+
+	v->mtu = dev->mtu;
+	ether_addr_copy(v->dev_addr, dev->dev_addr);
+	v->ifindex = dev->ifindex;
+	v->refs = 1;
+
+	return v;
+}
+
+/*
+ * nss_vlan_mgr_instance_free()
+ */
+static void nss_vlan_mgr_instance_free(struct nss_vlan_pvt *v)
+{
+	spin_lock(&vlan_mgr_ctx.lock);
+	BUG_ON(--v->refs);
+	if (!list_empty(&v->list)) {
+		list_del(&v->list);
+	}
+	spin_unlock(&vlan_mgr_ctx.lock);
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	if (v->ppe_vsi) {
+		/*
+		 * Detach VSI
+		 */
+		if (nss_vlan_tx_vsi_detach_msg(v->nss_if, v->ppe_vsi)) {
+			nss_vlan_mgr_warn("%p: Failed to detach vsi %d\n", v, v->ppe_vsi);
+		}
+
+		/*
+		 * Delete ingress vlan translation rule
+		 */
+		if (ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, 0xffff)) {
+			nss_vlan_mgr_warn("%p: Failed to delete old ingress translation rule\n", v);
+		}
+
+		/*
+		 * Delete egress vlan translation rule
+		 */
+		if (fal_port_vlan_trans_del(0, v->port, &v->eg_xlt_entry)) {
+			nss_vlan_mgr_warn("%p: Failed to delete vlan translation rule\n", v);
+		}
+
+		/*
+		 * Free PPE VSI
+		 */
+		if (ppe_vsi_free(0, v->ppe_vsi)) {
+			nss_vlan_mgr_warn("%p: Failed to free VLAN VSI\n", v);
+		}
+	}
+
+	if (nss_vlan_mgr_calculate_new_port_role(v->port)) {
+		nss_vlan_mgr_port_role_event(v->port);
+	}
+#endif
+
+	if (v->nss_if) {
+		nss_unregister_vlan_if(v->nss_if);
+		if (nss_dynamic_interface_dealloc_node(v->nss_if, NSS_DYNAMIC_INTERFACE_TYPE_VLAN) != NSS_TX_SUCCESS) {
+			nss_vlan_mgr_warn("%p: Failed to dealloc vlan dynamic interface\n", v);
+		}
+	}
+
+	if (v->parent)
+		nss_vlan_mgr_instance_deref(v->parent);
+
+	kfree(v);
+}
+
+/*
+ * nss_vlan_mgr_changemtu_event()
+ */
+static int nss_vlan_mgr_changemtu_event(struct netdev_notifier_info *info)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(info);
+	struct nss_vlan_pvt *v_pvt = nss_vlan_mgr_instance_find_and_ref(dev);
+
+	if (!v_pvt)
+		return NOTIFY_DONE;
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	if (v_pvt->mtu == dev->mtu) {
+		spin_unlock(&vlan_mgr_ctx.lock);
+		nss_vlan_mgr_instance_deref(v_pvt);
+		return NOTIFY_DONE;
+	}
+	spin_unlock(&vlan_mgr_ctx.lock);
+
+	if (nss_vlan_tx_set_mtu_msg(v_pvt->nss_if, dev->mtu) != NSS_TX_SUCCESS) {
+		nss_vlan_mgr_warn("%s: Failed to send change MTU(%d) message to NSS\n", dev->name, dev->mtu);
+		nss_vlan_mgr_instance_deref(v_pvt);
+		return NOTIFY_BAD;
+	}
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	v_pvt->mtu = dev->mtu;
+	spin_unlock(&vlan_mgr_ctx.lock);
+	nss_vlan_mgr_trace("%s: MTU changed to %d, NSS updated\n", dev->name, dev->mtu);
+	nss_vlan_mgr_instance_deref(v_pvt);
+	return NOTIFY_DONE;
+}
+
+/*
+ * int nss_vlan_mgr_changeaddr_event()
+ */
+static int nss_vlan_mgr_changeaddr_event(struct netdev_notifier_info *info)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(info);
+	struct nss_vlan_pvt *v_pvt = nss_vlan_mgr_instance_find_and_ref(dev);
+
+	if (!v_pvt)
+		return NOTIFY_DONE;
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	if (!memcmp(v_pvt->dev_addr, dev->dev_addr, ETH_ALEN)) {
+		spin_unlock(&vlan_mgr_ctx.lock);
+		nss_vlan_mgr_instance_deref(v_pvt);
+		return NOTIFY_DONE;
+	}
+	spin_unlock(&vlan_mgr_ctx.lock);
+
+	if (nss_vlan_tx_set_mac_addr_msg(v_pvt->nss_if, dev->dev_addr) != NSS_TX_SUCCESS) {
+		nss_vlan_mgr_warn("%s: Failed to send change MAC address message to NSS\n", dev->name);
+		nss_vlan_mgr_instance_deref(v_pvt);
+		return NOTIFY_BAD;
+	}
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	ether_addr_copy(v_pvt->dev_addr, dev->dev_addr);
+	spin_unlock(&vlan_mgr_ctx.lock);
+	nss_vlan_mgr_trace("%s: MAC changed to %pM, updated NSS\n", dev->name, dev->dev_addr);
+	nss_vlan_mgr_instance_deref(v_pvt);
+	return NOTIFY_DONE;
+}
+
+/*
+ * nss_vlan_mgr_register_event()
+ */
+static int nss_vlan_mgr_register_event(struct netdev_notifier_info *info)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(info);
+	struct nss_vlan_pvt *v;
+	int if_num, ret;
+	uint32_t vlan_tag;
+
+	v = nss_vlan_mgr_create_instance(dev);
+	if (!v)
+		return NOTIFY_DONE;
+
+	if_num = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_VLAN);
+	if (if_num < 0) {
+		nss_vlan_mgr_warn("%s: failed to alloc NSS dynamic interface\n", dev->name);
+		nss_vlan_mgr_instance_free(v);
+		return NOTIFY_DONE;
+	}
+
+	if (!nss_register_vlan_if(if_num, NULL, dev, 0, v)) {
+		nss_vlan_mgr_warn("%s: failed to register NSS dynamic interface", dev->name);
+		if (nss_dynamic_interface_dealloc_node(if_num, NSS_DYNAMIC_INTERFACE_TYPE_VLAN) != NSS_TX_SUCCESS) {
+			nss_vlan_mgr_warn("%p: Failed to dealloc vlan dynamic interface\n", v);
+		}
+		nss_vlan_mgr_instance_free(v);
+		return NOTIFY_DONE;
+	}
+	v->nss_if = if_num;
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	ret = nss_vlan_mgr_configure_ppe(v, dev);
+	if (ret < 0) {
+		nss_vlan_mgr_instance_free(v);
+		return NOTIFY_DONE;
+	}
+#endif
+
+	if (nss_vlan_tx_set_mac_addr_msg(v->nss_if, v->dev_addr) != NSS_TX_SUCCESS) {
+		nss_vlan_mgr_warn("%s: failed to set mac_addr msg\n", dev->name);
+		nss_vlan_mgr_instance_free(v);
+		return NOTIFY_DONE;
+	}
+
+	if (nss_vlan_tx_set_mtu_msg(v->nss_if, v->mtu) != NSS_TX_SUCCESS) {
+		nss_vlan_mgr_warn("%s: failed to set mtu msg\n", dev->name);
+		nss_vlan_mgr_instance_free(v);
+		return NOTIFY_DONE;
+	}
+
+	vlan_tag = (v->tpid << NSS_VLAN_TPID_SHIFT | v->vid);
+	if (nss_vlan_tx_add_tag_msg(v->nss_if, vlan_tag, (v->parent ? v->parent->nss_if : v->port), v->port) != NSS_TX_SUCCESS) {
+		nss_vlan_mgr_warn("%s: failed to add vlan in nss\n", dev->name);
+		nss_vlan_mgr_instance_free(v);
+		return NOTIFY_DONE;
+	}
+
+	spin_lock(&vlan_mgr_ctx.lock);
+	list_add(&v->list, &vlan_mgr_ctx.list);
+	spin_unlock(&vlan_mgr_ctx.lock);
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	if (ret == NSS_VLAN_PORT_ROLE_CHANGED)
+		nss_vlan_mgr_port_role_event(v->port);
+#endif
+	return NOTIFY_DONE;
+}
+
+/*
+ * nss_vlan_mgr_unregister_event()
+ */
+static int nss_vlan_mgr_unregister_event(struct netdev_notifier_info *info)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(info);
+	struct nss_vlan_pvt *v = nss_vlan_mgr_instance_find_and_ref(dev);
+
+	/*
+	 * Do we have it on record?
+	 */
+	if (!v)
+		return NOTIFY_DONE;
+
+	nss_vlan_mgr_trace("Vlan %s unregsitered. Freeing NSS dynamic interface %d\n", dev->name, v->nss_if);
+
+	/*
+	 * Release reference got by "nss_vlan_mgr_instance_find_and_ref"
+	 */
+	nss_vlan_mgr_instance_deref(v);
+
+	/*
+	 * Free instance
+	 */
+	nss_vlan_mgr_instance_free(v);
+
+	return NOTIFY_DONE;
+}
+
+/*
+ * nss_vlan_mgr_netdevice_event()
+ */
+static int nss_vlan_mgr_netdevice_event(struct notifier_block *unused,
+				unsigned long event, void *ptr)
+{
+	struct netdev_notifier_info *info = (struct netdev_notifier_info *)ptr;
+
+	switch (event) {
+	case NETDEV_CHANGEADDR:
+		return nss_vlan_mgr_changeaddr_event(info);
+	case NETDEV_CHANGEMTU:
+		return nss_vlan_mgr_changemtu_event(info);
+	case NETDEV_REGISTER:
+		return nss_vlan_mgr_register_event(info);
+	case NETDEV_UNREGISTER:
+		return nss_vlan_mgr_unregister_event(info);
+	}
+
+	/*
+	 * Notify done for all the events we don't care
+	 */
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block nss_vlan_mgr_netdevice_nb __read_mostly = {
+	.notifier_call = nss_vlan_mgr_netdevice_event,
+};
+
+/*
+ * nss_vlan_mgr_join_bridge()
+ *	update ingress and egress vlan translation rule to use bridge VSI
+ */
+int nss_vlan_mgr_join_bridge(struct net_device *dev, uint32_t bridge_vsi)
+{
+	struct nss_vlan_pvt *v = nss_vlan_mgr_instance_find_and_ref(dev);
+
+	if (!v)
+		return 0;
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	if ((v->bridge_vsi == bridge_vsi) || v->bridge_vsi) {
+		nss_vlan_mgr_warn("%s is already in bridge VSI %d, can't change to %d\n",
+				  dev->name, v->bridge_vsi, bridge_vsi);
+		nss_vlan_mgr_instance_deref(v);
+		return 0;
+	}
+
+	/*
+	 * Delete old ingress vlan translation rule
+	 */
+	ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, 0xffff);
+
+	/*
+	 * Delete old egress vlan translation rule
+	 */
+	fal_port_vlan_trans_del(0, v->port, &v->eg_xlt_entry);
+
+	/*
+	 * Add new ingress vlan translation rule to use bridge VSI
+	 */
+	if (ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, bridge_vsi)) {
+		nss_vlan_mgr_warn("%s: failed to change ingress vlan translation\n", dev->name);
+		nss_vlan_mgr_instance_deref(v);
+		return -1;
+	}
+
+	/*
+	 * Add new egress vlan translation rule to use bridge VSI
+	 */
+	v->eg_xlt_entry.vsi = bridge_vsi;
+	if (fal_port_vlan_trans_add(0, v->port, &v->eg_xlt_entry)) {
+		nss_vlan_mgr_warn("%s: failed to change egress vlan translation\n", dev->name);
+		nss_vlan_mgr_instance_deref(v);
+		return -1;
+	}
+
+	v->bridge_vsi = bridge_vsi;
+#endif
+	nss_vlan_mgr_instance_deref(v);
+	return 0;
+}
+EXPORT_SYMBOL(nss_vlan_mgr_join_bridge);
+
+/*
+ * nss_vlan_mgr_leave_bridge()
+ *	update ingress and egress vlan translation rule to restore vlan VSI
+ */
+int nss_vlan_mgr_leave_bridge(struct net_device *dev, uint32_t bridge_vsi)
+{
+	struct nss_vlan_pvt *v = nss_vlan_mgr_instance_find_and_ref(dev);
+
+	if (!v)
+		return 0;
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	if (v->bridge_vsi != bridge_vsi) {
+		nss_vlan_mgr_warn("%s is not in bridge VSI %d, ignore\n", dev->name, bridge_vsi);
+		nss_vlan_mgr_instance_deref(v);
+		return 0;
+	}
+
+	/*
+	 * Delete old ingress vlan translation rule
+	 */
+	ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, 0xffff);
+
+	/*
+	 * Delete old egress vlan translation rule
+	 */
+	fal_port_vlan_trans_del(0, v->port, &v->eg_xlt_entry);
+
+	/*
+	 * Record this change
+	 */
+	v->bridge_vsi = 0;
+
+	/*
+	 * Add new ingress vlan translation rule to use vlan VSI
+	 */
+	if (ppe_port_vlan_vsi_set(0, v->port, v->ppe_svid, v->ppe_cvid, v->ppe_vsi)) {
+		nss_vlan_mgr_warn("%s: failed to change ingress vlan translation\n", dev->name);
+		nss_vlan_mgr_instance_deref(v);
+		return -1;
+	}
+
+	/*
+	 * Add new egress vlan translation rule to use vlan VSI
+	 */
+	v->eg_xlt_entry.vsi = v->ppe_vsi;
+	if (fal_port_vlan_trans_add(0, v->port, &v->eg_xlt_entry)) {
+		nss_vlan_mgr_warn("%s: failed to change egress vlan translation\n", dev->name);
+		nss_vlan_mgr_instance_deref(v);
+		return -1;
+	}
+#endif
+	nss_vlan_mgr_instance_deref(v);
+	return 0;
+}
+EXPORT_SYMBOL(nss_vlan_mgr_leave_bridge);
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+/*
+ * int nss_vlan_mgr_update_ppe_tpid()
+ */
+static int nss_vlan_mgr_update_ppe_tpid(void)
+{
+	fal_tpid_t tpid;
+
+	tpid.mask = FAL_TPID_CTAG_EN | FAL_TPID_STAG_EN;
+	tpid.ctpid = vlan_mgr_ctx.ctpid;
+	tpid.stpid = vlan_mgr_ctx.stpid;
+
+	if (fal_ingress_tpid_set(0, &tpid) || fal_egress_tpid_set(0, &tpid)) {
+		nss_vlan_mgr_warn("failed to set ctpid %d stpid %d\n", tpid.ctpid, tpid.stpid);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * nss_vlan_mgr_tpid_proc_handler()
+ *	Sets customer TPID and service TPID
+ */
+static int nss_vlan_mgr_tpid_proc_handler(struct ctl_table *ctl,
+					  int write, void __user *buffer,
+					  size_t *lenp, loff_t *ppos)
+{
+	int ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+	if (write)
+		nss_vlan_mgr_update_ppe_tpid();
+
+	return ret;
+}
+
+/*
+ * nss_vlan sysctl table
+ */
+static struct ctl_table nss_vlan_table[] = {
+	{
+		.procname	= "ctpid",
+		.data		= &vlan_mgr_ctx.ctpid,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &nss_vlan_mgr_tpid_proc_handler,
+	},
+	{
+		.procname	= "stpid",
+		.data		= &vlan_mgr_ctx.stpid,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &nss_vlan_mgr_tpid_proc_handler,
+	},
+	{ }
+};
+
+/*
+ * nss_vlan sysctl dir
+ */
+static struct ctl_table nss_vlan_dir[] = {
+	{
+		.procname		= "vlan_client",
+		.mode			= 0555,
+		.child			= nss_vlan_table,
+	},
+	{ }
+};
+
+/*
+ * nss_vlan systel root dir
+ */
+static struct ctl_table nss_vlan_root_dir[] = {
+	{
+		.procname		= "nss",
+		.mode			= 0555,
+		.child			= nss_vlan_dir,
+	},
+	{ }
+};
+#endif
+
+/*
+ * nss_vlan_mgr_init_module()
+ *	vlan_mgr module init function
+ */
+int __init nss_vlan_mgr_init_module(void)
+{
+	int idx;
+
+	INIT_LIST_HEAD(&vlan_mgr_ctx.list);
+	spin_lock_init(&vlan_mgr_ctx.lock);
+
+	vlan_mgr_ctx.ctpid = ETH_P_8021Q;
+	vlan_mgr_ctx.stpid = ETH_P_8021Q;
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	vlan_mgr_ctx.sys_hdr = register_sysctl_table(nss_vlan_root_dir);
+	if (!vlan_mgr_ctx.sys_hdr) {
+		nss_vlan_mgr_warn("Unabled to register sysctl table for vlan manager\n");
+		return -EFAULT;
+	}
+
+	if (nss_vlan_mgr_update_ppe_tpid()) {
+		unregister_sysctl_table(vlan_mgr_ctx.sys_hdr);
+		return -EFAULT;
+	}
+
+	for (idx = 0; idx < NSS_VLAN_PHY_PORT_NUM; idx++) {
+		vlan_mgr_ctx.port_role[idx] = FAL_QINQ_EDGE_PORT;
+	}
+#endif
+	register_netdevice_notifier(&nss_vlan_mgr_netdevice_nb);
+
+	nss_vlan_mgr_info("Module (Build %s) loaded\n", NSS_CLIENT_BUILD_ID);
+	return 0;
+}
+
+/*
+ * nss_vlan_mgr_exit_module()
+ *	vlan_mgr module exit function
+ */
+void __exit nss_vlan_mgr_exit_module(void)
+{
+	unregister_netdevice_notifier(&nss_vlan_mgr_netdevice_nb);
+
+#ifdef NSS_VLAN_MGR_PPE_SUPPORT
+	if (vlan_mgr_ctx.sys_hdr)
+		unregister_sysctl_table(vlan_mgr_ctx.sys_hdr);
+#endif
+	nss_vlan_mgr_info("Module unloaded\n");
+}
+
+module_init(nss_vlan_mgr_init_module);
+module_exit(nss_vlan_mgr_exit_module);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("NSS vlan manager");