]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
Merge branch 'net-ethernet-ti-cpsw-fix-vlan-mcast'
authorDavid S. Miller <davem@davemloft.net>
Fri, 9 Nov 2018 04:30:58 +0000 (20:30 -0800)
committerDavid S. Miller <davem@davemloft.net>
Fri, 9 Nov 2018 04:30:58 +0000 (20:30 -0800)
Ivan Khoronzhuk says:

====================
net: ethernet: ti: cpsw: fix vlan mcast

The cpsw holds separate mcast entires for vlan entries. At this moment
driver adds only not vlan mcast addresses, omitting vlan/mcast entries.
As result mcast for vlans doesn't work. It can be fixed by adding same
mcast entries for every created vlan, but this patchseries uses more
sophisticated way and allows to create mcast entries only for vlans
that really require it. Generic functions from this series can be
reused for fixing vlan and macvlan unicast.

Simple example of ALE table before and after this series, having same
mcast entries as for vlan 100 as for real device (reserved vlan 2),
and one mcast address only for vlan 100 - 01:1b:19:00:00:00.

<---- Before this patchset ---->
vlan , vid = 2, untag_force = 0x5, reg_mcast = 0x5, mem_list = 0x5
mcast, vid = 2, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 2, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
vlan , vid = 0, untag_force = 0x7, reg_mcast = 0x0, mem_list = 0x7
mcast, vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:00:5e:00:00:01, port_mask = 0x1
vlan , vid = 1, untag_force = 0x3, reg_mcast = 0x3, mem_list = 0x3
mcast, vid = 1, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 1, addr = 74:da:ea:47:7d:9c, persistant, port_num = 0x0
mcast, vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 2, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 1, addr = 33:33:ff:47:7d:9c, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:fb, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:fc, port_mask = 0x1
vlan , vid = 100, untag_force = 0x0, reg_mcast = 0x5, mem_list = 0x5
ucast, vid = 100, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
mcast, vid = 100, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
mcast, vid = 2, addr = 01:1b:19:00:00:00, port_mask = 0x1
 ^^^
 Here mcast entry (ptpl2), has to be added only for vlan 100
 but added for reserved vlan 2...that's not enough.

<---- After this patchset ---->
vlan , vid = 2, untag_force = 0x5, reg_mcast = 0x5, mem_list = 0x5
mcast, vid = 2, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 2, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
vlan , vid = 0, untag_force = 0x7, reg_mcast = 0x0, mem_list = 0x7
mcast, vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:00:5e:00:00:01, port_mask = 0x1
vlan , vid = 1, untag_force = 0x3, reg_mcast = 0x3, mem_list = 0x3
mcast, vid = 1, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
ucast, vid = 1, addr = 74:da:ea:47:7d:9c, persistant, port_num = 0x0
mcast, vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 1, addr = 01:00:5e:00:00:01, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 2, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 1, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 2, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
mcast, vid = 1, addr = 33:33:ff:47:7d:9c, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 2, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 1, addr = 33:33:00:01:00:03, port_mask = 0x1
vlan , vid = 100, untag_force = 0x0, reg_mcast = 0x5, mem_list = 0x5
ucast, vid = 100, addr = 74:da:ea:47:7d:9d, persistant, port_num = 0x0
mcast, vid = 100, addr = ff:ff:ff:ff:ff:ff, port_mask = 0x1
mcast, vid = 100, addr = 33:33:00:00:00:01, port_mask = 0x1
mcast, vid = 100, addr = 01:00:5e:00:00:01, port_mask = 0x1
mcast, vid = 100, addr = 33:33:ff:47:7d:9d, port_mask = 0x1
mcast, vid = 100, addr = 01:80:c2:00:00:00, port_mask = 0x1
mcast, vid = 100, addr = 01:80:c2:00:00:03, port_mask = 0x1
mcast, vid = 100, addr = 01:80:c2:00:00:0e, port_mask = 0x1
mcast, vid = 100, addr = 33:33:00:00:00:fb, port_mask = 0x1
mcast, vid = 100, addr = 33:33:00:01:00:03, port_mask = 0x1
mcast, vid = 100, addr = 01:1b:19:00:00:00, port_mask = 0x1
 ^^^
    Here mcast entry (ptpl2), is added only for vlan 100
    as it should be.

Based on net-next/master

v2..v1:
  net: ethernet: ti: cpsw: fix vlan mcast
