From: Farah Smith <[email protected]>

When the CMM block free-list runs dry before all records in a pool are
consumed, the CPM permanently excluded the pool from new allocations.
Under sustained insert/delete churn, pool slots were consumed one by
one until no slots remained and all subsequent flow inserts failed.

This applies three related fixes. First, make the block-size-limited
condition recoverable once enough records are freed back. Second,
transparently rotate to the next available pool and retry on
mid-insert exhaustion. Third, apply the related cfa_mm bookkeeping
fixes needed to support the above.

Fixes: 80317ff6adfd ("net/bnxt/tf_core: support Thor2")
Cc: [email protected]

Signed-off-by: Farah Smith <[email protected]>
Signed-off-by: Manish Kurup <[email protected]>
---
 drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm.c     |  42 +++-
 .../net/bnxt/hcapi/cfa_v3/mm/cfa_mm_priv.h    |   5 +
 .../net/bnxt/hcapi/cfa_v3/mm/include/cfa_mm.h |  23 ++
 drivers/net/bnxt/tf_core/v3/tfc_act.c         | 207 ++++++++++++------
 drivers/net/bnxt/tf_core/v3/tfc_cpm.c         |  41 +++-
 drivers/net/bnxt/tf_core/v3/tfc_cpm.h         |  31 ++-
 drivers/net/bnxt/tf_core/v3/tfc_em.c          | 207 +++++++++++-------
 7 files changed, 401 insertions(+), 155 deletions(-)

diff --git a/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm.c 
b/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm.c
index 6e21d513ac..61822cad46 100644
--- a/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm.c
+++ b/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm.c
@@ -124,6 +124,7 @@ int cfa_mm_open(void *cmm, struct cfa_mm_open_parms *parms)
 
        context->blk_list_tbl[0].first_blk_idx = 0;
        context->blk_list_tbl[0].last_blk_idx = 0;
+       context->free_blk_count = num_blocks;
 
        for (i = 1; i < num_lists; i++) {
                context->blk_list_tbl[i].first_blk_idx = CFA_MM_INVALID32;
@@ -196,6 +197,8 @@ static uint32_t cfa_mm_blk_alloc(struct cfa_mm *context)
        context->blk_tbl[blk_idx].prev_blk_idx = CFA_MM_INVALID32;
        context->blk_tbl[blk_idx].next_blk_idx = CFA_MM_INVALID32;
 
+       context->free_blk_count--;
+
        return blk_idx;
 }
 
@@ -206,6 +209,7 @@ static void cfa_mm_blk_free(struct cfa_mm *context, 
uint32_t blk_idx)
 
        context->blk_tbl[blk_idx].prev_blk_idx = CFA_MM_INVALID32;
        context->blk_tbl[blk_idx].next_blk_idx = free_list->first_blk_idx;
+       context->free_blk_count++;
        context->blk_tbl[blk_idx].num_free_records = context->records_per_block;
        context->blk_tbl[blk_idx].first_free_record = 0;
        context->blk_tbl[blk_idx].num_contig_records = 0;
