summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c
diff options
context:
space:
mode:
authorShannon Nelson <snelson@pensando.io>2021-08-25 18:24:48 -0700
committerDavid S. Miller <davem@davemloft.net>2021-08-26 09:41:50 +0100
commit969f843946041a8ac10a5af06127a68ab7880ad5 (patch)
tree7d1921d57f5803d98d7054f075f0c6f702c9a5b9 /drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c
parentb941ea057177daf7dd661959803f351808754e6e (diff)
ionic: sync the filters in the work task
In order to separate the atomic needs of __dev_uc_sync() and __dev_mc_sync() from the safe rx_mode handling, we need to have the ndo handler manipulate the driver's filter list, and later have the driver sync the filters to the firmware, outside of the atomic context. Here we put __dev_mc_sync() and __dev_uc_sync() back into the ndo callback to give them their netif_addr_lock context and have them update the driver's filter list, flagging changes that should be made to the device filter list. Later, in the rx_mode handler, we read those hints and sync up the device's list as needed. It is possible for multiple add/delete requests to come from the stack before the rx_mode task processes the list, but the handling of the sync status flag should keep everything sorted correctly. For example, if a delete of an existing filter is followed by another add before the rx_mode task is run, as can happen when going in and out of a bond, the add will cancel the delete and no actual changes will be sent to the device. We also add a check in the watchdog to see if there are any stray unsync'd filters, possibly left over from a filter overflow and waiting to get sync'd after some other filter gets removed to make room. Signed-off-by: Shannon Nelson <snelson@pensando.io> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c')
-rw-r--r--drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c143
1 files changed, 134 insertions, 9 deletions
diff --git a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c
index d71316d9ded2..7e3a5634c161 100644
--- a/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c
+++ b/drivers/net/ethernet/pensando/ionic/ionic_rx_filter.c
@@ -4,6 +4,7 @@
#include <linux/netdevice.h>
#include <linux/dynamic_debug.h>
#include <linux/etherdevice.h>
+#include <linux/list.h>
#include "ionic.h"
#include "ionic_lif.h"
@@ -120,11 +121,12 @@ void ionic_rx_filters_deinit(struct ionic_lif *lif)
}
int ionic_rx_filter_save(struct ionic_lif *lif, u32 flow_id, u16 rxq_index,
- u32 hash, struct ionic_admin_ctx *ctx)
+ u32 hash, struct ionic_admin_ctx *ctx,
+ enum ionic_filter_state state)
{
struct device *dev = lif->ionic->dev;
struct ionic_rx_filter_add_cmd *ac;
- struct ionic_rx_filter *f;
+ struct ionic_rx_filter *f = NULL;
struct hlist_head *head;
unsigned int key;
@@ -133,9 +135,11 @@ int ionic_rx_filter_save(struct ionic_lif *lif, u32 flow_id, u16 rxq_index,
switch (le16_to_cpu(ac->match)) {
case IONIC_RX_FILTER_MATCH_VLAN:
key = le16_to_cpu(ac->vlan.vlan);
+ f = ionic_rx_filter_by_vlan(lif, le16_to_cpu(ac->vlan.vlan));
break;
case IONIC_RX_FILTER_MATCH_MAC:
key = *(u32 *)ac->mac.addr;
+ f = ionic_rx_filter_by_addr(lif, ac->mac.addr);
break;
case IONIC_RX_FILTER_MATCH_MAC_VLAN:
key = le16_to_cpu(ac->mac_vlan.vlan);
@@ -147,12 +151,19 @@ int ionic_rx_filter_save(struct ionic_lif *lif, u32 flow_id, u16 rxq_index,
return -EINVAL;
}
- f = devm_kzalloc(dev, sizeof(*f), GFP_KERNEL);
- if (!f)
- return -ENOMEM;
+ if (f) {
+ /* remove from current linking so we can refresh it */
+ hlist_del(&f->by_id);
+ hlist_del(&f->by_hash);
+ } else {
+ f = devm_kzalloc(dev, sizeof(*f), GFP_ATOMIC);
+ if (!f)
+ return -ENOMEM;
+ }
f->flow_id = flow_id;
f->filter_id = le32_to_cpu(ctx->comp.rx_filter_add.filter_id);
+ f->state = state;
f->rxq_index = rxq_index;
memcpy(&f->cmd, ac, sizeof(f->cmd));
netdev_dbg(lif->netdev, "rx_filter add filter_id %d\n", f->filter_id);
@@ -160,8 +171,6 @@ int ionic_rx_filter_save(struct ionic_lif *lif, u32 flow_id, u16 rxq_index,
INIT_HLIST_NODE(&f->by_hash);
INIT_HLIST_NODE(&f->by_id);
- spin_lock_bh(&lif->rx_filters.lock);
-
key = hash_32(key, IONIC_RX_FILTER_HASH_BITS);
head = &lif->rx_filters.by_hash[key];
hlist_add_head(&f->by_hash, head);
@@ -170,8 +179,6 @@ int ionic_rx_filter_save(struct ionic_lif *lif, u32 flow_id, u16 rxq_index,
head = &lif->rx_filters.by_id[key];
hlist_add_head(&f->by_id, head);
- spin_unlock_bh(&lif->rx_filters.lock);
-
return 0;
}
@@ -231,3 +238,121 @@ struct ionic_rx_filter *ionic_rx_filter_rxsteer(struct ionic_lif *lif)
return NULL;
}
+
+int ionic_lif_list_addr(struct ionic_lif *lif, const u8 *addr, bool mode)
+{
+ struct ionic_rx_filter *f;
+ int err;
+
+ spin_lock_bh(&lif->rx_filters.lock);
+
+ f = ionic_rx_filter_by_addr(lif, addr);
+ if (mode == ADD_ADDR && !f) {
+ struct ionic_admin_ctx ctx = {
+ .work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
+ .cmd.rx_filter_add = {
+ .opcode = IONIC_CMD_RX_FILTER_ADD,
+ .lif_index = cpu_to_le16(lif->index),
+ .match = cpu_to_le16(IONIC_RX_FILTER_MATCH_MAC),
+ },
+ };
+
+ memcpy(ctx.cmd.rx_filter_add.mac.addr, addr, ETH_ALEN);
+ err = ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx,
+ IONIC_FILTER_STATE_NEW);
+ if (err) {
+ spin_unlock_bh(&lif->rx_filters.lock);
+ return err;
+ }
+
+ } else if (mode == ADD_ADDR && f) {
+ if (f->state == IONIC_FILTER_STATE_OLD)
+ f->state = IONIC_FILTER_STATE_SYNCED;
+
+ } else if (mode == DEL_ADDR && f) {
+ if (f->state == IONIC_FILTER_STATE_NEW)
+ ionic_rx_filter_free(lif, f);
+ else if (f->state == IONIC_FILTER_STATE_SYNCED)
+ f->state = IONIC_FILTER_STATE_OLD;
+ } else if (mode == DEL_ADDR && !f) {
+ spin_unlock_bh(&lif->rx_filters.lock);
+ return -ENOENT;
+ }
+
+ spin_unlock_bh(&lif->rx_filters.lock);
+
+ set_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state);
+
+ return 0;
+}
+
+struct sync_item {
+ struct list_head list;
+ struct ionic_rx_filter f;
+};
+
+void ionic_rx_filter_sync(struct ionic_lif *lif)
+{
+ struct device *dev = lif->ionic->dev;
+ struct list_head sync_add_list;
+ struct list_head sync_del_list;
+ struct sync_item *sync_item;
+ struct ionic_rx_filter *f;
+ struct hlist_head *head;
+ struct hlist_node *tmp;
+ struct sync_item *spos;
+ unsigned int i;
+
+ INIT_LIST_HEAD(&sync_add_list);
+ INIT_LIST_HEAD(&sync_del_list);
+
+ clear_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state);
+
+ /* Copy the filters to be added and deleted
+ * into a separate local list that needs no locking.
+ */
+ spin_lock_bh(&lif->rx_filters.lock);
+ for (i = 0; i < IONIC_RX_FILTER_HLISTS; i++) {
+ head = &lif->rx_filters.by_id[i];
+ hlist_for_each_entry_safe(f, tmp, head, by_id) {
+ if (f->state == IONIC_FILTER_STATE_NEW ||
+ f->state == IONIC_FILTER_STATE_OLD) {
+ sync_item = devm_kzalloc(dev, sizeof(*sync_item),
+ GFP_KERNEL);
+ if (!sync_item)
+ goto loop_out;
+
+ sync_item->f = *f;
+
+ if (f->state == IONIC_FILTER_STATE_NEW)
+ list_add(&sync_item->list, &sync_add_list);
+ else
+ list_add(&sync_item->list, &sync_del_list);
+ }
+ }
+ }
+loop_out:
+ spin_unlock_bh(&lif->rx_filters.lock);
+
+ /* If the add or delete fails, it won't get marked as sync'd
+ * and will be tried again in the next sync action.
+ * Do the deletes first in case we're in an overflow state and
+ * they can clear room for some new filters
+ */
+ list_for_each_entry_safe(sync_item, spos, &sync_del_list, list) {
+ (void)ionic_lif_addr_del(lif, sync_item->f.cmd.mac.addr);
+
+ list_del(&sync_item->list);
+ devm_kfree(dev, sync_item);
+ }
+
+ list_for_each_entry_safe(sync_item, spos, &sync_add_list, list) {
+ (void)ionic_lif_addr_add(lif, sync_item->f.cmd.mac.addr);
+
+ if (sync_item->f.state != IONIC_FILTER_STATE_SYNCED)
+ set_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state);
+
+ list_del(&sync_item->list);
+ devm_kfree(dev, sync_item);
+ }
+}