- removed limit for legacy switch cpsw mode
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/ti/cpsw.c
include/linux/if_vlan.h
include/linux/netdevice.h
net/8021q/vlan_core.c
net/core/dev_addr_lists.c

index 500f7ed8c58c3b36480c3b08ea37c79f220024e0..9434fd5a5477234889020e3fefe072f343a1a133 100644 (file)
@@ -565,26 +565,14 @@ static const struct cpsw_stats cpsw_gstrings_ch_stats[] = {
                                (func)(slave++, ##arg);                 \
        } while (0)
 
+static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
+                                   __be16 proto, u16 vid);
+
 static inline int cpsw_get_slave_port(u32 slave_num)
 {
        return slave_num + 1;
 }
 
-static void cpsw_add_mcast(struct cpsw_priv *priv, const u8 *addr)
-{
-       struct cpsw_common *cpsw = priv->cpsw;
-
-       if (cpsw->data.dual_emac) {
-               struct cpsw_slave *slave = cpsw->slaves + priv->emac_port;
-
-               cpsw_ale_add_mcast(cpsw->ale, addr, ALE_PORT_HOST,
-                                  ALE_VLAN, slave->port_vlan, 0);
-               return;
-       }
-
-       cpsw_ale_add_mcast(cpsw->ale, addr, ALE_ALL_PORTS, 0, 0, 0);
-}
-
 static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
 {
        struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
@@ -640,7 +628,7 @@ static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
 
                        /* Clear all mcast from ALE */
                        cpsw_ale_flush_multicast(ale, ALE_ALL_PORTS, -1);
-                       __dev_mc_unsync(ndev, NULL);
+                       __hw_addr_ref_unsync_dev(&ndev->mc, ndev, NULL);
 
                        /* Flood All Unicast Packets to Host port */
                        cpsw_ale_control_set(ale, 0, ALE_P0_UNI_FLOOD, 1);
@@ -661,29 +649,148 @@ static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
        }
 }
 
-static int cpsw_add_mc_addr(struct net_device *ndev, const u8 *addr)
+struct addr_sync_ctx {
+       struct net_device *ndev;
+       const u8 *addr;         /* address to be synched */
+       int consumed;           /* number of address instances */
+       int flush;              /* flush flag */
+};
+
+/**
+ * cpsw_set_mc - adds multicast entry to the table if it's not added or deletes
+ * if it's not deleted
+ * @ndev: device to sync
+ * @addr: address to be added or deleted
+ * @vid: vlan id, if vid < 0 set/unset address for real device
+ * @add: add address if the flag is set or remove otherwise
+ */
+static int cpsw_set_mc(struct net_device *ndev, const u8 *addr,
+                      int vid, int add)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
+       struct cpsw_common *cpsw = priv->cpsw;
+       int mask, flags, ret;
+
+       if (vid < 0) {
+               if (cpsw->data.dual_emac)
+                       vid = cpsw->slaves[priv->emac_port].port_vlan;
+               else
+                       vid = 0;
+       }
+
+       mask = cpsw->data.dual_emac ? ALE_PORT_HOST : ALE_ALL_PORTS;
+       flags = vid ? ALE_VLAN : 0;
+
+       if (add)
+               ret = cpsw_ale_add_mcast(cpsw->ale, addr, mask, flags, vid, 0);
+       else
+               ret = cpsw_ale_del_mcast(cpsw->ale, addr, 0, flags, vid);
+
+       return ret;
+}
+
+static int cpsw_update_vlan_mc(struct net_device *vdev, int vid, void *ctx)
+{
+       struct addr_sync_ctx *sync_ctx = ctx;
+       struct netdev_hw_addr *ha;
+       int found = 0, ret = 0;
+
+       if (!vdev || !(vdev->flags & IFF_UP))
+               return 0;
+
+       /* vlan address is relevant if its sync_cnt != 0 */
+       netdev_for_each_mc_addr(ha, vdev) {
+               if (ether_addr_equal(ha->addr, sync_ctx->addr)) {
+                       found = ha->sync_cnt;
+                       break;
+               }
+       }
+
+       if (found)
+               sync_ctx->consumed++;
+
+       if (sync_ctx->flush) {
+               if (!found)
+                       cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0);
+               return 0;
+       }
+
+       if (found)
+               ret = cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 1);
+
+       return ret;
+}
+
+static int cpsw_add_mc_addr(struct net_device *ndev, const u8 *addr, int num)
+{
+       struct addr_sync_ctx sync_ctx;
+       int ret;
+
+       sync_ctx.consumed = 0;
+       sync_ctx.addr = addr;
+       sync_ctx.ndev = ndev;
+       sync_ctx.flush = 0;
+
+       ret = vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx);
+       if (sync_ctx.consumed < num && !ret)
+               ret = cpsw_set_mc(ndev, addr, -1, 1);
+
+       return ret;
+}
+
+static int cpsw_del_mc_addr(struct net_device *ndev, const u8 *addr, int num)
+{
+       struct addr_sync_ctx sync_ctx;
+
+       sync_ctx.consumed = 0;
+       sync_ctx.addr = addr;
+       sync_ctx.ndev = ndev;
+       sync_ctx.flush = 1;
+
+       vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx);
+       if (sync_ctx.consumed == num)
+               cpsw_set_mc(ndev, addr, -1, 0);
 
