sfc: Implement ndo_vlan_rx_{add, kill}_vid() callbacks
[deliverable/linux.git] / drivers / net / ethernet / sfc / ef10.c
index 1f309127457d24365c611c505f4611659d445209..a3c00ffae8a05efb0995b4082bbcaa03999c66f3 100644 (file)
@@ -50,9 +50,29 @@ enum {
 #define HUNT_FILTER_TBL_ROWS 8192
 
 #define EFX_EF10_FILTER_ID_INVALID 0xffff
+
+#define EFX_EF10_FILTER_DEV_UC_MAX     32
+#define EFX_EF10_FILTER_DEV_MC_MAX     256
+
+/* VLAN list entry */
+struct efx_ef10_vlan {
+       struct list_head list;
+       u16 vid;
+};
+
+/* Per-VLAN filters information */
+struct efx_ef10_filter_vlan {
+       struct list_head list;
+       u16 vid;
+       u16 uc[EFX_EF10_FILTER_DEV_UC_MAX];
+       u16 mc[EFX_EF10_FILTER_DEV_MC_MAX];
+       u16 ucdef;
+       u16 bcast;
+       u16 mcdef;
+};
+
 struct efx_ef10_dev_addr {
        u8 addr[ETH_ALEN];
-       u16 id;
 };
 
 struct efx_ef10_filter_table {
@@ -73,16 +93,16 @@ struct efx_ef10_filter_table {
        } *entry;
        wait_queue_head_t waitq;
 /* Shadow of net_device address lists, guarded by mac_lock */
-#define EFX_EF10_FILTER_DEV_UC_MAX     32
-#define EFX_EF10_FILTER_DEV_MC_MAX     256
        struct efx_ef10_dev_addr dev_uc_list[EFX_EF10_FILTER_DEV_UC_MAX];
        struct efx_ef10_dev_addr dev_mc_list[EFX_EF10_FILTER_DEV_MC_MAX];
        int dev_uc_count;
        int dev_mc_count;
-/* Indices (like efx_ef10_dev_addr.id) for promisc/allmulti filters */
-       u16 ucdef_id;
-       u16 bcast_id;
-       u16 mcdef_id;
+       bool uc_promisc;
+       bool mc_promisc;
+/* Whether in multicast promiscuous mode when last changed */
+       bool mc_promisc_last;
+       bool vlan_filter;
+       struct list_head vlan_list;
 };
 
 /* An arbitrary search limit for the software hash table */
@@ -90,6 +110,10 @@ struct efx_ef10_filter_table {
 
 static void efx_ef10_rx_free_indir_table(struct efx_nic *efx);
 static void efx_ef10_filter_table_remove(struct efx_nic *efx);
+static int efx_ef10_filter_add_vlan(struct efx_nic *efx, u16 vid);
+static void efx_ef10_filter_del_vlan_internal(struct efx_nic *efx,
+                                             struct efx_ef10_filter_vlan *vlan);
+static void efx_ef10_filter_del_vlan(struct efx_nic *efx, u16 vid);
 
 static int efx_ef10_get_warm_boot_count(struct efx_nic *efx)
 {
@@ -275,6 +299,131 @@ static ssize_t efx_ef10_show_primary_flag(struct device *dev,
                       ? 1 : 0);
 }
 
+static struct efx_ef10_vlan *efx_ef10_find_vlan(struct efx_nic *efx, u16 vid)
+{
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
+       struct efx_ef10_vlan *vlan;
+
+       WARN_ON(!mutex_is_locked(&nic_data->vlan_lock));
+
+       list_for_each_entry(vlan, &nic_data->vlan_list, list) {
+               if (vlan->vid == vid)
+                       return vlan;
+       }
+
+       return NULL;
+}
+
+static int efx_ef10_add_vlan(struct efx_nic *efx, u16 vid)
+{
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
+       struct efx_ef10_vlan *vlan;
+       int rc;
+
+       mutex_lock(&nic_data->vlan_lock);
+
+       vlan = efx_ef10_find_vlan(efx, vid);
+       if (vlan) {
+               /* We add VID 0 on init. 8021q adds it on module init
+                * for all interfaces with VLAN filtring feature.
+                */
+               if (vid == 0)
+                       goto done_unlock;
+               netif_warn(efx, drv, efx->net_dev,
+                          "VLAN %u already added\n", vid);
+               rc = -EALREADY;
+               goto fail_exist;
+       }
+
+       rc = -ENOMEM;
+       vlan = kzalloc(sizeof(*vlan), GFP_KERNEL);
+       if (!vlan)
+               goto fail_alloc;
+
+       vlan->vid = vid;
+
+       list_add_tail(&vlan->list, &nic_data->vlan_list);
+
+       if (efx->filter_state) {
+               mutex_lock(&efx->mac_lock);
+               down_write(&efx->filter_sem);
+               rc = efx_ef10_filter_add_vlan(efx, vlan->vid);
+               up_write(&efx->filter_sem);
+               mutex_unlock(&efx->mac_lock);
+               if (rc)
+                       goto fail_filter_add_vlan;
+       }
+
+done_unlock:
+       mutex_unlock(&nic_data->vlan_lock);
+       return 0;
+
+fail_filter_add_vlan:
+       list_del(&vlan->list);
+       kfree(vlan);
+fail_alloc:
+fail_exist:
+       mutex_unlock(&nic_data->vlan_lock);
+       return rc;
+}
+
+static void efx_ef10_del_vlan_internal(struct efx_nic *efx,
+                                      struct efx_ef10_vlan *vlan)
+{
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
+
+       WARN_ON(!mutex_is_locked(&nic_data->vlan_lock));
+
+       if (efx->filter_state) {
+               down_write(&efx->filter_sem);
+               efx_ef10_filter_del_vlan(efx, vlan->vid);
+               up_write(&efx->filter_sem);
+       }
+
+       list_del(&vlan->list);
+       kfree(vlan);
+}
+
+static int efx_ef10_del_vlan(struct efx_nic *efx, u16 vid)
+{
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
+       struct efx_ef10_vlan *vlan;
+       int rc = 0;
+
+       /* 8021q removes VID 0 on module unload for all interfaces
+        * with VLAN filtering feature. We need to keep it to receive
+        * untagged traffic.
+        */
+       if (vid == 0)
+               return 0;
+
+       mutex_lock(&nic_data->vlan_lock);
+
+       vlan = efx_ef10_find_vlan(efx, vid);
+       if (!vlan) {
+               netif_err(efx, drv, efx->net_dev,
+                         "VLAN %u to be deleted not found\n", vid);
+               rc = -ENOENT;
+       } else {
+               efx_ef10_del_vlan_internal(efx, vlan);
+       }
+
+       mutex_unlock(&nic_data->vlan_lock);
+
+       return rc;
+}
+
+static void efx_ef10_cleanup_vlans(struct efx_nic *efx)
+{
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
+       struct efx_ef10_vlan *vlan, *next_vlan;
+
+       mutex_lock(&nic_data->vlan_lock);
+       list_for_each_entry_safe(vlan, next_vlan, &nic_data->vlan_list, list)
+               efx_ef10_del_vlan_internal(efx, vlan);
+       mutex_unlock(&nic_data->vlan_lock);
+}
+
 static DEVICE_ATTR(link_control_flag, 0444, efx_ef10_show_link_control_flag,
                   NULL);
 static DEVICE_ATTR(primary_flag, 0444, efx_ef10_show_primary_flag, NULL);
@@ -421,8 +570,30 @@ static int efx_ef10_probe(struct efx_nic *efx)
 #endif
                ether_addr_copy(nic_data->port_id, efx->net_dev->perm_addr);
 
+       INIT_LIST_HEAD(&nic_data->vlan_list);
+       mutex_init(&nic_data->vlan_lock);
+
+       /* Add unspecified VID to support VLAN filtering being disabled */
+       rc = efx_ef10_add_vlan(efx, EFX_FILTER_VID_UNSPEC);
+       if (rc)
+               goto fail_add_vid_unspec;
+
+       /* If VLAN filtering is enabled, we need VID 0 to get untagged
+        * traffic.  It is added automatically if 8021q module is loaded,
+        * but we can't rely on it since module may be not loaded.
+        */
+       rc = efx_ef10_add_vlan(efx, 0);
+       if (rc)
+               goto fail_add_vid_0;
+
        return 0;
 
+fail_add_vid_0:
+       efx_ef10_cleanup_vlans(efx);
+fail_add_vid_unspec:
+       mutex_destroy(&nic_data->vlan_lock);
+       efx_ptp_remove(efx);
+       efx_mcdi_mon_remove(efx);
 fail5:
        device_remove_file(&efx->pci_dev->dev, &dev_attr_primary_flag);
 fail4:
@@ -676,6 +847,9 @@ static void efx_ef10_remove(struct efx_nic *efx)
        }
 #endif
 
+       efx_ef10_cleanup_vlans(efx);
+       mutex_destroy(&nic_data->vlan_lock);
+
        efx_ptp_remove(efx);
 
        efx_mcdi_mon_remove(efx);
@@ -3724,15 +3898,39 @@ static int efx_ef10_filter_match_flags_from_mcdi(u32 mcdi_flags)
        return match_flags;
 }
 
