mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-03 17:51:23 +00:00

ACLs in Spectrum-2 and newer ASICs can reside in the algorithmic TCAM
(A-TCAM) or in the ordinary circuit TCAM (C-TCAM). The former can
contain more ACLs (i.e., tc filters), but the number of masks in each
region (i.e., tc chain) is limited.
In order to mitigate the effects of the above limitation, the device
allows filters to share a single mask if their masks only differ in up
to 8 consecutive bits. For example, dst_ip/25 can be represented using
dst_ip/24 with a delta of 1 bit. The C-TCAM does not have a limit on the
number of masks being used (and therefore does not support mask
aggregation), but can contain a limited number of filters.
The driver uses the "objagg" library to perform the mask aggregation by
passing it objects that consist of the filter's mask and whether the
filter is to be inserted into the A-TCAM or the C-TCAM since filters in
different TCAMs cannot share a mask.
The set of created objects is dependent on the insertion order of the
filters and is not necessarily optimal. Therefore, the driver will
periodically ask the library to compute a more optimal set ("hints") by
looking at all the existing objects.
When the library asks the driver whether two objects can be aggregated
the driver only compares the provided masks and ignores the A-TCAM /
C-TCAM indication. This is the right thing to do since the goal is to
move as many filters as possible to the A-TCAM. The driver also forbids
two identical masks from being aggregated since this can only happen if
one was intentionally put in the C-TCAM to avoid a conflict in the
A-TCAM.
The above can result in the following set of hints:
H1: {mask X, A-TCAM} -> H2: {mask Y, A-TCAM} // X is Y + delta
H3: {mask Y, C-TCAM} -> H4: {mask Z, A-TCAM} // Y is Z + delta
After getting the hints from the library the driver will start migrating
filters from one region to another while consulting the computed hints
and instructing the device to perform a lookup in both regions during
the transition.
Assuming a filter with mask X is being migrated into the A-TCAM in the
new region, the hints lookup will return H1. Since H2 is the parent of
H1, the library will try to find the object associated with it and
create it if necessary in which case another hints lookup (recursive)
will be performed. This hints lookup for {mask Y, A-TCAM} will either
return H2 or H3 since the driver passes the library an object comparison
function that ignores the A-TCAM / C-TCAM indication.
This can eventually lead to nested objects which are not supported by
the library [1].
Fix by removing the object comparison function from both the driver and
the library as the driver was the only user. That way the lookup will
only return exact matches.
I do not have a reliable reproducer that can reproduce the issue in a
timely manner, but before the fix the issue would reproduce in several
minutes and with the fix it does not reproduce in over an hour.
Note that the current usefulness of the hints is limited because they
include the C-TCAM indication and represent aggregation that cannot
actually happen. This will be addressed in net-next.
[1]
WARNING: CPU: 0 PID: 153 at lib/objagg.c:170 objagg_obj_parent_assign+0xb5/0xd0
Modules linked in:
CPU: 0 PID: 153 Comm: kworker/0:18 Not tainted 6.9.0-rc6-custom-g70fbc2c1c38b #42
Hardware name: Mellanox Technologies Ltd. MSN3700C/VMOD0008, BIOS 5.11 10/10/2018
Workqueue: mlxsw_core mlxsw_sp_acl_tcam_vregion_rehash_work
RIP: 0010:objagg_obj_parent_assign+0xb5/0xd0
[...]
Call Trace:
<TASK>
__objagg_obj_get+0x2bb/0x580
objagg_obj_get+0xe/0x80
mlxsw_sp_acl_erp_mask_get+0xb5/0xf0
mlxsw_sp_acl_atcam_entry_add+0xe8/0x3c0
mlxsw_sp_acl_tcam_entry_create+0x5e/0xa0
mlxsw_sp_acl_tcam_vchunk_migrate_one+0x16b/0x270
mlxsw_sp_acl_tcam_vregion_rehash_work+0xbe/0x510
process_one_work+0x151/0x370
Fixes: 9069a3817d
("lib: objagg: implement optimization hints assembly and use hints for object creation")
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: Amit Cohen <amcohen@nvidia.com>
Tested-by: Alexander Zubkov <green@qrator.net>
Signed-off-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
1589 lines
44 KiB
C
1589 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;
|
|
unsigned long index;
|
|
|
|
/* 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;
|
|
|
|
index = gen_pool_alloc(erp_core->erp_tables, num_rows * entry_size);
|
|
if (!index)
|
|
return -ENOBUFS;
|
|
|
|
*p_index = 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);
|
|
}
|