-       cpsw_add_mcast(priv, addr);
        return 0;
 }
 
-static int cpsw_del_mc_addr(struct net_device *ndev, const u8 *addr)
+static int cpsw_purge_vlan_mc(struct net_device *vdev, int vid, void *ctx)
 {
-       struct cpsw_priv *priv = netdev_priv(ndev);
-       struct cpsw_common *cpsw = priv->cpsw;
-       int vid, flags;
+       struct addr_sync_ctx *sync_ctx = ctx;
+       struct netdev_hw_addr *ha;
+       int found = 0;
 
-       if (cpsw->data.dual_emac) {
-               vid = cpsw->slaves[priv->emac_port].port_vlan;
-               flags = ALE_VLAN;
-       } else {
-               vid = 0;
-               flags = 0;
+       if (!vdev || !(vdev->flags & IFF_UP))
+               return 0;
+
+       /* vlan address is relevant if its sync_cnt != 0 */
+       netdev_for_each_mc_addr(ha, vdev) {
+               if (ether_addr_equal(ha->addr, sync_ctx->addr)) {
+                       found = ha->sync_cnt;
+                       break;
+               }
        }
 
-       cpsw_ale_del_mcast(cpsw->ale, addr, 0, flags, vid);
+       if (!found)
+               return 0;
+
+       sync_ctx->consumed++;
+       cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0);
+       return 0;
+}
+
+static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num)
+{
+       struct addr_sync_ctx sync_ctx;
+
+       sync_ctx.addr = addr;
+       sync_ctx.ndev = ndev;
+       sync_ctx.consumed = 0;
+
+       vlan_for_each(ndev, cpsw_purge_vlan_mc, &sync_ctx);
+       if (sync_ctx.consumed < num)
+               cpsw_set_mc(ndev, addr, -1, 0);
+
        return 0;
 }
 
@@ -704,7 +811,9 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev)
        /* Restore allmulti on vlans if necessary */
        cpsw_ale_set_allmulti(cpsw->ale, ndev->flags & IFF_ALLMULTI);
 
-       __dev_mc_sync(ndev, cpsw_add_mc_addr, cpsw_del_mc_addr);
+       /* add/remove mcast address either for real netdev or for vlan */
+       __hw_addr_ref_sync_dev(&ndev->mc, ndev, cpsw_add_mc_addr,
+                              cpsw_del_mc_addr);
 }
 
 static void cpsw_intr_enable(struct cpsw_common *cpsw)
@@ -1845,9 +1954,23 @@ static void cpsw_mqprio_resume(struct cpsw_slave *slave, struct cpsw_priv *priv)
        slave_write(slave, tx_prio_map, tx_prio_rg);
 }
 
