[RFC 3/5] fib: add shared tbl8 pool

Maxime Leroy maxime at leroys.fr
Tue Mar 31 23:41:15 CEST 2026


Replace the per-FIB tbl8 allocation with a common refcounted tbl8
pool (fib_tbl8_pool).

A FIB can either use an internal pool (created transparently from the
existing num_tbl8 config parameter) or attach to an external shared
pool via the new tbl8_pool config field. The shared pool allows
multiple FIB instances (e.g. one per VRF) to draw tbl8 groups from
the same memory, reducing overall allocation.

The pool is refcounted: internal pools start at refcount 1 and are
freed when the owning FIB is destroyed. External pools are
incremented on FIB attach and decremented on FIB detach; the creator
releases its reference via rte_fib_tbl8_pool_free().

The per-FIB RCU defer queue callback is shared across both backends
(fib_tbl8_pool_rcu_free_cb).

New public API:
  - rte_fib_tbl8_pool_create()
  - rte_fib_tbl8_pool_free()

Signed-off-by: Maxime Leroy <maxime at leroys.fr>
---
 lib/fib/dir24_8.c           | 128 ++++++++++---------------------
 lib/fib/dir24_8.h           |   6 +-
 lib/fib/fib_tbl8_pool.c     | 148 ++++++++++++++++++++++++++++++++++++
 lib/fib/fib_tbl8_pool.h     |  74 ++++++++++++++++++
 lib/fib/meson.build         |   5 +-
 lib/fib/rte_fib.h           |   3 +
 lib/fib/rte_fib6.h          |   3 +
 lib/fib/rte_fib_tbl8_pool.h |  76 ++++++++++++++++++
 lib/fib/trie.c              | 134 ++++++++++----------------------
 lib/fib/trie.h              |   6 +-
 10 files changed, 394 insertions(+), 189 deletions(-)
 create mode 100644 lib/fib/fib_tbl8_pool.c
 create mode 100644 lib/fib/fib_tbl8_pool.h
 create mode 100644 lib/fib/rte_fib_tbl8_pool.h

diff --git a/lib/fib/dir24_8.c b/lib/fib/dir24_8.c
index 935eca12c3..b8e588a56a 100644
--- a/lib/fib/dir24_8.c
+++ b/lib/fib/dir24_8.c
@@ -152,41 +152,18 @@ dir24_8_get_lookup_fn(void *p, enum rte_fib_lookup_type type, bool be_addr)
 	return NULL;
 }
 
-/*
- * Get an index of a free tbl8 from the pool
- */
-static inline int32_t
-tbl8_get(struct dir24_8_tbl *dp)
-{
-	if (dp->tbl8_pool_pos == dp->number_tbl8s)
-		/* no more free tbl8 */
-		return -ENOSPC;
-
-	/* next index */
-	return dp->tbl8_pool[dp->tbl8_pool_pos++];
-}
-
-/*
- * Put an index of a free tbl8 back to the pool
- */
-static inline void
-tbl8_put(struct dir24_8_tbl *dp, uint32_t tbl8_ind)
-{
-	dp->tbl8_pool[--dp->tbl8_pool_pos] = tbl8_ind;
-}
-
 static int
 tbl8_alloc(struct dir24_8_tbl *dp, uint64_t nh)
 {
 	int64_t	tbl8_idx;
 	uint8_t	*tbl8_ptr;
 
-	tbl8_idx = tbl8_get(dp);
+	tbl8_idx = fib_tbl8_pool_get(dp->pool);
 
 	/* If there are no tbl8 groups try to reclaim one. */
 	if (unlikely(tbl8_idx == -ENOSPC && dp->dq &&
 			!rte_rcu_qsbr_dq_reclaim(dp->dq, 1, NULL, NULL, NULL)))
-		tbl8_idx = tbl8_get(dp);
+		tbl8_idx = fib_tbl8_pool_get(dp->pool);
 
 	if (tbl8_idx < 0)
 		return tbl8_idx;
@@ -200,24 +177,6 @@ tbl8_alloc(struct dir24_8_tbl *dp, uint64_t nh)
 	return tbl8_idx;
 }
 