+static void efx_ef10_filter_cleanup_vlans(struct efx_nic *efx)
+{
+       struct efx_ef10_filter_table *table = efx->filter_state;
+       struct efx_ef10_filter_vlan *vlan, *next_vlan;
+
+       /* See comment in efx_ef10_filter_table_remove() */
+       if (!efx_rwsem_assert_write_locked(&efx->filter_sem))
+               return;
+
+       if (!table)
+               return;
+
+       list_for_each_entry_safe(vlan, next_vlan, &table->vlan_list, list)
+               efx_ef10_filter_del_vlan_internal(efx, vlan);
+}
+
 static int efx_ef10_filter_table_probe(struct efx_nic *efx)
 {
        MCDI_DECLARE_BUF(inbuf, MC_CMD_GET_PARSER_DISP_INFO_IN_LEN);
        MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_PARSER_DISP_INFO_OUT_LENMAX);
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
        unsigned int pd_match_pri, pd_match_count;
        struct efx_ef10_filter_table *table;
+       struct efx_ef10_vlan *vlan;
        size_t outlen;
        int rc;
 
+       if (!efx_rwsem_assert_write_locked(&efx->filter_sem))
+               return -EINVAL;
+
+       if (efx->filter_state) /* already probed */
+               return 0;
+
        table = kzalloc(sizeof(*table), GFP_KERNEL);
        if (!table)
                return -ENOMEM;