@@ -385,6 +389,7 @@ static int cfa_mm_test_and_set_bits(uint8_t *bmap, uint16_t 
start,
 int cfa_mm_alloc(void *cmm, struct cfa_mm_alloc_parms *parms)
 {
        int ret = 0;
+       bool free_list_empty = false;
        uint16_t list_idx, num_records;
        uint32_t i, cnt, blk_idx, record_idx;
        struct cfa_mm_blk_list *blk_list;
@@ -422,6 +427,16 @@ int cfa_mm_alloc(void *cmm, struct cfa_mm_alloc_parms 
*parms)
        if (blk_list->first_blk_idx == CFA_MM_INVALID32) {
                blk_idx = cfa_mm_blk_alloc(context);
                if (unlikely(blk_idx == CFA_MM_INVALID32)) {
+                       /*
+                        * The master free-block pool is empty.
+                        * Records from other size-class lists cannot be
+                        * reclaimed for this request — each block is locked
+                        * to one size class until all its records are freed.
+                        * Signal the caller that this CMM instance is
+                        * effectively full so the CPM can retire it and
+                        * rotate to a new pool on the next allocation.
+                        */
+                       free_list_empty = true;
                        ret = -ENOMEM;
                        goto cfa_mm_alloc_exit;
                }
@@ -447,6 +462,7 @@ int cfa_mm_alloc(void *cmm, struct cfa_mm_alloc_parms 
*parms)
                    !blk_info->num_free_records) {
                        blk_idx = cfa_mm_blk_alloc(context);
                        if (unlikely(blk_idx == CFA_MM_INVALID32)) {
+                               free_list_empty = true;
                                ret = -ENOMEM;
                                goto cfa_mm_alloc_exit;
                        }
@@ -514,7 +530,15 @@ int cfa_mm_alloc(void *cmm, struct cfa_mm_alloc_parms 
*parms)
 
        parms->used_count = context->records_in_use;
 
-       parms->all_used = (context->records_in_use >= context->max_records);
+       /*
+        * Mark the pool as fully used if the free-block pool ran dry even
+        * when records_in_use < max_records.  An empty free-block pool means
+        * no further allocations are possible: stranded records in size-class
+        * lists cannot be reclaimed.  Setting all_used lets the caller (CPM)
+        * retire this pool and rotate to a fresh one.
+        */
+       parms->all_used = free_list_empty ||
+                         (context->records_in_use >= context->max_records);
 
        return ret;
 }
@@ -602,6 +626,22 @@ int cfa_mm_free(void *cmm, struct cfa_mm_free_parms *parms)
        return 0;
 }
 
+/** Return the number of unassigned blocks currently in the master free-block
+ *  pool (list_0).  This is an O(1) read of a counter maintained by
+ *  cfa_mm_blk_alloc() and cfa_mm_blk_free().  Callers compare the return
+ *  value against TFC_CPM_BLK_RECOVERY_THRESHOLD to decide whether a
+ *  blk_sz_limited pool has recovered enough capacity to re-enter rotation.
+ */
+uint32_t cfa_mm_free_blk_count(void *cmm)
+{
+       struct cfa_mm *context = (struct cfa_mm *)cmm;
+
+       if (unlikely(cmm == NULL || context->signature != CFA_MM_SIGNATURE))
+               return 0;
+
+       return context->free_blk_count;
+}
+
 int cfa_mm_entry_size_get(void *cmm, uint32_t entry_id, uint8_t *size)
 {
        uint8_t *blk_bmap;
diff --git a/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm_priv.h 
b/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm_priv.h
index dbac1c3cf2..05d3eea8d0 100644
--- a/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm_priv.h
+++ b/drivers/net/bnxt/hcapi/cfa_v3/mm/cfa_mm_priv.h
@@ -62,6 +62,11 @@ struct cfa_mm {
        uint32_t max_records;
        /* Number of CFA Records in use*/
        uint32_t records_in_use;
+       /* Number of unassigned blocks currently in the master free-block pool 
(list_0).
+        * Maintained O(1) by cfa_mm_blk_alloc / cfa_mm_blk_free.
+        * Read by cfa_mm_free_blk_count() for the CPM recovery threshold check.
+        */
+       uint32_t free_blk_count;
        /* Number of Records per block */
        uint16_t records_per_block;
        /* Maximum number of contiguous records */
diff --git a/drivers/net/bnxt/hcapi/cfa_v3/mm/include/cfa_mm.h 
b/drivers/net/bnxt/hcapi/cfa_v3/mm/include/cfa_mm.h
index 507a9cb773..c74b10778f 100644
--- a/drivers/net/bnxt/hcapi/cfa_v3/mm/include/cfa_mm.h
+++ b/drivers/net/bnxt/hcapi/cfa_v3/mm/include/cfa_mm.h
@@ -161,6 +161,29 @@ int cfa_mm_free(void *cmm, struct cfa_mm_free_parms 
*parms);
  */
 int cfa_mm_entry_size_get(void *cmm, uint32_t entry_id, uint8_t *size);
 
+/** CFA Memory Manager Free Block Count API
+ *
+ * Returns the number of unassigned blocks currently in the master
+ * free-block pool (list_0).  Each block can serve any size class up to
+ * max_contig_records, so this count is the maximum number of additional
+ * allocations (of any size) the CMM instance can still satisfy before its
+ * block pool is exhausted.
+ *
+ * This is an O(1) operation — a single field read of a counter maintained
+ * by cfa_mm_blk_alloc() and cfa_mm_blk_free() with no list traversal.
+ *
+ * Callers compare the return value against TFC_CPM_BLK_RECOVERY_THRESHOLD
+ * (defined in tfc_cpm.h) to decide whether a blk_sz_limited pool has
+ * recovered enough capacity to re-enter rotation.
+ *
+ * @param[in] cmm
+ *   Pointer to the CFA Memory Manager database
+ *
+ * @return
+ *   Number of free blocks in list_0; 0 on invalid input or empty pool
+ */
+uint32_t cfa_mm_free_blk_count(void *cmm);
+
 /**@}*/
 
 #endif /* _CFA_MM_H_ */
diff --git a/drivers/net/bnxt/tf_core/v3/tfc_act.c 
b/drivers/net/bnxt/tf_core/v3/tfc_act.c
index d93064dbc6..7cd419150f 100644
--- a/drivers/net/bnxt/tf_core/v3/tfc_act.c
+++ b/drivers/net/bnxt/tf_core/v3/tfc_act.c
@@ -31,6 +31,104 @@
 /* Max additional data size in bytes */
 #define TFC_ACT_DISCARD_DATA_SIZE 128
 
+/*
+ * Select or create an ACT pool and return its pool_id and CMM instance.
+ *
+ * If the CPM has an available pool, its CMM is returned directly.
+ * Otherwise a new pool is allocated from the TPM, a fresh CMM is opened
+ * for it, and it is registered with the CPM before returning.
+ *
+ * Returns 0 on success, -ENOMEM if no pools remain (non-shared scope),
+ * -EINVAL on any other error.
+ */
+static int act_get_or_alloc_pool(struct tfc *tfcp,
+                                uint8_t tsid,
+                                struct tfc_cmm_info *cmm_info,
+                                struct tfc_cpm *cpm_act,
+                                enum cfa_scope_type scope_type,
+                                uint16_t max_pools,
+                                struct tfc_ts_mem_cfg *mem_cfg,
+                                struct tfc_ts_pool_info *pi,
+                                uint16_t *pool_id,
+                                struct tfc_cmm **cmm)
+{
+       int rc;
+       struct cfa_mm_query_parms qparms;
+       struct cfa_mm_open_parms oparms;
+       uint16_t fid;
+
+       rc = tfc_cpm_get_avail_pool(cpm_act, pool_id);
+       if (!rc) {
+               rc = tfc_cpm_get_cmm_inst(cpm_act, *pool_id, cmm);
+               if (unlikely(rc)) {
+                       PMD_DRV_LOG_LINE(ERR, "tfc_cpm_get_cmm_inst() failed: 
%d", rc);
+                       return -EINVAL;
+               }
+               return 0;
+       }
+
+       /* No pool available — allocate a new one from TPM */
+       if (unlikely(scope_type == CFA_SCOPE_TYPE_NON_SHARED)) {
+               PMD_DRV_LOG_LINE(ERR, "no records remain");
+               return -ENOMEM;
+       }
+
+       rc = tfc_get_fid(tfcp, &fid);
+       if (unlikely(rc))
+               return rc;
+
+       rc = tfc_tbl_scope_pool_alloc(tfcp, fid, tsid, CFA_REGION_TYPE_ACT,
+                                     cmm_info->dir, NULL, pool_id);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "table scope pool alloc failed: %s", 
strerror(-rc));
+               return -EINVAL;
+       }
+
+       if (max_pools > 0 && scope_type != CFA_SCOPE_TYPE_NON_SHARED)
+               qparms.max_records = mem_cfg->rec_cnt / max_pools;
+       else
+               qparms.max_records = mem_cfg->rec_cnt;
+       if (unlikely(qparms.max_records == 0)) {
+               PMD_DRV_LOG_LINE(WARNING,
+                                "rec_cnt=0 for tsid ACT, using min 1 record 
for CMM (max_pools=%u)",
+                                max_pools);
+               qparms.max_records = 1;
+       }
+       qparms.max_contig_records = 1U << 
next_pow2((uint32_t)pi->act_max_contig_rec);
+       rc = cfa_mm_query(&qparms);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "cfa_mm_query() failed: %s", 
strerror(-rc));
+               return rc;
+       }
+
+       *cmm = rte_zmalloc("tf", qparms.db_size, 0);
+       if (unlikely(*cmm == NULL)) {
+               PMD_DRV_LOG_LINE(ERR, "rte_zmalloc() failed for CMM instance");
+               return -ENOMEM;
+       }
+       oparms.db_mem_size = qparms.db_size;
+       oparms.max_contig_records = qparms.max_contig_records;
+       oparms.max_records = qparms.max_records;
+       rc = cfa_mm_open(*cmm, &oparms);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "cfa_mm_open() failed: %d", rc);
+               rte_free(*cmm);
+               *cmm = NULL;
+               return -EINVAL;
+       }
+
+       rc = tfc_cpm_set_cmm_inst(cpm_act, *pool_id, *cmm);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "tfc_cpm_set_cmm_inst() failed: %d", rc);
+               rte_free(*cmm);
+               *cmm = NULL;
+               return -EINVAL;
+       }
+
+       tfo_ts_set_pool_info(tfcp->tfo, tsid, cmm_info->dir, pi);
+       return 0;
+}
+
 int tfc_act_alloc(struct tfc *tfcp,
                  uint8_t tsid,
                  struct tfc_cmm_info *cmm_info,
@@ -91,88 +189,53 @@ int tfc_act_alloc(struct tfc *tfcp,
                return -EINVAL;
        }
 
-       /* if no pool available locally or all pools full */
-       rc = tfc_cpm_get_avail_pool(cpm_act, &pool_id);
+       /* Select an available pool or allocate a new one from TPM */
+       rc = act_get_or_alloc_pool(tfcp, tsid, cmm_info, cpm_act, scope_type,
+                                  max_pools, &mem_cfg, &pi, &pool_id, &cmm);
+       if (unlikely(rc))
+               return rc;
 
-       if (rc) {
-               /* Allocate a pool */
-               struct cfa_mm_query_parms qparms;
-               struct cfa_mm_open_parms oparms;
-               uint16_t fid;
+       aparms.num_contig_records = (num_contig_rec == 1) ?
+                       1 : 1 << next_pow2(num_contig_rec);
 
-               /* There is only 1 pool for a non-shared table scope
-                * and it is full.
+       rc = cfa_mm_alloc(cmm, &aparms);
+       if (unlikely(rc == -ENOMEM)) {
+               /*
+                * The pool cannot serve this allocation — either its block
+                * free-list is exhausted (fragmented) or the pool is nearly
+                * full.  Mark it unavailable and rotate to a fresh pool so
+                * the caller does not see a spurious failure.
+                * Always force all_used=true here so the CPM pushes this pool
+                * to the tail regardless of the actual records_in_use value.
                 */
-               if (unlikely(scope_type == CFA_SCOPE_TYPE_NON_SHARED)) {
-                       PMD_DRV_LOG_LINE(ERR, "%s: no records remain",
-                                        __func__);
-                       return -ENOMEM;
-               }
-               rc = tfc_get_fid(tfcp, &fid);
-               if (unlikely(rc))
-                       return rc;
-
-               rc = tfc_tbl_scope_pool_alloc(tfcp,
-                                             fid,
-                                             tsid,
-                                             CFA_REGION_TYPE_ACT,
-                                             cmm_info->dir,
-                                             NULL,
-                                             &pool_id);
+               tfc_cpm_set_usage(cpm_act, pool_id, aparms.used_count, true, 
false);
 
+               rc = act_get_or_alloc_pool(tfcp, tsid, cmm_info, cpm_act,
+                                          scope_type, max_pools, &mem_cfg,
+                                          &pi, &pool_id, &cmm);
                if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "table scope pool alloc failed: 
%s",
+                       PMD_DRV_LOG_LINE(ERR, "no pool available after 
rotation: %s",
                                         strerror(-rc));
-                       return -EINVAL;
-               }
-
-               /* Create pool CMM instance */
-               qparms.max_records = mem_cfg.rec_cnt;
-               qparms.max_contig_records = pi.act_max_contig_rec;
-               rc = cfa_mm_query(&qparms);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "cfa_mm_query() failed: %s", 
strerror(-rc));
                        return rc;
                }
