mlxsw: spectrum_acl: Add support for C-TCAM eRPs
authorIdo Schimmel <idosch@mellanox.com>
Wed, 25 Jul 2018 06:23:57 +0000 (09:23 +0300)
committerDavid S. Miller <davem@davemloft.net>
Wed, 25 Jul 2018 23:46:01 +0000 (16:46 -0700)
The number of eRPs that can be used by a single A-TCAM region is limited
to 16. When more eRPs are needed, an ordinary circuit TCAM (C-TCAM) can
be used to hold the extra eRPs.

Unlike the A-TCAM, only a single (last) lookup is performed in the
C-TCAM and not a lookup per-eRP. However, modeling the C-TCAM as extra
eRPs will allow us to easily introduce support for pruning in a
follow-up patch set and is also logically correct.

The following diagram depicts the relation between both TCAMs:
                                                                 C-TCAM
+-------------------+               +--------------------+    +-----------+
|                   |               |                    |    |           |
|  eRP #1 (A-TCAM)  +----> ... +----+  eRP #16 (A-TCAM)  +----+  eRP #17  |
|                   |               |                    |    |    ...    |
+-------------------+               +--------------------+    |  eRP #N   |
                                                              |           |
                                                              +-----------+
Lookup order is from left to right.

Extend the eRP core APIs with a C-TCAM parameter which indicates whether
the requested eRP is to be used with the C-TCAM or not.

Since the C-TCAM is only meant to absorb rules that can't fit in the
A-TCAM due to exceeded number of eRPs or key collision, an error is
returned when a C-TCAM eRP needs to be created when the eRP state
machine is in its initial state (i.e., 'no masks'). This should only
happen in the face of very unlikely errors when trying to push rules
into the A-TCAM.

In order not to perform unnecessary lookups, the eRP core will only
enable a C-TCAM lookup for a given region if it knows there are C-TCAM
eRPs present.

Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Reviewed-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_erp.c
drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.h

