| /* |
| * Copyright (c) 2017, Mellanox Technologies. All rights reserved. |
| * |
| * This software is available to you under a choice of one of two |
| * licenses. You may choose to be licensed under the terms of the GNU |
| * General Public License (GPL) Version 2, available from the file |
| * COPYING in the main directory of this source tree, or the |
| * OpenIB.org BSD license below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <linux/etherdevice.h> |
| #include <linux/mlx5/driver.h> |
| #include <linux/mlx5/mlx5_ifc.h> |
| #include <linux/mlx5/mpfs.h> |
| #include <linux/mlx5/eswitch.h> |
| #include "mlx5_core.h" |
| #include "lib/mpfs.h" |
| |
| /* HW L2 Table (MPFS) management */ |
| static int set_l2table_entry_cmd(struct mlx5_core_dev *dev, u32 index, u8 *mac) |
| { |
| u32 in[MLX5_ST_SZ_DW(set_l2_table_entry_in)] = {}; |
| u8 *in_mac_addr; |
| |
| MLX5_SET(set_l2_table_entry_in, in, opcode, MLX5_CMD_OP_SET_L2_TABLE_ENTRY); |
| MLX5_SET(set_l2_table_entry_in, in, table_index, index); |
| |
| in_mac_addr = MLX5_ADDR_OF(set_l2_table_entry_in, in, mac_address); |
| ether_addr_copy(&in_mac_addr[2], mac); |
| |
| return mlx5_cmd_exec_in(dev, set_l2_table_entry, in); |
| } |
| |
| static int del_l2table_entry_cmd(struct mlx5_core_dev *dev, u32 index) |
| { |
| u32 in[MLX5_ST_SZ_DW(delete_l2_table_entry_in)] = {}; |
| |
| MLX5_SET(delete_l2_table_entry_in, in, opcode, MLX5_CMD_OP_DELETE_L2_TABLE_ENTRY); |
| MLX5_SET(delete_l2_table_entry_in, in, table_index, index); |
| return mlx5_cmd_exec_in(dev, delete_l2_table_entry, in); |
| } |
| |
| /* UC L2 table hash node */ |
| struct l2table_node { |
| struct l2addr_node node; |
| u32 index; /* index in HW l2 table */ |
| int ref_count; |
| }; |
| |
| struct mlx5_mpfs { |
| struct hlist_head hash[MLX5_L2_ADDR_HASH_SIZE]; |
| struct mutex lock; /* Synchronize l2 table access */ |
| u32 size; |
| unsigned long *bitmap; |
| }; |
| |
| static int alloc_l2table_index(struct mlx5_mpfs *l2table, u32 *ix) |
| { |
| int err = 0; |
| |
| *ix = find_first_zero_bit(l2table->bitmap, l2table->size); |
| if (*ix >= l2table->size) |
| err = -ENOSPC; |
| else |
| __set_bit(*ix, l2table->bitmap); |
| |
| return err; |
| } |
| |
| static void free_l2table_index(struct mlx5_mpfs *l2table, u32 ix) |
| { |
| __clear_bit(ix, l2table->bitmap); |
| } |
| |
| int mlx5_mpfs_init(struct mlx5_core_dev *dev) |
| { |
| int l2table_size = 1 << MLX5_CAP_GEN(dev, log_max_l2_table); |
| struct mlx5_mpfs *mpfs; |
| |
| if (!MLX5_ESWITCH_MANAGER(dev)) |
| return 0; |
| |
| mpfs = kzalloc(sizeof(*mpfs), GFP_KERNEL); |
| if (!mpfs) |
| return -ENOMEM; |
| |
| mutex_init(&mpfs->lock); |
| mpfs->size = l2table_size; |
| mpfs->bitmap = bitmap_zalloc(l2table_size, GFP_KERNEL); |
| if (!mpfs->bitmap) { |
| kfree(mpfs); |
| return -ENOMEM; |
| } |
| |
| dev->priv.mpfs = mpfs; |
| return 0; |
| } |
| |
| void mlx5_mpfs_cleanup(struct mlx5_core_dev *dev) |
| { |
| struct mlx5_mpfs *mpfs = dev->priv.mpfs; |
| |
| if (!MLX5_ESWITCH_MANAGER(dev)) |
| return; |
| |
| WARN_ON(!hlist_empty(mpfs->hash)); |
| bitmap_free(mpfs->bitmap); |
| kfree(mpfs); |
| } |
| |
| int mlx5_mpfs_add_mac(struct mlx5_core_dev *dev, u8 *mac) |
| { |
| struct mlx5_mpfs *mpfs = dev->priv.mpfs; |
| struct l2table_node *l2addr; |
| int err = 0; |
| u32 index; |
| |
| if (!MLX5_ESWITCH_MANAGER(dev)) |
| return 0; |
| |
| mutex_lock(&mpfs->lock); |
| |
| l2addr = l2addr_hash_find(mpfs->hash, mac, struct l2table_node); |
| if (l2addr) { |
| l2addr->ref_count++; |
| goto out; |
| } |
| |
| err = alloc_l2table_index(mpfs, &index); |
| if (err) |
| goto out; |
| |
| l2addr = l2addr_hash_add(mpfs->hash, mac, struct l2table_node, GFP_KERNEL); |
| if (!l2addr) { |
| err = -ENOMEM; |
| goto hash_add_err; |
| } |
| |
| err = set_l2table_entry_cmd(dev, index, mac); |
| if (err) |
| goto set_table_entry_err; |
| |
| l2addr->index = index; |
| l2addr->ref_count = 1; |
| |
| mlx5_core_dbg(dev, "MPFS mac added %pM, index (%d)\n", mac, index); |
| goto out; |
| |
| set_table_entry_err: |
| l2addr_hash_del(l2addr); |
| hash_add_err: |
| free_l2table_index(mpfs, index); |
| out: |
| mutex_unlock(&mpfs->lock); |
| return err; |
| } |
| EXPORT_SYMBOL(mlx5_mpfs_add_mac); |
| |
| int mlx5_mpfs_del_mac(struct mlx5_core_dev *dev, u8 *mac) |
| { |
| struct mlx5_mpfs *mpfs = dev->priv.mpfs; |
| struct l2table_node *l2addr; |
| int err = 0; |
| u32 index; |
| |
| if (!MLX5_ESWITCH_MANAGER(dev)) |
| return 0; |
| |
| mutex_lock(&mpfs->lock); |
| |
| l2addr = l2addr_hash_find(mpfs->hash, mac, struct l2table_node); |
| if (!l2addr) { |
| err = -ENOENT; |
| goto unlock; |
| } |
| |
| if (--l2addr->ref_count > 0) |
| goto unlock; |
| |
| index = l2addr->index; |
| del_l2table_entry_cmd(dev, index); |
| l2addr_hash_del(l2addr); |
| free_l2table_index(mpfs, index); |
| mlx5_core_dbg(dev, "MPFS mac deleted %pM, index (%d)\n", mac, index); |
| unlock: |
| mutex_unlock(&mpfs->lock); |
| return err; |
| } |
| EXPORT_SYMBOL(mlx5_mpfs_del_mac); |