{
        struct ionic *ionic = from_timer(ionic, t, watchdog_timer);
        struct ionic_lif *lif = ionic->lif;
+       struct ionic_deferred_work *work;
        int hb;
 
        mod_timer(&ionic->watchdog_timer,
        if (hb >= 0 &&
            !test_bit(IONIC_LIF_F_FW_RESET, lif->state))
                ionic_link_status_check_request(lif, CAN_NOT_SLEEP);
+
+       if (test_bit(IONIC_LIF_F_FILTER_SYNC_NEEDED, lif->state)) {
+               work = kzalloc(sizeof(*work), GFP_ATOMIC);
+               if (!work) {
+                       netdev_err(lif->netdev, "rxmode change dropped\n");
+                       return;
+               }
+
+               work->type = IONIC_DW_TYPE_RX_MODE;
+               netdev_dbg(lif->netdev, "deferred: rx_mode\n");
+               ionic_lif_deferred_enqueue(&lif->deferred, work);
+       }
 }
 
 void ionic_init_devinfo(struct ionic *ionic)
 
                                      */
 };
 
-static void ionic_lif_rx_mode(struct ionic_lif *lif);
-static int ionic_lif_addr_add(struct ionic_lif *lif, const u8 *addr);
-static int ionic_lif_addr_del(struct ionic_lif *lif, const u8 *addr);
 static void ionic_link_status_check(struct ionic_lif *lif);
 static void ionic_lif_handle_fw_down(struct ionic_lif *lif);
 static void ionic_lif_handle_fw_up(struct ionic_lif *lif);
        if (err && err != -EEXIST)
                return err;
 
-       return ionic_rx_filter_save(lif, 0, qid, 0, &ctx);
+       spin_lock_bh(&lif->rx_filters.lock);
+       err = ionic_rx_filter_save(lif, 0, qid, 0, &ctx, IONIC_FILTER_STATE_SYNCED);
+       spin_unlock_bh(&lif->rx_filters.lock);
+
+       return err;
 }
 
 int ionic_lif_set_hwstamp_rxfilt(struct ionic_lif *lif, u64 pkt_class)
        ns->tx_errors = ns->tx_aborted_errors;
 }
 
-static int ionic_lif_addr_add(struct ionic_lif *lif, const u8 *addr)
+int ionic_lif_addr_add(struct ionic_lif *lif, const u8 *addr)
 {
        struct ionic_admin_ctx ctx = {
                .work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
                },
        };
        struct ionic_rx_filter *f;
-       int err;
+       int err = 0;
 
-       /* don't bother if we already have it */
        spin_lock_bh(&lif->rx_filters.lock);
        f = ionic_rx_filter_by_addr(lif, addr);
+       if (f) {
+               /* don't bother if we already have it and it is sync'd */
+               if (f->state == IONIC_FILTER_STATE_SYNCED) {
+                       spin_unlock_bh(&lif->rx_filters.lock);
+                       return 0;
+               }
+
+               /* mark preemptively as sync'd to block any parallel attempts */
+               f->state = IONIC_FILTER_STATE_SYNCED;
+       } else {
+               /* save as SYNCED to catch any DEL requests while processing */
+               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_SYNCED);
+       }
        spin_unlock_bh(&lif->rx_filters.lock);
-       if (f)
-               return 0;
+       if (err)
+               return err;
 
        netdev_dbg(lif->netdev, "rx_filter add ADDR %pM\n", addr);
 
-       memcpy(ctx.cmd.rx_filter_add.mac.addr, addr, ETH_ALEN);
        err = ionic_adminq_post_wait(lif, &ctx);
-       if (err && err != -EEXIST)
+
+       spin_lock_bh(&lif->rx_filters.lock);
+       if (err && err != -EEXIST) {
+               /* set the state back to NEW so we can try again later */
+               f = ionic_rx_filter_by_addr(lif, addr);
+               if (f && f->state == IONIC_FILTER_STATE_SYNCED)
+                       f->state = IONIC_FILTER_STATE_NEW;
+
+               spin_unlock_bh(&lif->rx_filters.lock);
                return err;
+       }
+
+       f = ionic_rx_filter_by_addr(lif, addr);
+       if (f && f->state == IONIC_FILTER_STATE_OLD) {
+               /* Someone requested a delete while we were adding
+                * so update the filter info with the results from the add
+                * and the data will be there for the delete on the next
+                * sync cycle.
+                */
+               err = ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx,
+                                          IONIC_FILTER_STATE_OLD);
+       } else {
+               err = ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx,
+                                          IONIC_FILTER_STATE_SYNCED);
+       }
 
