summaryrefslogblamecommitdiffstats
path: root/drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c
blob: bb07723a05774fb86ade393c2096fe43a359a67a (plain) (tree)





























































                                                                               
                   





























                                                                          
                                    

























































                                                                              




                                                                      











































































































































































































































































































                                                                                 

                                                                       




                                                                             
                                                                       















                                                                                



                                                                          
                                                                        
                                                             





























                                                                              
                                                      



















                                                                             
                                                                 



















































                                                                              
                                                              






































                                                                                   
                                                      

                                             
                                                                       












                                                                                
                                                      

                                             
                                                                       







                                                                                


























































































































































                                                                               






                                                                      


                                                                            





































                                                                       


                                                                




                                                          
                                                                             









                                                                             


                                                                          









































                                                                              


                                                                















                                                                            


                                        

























                                                                             
                                                  










                                                                       
                          











































































                                                                               
                                                                            













                                                                             


                                                                     



                                                         


                                                                       




















































































































                                                                                
/*
 * drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c
 * Copyright (c) 2018 Mellanox Technologies. All rights reserved.
 * Copyright (c) 2018 Ido Schimmel <idosch@mellanox.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the names of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#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/rhashtable.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;
	unsigned int num_erp_banks;
};

struct mlxsw_sp_acl_erp_key {
	char mask[MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN];
	bool ctcam;
};

struct mlxsw_sp_acl_erp {
	struct mlxsw_sp_acl_erp_key key;
	u8 id;
	u8 index;
	refcount_t refcnt;
	DECLARE_BITMAP(mask_bitmap, MLXSW_SP_ACL_TCAM_MASK_LEN);
	struct list_head list;
	struct rhash_head ht_node;
	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 rhashtable erp_ht;
	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;
};

static const struct rhashtable_params mlxsw_sp_acl_erp_ht_params = {
	.key_len = sizeof(struct mlxsw_sp_acl_erp_key),
	.key_offset = offsetof(struct mlxsw_sp_acl_erp, key),
	.head_offset = offsetof(struct mlxsw_sp_acl_erp, ht_node),
};

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,
};

bool mlxsw_sp_acl_erp_is_ctcam_erp(const struct mlxsw_sp_acl_erp *erp)
{
	return erp->key.ctcam;
}

u8 mlxsw_sp_acl_erp_id(const struct mlxsw_sp_acl_erp *erp)
{
	return erp->id;
}

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,
				 const struct mlxsw_sp_acl_erp *erp)
{
	unsigned long bit;
	int err;

	for_each_set_bit(bit, erp->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, erp->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,
				   const struct mlxsw_sp_acl_erp *erp)
{
	unsigned long bit;
	int err;

	for_each_set_bit(bit, erp->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, erp->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));
	bitmap_from_arr32(erp->mask_bitmap, (u32 *) key->mask,
			  MLXSW_SP_ACL_TCAM_MASK_LEN);
	list_add(&erp->list, &erp_table->atcam_erps_list);
	refcount_set(&erp->refcnt, 1);
	erp_table->num_atcam_erps++;
	erp->erp_table = erp_table;

	err = mlxsw_sp_acl_erp_master_mask_set(erp_table, erp);
	if (err)
		goto err_master_mask_set;

	err = rhashtable_insert_fast(&erp_table->erp_ht, &erp->ht_node,
				     mlxsw_sp_acl_erp_ht_params);
	if (err)
		goto err_rhashtable_insert;

	return erp;

err_rhashtable_insert:
	mlxsw_sp_acl_erp_master_mask_clear(erp_table, 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;

	rhashtable_remove_fast(&erp_table->erp_ht, &erp->ht_node,
			       mlxsw_sp_acl_erp_ht_params);
	mlxsw_sp_acl_erp_master_mask_clear(erp_table, erp);
	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_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;
	}

	/* Maintain the same eRP bank for the master RP, so that we
	 * wouldn't need to update the bloom filter
	 */
	master_rp->index = master_rp->index % erp_core->num_erp_banks;
	__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;

	err = mlxsw_sp_acl_erp_table_enable(erp_table, false);
	if (err)
		goto err_table_enable;

	return 0;

err_table_enable:
	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_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 void
mlxsw_sp_acl_erp_ctcam_table_ops_set(struct mlxsw_sp_acl_erp_table *erp_table)
{
	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 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);
	refcount_set(&erp->refcnt, 1);
	erp_table->num_ctcam_erps++;
	erp->erp_table = erp_table;

	err = mlxsw_sp_acl_erp_master_mask_set(erp_table, erp);
	if (err)
		goto err_master_mask_set;

	err = rhashtable_insert_fast(&erp_table->erp_ht, &erp->ht_node,
				     mlxsw_sp_acl_erp_ht_params);
	if (err)
		goto err_rhashtable_insert;

	err = mlxsw_sp_acl_erp_region_ctcam_enable(erp_table);
	if (err)
		goto err_erp_region_ctcam_enable;

	/* When C-TCAM is used, the eRP table must be used */
	erp_table->ops = &erp_multiple_masks_ops;

	return erp;

