]> Pileus Git - ~andy/linux/blobdiff - drivers/net/ethernet/mellanox/mlx4/en_netdev.c
bridge: Add vlan support to static neighbors
[~andy/linux] / drivers / net / ethernet / mellanox / mlx4 / en_netdev.c
index 80067d572c0c7c17dee6fb65f03d6ea88d37c499..5088dc5c3d1a8ee5393e54d4d3810f9eafce3365 100644 (file)
@@ -545,13 +545,10 @@ static int mlx4_en_get_qp(struct mlx4_en_priv *priv)
        memcpy(entry->mac, priv->dev->dev_addr, sizeof(entry->mac));
        entry->reg_id = reg_id;
 
-       err = radix_tree_insert(&priv->mac_tree, *qpn, entry);
-       if (err)
-               goto insert_err;
-       return 0;
+       hlist_add_head_rcu(&entry->hlist,
+                          &priv->mac_hash[entry->mac[MLX4_EN_MAC_HASH_IDX]]);
 
-insert_err:
-       kfree(entry);
+       return 0;
 
 alloc_err:
        mlx4_en_uc_steer_release(priv, priv->dev->dev_addr, *qpn, reg_id);
@@ -568,7 +565,6 @@ static void mlx4_en_put_qp(struct mlx4_en_priv *priv)
 {
        struct mlx4_en_dev *mdev = priv->mdev;
        struct mlx4_dev *dev = mdev->dev;
-       struct mlx4_mac_entry *entry;
        int qpn = priv->base_qpn;
        u64 mac = mlx4_en_mac_to_u64(priv->dev->dev_addr);
 
@@ -577,44 +573,67 @@ static void mlx4_en_put_qp(struct mlx4_en_priv *priv)
        mlx4_unregister_mac(dev, priv->port, mac);
 
        if (dev->caps.steering_mode != MLX4_STEERING_MODE_A0) {
-               entry = radix_tree_lookup(&priv->mac_tree, qpn);
-               if (entry) {
-                       en_dbg(DRV, priv, "Releasing qp: port %d, MAC %pM, qpn %d\n",
-                              priv->port, entry->mac, qpn);
-                       mlx4_en_uc_steer_release(priv, entry->mac,
-                                                qpn, entry->reg_id);
-                       mlx4_qp_release_range(dev, qpn, 1);
-                       radix_tree_delete(&priv->mac_tree, qpn);
-                       kfree(entry);
+               struct mlx4_mac_entry *entry;
+               struct hlist_node *n, *tmp;
+               struct hlist_head *bucket;
+               unsigned int mac_hash;
+
+               mac_hash = priv->dev->dev_addr[MLX4_EN_MAC_HASH_IDX];
+               bucket = &priv->mac_hash[mac_hash];
+               hlist_for_each_entry_safe(entry, n, tmp, bucket, hlist) {
+                       if (ether_addr_equal_64bits(entry->mac,
+                                                   priv->dev->dev_addr)) {
+                               en_dbg(DRV, priv, "Releasing qp: port %d, MAC %pM, qpn %d\n",
+                                      priv->port, priv->dev->dev_addr, qpn);
+                               mlx4_en_uc_steer_release(priv, entry->mac,
+                                                        qpn, entry->reg_id);
+                               mlx4_qp_release_range(dev, qpn, 1);
+
+                               hlist_del_rcu(&entry->hlist);
+                               kfree_rcu(entry, rcu);
+                               break;
+                       }
                }
        }
 }
 
 static int mlx4_en_replace_mac(struct mlx4_en_priv *priv, int qpn,
-                              unsigned char *new_mac)
+                              unsigned char *new_mac, unsigned char *prev_mac)
 {
        struct mlx4_en_dev *mdev = priv->mdev;
        struct mlx4_dev *dev = mdev->dev;
-       struct mlx4_mac_entry *entry;
        int err = 0;
        u64 new_mac_u64 = mlx4_en_mac_to_u64(new_mac);
 
        if (dev->caps.steering_mode != MLX4_STEERING_MODE_A0) {
-               u64 prev_mac_u64;
-
-               entry = radix_tree_lookup(&priv->mac_tree, qpn);
-               if (!entry)
-                       return -EINVAL;
-               prev_mac_u64 = mlx4_en_mac_to_u64(entry->mac);
-               mlx4_en_uc_steer_release(priv, entry->mac,
-                                        qpn, entry->reg_id);
-               mlx4_unregister_mac(dev, priv->port, prev_mac_u64);
-               memcpy(entry->mac, new_mac, ETH_ALEN);
-               entry->reg_id = 0;
-               mlx4_register_mac(dev, priv->port, new_mac_u64);
-               err = mlx4_en_uc_steer_add(priv, new_mac,
-                                          &qpn, &entry->reg_id);
-               return err;
+               struct hlist_head *bucket;
+               unsigned int mac_hash;
+               struct mlx4_mac_entry *entry;
+               struct hlist_node *n, *tmp;
+               u64 prev_mac_u64 = mlx4_en_mac_to_u64(prev_mac);
+
+               bucket = &priv->mac_hash[prev_mac[MLX4_EN_MAC_HASH_IDX]];
+               hlist_for_each_entry_safe(entry, n, tmp, bucket, hlist) {
+                       if (ether_addr_equal_64bits(entry->mac, prev_mac)) {
+                               mlx4_en_uc_steer_release(priv, entry->mac,
+                                                        qpn, entry->reg_id);
+                               mlx4_unregister_mac(dev, priv->port,
+                                                   prev_mac_u64);
+                               hlist_del_rcu(&entry->hlist);
+                               synchronize_rcu();
+                               memcpy(entry->mac, new_mac, ETH_ALEN);
+                               entry->reg_id = 0;
+                               mac_hash = new_mac[MLX4_EN_MAC_HASH_IDX];
+                               hlist_add_head_rcu(&entry->hlist,
+                                                  &priv->mac_hash[mac_hash]);
+                               mlx4_register_mac(dev, priv->port, new_mac_u64);
+                               err = mlx4_en_uc_steer_add(priv, new_mac,
+                                                          &qpn,
+                                                          &entry->reg_id);
+                               return err;
+                       }
+               }
+               return -EINVAL;
        }
 
        return __mlx4_replace_mac(dev, priv->port, qpn, new_mac_u64);
@@ -657,7 +676,7 @@ static void mlx4_en_do_set_mac(struct work_struct *work)
        if (priv->port_up) {
                /* Remove old MAC and insert the new one */
                err = mlx4_en_replace_mac(priv, priv->base_qpn,
-                                         priv->dev->dev_addr);
+                                         priv->dev->dev_addr, priv->prev_mac);
                if (err)
                        en_err(priv, "Failed changing HW MAC address\n");
                memcpy(priv->prev_mac, priv->dev->dev_addr,
@@ -689,7 +708,6 @@ static void mlx4_en_cache_mclist(struct net_device *dev)
        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;
                }
@@ -733,14 +751,12 @@ static void update_mclist_flags(struct mlx4_en_priv *priv,
                        }
                }
                if (!found) {
-                       new_mc = kmalloc(sizeof(struct mlx4_en_mc_list),
+                       new_mc = kmemdup(src_tmp,
+                                        sizeof(struct mlx4_en_mc_list),
                                         GFP_KERNEL);
-                       if (!new_mc) {
-                               en_err(priv, "Failed to allocate current multicast list\n");
+                       if (!new_mc)
                                return;
-                       }
-                       memcpy(new_mc, src_tmp,
-                              sizeof(struct mlx4_en_mc_list));
+
                        new_mc->action = MCLIST_ADD;
                        list_add_tail(&new_mc->list, dst);
                }
@@ -997,6 +1013,126 @@ static void mlx4_en_do_multicast(struct mlx4_en_priv *priv,
        }
 }
 
