1587 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1587 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
 | |
| /* Copyright (c) 2018 Mellanox Technologies. All rights reserved */
 | |
| 
 | |
| #include <linux/bitmap.h>
 | |
| #include <linux/errno.h>
 | |
| #include <linux/genalloc.h>
 | |
| #include <linux/gfp.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/list.h>
 | |
| #include <linux/mutex.h>
 | |
| #include <linux/objagg.h>
 | |
| #include <linux/rtnetlink.h>
 | |
| #include <linux/slab.h>
 | |
| 
 | |
| #include "core.h"
 | |
| #include "reg.h"
 | |
| #include "spectrum.h"
 | |
| #include "spectrum_acl_tcam.h"
 | |
| 
 | |
| /* gen_pool_alloc() returns 0 when allocation fails, so use an offset */
 | |
| #define MLXSW_SP_ACL_ERP_GENALLOC_OFFSET 0x100
 | |
| #define MLXSW_SP_ACL_ERP_MAX_PER_REGION 16
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_core {
 | |
| 	unsigned int erpt_entries_size[MLXSW_SP_ACL_ATCAM_REGION_TYPE_MAX + 1];
 | |
| 	struct gen_pool *erp_tables;
 | |
| 	struct mlxsw_sp *mlxsw_sp;
 | |
| 	struct mlxsw_sp_acl_bf *bf;
 | |
| 	unsigned int num_erp_banks;
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_key {
 | |
| 	char mask[MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN];
 | |
| #define __MASK_LEN 0x38
 | |
| #define __MASK_IDX(i) (__MASK_LEN - (i) - 1)
 | |
| 	bool ctcam;
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_acl_erp {
 | |
| 	struct mlxsw_sp_acl_erp_key key;
 | |
| 	u8 id;
 | |
| 	u8 index;
 | |
| 	DECLARE_BITMAP(mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 	struct list_head list;
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table;
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_master_mask {
 | |
| 	DECLARE_BITMAP(bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 	unsigned int count[MLXSW_SP_ACL_TCAM_MASK_LEN];
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_table {
 | |
| 	struct mlxsw_sp_acl_erp_master_mask master_mask;
 | |
| 	DECLARE_BITMAP(erp_id_bitmap, MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	DECLARE_BITMAP(erp_index_bitmap, MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	struct list_head atcam_erps_list;
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core;
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion;
 | |
| 	const struct mlxsw_sp_acl_erp_table_ops *ops;
 | |
| 	unsigned long base_index;
 | |
| 	unsigned int num_atcam_erps;
 | |
| 	unsigned int num_max_atcam_erps;
 | |
| 	unsigned int num_ctcam_erps;
 | |
| 	unsigned int num_deltas;
 | |
| 	struct objagg *objagg;
 | |
| 	struct mutex objagg_lock; /* guards objagg manipulation */
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_table_ops {
 | |
| 	struct mlxsw_sp_acl_erp *
 | |
| 		(*erp_create)(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			      struct mlxsw_sp_acl_erp_key *key);
 | |
| 	void (*erp_destroy)(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			    struct mlxsw_sp_acl_erp *erp);
 | |
| };
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			     struct mlxsw_sp_acl_erp_key *key);
 | |
| static void
 | |
| mlxsw_sp_acl_erp_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			      struct mlxsw_sp_acl_erp *erp);
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_second_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				    struct mlxsw_sp_acl_erp_key *key);
 | |
| static void
 | |
| mlxsw_sp_acl_erp_second_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				     struct mlxsw_sp_acl_erp *erp);
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_first_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   struct mlxsw_sp_acl_erp_key *key);
 | |
| static void
 | |
| mlxsw_sp_acl_erp_first_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				    struct mlxsw_sp_acl_erp *erp);
 | |
| static void
 | |
| mlxsw_sp_acl_erp_no_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				 struct mlxsw_sp_acl_erp *erp);
 | |
| 
 | |
| static const struct mlxsw_sp_acl_erp_table_ops erp_multiple_masks_ops = {
 | |
| 	.erp_create = mlxsw_sp_acl_erp_mask_create,
 | |
| 	.erp_destroy = mlxsw_sp_acl_erp_mask_destroy,
 | |
| };
 | |
| 
 | |
| static const struct mlxsw_sp_acl_erp_table_ops erp_two_masks_ops = {
 | |
| 	.erp_create = mlxsw_sp_acl_erp_mask_create,
 | |
| 	.erp_destroy = mlxsw_sp_acl_erp_second_mask_destroy,
 | |
| };
 | |
| 
 | |
| static const struct mlxsw_sp_acl_erp_table_ops erp_single_mask_ops = {
 | |
| 	.erp_create = mlxsw_sp_acl_erp_second_mask_create,
 | |
| 	.erp_destroy = mlxsw_sp_acl_erp_first_mask_destroy,
 | |
| };
 | |
| 
 | |
| static const struct mlxsw_sp_acl_erp_table_ops erp_no_mask_ops = {
 | |
| 	.erp_create = mlxsw_sp_acl_erp_first_mask_create,
 | |
| 	.erp_destroy = mlxsw_sp_acl_erp_no_mask_destroy,
 | |
| };
 | |
| 
 | |
| static bool
 | |
| mlxsw_sp_acl_erp_table_is_used(const struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	return erp_table->ops != &erp_single_mask_ops &&
 | |
| 	       erp_table->ops != &erp_no_mask_ops;
 | |
| }
 | |
| 
 | |
| static unsigned int
 | |
| mlxsw_sp_acl_erp_bank_get(const struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	return erp->index % erp->erp_table->erp_core->num_erp_banks;
 | |
| }
 | |
| 
 | |
| static unsigned int
 | |
| mlxsw_sp_acl_erp_table_entry_size(const struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = erp_table->aregion;
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core = erp_table->erp_core;
 | |
| 
 | |
| 	return erp_core->erpt_entries_size[aregion->type];
 | |
| }
 | |
| 
 | |
| static int mlxsw_sp_acl_erp_id_get(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   u8 *p_id)
 | |
| {
 | |
| 	u8 id;
 | |
| 
 | |
| 	id = find_first_zero_bit(erp_table->erp_id_bitmap,
 | |
| 				 MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	if (id < MLXSW_SP_ACL_ERP_MAX_PER_REGION) {
 | |
| 		__set_bit(id, erp_table->erp_id_bitmap);
 | |
| 		*p_id = id;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return -ENOBUFS;
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_id_put(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				    u8 id)
 | |
| {
 | |
| 	__clear_bit(id, erp_table->erp_id_bitmap);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_master_mask_bit_set(unsigned long bit,
 | |
| 				     struct mlxsw_sp_acl_erp_master_mask *mask)
 | |
| {
 | |
| 	if (mask->count[bit]++ == 0)
 | |
| 		__set_bit(bit, mask->bitmap);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_master_mask_bit_clear(unsigned long bit,
 | |
| 				       struct mlxsw_sp_acl_erp_master_mask *mask)
 | |
| {
 | |
| 	if (--mask->count[bit] == 0)
 | |
| 		__clear_bit(bit, mask->bitmap);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_master_mask_update(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_tcam_region *region = erp_table->aregion->region;
 | |
| 	struct mlxsw_sp *mlxsw_sp = region->mlxsw_sp;
 | |
| 	char percr_pl[MLXSW_REG_PERCR_LEN];
 | |
| 	char *master_mask;
 | |
| 
 | |
| 	mlxsw_reg_percr_pack(percr_pl, region->id);
 | |
| 	master_mask = mlxsw_reg_percr_master_mask_data(percr_pl);
 | |
| 	bitmap_to_arr32((u32 *) master_mask, erp_table->master_mask.bitmap,
 | |
| 			MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 
 | |
| 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(percr), percr_pl);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_master_mask_set(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				 struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	DECLARE_BITMAP(mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 	unsigned long bit;
 | |
| 	int err;
 | |
| 
 | |
| 	bitmap_from_arr32(mask_bitmap, (u32 *) key->mask,
 | |
| 			  MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 	for_each_set_bit(bit, mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN)
 | |
| 		mlxsw_sp_acl_erp_master_mask_bit_set(bit,
 | |
| 						     &erp_table->master_mask);
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_master_mask_update(erp_table);
 | |
| 	if (err)
 | |
| 		goto err_master_mask_update;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_master_mask_update:
 | |
| 	for_each_set_bit(bit, mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN)
 | |
| 		mlxsw_sp_acl_erp_master_mask_bit_clear(bit,
 | |
| 						       &erp_table->master_mask);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_master_mask_clear(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	DECLARE_BITMAP(mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 	unsigned long bit;
 | |
| 	int err;
 | |
| 
 | |
| 	bitmap_from_arr32(mask_bitmap, (u32 *) key->mask,
 | |
| 			  MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 	for_each_set_bit(bit, mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN)
 | |
| 		mlxsw_sp_acl_erp_master_mask_bit_clear(bit,
 | |
| 						       &erp_table->master_mask);
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_master_mask_update(erp_table);
 | |
| 	if (err)
 | |
| 		goto err_master_mask_update;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_master_mask_update:
 | |
| 	for_each_set_bit(bit, mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN)
 | |
| 		mlxsw_sp_acl_erp_master_mask_bit_set(bit,
 | |
| 						     &erp_table->master_mask);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_generic_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp *erp;
 | |
| 	int err;
 | |
| 
 | |
| 	erp = kzalloc(sizeof(*erp), GFP_KERNEL);
 | |
| 	if (!erp)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_id_get(erp_table, &erp->id);
 | |
| 	if (err)
 | |
| 		goto err_erp_id_get;
 | |
| 
 | |
| 	memcpy(&erp->key, key, sizeof(*key));
 | |
| 	list_add(&erp->list, &erp_table->atcam_erps_list);
 | |
| 	erp_table->num_atcam_erps++;
 | |
| 	erp->erp_table = erp_table;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_master_mask_set(erp_table, &erp->key);
 | |
| 	if (err)
 | |
| 		goto err_master_mask_set;
 | |
| 
 | |
| 	return erp;
 | |
| 
 | |
| err_master_mask_set:
 | |
| 	erp_table->num_atcam_erps--;
 | |
| 	list_del(&erp->list);
 | |
| 	mlxsw_sp_acl_erp_id_put(erp_table, erp->id);
 | |
| err_erp_id_get:
 | |
| 	kfree(erp);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_generic_destroy(struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = erp->erp_table;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_master_mask_clear(erp_table, &erp->key);
 | |
| 	erp_table->num_atcam_erps--;
 | |
| 	list_del(&erp->list);
 | |
| 	mlxsw_sp_acl_erp_id_put(erp_table, erp->id);
 | |
| 	kfree(erp);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_table_alloc(struct mlxsw_sp_acl_erp_core *erp_core,
 | |
| 			     unsigned int num_erps,
 | |
| 			     enum mlxsw_sp_acl_atcam_region_type region_type,
 | |
| 			     unsigned long *p_index)
 | |
| {
 | |
| 	unsigned int num_rows, entry_size;
 | |
| 
 | |
| 	/* We only allow allocations of entire rows */
 | |
| 	if (num_erps % erp_core->num_erp_banks != 0)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	entry_size = erp_core->erpt_entries_size[region_type];
 | |
| 	num_rows = num_erps / erp_core->num_erp_banks;
 | |
| 
 | |
| 	*p_index = gen_pool_alloc(erp_core->erp_tables, num_rows * entry_size);
 | |
| 	if (*p_index == 0)
 | |
| 		return -ENOBUFS;
 | |
| 	*p_index -= MLXSW_SP_ACL_ERP_GENALLOC_OFFSET;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_table_free(struct mlxsw_sp_acl_erp_core *erp_core,
 | |
| 			    unsigned int num_erps,
 | |
| 			    enum mlxsw_sp_acl_atcam_region_type region_type,
 | |
| 			    unsigned long index)
 | |
| {
 | |
| 	unsigned long base_index;
 | |
| 	unsigned int entry_size;
 | |
| 	size_t size;
 | |
| 
 | |
| 	entry_size = erp_core->erpt_entries_size[region_type];
 | |
| 	base_index = index + MLXSW_SP_ACL_ERP_GENALLOC_OFFSET;
 | |
| 	size = num_erps / erp_core->num_erp_banks * entry_size;
 | |
| 	gen_pool_free(erp_core->erp_tables, base_index, size);
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_table_master_rp(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	if (!list_is_singular(&erp_table->atcam_erps_list))
 | |
| 		return NULL;
 | |
| 
 | |
| 	return list_first_entry(&erp_table->atcam_erps_list,
 | |
| 				struct mlxsw_sp_acl_erp, list);
 | |
| }
 | |
| 
 | |
| static int mlxsw_sp_acl_erp_index_get(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				      u8 *p_index)
 | |
| {
 | |
| 	u8 index;
 | |
| 
 | |
| 	index = find_first_zero_bit(erp_table->erp_index_bitmap,
 | |
| 				    erp_table->num_max_atcam_erps);
 | |
| 	if (index < erp_table->num_max_atcam_erps) {
 | |
| 		__set_bit(index, erp_table->erp_index_bitmap);
 | |
| 		*p_index = index;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return -ENOBUFS;
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_index_put(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				       u8 index)
 | |
| {
 | |
| 	__clear_bit(index, erp_table->erp_index_bitmap);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_table_locate(const struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			      const struct mlxsw_sp_acl_erp *erp,
 | |
| 			      u8 *p_erpt_bank, u8 *p_erpt_index)
 | |
| {
 | |
| 	unsigned int entry_size = mlxsw_sp_acl_erp_table_entry_size(erp_table);
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core = erp_table->erp_core;
 | |
| 	unsigned int row;
 | |
| 
 | |
| 	*p_erpt_bank = erp->index % erp_core->num_erp_banks;
 | |
| 	row = erp->index / erp_core->num_erp_banks;
 | |
| 	*p_erpt_index = erp_table->base_index + row * entry_size;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_table_erp_add(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			       struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp *mlxsw_sp = erp_table->erp_core->mlxsw_sp;
 | |
| 	enum mlxsw_reg_perpt_key_size key_size;
 | |
| 	char perpt_pl[MLXSW_REG_PERPT_LEN];
 | |
| 	u8 erpt_bank, erpt_index;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_table_locate(erp_table, erp, &erpt_bank, &erpt_index);
 | |
| 	key_size = (enum mlxsw_reg_perpt_key_size) erp_table->aregion->type;
 | |
| 	mlxsw_reg_perpt_pack(perpt_pl, erpt_bank, erpt_index, key_size, erp->id,
 | |
| 			     0, erp_table->base_index, erp->index,
 | |
| 			     erp->key.mask);
 | |
| 	mlxsw_reg_perpt_erp_vector_pack(perpt_pl, erp_table->erp_index_bitmap,
 | |
| 					MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	mlxsw_reg_perpt_erp_vector_set(perpt_pl, erp->index, true);
 | |
| 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(perpt), perpt_pl);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_table_erp_del(struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	char empty_mask[MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN] = { 0 };
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = erp->erp_table;
 | |
| 	struct mlxsw_sp *mlxsw_sp = erp_table->erp_core->mlxsw_sp;
 | |
| 	enum mlxsw_reg_perpt_key_size key_size;
 | |
| 	char perpt_pl[MLXSW_REG_PERPT_LEN];
 | |
| 	u8 erpt_bank, erpt_index;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_table_locate(erp_table, erp, &erpt_bank, &erpt_index);
 | |
| 	key_size = (enum mlxsw_reg_perpt_key_size) erp_table->aregion->type;
 | |
| 	mlxsw_reg_perpt_pack(perpt_pl, erpt_bank, erpt_index, key_size, erp->id,
 | |
| 			     0, erp_table->base_index, erp->index, empty_mask);
 | |
| 	mlxsw_reg_perpt_erp_vector_pack(perpt_pl, erp_table->erp_index_bitmap,
 | |
| 					MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	mlxsw_reg_perpt_erp_vector_set(perpt_pl, erp->index, false);
 | |
| 	mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(perpt), perpt_pl);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_table_enable(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			      bool ctcam_le)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_tcam_region *region = erp_table->aregion->region;
 | |
| 	struct mlxsw_sp *mlxsw_sp = erp_table->erp_core->mlxsw_sp;
 | |
| 	char pererp_pl[MLXSW_REG_PERERP_LEN];
 | |
| 
 | |
| 	mlxsw_reg_pererp_pack(pererp_pl, region->id, ctcam_le, true, 0,
 | |
| 			      erp_table->base_index, 0);
 | |
| 	mlxsw_reg_pererp_erp_vector_pack(pererp_pl, erp_table->erp_index_bitmap,
 | |
| 					 MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 
 | |
| 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_table_disable(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_tcam_region *region = erp_table->aregion->region;
 | |
| 	struct mlxsw_sp *mlxsw_sp = erp_table->erp_core->mlxsw_sp;
 | |
| 	char pererp_pl[MLXSW_REG_PERERP_LEN];
 | |
| 	struct mlxsw_sp_acl_erp *master_rp;
 | |
| 
 | |
| 	master_rp = mlxsw_sp_acl_erp_table_master_rp(erp_table);
 | |
| 	/* It is possible we do not have a master RP when we disable the
 | |
| 	 * table when there are no rules in the A-TCAM and the last C-TCAM
 | |
| 	 * rule is deleted
 | |
| 	 */
 | |
| 	mlxsw_reg_pererp_pack(pererp_pl, region->id, false, false, 0, 0,
 | |
| 			      master_rp ? master_rp->id : 0);
 | |
| 	mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_table_relocate(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp *erp;
 | |
| 	int err;
 | |
| 
 | |
| 	list_for_each_entry(erp, &erp_table->atcam_erps_list, list) {
 | |
| 		err = mlxsw_sp_acl_erp_table_erp_add(erp_table, erp);
 | |
| 		if (err)
 | |
| 			goto err_table_erp_add;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_table_erp_add:
 | |
| 	list_for_each_entry_continue_reverse(erp, &erp_table->atcam_erps_list,
 | |
| 					     list)
 | |
| 		mlxsw_sp_acl_erp_table_erp_del(erp);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_table_expand(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	unsigned int num_erps, old_num_erps = erp_table->num_max_atcam_erps;
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core = erp_table->erp_core;
 | |
| 	unsigned long old_base_index = erp_table->base_index;
 | |
| 	bool ctcam_le = erp_table->num_ctcam_erps > 0;
 | |
| 	int err;
 | |
| 
 | |
| 	if (erp_table->num_atcam_erps < erp_table->num_max_atcam_erps)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (erp_table->num_max_atcam_erps == MLXSW_SP_ACL_ERP_MAX_PER_REGION)
 | |
| 		return -ENOBUFS;
 | |
| 
 | |
| 	num_erps = old_num_erps + erp_core->num_erp_banks;
 | |
| 	err = mlxsw_sp_acl_erp_table_alloc(erp_core, num_erps,
 | |
| 					   erp_table->aregion->type,
 | |
| 					   &erp_table->base_index);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 	erp_table->num_max_atcam_erps = num_erps;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_table_relocate(erp_table);
 | |
| 	if (err)
 | |
| 		goto err_table_relocate;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_table_enable(erp_table, ctcam_le);
 | |
| 	if (err)
 | |
| 		goto err_table_enable;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_table_free(erp_core, old_num_erps,
 | |
| 				    erp_table->aregion->type, old_base_index);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_table_enable:
 | |
| err_table_relocate:
 | |
| 	erp_table->num_max_atcam_erps = old_num_erps;
 | |
| 	mlxsw_sp_acl_erp_table_free(erp_core, num_erps,
 | |
| 				    erp_table->aregion->type,
 | |
| 				    erp_table->base_index);
 | |
| 	erp_table->base_index = old_base_index;
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_acl_erp_table_bf_add(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			   struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = erp_table->aregion;
 | |
| 	unsigned int erp_bank = mlxsw_sp_acl_erp_bank_get(erp);
 | |
| 	struct mlxsw_sp_acl_atcam_entry *aentry;
 | |
| 	int err;
 | |
| 
 | |
| 	list_for_each_entry(aentry, &aregion->entries_list, list) {
 | |
| 		err = mlxsw_sp_acl_bf_entry_add(aregion->region->mlxsw_sp,
 | |
| 						erp_table->erp_core->bf,
 | |
| 						aregion, erp_bank, aentry);
 | |
| 		if (err)
 | |
| 			goto bf_entry_add_err;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| bf_entry_add_err:
 | |
| 	list_for_each_entry_continue_reverse(aentry, &aregion->entries_list,
 | |
| 					     list)
 | |
| 		mlxsw_sp_acl_bf_entry_del(aregion->region->mlxsw_sp,
 | |
| 					  erp_table->erp_core->bf,
 | |
| 					  aregion, erp_bank, aentry);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_acl_erp_table_bf_del(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			   struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = erp_table->aregion;
 | |
| 	unsigned int erp_bank = mlxsw_sp_acl_erp_bank_get(erp);
 | |
| 	struct mlxsw_sp_acl_atcam_entry *aentry;
 | |
| 
 | |
| 	list_for_each_entry_reverse(aentry, &aregion->entries_list, list)
 | |
| 		mlxsw_sp_acl_bf_entry_del(aregion->region->mlxsw_sp,
 | |
| 					  erp_table->erp_core->bf,
 | |
| 					  aregion, erp_bank, aentry);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_region_table_trans(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core = erp_table->erp_core;
 | |
| 	struct mlxsw_sp_acl_erp *master_rp;
 | |
| 	int err;
 | |
| 
 | |
| 	/* Initially, allocate a single eRP row. Expand later as needed */
 | |
| 	err = mlxsw_sp_acl_erp_table_alloc(erp_core, erp_core->num_erp_banks,
 | |
| 					   erp_table->aregion->type,
 | |
| 					   &erp_table->base_index);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 	erp_table->num_max_atcam_erps = erp_core->num_erp_banks;
 | |
| 
 | |
| 	/* Transition the sole RP currently configured (the master RP)
 | |
| 	 * to the eRP table
 | |
| 	 */
 | |
| 	master_rp = mlxsw_sp_acl_erp_table_master_rp(erp_table);
 | |
| 	if (!master_rp) {
 | |
| 		err = -EINVAL;
 | |
| 		goto err_table_master_rp;
 | |
| 	}
 | |
| 
 | |
| 	/* Make sure the master RP is using a valid index, as
 | |
| 	 * only a single eRP row is currently allocated.
 | |
| 	 */
 | |
| 	master_rp->index = 0;
 | |
| 	__set_bit(master_rp->index, erp_table->erp_index_bitmap);
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_table_erp_add(erp_table, master_rp);
 | |
| 	if (err)
 | |
| 		goto err_table_master_rp_add;
 | |
| 
 | |
| 	/* Update Bloom filter before enabling eRP table, as rules
 | |
| 	 * on the master RP were not set to Bloom filter up to this
 | |
| 	 * point.
 | |
| 	 */
 | |
| 	err = mlxsw_acl_erp_table_bf_add(erp_table, master_rp);
 | |
| 	if (err)
 | |
| 		goto err_table_bf_add;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_table_enable(erp_table, false);
 | |
| 	if (err)
 | |
| 		goto err_table_enable;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_table_enable:
 | |
| 	mlxsw_acl_erp_table_bf_del(erp_table, master_rp);
 | |
| err_table_bf_add:
 | |
| 	mlxsw_sp_acl_erp_table_erp_del(master_rp);
 | |
| err_table_master_rp_add:
 | |
| 	__clear_bit(master_rp->index, erp_table->erp_index_bitmap);
 | |
| err_table_master_rp:
 | |
| 	mlxsw_sp_acl_erp_table_free(erp_core, erp_table->num_max_atcam_erps,
 | |
| 				    erp_table->aregion->type,
 | |
| 				    erp_table->base_index);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_region_master_mask_trans(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core = erp_table->erp_core;
 | |
| 	struct mlxsw_sp_acl_erp *master_rp;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_table_disable(erp_table);
 | |
| 	master_rp = mlxsw_sp_acl_erp_table_master_rp(erp_table);
 | |
| 	if (!master_rp)
 | |
| 		return;
 | |
| 	mlxsw_acl_erp_table_bf_del(erp_table, master_rp);
 | |
| 	mlxsw_sp_acl_erp_table_erp_del(master_rp);
 | |
| 	__clear_bit(master_rp->index, erp_table->erp_index_bitmap);
 | |
| 	mlxsw_sp_acl_erp_table_free(erp_core, erp_table->num_max_atcam_erps,
 | |
| 				    erp_table->aregion->type,
 | |
| 				    erp_table->base_index);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_region_erp_add(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_tcam_region *region = erp_table->aregion->region;
 | |
| 	struct mlxsw_sp *mlxsw_sp = erp_table->erp_core->mlxsw_sp;
 | |
| 	bool ctcam_le = erp_table->num_ctcam_erps > 0;
 | |
| 	char pererp_pl[MLXSW_REG_PERERP_LEN];
 | |
| 
 | |
| 	mlxsw_reg_pererp_pack(pererp_pl, region->id, ctcam_le, true, 0,
 | |
| 			      erp_table->base_index, 0);
 | |
| 	mlxsw_reg_pererp_erp_vector_pack(pererp_pl, erp_table->erp_index_bitmap,
 | |
| 					 MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	mlxsw_reg_pererp_erpt_vector_set(pererp_pl, erp->index, true);
 | |
| 
 | |
| 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_region_erp_del(struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = erp->erp_table;
 | |
| 	struct mlxsw_sp_acl_tcam_region *region = erp_table->aregion->region;
 | |
| 	struct mlxsw_sp *mlxsw_sp = erp_table->erp_core->mlxsw_sp;
 | |
| 	bool ctcam_le = erp_table->num_ctcam_erps > 0;
 | |
| 	char pererp_pl[MLXSW_REG_PERERP_LEN];
 | |
| 
 | |
| 	mlxsw_reg_pererp_pack(pererp_pl, region->id, ctcam_le, true, 0,
 | |
| 			      erp_table->base_index, 0);
 | |
| 	mlxsw_reg_pererp_erp_vector_pack(pererp_pl, erp_table->erp_index_bitmap,
 | |
| 					 MLXSW_SP_ACL_ERP_MAX_PER_REGION);
 | |
| 	mlxsw_reg_pererp_erpt_vector_set(pererp_pl, erp->index, false);
 | |
| 
 | |
| 	mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_region_ctcam_enable(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	/* No need to re-enable lookup in the C-TCAM */
 | |
| 	if (erp_table->num_ctcam_erps > 1)
 | |
| 		return 0;
 | |
| 
 | |
| 	return mlxsw_sp_acl_erp_table_enable(erp_table, true);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_region_ctcam_disable(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	/* Only disable C-TCAM lookup when last C-TCAM eRP is deleted */
 | |
| 	if (erp_table->num_ctcam_erps > 1)
 | |
| 		return;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_table_enable(erp_table, false);
 | |
| }
 | |
| 
 | |
| static int
 | |
| __mlxsw_sp_acl_erp_table_other_inc(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   unsigned int *inc_num)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	/* If there are C-TCAM eRP or deltas in use we need to transition
 | |
| 	 * the region to use eRP table, if it is not already done
 | |
| 	 */
 | |
| 	if (!mlxsw_sp_acl_erp_table_is_used(erp_table)) {
 | |
| 		err = mlxsw_sp_acl_erp_region_table_trans(erp_table);
 | |
| 		if (err)
 | |
| 			return err;
 | |
| 	}
 | |
| 
 | |
| 	/* When C-TCAM or deltas are used, the eRP table must be used */
 | |
| 	if (erp_table->ops != &erp_multiple_masks_ops)
 | |
| 		erp_table->ops = &erp_multiple_masks_ops;
 | |
| 
 | |
| 	(*inc_num)++;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mlxsw_sp_acl_erp_ctcam_inc(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	return __mlxsw_sp_acl_erp_table_other_inc(erp_table,
 | |
| 						  &erp_table->num_ctcam_erps);
 | |
| }
 | |
| 
 | |
| static int mlxsw_sp_acl_erp_delta_inc(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	return __mlxsw_sp_acl_erp_table_other_inc(erp_table,
 | |
| 						  &erp_table->num_deltas);
 | |
| }
 | |
| 
 | |
| static void
 | |
| __mlxsw_sp_acl_erp_table_other_dec(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   unsigned int *dec_num)
 | |
| {
 | |
| 	(*dec_num)--;
 | |
| 
 | |
| 	/* If there are no C-TCAM eRP or deltas in use, the state we
 | |
| 	 * transition to depends on the number of A-TCAM eRPs currently
 | |
| 	 * in use.
 | |
| 	 */
 | |
| 	if (erp_table->num_ctcam_erps > 0 || erp_table->num_deltas > 0)
 | |
| 		return;
 | |
| 
 | |
| 	switch (erp_table->num_atcam_erps) {
 | |
| 	case 2:
 | |
| 		/* Keep using the eRP table, but correctly set the
 | |
| 		 * operations pointer so that when an A-TCAM eRP is
 | |
| 		 * deleted we will transition to use the master mask
 | |
| 		 */
 | |
| 		erp_table->ops = &erp_two_masks_ops;
 | |
| 		break;
 | |
| 	case 1:
 | |
| 		/* We only kept the eRP table because we had C-TCAM
 | |
| 		 * eRPs in use. Now that the last C-TCAM eRP is gone we
 | |
| 		 * can stop using the table and transition to use the
 | |
| 		 * master mask
 | |
| 		 */
 | |
| 		mlxsw_sp_acl_erp_region_master_mask_trans(erp_table);
 | |
| 		erp_table->ops = &erp_single_mask_ops;
 | |
| 		break;
 | |
| 	case 0:
 | |
| 		/* There are no more eRPs of any kind used by the region
 | |
| 		 * so free its eRP table and transition to initial state
 | |
| 		 */
 | |
| 		mlxsw_sp_acl_erp_table_disable(erp_table);
 | |
| 		mlxsw_sp_acl_erp_table_free(erp_table->erp_core,
 | |
| 					    erp_table->num_max_atcam_erps,
 | |
| 					    erp_table->aregion->type,
 | |
| 					    erp_table->base_index);
 | |
| 		erp_table->ops = &erp_no_mask_ops;
 | |
| 		break;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_ctcam_dec(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	__mlxsw_sp_acl_erp_table_other_dec(erp_table,
 | |
| 					   &erp_table->num_ctcam_erps);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_delta_dec(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	__mlxsw_sp_acl_erp_table_other_dec(erp_table,
 | |
| 					   &erp_table->num_deltas);
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_ctcam_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp *erp;
 | |
| 	int err;
 | |
| 
 | |
| 	erp = kzalloc(sizeof(*erp), GFP_KERNEL);
 | |
| 	if (!erp)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	memcpy(&erp->key, key, sizeof(*key));
 | |
| 	bitmap_from_arr32(erp->mask_bitmap, (u32 *) key->mask,
 | |
| 			  MLXSW_SP_ACL_TCAM_MASK_LEN);
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_ctcam_inc(erp_table);
 | |
| 	if (err)
 | |
| 		goto err_erp_ctcam_inc;
 | |
| 
 | |
| 	erp->erp_table = erp_table;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_master_mask_set(erp_table, &erp->key);
 | |
| 	if (err)
 | |
| 		goto err_master_mask_set;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_region_ctcam_enable(erp_table);
 | |
| 	if (err)
 | |
| 		goto err_erp_region_ctcam_enable;
 | |
| 
 | |
| 	return erp;
 | |
| 
 | |
| err_erp_region_ctcam_enable:
 | |
| 	mlxsw_sp_acl_erp_master_mask_clear(erp_table, &erp->key);
 | |
| err_master_mask_set:
 | |
| 	mlxsw_sp_acl_erp_ctcam_dec(erp_table);
 | |
| err_erp_ctcam_inc:
 | |
| 	kfree(erp);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_ctcam_mask_destroy(struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = erp->erp_table;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_region_ctcam_disable(erp_table);
 | |
| 	mlxsw_sp_acl_erp_master_mask_clear(erp_table, &erp->key);
 | |
| 	mlxsw_sp_acl_erp_ctcam_dec(erp_table);
 | |
| 	kfree(erp);
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			     struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp *erp;
 | |
| 	int err;
 | |
| 
 | |
| 	if (key->ctcam)
 | |
| 		return mlxsw_sp_acl_erp_ctcam_mask_create(erp_table, key);
 | |
| 
 | |
| 	/* Expand the eRP table for the new eRP, if needed */
 | |
| 	err = mlxsw_sp_acl_erp_table_expand(erp_table);
 | |
| 	if (err)
 | |
| 		return ERR_PTR(err);
 | |
| 
 | |
| 	erp = mlxsw_sp_acl_erp_generic_create(erp_table, key);
 | |
| 	if (IS_ERR(erp))
 | |
| 		return erp;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_index_get(erp_table, &erp->index);
 | |
| 	if (err)
 | |
| 		goto err_erp_index_get;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_table_erp_add(erp_table, erp);
 | |
| 	if (err)
 | |
| 		goto err_table_erp_add;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_region_erp_add(erp_table, erp);
 | |
| 	if (err)
 | |
| 		goto err_region_erp_add;
 | |
| 
 | |
| 	erp_table->ops = &erp_multiple_masks_ops;
 | |
| 
 | |
| 	return erp;
 | |
| 
 | |
| err_region_erp_add:
 | |
| 	mlxsw_sp_acl_erp_table_erp_del(erp);
 | |
| err_table_erp_add:
 | |
| 	mlxsw_sp_acl_erp_index_put(erp_table, erp->index);
 | |
| err_erp_index_get:
 | |
| 	mlxsw_sp_acl_erp_generic_destroy(erp);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 			      struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	if (erp->key.ctcam)
 | |
| 		return mlxsw_sp_acl_erp_ctcam_mask_destroy(erp);
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_region_erp_del(erp);
 | |
| 	mlxsw_sp_acl_erp_table_erp_del(erp);
 | |
| 	mlxsw_sp_acl_erp_index_put(erp_table, erp->index);
 | |
| 	mlxsw_sp_acl_erp_generic_destroy(erp);
 | |
| 
 | |
| 	if (erp_table->num_atcam_erps == 2 && erp_table->num_ctcam_erps == 0 &&
 | |
| 	    erp_table->num_deltas == 0)
 | |
| 		erp_table->ops = &erp_two_masks_ops;
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_second_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				    struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp *erp;
 | |
| 	int err;
 | |
| 
 | |
| 	if (key->ctcam)
 | |
| 		return mlxsw_sp_acl_erp_ctcam_mask_create(erp_table, key);
 | |
| 
 | |
| 	/* Transition to use eRP table instead of master mask */
 | |
| 	err = mlxsw_sp_acl_erp_region_table_trans(erp_table);
 | |
| 	if (err)
 | |
| 		return ERR_PTR(err);
 | |
| 
 | |
| 	erp = mlxsw_sp_acl_erp_generic_create(erp_table, key);
 | |
| 	if (IS_ERR(erp)) {
 | |
| 		err = PTR_ERR(erp);
 | |
| 		goto err_erp_create;
 | |
| 	}
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_index_get(erp_table, &erp->index);
 | |
| 	if (err)
 | |
| 		goto err_erp_index_get;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_table_erp_add(erp_table, erp);
 | |
| 	if (err)
 | |
| 		goto err_table_erp_add;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_region_erp_add(erp_table, erp);
 | |
| 	if (err)
 | |
| 		goto err_region_erp_add;
 | |
| 
 | |
| 	erp_table->ops = &erp_two_masks_ops;
 | |
| 
 | |
| 	return erp;
 | |
| 
 | |
| err_region_erp_add:
 | |
| 	mlxsw_sp_acl_erp_table_erp_del(erp);
 | |
| err_table_erp_add:
 | |
| 	mlxsw_sp_acl_erp_index_put(erp_table, erp->index);
 | |
| err_erp_index_get:
 | |
| 	mlxsw_sp_acl_erp_generic_destroy(erp);
 | |
| err_erp_create:
 | |
| 	mlxsw_sp_acl_erp_region_master_mask_trans(erp_table);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_second_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				     struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	if (erp->key.ctcam)
 | |
| 		return mlxsw_sp_acl_erp_ctcam_mask_destroy(erp);
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_region_erp_del(erp);
 | |
| 	mlxsw_sp_acl_erp_table_erp_del(erp);
 | |
| 	mlxsw_sp_acl_erp_index_put(erp_table, erp->index);
 | |
| 	mlxsw_sp_acl_erp_generic_destroy(erp);
 | |
| 	/* Transition to use master mask instead of eRP table */
 | |
| 	mlxsw_sp_acl_erp_region_master_mask_trans(erp_table);
 | |
| 
 | |
| 	erp_table->ops = &erp_single_mask_ops;
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp *
 | |
| mlxsw_sp_acl_erp_first_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				   struct mlxsw_sp_acl_erp_key *key)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp *erp;
 | |
| 
 | |
| 	if (key->ctcam)
 | |
| 		return ERR_PTR(-EINVAL);
 | |
| 
 | |
| 	erp = mlxsw_sp_acl_erp_generic_create(erp_table, key);
 | |
| 	if (IS_ERR(erp))
 | |
| 		return erp;
 | |
| 
 | |
| 	erp_table->ops = &erp_single_mask_ops;
 | |
| 
 | |
| 	return erp;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_first_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				    struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	mlxsw_sp_acl_erp_generic_destroy(erp);
 | |
| 	erp_table->ops = &erp_no_mask_ops;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_no_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 | |
| 				 struct mlxsw_sp_acl_erp *erp)
 | |
| {
 | |
| 	WARN_ON(1);
 | |
| }
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_mask *
 | |
| mlxsw_sp_acl_erp_mask_get(struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 			  const char *mask, bool ctcam)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 	struct mlxsw_sp_acl_erp_key key;
 | |
| 	struct objagg_obj *objagg_obj;
 | |
| 
 | |
| 	memcpy(key.mask, mask, MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN);
 | |
| 	key.ctcam = ctcam;
 | |
| 	mutex_lock(&erp_table->objagg_lock);
 | |
| 	objagg_obj = objagg_obj_get(erp_table->objagg, &key);
 | |
| 	mutex_unlock(&erp_table->objagg_lock);
 | |
| 	if (IS_ERR(objagg_obj))
 | |
| 		return ERR_CAST(objagg_obj);
 | |
| 	return (struct mlxsw_sp_acl_erp_mask *) objagg_obj;
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_acl_erp_mask_put(struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 			       struct mlxsw_sp_acl_erp_mask *erp_mask)
 | |
| {
 | |
| 	struct objagg_obj *objagg_obj = (struct objagg_obj *) erp_mask;
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 
 | |
| 	mutex_lock(&erp_table->objagg_lock);
 | |
| 	objagg_obj_put(erp_table->objagg, objagg_obj);
 | |
| 	mutex_unlock(&erp_table->objagg_lock);
 | |
| }
 | |
| 
 | |
| int mlxsw_sp_acl_erp_bf_insert(struct mlxsw_sp *mlxsw_sp,
 | |
| 			       struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 			       struct mlxsw_sp_acl_erp_mask *erp_mask,
 | |
| 			       struct mlxsw_sp_acl_atcam_entry *aentry)
 | |
| {
 | |
| 	struct objagg_obj *objagg_obj = (struct objagg_obj *) erp_mask;
 | |
| 	const struct mlxsw_sp_acl_erp *erp = objagg_obj_root_priv(objagg_obj);
 | |
| 	unsigned int erp_bank;
 | |
| 
 | |
| 	if (!mlxsw_sp_acl_erp_table_is_used(erp->erp_table))
 | |
| 		return 0;
 | |
| 
 | |
| 	erp_bank = mlxsw_sp_acl_erp_bank_get(erp);
 | |
| 	return mlxsw_sp_acl_bf_entry_add(mlxsw_sp,
 | |
| 					erp->erp_table->erp_core->bf,
 | |
| 					aregion, erp_bank, aentry);
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_acl_erp_bf_remove(struct mlxsw_sp *mlxsw_sp,
 | |
| 				struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 				struct mlxsw_sp_acl_erp_mask *erp_mask,
 | |
| 				struct mlxsw_sp_acl_atcam_entry *aentry)
 | |
| {
 | |
| 	struct objagg_obj *objagg_obj = (struct objagg_obj *) erp_mask;
 | |
| 	const struct mlxsw_sp_acl_erp *erp = objagg_obj_root_priv(objagg_obj);
 | |
| 	unsigned int erp_bank;
 | |
| 
 | |
| 	if (!mlxsw_sp_acl_erp_table_is_used(erp->erp_table))
 | |
| 		return;
 | |
| 
 | |
| 	erp_bank = mlxsw_sp_acl_erp_bank_get(erp);
 | |
| 	mlxsw_sp_acl_bf_entry_del(mlxsw_sp,
 | |
| 				  erp->erp_table->erp_core->bf,
 | |
| 				  aregion, erp_bank, aentry);
 | |
| }
 | |
| 
 | |
| bool
 | |
| mlxsw_sp_acl_erp_mask_is_ctcam(const struct mlxsw_sp_acl_erp_mask *erp_mask)
 | |
| {
 | |
| 	struct objagg_obj *objagg_obj = (struct objagg_obj *) erp_mask;
 | |
| 	const struct mlxsw_sp_acl_erp_key *key = objagg_obj_raw(objagg_obj);
 | |
| 
 | |
| 	return key->ctcam;
 | |
| }
 | |
| 
 | |
| u8 mlxsw_sp_acl_erp_mask_erp_id(const struct mlxsw_sp_acl_erp_mask *erp_mask)
 | |
| {
 | |
| 	struct objagg_obj *objagg_obj = (struct objagg_obj *) erp_mask;
 | |
| 	const struct mlxsw_sp_acl_erp *erp = objagg_obj_root_priv(objagg_obj);
 | |
| 
 | |
| 	return erp->id;
 | |
| }
 | |
| 
 | |
| struct mlxsw_sp_acl_erp_delta {
 | |
| 	struct mlxsw_sp_acl_erp_key key;
 | |
| 	u16 start;
 | |
| 	u8 mask;
 | |
| };
 | |
| 
 | |
| u16 mlxsw_sp_acl_erp_delta_start(const struct mlxsw_sp_acl_erp_delta *delta)
 | |
| {
 | |
| 	return delta->start;
 | |
| }
 | |
| 
 | |
| u8 mlxsw_sp_acl_erp_delta_mask(const struct mlxsw_sp_acl_erp_delta *delta)
 | |
| {
 | |
| 	return delta->mask;
 | |
| }
 | |
| 
 | |
| u8 mlxsw_sp_acl_erp_delta_value(const struct mlxsw_sp_acl_erp_delta *delta,
 | |
| 				const char *enc_key)
 | |
| {
 | |
| 	u16 start = delta->start;
 | |
| 	u8 mask = delta->mask;
 | |
| 	u16 tmp;
 | |
| 
 | |
| 	if (!mask)
 | |
| 		return 0;
 | |
| 
 | |
| 	tmp = (unsigned char) enc_key[__MASK_IDX(start / 8)];
 | |
| 	if (start / 8 + 1 < __MASK_LEN)
 | |
| 		tmp |= (unsigned char) enc_key[__MASK_IDX(start / 8 + 1)] << 8;
 | |
| 	tmp >>= start % 8;
 | |
| 	tmp &= mask;
 | |
| 	return tmp;
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_acl_erp_delta_clear(const struct mlxsw_sp_acl_erp_delta *delta,
 | |
| 				  const char *enc_key)
 | |
| {
 | |
| 	u16 start = delta->start;
 | |
| 	u8 mask = delta->mask;
 | |
| 	unsigned char *byte;
 | |
| 	u16 tmp;
 | |
| 
 | |
| 	tmp = mask;
 | |
| 	tmp <<= start % 8;
 | |
| 	tmp = ~tmp;
 | |
| 
 | |
| 	byte = (unsigned char *) &enc_key[__MASK_IDX(start / 8)];
 | |
| 	*byte &= tmp & 0xff;
 | |
| 	if (start / 8 + 1 < __MASK_LEN) {
 | |
| 		byte = (unsigned char *) &enc_key[__MASK_IDX(start / 8 + 1)];
 | |
| 		*byte &= (tmp >> 8) & 0xff;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static const struct mlxsw_sp_acl_erp_delta
 | |
| mlxsw_sp_acl_erp_delta_default = {};
 | |
| 
 | |
| const struct mlxsw_sp_acl_erp_delta *
 | |
| mlxsw_sp_acl_erp_delta(const struct mlxsw_sp_acl_erp_mask *erp_mask)
 | |
| {
 | |
| 	struct objagg_obj *objagg_obj = (struct objagg_obj *) erp_mask;
 | |
| 	const struct mlxsw_sp_acl_erp_delta *delta;
 | |
| 
 | |
| 	delta = objagg_obj_delta_priv(objagg_obj);
 | |
| 	if (!delta)
 | |
| 		delta = &mlxsw_sp_acl_erp_delta_default;
 | |
| 	return delta;
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_delta_fill(const struct mlxsw_sp_acl_erp_key *parent_key,
 | |
| 			    const struct mlxsw_sp_acl_erp_key *key,
 | |
| 			    u16 *delta_start, u8 *delta_mask)
 | |
| {
 | |
| 	int offset = 0;
 | |
| 	int si = -1;
 | |
| 	u16 pmask;
 | |
| 	u16 mask;
 | |
| 	int i;
 | |
| 
 | |
| 	/* The difference between 2 masks can be up to 8 consecutive bits. */
 | |
| 	for (i = 0; i < __MASK_LEN; i++) {
 | |
| 		if (parent_key->mask[__MASK_IDX(i)] == key->mask[__MASK_IDX(i)])
 | |
| 			continue;
 | |
| 		if (si == -1)
 | |
| 			si = i;
 | |
| 		else if (si != i - 1)
 | |
| 			return -EINVAL;
 | |
| 	}
 | |
| 	if (si == -1) {
 | |
| 		/* The masks are the same, this can happen in case eRPs with
 | |
| 		 * the same mask were created in both A-TCAM and C-TCAM.
 | |
| 		 * The only possible condition under which this can happen
 | |
| 		 * is identical rule insertion. Delta is not possible here.
 | |
| 		 */
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	pmask = (unsigned char) parent_key->mask[__MASK_IDX(si)];
 | |
| 	mask = (unsigned char) key->mask[__MASK_IDX(si)];
 | |
| 	if (si + 1 < __MASK_LEN) {
 | |
| 		pmask |= (unsigned char) parent_key->mask[__MASK_IDX(si + 1)] << 8;
 | |
| 		mask |= (unsigned char) key->mask[__MASK_IDX(si + 1)] << 8;
 | |
| 	}
 | |
| 
 | |
| 	if ((pmask ^ mask) & pmask)
 | |
| 		return -EINVAL;
 | |
| 	mask &= ~pmask;
 | |
| 	while (!(mask & (1 << offset)))
 | |
| 		offset++;
 | |
| 	while (!(mask & 1))
 | |
| 		mask >>= 1;
 | |
| 	if (mask & 0xff00)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	*delta_start = si * 8 + offset;
 | |
| 	*delta_mask = mask;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static bool mlxsw_sp_acl_erp_delta_check(void *priv, const void *parent_obj,
 | |
| 					 const void *obj)
 | |
| {
 | |
| 	const struct mlxsw_sp_acl_erp_key *parent_key = parent_obj;
 | |
| 	const struct mlxsw_sp_acl_erp_key *key = obj;
 | |
| 	u16 delta_start;
 | |
| 	u8 delta_mask;
 | |
| 	int err;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_delta_fill(parent_key, key,
 | |
| 					  &delta_start, &delta_mask);
 | |
| 	return err ? false : true;
 | |
| }
 | |
| 
 | |
| static void *mlxsw_sp_acl_erp_delta_create(void *priv, void *parent_obj,
 | |
| 					   void *obj)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_key *parent_key = parent_obj;
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = priv;
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 	struct mlxsw_sp_acl_erp_key *key = obj;
 | |
| 	struct mlxsw_sp_acl_erp_delta *delta;
 | |
| 	u16 delta_start;
 | |
| 	u8 delta_mask;
 | |
| 	int err;
 | |
| 
 | |
| 	if (parent_key->ctcam || key->ctcam)
 | |
| 		return ERR_PTR(-EINVAL);
 | |
| 	err = mlxsw_sp_acl_erp_delta_fill(parent_key, key,
 | |
| 					  &delta_start, &delta_mask);
 | |
| 	if (err)
 | |
| 		return ERR_PTR(-EINVAL);
 | |
| 
 | |
| 	delta = kzalloc(sizeof(*delta), GFP_KERNEL);
 | |
| 	if (!delta)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 	delta->start = delta_start;
 | |
| 	delta->mask = delta_mask;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_delta_inc(erp_table);
 | |
| 	if (err)
 | |
| 		goto err_erp_delta_inc;
 | |
| 
 | |
| 	memcpy(&delta->key, key, sizeof(*key));
 | |
| 	err = mlxsw_sp_acl_erp_master_mask_set(erp_table, &delta->key);
 | |
| 	if (err)
 | |
| 		goto err_master_mask_set;
 | |
| 
 | |
| 	return delta;
 | |
| 
 | |
| err_master_mask_set:
 | |
| 	mlxsw_sp_acl_erp_delta_dec(erp_table);
 | |
| err_erp_delta_inc:
 | |
| 	kfree(delta);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_delta_destroy(void *priv, void *delta_priv)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_delta *delta = delta_priv;
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = priv;
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 
 | |
| 	mlxsw_sp_acl_erp_master_mask_clear(erp_table, &delta->key);
 | |
| 	mlxsw_sp_acl_erp_delta_dec(erp_table);
 | |
| 	kfree(delta);
 | |
| }
 | |
| 
 | |
| static void *mlxsw_sp_acl_erp_root_create(void *priv, void *obj,
 | |
| 					  unsigned int root_id)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = priv;
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 	struct mlxsw_sp_acl_erp_key *key = obj;
 | |
| 
 | |
| 	if (!key->ctcam &&
 | |
| 	    root_id != OBJAGG_OBJ_ROOT_ID_INVALID &&
 | |
| 	    root_id >= MLXSW_SP_ACL_ERP_MAX_PER_REGION)
 | |
| 		return ERR_PTR(-ENOBUFS);
 | |
| 	return erp_table->ops->erp_create(erp_table, key);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_root_destroy(void *priv, void *root_priv)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_atcam_region *aregion = priv;
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 
 | |
| 	erp_table->ops->erp_destroy(erp_table, root_priv);
 | |
| }
 | |
| 
 | |
| static const struct objagg_ops mlxsw_sp_acl_erp_objagg_ops = {
 | |
| 	.obj_size = sizeof(struct mlxsw_sp_acl_erp_key),
 | |
| 	.delta_check = mlxsw_sp_acl_erp_delta_check,
 | |
| 	.delta_create = mlxsw_sp_acl_erp_delta_create,
 | |
| 	.delta_destroy = mlxsw_sp_acl_erp_delta_destroy,
 | |
| 	.root_create = mlxsw_sp_acl_erp_root_create,
 | |
| 	.root_destroy = mlxsw_sp_acl_erp_root_destroy,
 | |
| };
 | |
| 
 | |
| static struct mlxsw_sp_acl_erp_table *
 | |
| mlxsw_sp_acl_erp_table_create(struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 			      struct objagg_hints *hints)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table;
 | |
| 	int err;
 | |
| 
 | |
| 	erp_table = kzalloc(sizeof(*erp_table), GFP_KERNEL);
 | |
| 	if (!erp_table)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	erp_table->objagg = objagg_create(&mlxsw_sp_acl_erp_objagg_ops,
 | |
| 					  hints, aregion);
 | |
| 	if (IS_ERR(erp_table->objagg)) {
 | |
| 		err = PTR_ERR(erp_table->objagg);
 | |
| 		goto err_objagg_create;
 | |
| 	}
 | |
| 
 | |
| 	erp_table->erp_core = aregion->atcam->erp_core;
 | |
| 	erp_table->ops = &erp_no_mask_ops;
 | |
| 	INIT_LIST_HEAD(&erp_table->atcam_erps_list);
 | |
| 	erp_table->aregion = aregion;
 | |
| 	mutex_init(&erp_table->objagg_lock);
 | |
| 
 | |
| 	return erp_table;
 | |
| 
 | |
| err_objagg_create:
 | |
| 	kfree(erp_table);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_acl_erp_table_destroy(struct mlxsw_sp_acl_erp_table *erp_table)
 | |
| {
 | |
| 	WARN_ON(!list_empty(&erp_table->atcam_erps_list));
 | |
| 	mutex_destroy(&erp_table->objagg_lock);
 | |
| 	objagg_destroy(erp_table->objagg);
 | |
| 	kfree(erp_table);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_master_mask_init(struct mlxsw_sp_acl_atcam_region *aregion)
 | |
| {
 | |
| 	struct mlxsw_sp *mlxsw_sp = aregion->region->mlxsw_sp;
 | |
| 	char percr_pl[MLXSW_REG_PERCR_LEN];
 | |
| 
 | |
| 	mlxsw_reg_percr_pack(percr_pl, aregion->region->id);
 | |
| 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(percr), percr_pl);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_region_param_init(struct mlxsw_sp_acl_atcam_region *aregion)
 | |
| {
 | |
| 	struct mlxsw_sp *mlxsw_sp = aregion->region->mlxsw_sp;
 | |
| 	char pererp_pl[MLXSW_REG_PERERP_LEN];
 | |
| 
 | |
| 	mlxsw_reg_pererp_pack(pererp_pl, aregion->region->id, false, false, 0,
 | |
| 			      0, 0);
 | |
| 	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_hints_check(struct mlxsw_sp *mlxsw_sp,
 | |
| 			     struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 			     struct objagg_hints *hints, bool *p_rehash_needed)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 	const struct objagg_stats *ostats;
 | |
| 	const struct objagg_stats *hstats;
 | |
| 	int err;
 | |
| 
 | |
| 	*p_rehash_needed = false;
 | |
| 
 | |
| 	mutex_lock(&erp_table->objagg_lock);
 | |
| 	ostats = objagg_stats_get(erp_table->objagg);
 | |
| 	mutex_unlock(&erp_table->objagg_lock);
 | |
| 	if (IS_ERR(ostats)) {
 | |
| 		dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Failed to get ERP stats\n");
 | |
| 		return PTR_ERR(ostats);
 | |
| 	}
 | |
| 
 | |
| 	hstats = objagg_hints_stats_get(hints);
 | |
| 	if (IS_ERR(hstats)) {
 | |
| 		dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Failed to get ERP hints stats\n");
 | |
| 		err = PTR_ERR(hstats);
 | |
| 		goto err_hints_stats_get;
 | |
| 	}
 | |
| 
 | |
| 	/* Very basic criterion for now. */
 | |
| 	if (hstats->root_count < ostats->root_count)
 | |
| 		*p_rehash_needed = true;
 | |
| 
 | |
| 	err = 0;
 | |
| 
 | |
| 	objagg_stats_put(hstats);
 | |
| err_hints_stats_get:
 | |
| 	objagg_stats_put(ostats);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void *
 | |
| mlxsw_sp_acl_erp_rehash_hints_get(struct mlxsw_sp_acl_atcam_region *aregion)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
 | |
| 	struct mlxsw_sp *mlxsw_sp = aregion->region->mlxsw_sp;
 | |
| 	struct objagg_hints *hints;
 | |
| 	bool rehash_needed;
 | |
| 	int err;
 | |
| 
 | |
| 	mutex_lock(&erp_table->objagg_lock);
 | |
| 	hints = objagg_hints_get(erp_table->objagg,
 | |
| 				 OBJAGG_OPT_ALGO_SIMPLE_GREEDY);
 | |
| 	mutex_unlock(&erp_table->objagg_lock);
 | |
| 	if (IS_ERR(hints)) {
 | |
| 		dev_err_ratelimited(mlxsw_sp->bus_info->dev, "Failed to create ERP hints\n");
 | |
| 		return ERR_CAST(hints);
 | |
| 	}
 | |
| 	err = mlxsw_sp_acl_erp_hints_check(mlxsw_sp, aregion, hints,
 | |
| 					   &rehash_needed);
 | |
| 	if (err)
 | |
| 		goto errout;
 | |
| 
 | |
| 	if (!rehash_needed) {
 | |
| 		err = -EAGAIN;
 | |
| 		goto errout;
 | |
| 	}
 | |
| 	return hints;
 | |
| 
 | |
| errout:
 | |
| 	objagg_hints_put(hints);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_acl_erp_rehash_hints_put(void *hints_priv)
 | |
| {
 | |
| 	struct objagg_hints *hints = hints_priv;
 | |
| 
 | |
| 	objagg_hints_put(hints);
 | |
| }
 | |
| 
 | |
| int mlxsw_sp_acl_erp_region_init(struct mlxsw_sp_acl_atcam_region *aregion,
 | |
| 				 void *hints_priv)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_table *erp_table;
 | |
| 	struct objagg_hints *hints = hints_priv;
 | |
| 	int err;
 | |
| 
 | |
| 	erp_table = mlxsw_sp_acl_erp_table_create(aregion, hints);
 | |
| 	if (IS_ERR(erp_table))
 | |
| 		return PTR_ERR(erp_table);
 | |
| 	aregion->erp_table = erp_table;
 | |
| 
 | |
| 	/* Initialize the region's master mask to all zeroes */
 | |
| 	err = mlxsw_sp_acl_erp_master_mask_init(aregion);
 | |
| 	if (err)
 | |
| 		goto err_erp_master_mask_init;
 | |
| 
 | |
| 	/* Initialize the region to not use the eRP table */
 | |
| 	err = mlxsw_sp_acl_erp_region_param_init(aregion);
 | |
| 	if (err)
 | |
| 		goto err_erp_region_param_init;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_erp_region_param_init:
 | |
| err_erp_master_mask_init:
 | |
| 	mlxsw_sp_acl_erp_table_destroy(erp_table);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_acl_erp_region_fini(struct mlxsw_sp_acl_atcam_region *aregion)
 | |
| {
 | |
| 	mlxsw_sp_acl_erp_table_destroy(aregion->erp_table);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_acl_erp_tables_sizes_query(struct mlxsw_sp *mlxsw_sp,
 | |
| 				    struct mlxsw_sp_acl_erp_core *erp_core)
 | |
| {
 | |
| 	unsigned int size;
 | |
| 
 | |
| 	if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, ACL_ERPT_ENTRIES_2KB) ||
 | |
| 	    !MLXSW_CORE_RES_VALID(mlxsw_sp->core, ACL_ERPT_ENTRIES_4KB) ||
 | |
| 	    !MLXSW_CORE_RES_VALID(mlxsw_sp->core, ACL_ERPT_ENTRIES_8KB) ||
 | |
| 	    !MLXSW_CORE_RES_VALID(mlxsw_sp->core, ACL_ERPT_ENTRIES_12KB))
 | |
| 		return -EIO;
 | |
| 
 | |
| 	size = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_ERPT_ENTRIES_2KB);
 | |
| 	erp_core->erpt_entries_size[MLXSW_SP_ACL_ATCAM_REGION_TYPE_2KB] = size;
 | |
| 
 | |
| 	size = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_ERPT_ENTRIES_4KB);
 | |
| 	erp_core->erpt_entries_size[MLXSW_SP_ACL_ATCAM_REGION_TYPE_4KB] = size;
 | |
| 
 | |
| 	size = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_ERPT_ENTRIES_8KB);
 | |
| 	erp_core->erpt_entries_size[MLXSW_SP_ACL_ATCAM_REGION_TYPE_8KB] = size;
 | |
| 
 | |
| 	size = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_ERPT_ENTRIES_12KB);
 | |
| 	erp_core->erpt_entries_size[MLXSW_SP_ACL_ATCAM_REGION_TYPE_12KB] = size;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mlxsw_sp_acl_erp_tables_init(struct mlxsw_sp *mlxsw_sp,
 | |
| 					struct mlxsw_sp_acl_erp_core *erp_core)
 | |
| {
 | |
| 	unsigned int erpt_bank_size;
 | |
| 	int err;
 | |
| 
 | |
| 	if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, ACL_MAX_ERPT_BANK_SIZE) ||
 | |
| 	    !MLXSW_CORE_RES_VALID(mlxsw_sp->core, ACL_MAX_ERPT_BANKS))
 | |
| 		return -EIO;
 | |
| 	erpt_bank_size = MLXSW_CORE_RES_GET(mlxsw_sp->core,
 | |
| 					    ACL_MAX_ERPT_BANK_SIZE);
 | |
| 	erp_core->num_erp_banks = MLXSW_CORE_RES_GET(mlxsw_sp->core,
 | |
| 						     ACL_MAX_ERPT_BANKS);
 | |
| 
 | |
| 	erp_core->erp_tables = gen_pool_create(0, -1);
 | |
| 	if (!erp_core->erp_tables)
 | |
| 		return -ENOMEM;
 | |
| 	gen_pool_set_algo(erp_core->erp_tables, gen_pool_best_fit, NULL);
 | |
| 
 | |
| 	err = gen_pool_add(erp_core->erp_tables,
 | |
| 			   MLXSW_SP_ACL_ERP_GENALLOC_OFFSET, erpt_bank_size,
 | |
| 			   -1);
 | |
| 	if (err)
 | |
| 		goto err_gen_pool_add;
 | |
| 
 | |
| 	erp_core->bf = mlxsw_sp_acl_bf_init(mlxsw_sp, erp_core->num_erp_banks);
 | |
| 	if (IS_ERR(erp_core->bf)) {
 | |
| 		err = PTR_ERR(erp_core->bf);
 | |
| 		goto err_bf_init;
 | |
| 	}
 | |
| 
 | |
| 	/* Different regions require masks of different sizes */
 | |
| 	err = mlxsw_sp_acl_erp_tables_sizes_query(mlxsw_sp, erp_core);
 | |
| 	if (err)
 | |
| 		goto err_erp_tables_sizes_query;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_erp_tables_sizes_query:
 | |
| 	mlxsw_sp_acl_bf_fini(erp_core->bf);
 | |
| err_bf_init:
 | |
| err_gen_pool_add:
 | |
| 	gen_pool_destroy(erp_core->erp_tables);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_acl_erp_tables_fini(struct mlxsw_sp *mlxsw_sp,
 | |
| 					 struct mlxsw_sp_acl_erp_core *erp_core)
 | |
| {
 | |
| 	mlxsw_sp_acl_bf_fini(erp_core->bf);
 | |
| 	gen_pool_destroy(erp_core->erp_tables);
 | |
| }
 | |
| 
 | |
| int mlxsw_sp_acl_erps_init(struct mlxsw_sp *mlxsw_sp,
 | |
| 			   struct mlxsw_sp_acl_atcam *atcam)
 | |
| {
 | |
| 	struct mlxsw_sp_acl_erp_core *erp_core;
 | |
| 	int err;
 | |
| 
 | |
| 	erp_core = kzalloc(sizeof(*erp_core), GFP_KERNEL);
 | |
| 	if (!erp_core)
 | |
| 		return -ENOMEM;
 | |
| 	erp_core->mlxsw_sp = mlxsw_sp;
 | |
| 	atcam->erp_core = erp_core;
 | |
| 
 | |
| 	err = mlxsw_sp_acl_erp_tables_init(mlxsw_sp, erp_core);
 | |
| 	if (err)
 | |
| 		goto err_erp_tables_init;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_erp_tables_init:
 | |
| 	kfree(erp_core);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_acl_erps_fini(struct mlxsw_sp *mlxsw_sp,
 | |
| 			    struct mlxsw_sp_acl_atcam *atcam)
 | |
| {
 | |
| 	mlxsw_sp_acl_erp_tables_fini(mlxsw_sp, atcam->erp_core);
 | |
| 	kfree(atcam->erp_core);
 | |
| }
 |