-static void
-tbl8_cleanup_and_free(struct dir24_8_tbl *dp, uint64_t tbl8_idx)
-{
-	uint8_t *ptr = (uint8_t *)dp->tbl8 + (tbl8_idx * FIB_TBL8_GRP_NUM_ENT << dp->nh_sz);
-
-	memset(ptr, 0, FIB_TBL8_GRP_NUM_ENT << dp->nh_sz);
-	tbl8_put(dp, tbl8_idx);
-}
-
-static void
-__rcu_qsbr_free_resource(void *p, void *data, unsigned int n __rte_unused)
-{
-	struct dir24_8_tbl *dp = p;
-	uint64_t tbl8_idx = *(uint64_t *)data;
-
-	tbl8_cleanup_and_free(dp, tbl8_idx);
-}
-
 static void
 tbl8_recycle(struct dir24_8_tbl *dp, uint32_t ip, uint64_t tbl8_idx)
 {
@@ -276,10 +235,10 @@ tbl8_recycle(struct dir24_8_tbl *dp, uint32_t ip, uint64_t tbl8_idx)
 	}
 
 	if (dp->v == NULL) {
-		tbl8_cleanup_and_free(dp, tbl8_idx);
+		fib_tbl8_pool_cleanup_and_free(dp->pool, tbl8_idx);
 	} else if (dp->rcu_mode == RTE_FIB_QSBR_MODE_SYNC) {
 		rte_rcu_qsbr_synchronize(dp->v, RTE_QSBR_THRID_INVALID);
-		tbl8_cleanup_and_free(dp, tbl8_idx);
+		fib_tbl8_pool_cleanup_and_free(dp->pool, tbl8_idx);
 	} else { /* RTE_FIB_QSBR_MODE_DQ */
 		if (rte_rcu_qsbr_dq_enqueue(dp->dq, &tbl8_idx))
 			FIB_LOG(ERR, "Failed to push QSBR FIFO");
@@ -310,14 +269,14 @@ install_to_fib(struct dir24_8_tbl *dp, uint32_t ledge, uint32_t redge,
 				 * needs tbl8 for ledge and redge.
 				 */
 				tbl8_idx = tbl8_alloc(dp, tbl24_tmp);
-				tmp_tbl8_idx = tbl8_get(dp);
+				tmp_tbl8_idx = fib_tbl8_pool_get(dp->pool);
 				if (tbl8_idx < 0)
 					return -ENOSPC;
 				else if (tmp_tbl8_idx < 0) {
-					tbl8_put(dp, tbl8_idx);
+					fib_tbl8_pool_cleanup_and_free(dp->pool, tbl8_idx);
 					return -ENOSPC;
 				}
-				tbl8_put(dp, tmp_tbl8_idx);
+				fib_tbl8_pool_put(dp->pool, tmp_tbl8_idx);
 				/*update dir24 entry with tbl8 index*/
 				fib_tbl8_write(get_tbl24_p(dp, ledge,
 					dp->nh_sz), (tbl8_idx << 1)|
@@ -477,7 +436,7 @@ dir24_8_modify(struct rte_fib *fib, uint32_t ip, uint8_t depth,
 			tmp = rte_rib_get_nxt(rib, ip, 24, NULL,
 				RTE_RIB_GET_NXT_COVER);
 			if ((tmp == NULL) &&
-				(dp->rsvd_tbl8s >= dp->number_tbl8s))
+				(dp->rsvd_tbl8s >= dp->pool->num_tbl8s))
 				return -ENOSPC;
 
 		}
@@ -533,18 +492,13 @@ dir24_8_create(const char *name, int socket_id, struct rte_fib_conf *fib_conf)
 {
 	char mem_name[DIR24_8_NAMESIZE];
 	struct dir24_8_tbl *dp;
+	struct rte_fib_tbl8_pool *pool;
 	uint64_t	def_nh;
-	uint64_t	tbl8_sz;
-	uint32_t	num_tbl8;
-	uint32_t	i;
 	enum rte_fib_dir24_8_nh_sz	nh_sz;
 
 	if ((name == NULL) || (fib_conf == NULL) ||
 			(fib_conf->dir24_8.nh_sz < RTE_FIB_DIR24_8_1B) ||
 			(fib_conf->dir24_8.nh_sz > RTE_FIB_DIR24_8_8B) ||
-			(fib_conf->dir24_8.num_tbl8 >
-			get_max_nh(fib_conf->dir24_8.nh_sz)) ||
-			(fib_conf->dir24_8.num_tbl8 == 0) ||
 			(fib_conf->default_nh >
 			get_max_nh(fib_conf->dir24_8.nh_sz))) {
 		rte_errno = EINVAL;
@@ -553,46 +507,47 @@ dir24_8_create(const char *name, int socket_id, struct rte_fib_conf *fib_conf)
 
 	def_nh = fib_conf->default_nh;
 	nh_sz = fib_conf->dir24_8.nh_sz;
-	num_tbl8 = fib_conf->dir24_8.num_tbl8;
+
+	if (fib_conf->dir24_8.tbl8_pool != NULL) {
+		/* External shared pool */
+		pool = fib_conf->dir24_8.tbl8_pool;
+		if (pool->nh_sz != nh_sz) {
+			rte_errno = EINVAL;
+			return NULL;
+		}
+		fib_tbl8_pool_ref(pool);
+	} else {
+		/* Internal pool */
+		if ((fib_conf->dir24_8.num_tbl8 >
+		     get_max_nh(fib_conf->dir24_8.nh_sz)) ||
+		    (fib_conf->dir24_8.num_tbl8 == 0)) {
+			rte_errno = EINVAL;
+			return NULL;
+		}
+		struct rte_fib_tbl8_pool_conf pool_conf = {
+			.num_tbl8 = fib_conf->dir24_8.num_tbl8,
+			.nh_sz = nh_sz,
+			.socket_id = socket_id,
+		};
+		pool = rte_fib_tbl8_pool_create(name, &pool_conf);
+		if (pool == NULL)
+			return NULL;
+	}
 
 	snprintf(mem_name, sizeof(mem_name), "DP_%s", name);
 	dp = rte_zmalloc_socket(name, sizeof(struct dir24_8_tbl) +
 		DIR24_8_TBL24_NUM_ENT * (1 << nh_sz) + sizeof(uint32_t),
 		RTE_CACHE_LINE_SIZE, socket_id);
 	if (dp == NULL) {
+		fib_tbl8_pool_unref(pool);
 		rte_errno = ENOMEM;
 		return NULL;
 	}
 
-	snprintf(mem_name, sizeof(mem_name), "TBL8_%p", dp);
-	tbl8_sz = FIB_TBL8_GRP_NUM_ENT * (1ULL << nh_sz) *
-			(num_tbl8 + 1);
-	dp->tbl8 = rte_zmalloc_socket(mem_name, tbl8_sz,
-			RTE_CACHE_LINE_SIZE, socket_id);
-	if (dp->tbl8 == NULL) {
-		rte_errno = ENOMEM;
-		rte_free(dp);
-		return NULL;
-	}
+	dp->pool = pool;
+	dp->tbl8 = pool->tbl8;
 	dp->def_nh = def_nh;
 	dp->nh_sz = nh_sz;
-	dp->number_tbl8s = num_tbl8;
-
-	snprintf(mem_name, sizeof(mem_name), "TBL8_idxes_%p", dp);
-	dp->tbl8_pool = rte_zmalloc_socket(mem_name,
-			sizeof(uint32_t) * dp->number_tbl8s,
-			RTE_CACHE_LINE_SIZE, socket_id);
-	if (dp->tbl8_pool == NULL) {
-		rte_errno = ENOMEM;
-		rte_free(dp->tbl8);
-		rte_free(dp);
-		return NULL;
-	}
-
-	/* Init pool with all tbl8 indices free */
-	for (i = 0; i < dp->number_tbl8s; i++)
-		dp->tbl8_pool[i] = i;
-	dp->tbl8_pool_pos = 0;
 
 	/* Init table with default value */
 	fib_tbl8_write(dp->tbl24, (def_nh << 1), nh_sz, 1 << 24);
@@ -606,8 +561,7 @@ dir24_8_free(void *p)
 	struct dir24_8_tbl *dp = (struct dir24_8_tbl *)p;
 
 	rte_rcu_qsbr_dq_delete(dp->dq);
-	rte_free(dp->tbl8_pool);
-	rte_free(dp->tbl8);
+	fib_tbl8_pool_unref(dp->pool);
 	rte_free(dp);
 }
 
@@ -639,8 +593,8 @@ dir24_8_rcu_qsbr_add(struct dir24_8_tbl *dp, struct rte_fib_rcu_config *cfg,
 		if (params.max_reclaim_size == 0)
 			params.max_reclaim_size = RTE_FIB_RCU_DQ_RECLAIM_MAX;
 		params.esize = sizeof(uint64_t);
-		params.free_fn = __rcu_qsbr_free_resource;
-		params.p = dp;
+		params.free_fn = fib_tbl8_pool_rcu_free_cb;
+		params.p = dp->pool;
 		params.v = cfg->v;
 		dp->dq = rte_rcu_qsbr_dq_create(&params);
 		if (dp->dq == NULL) {
diff --git a/lib/fib/dir24_8.h b/lib/fib/dir24_8.h
index e75bd120ad..287b91ef4b 100644
--- a/lib/fib/dir24_8.h
+++ b/lib/fib/dir24_8.h
@@ -14,7 +14,7 @@
 #include <rte_branch_prediction.h>
 #include <rte_rcu_qsbr.h>
 
-#include "fib_tbl8.h"
+#include "fib_tbl8_pool.h"
 
 /**
  * @file
@@ -26,9 +26,7 @@
 #define DIR24_8_TBL24_MASK		0xffffff00
 
 struct dir24_8_tbl {
-	uint32_t	number_tbl8s;	/**< Total number of tbl8s */
 	uint32_t	rsvd_tbl8s;	/**< Number of reserved tbl8s */
-	uint32_t	tbl8_pool_pos;	/**< Next free index in pool */
 	enum rte_fib_dir24_8_nh_sz	nh_sz;	/**< Size of nexthop entry */
 	/* RCU config. */
 	enum rte_fib_qsbr_mode rcu_mode;/* Blocking, defer queue. */
@@ -36,7 +34,7 @@ struct dir24_8_tbl {
 	struct rte_rcu_qsbr_dq *dq;	/* RCU QSBR defer queue. */
 	uint64_t	def_nh;		/**< Default next hop */
 	uint64_t	*tbl8;		/**< tbl8 table. */
-	uint32_t	*tbl8_pool;	/**< Stack of free tbl8 indices */
+	struct rte_fib_tbl8_pool *pool;	/**< tbl8 pool */
 	/* tbl24 table. */
 	alignas(RTE_CACHE_LINE_SIZE) uint64_t	tbl24[];
 };
diff --git a/lib/fib/fib_tbl8_pool.c b/lib/fib/fib_tbl8_pool.c
new file mode 100644
index 0000000000..5f8ba74219
--- /dev/null
+++ b/lib/fib/fib_tbl8_pool.c
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Maxime Leroy, Free Mobile
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <eal_export.h>
+#include <rte_debug.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+
+#include "fib_tbl8_pool.h"
+
+static void
+pool_init_free_list(struct rte_fib_tbl8_pool *pool)
+{
+	uint32_t i;
+
+	/* put entire range of indexes to the tbl8 pool */
+	for (i = 0; i < pool->num_tbl8s; i++)
+		pool->free_list[i] = i;
+
+	pool->cur_tbl8s = 0;
+}
+
+int32_t
+fib_tbl8_pool_get(struct rte_fib_tbl8_pool *pool)
+{
+	if (pool->cur_tbl8s == pool->num_tbl8s)
+		/* no more free tbl8 */
+		return -ENOSPC;
+
+	/* next index */
+	return pool->free_list[pool->cur_tbl8s++];
+}
+
+void
+fib_tbl8_pool_put(struct rte_fib_tbl8_pool *pool, uint32_t idx)
+{
+	RTE_ASSERT(pool->cur_tbl8s > 0);
+	pool->free_list[--pool->cur_tbl8s] = idx;
+}
+
+void
+fib_tbl8_pool_cleanup_and_free(struct rte_fib_tbl8_pool *pool, uint64_t idx)
+{
+	uint8_t *ptr = (uint8_t *)pool->tbl8 +
+		((idx * FIB_TBL8_GRP_NUM_ENT) << pool->nh_sz);
+
+	memset(ptr, 0, FIB_TBL8_GRP_NUM_ENT << pool->nh_sz);
+	fib_tbl8_pool_put(pool, idx);
+}
+
+void
+fib_tbl8_pool_rcu_free_cb(void *p, void *data,
+			  unsigned int n __rte_unused)
+{
+	struct rte_fib_tbl8_pool *pool = p;
+	uint64_t tbl8_idx = *(uint64_t *)data;
+
+	fib_tbl8_pool_cleanup_and_free(pool, tbl8_idx);
+}
+
+void
+fib_tbl8_pool_ref(struct rte_fib_tbl8_pool *pool)
+{
+	pool->refcnt++;
+}
+
+static void
+pool_free(struct rte_fib_tbl8_pool *pool)
+{
+	rte_free(pool->free_list);
+	rte_free(pool->tbl8);
+	rte_free(pool);
+}
+
+void
+fib_tbl8_pool_unref(struct rte_fib_tbl8_pool *pool)
+{
+	if (--pool->refcnt == 0)
+		pool_free(pool);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fib_tbl8_pool_create, 26.07)
+struct rte_fib_tbl8_pool *
+rte_fib_tbl8_pool_create(const char *name,
+			 const struct rte_fib_tbl8_pool_conf *conf)
+{
+	struct rte_fib_tbl8_pool *pool;
+	char mem_name[64];
+
+	if (name == NULL || conf == NULL || conf->num_tbl8 == 0 ||
+	    conf->nh_sz > 3) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	snprintf(mem_name, sizeof(mem_name), "TBL8_POOL_%s", name);
+	pool = rte_zmalloc_socket(mem_name, sizeof(*pool),
+		RTE_CACHE_LINE_SIZE, conf->socket_id);
+	if (pool == NULL) {
+		rte_errno = ENOMEM;
+		return NULL;
+	}
+
+	pool->nh_sz = conf->nh_sz;
+	pool->num_tbl8s = conf->num_tbl8;
+	pool->socket_id = conf->socket_id;
+	pool->refcnt = 1;
+
+	snprintf(mem_name, sizeof(mem_name), "TBL8_%s", name);
+	pool->tbl8 = rte_zmalloc_socket(mem_name,
+		FIB_TBL8_GRP_NUM_ENT * (1ULL << pool->nh_sz) *
+		(pool->num_tbl8s + 1),
+		RTE_CACHE_LINE_SIZE, conf->socket_id);
+	if (pool->tbl8 == NULL) {
+		rte_errno = ENOMEM;
+		rte_free(pool);
+		return NULL;
+	}
+
+	snprintf(mem_name, sizeof(mem_name), "TBL8_FL_%s", name);
+	pool->free_list = rte_zmalloc_socket(mem_name,
+		sizeof(uint32_t) * pool->num_tbl8s,
+		RTE_CACHE_LINE_SIZE, conf->socket_id);
+	if (pool->free_list == NULL) {
+		rte_errno = ENOMEM;
+		rte_free(pool->tbl8);
+		rte_free(pool);
+		return NULL;
+	}
+
+	pool_init_free_list(pool);
+
+	return pool;
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_fib_tbl8_pool_free, 26.07)
+void
+rte_fib_tbl8_pool_free(struct rte_fib_tbl8_pool *pool)
+{
+	if (pool == NULL)
+		return;
+
+	fib_tbl8_pool_unref(pool);
+}
diff --git a/lib/fib/fib_tbl8_pool.h b/lib/fib/fib_tbl8_pool.h
new file mode 100644
index 0000000000..285f06d87f
--- /dev/null
+++ b/lib/fib/fib_tbl8_pool.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Maxime Leroy, Free Mobile
+ */
+
+#ifndef _FIB_TBL8_POOL_H_
+#define _FIB_TBL8_POOL_H_
+
+/**
+ * @file
+ * Internal tbl8 pool header.
+ *
+ * The pool is not thread-safe. When multiple FIBs share a pool,
+ * all operations (route modifications, FIB creation/destruction)
+ * must be serialized by the caller.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <rte_common.h>
+
+#include "fib_tbl8.h"
+#include "rte_fib_tbl8_pool.h"
+
+struct rte_fib_tbl8_pool {
+	uint64_t	*tbl8;		/**< tbl8 group array */
+	uint32_t	*free_list;	/**< Stack of free group indices */
+	uint32_t	cur_tbl8s;	/**< Number of allocated groups */
+	uint32_t	num_tbl8s;	/**< Total number of tbl8 groups */
+	uint8_t		nh_sz;		/**< Nexthop entry size (0-3) */
+	int		socket_id;
+	uint32_t	refcnt;		/**< Reference count */
+};
+
+/**
+ * Get a free tbl8 group index from the pool.
+ * @return index on success, -ENOSPC if pool is full
+ */
+int32_t
+fib_tbl8_pool_get(struct rte_fib_tbl8_pool *pool);
+
+/**
+ * Return a tbl8 group index to the pool.
+ */
+void
+fib_tbl8_pool_put(struct rte_fib_tbl8_pool *pool, uint32_t idx);
+
+/**
+ * Clear a tbl8 group and return its index to the pool.
+ */
+void
+fib_tbl8_pool_cleanup_and_free(struct rte_fib_tbl8_pool *pool, uint64_t idx);
+
+/**
+ * RCU defer queue callback for tbl8 group reclamation.
+ * Shared by dir24_8 and trie backends.
+ * Use as params.free_fn with params.p = pool.
+ */
+void
+fib_tbl8_pool_rcu_free_cb(void *p, void *data, unsigned int n);
+
+/**
+ * Increment pool reference count.
+ */
+void
+fib_tbl8_pool_ref(struct rte_fib_tbl8_pool *pool);
+
+/**
+ * Decrement pool reference count. Free the pool if it reaches 0.
+ */
+void
+fib_tbl8_pool_unref(struct rte_fib_tbl8_pool *pool);
+
+#endif /* _FIB_TBL8_POOL_H_ */
diff --git a/lib/fib/meson.build b/lib/fib/meson.build
index 573fc50ff1..6ecd954b26 100644
--- a/lib/fib/meson.build
+++ b/lib/fib/meson.build
@@ -2,8 +2,9 @@
 # Copyright(c) 2018 Vladimir Medvedkin <medvedkinv at gmail.com>
 # Copyright(c) 2019 Intel Corporation
 
-sources = files('rte_fib.c', 'rte_fib6.c', 'dir24_8.c', 'trie.c')
-headers = files('rte_fib.h', 'rte_fib6.h')
+sources = files('rte_fib.c', 'rte_fib6.c', 'dir24_8.c', 'trie.c',
+	'fib_tbl8_pool.c')
+headers = files('rte_fib.h', 'rte_fib6.h', 'rte_fib_tbl8_pool.h')
 deps += ['rib']
 deps += ['rcu']
 deps += ['net']
diff --git a/lib/fib/rte_fib.h b/lib/fib/rte_fib.h
index b16a653535..b8c86566ad 100644
--- a/lib/fib/rte_fib.h
+++ b/lib/fib/rte_fib.h
@@ -19,6 +19,7 @@
 
 #include <rte_common.h>
 #include <rte_rcu_qsbr.h>
+#include <rte_fib_tbl8_pool.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -107,6 +108,8 @@ struct rte_fib_conf {
 		struct {
 			enum rte_fib_dir24_8_nh_sz nh_sz;
 			uint32_t	num_tbl8;
+			/** Shared tbl8 pool (NULL = internal pool) */
+			struct rte_fib_tbl8_pool *tbl8_pool;
 		} dir24_8;
 	};
 	unsigned int flags; /**< Optional feature flags from RTE_FIB_F_* */
diff --git a/lib/fib/rte_fib6.h b/lib/fib/rte_fib6.h
index 4527328bf0..655a4c9501 100644
--- a/lib/fib/rte_fib6.h
+++ b/lib/fib/rte_fib6.h
@@ -20,6 +20,7 @@
 #include <rte_common.h>
 #include <rte_ip6.h>
 #include <rte_rcu_qsbr.h>
+#include <rte_fib_tbl8_pool.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -95,6 +96,8 @@ struct rte_fib6_conf {
 		struct {
 			enum rte_fib_trie_nh_sz nh_sz;
 			uint32_t	num_tbl8;
+			/** Shared tbl8 pool (NULL = internal pool) */
+			struct rte_fib_tbl8_pool *tbl8_pool;
 		} trie;
 	};
 };
diff --git a/lib/fib/rte_fib_tbl8_pool.h b/lib/fib/rte_fib_tbl8_pool.h
new file mode 100644
index 0000000000..e362efe74b
--- /dev/null
+++ b/lib/fib/rte_fib_tbl8_pool.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Maxime Leroy, Free Mobile
+ */
+
+#ifndef _RTE_FIB_TBL8_POOL_H_
+#define _RTE_FIB_TBL8_POOL_H_
+
+/**
+ * @file
+ * Shared tbl8 pool for FIB backends.
+ *
+ * A tbl8 pool manages a shared array of tbl8 groups that can be used
+ * across multiple FIB instances (e.g., one per VRF).
+ *
+ * Two modes of operation:
+ *  - Internal pool: set num_tbl8 in the FIB config and leave tbl8_pool
+ *    NULL. The pool is created and destroyed with the FIB.
+ *  - External shared pool: create with rte_fib_tbl8_pool_create(), pass
+ *    the handle via the tbl8_pool config field. Each FIB holds a
+ *    reference; the creator releases its reference with
+ *    rte_fib_tbl8_pool_free(). The pool is freed when the last
+ *    reference is dropped.
+ *
+ * Thread safety: none. The pool is not thread-safe. All operations
+ * on FIBs sharing the same pool (route updates, FIB creation and
+ * destruction, pool create/free) must be serialized by the caller.
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rte_fib_tbl8_pool;
+
+/** tbl8 pool configuration */
+struct rte_fib_tbl8_pool_conf {
+	uint32_t num_tbl8;	/**< Number of tbl8 groups */
+	uint8_t  nh_sz;		/**< Nexthop size: 0=1B, 1=2B, 2=4B, 3=8B */
+	int      socket_id;	/**< NUMA socket for memory allocation */
+};
+
+/**
+ * Create a tbl8 pool.
+ *
+ * @param name
+ *   Pool name (for memory allocation tracking)
+ * @param conf
+ *   Pool configuration
+ * @return
+ *   Pool handle on success, NULL on failure with rte_errno set
+ */
+__rte_experimental
+struct rte_fib_tbl8_pool *
+rte_fib_tbl8_pool_create(const char *name,
+			 const struct rte_fib_tbl8_pool_conf *conf);
+
+/**
+ * Release the creator's reference on a tbl8 pool.
+ *
+ * The pool is freed when the last reference is dropped (i.e. after
+ * all FIBs using this pool have been destroyed).
+ *
+ * @param pool
+ *   Pool handle (NULL is allowed)
+ */
+__rte_experimental
+void
+rte_fib_tbl8_pool_free(struct rte_fib_tbl8_pool *pool);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_FIB_TBL8_POOL_H_ */
diff --git a/lib/fib/trie.c b/lib/fib/trie.c
index 198fc54395..798d322b1e 100644
--- a/lib/fib/trie.c
+++ b/lib/fib/trie.c
@@ -99,53 +99,18 @@ trie_get_lookup_fn(void *p, enum rte_fib6_lookup_type type)
 	return NULL;
 }
 