+static void mlx4_en_do_uc_filter(struct mlx4_en_priv *priv,
+                                struct net_device *dev,
+                                struct mlx4_en_dev *mdev)
+{
+       struct netdev_hw_addr *ha;
+       struct mlx4_mac_entry *entry;
+       struct hlist_node *n, *tmp;
+       bool found;
+       u64 mac;
+       int err = 0;
+       struct hlist_head *bucket;
+       unsigned int i;
+       int removed = 0;
+       u32 prev_flags;
+
+       /* Note that we do not need to protect our mac_hash traversal with rcu,
+        * since all modification code is protected by mdev->state_lock
+        */
+
+       /* find what to remove */
+       for (i = 0; i < MLX4_EN_MAC_HASH_SIZE; ++i) {
+               bucket = &priv->mac_hash[i];
+               hlist_for_each_entry_safe(entry, n, tmp, bucket, hlist) {
+                       found = false;
+                       netdev_for_each_uc_addr(ha, dev) {
+                               if (ether_addr_equal_64bits(entry->mac,
+                                                           ha->addr)) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+
+                       /* MAC address of the port is not in uc list */
+                       if (ether_addr_equal_64bits(entry->mac, dev->dev_addr))
+                               found = true;
+
+                       if (!found) {
+                               mac = mlx4_en_mac_to_u64(entry->mac);
+                               mlx4_en_uc_steer_release(priv, entry->mac,
+                                                        priv->base_qpn,
+                                                        entry->reg_id);
+                               mlx4_unregister_mac(mdev->dev, priv->port, mac);
+
+                               hlist_del_rcu(&entry->hlist);
+                               kfree_rcu(entry, rcu);
+                               en_dbg(DRV, priv, "Removed MAC %pM on port:%d\n",
+                                      entry->mac, priv->port);
+                               ++removed;
+                       }
+               }
+       }
+
+       /* if we didn't remove anything, there is no use in trying to add
+        * again once we are in a forced promisc mode state
+        */
+       if ((priv->flags & MLX4_EN_FLAG_FORCE_PROMISC) && 0 == removed)
+               return;
+
+       prev_flags = priv->flags;
+       priv->flags &= ~MLX4_EN_FLAG_FORCE_PROMISC;
+
+       /* find what to add */
+       netdev_for_each_uc_addr(ha, dev) {
+               found = false;
+               bucket = &priv->mac_hash[ha->addr[MLX4_EN_MAC_HASH_IDX]];
+               hlist_for_each_entry(entry, n, bucket, hlist) {
+                       if (ether_addr_equal_64bits(entry->mac, ha->addr)) {
+                               found = true;
+                               break;
+                       }
+               }
+
+               if (!found) {
+                       entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+                       if (!entry) {
+                               en_err(priv, "Failed adding MAC %pM on port:%d (out of memory)\n",
+                                      ha->addr, priv->port);
+                               priv->flags |= MLX4_EN_FLAG_FORCE_PROMISC;
+                               break;
+                       }
+                       mac = mlx4_en_mac_to_u64(ha->addr);
+                       memcpy(entry->mac, ha->addr, ETH_ALEN);
+                       err = mlx4_register_mac(mdev->dev, priv->port, mac);
+                       if (err < 0) {
+                               en_err(priv, "Failed registering MAC %pM on port %d: %d\n",
+                                      ha->addr, priv->port, err);
+                               kfree(entry);
+                               priv->flags |= MLX4_EN_FLAG_FORCE_PROMISC;
+                               break;
+                       }
+                       err = mlx4_en_uc_steer_add(priv, ha->addr,
+                                                  &priv->base_qpn,
+                                                  &entry->reg_id);
+                       if (err) {
+                               en_err(priv, "Failed adding MAC %pM on port %d: %d\n",
+                                      ha->addr, priv->port, err);
+                               mlx4_unregister_mac(mdev->dev, priv->port, mac);
+                               kfree(entry);
+                               priv->flags |= MLX4_EN_FLAG_FORCE_PROMISC;
+                               break;
+                       } else {
+                               unsigned int mac_hash;
+                               en_dbg(DRV, priv, "Added MAC %pM on port:%d\n",
+                                      ha->addr, priv->port);
+                               mac_hash = ha->addr[MLX4_EN_MAC_HASH_IDX];
+                               bucket = &priv->mac_hash[mac_hash];
+                               hlist_add_head_rcu(&entry->hlist, bucket);
+                       }
+               }
+       }
+
+       if (priv->flags & MLX4_EN_FLAG_FORCE_PROMISC) {
+               en_warn(priv, "Forcing promiscuous mode on port:%d\n",
+                       priv->port);
+       } else if (prev_flags & MLX4_EN_FLAG_FORCE_PROMISC) {
+               en_warn(priv, "Stop forcing promiscuous mode on port:%d\n",
+                       priv->port);
+       }
+}
+
 static void mlx4_en_do_set_rx_mode(struct work_struct *work)
 {
        struct mlx4_en_priv *priv = container_of(work, struct mlx4_en_priv,
@@ -1024,8 +1160,12 @@ static void mlx4_en_do_set_rx_mode(struct work_struct *work)
                }
        }
 
+       if (dev->priv_flags & IFF_UNICAST_FLT)
+               mlx4_en_do_uc_filter(priv, dev, mdev);
+
        /* Promsicuous mode: disable all filters */
-       if (dev->flags & IFF_PROMISC) {
+       if ((dev->flags & IFF_PROMISC) ||
+           (priv->flags & MLX4_EN_FLAG_FORCE_PROMISC)) {
                mlx4_en_set_promisc_mode(priv, mdev);
                goto out;
        }
@@ -1542,15 +1682,12 @@ static void mlx4_en_restart(struct work_struct *work)
                                                 watchdog_task);
        struct mlx4_en_dev *mdev = priv->mdev;
        struct net_device *dev = priv->dev;
-       int i;
 
        en_dbg(DRV, priv, "Watchdog task called for port %d\n", priv->port);
 
        mutex_lock(&mdev->state_lock);
        if (priv->port_up) {
                mlx4_en_stop_port(dev, 1);
-               for (i = 0; i < priv->tx_ring_num; i++)
-                       netdev_tx_reset_queue(priv->tx_ring[i].tx_queue);
                if (mlx4_en_start_port(dev))
                        en_err(priv, "Failed restarting port %d\n", priv->port);
        }
@@ -1788,6 +1925,79 @@ static int mlx4_en_set_features(struct net_device *netdev,
 
 }
 
+static int mlx4_en_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
+                          struct net_device *dev,
+                          const unsigned char *addr, u16 flags)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_dev *mdev = priv->mdev->dev;
+       int err;
+
+       if (!mlx4_is_mfunc(mdev))
+               return -EOPNOTSUPP;
+
+       /* Hardware does not support aging addresses, allow only
+        * permanent addresses if ndm_state is given
+        */
+       if (ndm->ndm_state && !(ndm->ndm_state & NUD_PERMANENT)) {
+               en_info(priv, "Add FDB only supports static addresses\n");
+               return -EINVAL;
+       }
+
+       if (is_unicast_ether_addr(addr) || is_link_local_ether_addr(addr))
+               err = dev_uc_add_excl(dev, addr);
+       else if (is_multicast_ether_addr(addr))
+               err = dev_mc_add_excl(dev, addr);
+       else
+               err = -EINVAL;
+
+       /* Only return duplicate errors if NLM_F_EXCL is set */
+       if (err == -EEXIST && !(flags & NLM_F_EXCL))
+               err = 0;
+
+       return err;
+}
+
+static int mlx4_en_fdb_del(struct ndmsg *ndm,
+                          struct nlattr *tb[],
+                          struct net_device *dev,
+                          const unsigned char *addr)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_dev *mdev = priv->mdev->dev;
+       int err;
+
+       if (!mlx4_is_mfunc(mdev))
+               return -EOPNOTSUPP;
+
+       if (ndm->ndm_state && !(ndm->ndm_state & NUD_PERMANENT)) {
+               en_info(priv, "Del FDB only supports static addresses\n");
+               return -EINVAL;
+       }
+
+       if (is_unicast_ether_addr(addr) || is_link_local_ether_addr(addr))
+               err = dev_uc_del(dev, addr);
+       else if (is_multicast_ether_addr(addr))
+               err = dev_mc_del(dev, addr);
+       else
+               err = -EINVAL;
+
+       return err;
+}
+
+static int mlx4_en_fdb_dump(struct sk_buff *skb,
+                           struct netlink_callback *cb,
+                           struct net_device *dev, int idx)
+{
+       struct mlx4_en_priv *priv = netdev_priv(dev);
+       struct mlx4_dev *mdev = priv->mdev->dev;
+
+       if (mlx4_is_mfunc(mdev))
+               idx = ndo_dflt_fdb_dump(skb, cb, dev, idx);
+
+       return idx;
+}
+
 static const struct net_device_ops mlx4_netdev_ops = {
        .ndo_open               = mlx4_en_open,
        .ndo_stop               = mlx4_en_close,
@@ -1809,6 +2019,9 @@ static const struct net_device_ops mlx4_netdev_ops = {
 #ifdef CONFIG_RFS_ACCEL
        .ndo_rx_flow_steer      = mlx4_en_filter_rfs,
 #endif
+       .ndo_fdb_add            = mlx4_en_fdb_add,
+       .ndo_fdb_del            = mlx4_en_fdb_del,
+       .ndo_fdb_dump           = mlx4_en_fdb_dump,
 };
 
 int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
@@ -1816,6 +2029,7 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
 {
        struct net_device *dev;
        struct mlx4_en_priv *priv;
+       int i;
        int err;
 
        dev = alloc_etherdev_mqs(sizeof(struct mlx4_en_priv),
@@ -1874,7 +2088,8 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
                dev->dcbnl_ops = &mlx4_en_dcbnl_ops;
 #endif
 
-       INIT_RADIX_TREE(&priv->mac_tree, GFP_KERNEL);
+       for (i = 0; i < MLX4_EN_MAC_HASH_SIZE; ++i)
+               INIT_HLIST_HEAD(&priv->mac_hash[i]);
 
        /* Query for default mac and max mtu */
        priv->max_mtu = mdev->dev->caps.eth_mtu_cap[priv->port];
@@ -1940,6 +2155,9 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
            MLX4_STEERING_MODE_DEVICE_MANAGED)
                dev->hw_features |= NETIF_F_NTUPLE;
 
+       if (mdev->dev->caps.steering_mode != MLX4_STEERING_MODE_A0)
+               dev->priv_flags |= IFF_UNICAST_FLT;
+
        mdev->pndev[port] = dev;
 
        netif_carrier_off(dev);