@@ -3775,14 +3973,25 @@ static int efx_ef10_filter_table_probe(struct efx_nic *efx)
                goto fail;
        }
 
-       table->ucdef_id = EFX_EF10_FILTER_ID_INVALID;
-       table->bcast_id = EFX_EF10_FILTER_ID_INVALID;
-       table->mcdef_id = EFX_EF10_FILTER_ID_INVALID;
+       table->mc_promisc_last = false;
+       table->vlan_filter =
+               !!(efx->net_dev->features & NETIF_F_HW_VLAN_CTAG_FILTER);
+       INIT_LIST_HEAD(&table->vlan_list);
 
        efx->filter_state = table;
        init_waitqueue_head(&table->waitq);
+
+       list_for_each_entry(vlan, &nic_data->vlan_list, list) {
+               rc = efx_ef10_filter_add_vlan(efx, vlan->vid);
+               if (rc)
+                       goto fail_add_vlan;
+       }
+
        return 0;
 
+fail_add_vlan:
+       efx_ef10_filter_cleanup_vlans(efx);
+       efx->filter_state = NULL;
 fail:
        kfree(table);
        return rc;
@@ -3843,7 +4052,6 @@ static void efx_ef10_filter_table_restore(struct efx_nic *efx)
                nic_data->must_restore_filters = false;
 }
 
-/* Caller must hold efx->filter_sem for write */
 static void efx_ef10_filter_table_remove(struct efx_nic *efx)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
@@ -3852,7 +4060,17 @@ static void efx_ef10_filter_table_remove(struct efx_nic *efx)
        unsigned int filter_idx;
        int rc;
 
+       efx_ef10_filter_cleanup_vlans(efx);
        efx->filter_state = NULL;
+       /* If we were called without locking, then it's not safe to free
+        * the table as others might be using it.  So we just WARN, leak
+        * the memory, and potentially get an inconsistent filter table
+        * state.
+        * This should never actually happen.
+        */
+       if (!efx_rwsem_assert_write_locked(&efx->filter_sem))
+               return;
+
        if (!table)
                return;
 
@@ -3880,37 +4098,54 @@ static void efx_ef10_filter_table_remove(struct efx_nic *efx)
        kfree(table);
 }
 
-#define EFX_EF10_FILTER_DO_MARK_OLD(id) \
-       if (id != EFX_EF10_FILTER_ID_INVALID) { \
-               filter_idx = efx_ef10_filter_get_unsafe_id(efx, id); \
-               if (!table->entry[filter_idx].spec) \
-                       netif_dbg(efx, drv, efx->net_dev, \
-                                 "%s: marked null spec old %04x:%04x\n", \
-                                 __func__, id, filter_idx); \
-               table->entry[filter_idx].spec |= EFX_EF10_FILTER_FLAG_AUTO_OLD;\
-       }
-static void efx_ef10_filter_mark_old(struct efx_nic *efx)
+static void efx_ef10_filter_mark_one_old(struct efx_nic *efx, uint16_t *id)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
-       unsigned int filter_idx, i;
+       unsigned int filter_idx;
 
-       if (!table)
-               return;
+       if (*id != EFX_EF10_FILTER_ID_INVALID) {
+               filter_idx = efx_ef10_filter_get_unsafe_id(efx, *id);
+               if (!table->entry[filter_idx].spec)
+                       netif_dbg(efx, drv, efx->net_dev,
+                                 "marked null spec old %04x:%04x\n", *id,
+                                 filter_idx);
+               table->entry[filter_idx].spec |= EFX_EF10_FILTER_FLAG_AUTO_OLD;
+               *id = EFX_EF10_FILTER_ID_INVALID;
+       }
+}
+
+/* Mark old per-VLAN filters that may need to be removed */
+static void _efx_ef10_filter_vlan_mark_old(struct efx_nic *efx,
+                                          struct efx_ef10_filter_vlan *vlan)
+{
+       struct efx_ef10_filter_table *table = efx->filter_state;
+       unsigned int i;
 
-       /* Mark old filters that may need to be removed */
-       spin_lock_bh(&efx->filter_lock);
        for (i = 0; i < table->dev_uc_count; i++)
-               EFX_EF10_FILTER_DO_MARK_OLD(table->dev_uc_list[i].id);
+               efx_ef10_filter_mark_one_old(efx, &vlan->uc[i]);
        for (i = 0; i < table->dev_mc_count; i++)
-               EFX_EF10_FILTER_DO_MARK_OLD(table->dev_mc_list[i].id);
-       EFX_EF10_FILTER_DO_MARK_OLD(table->ucdef_id);
-       EFX_EF10_FILTER_DO_MARK_OLD(table->bcast_id);
-       EFX_EF10_FILTER_DO_MARK_OLD(table->mcdef_id);
+               efx_ef10_filter_mark_one_old(efx, &vlan->mc[i]);
+       efx_ef10_filter_mark_one_old(efx, &vlan->ucdef);
+       efx_ef10_filter_mark_one_old(efx, &vlan->bcast);
+       efx_ef10_filter_mark_one_old(efx, &vlan->mcdef);
+}
+
+/* Mark old filters that may need to be removed.
+ * Caller must hold efx->filter_sem for read if race against
+ * efx_ef10_filter_table_remove() is possible
+ */
+static void efx_ef10_filter_mark_old(struct efx_nic *efx)
+{
+       struct efx_ef10_filter_table *table = efx->filter_state;
+       struct efx_ef10_filter_vlan *vlan;
+
+       spin_lock_bh(&efx->filter_lock);
+       list_for_each_entry(vlan, &table->vlan_list, list)
+               _efx_ef10_filter_vlan_mark_old(efx, vlan);
        spin_unlock_bh(&efx->filter_lock);
 }