-
-               cmm = rte_zmalloc("tf", qparms.db_size, 0);
-               oparms.db_mem_size = qparms.db_size;
-               oparms.max_contig_records = qparms.max_contig_records;
-               oparms.max_records = qparms.max_records / max_pools;
-               rc = cfa_mm_open(cmm, &oparms);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "cfa_mm_open() failed: %d", rc);
-                       return -EINVAL;
-               }
-
-               /* Store CMM instance in the CPM */
-               rc = tfc_cpm_set_cmm_inst(cpm_act, pool_id, cmm);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "tfc_cpm_set_cmm_inst() failed: 
%d", rc);
-                       return -EINVAL;
-               }
-               /* store updated pool info */
-               tfo_ts_set_pool_info(tfcp->tfo, tsid, cmm_info->dir, &pi);
-
-       } else {
-               /* Get the pool instance and allocate an act rec index from the 
pool */
-               rc = tfc_cpm_get_cmm_inst(cpm_act, pool_id, &cmm);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "tfc_cpm_get_cmm_inst() failed: 
%d", rc);
-                       return -EINVAL;
-               }
+               rc = cfa_mm_alloc(cmm, &aparms);
        }
-       aparms.num_contig_records = 1 << next_pow2(num_contig_rec);
-       rc = cfa_mm_alloc(cmm, &aparms);
        if (unlikely(rc)) {
                PMD_DRV_LOG_LINE(ERR, "cfa_mm_alloc() failed: %d", rc);
-               return -EINVAL;
+               /* alloc path: recovery only occurs on free, pass false */
+               if (aparms.all_used)
+                       tfc_cpm_set_usage(cpm_act, pool_id,
+                                         aparms.used_count, true, false);
+               return rc;
        }
 