-static void
-tbl8_pool_init(struct rte_trie_tbl *dp)
-{
-	uint32_t i;
-
-	/* put entire range of indexes to the tbl8 pool */
-	for (i = 0; i < dp->number_tbl8s; i++)
-		dp->tbl8_pool[i] = i;
-
-	dp->tbl8_pool_pos = 0;
-}
-
-/*
- * Get an index of a free tbl8 from the pool
- */
-static inline int32_t
-tbl8_get(struct rte_trie_tbl *dp)
-{
-	if (dp->tbl8_pool_pos == dp->number_tbl8s)
-		/* no more free tbl8 */
-		return -ENOSPC;
-
-	/* next index */
-	return dp->tbl8_pool[dp->tbl8_pool_pos++];
-}
-
-/*
- * Put an index of a free tbl8 back to the pool
- */
-static inline void
-tbl8_put(struct rte_trie_tbl *dp, uint32_t tbl8_ind)
-{
-	dp->tbl8_pool[--dp->tbl8_pool_pos] = tbl8_ind;
-}
-
 static int
 tbl8_alloc(struct rte_trie_tbl *dp, uint64_t nh)
 {
 	int64_t		tbl8_idx;
 	uint8_t		*tbl8_ptr;
 
-	tbl8_idx = tbl8_get(dp);
+	tbl8_idx = fib_tbl8_pool_get(dp->pool);
 
 	/* If there are no tbl8 groups try to reclaim one. */
 	if (unlikely(tbl8_idx == -ENOSPC && dp->dq &&
 			!rte_rcu_qsbr_dq_reclaim(dp->dq, 1, NULL, NULL, NULL)))
-		tbl8_idx = tbl8_get(dp);
+		tbl8_idx = fib_tbl8_pool_get(dp->pool);
 
 	if (tbl8_idx < 0)
 		return tbl8_idx;
@@ -157,23 +122,6 @@ tbl8_alloc(struct rte_trie_tbl *dp, uint64_t nh)
 	return tbl8_idx;
 }
 