-#undef EFX_EF10_FILTER_DO_MARK_OLD
 
-static void efx_ef10_filter_uc_addr_list(struct efx_nic *efx, bool *promisc)
+static void efx_ef10_filter_uc_addr_list(struct efx_nic *efx)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
        struct net_device *net_dev = efx->net_dev;
@@ -3918,45 +4153,38 @@ static void efx_ef10_filter_uc_addr_list(struct efx_nic *efx, bool *promisc)
        int addr_count;
        unsigned int i;
 
-       table->ucdef_id = EFX_EF10_FILTER_ID_INVALID;
        addr_count = netdev_uc_count(net_dev);
-       if (net_dev->flags & IFF_PROMISC)
-               *promisc = true;
+       table->uc_promisc = !!(net_dev->flags & IFF_PROMISC);
        table->dev_uc_count = 1 + addr_count;
        ether_addr_copy(table->dev_uc_list[0].addr, net_dev->dev_addr);
        i = 1;
        netdev_for_each_uc_addr(uc, net_dev) {
                if (i >= EFX_EF10_FILTER_DEV_UC_MAX) {
-                       *promisc = true;
+                       table->uc_promisc = true;
                        break;
                }
                ether_addr_copy(table->dev_uc_list[i].addr, uc->addr);
-               table->dev_uc_list[i].id = EFX_EF10_FILTER_ID_INVALID;
                i++;
        }
 }
 
-static void efx_ef10_filter_mc_addr_list(struct efx_nic *efx, bool *promisc)
+static void efx_ef10_filter_mc_addr_list(struct efx_nic *efx)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
        struct net_device *net_dev = efx->net_dev;
        struct netdev_hw_addr *mc;
        unsigned int i, addr_count;
 
-       table->mcdef_id = EFX_EF10_FILTER_ID_INVALID;
-       table->bcast_id = EFX_EF10_FILTER_ID_INVALID;
-       if (net_dev->flags & (IFF_PROMISC | IFF_ALLMULTI))
-               *promisc = true;
+       table->mc_promisc = !!(net_dev->flags & (IFF_PROMISC | IFF_ALLMULTI));
 
        addr_count = netdev_mc_count(net_dev);
        i = 0;
        netdev_for_each_mc_addr(mc, net_dev) {
                if (i >= EFX_EF10_FILTER_DEV_MC_MAX) {
-                       *promisc = true;
+                       table->mc_promisc = true;
                        break;
                }
                ether_addr_copy(table->dev_mc_list[i].addr, mc->addr);
-               table->dev_mc_list[i].id = EFX_EF10_FILTER_ID_INVALID;
                i++;
        }
 
@@ -3964,7 +4192,8 @@ static void efx_ef10_filter_mc_addr_list(struct efx_nic *efx, bool *promisc)
 }
 
 static int efx_ef10_filter_insert_addr_list(struct efx_nic *efx,
-                                            bool multicast, bool rollback)
+                                           struct efx_ef10_filter_vlan *vlan,
+                                           bool multicast, bool rollback)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
        struct efx_ef10_dev_addr *addr_list;