-       /* Update CPM info so it will determine best pool to use next alloc */
-       rc = tfc_cpm_set_usage(pi.act_cpm, pool_id, aparms.used_count, 
aparms.all_used);
+       /* Update CPM so it determines the best pool for the next alloc.
+        * Recovery (blk_sz_recovered) is not possible on the alloc path —
+        * the free-block pool only replenishes when records are freed.
+        */
+       rc = tfc_cpm_set_usage(cpm_act, pool_id, aparms.used_count, 
aparms.all_used, false);
        if (unlikely(rc))
-               PMD_DRV_LOG_LINE(ERR, "EM insert tfc_cpm_set_usage() failed: 
%d", rc);
+               PMD_DRV_LOG_LINE(ERR, "ACT alloc tfc_cpm_set_usage() failed: 
%d", rc);
 
        CREATE_OFFSET(&entry_offset, pi.act_pool_sz_exp, pool_id, 
aparms.record_offset);
 
@@ -800,7 +863,13 @@ int tfc_act_free(struct tfc *tfcp,
                return -EINVAL;
        }
 
-       rc = tfc_cpm_set_usage(cpm_act, pool_id, 0, false);
+       /* Update CPM with actual remaining used count so pool ordering stays 
correct.
+        * Signal recovery only when free block count meets the threshold — a 
small
+        * number of freed records may transiently return one block but the 
pool is
+        * still heavily fragmented.
+        */
+       rc = tfc_cpm_set_usage(cpm_act, pool_id, fparms.used_count, false,
+                              cfa_mm_free_blk_count(cmm) >= 
TFC_CPM_BLK_RECOVERY_THRESHOLD);
        if (unlikely(rc))
                PMD_DRV_LOG_LINE(ERR, "failed to set usage: %d", rc);
 
diff --git a/drivers/net/bnxt/tf_core/v3/tfc_cpm.c 
b/drivers/net/bnxt/tf_core/v3/tfc_cpm.c
index 8d95a0c205..8328c071f9 100644
--- a/drivers/net/bnxt/tf_core/v3/tfc_cpm.c
+++ b/drivers/net/bnxt/tf_core/v3/tfc_cpm.c
@@ -16,6 +16,16 @@ struct cpm_pool_entry {
        struct tfc_cmm *cmm;
        uint32_t used_count;
        bool all_used;
+       /*
+        * Set when the CMM block free-list (list_0) is exhausted before
+        * records_in_use reaches pool_size.  Per-size-class block lists
+        * strand records that cannot be reclaimed for a different size
+        * class without fully freeing those blocks.  While set, this flag
+        * forces all_used=true so the CPM skips this pool.  It is cleared
+        * by tfc_cpm_set_usage() once list_0 has at least one free block
+        * again, meaning any size class can be served immediately.
+        */
+       bool blk_sz_limited;
        struct cpm_pool_use *pool_use;
 };
 
@@ -312,6 +322,7 @@ int tfc_cpm_set_cmm_inst(struct tfc_cpm *cpm, uint16_t 
pool_id, struct tfc_cmm *
        pool->cmm = cmm;
        pool->used_count = 0;
        pool->all_used = false;
+       pool->blk_sz_limited = false;
        pool->pool_use = NULL;
 
        if (cmm == NULL) {
@@ -369,7 +380,8 @@ int tfc_cpm_get_avail_pool(struct tfc_cpm *cpm, uint16_t 
*pool_id)
        return 0;
 }
 
-int tfc_cpm_set_usage(struct tfc_cpm *cpm, uint16_t pool_id, uint32_t 
used_count, bool all_used)
+int tfc_cpm_set_usage(struct tfc_cpm *cpm, uint16_t pool_id, uint32_t 
used_count,
+                     bool all_used, bool blk_sz_recovered)
 {
        struct cpm_pool_entry *pool;
 
@@ -396,6 +408,33 @@ int tfc_cpm_set_usage(struct tfc_cpm *cpm, uint16_t 
pool_id, uint32_t used_count
                return -EINVAL;
        }
 
+       /*
+        * Block-size-limited detection: all_used=true but records_in_use is
+        * still below pool_size means the CMM block free-list (list_0) ran
+        * dry before all records were consumed.  This happens because
+        * per-size-class block lists strand records that cannot be reclaimed
+        * for a different size class.  Mark the pool blk_sz_limited so the
+        * CPM skips it until list_0 recovers.
+        */
+       if (all_used && used_count < cpm->pool_size && !pool->blk_sz_limited)
+               pool->blk_sz_limited = true;
+
+       /*
+        * Recovery: if the caller has confirmed that list_0 has at least one
+        * free block (sufficient to serve any size class), clear the flag and
+        * let all_used be evaluated normally so the pool re-enters rotation.
+        */
+       if (pool->blk_sz_limited && blk_sz_recovered)
+               pool->blk_sz_limited = false;
+
+       /*
+        * While blk_sz_limited is set keep all_used=true so the pool stays
+        * at the tail of the sorted list and is never returned by
+        * tfc_cpm_get_avail_pool().
+        */
+       if (pool->blk_sz_limited)
+               all_used = true;
+
        pool->all_used = all_used;
        pool->used_count = used_count;
 
diff --git a/drivers/net/bnxt/tf_core/v3/tfc_cpm.h 
b/drivers/net/bnxt/tf_core/v3/tfc_cpm.h
index 9e9ab858db..3d1bd874ab 100644
--- a/drivers/net/bnxt/tf_core/v3/tfc_cpm.h
+++ b/drivers/net/bnxt/tf_core/v3/tfc_cpm.h
@@ -30,6 +30,21 @@ struct tfc_cpm;
 
 #define TFC_CPM_INVALID_POOL_ID 0xFFFF
 
+/*
+ * Minimum number of free blocks that must be present in a CMM instance's
+ * master free-block pool (list_0) before a blk_sz_limited pool is allowed to
+ * re-enter rotation.  The CMM guarantees at least 8 records per block
+ * (enforced internally via CFA_MM_MIN_RECORDS_PER_BLOCK), so this threshold
+ * ensures at least 64 usable records are available before recovery is
+ * signalled.
+ *
+ * A value of 1 is technically sufficient (one free block can serve any size
+ * class), but cleanup rollbacks from failed allocations can transiently return
+ * exactly 1 block, causing premature recovery and a tight exhaust/recover
+ * oscillation.  Using 8 prevents that transient from triggering recovery.
+ */
+#define TFC_CPM_BLK_RECOVERY_THRESHOLD 8
+
 /**
  * int tfc_cpm_open
  *
@@ -167,7 +182,12 @@ int tfc_cpm_get_avail_pool(struct tfc_cpm *cpm, uint16_t 
*pool_id);
 /**
  * int tfc_cpm_set_usage
  *
- * Set the usage_count and all_used fields for the specified pool_id
+ * Set the usage_count and all_used fields for the specified pool_id.
+ * Also handles blk_sz_limited flag set/clear logic:
+ *   - Sets blk_sz_limited when all_used=true but used_count < pool_size
+ *     (CMM block free-list exhausted before records ran out).
+ *   - Clears blk_sz_limited when blk_sz_recovered=true, allowing the pool
+ *     to re-enter the available rotation.
  *
  * @param[in] cpm
  *  Pointer to the CPM instance
@@ -181,11 +201,18 @@ int tfc_cpm_get_avail_pool(struct tfc_cpm *cpm, uint16_t 
*pool_id);
  * @param[in] all_used
  *  Set if all pool entries are used
  *
+ * @param[in] blk_sz_recovered
+ *  Pass true on free paths when cfa_mm_free_blk_count() returns a value
+ *  >= TFC_CPM_BLK_RECOVERY_THRESHOLD, indicating the CMM free-block pool
+ *  has recovered enough capacity for blk_sz_limited to be cleared.
+ *  Pass false on alloc paths and on free paths when the threshold is not met.
+ *
  * Returns:
  * 0 - Success
  * -EINVAL - Invalid argument
  */
-int tfc_cpm_set_usage(struct tfc_cpm *cpm, uint16_t pool_id, uint32_t 
used_count, bool all_used);
+int tfc_cpm_set_usage(struct tfc_cpm *cpm, uint16_t pool_id, uint32_t 
used_count,
+                     bool all_used, bool blk_sz_recovered);
 
 /**
  * int tfc_cpm_srchm_by_configured_pool
diff --git a/drivers/net/bnxt/tf_core/v3/tfc_em.c 
b/drivers/net/bnxt/tf_core/v3/tfc_em.c
index 3fe4dbe3fe..5577eb6e8d 100644
--- a/drivers/net/bnxt/tf_core/v3/tfc_em.c
+++ b/drivers/net/bnxt/tf_core/v3/tfc_em.c
@@ -108,6 +108,99 @@ static int tfc_em_insert_response(struct cfa_bld_mpcinfo 
*mpc_info,
        return rc;
 }
 
+/*
+ * Select or create a LKUP pool and return its pool_id and CMM instance.
+ *
+ * If the CPM has an available pool, its CMM is returned directly.
+ * Otherwise a new pool is allocated from the TPM, a fresh CMM is opened
+ * for it, and it is registered with the CPM before returning.
+ *
+ * Returns 0 on success, -ENOMEM if no pools remain (non-shared scope),
+ * -EINVAL on any other error.
+ */
+static int em_get_or_alloc_pool(struct tfc *tfcp,
+                               uint8_t tsid,
+                               enum cfa_dir dir,
+                               struct tfc_cpm *cpm_lkup,
+                               enum cfa_scope_type scope_type,
+                               uint16_t max_pools,
+                               struct tfc_ts_mem_cfg *mem_cfg,
+                               struct tfc_ts_pool_info *pi,
+                               uint16_t *pool_id,
+                               struct tfc_cmm **cmm)
+{
+       int rc;
+       struct cfa_mm_query_parms qparms;
+       struct cfa_mm_open_parms oparms;
+       uint16_t fid;
+
+       rc = tfc_cpm_get_avail_pool(cpm_lkup, pool_id);
+       if (!rc) {
+               rc = tfc_cpm_get_cmm_inst(cpm_lkup, *pool_id, cmm);
+               if (unlikely(rc)) {
+                       PMD_DRV_LOG_LINE(ERR, "tfc_cpm_get_cmm_inst() failed: 
%s", strerror(-rc));
+                       return -EINVAL;
+               }
+               return 0;
+       }
+
+       /* No pool available — allocate a new one from TPM */
+       if (scope_type == CFA_SCOPE_TYPE_NON_SHARED) {
+               PMD_DRV_LOG_LINE(ERR, "no records remain");
+               return -ENOMEM;
+       }
+
+       rc = tfc_get_fid(tfcp, &fid);
+       if (unlikely(rc))
+               return rc;
+
+       rc = tfc_tbl_scope_pool_alloc(tfcp, fid, tsid, CFA_REGION_TYPE_LKUP,
+                                     dir, NULL, pool_id);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "table scope pool alloc failed: %s", 
strerror(-rc));
+               return -EINVAL;
+       }
+
+       /*
+        * rec_cnt includes static buckets; subtract lkup_rec_start_offset to
+        * get the number of usable lookup records for this pool.
+        */
+       qparms.max_records = (mem_cfg->rec_cnt - 
mem_cfg->lkup_rec_start_offset) / max_pools;
+       qparms.max_contig_records = 1U << 
next_pow2((uint32_t)pi->lkup_max_contig_rec);
+       rc = cfa_mm_query(&qparms);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "cfa_mm_query() failed: %s", 
strerror(-rc));
+               return -EINVAL;
+       }
+
+       *cmm = rte_zmalloc("tf", qparms.db_size, 0);
+       if (unlikely(*cmm == NULL)) {
+               PMD_DRV_LOG_LINE(ERR, "rte_zmalloc() failed for CMM instance");
+               return -ENOMEM;
+       }
+       oparms.db_mem_size = qparms.db_size;
+       oparms.max_contig_records = qparms.max_contig_records;
+       oparms.max_records = qparms.max_records;
+       rc = cfa_mm_open(*cmm, &oparms);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "cfa_mm_open() failed: %s", 
strerror(-rc));
+               rte_free(*cmm);
+               *cmm = NULL;
+               return -EINVAL;
+       }
+
+       rc = tfc_cpm_set_cmm_inst(cpm_lkup, *pool_id, *cmm);
+       if (unlikely(rc)) {
+               PMD_DRV_LOG_LINE(ERR, "tfc_cpm_set_cmm_inst() failed: %s", 
strerror(-rc));
+               rte_free(*cmm);
+               *cmm = NULL;
+               return -EINVAL;
+       }
+
+       tfo_ts_set_pool_info(tfcp->tfo, tsid, dir, pi);
+       return 0;
+}
+
 int tfc_em_insert(struct tfc *tfcp, uint8_t tsid,
                  struct tfc_em_insert_parms *parms)
 {
@@ -189,92 +282,36 @@ int tfc_em_insert(struct tfc *tfcp, uint8_t tsid,
 
        tfo_ts_get_pool_info(tfcp->tfo, tsid, parms->dir, &pi);
 
-       rc = tfc_cpm_get_avail_pool(cpm_lkup, &pool_id);
-
-       /* if no pool available locally or all pools full */
-       if (rc) {
-               /* Allocate a pool */
-               struct cfa_mm_query_parms qparms;
-               struct cfa_mm_open_parms oparms;
-               uint16_t fid;
-
-               /* There is only 1 pool for a non-shared table scope and
-                * it is full.
-                */
-               if (scope_type == CFA_SCOPE_TYPE_NON_SHARED) {
-                       PMD_DRV_LOG_LINE(ERR, "%s: no records remain",
-                                        __func__);
-                       return -ENOMEM;
-               }
-
-               rc = tfc_get_fid(tfcp, &fid);
-               if (unlikely(rc))
-                       return rc;
-
-               rc = tfc_tbl_scope_pool_alloc(tfcp,
-                                             fid,
-                                             tsid,
-                                             CFA_REGION_TYPE_LKUP,
-                                             parms->dir,
-                                             NULL,
-                                             &pool_id);
-
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "table scope pool alloc failed: 
%s",
-                                        strerror(-rc));
-                       return -EINVAL;
-               }
+       /* Select an available pool or allocate a new one from TPM */
+       rc = em_get_or_alloc_pool(tfcp, tsid, parms->dir, cpm_lkup, scope_type,
+                                 max_pools, &mem_cfg, &pi, &pool_id, &cmm);
+       if (unlikely(rc))
+               return rc;
 
+       aparms.num_contig_records = num_contig_records;
+       rc = cfa_mm_alloc(cmm, &aparms);
+       if (unlikely(rc == -ENOMEM)) {
                /*
-                * Create pool CMM instance.
-                * rec_cnt is the total number of records which includes static 
buckets,
+                * The pool cannot serve this allocation, either its block
+                * free-list is exhausted (fragmented) or the pool is nearly
+                * full.  Mark it unavailable and rotate to a fresh pool so
+                * the caller does not see a spurious failure.
                 */
-               qparms.max_records = (mem_cfg.rec_cnt - 
mem_cfg.lkup_rec_start_offset) / max_pools;
-               qparms.max_contig_records = pi.lkup_max_contig_rec;
-               rc = cfa_mm_query(&qparms);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "cfa_mm_query() failed: %s", 
strerror(-rc));
-                       rte_free(cmm);
-                       return -EINVAL;
-               }
-
-               cmm = rte_zmalloc("tf", qparms.db_size, 0);
-               oparms.db_mem_size = qparms.db_size;
-               oparms.max_contig_records = qparms.max_contig_records;
-               oparms.max_records = qparms.max_records;
-               rc = cfa_mm_open(cmm, &oparms);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "cfa_mm_open() failed: %s", 
strerror(-rc));
-                       rte_free(cmm);
-                       return -EINVAL;
-               }
-
-               /* Store CMM instance in the CPM */
-               rc = tfc_cpm_set_cmm_inst(cpm_lkup, pool_id, cmm);
-               if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "tfc_cpm_set_cmm_inst() failed: 
%s",
-                                        strerror(-rc));
-                       return -EINVAL;
-               }
+               tfc_cpm_set_usage(cpm_lkup, pool_id, aparms.used_count, true, 
false);
 