-static void
-tbl8_cleanup_and_free(struct rte_trie_tbl *dp, uint64_t tbl8_idx)
-{
-	uint8_t *ptr = (uint8_t *)dp->tbl8 + (tbl8_idx * FIB_TBL8_GRP_NUM_ENT << dp->nh_sz);
-
-	memset(ptr, 0, FIB_TBL8_GRP_NUM_ENT << dp->nh_sz);
-	tbl8_put(dp, tbl8_idx);
-}
-
-static void
-__rcu_qsbr_free_resource(void *p, void *data, unsigned int n __rte_unused)
-{
-	struct rte_trie_tbl *dp = p;
-	uint64_t tbl8_idx = *(uint64_t *)data;
-	tbl8_cleanup_and_free(dp, tbl8_idx);
-}
-
 static void
 tbl8_recycle(struct rte_trie_tbl *dp, void *par, uint64_t tbl8_idx)
 {
@@ -223,10 +171,10 @@ tbl8_recycle(struct rte_trie_tbl *dp, void *par, uint64_t tbl8_idx)
 	}
 
 	if (dp->v == NULL) {
-		tbl8_cleanup_and_free(dp, tbl8_idx);
+		fib_tbl8_pool_cleanup_and_free(dp->pool, tbl8_idx);
 	} else if (dp->rcu_mode == RTE_FIB6_QSBR_MODE_SYNC) {
 		rte_rcu_qsbr_synchronize(dp->v, RTE_QSBR_THRID_INVALID);
-		tbl8_cleanup_and_free(dp, tbl8_idx);
+		fib_tbl8_pool_cleanup_and_free(dp->pool, tbl8_idx);
 	} else { /* RTE_FIB6_QSBR_MODE_DQ */
 		if (rte_rcu_qsbr_dq_enqueue(dp->dq, &tbl8_idx))
 			FIB_LOG(ERR, "Failed to push QSBR FIFO");
@@ -583,7 +531,7 @@ trie_modify(struct rte_fib6 *fib, const struct rte_ipv6_addr *ip,
 			return 0;
 		}
 
-		if ((depth > 24) && (dp->rsvd_tbl8s + depth_diff > dp->number_tbl8s))
+		if ((depth > 24) && (dp->rsvd_tbl8s + depth_diff > dp->pool->num_tbl8s))
 			return -ENOSPC;
 
 		node = rte_rib6_insert(rib, &ip_masked, depth);
@@ -636,63 +584,66 @@ trie_create(const char *name, int socket_id,
 {
 	char mem_name[TRIE_NAMESIZE];
 	struct rte_trie_tbl *dp = NULL;
+	struct rte_fib_tbl8_pool *pool;
 	uint64_t	def_nh;
-	uint32_t	num_tbl8;
 	enum rte_fib_trie_nh_sz	nh_sz;
 
 	if ((name == NULL) || (conf == NULL) ||
 			(conf->trie.nh_sz < RTE_FIB6_TRIE_2B) ||
 			(conf->trie.nh_sz > RTE_FIB6_TRIE_8B) ||
-			(conf->trie.num_tbl8 >
-			get_max_nh(conf->trie.nh_sz)) ||
-			(conf->trie.num_tbl8 == 0) ||
 			(conf->default_nh >
 			get_max_nh(conf->trie.nh_sz))) {
-
 		rte_errno = EINVAL;
 		return NULL;
 	}
 
 	def_nh = conf->default_nh;
 	nh_sz = conf->trie.nh_sz;
-	num_tbl8 = conf->trie.num_tbl8;
+
+	if (conf->trie.tbl8_pool != NULL) {
+		/* External shared pool: validate nh_sz matches. */
+		pool = conf->trie.tbl8_pool;
+		if (pool->nh_sz != nh_sz) {
+			rte_errno = EINVAL;
+			return NULL;
+		}
+		fib_tbl8_pool_ref(pool);
+	} else {
+		/* Internal pool: create from config. */
+		struct rte_fib_tbl8_pool_conf pool_conf = {
+			.num_tbl8 = conf->trie.num_tbl8,
+			.nh_sz = nh_sz,
+			.socket_id = socket_id,
+		};
+
+		if (conf->trie.num_tbl8 == 0 ||
+		    conf->trie.num_tbl8 >
+		    get_max_nh(nh_sz)) {
+			rte_errno = EINVAL;
+			return NULL;
+		}
+
+		pool = rte_fib_tbl8_pool_create(name, &pool_conf);
+		if (pool == NULL)
+			return NULL;
+	}
 
 	snprintf(mem_name, sizeof(mem_name), "DP_%s", name);
 	dp = rte_zmalloc_socket(name, sizeof(struct rte_trie_tbl) +
 		TRIE_TBL24_NUM_ENT * (1 << nh_sz) + sizeof(uint32_t),
 		RTE_CACHE_LINE_SIZE, socket_id);
 	if (dp == NULL) {
+		fib_tbl8_pool_unref(pool);
 		rte_errno = ENOMEM;
-		return dp;
-	}
-
-	fib_tbl8_write(&dp->tbl24, (def_nh << 1), nh_sz, 1 << 24);
-
-	snprintf(mem_name, sizeof(mem_name), "TBL8_%p", dp);
-	dp->tbl8 = rte_zmalloc_socket(mem_name, FIB_TBL8_GRP_NUM_ENT *
-			(1ll << nh_sz) * (num_tbl8 + 1),
-			RTE_CACHE_LINE_SIZE, socket_id);
-	if (dp->tbl8 == NULL) {
-		rte_errno = ENOMEM;
-		rte_free(dp);
 		return NULL;
 	}
+
 	dp->def_nh = def_nh;
 	dp->nh_sz = nh_sz;
-	dp->number_tbl8s = num_tbl8;
+	dp->pool = pool;
+	dp->tbl8 = pool->tbl8;
 
-	snprintf(mem_name, sizeof(mem_name), "TBL8_idxes_%p", dp);
-	dp->tbl8_pool = rte_zmalloc_socket(mem_name,
-			sizeof(uint32_t) * dp->number_tbl8s,
-			RTE_CACHE_LINE_SIZE, socket_id);
-	if (dp->tbl8_pool == NULL) {
-		rte_errno = ENOMEM;
-		rte_free(dp->tbl8);
-		rte_free(dp);
-		return NULL;
-	}
-
-	tbl8_pool_init(dp);
+	fib_tbl8_write(&dp->tbl24, (def_nh << 1), nh_sz, 1 << 24);
 
 	return dp;
 }
@@ -703,8 +654,7 @@ trie_free(void *p)
 	struct rte_trie_tbl *dp = (struct rte_trie_tbl *)p;
 
 	rte_rcu_qsbr_dq_delete(dp->dq);
-	rte_free(dp->tbl8_pool);
-	rte_free(dp->tbl8);
+	fib_tbl8_pool_unref(dp->pool);
 	rte_free(dp);
 }
 
@@ -735,8 +685,8 @@ trie_rcu_qsbr_add(struct rte_trie_tbl *dp, struct rte_fib6_rcu_config *cfg,
 		if (params.max_reclaim_size == 0)
 			params.max_reclaim_size = RTE_FIB6_RCU_DQ_RECLAIM_MAX;
 		params.esize = sizeof(uint64_t);
-		params.free_fn = __rcu_qsbr_free_resource;
-		params.p = dp;
+		params.free_fn = fib_tbl8_pool_rcu_free_cb;
+		params.p = dp->pool;
 		params.v = cfg->v;
 		dp->dq = rte_rcu_qsbr_dq_create(&params);
 		if (dp->dq == NULL) {
diff --git a/lib/fib/trie.h b/lib/fib/trie.h
index 30fa886792..61df56b1bb 100644
--- a/lib/fib/trie.h
+++ b/lib/fib/trie.h
@@ -11,7 +11,7 @@
 #include <rte_common.h>
 #include <rte_fib6.h>
 
-#include "fib_tbl8.h"
+#include "fib_tbl8_pool.h"
 
 /**
  * @file
@@ -26,13 +26,11 @@
 #define TRIE_EXT_ENT		1
 
 struct rte_trie_tbl {
-	uint32_t	number_tbl8s;	/**< Total number of tbl8s */
 	uint32_t	rsvd_tbl8s;	/**< Number of reserved tbl8s */
 	uint64_t	def_nh;		/**< Default next hop */
 	enum rte_fib_trie_nh_sz	nh_sz;	/**< Size of nexthop entry */
 	uint64_t	*tbl8;		/**< tbl8 table. */
-	uint32_t	*tbl8_pool;	/**< bitmap containing free tbl8 idxes*/
-	uint32_t	tbl8_pool_pos;
+	struct rte_fib_tbl8_pool *pool;	/**< tbl8 pool */
 	/* RCU config. */
 	enum rte_fib6_qsbr_mode rcu_mode; /**< Blocking, defer queue. */
 	struct rte_rcu_qsbr *v; /**< RCU QSBR variable. */
-- 
2.43.0



More information about the dev mailing list