@@ -3973,14 +4202,17 @@ static int efx_ef10_filter_insert_addr_list(struct efx_nic *efx,
        u8 baddr[ETH_ALEN];
        unsigned int i, j;
        int addr_count;
+       u16 *ids;
        int rc;
 
        if (multicast) {
                addr_list = table->dev_mc_list;
                addr_count = table->dev_mc_count;
+               ids = vlan->mc;
        } else {
                addr_list = table->dev_uc_list;
                addr_count = table->dev_uc_count;
+               ids = vlan->uc;
        }
 
        filter_flags = efx_rss_enabled(efx) ? EFX_FILTER_FLAG_RX_RSS : 0;
@@ -3988,8 +4220,7 @@ static int efx_ef10_filter_insert_addr_list(struct efx_nic *efx,
        /* Insert/renew filters */
        for (i = 0; i < addr_count; i++) {
                efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO, filter_flags, 0);
-               efx_filter_set_eth_local(&spec, EFX_FILTER_VID_UNSPEC,
-                                        addr_list[i].addr);
+               efx_filter_set_eth_local(&spec, vlan->vid, addr_list[i].addr);
                rc = efx_ef10_filter_insert(efx, &spec, true);
                if (rc < 0) {
                        if (rollback) {
@@ -3998,12 +4229,12 @@ static int efx_ef10_filter_insert_addr_list(struct efx_nic *efx,
                                           rc);
                                /* Fall back to promiscuous */
                                for (j = 0; j < i; j++) {
-                                       if (addr_list[j].id == EFX_EF10_FILTER_ID_INVALID)
+                                       if (ids[j] == EFX_EF10_FILTER_ID_INVALID)
                                                continue;
                                        efx_ef10_filter_remove_unsafe(
                                                efx, EFX_FILTER_PRI_AUTO,
-                                               addr_list[j].id);
-                                       addr_list[j].id = EFX_EF10_FILTER_ID_INVALID;
+                                               ids[j]);
+                                       ids[j] = EFX_EF10_FILTER_ID_INVALID;
                                }
                                return rc;
                        } else {
@@ -4011,40 +4242,42 @@ static int efx_ef10_filter_insert_addr_list(struct efx_nic *efx,
                                rc = EFX_EF10_FILTER_ID_INVALID;
                        }
                }
-               addr_list[i].id = efx_ef10_filter_get_unsafe_id(efx, rc);
+               ids[i] = efx_ef10_filter_get_unsafe_id(efx, rc);
        }
 
        if (multicast && rollback) {
                /* Also need an Ethernet broadcast filter */
                efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO, filter_flags, 0);
                eth_broadcast_addr(baddr);
-               efx_filter_set_eth_local(&spec, EFX_FILTER_VID_UNSPEC, baddr);
+               efx_filter_set_eth_local(&spec, vlan->vid, baddr);
                rc = efx_ef10_filter_insert(efx, &spec, true);
                if (rc < 0) {
                        netif_warn(efx, drv, efx->net_dev,
                                   "Broadcast filter insert failed rc=%d\n", rc);
                        /* Fall back to promiscuous */
                        for (j = 0; j < i; j++) {
-                               if (addr_list[j].id == EFX_EF10_FILTER_ID_INVALID)
+                               if (ids[j] == EFX_EF10_FILTER_ID_INVALID)
                                        continue;
                                efx_ef10_filter_remove_unsafe(
                                        efx, EFX_FILTER_PRI_AUTO,
-                                       addr_list[j].id);
-                               addr_list[j].id = EFX_EF10_FILTER_ID_INVALID;
+                                       ids[j]);
+                               ids[j] = EFX_EF10_FILTER_ID_INVALID;
                        }
                        return rc;
                } else {
-                       table->bcast_id = efx_ef10_filter_get_unsafe_id(efx, rc);
+                       EFX_WARN_ON_PARANOID(vlan->bcast !=
+                                            EFX_EF10_FILTER_ID_INVALID);
+                       vlan->bcast = efx_ef10_filter_get_unsafe_id(efx, rc);
                }
        }
 
        return 0;
 }
 