index 86600c780c9519421df402a9a4b4ade474e5dcbb..e26efa451d4a88af94440658549946275976f9c3 100644 (file)
@@ -60,6 +60,7 @@ struct mlxsw_sp_acl_erp_core {
 
 struct mlxsw_sp_acl_erp_key {
        char mask[MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN];
+       bool ctcam;
 };
 
 struct mlxsw_sp_acl_erp {
@@ -90,6 +91,7 @@ struct mlxsw_sp_acl_erp_table {
        unsigned long base_index;
        unsigned int num_atcam_erps;
        unsigned int num_max_atcam_erps;
+       unsigned int num_ctcam_erps;
 };
 
 static const struct rhashtable_params mlxsw_sp_acl_erp_ht_params = {
@@ -448,13 +450,14 @@ static void mlxsw_sp_acl_erp_table_erp_del(struct mlxsw_sp_acl_erp *erp)
 }
 
 static int
-mlxsw_sp_acl_erp_table_enable(struct mlxsw_sp_acl_erp_table *erp_table)
+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, false, true, 0,
+       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);
@@ -471,11 +474,12 @@ mlxsw_sp_acl_erp_table_disable(struct mlxsw_sp_acl_erp_table *erp_table)
        struct mlxsw_sp_acl_erp *master_rp;
 
        master_rp = mlxsw_sp_acl_erp_table_master_rp(erp_table);
-       if (!master_rp)
-               return;
-
+       /* 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->id);
+                             master_rp ? master_rp->id : 0);
        mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 }
 
@@ -506,6 +510,7 @@ 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)
@@ -526,7 +531,7 @@ mlxsw_sp_acl_erp_table_expand(struct mlxsw_sp_acl_erp_table *erp_table)
        if (err)
                goto err_table_relocate;
 
-       err = mlxsw_sp_acl_erp_table_enable(erp_table);
+       err = mlxsw_sp_acl_erp_table_enable(erp_table, ctcam_le);
        if (err)
                goto err_table_enable;
 
@@ -579,7 +584,7 @@ mlxsw_sp_acl_erp_region_table_trans(struct mlxsw_sp_acl_erp_table *erp_table)
        if (err)
                goto err_table_master_rp_add;
 
-       err = mlxsw_sp_acl_erp_table_enable(erp_table);
+       err = mlxsw_sp_acl_erp_table_enable(erp_table, false);
        if (err)
                goto err_table_enable;
 
@@ -619,9 +624,10 @@ mlxsw_sp_acl_erp_region_erp_add(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;
+       bool ctcam_le = erp_table->num_ctcam_erps > 0;
        char pererp_pl[MLXSW_REG_PERERP_LEN];
 
-       mlxsw_reg_pererp_pack(pererp_pl, region->id, false, true, 0,
+       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);
@@ -635,9 +641,10 @@ 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, false, true, 0,
+       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);
@@ -646,6 +653,161 @@ static void mlxsw_sp_acl_erp_region_erp_del(struct mlxsw_sp_acl_erp *erp)
        mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pererp), pererp_pl);
 }
 
+static int
+mlxsw_sp_acl_erp_region_ctcam_enable(struct mlxsw_sp_acl_erp_table *erp_table)
+{
+       /* No need to re-enable lookup in the C-TCAM */
+       if (erp_table->num_ctcam_erps > 1)
+               return 0;
+
+       return mlxsw_sp_acl_erp_table_enable(erp_table, true);
+}
+
+static void
+mlxsw_sp_acl_erp_region_ctcam_disable(struct mlxsw_sp_acl_erp_table *erp_table)
+{
+       /* Only disable C-TCAM lookup when last C-TCAM eRP is deleted */
+       if (erp_table->num_ctcam_erps > 1)
+               return;
+
+       mlxsw_sp_acl_erp_table_enable(erp_table, false);
+}
+
+static void
+mlxsw_sp_acl_erp_ctcam_table_ops_set(struct mlxsw_sp_acl_erp_table *erp_table)
+{
+       switch (erp_table->num_atcam_erps) {
+       case 2:
+               /* Keep using the eRP table, but correctly set the
+                * operations pointer so that when an A-TCAM eRP is
+                * deleted we will transition to use the master mask
+                */
+               erp_table->ops = &erp_two_masks_ops;
+               break;
+       case 1:
+               /* We only kept the eRP table because we had C-TCAM
+                * eRPs in use. Now that the last C-TCAM eRP is gone we
+                * can stop using the table and transition to use the
+                * master mask
+                */
+               mlxsw_sp_acl_erp_region_master_mask_trans(erp_table);
+               erp_table->ops = &erp_single_mask_ops;
+               break;
+       case 0:
+               /* There are no more eRPs of any kind used by the region
+                * so free its eRP table and transition to initial state
+                */
+               mlxsw_sp_acl_erp_table_disable(erp_table);
+               mlxsw_sp_acl_erp_table_free(erp_table->erp_core,
+                                           erp_table->num_max_atcam_erps,
+                                           erp_table->aregion->type,
+                                           erp_table->base_index);
+               erp_table->ops = &erp_no_mask_ops;
+               break;
+       default:
+               break;
+       }
+}
+
+static struct mlxsw_sp_acl_erp *
+__mlxsw_sp_acl_erp_ctcam_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
+                                    struct mlxsw_sp_acl_erp_key *key)
+{
+       struct mlxsw_sp_acl_erp *erp;
+       int err;
+
+       erp = kzalloc(sizeof(*erp), GFP_KERNEL);
+       if (!erp)
+               return ERR_PTR(-ENOMEM);
+
+       memcpy(&erp->key, key, sizeof(*key));
+       bitmap_from_arr32(erp->mask_bitmap, (u32 *) key->mask,
+                         MLXSW_SP_ACL_TCAM_MASK_LEN);
+       refcount_set(&erp->refcnt, 1);
+       erp_table->num_ctcam_erps++;
+       erp->erp_table = erp_table;
+
+       err = mlxsw_sp_acl_erp_master_mask_set(erp_table, erp);
+       if (err)
+               goto err_master_mask_set;
+
+       err = rhashtable_insert_fast(&erp_table->erp_ht, &erp->ht_node,
+                                    mlxsw_sp_acl_erp_ht_params);
+       if (err)
+               goto err_rhashtable_insert;
+
+       err = mlxsw_sp_acl_erp_region_ctcam_enable(erp_table);
+       if (err)
+               goto err_erp_region_ctcam_enable;
+
+       /* When C-TCAM is used, the eRP table must be used */
+       erp_table->ops = &erp_multiple_masks_ops;
+
+       return erp;
+
+err_erp_region_ctcam_enable:
+       rhashtable_remove_fast(&erp_table->erp_ht, &erp->ht_node,
+                              mlxsw_sp_acl_erp_ht_params);
+err_rhashtable_insert:
+       mlxsw_sp_acl_erp_master_mask_clear(erp_table, erp);
+err_master_mask_set:
+       erp_table->num_ctcam_erps--;
+       kfree(erp);
+       return ERR_PTR(err);
+}
+
+static struct mlxsw_sp_acl_erp *
+mlxsw_sp_acl_erp_ctcam_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
+                                  struct mlxsw_sp_acl_erp_key *key)
+{
+       struct mlxsw_sp_acl_erp *erp;
+       int err;
+
+       /* There is a special situation where we need to spill rules
+        * into the C-TCAM, yet the region is still using a master
+        * mask and thus not performing a lookup in the C-TCAM. This
+        * can happen when two rules that only differ in priority - and
+        * thus sharing the same key - are programmed. In this case
+        * we transition the region to use an eRP table
+        */
+       err = mlxsw_sp_acl_erp_region_table_trans(erp_table);
+       if (err)
+               return ERR_PTR(err);
+
+       erp = __mlxsw_sp_acl_erp_ctcam_mask_create(erp_table, key);
+       if (IS_ERR(erp)) {
+               err = PTR_ERR(erp);
+               goto err_erp_create;
+       }
+
+       return erp;
+
+err_erp_create:
+       mlxsw_sp_acl_erp_region_master_mask_trans(erp_table);
+       return ERR_PTR(err);
+}
+
+static void
+mlxsw_sp_acl_erp_ctcam_mask_destroy(struct mlxsw_sp_acl_erp *erp)
+{
+       struct mlxsw_sp_acl_erp_table *erp_table = erp->erp_table;
+
+       mlxsw_sp_acl_erp_region_ctcam_disable(erp_table);
+       rhashtable_remove_fast(&erp_table->erp_ht, &erp->ht_node,
+                              mlxsw_sp_acl_erp_ht_params);
+       mlxsw_sp_acl_erp_master_mask_clear(erp_table, erp);
+       erp_table->num_ctcam_erps--;
+       kfree(erp);
+
+       /* Once the last C-TCAM eRP was destroyed, the state we
+        * transition to depends on the number of A-TCAM eRPs currently
+        * in use
+        */
+       if (erp_table->num_ctcam_erps > 0)
+               return;
+       mlxsw_sp_acl_erp_ctcam_table_ops_set(erp_table);
+}
+
 static struct mlxsw_sp_acl_erp *
 mlxsw_sp_acl_erp_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
                             struct mlxsw_sp_acl_erp_key *key)