+static int cpsw_restore_vlans(struct net_device *vdev, int vid, void *arg)
+{
+       struct cpsw_priv *priv = arg;
+
+       if (!vdev)
+               return 0;
+
+       cpsw_ndo_vlan_rx_add_vid(priv->ndev, 0, vid);
+       return 0;
+}
+
 /* restore resources after port reset */
 static void cpsw_restore(struct cpsw_priv *priv)
 {
+       /* restore vlan configurations */
+       vlan_for_each(priv->ndev, cpsw_restore_vlans, priv);
+
        /* restore MQPRIO offload */
        for_each_slave(priv, cpsw_mqprio_resume, priv);
 
@@ -1964,7 +2087,7 @@ static int cpsw_ndo_stop(struct net_device *ndev)
        struct cpsw_common *cpsw = priv->cpsw;
 
        cpsw_info(priv, ifdown, "shutting down cpsw device\n");
-       __dev_mc_unsync(priv->ndev, cpsw_del_mc_addr);
+       __hw_addr_ref_unsync_dev(&ndev->mc, ndev, cpsw_purge_all_mc);
        netif_tx_stop_all_queues(priv->ndev);
        netif_carrier_off(priv->ndev);
 
@@ -2415,6 +2538,7 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
                                  HOST_PORT_NUM, ALE_VLAN, vid);
        ret |= cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast,
                                  0, ALE_VLAN, vid);
+       ret |= cpsw_ale_flush_multicast(cpsw->ale, 0, vid);
 err:
        pm_runtime_put(cpsw->dev);
        return ret;