-static int efx_ef10_filter_insert_def(struct efx_nic *efx, bool multicast,
-                                     bool rollback)
+static int efx_ef10_filter_insert_def(struct efx_nic *efx,
+                                     struct efx_ef10_filter_vlan *vlan,
+                                     bool multicast, bool rollback)
 {
-       struct efx_ef10_filter_table *table = efx->filter_state;
        struct efx_ef10_nic_data *nic_data = efx->nic_data;
        enum efx_filter_flags filter_flags;
        struct efx_filter_spec spec;
@@ -4060,6 +4293,9 @@ static int efx_ef10_filter_insert_def(struct efx_nic *efx, bool multicast,
        else
                efx_filter_set_uc_def(&spec);
 
+       if (vlan->vid != EFX_FILTER_VID_UNSPEC)
+               efx_filter_set_eth_local(&spec, vlan->vid, NULL);
+
        rc = efx_ef10_filter_insert(efx, &spec, true);
        if (rc < 0) {
                netif_printk(efx, drv, rc == -EPERM ? KERN_DEBUG : KERN_WARNING,
@@ -4067,14 +4303,14 @@ static int efx_ef10_filter_insert_def(struct efx_nic *efx, bool multicast,
                             "%scast mismatch filter insert failed rc=%d\n",
                             multicast ? "Multi" : "Uni", rc);
        } else if (multicast) {
-               table->mcdef_id = efx_ef10_filter_get_unsafe_id(efx, rc);
+               EFX_WARN_ON_PARANOID(vlan->mcdef != EFX_EF10_FILTER_ID_INVALID);
+               vlan->mcdef = efx_ef10_filter_get_unsafe_id(efx, rc);
                if (!nic_data->workaround_26807) {
                        /* Also need an Ethernet broadcast filter */
                        efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO,
                                           filter_flags, 0);
                        eth_broadcast_addr(baddr);
-                       efx_filter_set_eth_local(&spec, EFX_FILTER_VID_UNSPEC,
-                                                baddr);
+                       efx_filter_set_eth_local(&spec, vlan->vid, baddr);
                        rc = efx_ef10_filter_insert(efx, &spec, true);
                        if (rc < 0) {
                                netif_warn(efx, drv, efx->net_dev,
@@ -4084,17 +4320,20 @@ static int efx_ef10_filter_insert_def(struct efx_nic *efx, bool multicast,
                                        /* Roll back the mc_def filter */
                                        efx_ef10_filter_remove_unsafe(
                                                        efx, EFX_FILTER_PRI_AUTO,
-                                                       table->mcdef_id);
-                                       table->mcdef_id = EFX_EF10_FILTER_ID_INVALID;
+                                                       vlan->mcdef);
+                                       vlan->mcdef = EFX_EF10_FILTER_ID_INVALID;
                                        return rc;
                                }
                        } else {
-                               table->bcast_id = efx_ef10_filter_get_unsafe_id(efx, rc);
+                               EFX_WARN_ON_PARANOID(vlan->bcast !=
+                                                    EFX_EF10_FILTER_ID_INVALID);
+                               vlan->bcast = efx_ef10_filter_get_unsafe_id(efx, rc);
                        }
                }
                rc = 0;
        } else {
-               table->ucdef_id = rc;
+               EFX_WARN_ON_PARANOID(vlan->ucdef != EFX_EF10_FILTER_ID_INVALID);
+               vlan->ucdef = rc;
                rc = 0;
        }
        return rc;
@@ -4203,64 +4442,55 @@ reset_nic:
 /* Caller must hold efx->filter_sem for read if race against
  * efx_ef10_filter_table_remove() is possible
  */
-static void efx_ef10_filter_sync_rx_mode(struct efx_nic *efx)
+static void efx_ef10_filter_vlan_sync_rx_mode(struct efx_nic *efx,
+                                             struct efx_ef10_filter_vlan *vlan)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
        struct efx_ef10_nic_data *nic_data = efx->nic_data;
-       struct net_device *net_dev = efx->net_dev;
-       bool uc_promisc = false, mc_promisc = false;
-
-       if (!efx_dev_registered(efx))
-               return;
-
-       if (!table)
-               return;
 
-       efx_ef10_filter_mark_old(efx);
-
-       /* Copy/convert the address lists; add the primary station
-        * address and broadcast address
+       /* Do not install unspecified VID if VLAN filtering is enabled.
+        * Do not install all specified VIDs if VLAN filtering is disabled.
         */
-       netif_addr_lock_bh(net_dev);
-       efx_ef10_filter_uc_addr_list(efx, &uc_promisc);
-       efx_ef10_filter_mc_addr_list(efx, &mc_promisc);
-       netif_addr_unlock_bh(net_dev);
+       if ((vlan->vid == EFX_FILTER_VID_UNSPEC) == table->vlan_filter)
+               return;
 
        /* Insert/renew unicast filters */
-       if (uc_promisc) {
-               efx_ef10_filter_insert_def(efx, false, false);
-               efx_ef10_filter_insert_addr_list(efx, false, false);
+       if (table->uc_promisc) {
+               efx_ef10_filter_insert_def(efx, vlan, false, false);
+               efx_ef10_filter_insert_addr_list(efx, vlan, false, false);
        } else {
                /* If any of the filters failed to insert, fall back to
                 * promiscuous mode - add in the uc_def filter.  But keep
                 * our individual unicast filters.
                 */
-               if (efx_ef10_filter_insert_addr_list(efx, false, false))
-                       efx_ef10_filter_insert_def(efx, false, false);
+               if (efx_ef10_filter_insert_addr_list(efx, vlan, false, false))
+                       efx_ef10_filter_insert_def(efx, vlan, false, false);
        }
 
        /* Insert/renew multicast filters */
        /* If changing promiscuous state with cascaded multicast filters, remove
         * old filters first, so that packets are dropped rather than duplicated
         */
-       if (nic_data->workaround_26807 && efx->mc_promisc != mc_promisc)
+       if (nic_data->workaround_26807 &&
+           table->mc_promisc_last != table->mc_promisc)
                efx_ef10_filter_remove_old(efx);
-       if (mc_promisc) {
+       if (table->mc_promisc) {
                if (nic_data->workaround_26807) {
                        /* If we failed to insert promiscuous filters, rollback
                         * and fall back to individual multicast filters
                         */
-                       if (efx_ef10_filter_insert_def(efx, true, true)) {
+                       if (efx_ef10_filter_insert_def(efx, vlan, true, true)) {
                                /* Changing promisc state, so remove old filters */
                                efx_ef10_filter_remove_old(efx);
-                               efx_ef10_filter_insert_addr_list(efx, true, false);
+                               efx_ef10_filter_insert_addr_list(efx, vlan,
+                                                                true, false);
                        }
                } else {
                        /* If we failed to insert promiscuous filters, don't
                         * rollback.  Regardless, also insert the mc_list
                         */
-                       efx_ef10_filter_insert_def(efx, true, false);
-                       efx_ef10_filter_insert_addr_list(efx, true, false);
+                       efx_ef10_filter_insert_def(efx, vlan, true, false);
+                       efx_ef10_filter_insert_addr_list(efx, vlan, true, false);
                }
        } else {
                /* If any filters failed to insert, rollback and fall back to
@@ -4268,17 +4498,163 @@ static void efx_ef10_filter_sync_rx_mode(struct efx_nic *efx)
                 * that fails, roll back again and insert as many of our
                 * individual multicast filters as we can.
                 */
-               if (efx_ef10_filter_insert_addr_list(efx, true, true)) {
+               if (efx_ef10_filter_insert_addr_list(efx, vlan, true, true)) {
                        /* Changing promisc state, so remove old filters */
                        if (nic_data->workaround_26807)
                                efx_ef10_filter_remove_old(efx);
-                       if (efx_ef10_filter_insert_def(efx, true, true))
-                               efx_ef10_filter_insert_addr_list(efx, true, false);
+                       if (efx_ef10_filter_insert_def(efx, vlan, true, true))
+                               efx_ef10_filter_insert_addr_list(efx, vlan,
+                                                                true, false);
                }
        }
+}
+
+/* Caller must hold efx->filter_sem for read if race against
+ * efx_ef10_filter_table_remove() is possible
+ */
+static void efx_ef10_filter_sync_rx_mode(struct efx_nic *efx)
+{
+       struct efx_ef10_filter_table *table = efx->filter_state;
+       struct net_device *net_dev = efx->net_dev;
+       struct efx_ef10_filter_vlan *vlan;
+       bool vlan_filter;
+
+       if (!efx_dev_registered(efx))
+               return;
+
+       if (!table)
+               return;
+
+       efx_ef10_filter_mark_old(efx);
+
+       /* Copy/convert the address lists; add the primary station
+        * address and broadcast address
+        */
+       netif_addr_lock_bh(net_dev);
+       efx_ef10_filter_uc_addr_list(efx);
+       efx_ef10_filter_mc_addr_list(efx);
+       netif_addr_unlock_bh(net_dev);
+
+       /* If VLAN filtering changes, all old filters are finally removed.
+        * Do it in advance to avoid conflicts for unicast untagged and
+        * VLAN 0 tagged filters.
+        */
+       vlan_filter = !!(net_dev->features & NETIF_F_HW_VLAN_CTAG_FILTER);
+       if (table->vlan_filter != vlan_filter) {
+               table->vlan_filter = vlan_filter;
+               efx_ef10_filter_remove_old(efx);
+       }
+
+       list_for_each_entry(vlan, &table->vlan_list, list)
+               efx_ef10_filter_vlan_sync_rx_mode(efx, vlan);
 
        efx_ef10_filter_remove_old(efx);
-       efx->mc_promisc = mc_promisc;
+       table->mc_promisc_last = table->mc_promisc;
+}
+
+static struct efx_ef10_filter_vlan *efx_ef10_filter_find_vlan(struct efx_nic *efx, u16 vid)
+{
+       struct efx_ef10_filter_table *table = efx->filter_state;
+       struct efx_ef10_filter_vlan *vlan;
+
+       WARN_ON(!rwsem_is_locked(&efx->filter_sem));
+
+       list_for_each_entry(vlan, &table->vlan_list, list) {
+               if (vlan->vid == vid)
+                       return vlan;
+       }
+
+       return NULL;
+}
+
+static int efx_ef10_filter_add_vlan(struct efx_nic *efx, u16 vid)
+{
+       struct efx_ef10_filter_table *table = efx->filter_state;
+       struct efx_ef10_filter_vlan *vlan;
+       unsigned int i;
+
+       if (!efx_rwsem_assert_write_locked(&efx->filter_sem))
+               return -EINVAL;
+
+       vlan = efx_ef10_filter_find_vlan(efx, vid);
+       if (WARN_ON(vlan)) {
+               netif_err(efx, drv, efx->net_dev,
+                         "VLAN %u already added\n", vid);
+               return -EALREADY;
+       }
+
+       vlan = kzalloc(sizeof(*vlan), GFP_KERNEL);
+       if (!vlan)
+               return -ENOMEM;
+
+       vlan->vid = vid;
+
+       for (i = 0; i < ARRAY_SIZE(vlan->uc); i++)
+               vlan->uc[i] = EFX_EF10_FILTER_ID_INVALID;
+       for (i = 0; i < ARRAY_SIZE(vlan->mc); i++)
+               vlan->mc[i] = EFX_EF10_FILTER_ID_INVALID;
+       vlan->ucdef = EFX_EF10_FILTER_ID_INVALID;
+       vlan->bcast = EFX_EF10_FILTER_ID_INVALID;
+       vlan->mcdef = EFX_EF10_FILTER_ID_INVALID;
+
+       list_add_tail(&vlan->list, &table->vlan_list);
+
+       if (efx_dev_registered(efx))
+               efx_ef10_filter_vlan_sync_rx_mode(efx, vlan);
+
+       return 0;
+}
+
+static void efx_ef10_filter_del_vlan_internal(struct efx_nic *efx,
+                                             struct efx_ef10_filter_vlan *vlan)
+{
+       unsigned int i;
+
+       /* See comment in efx_ef10_filter_table_remove() */
+       if (!efx_rwsem_assert_write_locked(&efx->filter_sem))
+               return;
+
+       list_del(&vlan->list);
+
+       for (i = 0; i < ARRAY_SIZE(vlan->uc); i++) {
+               if (vlan->uc[i] != EFX_EF10_FILTER_ID_INVALID)
+                       efx_ef10_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO,
+                                                     vlan->uc[i]);
+       }
+       for (i = 0; i < ARRAY_SIZE(vlan->mc); i++) {
+               if (vlan->mc[i] != EFX_EF10_FILTER_ID_INVALID)
+                       efx_ef10_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO,
+                                                     vlan->mc[i]);
+       }
+       if (vlan->ucdef != EFX_EF10_FILTER_ID_INVALID)
+               efx_ef10_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO,
+                                             vlan->ucdef);
+       if (vlan->bcast != EFX_EF10_FILTER_ID_INVALID)
+               efx_ef10_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO,
+                                             vlan->bcast);
+       if (vlan->mcdef != EFX_EF10_FILTER_ID_INVALID)
+               efx_ef10_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO,
+                                             vlan->mcdef);
+
+       kfree(vlan);
+}
+
+static void efx_ef10_filter_del_vlan(struct efx_nic *efx, u16 vid)
+{
+       struct efx_ef10_filter_vlan *vlan;
+
+       /* See comment in efx_ef10_filter_table_remove() */
+       if (!efx_rwsem_assert_write_locked(&efx->filter_sem))
+               return;
+
+       vlan = efx_ef10_filter_find_vlan(efx, vid);
+       if (!vlan) {
+               netif_err(efx, drv, efx->net_dev,
+                         "VLAN %u not found in filter state\n", vid);
+               return;
+       }
+
+       efx_ef10_filter_del_vlan_internal(efx, vlan);
 }
 
 static int efx_ef10_set_mac_address(struct efx_nic *efx)
