--- /dev/null
+/*
+ * drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c
+ * Copyright (c) 2017 Mellanox Technologies. All rights reserved.
+ * Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/rhashtable.h>
+#include <linux/netdevice.h>
+
+#include "reg.h"
+#include "core.h"
+#include "resources.h"
+#include "spectrum.h"
+#include "core_acl_flex_keys.h"
+#include "core_acl_flex_actions.h"
+#include "spectrum_acl_flex_keys.h"
+
+struct mlxsw_sp_acl {
+ struct mlxsw_afk *afk;
+ struct mlxsw_afa *afa;
+ const struct mlxsw_sp_acl_ops *ops;
+ struct rhashtable ruleset_ht;
+ unsigned long priv[0];
+ /* priv has to be always the last item */
+};
+
+struct mlxsw_afk *mlxsw_sp_acl_afk(struct mlxsw_sp_acl *acl)
+{
+ return acl->afk;
+}
+
+struct mlxsw_sp_acl_ruleset_ht_key {
+ struct net_device *dev; /* dev this ruleset is bound to */
+ bool ingress;
+ const struct mlxsw_sp_acl_profile_ops *ops;
+};
+
+struct mlxsw_sp_acl_ruleset {
+ struct rhash_head ht_node; /* Member of acl HT */
+ struct mlxsw_sp_acl_ruleset_ht_key ht_key;
+ struct rhashtable rule_ht;
+ unsigned int ref_count;
+ unsigned long priv[0];
+ /* priv has to be always the last item */
+};
+
+struct mlxsw_sp_acl_rule {
+ struct rhash_head ht_node; /* Member of rule HT */
+ unsigned long cookie; /* HT key */
+ struct mlxsw_sp_acl_ruleset *ruleset;
+ struct mlxsw_sp_acl_rule_info *rulei;
+ unsigned long priv[0];
+ /* priv has to be always the last item */
+};
+
+static const struct rhashtable_params mlxsw_sp_acl_ruleset_ht_params = {
+ .key_len = sizeof(struct mlxsw_sp_acl_ruleset_ht_key),
+ .key_offset = offsetof(struct mlxsw_sp_acl_ruleset, ht_key),
+ .head_offset = offsetof(struct mlxsw_sp_acl_ruleset, ht_node),
+ .automatic_shrinking = true,
+};
+
+static const struct rhashtable_params mlxsw_sp_acl_rule_ht_params = {
+ .key_len = sizeof(unsigned long),
+ .key_offset = offsetof(struct mlxsw_sp_acl_rule, cookie),
+ .head_offset = offsetof(struct mlxsw_sp_acl_rule, ht_node),
+ .automatic_shrinking = true,
+};
+
+static struct mlxsw_sp_acl_ruleset *
+mlxsw_sp_acl_ruleset_create(struct mlxsw_sp *mlxsw_sp,
+ const struct mlxsw_sp_acl_profile_ops *ops)
+{
+ struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
+ struct mlxsw_sp_acl_ruleset *ruleset;
+ size_t alloc_size;
+ int err;
+
+ alloc_size = sizeof(*ruleset) + ops->ruleset_priv_size;
+ ruleset = kzalloc(alloc_size, GFP_KERNEL);
+ if (!ruleset)
+ return ERR_PTR(-ENOMEM);
+ ruleset->ref_count = 1;
+ ruleset->ht_key.ops = ops;
+
+ err = rhashtable_init(&ruleset->rule_ht, &mlxsw_sp_acl_rule_ht_params);
+ if (err)
+ goto err_rhashtable_init;
+
+ err = ops->ruleset_add(mlxsw_sp, acl->priv, ruleset->priv);
+ if (err)
+ goto err_ops_ruleset_add;
+
+ return ruleset;
+
+err_ops_ruleset_add:
+ rhashtable_destroy(&ruleset->rule_ht);
+err_rhashtable_init:
+ kfree(ruleset);
+ return ERR_PTR(err);
+}
+
+static void mlxsw_sp_acl_ruleset_destroy(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset)
+{
+ const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
+
+ ops->ruleset_del(mlxsw_sp, ruleset->priv);
+ rhashtable_destroy(&ruleset->rule_ht);
+ kfree(ruleset);
+}
+
+static int mlxsw_sp_acl_ruleset_bind(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset,
+ struct net_device *dev, bool ingress)
+{
+ const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
+ struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
+ int err;
+
+ ruleset->ht_key.dev = dev;
+ ruleset->ht_key.ingress = ingress;
+ err = rhashtable_insert_fast(&acl->ruleset_ht, &ruleset->ht_node,
+ mlxsw_sp_acl_ruleset_ht_params);
+ if (err)
+ return err;
+ err = ops->ruleset_bind(mlxsw_sp, ruleset->priv, dev, ingress);
+ if (err)
+ goto err_ops_ruleset_bind;
+ return 0;
+
+err_ops_ruleset_bind:
+ rhashtable_remove_fast(&acl->ruleset_ht, &ruleset->ht_node,
+ mlxsw_sp_acl_ruleset_ht_params);
+ return err;
+}
+
+static void mlxsw_sp_acl_ruleset_unbind(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset)
+{
+ const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
+ struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
+
+ ops->ruleset_unbind(mlxsw_sp, ruleset->priv);
+ rhashtable_remove_fast(&acl->ruleset_ht, &ruleset->ht_node,
+ mlxsw_sp_acl_ruleset_ht_params);
+}
+
+static void mlxsw_sp_acl_ruleset_ref_inc(struct mlxsw_sp_acl_ruleset *ruleset)
+{
+ ruleset->ref_count++;
+}
+
+static void mlxsw_sp_acl_ruleset_ref_dec(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset)
+{
+ if (--ruleset->ref_count)
+ return;
+ mlxsw_sp_acl_ruleset_unbind(mlxsw_sp, ruleset);
+ mlxsw_sp_acl_ruleset_destroy(mlxsw_sp, ruleset);
+}
+
+struct mlxsw_sp_acl_ruleset *
+mlxsw_sp_acl_ruleset_get(struct mlxsw_sp *mlxsw_sp,
+ struct net_device *dev, bool ingress,
+ enum mlxsw_sp_acl_profile profile)
+{
+ const struct mlxsw_sp_acl_profile_ops *ops;
+ struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
+ struct mlxsw_sp_acl_ruleset_ht_key ht_key;
+ struct mlxsw_sp_acl_ruleset *ruleset;
+ int err;
+
+ ops = acl->ops->profile_ops(mlxsw_sp, profile);
+ if (!ops)
+ return ERR_PTR(-EINVAL);
+
+ memset(&ht_key, 0, sizeof(ht_key));
+ ht_key.dev = dev;
+ ht_key.ingress = ingress;
+ ht_key.ops = ops;
+ ruleset = rhashtable_lookup_fast(&acl->ruleset_ht, &ht_key,
+ mlxsw_sp_acl_ruleset_ht_params);
+ if (ruleset) {
+ mlxsw_sp_acl_ruleset_ref_inc(ruleset);
+ return ruleset;
+ }
+ ruleset = mlxsw_sp_acl_ruleset_create(mlxsw_sp, ops);
+ if (IS_ERR(ruleset))
+ return ruleset;
+ err = mlxsw_sp_acl_ruleset_bind(mlxsw_sp, ruleset, dev, ingress);
+ if (err)
+ goto err_ruleset_bind;
+ return ruleset;
+
+err_ruleset_bind:
+ mlxsw_sp_acl_ruleset_destroy(mlxsw_sp, ruleset);
+ return ERR_PTR(err);
+}
+
+void mlxsw_sp_acl_ruleset_put(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset)
+{
+ mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
+}
+
+struct mlxsw_sp_acl_rule_info *
+mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl)
+{
+ struct mlxsw_sp_acl_rule_info *rulei;
+ int err;
+
+ rulei = kzalloc(sizeof(*rulei), GFP_KERNEL);
+ if (!rulei)
+ return NULL;
+ rulei->act_block = mlxsw_afa_block_create(acl->afa);
+ if (IS_ERR(rulei->act_block)) {
+ err = PTR_ERR(rulei->act_block);
+ goto err_afa_block_create;
+ }
+ return rulei;
+
+err_afa_block_create:
+ kfree(rulei);
+ return ERR_PTR(err);
+}
+
+void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp_acl_rule_info *rulei)
+{
+ mlxsw_afa_block_destroy(rulei->act_block);
+ kfree(rulei);
+}
+
+int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei)
+{
+ return mlxsw_afa_block_commit(rulei->act_block);
+}
+
+void mlxsw_sp_acl_rulei_priority(struct mlxsw_sp_acl_rule_info *rulei,
+ unsigned int priority)
+{
+ rulei->priority = priority;
+}
+
+void mlxsw_sp_acl_rulei_keymask_u32(struct mlxsw_sp_acl_rule_info *rulei,
+ enum mlxsw_afk_element element,
+ u32 key_value, u32 mask_value)
+{
+ mlxsw_afk_values_add_u32(&rulei->values, element,
+ key_value, mask_value);
+}
+
+void mlxsw_sp_acl_rulei_keymask_buf(struct mlxsw_sp_acl_rule_info *rulei,
+ enum mlxsw_afk_element element,
+ const char *key_value,
+ const char *mask_value, unsigned int len)
+{
+ mlxsw_afk_values_add_buf(&rulei->values, element,
+ key_value, mask_value, len);
+}
+
+void mlxsw_sp_acl_rulei_act_continue(struct mlxsw_sp_acl_rule_info *rulei)
+{
+ mlxsw_afa_block_continue(rulei->act_block);
+}
+
+void mlxsw_sp_acl_rulei_act_jump(struct mlxsw_sp_acl_rule_info *rulei,
+ u16 group_id)
+{
+ mlxsw_afa_block_jump(rulei->act_block, group_id);
+}
+
+int mlxsw_sp_acl_rulei_act_drop(struct mlxsw_sp_acl_rule_info *rulei)
+{
+ return mlxsw_afa_block_append_drop(rulei->act_block);
+}
+
+int mlxsw_sp_acl_rulei_act_fwd(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_rule_info *rulei,
+ struct net_device *out_dev)
+{
+ struct mlxsw_sp_port *mlxsw_sp_port;
+ u8 local_port;
+ bool in_port;
+
+ if (out_dev) {
+ if (!mlxsw_sp_port_dev_check(out_dev))
+ return -EINVAL;
+ mlxsw_sp_port = netdev_priv(out_dev);
+ if (mlxsw_sp_port->mlxsw_sp != mlxsw_sp)
+ return -EINVAL;
+ local_port = mlxsw_sp_port->local_port;
+ in_port = false;
+ } else {
+ /* If out_dev is NULL, the called wants to
+ * set forward to ingress port.
+ */
+ local_port = 0;
+ in_port = true;
+ }
+ return mlxsw_afa_block_append_fwd(rulei->act_block,
+ local_port, in_port);
+}
+
+struct mlxsw_sp_acl_rule *
+mlxsw_sp_acl_rule_create(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset,
+ unsigned long cookie)
+{
+ const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
+ struct mlxsw_sp_acl_rule *rule;
+ int err;
+
+ mlxsw_sp_acl_ruleset_ref_inc(ruleset);
+ rule = kzalloc(sizeof(*rule) + ops->rule_priv_size, GFP_KERNEL);
+ if (!rule) {
+ err = -ENOMEM;
+ goto err_alloc;
+ }
+ rule->cookie = cookie;
+ rule->ruleset = ruleset;
+
+ rule->rulei = mlxsw_sp_acl_rulei_create(mlxsw_sp->acl);
+ if (IS_ERR(rule->rulei)) {
+ err = PTR_ERR(rule->rulei);
+ goto err_rulei_create;
+ }
+ return rule;
+
+err_rulei_create:
+ kfree(rule);
+err_alloc:
+ mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
+ return ERR_PTR(err);
+}
+
+void mlxsw_sp_acl_rule_destroy(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_rule *rule)
+{
+ struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
+
+ mlxsw_sp_acl_rulei_destroy(rule->rulei);
+ kfree(rule);
+ mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
+}
+
+int mlxsw_sp_acl_rule_add(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_rule *rule)
+{
+ struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
+ const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
+ int err;
+
+ err = ops->rule_add(mlxsw_sp, ruleset->priv, rule->priv, rule->rulei);
+ if (err)
+ return err;
+
+ err = rhashtable_insert_fast(&ruleset->rule_ht, &rule->ht_node,
+ mlxsw_sp_acl_rule_ht_params);
+ if (err)
+ goto err_rhashtable_insert;
+
+ return 0;
+
+err_rhashtable_insert:
+ ops->rule_del(mlxsw_sp, rule->priv);
+ return err;
+}
+
+void mlxsw_sp_acl_rule_del(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_rule *rule)
+{
+ struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
+ const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
+
+ rhashtable_remove_fast(&ruleset->rule_ht, &rule->ht_node,
+ mlxsw_sp_acl_rule_ht_params);
+ ops->rule_del(mlxsw_sp, rule->priv);
+}
+
+struct mlxsw_sp_acl_rule *
+mlxsw_sp_acl_rule_lookup(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_ruleset *ruleset,
+ unsigned long cookie)
+{
+ return rhashtable_lookup_fast(&ruleset->rule_ht, &cookie,
+ mlxsw_sp_acl_rule_ht_params);
+}
+
+struct mlxsw_sp_acl_rule_info *
+mlxsw_sp_acl_rule_rulei(struct mlxsw_sp_acl_rule *rule)
+{
+ return rule->rulei;
+}
+
+#define MLXSW_SP_KDVL_ACT_EXT_SIZE 1
+
+static int mlxsw_sp_act_kvdl_set_add(void *priv, u32 *p_kvdl_index,
+ char *enc_actions, bool is_first)
+{
+ struct mlxsw_sp *mlxsw_sp = priv;
+ char pefa_pl[MLXSW_REG_PEFA_LEN];
+ u32 kvdl_index;
+ int ret;
+ int err;
+
+ /* The first action set of a TCAM entry is stored directly in TCAM,
+ * not KVD linear area.
+ */
+ if (is_first)
+ return 0;
+
+ ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, MLXSW_SP_KDVL_ACT_EXT_SIZE);
+ if (ret < 0)
+ return ret;
+ kvdl_index = ret;
+ mlxsw_reg_pefa_pack(pefa_pl, kvdl_index, enc_actions);
+ err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pefa), pefa_pl);
+ if (err)
+ goto err_pefa_write;
+ *p_kvdl_index = kvdl_index;
+ return 0;
+
+err_pefa_write:
+ mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
+ return err;
+}
+
+static void mlxsw_sp_act_kvdl_set_del(void *priv, u32 kvdl_index,
+ bool is_first)
+{
+ struct mlxsw_sp *mlxsw_sp = priv;
+
+ if (is_first)
+ return;
+ mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
+}
+
+static int mlxsw_sp_act_kvdl_fwd_entry_add(void *priv, u32 *p_kvdl_index,
+ u8 local_port)
+{
+ struct mlxsw_sp *mlxsw_sp = priv;
+ char ppbs_pl[MLXSW_REG_PPBS_LEN];
+ u32 kvdl_index;
+ int ret;
+ int err;
+
+ ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, 1);
+ if (ret < 0)
+ return ret;
+ kvdl_index = ret;
+ mlxsw_reg_ppbs_pack(ppbs_pl, kvdl_index, local_port);
+ err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbs), ppbs_pl);
+ if (err)
+ goto err_ppbs_write;
+ *p_kvdl_index = kvdl_index;
+ return 0;
+
+err_ppbs_write:
+ mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
+ return err;
+}
+
+static void mlxsw_sp_act_kvdl_fwd_entry_del(void *priv, u32 kvdl_index)
+{
+ struct mlxsw_sp *mlxsw_sp = priv;
+
+ mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
+}
+
+static const struct mlxsw_afa_ops mlxsw_sp_act_afa_ops = {
+ .kvdl_set_add = mlxsw_sp_act_kvdl_set_add,
+ .kvdl_set_del = mlxsw_sp_act_kvdl_set_del,
+ .kvdl_fwd_entry_add = mlxsw_sp_act_kvdl_fwd_entry_add,
+ .kvdl_fwd_entry_del = mlxsw_sp_act_kvdl_fwd_entry_del,
+};
+
+int mlxsw_sp_acl_init(struct mlxsw_sp *mlxsw_sp)
+{
+ const struct mlxsw_sp_acl_ops *acl_ops = &mlxsw_sp_acl_tcam_ops;
+ struct mlxsw_sp_acl *acl;
+ int err;
+
+ acl = kzalloc(sizeof(*acl) + acl_ops->priv_size, GFP_KERNEL);
+ if (!acl)
+ return -ENOMEM;
+ mlxsw_sp->acl = acl;
+
+ acl->afk = mlxsw_afk_create(MLXSW_CORE_RES_GET(mlxsw_sp->core,
+ ACL_FLEX_KEYS),
+ mlxsw_sp_afk_blocks,
+ MLXSW_SP_AFK_BLOCKS_COUNT);
+ if (!acl->afk) {
+ err = -ENOMEM;
+ goto err_afk_create;
+ }
+
+ acl->afa = mlxsw_afa_create(MLXSW_CORE_RES_GET(mlxsw_sp->core,
+ ACL_ACTIONS_PER_SET),
+ &mlxsw_sp_act_afa_ops, mlxsw_sp);
+ if (IS_ERR(acl->afa)) {
+ err = PTR_ERR(acl->afa);
+ goto err_afa_create;
+ }
+
+ err = rhashtable_init(&acl->ruleset_ht,
+ &mlxsw_sp_acl_ruleset_ht_params);
+ if (err)
+ goto err_rhashtable_init;
+
+ err = acl_ops->init(mlxsw_sp, acl->priv);
+ if (err)
+ goto err_acl_ops_init;
+
+ acl->ops = acl_ops;
+ return 0;
+
+err_acl_ops_init:
+ rhashtable_destroy(&acl->ruleset_ht);
+err_rhashtable_init:
+ mlxsw_afa_destroy(acl->afa);
+err_afa_create:
+ mlxsw_afk_destroy(acl->afk);
+err_afk_create:
+ kfree(acl);
+ return err;
+}
+
+void mlxsw_sp_acl_fini(struct mlxsw_sp *mlxsw_sp)
+{
+ struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
+ const struct mlxsw_sp_acl_ops *acl_ops = acl->ops;
+
+ acl_ops->fini(mlxsw_sp, acl->priv);
+ rhashtable_destroy(&acl->ruleset_ht);
+ mlxsw_afa_destroy(acl->afa);
+ mlxsw_afk_destroy(acl->afk);
+ kfree(acl);
+}
--- /dev/null
+/*
+ * drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.c
+ * Copyright (c) 2017 Mellanox Technologies. All rights reserved.
+ * Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/list.h>
+#include <linux/rhashtable.h>
+#include <linux/netdevice.h>
+#include <linux/parman.h>
+
+#include "reg.h"
+#include "core.h"
+#include "resources.h"
+#include "spectrum.h"
+#include "core_acl_flex_keys.h"
+
+struct mlxsw_sp_acl_tcam {
+ unsigned long *used_regions; /* bit array */
+ unsigned int max_regions;
+ unsigned long *used_groups; /* bit array */
+ unsigned int max_groups;
+ unsigned int max_group_size;
+};
+
+static int mlxsw_sp_acl_tcam_init(struct mlxsw_sp *mlxsw_sp, void *priv)
+{
+ struct mlxsw_sp_acl_tcam *tcam = priv;
+ u64 max_tcam_regions;
+ u64 max_regions;
+ u64 max_groups;
+ size_t alloc_size;
+ int err;
+
+ max_tcam_regions = MLXSW_CORE_RES_GET(mlxsw_sp->core,
+ ACL_MAX_TCAM_REGIONS);
+ max_regions = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_MAX_REGIONS);
+
+ /* Use 1:1 mapping between ACL region and TCAM region */
+ if (max_tcam_regions < max_regions)
+ max_regions = max_tcam_regions;
+
+ alloc_size = sizeof(tcam->used_regions[0]) * BITS_TO_LONGS(max_regions);
+ tcam->used_regions = kzalloc(alloc_size, GFP_KERNEL);
+ if (!tcam->used_regions)
+ return -ENOMEM;
+ tcam->max_regions = max_regions;
+
+ max_groups = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_MAX_GROUPS);
+ alloc_size = sizeof(tcam->used_groups[0]) * BITS_TO_LONGS(max_groups);
+ tcam->used_groups = kzalloc(alloc_size, GFP_KERNEL);
+ if (!tcam->used_groups) {
+ err = -ENOMEM;
+ goto err_alloc_used_groups;
+ }
+ tcam->max_groups = max_groups;
+ tcam->max_group_size = MLXSW_CORE_RES_GET(mlxsw_sp->core,
+ ACL_MAX_GROUP_SIZE);
+ return 0;
+
+err_alloc_used_groups:
+ kfree(tcam->used_regions);
+ return err;
+}
+
+static void mlxsw_sp_acl_tcam_fini(struct mlxsw_sp *mlxsw_sp, void *priv)
+{
+ struct mlxsw_sp_acl_tcam *tcam = priv;
+
+ kfree(tcam->used_groups);
+ kfree(tcam->used_regions);
+}
+
+static int mlxsw_sp_acl_tcam_region_id_get(struct mlxsw_sp_acl_tcam *tcam,
+ u16 *p_id)
+{
+ u16 id;
+
+ id = find_first_zero_bit(tcam->used_regions, tcam->max_regions);
+ if (id < tcam->max_regions) {
+ __set_bit(id, tcam->used_regions);
+ *p_id = id;
+ return 0;
+ }
+ return -ENOBUFS;
+}
+
+static void mlxsw_sp_acl_tcam_region_id_put(struct mlxsw_sp_acl_tcam *tcam,
+ u16 id)
+{
+ __clear_bit(id, tcam->used_regions);
+}
+
+static int mlxsw_sp_acl_tcam_group_id_get(struct mlxsw_sp_acl_tcam *tcam,
+ u16 *p_id)
+{
+ u16 id;
+
+ id = find_first_zero_bit(tcam->used_groups, tcam->max_groups);
+ if (id < tcam->max_groups) {
+ __set_bit(id, tcam->used_groups);
+ *p_id = id;
+ return 0;
+ }
+ return -ENOBUFS;
+}
+
+static void mlxsw_sp_acl_tcam_group_id_put(struct mlxsw_sp_acl_tcam *tcam,
+ u16 id)
+{
+ __clear_bit(id, tcam->used_groups);
+}
+
+struct mlxsw_sp_acl_tcam_pattern {
+ const enum mlxsw_afk_element *elements;
+ unsigned int elements_count;
+};
+
+struct mlxsw_sp_acl_tcam_group {
+ struct mlxsw_sp_acl_tcam *tcam;
+ u16 id;
+ struct list_head region_list;
+ unsigned int region_count;
+ struct rhashtable chunk_ht;
+ struct {
+ u16 local_port;
+ bool ingress;
+ } bound;
+ struct mlxsw_sp_acl_tcam_group_ops *ops;
+ const struct mlxsw_sp_acl_tcam_pattern *patterns;
+ unsigned int patterns_count;
+};
+
+struct mlxsw_sp_acl_tcam_region {
+ struct list_head list; /* Member of a TCAM group */
+ struct list_head chunk_list; /* List of chunks under this region */
+ struct parman *parman;
+ struct mlxsw_sp *mlxsw_sp;
+ struct mlxsw_sp_acl_tcam_group *group;
+ u16 id; /* ACL ID and region ID - they are same */
+ char tcam_region_info[MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN];
+ struct mlxsw_afk_key_info *key_info;
+ struct {
+ struct parman_prio parman_prio;
+ struct parman_item parman_item;
+ struct mlxsw_sp_acl_rule_info *rulei;
+ } catchall;
+};
+
+struct mlxsw_sp_acl_tcam_chunk {
+ struct list_head list; /* Member of a TCAM region */
+ struct rhash_head ht_node; /* Member of a chunk HT */
+ unsigned int priority; /* Priority within the region and group */
+ struct parman_prio parman_prio;
+ struct mlxsw_sp_acl_tcam_group *group;
+ struct mlxsw_sp_acl_tcam_region *region;
+ unsigned int ref_count;
+};
+
+struct mlxsw_sp_acl_tcam_entry {
+ struct parman_item parman_item;
+ struct mlxsw_sp_acl_tcam_chunk *chunk;
+};
+
+static const struct rhashtable_params mlxsw_sp_acl_tcam_chunk_ht_params = {
+ .key_len = sizeof(unsigned int),
+ .key_offset = offsetof(struct mlxsw_sp_acl_tcam_chunk, priority),
+ .head_offset = offsetof(struct mlxsw_sp_acl_tcam_chunk, ht_node),
+ .automatic_shrinking = true,
+};
+
+static int mlxsw_sp_acl_tcam_group_update(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group)
+{
+ struct mlxsw_sp_acl_tcam_region *region;
+ char pagt_pl[MLXSW_REG_PAGT_LEN];
+ int acl_index = 0;
+
+ mlxsw_reg_pagt_pack(pagt_pl, group->id);
+ list_for_each_entry(region, &group->region_list, list)
+ mlxsw_reg_pagt_acl_id_pack(pagt_pl, acl_index++, region->id);
+ mlxsw_reg_pagt_size_set(pagt_pl, acl_index);
+ return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pagt), pagt_pl);
+}
+
+static int
+mlxsw_sp_acl_tcam_group_add(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam *tcam,
+ struct mlxsw_sp_acl_tcam_group *group,
+ const struct mlxsw_sp_acl_tcam_pattern *patterns,
+ unsigned int patterns_count)
+{
+ int err;
+
+ group->tcam = tcam;
+ group->patterns = patterns;
+ group->patterns_count = patterns_count;
+ INIT_LIST_HEAD(&group->region_list);
+ err = mlxsw_sp_acl_tcam_group_id_get(tcam, &group->id);
+ if (err)
+ return err;
+
+ err = mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
+ if (err)
+ goto err_group_update;
+
+ err = rhashtable_init(&group->chunk_ht,
+ &mlxsw_sp_acl_tcam_chunk_ht_params);
+ if (err)
+ goto err_rhashtable_init;
+
+ return 0;
+
+err_rhashtable_init:
+err_group_update:
+ mlxsw_sp_acl_tcam_group_id_put(tcam, group->id);
+ return err;
+}
+
+static void mlxsw_sp_acl_tcam_group_del(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group)
+{
+ struct mlxsw_sp_acl_tcam *tcam = group->tcam;
+
+ rhashtable_destroy(&group->chunk_ht);
+ mlxsw_sp_acl_tcam_group_id_put(tcam, group->id);
+ WARN_ON(!list_empty(&group->region_list));
+}
+
+static int
+mlxsw_sp_acl_tcam_group_bind(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group,
+ struct net_device *dev, bool ingress)
+{
+ struct mlxsw_sp_port *mlxsw_sp_port;
+ char ppbt_pl[MLXSW_REG_PPBT_LEN];
+
+ if (!mlxsw_sp_port_dev_check(dev))
+ return -EINVAL;
+
+ mlxsw_sp_port = netdev_priv(dev);
+ group->bound.local_port = mlxsw_sp_port->local_port;
+ group->bound.ingress = ingress;
+ mlxsw_reg_ppbt_pack(ppbt_pl,
+ group->bound.ingress ? MLXSW_REG_PXBT_E_IACL :
+ MLXSW_REG_PXBT_E_EACL,
+ MLXSW_REG_PXBT_OP_BIND, group->bound.local_port,
+ group->id);
+ return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbt), ppbt_pl);
+}
+
+static void
+mlxsw_sp_acl_tcam_group_unbind(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group)
+{
+ char ppbt_pl[MLXSW_REG_PPBT_LEN];
+
+ mlxsw_reg_ppbt_pack(ppbt_pl,
+ group->bound.ingress ? MLXSW_REG_PXBT_E_IACL :
+ MLXSW_REG_PXBT_E_EACL,
+ MLXSW_REG_PXBT_OP_UNBIND, group->bound.local_port,
+ group->id);
+ mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbt), ppbt_pl);
+}
+
+static unsigned int
+mlxsw_sp_acl_tcam_region_prio(struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct mlxsw_sp_acl_tcam_chunk *chunk;
+
+ if (list_empty(®ion->chunk_list))
+ return 0;
+ /* As a priority of a region, return priority of the first chunk */
+ chunk = list_first_entry(®ion->chunk_list, typeof(*chunk), list);
+ return chunk->priority;
+}
+
+static unsigned int
+mlxsw_sp_acl_tcam_region_max_prio(struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct mlxsw_sp_acl_tcam_chunk *chunk;
+
+ if (list_empty(®ion->chunk_list))
+ return 0;
+ chunk = list_last_entry(®ion->chunk_list, typeof(*chunk), list);
+ return chunk->priority;
+}
+
+static void
+mlxsw_sp_acl_tcam_group_list_add(struct mlxsw_sp_acl_tcam_group *group,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct mlxsw_sp_acl_tcam_region *region2;
+ struct list_head *pos;
+
+ /* Position the region inside the list according to priority */
+ list_for_each(pos, &group->region_list) {
+ region2 = list_entry(pos, typeof(*region2), list);
+ if (mlxsw_sp_acl_tcam_region_prio(region2) >
+ mlxsw_sp_acl_tcam_region_prio(region))
+ break;
+ }
+ list_add_tail(®ion->list, pos);
+ group->region_count++;
+}
+
+static void
+mlxsw_sp_acl_tcam_group_list_del(struct mlxsw_sp_acl_tcam_group *group,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ group->region_count--;
+ list_del(®ion->list);
+}
+
+static int
+mlxsw_sp_acl_tcam_group_region_attach(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ int err;
+
+ if (group->region_count == group->tcam->max_group_size)
+ return -ENOBUFS;
+
+ mlxsw_sp_acl_tcam_group_list_add(group, region);
+
+ err = mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
+ if (err)
+ goto err_group_update;
+ region->group = group;
+
+ return 0;
+
+err_group_update:
+ mlxsw_sp_acl_tcam_group_list_del(group, region);
+ mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
+ return err;
+}
+
+static void
+mlxsw_sp_acl_tcam_group_region_detach(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct mlxsw_sp_acl_tcam_group *group = region->group;
+
+ mlxsw_sp_acl_tcam_group_list_del(group, region);
+ mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
+}
+
+static struct mlxsw_sp_acl_tcam_region *
+mlxsw_sp_acl_tcam_group_region_find(struct mlxsw_sp_acl_tcam_group *group,
+ unsigned int priority,
+ struct mlxsw_afk_element_usage *elusage,
+ bool *p_need_split)
+{
+ struct mlxsw_sp_acl_tcam_region *region, *region2;
+ struct list_head *pos;
+ bool issubset;
+
+ list_for_each(pos, &group->region_list) {
+ region = list_entry(pos, typeof(*region), list);
+
+ /* First, check if the requested priority does not rather belong
+ * under some of the next regions.
+ */
+ if (pos->next != &group->region_list) { /* not last */
+ region2 = list_entry(pos->next, typeof(*region2), list);
+ if (priority >= mlxsw_sp_acl_tcam_region_prio(region2))
+ continue;
+ }
+
+ issubset = mlxsw_afk_key_info_subset(region->key_info, elusage);
+
+ /* If requested element usage would not fit and the priority
+ * is lower than the currently inspected region we cannot
+ * use this region, so return NULL to indicate new region has
+ * to be created.
+ */
+ if (!issubset &&
+ priority < mlxsw_sp_acl_tcam_region_prio(region))
+ return NULL;
+
+ /* If requested element usage would not fit and the priority
+ * is higher than the currently inspected region we cannot
+ * use this region. There is still some hope that the next
+ * region would be the fit. So let it be processed and
+ * eventually break at the check right above this.
+ */
+ if (!issubset &&
+ priority > mlxsw_sp_acl_tcam_region_max_prio(region))
+ continue;
+
+ /* Indicate if the region needs to be split in order to add
+ * the requested priority. Split is needed when requested
+ * element usage won't fit into the found region.
+ */
+ *p_need_split = !issubset;
+ return region;
+ }
+ return NULL; /* New region has to be created. */
+}
+
+static void
+mlxsw_sp_acl_tcam_group_use_patterns(struct mlxsw_sp_acl_tcam_group *group,
+ struct mlxsw_afk_element_usage *elusage,
+ struct mlxsw_afk_element_usage *out)
+{
+ const struct mlxsw_sp_acl_tcam_pattern *pattern;
+ int i;
+
+ for (i = 0; i < group->patterns_count; i++) {
+ pattern = &group->patterns[i];
+ mlxsw_afk_element_usage_fill(out, pattern->elements,
+ pattern->elements_count);
+ if (mlxsw_afk_element_usage_subset(elusage, out))
+ return;
+ }
+ memcpy(out, elusage, sizeof(*out));
+}
+
+#define MLXSW_SP_ACL_TCAM_REGION_BASE_COUNT 16
+#define MLXSW_SP_ACL_TCAM_REGION_RESIZE_STEP 16
+
+static int
+mlxsw_sp_acl_tcam_region_alloc(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct mlxsw_afk_key_info *key_info = region->key_info;
+ char ptar_pl[MLXSW_REG_PTAR_LEN];
+ unsigned int encodings_count;
+ int i;
+ int err;
+
+ mlxsw_reg_ptar_pack(ptar_pl, MLXSW_REG_PTAR_OP_ALLOC,
+ MLXSW_SP_ACL_TCAM_REGION_BASE_COUNT,
+ region->id, region->tcam_region_info);
+ encodings_count = mlxsw_afk_key_info_blocks_count_get(key_info);
+ for (i = 0; i < encodings_count; i++) {
+ u16 encoding;
+
+ encoding = mlxsw_afk_key_info_block_encoding_get(key_info, i);
+ mlxsw_reg_ptar_key_id_pack(ptar_pl, i, encoding);
+ }
+ err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptar), ptar_pl);
+ if (err)
+ return err;
+ mlxsw_reg_ptar_unpack(ptar_pl, region->tcam_region_info);
+ return 0;
+}
+
+static void
+mlxsw_sp_acl_tcam_region_free(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ char ptar_pl[MLXSW_REG_PTAR_LEN];
+
+ mlxsw_reg_ptar_pack(ptar_pl, MLXSW_REG_PTAR_OP_FREE, 0, region->id,
+ region->tcam_region_info);
+ mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptar), ptar_pl);
+}
+
+static int
+mlxsw_sp_acl_tcam_region_resize(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region,
+ u16 new_size)
+{
+ char ptar_pl[MLXSW_REG_PTAR_LEN];
+
+ mlxsw_reg_ptar_pack(ptar_pl, MLXSW_REG_PTAR_OP_RESIZE,
+ new_size, region->id, region->tcam_region_info);
+ return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptar), ptar_pl);
+}
+
+static int
+mlxsw_sp_acl_tcam_region_enable(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ char pacl_pl[MLXSW_REG_PACL_LEN];
+
+ mlxsw_reg_pacl_pack(pacl_pl, region->id, true,
+ region->tcam_region_info);
+ return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pacl), pacl_pl);
+}
+
+static void
+mlxsw_sp_acl_tcam_region_disable(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ char pacl_pl[MLXSW_REG_PACL_LEN];
+
+ mlxsw_reg_pacl_pack(pacl_pl, region->id, false,
+ region->tcam_region_info);
+ mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pacl), pacl_pl);
+}
+
+static int
+mlxsw_sp_acl_tcam_region_entry_insert(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region,
+ unsigned int offset,
+ struct mlxsw_sp_acl_rule_info *rulei)
+{
+ char ptce2_pl[MLXSW_REG_PTCE2_LEN];
+ char *act_set;
+ char *mask;
+ char *key;
+
+ mlxsw_reg_ptce2_pack(ptce2_pl, true, MLXSW_REG_PTCE2_OP_WRITE_WRITE,
+ region->tcam_region_info, offset);
+ key = mlxsw_reg_ptce2_flex_key_blocks_data(ptce2_pl);
+ mask = mlxsw_reg_ptce2_mask_data(ptce2_pl);
+ mlxsw_afk_encode(region->key_info, &rulei->values, key, mask);
+
+ /* Only the first action set belongs here, the rest is in KVD */
+ act_set = mlxsw_afa_block_first_set(rulei->act_block);
+ mlxsw_reg_ptce2_flex_action_set_memcpy_to(ptce2_pl, act_set);
+
+ return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptce2), ptce2_pl);
+}
+
+static void
+mlxsw_sp_acl_tcam_region_entry_remove(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region,
+ unsigned int offset)
+{
+ char ptce2_pl[MLXSW_REG_PTCE2_LEN];
+
+ mlxsw_reg_ptce2_pack(ptce2_pl, false, MLXSW_REG_PTCE2_OP_WRITE_WRITE,
+ region->tcam_region_info, offset);
+ mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptce2), ptce2_pl);
+}
+
+#define MLXSW_SP_ACL_TCAM_CATCHALL_PRIO (-1UL)
+
+static int
+mlxsw_sp_acl_tcam_region_catchall_add(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct parman_prio *parman_prio = ®ion->catchall.parman_prio;
+ struct parman_item *parman_item = ®ion->catchall.parman_item;
+ struct mlxsw_sp_acl_rule_info *rulei;
+ int err;
+
+ parman_prio_init(region->parman, parman_prio,
+ MLXSW_SP_ACL_TCAM_CATCHALL_PRIO);
+ err = parman_item_add(region->parman, parman_prio, parman_item);
+ if (err)
+ goto err_parman_item_add;
+
+ rulei = mlxsw_sp_acl_rulei_create(mlxsw_sp->acl);
+ if (IS_ERR(rulei)) {
+ err = PTR_ERR(rulei);
+ goto err_rulei_create;
+ }
+
+ mlxsw_sp_acl_rulei_act_continue(rulei);
+ err = mlxsw_sp_acl_rulei_commit(rulei);
+ if (err)
+ goto err_rulei_commit;
+
+ err = mlxsw_sp_acl_tcam_region_entry_insert(mlxsw_sp, region,
+ parman_item->index, rulei);
+ region->catchall.rulei = rulei;
+ if (err)
+ goto err_rule_insert;
+
+ return 0;
+
+err_rule_insert:
+err_rulei_commit:
+ mlxsw_sp_acl_rulei_destroy(rulei);
+err_rulei_create:
+ parman_item_remove(region->parman, parman_prio, parman_item);
+err_parman_item_add:
+ parman_prio_fini(parman_prio);
+ return err;
+}
+
+static void
+mlxsw_sp_acl_tcam_region_catchall_del(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ struct parman_prio *parman_prio = ®ion->catchall.parman_prio;
+ struct parman_item *parman_item = ®ion->catchall.parman_item;
+ struct mlxsw_sp_acl_rule_info *rulei = region->catchall.rulei;
+
+ mlxsw_sp_acl_tcam_region_entry_remove(mlxsw_sp, region,
+ parman_item->index);
+ mlxsw_sp_acl_rulei_destroy(rulei);
+ parman_item_remove(region->parman, parman_prio, parman_item);
+ parman_prio_fini(parman_prio);
+}
+
+static void
+mlxsw_sp_acl_tcam_region_move(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region,
+ u16 src_offset, u16 dst_offset, u16 size)
+{
+ char prcr_pl[MLXSW_REG_PRCR_LEN];
+
+ mlxsw_reg_prcr_pack(prcr_pl, MLXSW_REG_PRCR_OP_MOVE,
+ region->tcam_region_info, src_offset,
+ region->tcam_region_info, dst_offset, size);
+ mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(prcr), prcr_pl);
+}
+
+static int mlxsw_sp_acl_tcam_region_parman_resize(void *priv,
+ unsigned long new_count)
+{
+ struct mlxsw_sp_acl_tcam_region *region = priv;
+ struct mlxsw_sp *mlxsw_sp = region->mlxsw_sp;
+ u64 max_tcam_rules;
+
+ max_tcam_rules = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_MAX_TCAM_RULES);
+ if (new_count > max_tcam_rules)
+ return -EINVAL;
+ return mlxsw_sp_acl_tcam_region_resize(mlxsw_sp, region, new_count);
+}
+
+static void mlxsw_sp_acl_tcam_region_parman_move(void *priv,
+ unsigned long from_index,
+ unsigned long to_index,
+ unsigned long count)
+{
+ struct mlxsw_sp_acl_tcam_region *region = priv;
+ struct mlxsw_sp *mlxsw_sp = region->mlxsw_sp;
+
+ mlxsw_sp_acl_tcam_region_move(mlxsw_sp, region,
+ from_index, to_index, count);
+}
+
+static const struct parman_ops mlxsw_sp_acl_tcam_region_parman_ops = {
+ .base_count = MLXSW_SP_ACL_TCAM_REGION_BASE_COUNT,
+ .resize_step = MLXSW_SP_ACL_TCAM_REGION_RESIZE_STEP,
+ .resize = mlxsw_sp_acl_tcam_region_parman_resize,
+ .move = mlxsw_sp_acl_tcam_region_parman_move,
+ .algo = PARMAN_ALGO_TYPE_LSORT,
+};
+
+static struct mlxsw_sp_acl_tcam_region *
+mlxsw_sp_acl_tcam_region_create(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam *tcam,
+ struct mlxsw_afk_element_usage *elusage)
+{
+ struct mlxsw_afk *afk = mlxsw_sp_acl_afk(mlxsw_sp->acl);
+ struct mlxsw_sp_acl_tcam_region *region;
+ int err;
+
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
+ if (!region)
+ return ERR_PTR(-ENOMEM);
+ INIT_LIST_HEAD(®ion->chunk_list);
+ region->mlxsw_sp = mlxsw_sp;
+
+ region->parman = parman_create(&mlxsw_sp_acl_tcam_region_parman_ops,
+ region);
+ if (!region->parman) {
+ err = -ENOMEM;
+ goto err_parman_create;
+ }
+
+ region->key_info = mlxsw_afk_key_info_get(afk, elusage);
+ if (IS_ERR(region->key_info)) {
+ err = PTR_ERR(region->key_info);
+ goto err_key_info_get;
+ }
+
+ err = mlxsw_sp_acl_tcam_region_id_get(tcam, ®ion->id);
+ if (err)
+ goto err_region_id_get;
+
+ err = mlxsw_sp_acl_tcam_region_alloc(mlxsw_sp, region);
+ if (err)
+ goto err_tcam_region_alloc;
+
+ err = mlxsw_sp_acl_tcam_region_enable(mlxsw_sp, region);
+ if (err)
+ goto err_tcam_region_enable;
+
+ err = mlxsw_sp_acl_tcam_region_catchall_add(mlxsw_sp, region);
+ if (err)
+ goto err_tcam_region_catchall_add;
+
+ return region;
+
+err_tcam_region_catchall_add:
+ mlxsw_sp_acl_tcam_region_disable(mlxsw_sp, region);
+err_tcam_region_enable:
+ mlxsw_sp_acl_tcam_region_free(mlxsw_sp, region);
+err_tcam_region_alloc:
+ mlxsw_sp_acl_tcam_region_id_put(tcam, region->id);
+err_region_id_get:
+ mlxsw_afk_key_info_put(region->key_info);
+err_key_info_get:
+ parman_destroy(region->parman);
+err_parman_create:
+ kfree(region);
+ return ERR_PTR(err);
+}
+
+static void
+mlxsw_sp_acl_tcam_region_destroy(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_region *region)
+{
+ mlxsw_sp_acl_tcam_region_catchall_del(mlxsw_sp, region);
+ mlxsw_sp_acl_tcam_region_disable(mlxsw_sp, region);
+ mlxsw_sp_acl_tcam_region_free(mlxsw_sp, region);
+ mlxsw_sp_acl_tcam_region_id_put(region->group->tcam, region->id);
+ mlxsw_afk_key_info_put(region->key_info);
+ parman_destroy(region->parman);
+ kfree(region);
+}
+
+static int
+mlxsw_sp_acl_tcam_chunk_assoc(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group,
+ unsigned int priority,
+ struct mlxsw_afk_element_usage *elusage,
+ struct mlxsw_sp_acl_tcam_chunk *chunk)
+{
+ struct mlxsw_sp_acl_tcam_region *region;
+ bool region_created = false;
+ bool need_split;
+ int err;
+
+ region = mlxsw_sp_acl_tcam_group_region_find(group, priority, elusage,
+ &need_split);
+ if (region && need_split) {
+ /* According to priority, the chunk should belong to an
+ * existing region. However, this chunk needs elements
+ * that region does not contain. We need to split the existing
+ * region into two and create a new region for this chunk
+ * in between. This is not supported now.
+ */
+ return -EOPNOTSUPP;
+ }
+ if (!region) {
+ struct mlxsw_afk_element_usage region_elusage;
+
+ mlxsw_sp_acl_tcam_group_use_patterns(group, elusage,
+ ®ion_elusage);
+ region = mlxsw_sp_acl_tcam_region_create(mlxsw_sp, group->tcam,
+ ®ion_elusage);
+ if (IS_ERR(region))
+ return PTR_ERR(region);
+ region_created = true;
+ }
+
+ chunk->region = region;
+ list_add_tail(&chunk->list, ®ion->chunk_list);
+
+ if (!region_created)
+ return 0;
+
+ err = mlxsw_sp_acl_tcam_group_region_attach(mlxsw_sp, group, region);
+ if (err)
+ goto err_group_region_attach;
+
+ return 0;
+
+err_group_region_attach:
+ mlxsw_sp_acl_tcam_region_destroy(mlxsw_sp, region);
+ return err;
+}
+
+static void
+mlxsw_sp_acl_tcam_chunk_deassoc(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_chunk *chunk)
+{
+ struct mlxsw_sp_acl_tcam_region *region = chunk->region;
+
+ list_del(&chunk->list);
+ if (list_empty(®ion->chunk_list)) {
+ mlxsw_sp_acl_tcam_group_region_detach(mlxsw_sp, region);
+ mlxsw_sp_acl_tcam_region_destroy(mlxsw_sp, region);
+ }
+}
+
+static struct mlxsw_sp_acl_tcam_chunk *
+mlxsw_sp_acl_tcam_chunk_create(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group,
+ unsigned int priority,
+ struct mlxsw_afk_element_usage *elusage)
+{
+ struct mlxsw_sp_acl_tcam_chunk *chunk;
+ int err;
+
+ if (priority == MLXSW_SP_ACL_TCAM_CATCHALL_PRIO)
+ return ERR_PTR(-EINVAL);
+
+ chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
+ if (!chunk)
+ return ERR_PTR(-ENOMEM);
+ chunk->priority = priority;
+ chunk->group = group;
+ chunk->ref_count = 1;
+
+ err = mlxsw_sp_acl_tcam_chunk_assoc(mlxsw_sp, group, priority,
+ elusage, chunk);
+ if (err)
+ goto err_chunk_assoc;
+
+ parman_prio_init(chunk->region->parman, &chunk->parman_prio, priority);
+
+ err = rhashtable_insert_fast(&group->chunk_ht, &chunk->ht_node,
+ mlxsw_sp_acl_tcam_chunk_ht_params);
+ if (err)
+ goto err_rhashtable_insert;
+
+ return chunk;
+
+err_rhashtable_insert:
+ parman_prio_fini(&chunk->parman_prio);
+ mlxsw_sp_acl_tcam_chunk_deassoc(mlxsw_sp, chunk);
+err_chunk_assoc:
+ kfree(chunk);
+ return ERR_PTR(err);
+}
+
+static void
+mlxsw_sp_acl_tcam_chunk_destroy(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_chunk *chunk)
+{
+ struct mlxsw_sp_acl_tcam_group *group = chunk->group;
+
+ rhashtable_remove_fast(&group->chunk_ht, &chunk->ht_node,
+ mlxsw_sp_acl_tcam_chunk_ht_params);
+ parman_prio_fini(&chunk->parman_prio);
+ mlxsw_sp_acl_tcam_chunk_deassoc(mlxsw_sp, chunk);
+ kfree(chunk);
+}
+
+static struct mlxsw_sp_acl_tcam_chunk *
+mlxsw_sp_acl_tcam_chunk_get(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group,
+ unsigned int priority,
+ struct mlxsw_afk_element_usage *elusage)
+{
+ struct mlxsw_sp_acl_tcam_chunk *chunk;
+
+ chunk = rhashtable_lookup_fast(&group->chunk_ht, &priority,
+ mlxsw_sp_acl_tcam_chunk_ht_params);
+ if (chunk) {
+ if (WARN_ON(!mlxsw_afk_key_info_subset(chunk->region->key_info,
+ elusage)))
+ return ERR_PTR(-EINVAL);
+ chunk->ref_count++;
+ return chunk;
+ }
+ return mlxsw_sp_acl_tcam_chunk_create(mlxsw_sp, group,
+ priority, elusage);
+}
+
+static void mlxsw_sp_acl_tcam_chunk_put(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_chunk *chunk)
+{
+ if (--chunk->ref_count)
+ return;
+ mlxsw_sp_acl_tcam_chunk_destroy(mlxsw_sp, chunk);
+}
+
+static int mlxsw_sp_acl_tcam_entry_add(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_group *group,
+ struct mlxsw_sp_acl_tcam_entry *entry,
+ struct mlxsw_sp_acl_rule_info *rulei)
+{
+ struct mlxsw_sp_acl_tcam_chunk *chunk;
+ struct mlxsw_sp_acl_tcam_region *region;
+ int err;
+
+ chunk = mlxsw_sp_acl_tcam_chunk_get(mlxsw_sp, group, rulei->priority,
+ &rulei->values.elusage);
+ if (IS_ERR(chunk))
+ return PTR_ERR(chunk);
+
+ region = chunk->region;
+ err = parman_item_add(region->parman, &chunk->parman_prio,
+ &entry->parman_item);
+ if (err)
+ goto err_parman_item_add;
+
+ err = mlxsw_sp_acl_tcam_region_entry_insert(mlxsw_sp, region,
+ entry->parman_item.index,
+ rulei);
+ if (err)
+ goto err_rule_insert;
+ entry->chunk = chunk;
+
+ return 0;
+
+err_rule_insert:
+ parman_item_remove(region->parman, &chunk->parman_prio,
+ &entry->parman_item);
+err_parman_item_add:
+ mlxsw_sp_acl_tcam_chunk_put(mlxsw_sp, chunk);
+ return err;
+}
+
+static void mlxsw_sp_acl_tcam_entry_del(struct mlxsw_sp *mlxsw_sp,
+ struct mlxsw_sp_acl_tcam_entry *entry)
+{
+ struct mlxsw_sp_acl_tcam_chunk *chunk = entry->chunk;
+ struct mlxsw_sp_acl_tcam_region *region = chunk->region;
+
+ mlxsw_sp_acl_tcam_region_entry_remove(mlxsw_sp, region,
+ entry->parman_item.index);
+ parman_item_remove(region->parman, &chunk->parman_prio,
+ &entry->parman_item);
+ mlxsw_sp_acl_tcam_chunk_put(mlxsw_sp, chunk);
+}
+
+static const enum mlxsw_afk_element mlxsw_sp_acl_tcam_pattern_ipv4[] = {
+ MLXSW_AFK_ELEMENT_SRC_SYS_PORT,
+ MLXSW_AFK_ELEMENT_DMAC,
+ MLXSW_AFK_ELEMENT_SMAC,
+ MLXSW_AFK_ELEMENT_ETHERTYPE,
+ MLXSW_AFK_ELEMENT_IP_PROTO,
+ MLXSW_AFK_ELEMENT_SRC_IP4,
+ MLXSW_AFK_ELEMENT_DST_IP4,
+ MLXSW_AFK_ELEMENT_DST_L4_PORT,
+ MLXSW_AFK_ELEMENT_SRC_L4_PORT,
+};
+
+static const enum mlxsw_afk_element mlxsw_sp_acl_tcam_pattern_ipv6[] = {
+ MLXSW_AFK_ELEMENT_ETHERTYPE,
+ MLXSW_AFK_ELEMENT_IP_PROTO,
+ MLXSW_AFK_ELEMENT_SRC_IP6_HI,
+ MLXSW_AFK_ELEMENT_SRC_IP6_LO,
+ MLXSW_AFK_ELEMENT_DST_IP6_HI,
+ MLXSW_AFK_ELEMENT_DST_IP6_LO,
+ MLXSW_AFK_ELEMENT_DST_L4_PORT,
+ MLXSW_AFK_ELEMENT_SRC_L4_PORT,
+};
+
+static const struct mlxsw_sp_acl_tcam_pattern mlxsw_sp_acl_tcam_patterns[] = {
+ {
+ .elements = mlxsw_sp_acl_tcam_pattern_ipv4,
+ .elements_count = ARRAY_SIZE(mlxsw_sp_acl_tcam_pattern_ipv4),
+ },
+ {
+ .elements = mlxsw_sp_acl_tcam_pattern_ipv6,
+ .elements_count = ARRAY_SIZE(mlxsw_sp_acl_tcam_pattern_ipv6),
+ },
+};
+
+#define MLXSW_SP_ACL_TCAM_PATTERNS_COUNT \
+ ARRAY_SIZE(mlxsw_sp_acl_tcam_patterns)
+
+struct mlxsw_sp_acl_tcam_flower_ruleset {
+ struct mlxsw_sp_acl_tcam_group group;
+};
+
+struct mlxsw_sp_acl_tcam_flower_rule {
+ struct mlxsw_sp_acl_tcam_entry entry;
+};
+
+static int
+mlxsw_sp_acl_tcam_flower_ruleset_add(struct mlxsw_sp *mlxsw_sp,
+ void *priv, void *ruleset_priv)
+{
+ struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
+ struct mlxsw_sp_acl_tcam *tcam = priv;
+
+ return mlxsw_sp_acl_tcam_group_add(mlxsw_sp, tcam, &ruleset->group,
+ mlxsw_sp_acl_tcam_patterns,
+ MLXSW_SP_ACL_TCAM_PATTERNS_COUNT);
+}
+
+static void
+mlxsw_sp_acl_tcam_flower_ruleset_del(struct mlxsw_sp *mlxsw_sp,
+ void *ruleset_priv)
+{
+ struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
+
+ mlxsw_sp_acl_tcam_group_del(mlxsw_sp, &ruleset->group);
+}
+
+static int
+mlxsw_sp_acl_tcam_flower_ruleset_bind(struct mlxsw_sp *mlxsw_sp,
+ void *ruleset_priv,
+ struct net_device *dev, bool ingress)
+{
+ struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
+
+ return mlxsw_sp_acl_tcam_group_bind(mlxsw_sp, &ruleset->group,
+ dev, ingress);
+}
+
+static void
+mlxsw_sp_acl_tcam_flower_ruleset_unbind(struct mlxsw_sp *mlxsw_sp,
+ void *ruleset_priv)
+{
+ struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
+
+ mlxsw_sp_acl_tcam_group_unbind(mlxsw_sp, &ruleset->group);
+}
+
+static int
+mlxsw_sp_acl_tcam_flower_rule_add(struct mlxsw_sp *mlxsw_sp,
+ void *ruleset_priv, void *rule_priv,
+ struct mlxsw_sp_acl_rule_info *rulei)
+{
+ struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
+ struct mlxsw_sp_acl_tcam_flower_rule *rule = rule_priv;
+
+ return mlxsw_sp_acl_tcam_entry_add(mlxsw_sp, &ruleset->group,
+ &rule->entry, rulei);
+}
+
+static void
+mlxsw_sp_acl_tcam_flower_rule_del(struct mlxsw_sp *mlxsw_sp, void *rule_priv)
+{
+ struct mlxsw_sp_acl_tcam_flower_rule *rule = rule_priv;
+
+ mlxsw_sp_acl_tcam_entry_del(mlxsw_sp, &rule->entry);
+}
+
+static const struct mlxsw_sp_acl_profile_ops mlxsw_sp_acl_tcam_flower_ops = {
+ .ruleset_priv_size = sizeof(struct mlxsw_sp_acl_tcam_flower_ruleset),
+ .ruleset_add = mlxsw_sp_acl_tcam_flower_ruleset_add,
+ .ruleset_del = mlxsw_sp_acl_tcam_flower_ruleset_del,
+ .ruleset_bind = mlxsw_sp_acl_tcam_flower_ruleset_bind,
+ .ruleset_unbind = mlxsw_sp_acl_tcam_flower_ruleset_unbind,
+ .rule_priv_size = sizeof(struct mlxsw_sp_acl_tcam_flower_rule),
+ .rule_add = mlxsw_sp_acl_tcam_flower_rule_add,
+ .rule_del = mlxsw_sp_acl_tcam_flower_rule_del,
+};
+
+static const struct mlxsw_sp_acl_profile_ops *
+mlxsw_sp_acl_tcam_profile_ops_arr[] = {
+ [MLXSW_SP_ACL_PROFILE_FLOWER] = &mlxsw_sp_acl_tcam_flower_ops,
+};
+
+static const struct mlxsw_sp_acl_profile_ops *
+mlxsw_sp_acl_tcam_profile_ops(struct mlxsw_sp *mlxsw_sp,
+ enum mlxsw_sp_acl_profile profile)
+{
+ const struct mlxsw_sp_acl_profile_ops *ops;
+
+ if (WARN_ON(profile >= ARRAY_SIZE(mlxsw_sp_acl_tcam_profile_ops_arr)))
+ return NULL;
+ ops = mlxsw_sp_acl_tcam_profile_ops_arr[profile];
+ if (WARN_ON(!ops))
+ return NULL;
+ return ops;
+}
+
+const struct mlxsw_sp_acl_ops mlxsw_sp_acl_tcam_ops = {
+ .priv_size = sizeof(struct mlxsw_sp_acl_tcam),
+ .init = mlxsw_sp_acl_tcam_init,
+ .fini = mlxsw_sp_acl_tcam_fini,
+ .profile_ops = mlxsw_sp_acl_tcam_profile_ops,
+};