net/mlx4_en: Re-design multicast attachments flow
Yevgeny Petrilin [Thu, 5 Jul 2012 04:03:43 +0000 (04:03 +0000)]
Currently, for every change in the net device multicast list, the driver
detaches all the addresses from the HW device, and then attaches the
updated list. This behavior is wrong from two aspects: first, it causes
a load of firmware commands and second, there is period of time where
the correct addresses are not attached, which turned into packet loss.

To improve - a copy of the multicast list is saved by the driver. For
every change in the multicast list, the multicast list copy is used
to find the delta between those two lists and add or remove multicast
addresses as needed.

Reported-by: Shawn Bohrer <sbohrer@rgmadvisors.com>
Cc: Shawn Bohrer <sbohrer@rgmadvisors.com>
Signed-off-by: Hadar Hen Zion <hadarh@mellanox.co.il>
Signed-off-by: Yevgeny Petrilin <yevgenyp@mellanox.co.il>
Signed-off-by: David S. Miller <davem@davemloft.net>

drivers/net/ethernet/mellanox/mlx4/en_netdev.c
drivers/net/ethernet/mellanox/mlx4/mlx4_en.h

index 073b85b..bedcbb3 100644 (file)
@@ -170,33 +170,81 @@ static void mlx4_en_do_set_mac(struct work_struct *work)
 static void mlx4_en_clear_list(struct net_device *dev)
 {
        struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_en_mc_list *tmp, *mc_to_del;
 
-       kfree(priv->mc_addrs);
-       priv->mc_addrs = NULL;
-       priv->mc_addrs_cnt = 0;
+       list_for_each_entry_safe(mc_to_del, tmp, &priv->mc_list, list) {
+               list_del(&mc_to_del->list);
+               kfree(mc_to_del);
+       }
 }
 
 static void mlx4_en_cache_mclist(struct net_device *dev)
 {
        struct mlx4_en_priv *priv = netdev_priv(dev);
        struct netdev_hw_addr *ha;
-       char *mc_addrs;
-       int mc_addrs_cnt = netdev_mc_count(dev);
-       int i;
+       struct mlx4_en_mc_list *tmp;
 
-       mc_addrs = kmalloc(mc_addrs_cnt * ETH_ALEN, GFP_ATOMIC);
-       if (!mc_addrs) {
-               en_err(priv, "failed to allocate multicast list\n");
-               return;
-       }
-       i = 0;
-       netdev_for_each_mc_addr(ha, dev)
-               memcpy(mc_addrs + i++ * ETH_ALEN, ha->addr, ETH_ALEN);
        mlx4_en_clear_list(dev);
-       priv->mc_addrs = mc_addrs;
-       priv->mc_addrs_cnt = mc_addrs_cnt;
+       netdev_for_each_mc_addr(ha, dev) {
+               tmp = kzalloc(sizeof(struct mlx4_en_mc_list), GFP_ATOMIC);
+               if (!tmp) {
+                       en_err(priv, "failed to allocate multicast list\n");
+                       mlx4_en_clear_list(dev);
+                       return;
+               }
+               memcpy(tmp->addr, ha->addr, ETH_ALEN);
+               list_add_tail(&tmp->list, &priv->mc_list);
+       }
 }
 