-               /* Store the updated pool information */
-               tfo_ts_set_pool_info(tfcp->tfo, tsid, parms->dir, &pi);
-
-       } else {
-               /* Get the pool instance and allocate an lkup rec index from 
the pool */
-               rc = tfc_cpm_get_cmm_inst(cpm_lkup, pool_id, &cmm);
+               rc = em_get_or_alloc_pool(tfcp, tsid, parms->dir, cpm_lkup,
+                                         scope_type, max_pools, &mem_cfg,
+                                         &pi, &pool_id, &cmm);
                if (unlikely(rc)) {
-                       PMD_DRV_LOG_LINE(ERR, "tfc_cpm_get_cmm_inst() failed: 
%s",
+                       PMD_DRV_LOG_LINE(ERR, "no pool available after 
rotation: %s",
                                         strerror(-rc));
-                       return -EINVAL;
+                       return rc;
                }
+               rc = cfa_mm_alloc(cmm, &aparms);
        }
-
-       aparms.num_contig_records = num_contig_records;
-       rc = cfa_mm_alloc(cmm, &aparms);
        if (unlikely(rc)) {
                PMD_DRV_LOG_LINE(ERR, "cfa_mm_alloc() failed: %s", 
strerror(-rc));
-               return -EINVAL;
+               return rc;
        }
 
 #if TFC_EM_DYNAMIC_BUCKET_EN
@@ -381,8 +418,11 @@ int tfc_em_insert(struct tfc *tfcp, uint8_t tsid,
                                                     entry_offset,
                                                     hash);
 
-       /* Update CPM info so it will determine best pool to use next alloc */
-       rc = tfc_cpm_set_usage(cpm_lkup, pool_id, aparms.used_count, 
aparms.all_used);
+       /* Update CPM info so it will determine best pool to use next alloc.
+        * Recovery (blk_sz_recovered) is not possible on the alloc path —
+        * the free-block pool only replenishes when records are freed.
+        */
+       rc = tfc_cpm_set_usage(cpm_lkup, pool_id, aparms.used_count, 
aparms.all_used, false);
        if (unlikely(rc)) {
                PMD_DRV_LOG_LINE(ERR,
                                 "EM insert tfc_cpm_set_usage() failed: %d",
@@ -416,7 +456,10 @@ int tfc_em_insert(struct tfc *tfcp, uint8_t tsid,
        if (cleanup_rc != 0)
                PMD_DRV_LOG_LINE(ERR, "failed to free entry: %s", 
strerror(-rc));
 
-       cleanup_rc = tfc_cpm_set_usage(cpm_lkup, pool_id, fparms.used_count, 
false);
+       cleanup_rc = tfc_cpm_set_usage(cpm_lkup, pool_id, fparms.used_count,
+                                      false,
+                                      cfa_mm_free_blk_count(cmm) >=
+                                      TFC_CPM_BLK_RECOVERY_THRESHOLD);
        if (cleanup_rc != 0)
                PMD_DRV_LOG_LINE(ERR, "failed to set usage: %s", strerror(-rc));
 
@@ -692,10 +735,10 @@ int tfc_em_delete(struct tfc *tfcp, struct 
tfc_em_delete_parms *parms)
                return -EINVAL;
        }
 
-       rc = tfc_cpm_set_usage(cpm_lkup, pool_id, fparms.used_count, false);
+       rc = tfc_cpm_set_usage(cpm_lkup, pool_id, fparms.used_count, false,
+                              cfa_mm_free_blk_count(cmm) >= 
TFC_CPM_BLK_RECOVERY_THRESHOLD);
        if (rc != 0)
-               PMD_DRV_LOG_LINE(ERR, "failed to set usage: %s",
-                                strerror(-rc));
+               PMD_DRV_LOG_LINE(ERR, "failed to set usage: %s", strerror(-rc));
 
        return rc;
 }
-- 
2.31.1

Reply via email to