-       return ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx);
+       spin_unlock_bh(&lif->rx_filters.lock);
+
+       return err;
 }
 
-static int ionic_lif_addr_del(struct ionic_lif *lif, const u8 *addr)
+int ionic_lif_addr_del(struct ionic_lif *lif, const u8 *addr)
 {
        struct ionic_admin_ctx ctx = {
                .work = COMPLETION_INITIALIZER_ONSTACK(ctx.work),
                },
        };
        struct ionic_rx_filter *f;
+       int state;
        int err;
 
        spin_lock_bh(&lif->rx_filters.lock);
        netdev_dbg(lif->netdev, "rx_filter del ADDR %pM (id %d)\n",
                   addr, f->filter_id);
 
+       state = f->state;
        ctx.cmd.rx_filter_del.filter_id = cpu_to_le32(f->filter_id);
        ionic_rx_filter_free(lif, f);
        spin_unlock_bh(&lif->rx_filters.lock);
 
-       err = ionic_adminq_post_wait(lif, &ctx);
-       if (err && err != -EEXIST)
-               return err;
+       if (state != IONIC_FILTER_STATE_NEW) {
+               err = ionic_adminq_post_wait(lif, &ctx);
+               if (err && err != -EEXIST)
+                       return err;
+       }
 
        return 0;
 }
 
 static int ionic_addr_add(struct net_device *netdev, const u8 *addr)
 {
-       return ionic_lif_addr(netdev_priv(netdev), addr, ADD_ADDR);
+       return ionic_lif_list_addr(netdev_priv(netdev), addr, ADD_ADDR);
 }
 
 static int ionic_addr_del(struct net_device *netdev, const u8 *addr)
 {
-       return ionic_lif_addr(netdev_priv(netdev), addr, DEL_ADDR);
+       return ionic_lif_list_addr(netdev_priv(netdev), addr, DEL_ADDR);
 }
 