+static void update_mclist_flags(struct mlx4_en_priv *priv,
+                               struct list_head *dst,
+                               struct list_head *src)
+{
+       struct mlx4_en_mc_list *dst_tmp, *src_tmp, *new_mc;
+       bool found;
+
+       /* Find all the entries that should be removed from dst,
+        * These are the entries that are not found in src
+        */
+       list_for_each_entry(dst_tmp, dst, list) {
+               found = false;
+               list_for_each_entry(src_tmp, src, list) {
+                       if (!memcmp(dst_tmp->addr, src_tmp->addr, ETH_ALEN)) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found)
+                       dst_tmp->action = MCLIST_REM;
+       }
+
+       /* Add entries that exist in src but not in dst
+        * mark them as need to add
+        */
+       list_for_each_entry(src_tmp, src, list) {
+               found = false;
+               list_for_each_entry(dst_tmp, dst, list) {
+                       if (!memcmp(dst_tmp->addr, src_tmp->addr, ETH_ALEN)) {
+                               dst_tmp->action = MCLIST_NONE;
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found) {
+                       new_mc = kmalloc(sizeof(struct mlx4_en_mc_list),
+                                        GFP_KERNEL);
+                       if (!new_mc) {
+                               en_err(priv, "Failed to allocate current multicast list\n");
+                               return;
+                       }
+                       memcpy(new_mc, src_tmp,
+                              sizeof(struct mlx4_en_mc_list));
+                       new_mc->action = MCLIST_ADD;
+                       list_add_tail(&new_mc->list, dst);
+               }
+       }
+}
 
 static void mlx4_en_set_multicast(struct net_device *dev)
 {
@@ -214,6 +262,7 @@ static void mlx4_en_do_set_multicast(struct work_struct *work)
                                                 mcast_task);
        struct mlx4_en_dev *mdev = priv->mdev;
        struct net_device *dev = priv->dev;
+       struct mlx4_en_mc_list *mclist, *tmp;
        u64 mcast_addr = 0;
        u8 mc_list[16] = {0};
        int err;
@@ -336,7 +385,6 @@ static void mlx4_en_do_set_multicast(struct work_struct *work)
                        priv->flags |= MLX4_EN_FLAG_MC_PROMISC;
                }
        } else {
-               int i;
                /* Disable Multicast promisc */
                if (priv->flags & MLX4_EN_FLAG_MC_PROMISC) {
                        err = mlx4_multicast_promisc_remove(mdev->dev, priv->base_qpn,
@@ -351,13 +399,6 @@ static void mlx4_en_do_set_multicast(struct work_struct *work)
                if (err)
                        en_err(priv, "Failed disabling multicast filter\n");
 
-               /* Detach our qp from all the multicast addresses */
-               for (i = 0; i < priv->mc_addrs_cnt; i++) {
-                       memcpy(&mc_list[10], priv->mc_addrs + i * ETH_ALEN, ETH_ALEN);
-                       mc_list[5] = priv->port;
-                       mlx4_multicast_detach(mdev->dev, &priv->rss_map.indir_qp,
-                                             mc_list, MLX4_PROT_ETH);
-               }
                /* Flush mcast filter and init it with broadcast address */
                mlx4_SET_MCAST_FLTR(mdev->dev, priv->port, ETH_BCAST,
                                    1, MLX4_MCAST_CONFIG);
@@ -367,13 +408,8 @@ static void mlx4_en_do_set_multicast(struct work_struct *work)
                netif_tx_lock_bh(dev);
                mlx4_en_cache_mclist(dev);
                netif_tx_unlock_bh(dev);
-               for (i = 0; i < priv->mc_addrs_cnt; i++) {
-                       mcast_addr =
-                             mlx4_en_mac_to_u64(priv->mc_addrs + i * ETH_ALEN);
-                       memcpy(&mc_list[10], priv->mc_addrs + i * ETH_ALEN, ETH_ALEN);
-                       mc_list[5] = priv->port;
-                       mlx4_multicast_attach(mdev->dev, &priv->rss_map.indir_qp,
-                                             mc_list, 0, MLX4_PROT_ETH);
+               list_for_each_entry(mclist, &priv->mc_list, list) {
+                       mcast_addr = mlx4_en_mac_to_u64(mclist->addr);
                        mlx4_SET_MCAST_FLTR(mdev->dev, priv->port,
                                            mcast_addr, 0, MLX4_MCAST_CONFIG);
                }
@@ -381,6 +417,38 @@ static void mlx4_en_do_set_multicast(struct work_struct *work)
                                          0, MLX4_MCAST_ENABLE);
                if (err)
                        en_err(priv, "Failed enabling multicast filter\n");
+
+               update_mclist_flags(priv, &priv->curr_list, &priv->mc_list);
+               list_for_each_entry_safe(mclist, tmp, &priv->curr_list, list) {
+                       if (mclist->action == MCLIST_REM) {
+                               /* detach this address and delete from list */
+                               memcpy(&mc_list[10], mclist->addr, ETH_ALEN);
+                               mc_list[5] = priv->port;
+                               err = mlx4_multicast_detach(mdev->dev,
+                                                           &priv->rss_map.indir_qp,
+                                                           mc_list,
+                                                           MLX4_PROT_ETH);
+                               if (err)
+                                       en_err(priv, "Fail to detach multicast address\n");
+
+                               /* remove from list */
+                               list_del(&mclist->list);
+                               kfree(mclist);
+                       }
+
+                       if (mclist->action == MCLIST_ADD) {
+                               /* attach the address */
+                               memcpy(&mc_list[10], mclist->addr, ETH_ALEN);
+                               mc_list[5] = priv->port;
+                               err = mlx4_multicast_attach(mdev->dev,
+                                                           &priv->rss_map.indir_qp,
+                                                           mc_list, 0,
+                                                           MLX4_PROT_ETH);
+                               if (err)
+                                       en_err(priv, "Fail to attach multicast address\n");
+
+                       }
+               }
        }
 out:
        mutex_unlock(&mdev->state_lock);
@@ -605,6 +673,9 @@ int mlx4_en_start_port(struct net_device *dev)
                return 0;
        }
 
+       INIT_LIST_HEAD(&priv->mc_list);
+       INIT_LIST_HEAD(&priv->curr_list);
+
        /* Calculate Rx buf size */
        dev->mtu = min(dev->mtu, priv->max_mtu);
        mlx4_en_calc_rx_buf(dev);
@@ -760,6 +831,7 @@ void mlx4_en_stop_port(struct net_device *dev)
 {
        struct mlx4_en_priv *priv = netdev_priv(dev);
        struct mlx4_en_dev *mdev = priv->mdev;
+       struct mlx4_en_mc_list *mclist, *tmp;
        int i;
        u8 mc_list[16] = {0};
 
@@ -781,13 +853,18 @@ void mlx4_en_stop_port(struct net_device *dev)
        mc_list[5] = priv->port;
        mlx4_multicast_detach(mdev->dev, &priv->rss_map.indir_qp, mc_list,
                              MLX4_PROT_ETH);
-       for (i = 0; i < priv->mc_addrs_cnt; i++) {
-               memcpy(&mc_list[10], priv->mc_addrs + i * ETH_ALEN, ETH_ALEN);
+       list_for_each_entry(mclist, &priv->curr_list, list) {
+               memcpy(&mc_list[10], mclist->addr, ETH_ALEN);
                mc_list[5] = priv->port;
                mlx4_multicast_detach(mdev->dev, &priv->rss_map.indir_qp,
                                      mc_list, MLX4_PROT_ETH);
        }
        mlx4_en_clear_list(dev);
+       list_for_each_entry_safe(mclist, tmp, &priv->curr_list, list) {
+               list_del(&mclist->list);
+               kfree(mclist);
+       }
+
        /* Flush multicast filter */
        mlx4_SET_MCAST_FLTR(mdev->dev, priv->port, 0, 1, MLX4_MCAST_CONFIG);
 
index 225c20d..1bb00cd 100644 (file)
@@ -404,6 +404,18 @@ struct mlx4_en_perf_stats {
 #define NUM_PERF_COUNTERS              6
 };
 
+enum mlx4_en_mclist_act {
+       MCLIST_NONE,
+       MCLIST_REM,
+       MCLIST_ADD,
+};
+
+struct mlx4_en_mc_list {
+       struct list_head        list;
+       enum mlx4_en_mclist_act action;
+       u8                      addr[ETH_ALEN];
+};
+
 struct mlx4_en_frag_info {
        u16 frag_size;
        u16 frag_prefix_size;
@@ -489,8 +501,8 @@ struct mlx4_en_priv {
        struct mlx4_en_pkt_stats pkstats;
        struct mlx4_en_port_stats port_stats;
        u64 stats_bitmap;
-       char *mc_addrs;
-       int mc_addrs_cnt;
+       struct list_head mc_list;
+       struct list_head curr_list;
        struct mlx4_en_stat_out_mbox hw_stats;
        int vids[128];
        bool wol;