err_erp_region_ctcam_enable:
	rhashtable_remove_fast(&erp_table->erp_ht, &erp->ht_node,
			       mlxsw_sp_acl_erp_ht_params);
err_rhashtable_insert:
	mlxsw_sp_acl_erp_master_mask_clear(erp_table, erp);
err_master_mask_set:
	erp_table->num_ctcam_erps--;
	kfree(erp);
	return ERR_PTR(err);
}

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;

	/* There is a special situation where we need to spill rules
	 * into the C-TCAM, yet the region is still using a master
	 * mask and thus not performing a lookup in the C-TCAM. This
	 * can happen when two rules that only differ in priority - and
	 * thus sharing the same key - are programmed. In this case
	 * we transition the region to use an eRP table
	 */
	err = mlxsw_sp_acl_erp_region_table_trans(erp_table);
	if (err)
		return ERR_PTR(err);

	erp = __mlxsw_sp_acl_erp_ctcam_mask_create(erp_table, key);
	if (IS_ERR(erp)) {
		err = PTR_ERR(erp);
		goto err_erp_create;
	}

	return erp;

err_erp_create:
	mlxsw_sp_acl_erp_region_master_mask_trans(erp_table);
	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);
	rhashtable_remove_fast(&erp_table->erp_ht, &erp->ht_node,
			       mlxsw_sp_acl_erp_ht_params);
	mlxsw_sp_acl_erp_master_mask_clear(erp_table, erp);
	erp_table->num_ctcam_erps--;
	kfree(erp);

	/* Once the last C-TCAM eRP was destroyed, the state we
	 * transition to depends on the number of A-TCAM eRPs currently
	 * in use
	 */
	if (erp_table->num_ctcam_erps > 0)
		return;
	mlxsw_sp_acl_erp_ctcam_table_ops_set(erp_table);
}

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->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 *
mlxsw_sp_acl_erp_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 mlxsw_sp_acl_erp *erp;

	/* eRPs are allocated from a shared resource, but currently all
	 * allocations are done under RTNL.
	 */
	ASSERT_RTNL();

	memcpy(key.mask, mask, MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN);
	key.ctcam = ctcam;
	erp = rhashtable_lookup_fast(&erp_table->erp_ht, &key,
				     mlxsw_sp_acl_erp_ht_params);
	if (erp) {
		refcount_inc(&erp->refcnt);
		return erp;
	}

	return erp_table->ops->erp_create(erp_table, &key);
}

void mlxsw_sp_acl_erp_put(struct mlxsw_sp_acl_atcam_region *aregion,
			  struct mlxsw_sp_acl_erp *erp)
{
	struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;

	ASSERT_RTNL();

	if (!refcount_dec_and_test(&erp->refcnt))
		return;

	erp_table->ops->erp_destroy(erp_table, erp);
}

static struct mlxsw_sp_acl_erp_table *
mlxsw_sp_acl_erp_table_create(struct mlxsw_sp_acl_atcam_region *aregion)
{
	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);

	err = rhashtable_init(&erp_table->erp_ht, &mlxsw_sp_acl_erp_ht_params);
	if (err)
		goto err_rhashtable_init;

	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;

	return erp_table;

err_rhashtable_init:
	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));
	rhashtable_destroy(&erp_table->erp_ht);
	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];
	char *master_mask;

	mlxsw_reg_percr_pack(percr_pl, aregion->region->id);
	master_mask = mlxsw_reg_percr_master_mask_data(percr_pl);
	memset(master_mask, 0, MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN);
	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, true, true, 0,
			      0, 0);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
}

int mlxsw_sp_acl_erp_region_init(struct mlxsw_sp_acl_atcam_region *aregion)
{
	struct mlxsw_sp_acl_erp_table *erp_table;
	int err;

	erp_table = mlxsw_sp_acl_erp_table_create(aregion);
	if (IS_ERR(erp_table))
		return PTR_ERR(erp_table);
	aregion->erp_table = erp_table;

	/* Initialize the region's master mask to all ones for C-TCAM
	 * only mode
	 */
	err = mlxsw_sp_acl_erp_master_mask_init(aregion);
	if (err)
		goto err_erp_master_mask_init;

	/* Initialize the region to use the eRP table and enable C-TCAM
	 * lookup
	 */
	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;

	/* 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:
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)
{
	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);
}