@@ -653,6 +815,9 @@ mlxsw_sp_acl_erp_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
        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)
@@ -691,12 +856,15 @@ 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)
+       if (erp_table->num_atcam_erps == 2 && erp_table->num_ctcam_erps == 0)
                erp_table->ops = &erp_two_masks_ops;
 }
 
@@ -707,6 +875,9 @@ mlxsw_sp_acl_erp_second_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
        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)
@@ -749,6 +920,9 @@ 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);
@@ -765,6 +939,9 @@ mlxsw_sp_acl_erp_first_mask_create(struct mlxsw_sp_acl_erp_table *erp_table,
 {
        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;
@@ -791,7 +968,7 @@ mlxsw_sp_acl_erp_no_mask_destroy(struct mlxsw_sp_acl_erp_table *erp_table,
 
 struct mlxsw_sp_acl_erp *
 mlxsw_sp_acl_erp_get(struct mlxsw_sp_acl_atcam_region *aregion,
-                    const char *mask)
+                    const char *mask, bool ctcam)
 {
        struct mlxsw_sp_acl_erp_table *erp_table = aregion->erp_table;
        struct mlxsw_sp_acl_erp_key key;
@@ -803,6 +980,7 @@ mlxsw_sp_acl_erp_get(struct mlxsw_sp_acl_atcam_region *aregion,
        ASSERT_RTNL();
 
        memcpy(key.mask, mask, MLXSW_REG_PTCEX_FLEX_KEY_BLOCKS_LEN);
+       key.ctcam = ctcam;
        erp = rhashtable_lookup_fast(&erp_table->erp_ht, &key,
                                     mlxsw_sp_acl_erp_ht_params);
        if (erp) {
index af21f7c6c6df7305c9a42091d445d97f303e9ce6..70094936ca4388dd7db698680b7dc76ea9e9840b 100644 (file)
@@ -179,7 +179,7 @@ struct mlxsw_sp_acl_erp;
 u8 mlxsw_sp_acl_erp_id(const struct mlxsw_sp_acl_erp *erp);
 struct mlxsw_sp_acl_erp *
 mlxsw_sp_acl_erp_get(struct mlxsw_sp_acl_atcam_region *aregion,
-                    const char *mask);
+                    const char *mask, bool ctcam);
 void mlxsw_sp_acl_erp_put(struct mlxsw_sp_acl_atcam_region *aregion,
                          struct mlxsw_sp_acl_erp *erp);
 int mlxsw_sp_acl_erp_region_init(struct mlxsw_sp_acl_atcam_region *aregion);