-static void ionic_lif_rx_mode(struct ionic_lif *lif)
+void ionic_lif_rx_mode(struct ionic_lif *lif)
 {
        struct net_device *netdev = lif->netdev;
        unsigned int nfilters;
        rx_mode |= (nd_flags & IFF_PROMISC) ? IONIC_RX_MODE_F_PROMISC : 0;
        rx_mode |= (nd_flags & IFF_ALLMULTI) ? IONIC_RX_MODE_F_ALLMULTI : 0;
 
-       /* sync unicast addresses
-        * next check to see if we're in an overflow state
+       /* sync the mac filters */
+       ionic_rx_filter_sync(lif);
+
+       /* check for overflow state
         *    if so, we track that we overflowed and enable NIC PROMISC
         *    else if the overflow is set and not needed
         *       we remove our overflow flag and check the netdev flags
         *       to see if we can disable NIC PROMISC
         */
-       __dev_uc_sync(netdev, ionic_addr_add, ionic_addr_del);
        nfilters = le32_to_cpu(lif->identity->eth.max_ucast_filters);
        if (netdev_uc_count(netdev) + 1 > nfilters) {
                rx_mode |= IONIC_RX_MODE_F_PROMISC;
                        rx_mode &= ~IONIC_RX_MODE_F_PROMISC;
        }
 
-       /* same for multicast */
-       __dev_mc_sync(netdev, ionic_addr_add, ionic_addr_del);
        nfilters = le32_to_cpu(lif->identity->eth.max_mcast_filters);
        if (netdev_mc_count(netdev) > nfilters) {
                rx_mode |= IONIC_RX_MODE_F_ALLMULTI;
        struct ionic_lif *lif = netdev_priv(netdev);
        struct ionic_deferred_work *work;
 
+       /* Sync the kernel filter list with the driver filter list */
+       __dev_uc_sync(netdev, ionic_addr_add, ionic_addr_del);
+       __dev_mc_sync(netdev, ionic_addr_add, ionic_addr_del);
+
+       /* Shove off the rest of the rxmode work to the work task
+        * which will include syncing the filters to the firmware.
+        */
        work = kzalloc(sizeof(*work), GFP_ATOMIC);
        if (!work) {
                netdev_err(lif->netdev, "rxmode change dropped\n");
        if (!is_zero_ether_addr(netdev->dev_addr)) {
                netdev_info(netdev, "deleting mac addr %pM\n",
                            netdev->dev_addr);
-               ionic_addr_del(netdev, netdev->dev_addr);
+               ionic_lif_addr_del(netdev_priv(netdev), netdev->dev_addr);
        }
 
        eth_commit_mac_addr_change(netdev, addr);
        netdev_info(netdev, "updating mac addr %pM\n", mac);
 
-       return ionic_addr_add(netdev, mac);
+       return ionic_lif_addr_add(netdev_priv(netdev), mac);
 }
 
 static void ionic_stop_queues_reconfig(struct ionic_lif *lif)
        if (err)
                return err;
 
-       return ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx);
+       spin_lock_bh(&lif->rx_filters.lock);
+       err = ionic_rx_filter_save(lif, 0, IONIC_RXQ_INDEX_ANY, 0, &ctx,
+                                  IONIC_FILTER_STATE_SYNCED);
+       spin_unlock_bh(&lif->rx_filters.lock);
+
+       return err;
 }
 
 static int ionic_vlan_rx_kill_vid(struct net_device *netdev, __be16 proto,
 
        IONIC_LIF_F_SW_DEBUG_STATS,
        IONIC_LIF_F_UP,
        IONIC_LIF_F_LINK_CHECK_REQUESTED,
+       IONIC_LIF_F_FILTER_SYNC_NEEDED,
        IONIC_LIF_F_FW_RESET,
        IONIC_LIF_F_SPLIT_INTR,
        IONIC_LIF_F_BROKEN,
 int ionic_lif_init(struct ionic_lif *lif);
 void ionic_lif_free(struct ionic_lif *lif);
 void ionic_lif_deinit(struct ionic_lif *lif);
+
+int ionic_lif_addr_add(struct ionic_lif *lif, const u8 *addr);
+int ionic_lif_addr_del(struct ionic_lif *lif, const u8 *addr);
+
 int ionic_lif_register(struct ionic_lif *lif);
 void ionic_lif_unregister(struct ionic_lif *lif);
 int ionic_lif_identify(struct ionic *ionic, u8 lif_type,
 
 int ionic_lif_rss_config(struct ionic_lif *lif, u16 types,
                         const u8 *key, const u32 *indir);
+void ionic_lif_rx_mode(struct ionic_lif *lif);
 int ionic_reconfigure_queues(struct ionic_lif *lif,
                             struct ionic_queue_params *qparam);
 
 
 #include <linux/netdevice.h>
 #include <linux/dynamic_debug.h>
 #include <linux/etherdevice.h>
+#include <linux/list.h>
 
 #include "ionic.h"
 #include "ionic_lif.h"
 }
 
 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;
 
        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);
                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);
        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);
        head = &lif->rx_filters.by_id[key];
        hlist_add_head(&f->by_id, head);
 
-       spin_unlock_bh(&lif->rx_filters.lock);
-
        return 0;
 }
 
 
        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);
+       }
+}
 
 #define _IONIC_RX_FILTER_H_
 
 #define IONIC_RXQ_INDEX_ANY            (0xFFFF)
+
+enum ionic_filter_state {
+       IONIC_FILTER_STATE_SYNCED,
+       IONIC_FILTER_STATE_NEW,
+       IONIC_FILTER_STATE_OLD,
+};
+
 struct ionic_rx_filter {
        u32 flow_id;
        u32 filter_id;
        u16 rxq_index;
+       enum ionic_filter_state state;
        struct ionic_rx_filter_add_cmd cmd;
        struct hlist_node by_hash;
        struct hlist_node by_id;
 int ionic_rx_filters_init(struct ionic_lif *lif);
 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 ionic_rx_filter *ionic_rx_filter_by_vlan(struct ionic_lif *lif, u16 vid);
 struct ionic_rx_filter *ionic_rx_filter_by_addr(struct ionic_lif *lif, const u8 *addr);
 struct ionic_rx_filter *ionic_rx_filter_rxsteer(struct ionic_lif *lif);
+void ionic_rx_filter_sync(struct ionic_lif *lif);
+int ionic_lif_list_addr(struct ionic_lif *lif, const u8 *addr, bool mode);
+int ionic_rx_filters_need_sync(struct ionic_lif *lif);
 
 #endif /* _IONIC_RX_FILTER_H_ */