index 03b08ffded076077620b2daaf02e128bd900833b..1be5230921b5726b9677ed060028dcb4ac126da8 100644 (file)
@@ -133,6 +133,9 @@ struct vlan_pcpu_stats {
 
 extern struct net_device *__vlan_find_dev_deep_rcu(struct net_device *real_dev,
                                               __be16 vlan_proto, u16 vlan_id);
+extern int vlan_for_each(struct net_device *dev,
+                        int (*action)(struct net_device *dev, int vid,
+                                      void *arg), void *arg);
 extern struct net_device *vlan_dev_real_dev(const struct net_device *dev);
 extern u16 vlan_dev_vlan_id(const struct net_device *dev);
 extern __be16 vlan_dev_vlan_proto(const struct net_device *dev);
@@ -236,6 +239,14 @@ __vlan_find_dev_deep_rcu(struct net_device *real_dev,
        return NULL;
 }
 
+static inline int
+vlan_for_each(struct net_device *dev,
+             int (*action)(struct net_device *dev, int vid, void *arg),
+             void *arg)
+{
+       return 0;
+}
+
 static inline struct net_device *vlan_dev_real_dev(const struct net_device *dev)
 {
        BUG();
index 857f8abf7b91bc79731873fc8f68e31f6bff4d03..487fa5e0e1652da8ea91253c5c3b84ddcb0a8925 100644 (file)
@@ -4068,6 +4068,16 @@ int __hw_addr_sync_dev(struct netdev_hw_addr_list *list,
                       int (*sync)(struct net_device *, const unsigned char *),
                       int (*unsync)(struct net_device *,
                                     const unsigned char *));
+int __hw_addr_ref_sync_dev(struct netdev_hw_addr_list *list,
+                          struct net_device *dev,
+                          int (*sync)(struct net_device *,
+                                      const unsigned char *, int),
+                          int (*unsync)(struct net_device *,
+                                        const unsigned char *, int));
+void __hw_addr_ref_unsync_dev(struct netdev_hw_addr_list *list,
+                             struct net_device *dev,
+                             int (*unsync)(struct net_device *,
+                                           const unsigned char *, int));
 void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,
                          struct net_device *dev,
                          int (*unsync)(struct net_device *,
index 4f60e86f4b8d33618c3ef26058c8aa94a2a1bbcc..6308b5427a6693b003533619c77173af23bba227 100644 (file)
@@ -223,6 +223,33 @@ static int vlan_kill_rx_filter_info(struct net_device *dev, __be16 proto, u16 vi
                return -ENODEV;
 }
 
+int vlan_for_each(struct net_device *dev,
+                 int (*action)(struct net_device *dev, int vid, void *arg),
+                 void *arg)
+{
+       struct vlan_vid_info *vid_info;
+       struct vlan_info *vlan_info;
+       struct net_device *vdev;
+       int ret;
+
+       ASSERT_RTNL();
+
+       vlan_info = rtnl_dereference(dev->vlan_info);
+       if (!vlan_info)
+               return 0;
+
+       list_for_each_entry(vid_info, &vlan_info->vid_list, list) {
+               vdev = vlan_group_get_device(&vlan_info->grp, vid_info->proto,
+                                            vid_info->vid);
+               ret = action(vdev, vid_info->vid, arg);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(vlan_for_each);
+
 int vlan_filter_push_vids(struct vlan_info *vlan_info, __be16 proto)
 {
        struct net_device *real_dev = vlan_info->real_dev;
index d884d8f5f0e54328ef46fe94715703287fb93aba..81a8cd4ea3bdf98778574c0fbe7cc2793df40e4d 100644 (file)
@@ -277,6 +277,103 @@ int __hw_addr_sync_dev(struct netdev_hw_addr_list *list,
 }
 EXPORT_SYMBOL(__hw_addr_sync_dev);
 
+/**
+ *  __hw_addr_ref_sync_dev - Synchronize device's multicast address list taking
+ *  into account references
+ *  @list: address list to synchronize
+ *  @dev:  device to sync
+ *  @sync: function to call if address or reference on it should be added
+ *  @unsync: function to call if address or some reference on it should removed
+ *
+ *  This function is intended to be called from the ndo_set_rx_mode
+ *  function of devices that require explicit address or references on it
+ *  add/remove notifications. The unsync function may be NULL in which case
+ *  the addresses or references on it requiring removal will simply be
+ *  removed without any notification to the device. That is responsibility of
+ *  the driver to identify and distribute address or references on it between
+ *  internal address tables.
+ **/
+int __hw_addr_ref_sync_dev(struct netdev_hw_addr_list *list,
+                          struct net_device *dev,
+                          int (*sync)(struct net_device *,
+                                      const unsigned char *, int),
+                          int (*unsync)(struct net_device *,
+                                        const unsigned char *, int))
+{
+       struct netdev_hw_addr *ha, *tmp;
+       int err, ref_cnt;
+
+       /* first go through and flush out any unsynced/stale entries */
+       list_for_each_entry_safe(ha, tmp, &list->list, list) {
+               /* sync if address is not used */
+               if ((ha->sync_cnt << 1) <= ha->refcount)
+                       continue;
+
+               /* if fails defer unsyncing address */
+               ref_cnt = ha->refcount - ha->sync_cnt;
+               if (unsync && unsync(dev, ha->addr, ref_cnt))
+                       continue;
+
+               ha->refcount = (ref_cnt << 1) + 1;
+               ha->sync_cnt = ref_cnt;
+               __hw_addr_del_entry(list, ha, false, false);
+       }
+
+       /* go through and sync updated/new entries to the list */
+       list_for_each_entry_safe(ha, tmp, &list->list, list) {
+               /* sync if address added or reused */
+               if ((ha->sync_cnt << 1) >= ha->refcount)
+                       continue;
+
+               ref_cnt = ha->refcount - ha->sync_cnt;
+               err = sync(dev, ha->addr, ref_cnt);
+               if (err)
+                       return err;
+
+               ha->refcount = ref_cnt << 1;
+               ha->sync_cnt = ref_cnt;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(__hw_addr_ref_sync_dev);
+
+/**
+ *  __hw_addr_ref_unsync_dev - Remove synchronized addresses and references on
+ *  it from device
+ *  @list: address list to remove synchronized addresses (references on it) from
+ *  @dev:  device to sync
+ *  @unsync: function to call if address and references on it should be removed
+ *
+ *  Remove all addresses that were added to the device by
+ *  __hw_addr_ref_sync_dev(). This function is intended to be called from the
+ *  ndo_stop or ndo_open functions on devices that require explicit address (or
+ *  references on it) add/remove notifications. If the unsync function pointer
+ *  is NULL then this function can be used to just reset the sync_cnt for the
+ *  addresses in the list.
+ **/
+void __hw_addr_ref_unsync_dev(struct netdev_hw_addr_list *list,
+                             struct net_device *dev,
+                             int (*unsync)(struct net_device *,
+                                           const unsigned char *, int))
+{
+       struct netdev_hw_addr *ha, *tmp;
+
+       list_for_each_entry_safe(ha, tmp, &list->list, list) {
+               if (!ha->sync_cnt)
+                       continue;
+
+               /* if fails defer unsyncing address */
+               if (unsync && unsync(dev, ha->addr, ha->sync_cnt))
+                       continue;
+
+               ha->refcount -= ha->sync_cnt - 1;
+               ha->sync_cnt = 0;
+               __hw_addr_del_entry(list, ha, false, false);
+       }
+}
+EXPORT_SYMBOL(__hw_addr_ref_unsync_dev);
+
 /**
  *  __hw_addr_unsync_dev - Remove synchronized addresses from device
  *  @list: address list to remove synchronized addresses from