@@ -4703,6 +5079,29 @@ static int efx_ef10_ptp_set_ts_config(struct efx_nic *efx,
        }
 }
 
+static int efx_ef10_vlan_rx_add_vid(struct efx_nic *efx, __be16 proto, u16 vid)
+{
+       if (proto != htons(ETH_P_8021Q))
+               return -EINVAL;
+
+       return efx_ef10_add_vlan(efx, vid);
+}
+
+static int efx_ef10_vlan_rx_kill_vid(struct efx_nic *efx, __be16 proto, u16 vid)
+{
+       if (proto != htons(ETH_P_8021Q))
+               return -EINVAL;
+
+       return efx_ef10_del_vlan(efx, vid);
+}
+
+#define EF10_OFFLOAD_FEATURES          \
+       (NETIF_F_IP_CSUM |              \
+        NETIF_F_HW_VLAN_CTAG_FILTER |  \
+        NETIF_F_IPV6_CSUM |            \
+        NETIF_F_RXHASH |               \
+        NETIF_F_NTUPLE)
+
 const struct efx_nic_type efx_hunt_a0_vf_nic_type = {
        .is_vf = true,
        .mem_bar = EFX_MEM_VF_BAR,
@@ -4780,6 +5179,8 @@ const struct efx_nic_type efx_hunt_a0_vf_nic_type = {
 #endif
        .ptp_write_host_time = efx_ef10_ptp_write_host_time_vf,
        .ptp_set_ts_config = efx_ef10_ptp_set_ts_config_vf,
+       .vlan_rx_add_vid = efx_ef10_vlan_rx_add_vid,
+       .vlan_rx_kill_vid = efx_ef10_vlan_rx_kill_vid,
 #ifdef CONFIG_SFC_SRIOV
        .vswitching_probe = efx_ef10_vswitching_probe_vf,
        .vswitching_restore = efx_ef10_vswitching_restore_vf,
@@ -4798,8 +5199,7 @@ const struct efx_nic_type efx_hunt_a0_vf_nic_type = {
        .always_rx_scatter = true,
        .max_interrupt_mode = EFX_INT_MODE_MSIX,
        .timer_period_max = 1 << ERF_DD_EVQ_IND_TIMER_VAL_WIDTH,
-       .offload_features = (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
-                            NETIF_F_RXHASH | NETIF_F_NTUPLE),
+       .offload_features = EF10_OFFLOAD_FEATURES,
        .mcdi_max_ver = 2,
        .max_rx_ip_filters = HUNT_FILTER_TBL_ROWS,
        .hwtstamp_filters = 1 << HWTSTAMP_FILTER_NONE |
@@ -4891,6 +5291,8 @@ const struct efx_nic_type efx_hunt_a0_nic_type = {
        .ptp_write_host_time = efx_ef10_ptp_write_host_time,
        .ptp_set_ts_sync_events = efx_ef10_ptp_set_ts_sync_events,
        .ptp_set_ts_config = efx_ef10_ptp_set_ts_config,
+       .vlan_rx_add_vid = efx_ef10_vlan_rx_add_vid,
+       .vlan_rx_kill_vid = efx_ef10_vlan_rx_kill_vid,
 #ifdef CONFIG_SFC_SRIOV
        .sriov_configure = efx_ef10_sriov_configure,
        .sriov_init = efx_ef10_sriov_init,
@@ -4919,8 +5321,7 @@ const struct efx_nic_type efx_hunt_a0_nic_type = {
        .always_rx_scatter = true,
        .max_interrupt_mode = EFX_INT_MODE_MSIX,
        .timer_period_max = 1 << ERF_DD_EVQ_IND_TIMER_VAL_WIDTH,
-       .offload_features = (NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
-                            NETIF_F_RXHASH | NETIF_F_NTUPLE),
+       .offload_features = EF10_OFFLOAD_FEATURES,
        .mcdi_max_ver = 2,
        .max_rx_ip_filters = HUNT_FILTER_TBL_ROWS,
        .hwtstamp_filters = 1 << HWTSTAMP_FILTER_NONE |
This page took 0.039386 seconds and 5 git commands to generate.