|
@@ -54,8 +54,15 @@ enum {
|
|
|
#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];
|
|
@@ -94,7 +101,7 @@ struct efx_ef10_filter_table {
|
|
|
bool mc_promisc;
|
|
|
/* Whether in multicast promiscuous mode when last changed */
|
|
|
bool mc_promisc_last;
|
|
|
- struct efx_ef10_filter_vlan vlan;
|
|
|
+ struct list_head vlan_list;
|
|
|
};
|
|
|
|
|
|
/* An arbitrary search limit for the software hash table */
|
|
@@ -102,6 +109,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)
|
|
|
{
|
|
@@ -287,6 +298,96 @@ 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) {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 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);
|
|
@@ -433,8 +534,20 @@ 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;
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
+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:
|
|
@@ -688,6 +801,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);
|
|
@@ -3736,14 +3852,30 @@ 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_filter_vlan *vlan;
|
|
|
- unsigned int i;
|
|
|
+ struct efx_ef10_vlan *vlan;
|
|
|
size_t outlen;
|
|
|
int rc;
|
|
|
|
|
@@ -3795,21 +3927,23 @@ static int efx_ef10_filter_table_probe(struct efx_nic *efx)
|
|
|
goto fail;
|
|
|
}
|
|
|
|
|
|
- vlan = &table->vlan;
|
|
|
- vlan->vid = EFX_FILTER_VID_UNSPEC;
|
|
|
- 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;
|
|
|
table->mc_promisc_last = false;
|
|
|
+ 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;
|
|
@@ -3878,6 +4012,7 @@ 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
|
|
@@ -3947,13 +4082,18 @@ static void _efx_ef10_filter_vlan_mark_old(struct efx_nic *efx,
|
|
|
efx_ef10_filter_mark_one_old(efx, &vlan->mcdef);
|
|
|
}
|
|
|
|
|
|
-/* Mark old filters that may need to be removed */
|
|
|
+/* 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);
|
|
|
- _efx_ef10_filter_vlan_mark_old(efx, &table->vlan);
|
|
|
+ list_for_each_entry(vlan, &table->vlan_list, list)
|
|
|
+ _efx_ef10_filter_vlan_mark_old(efx, vlan);
|
|
|
spin_unlock_bh(&efx->filter_lock);
|
|
|
}
|
|
|
|
|
@@ -4254,30 +4394,11 @@ 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;
|
|
|
- struct efx_ef10_filter_vlan *vlan;
|
|
|
-
|
|
|
- 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);
|
|
|
-
|
|
|
- vlan = &table->vlan;
|
|
|
|
|
|
/* Insert/renew unicast filters */
|
|
|
if (table->uc_promisc) {
|
|
@@ -4332,11 +4453,145 @@ static void efx_ef10_filter_sync_rx_mode(struct efx_nic *efx)
|
|
|
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;
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ list_for_each_entry(vlan, &table->vlan_list, list)
|
|
|
+ efx_ef10_filter_vlan_sync_rx_mode(efx, vlan);
|
|
|
|
|
|
efx_ef10_filter_remove_old(efx);
|
|
|
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)
|
|
|
{
|
|
|
MCDI_DECLARE_BUF(inbuf, MC_CMD_VADAPTOR_SET_MAC_IN_LEN);
|