qdpc-host: add VLAN support.

Add a vlan sysfs file that takes an "add <interface> <VLAN ID>"
command and creates a netdev named <interface> that tags/untags packets
with <VLAN ID> but is not a VLAN interface from the kernel's
perspective.

This works around a restriction in the Mindspeed VWD driver's VLAN
support: VLAN interfaces must be named according to the
<lower interface>.<VLAN ID> convention, but our interfaces were named
wlanX, wlanX_portal, and wcliX for homogeneity with cfg80211 drivers.
This broke hardware forwarding and resulted in a 2x performance hit.
Instead, netdevs created using the vlan sysfs file are not VLAN
interfaces and this restriction does not apply.

Change-Id: I735751628bda5fac297dc49f655f0025fd792277
diff --git a/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.c b/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.c
index e13ac76..7983bb2 100644
--- a/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.c
+++ b/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.c
@@ -34,6 +34,7 @@
 #include <linux/proc_fs.h>
 #include <linux/skbuff.h>
 #include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
 #include <linux/sysfs.h>
 #include <linux/pci.h>
 
@@ -92,6 +93,7 @@
 #ifdef RX_IP_HDR_REALIGN
 static uint32_t align_cnt = 0, unalign_cnt = 0;
 #endif
+static int vlan_create(struct net_device *lower, const char *ifname, int vlan);
 
 
 #define RX_DONE_INTR_MSK	((0x1 << 6) -1)
@@ -394,9 +396,45 @@
 }
 static DEVICE_ATTR(pmctrl, S_IWUSR | S_IRUSR, vmac_pm_show, vmac_pm_set); /* dev_attr_pmctrl */
 
+static ssize_t vmac_vlan_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct net_device *ndev = container_of(dev, struct net_device, dev);
+	struct vmac_priv *vmp = netdev_priv(ndev);
+	int count = 0;
+	int vlan;
+
+	for (vlan = 0; vlan < NUM_VLANS; vlan++) {
+		if (vmp->vlans[vlan]) {
+			count += sprintf(buf + count, "%s %d\n", vmp->vlans[vlan]->name, vlan);
+		}
+	}
+
+	return count;
+}
+
+static ssize_t vmac_vlan_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct net_device *ndev = container_of(dev, struct net_device, dev);
+	char ifname[IFNAMSIZ];
+	int vlan;
+	int err;
+
+	if (sscanf(buf, "add %15s %d", ifname, &vlan) == 2) {
+		if (err = vlan_create(ndev, ifname, vlan)) {
+			return err;
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(vlan, S_IWUSR | S_IRUSR, vmac_vlan_show, vmac_vlan_set); /* dev_attr_vlan */
+
 static struct attribute *vmac_device_attrs[] = {
 	&dev_attr_dbg.attr,
 	&dev_attr_pmctrl.attr,
+	&dev_attr_vlan.attr,
 	NULL,
 };
 
@@ -729,6 +767,7 @@
 	uint16_t i = vmp->rx_bd_index;
 	volatile struct vmac_bd *rbdp = &vmp->rx_bd_base[i];
 	uint32_t descw1;
+	uint16_t vlan;
 
 	while (!((descw1 = le32_to_cpu(VMAC_REG_READ(&rbdp->buff_info))) & VMAC_BD_EMPTY) && (processed < budget)) {
 		struct sk_buff *skb;
@@ -766,6 +805,13 @@
 
 				processed++;
 
+				skb->dev = ndev;
+				if (!vlan_get_tag(skb, &vlan) && vlan < NUM_VLANS && vmp->vlans[vlan]) {
+					memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
+					skb_pull(skb, VLAN_HLEN);
+					skb->dev = vmp->vlans[vlan];
+				}
+
 #ifdef CONFIG_ARCH_COMCERTO
 				extern int comcerto_wifi_rx_fastpath(struct sk_buff *skb);
 				if (comcerto_wifi_rx_fastpath(skb))
@@ -781,7 +827,7 @@
 #endif
 
 					if (skb) {
-						skb->protocol = eth_type_trans(skb, ndev);
+						skb->protocol = eth_type_trans(skb, skb->dev);
 						netif_receive_skb(skb);
 					}
 				}
@@ -1478,12 +1524,23 @@
 void vmac_clean(struct net_device *ndev)
 {
 	struct vmac_priv *vmp;
+	struct net_device *vdev;
+	int vlan;
 
 	if (!ndev)
 		return;
 
 	vmp = netdev_priv(ndev);
 
+	for (vlan = 0; vlan < NUM_VLANS; vlan++) {
+		if (vmp->vlans[vlan]) {
+			vdev = vmp->vlans[vlan];
+			vmp->vlans[vlan] = NULL;
+			unregister_netdev(vdev);
+			free_netdev(vdev);
+		}
+	}
+
 	device_remove_file(&ndev->dev, &dev_attr_dbg);
 
 	unregister_netdev(ndev);
@@ -1577,3 +1634,56 @@
 {
 	return &(ndev->stats);
 }
+
+struct vlan_priv {
+	struct net_device *lower;
+	int vlan;
+};
+
+static netdev_tx_t vlan_start_xmit(struct sk_buff *skb, struct net_device *vdev)
+{
+	struct vlan_priv *vlp = netdev_priv(vdev);
+	struct net_device *lower = vlp->lower;
+	int vlan = vlp->vlan;
+	skb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), vlan);
+	return lower->netdev_ops->ndo_start_xmit(skb, lower);
+}
+
+static const struct net_device_ops vlan_device_ops = {
+	.ndo_start_xmit = vlan_start_xmit,
+	.ndo_set_mac_address = eth_mac_addr,
+};
+
+static int vlan_create(struct net_device *lower, const char *ifname, int vlan)
+{
+	struct vmac_priv *vmp = netdev_priv(lower);
+	struct net_device *vdev;
+	struct vlan_priv *vlp;
+	int err;
+
+	if (vlan < 0 || NUM_VLANS <= vlan) {
+		return -EINVAL;
+	}
+
+	if (vmp->vlans[vlan]) {
+		return -EEXIST;
+	}
+
+	if (!(vdev = alloc_netdev(sizeof(struct vlan_priv), ifname, NET_NAME_UNKNOWN, ether_setup))) {
+		return -ENOMEM;
+	}
+
+	vlp = netdev_priv(vdev);
+	vlp->lower = lower;
+	vlp->vlan = vlan;
+	vdev->netdev_ops = &vlan_device_ops;
+
+	if (err = register_netdev(vdev)) {
+		free_netdev(vdev);
+		return err;
+	}
+
+	vmp->vlans[vlan] = vdev;
+
+	return 0;
+}
diff --git a/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.h b/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.h
index 928b809..1e7312a 100644
--- a/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.h
+++ b/drivers/net/wireless/quantenna/pcie2/host/common/topaz_vnet.h
@@ -65,6 +65,8 @@
 
 #define VMAC_NL_BUF_SIZE		USHRT_MAX
 
+#define NUM_VLANS 5
+
 typedef struct qdpc_bar {
         void *b_vaddr; /* PCIe bar virtual address */
         dma_addr_t b_busaddr; /* PCIe bar physical address */
@@ -187,6 +189,8 @@
 	volatile uint32_t *ep_pmstate;
 	uint8_t *nl_buf;
 	size_t nl_len;
+
+	struct net_device *vlans[NUM_VLANS];
 };
 
 #define QTN_DISABLE_SOFTIRQ		(0xABCD)