// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ #include #include #include #include #include #include #include #include #include #include #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 int mlxsw_sp_acl_erp_hints_obj_cmp(const void *obj1, const void *obj2) { const struct mlxsw_sp_acl_erp_key *key1 = obj1; const struct mlxsw_sp_acl_erp_key *key2 = obj2; /* For hints purposes, two objects are considered equal * in case the masks are the same. Does not matter what * the "ctcam" value is. */ return memcmp(key1->mask, key2->mask, sizeof(key1->mask)); } 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, .hints_obj_cmp = mlxsw_sp_acl_erp_hints_obj_cmp, .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); }