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)