diff --git a/config/common_base b/config/common_base
index d12ae98bc..af40a9f81 100644
--- a/config/common_base
+++ b/config/common_base
@@ -792,6 +792,12 @@ CONFIG_RTE_LIBRTE_LATENCY_STATS=y
 #
 CONFIG_RTE_LIBRTE_TELEMETRY=n
 
+#
+# Compile librte_tqs
+#
+CONFIG_RTE_LIBRTE_TQS=y
+CONFIG_RTE_LIBRTE_TQS_DEBUG=n
+
 #
 # Compile librte_lpm
 #
diff --git a/lib/Makefile b/lib/Makefile
index b7370ef97..7095eac88 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -108,6 +108,8 @@ DIRS-$(CONFIG_RTE_LIBRTE_BPF) += librte_bpf
 DEPDIRS-librte_bpf := librte_eal librte_mempool librte_mbuf librte_ethdev
 DIRS-$(CONFIG_RTE_LIBRTE_TELEMETRY) += librte_telemetry
 DEPDIRS-librte_telemetry := librte_eal librte_metrics librte_ethdev
+DIRS-$(CONFIG_RTE_LIBRTE_TQS) += librte_tqs
+DEPDIRS-librte_tqs := librte_eal
 
 ifeq ($(CONFIG_RTE_EXEC_ENV_LINUXAPP),y)
 DIRS-$(CONFIG_RTE_LIBRTE_KNI) += librte_kni
diff --git a/lib/librte_tqs/Makefile b/lib/librte_tqs/Makefile
new file mode 100644
index 000000000..059de53e2
--- /dev/null
+++ b/lib/librte_tqs/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Arm Limited
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_tqs.a
+
+CFLAGS += -DALLOW_EXPERIMENTAL_API
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR) -O3
+LDLIBS += -lrte_eal
+
+EXPORT_MAP := rte_tqs_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_RING) := rte_tqs.c
+
+# install includes
+SYMLINK-$(CONFIG_RTE_LIBRTE_RING)-include := rte_tqs.h
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_tqs/meson.build b/lib/librte_tqs/meson.build
new file mode 100644
index 000000000..dd696ab07
--- /dev/null
+++ b/lib/librte_tqs/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Arm Limited
+
+sources = files('rte_tqs.c')
+headers = files('rte_tqs.h')
diff --git a/lib/librte_tqs/rte_tqs.c b/lib/librte_tqs/rte_tqs.c
new file mode 100644
index 000000000..cbc36864e
--- /dev/null
+++ b/lib/librte_tqs/rte_tqs.c
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2018 Arm Limited
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_log.h>
+#include <rte_memory.h>
+#include <rte_malloc.h>
+#include <rte_eal.h>
+#include <rte_eal_memconfig.h>
+#include <rte_atomic.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_errno.h>
+
+#include "rte_tqs.h"
+
+TAILQ_HEAD(rte_tqs_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_tqs_tailq = {
+	.name = RTE_TAILQ_TQS_NAME,
+};
+EAL_REGISTER_TAILQ(rte_tqs_tailq)
+
+/* Allocate a new TQS variable with the name *name* in memory. */
+struct rte_tqs * __rte_experimental
+rte_tqs_alloc(const char *name, int socket_id, uint64_t lcore_mask)
+{
+	char tqs_name[RTE_TQS_NAMESIZE];
+	struct rte_tailq_entry *te, *tmp_te;
+	struct rte_tqs_list *tqs_list;
+	struct rte_tqs *v, *tmp_v;
+	int ret;
+
+	if (name == NULL) {
+		RTE_LOG(ERR, TQS, "Invalid input parameters\n");
+		rte_errno = -EINVAL;
+		return NULL;
+	}
+
+	te = rte_zmalloc("TQS_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, TQS, "Cannot reserve memory for tailq\n");
+		rte_errno = -ENOMEM;
+		return NULL;
+	}
+
+	snprintf(tqs_name, sizeof(tqs_name), "%s", name);
+	v = rte_zmalloc_socket(tqs_name, sizeof(struct rte_tqs),
+				RTE_CACHE_LINE_SIZE, socket_id);
+	if (v == NULL) {
+		RTE_LOG(ERR, TQS, "Cannot reserve memory for TQS variable\n");
+		rte_errno = -ENOMEM;
+		goto alloc_error;
+	}
+
+	ret = snprintf(v->name, sizeof(v->name), "%s", name);
+	if (ret < 0 || ret >= (int)sizeof(v->name)) {
+		rte_errno = -ENAMETOOLONG;
+		goto alloc_error;
+	}
+
+	te->data = (void *) v;
+	v->lcore_mask = lcore_mask;
+
+	rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+	tqs_list = RTE_TAILQ_CAST(rte_tqs_tailq.head, rte_tqs_list);
+
+	/* Search if a TQS variable with the same name exists already */
+	TAILQ_FOREACH(tmp_te, tqs_list, next) {
+		tmp_v = (struct rte_tqs *) tmp_te->data;
+		if (strncmp(name, tmp_v->name, RTE_TQS_NAMESIZE) == 0)
+			break;
+	}
+
+	if (tmp_te != NULL) {
+		rte_errno = -EEXIST;
+		goto tqs_exist;
+	}
+
+	TAILQ_INSERT_TAIL(tqs_list, te, next);
+
+	rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+	return v;
+
+tqs_exist:
+	rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+alloc_error:
+	rte_free(te);
+	rte_free(v);
+	return NULL;
+}
+
+/* De-allocate all the memory used by a TQS variable. */
+void __rte_experimental
+rte_tqs_free(struct rte_tqs *v)
+{
+	struct rte_tqs_list *tqs_list;
+	struct rte_tailq_entry *te;
+
+	/* Search for the TQS variable in tailq */
+	tqs_list = RTE_TAILQ_CAST(rte_tqs_tailq.head, rte_tqs_list);
+	rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+	TAILQ_FOREACH(te, tqs_list, next) {
+		if (te->data == (void *) v)
+			break;
+	}
+
+	if (te != NULL)
+		TAILQ_REMOVE(tqs_list, te, next);
+	else
+		RTE_LOG(ERR, TQS, "TQS variable %s not found\n", v->name);
+
+	rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+	rte_free(te);
+	rte_free(v);
+}
+
+/* Add a reader thread, running on an lcore, to the list of threads
+ * reporting their quiescent state on a TQS variable.
+ */
+int __rte_experimental
+rte_tqs_register_lcore(struct rte_tqs *v, unsigned int lcore_id)
+{
+	TQS_RETURN_IF_TRUE((v == NULL || lcore_id >= RTE_TQS_MAX_LCORE),
+				-EINVAL);
+
+	/* Worker thread has to count the quiescent states
+	 * only from the current value of token.
+	 */
+	v->w[lcore_id].cnt = v->token;
+
+	/* Release the store to initial TQS count so that workers
+	 * can use it immediately after this function returns.
+	 */
+	__atomic_fetch_or(&v->lcore_mask, (1UL << lcore_id), __ATOMIC_RELEASE);
+
+	return 0;
+}
+
+/* Remove a reader thread, running on an lcore, from the list of threads
+ * reporting their quiescent state on a TQS variable.
+ */
+int __rte_experimental
+rte_tqs_unregister_lcore(struct rte_tqs *v, unsigned int lcore_id)
+{
+	TQS_RETURN_IF_TRUE((v == NULL ||
+				lcore_id >= RTE_TQS_MAX_LCORE), -EINVAL);
+
+	/* This can be a relaxed store. Since this is an API, make sure
+	 * the store is not reordered with other memory operations.
+	 */
+	__atomic_fetch_and(&v->lcore_mask,
+				~(1UL << lcore_id), __ATOMIC_RELEASE);
+
+	return 0;
+}
+
+/* Search a TQS variable, given its name. */
+struct rte_tqs * __rte_experimental
+rte_tqs_lookup(const char *name)
+{
+	struct rte_tqs_list *tqs_list;
+	struct rte_tailq_entry *te;
+	struct rte_tqs *v;
+
+	if (name == NULL) {
+		RTE_LOG(ERR, TQS, "Invalid input parameters\n");
+		rte_errno = -EINVAL;
+		return NULL;
+	}
+
+	v = NULL;
+	tqs_list = RTE_TAILQ_CAST(rte_tqs_tailq.head, rte_tqs_list);
+
+	rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+	/* find out tailq entry */
+	TAILQ_FOREACH(te, tqs_list, next) {
+		v = (struct rte_tqs *) te->data;
+		if (strncmp(name, v->name, RTE_TQS_NAMESIZE) == 0)
+			break;
+	}
+
+	if (te == NULL) {
+		rte_errno = -ENOENT;
+		v = NULL;
+	}
+
+	rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+	return v;
+}
+
+/* Dump the details of a single TQS variables to a file. */
+void __rte_experimental
+rte_tqs_dump(FILE *f, struct rte_tqs *v)
+{
+	uint64_t tmp_mask;
+	uint32_t i;
+
+	TQS_ERR_LOG_IF_TRUE(v == NULL || f == NULL);
+
+	fprintf(f, "\nTQS <%s>@%p\n", v->name, v);
+	fprintf(f, "  lcore mask = 0x%lx\n", v->lcore_mask);
+	fprintf(f, "  token = %u\n", v->token);
+
+	if (v->lcore_mask != 0) {
+		fprintf(f, "Quiescent State Counts for readers:\n");
+		tmp_mask = v->lcore_mask;
+		while (tmp_mask) {
+			i = __builtin_ctz(tmp_mask);
+			fprintf(f, "lcore # = %d, count = %u\n",
+				i, v->w[i].cnt);
+			tmp_mask &= ~(1UL << i);
+		}
+	}
+}
+
+/* Dump the details of all the TQS variables to a file. */
+void __rte_experimental
+rte_tqs_list_dump(FILE *f)
+{
+	const struct rte_tailq_entry *te;
+	struct rte_tqs_list *tqs_list;
+
+	TQS_ERR_LOG_IF_TRUE(f == NULL);
+
+	tqs_list = RTE_TAILQ_CAST(rte_tqs_tailq.head, rte_tqs_list);
+
+	rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+
+	TAILQ_FOREACH(te, tqs_list, next) {
+		rte_tqs_dump(f, (struct rte_tqs *) te->data);
+	}
+
+	rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+}
diff --git a/lib/librte_tqs/rte_tqs.h b/lib/librte_tqs/rte_tqs.h
new file mode 100644
index 000000000..9136418d2
--- /dev/null
+++ b/lib/librte_tqs/rte_tqs.h
@@ -0,0 +1,352 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2018 Arm Limited
+ */
+
+#ifndef _RTE_TQS_H_
+#define _RTE_TQS_H_
+
+/**
+ * @file
+ * RTE Thread Quiescent State
+ *
+ * Thread Quiescent State (TQS) is any point in the thread execution
+ * where the thread does not hold a reference to shared memory, i.e.
+ * a non-critical section. A critical section for a data structure can
+ * be a quiescent state for another data structure.
+ *
+ * An application can identify the quiescent state according to its
+ * needs. It can identify 1 quiescent state for each data structure or
+ * 1 quiescent state for a group/all of data structures.
+ *
+ * This library provides the flexibility for these use cases.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_lcore.h>
+
+#define RTE_TAILQ_TQS_NAME "RTE_TQS"
+
+/**< The maximum length of a TQS variable name. */
+#define RTE_TQS_NAMESIZE 32
+
+/**< Maximum number of lcores supported. */
+#if (RTE_MAX_LCORE > 64)
+#define RTE_TQS_MAX_LCORE 64
+#else
+#define RTE_TQS_MAX_LCORE RTE_MAX_LCORE
+#endif
+
+/* Macro for run-time checking of function parameters */
+#if defined(RTE_LIBRTE_TQS_DEBUG)
+#define TQS_RETURN_IF_TRUE(cond, retval) do { \
+	if ((cond)) \
+		return retval; \
+} while (0)
+#else
+#define TQS_RETURN_IF_TRUE(cond, retval)
+#endif
+
+/* Macro to log error message */
+#define TQS_ERR_LOG_IF_TRUE(cond) do { \
+	if ((cond)) { \
+		RTE_LOG(ERR, TQS, "Invalid parameters\n"); \
+		return; \
+	} \
+} while (0)
+
+/* Worker thread counter */
+struct rte_tqs_cnt {
+	volatile uint32_t cnt; /**< Quiescent state counter. */
+} __rte_cache_aligned;
+
+/**
+ * RTE Thread Quiescent State structure.
+ */
+struct rte_tqs {
+	char name[RTE_TQS_NAMESIZE];
+	/**< Name of the ring. */
+	uint64_t lcore_mask;
+	/**< Worker lcores reporting on this TQS */
+
+	uint32_t token __rte_cache_aligned;
+	/**< Counter to allow for multiple simultaneous TQS queries */
+
+	struct rte_tqs_cnt w[RTE_TQS_MAX_LCORE] __rte_cache_aligned;
+	/**< TQS counter for each worker thread, counts upto
+	 * current value of token.
+	 */
+} __rte_cache_aligned;
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Allocate a new TQS variable with the name *name* in memory.
+ *
+ * The TQS variable is added in RTE_TAILQ_TQS list.
+ *
+ * @param name
+ *   The name of the TQS variable.
+ * @param socket_id
+ *   The *socket_id* argument is the socket identifier in case of
+ *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
+ *   constraint for the reserved zone.
+ * @param lcore_mask
+ *   Data plane reader threads in this mask will report their quiescent
+ *   state on this TQS variable.
+ * @return
+ *   On success, the pointer to the new allocated TQS variable. NULL on
+ *   error with rte_errno set appropriately. Possible errno values include:
+ *    - EINVAL - invalid input parameters
+ *    - ENAMETOOLONG - TQS variable name is longer than RTE_TQS_NAMESIZE
+ *    - EEXIST - a TQS variable with the same name already exists
+ *    - ENOMEM - no appropriate memory area found in which to create memzone
+ */
+struct rte_tqs * __rte_experimental
+rte_tqs_alloc(const char *name, int socket_id, uint64_t lcore_mask);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * De-allocate all the memory used by a TQS variable. It is the
+ * application's responsibility to make sure that no other thread
+ * is using the TQS variable.
+ *
+ * The TQS variable is removed from RTE_TAILQ_TQS list.
+ *
+ * @param v
+ *   TQS variable to free.
+ */
+void __rte_experimental rte_tqs_free(struct rte_tqs *v);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Add a worker thread, running on an lcore, to the list of threads
+ * reporting their quiescent state on a TQS variable.
+ *
+ * This is implemented as a lock-free function. It is multi-thread
+ * safe. This API can be called from the worker threads during
+ * initialization. Any ongoing TQS queries may wait for the
+ * status from this registered worker thread.
+ *
+ * @param v
+ *   TQS variable
+ * @param lcore_id
+ *   Worker thread on this lcore will report its quiescent state on
+ *   this TQS variable.
+ * @return
+ *   - 0 if registered successfully.
+ *   - -EINVAL if the parameters are invalid (debug mode compilation only).
+ */
+int __rte_experimental
+rte_tqs_register_lcore(struct rte_tqs *v, unsigned int lcore_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Remove a worker thread, running on an lcore, from the list of threads
+ * reporting their quiescent state on a TQS variable.
+ *
+ * This is implemented as a lock-free function. It is multi-thread safe.
+ * This API can be called from the worker threads during shutdown.
+ * Any ongoing TQS queries may stop waiting for the status from this
+ * unregistered worker thread.
+ *
+ * @param v
+ *   TQS variable
+ * @param lcore_id
+ *   Worker thread on this lcore will stop reporting its quiescent state
+ *   on this TQS variable.
+ * @return
+ *   - 0 if un-registered successfully.
+ *   - -EINVAL if the parameters are invalid (debug mode compilation only).
+ */
+int __rte_experimental
+rte_tqs_unregister_lcore(struct rte_tqs *v, unsigned int lcore_id);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Trigger the worker threads to report the quiescent state
+ * status.
+ *
+ * This is implemented as a lock-free function. It is multi-thread
+ * safe and can be called from the worker threads as well.
+ *
+ * @param v
+ *   TQS variable
+ * @param n
+ *   Expected number of times the quiescent state is entered
+ * @param t
+ *   - If successful, this is the token for this call of the API.
+ *     This should be passed to rte_tqs_check API.
+ * @return
+ *   - -EINVAL if the parameters are invalid (debug mode compilation only).
+ *   - 0 Otherwise and always (non-debug mode compilation).
+ */
+static __rte_always_inline int __rte_experimental
+rte_tqs_start(struct rte_tqs *v, unsigned int n, uint32_t *t)
+{
+	TQS_RETURN_IF_TRUE((v == NULL || t == NULL), -EINVAL);
+
+	/* This store release will ensure that changes to any data
+	 * structure are visible to the workers before the token
+	 * update is visible.
+	 */
+	*t = __atomic_add_fetch(&v->token, n, __ATOMIC_RELEASE);
+
+	return 0;
+}
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Update quiescent state for the worker thread on a lcore.
+ *
+ * This is implemented as a lock-free function. It is multi-thread safe.
+ * All the worker threads registered to report their quiescent state
+ * on the TQS variable must call this API.
+ *
+ * @param v
+ *   TQS variable
+ */
+static __rte_always_inline void __rte_experimental
+rte_tqs_update(struct rte_tqs *v, unsigned int lcore_id)
+{
+	uint32_t t;
+
+	TQS_ERR_LOG_IF_TRUE(v == NULL || lcore_id >= RTE_TQS_MAX_LCORE);
+
+	/* Load the token before the worker thread loads any other
+	 * (lock-free) data structure. This ensures that updates
+	 * to the data structures are visible if the update
+	 * to token is visible.
+	 */
+	t = __atomic_load_n(&v->token, __ATOMIC_ACQUIRE);
+	if (v->w[lcore_id].cnt != t)
+		v->w[lcore_id].cnt++;
+}
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Checks if all the worker threads have entered the quiescent state
+ * 'n' number of times. 'n' is provided in rte_tqs_start API.
+ *
+ * This is implemented as a lock-free function. It is multi-thread
+ * safe and can be called from the worker threads as well.
+ *
+ * @param v
+ *   TQS variable
+ * @param t
+ *   Token returned by rte_tqs_start API
+ * @param wait
+ *   If true, block till all the worker threads have completed entering
+ *   the quiescent state 'n' number of times
+ * @return
+ *   - 0 if all worker threads have NOT passed through specified number
+ *     of quiescent states.
+ *   - 1 if all worker threads have passed through specified number
+ *     of quiescent states.
+ *   - -EINVAL if the parameters are invalid (debug mode compilation only).
+ */
+static __rte_always_inline int __rte_experimental
+rte_tqs_check(struct rte_tqs *v, uint32_t t, bool wait)
+{
+	uint64_t l;
+	uint64_t lcore_mask;
+
+	TQS_RETURN_IF_TRUE((v == NULL), -EINVAL);
+
+	do {
+		/* Load the current lcore_mask before loading the
+		 * worker thread quiescent state counters.
+		 */
+		lcore_mask = __atomic_load_n(&v->lcore_mask, __ATOMIC_ACQUIRE);
+
+		while (lcore_mask) {
+			l = __builtin_ctz(lcore_mask);
+			if (v->w[l].cnt != t)
+				break;
+
+			lcore_mask &= ~(1UL << l);
+		}
+
+		if (lcore_mask == 0)
+			return 1;
+
+		rte_pause();
+	} while (wait);
+
+	return 0;
+}
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Search a TQS variable, given its name.
+ *
+ * It is multi-thread safe.
+ *
+ * @param name
+ *   The name of the TQS variable.
+ * @return
+ *   On success, the pointer to the TQS variable. NULL on
+ *   error with rte_errno set appropriately. Possible errno values include:
+ *    - EINVAL - invalid input parameters.
+ *    - ENOENT - entry not found.
+ */
+struct rte_tqs * __rte_experimental
+rte_tqs_lookup(const char *name);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Dump the details of a single TQS variables to a file.
+ *
+ * It is NOT multi-thread safe.
+ *
+ * @param f
+ *   A pointer to a file for output
+ * @param tqs
+ *   TQS variable
+ */
+void __rte_experimental
+rte_tqs_dump(FILE *f, struct rte_tqs *v);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Dump the details of all the TQS variables to a file.
+ *
+ * It is multi-thread safe.
+ *
+ * @param f
+ *   A pointer to a file for output
+ */
+void __rte_experimental
+rte_tqs_list_dump(FILE *f);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_TQS_H_ */
diff --git a/lib/librte_tqs/rte_tqs_version.map b/lib/librte_tqs/rte_tqs_version.map
new file mode 100644
index 000000000..2e4d5c094
--- /dev/null
+++ b/lib/librte_tqs/rte_tqs_version.map
@@ -0,0 +1,16 @@
+EXPERIMENTAL {
+	global:
+
+	rte_tqs_alloc;
+	rte_tqs_free;
+	rte_tqs_register_lcore;
+	rte_tqs_unregister_lcore;
+	rte_tqs_start;
+	rte_tqs_update;
+	rte_tqs_check;
+	rte_tqs_lookup;
+	rte_tqs_list_dump;
+	rte_tqs_dump;
+
+	local: *;
+};
diff --git a/lib/meson.build b/lib/meson.build
index bb7f443f9..ee19c483e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -21,7 +21,7 @@ libraries = [ 'compat', # just a header, used for versioning
 	'gro', 'gso', 'ip_frag', 'jobstats',
 	'kni', 'latencystats', 'lpm', 'member',
 	'meter', 'power', 'pdump', 'rawdev',
-	'reorder', 'sched', 'security', 'vhost',
+	'reorder', 'sched', 'security', 'tqs', 'vhost',
 	# add pkt framework libs which use other libs from above
 	'port', 'table', 'pipeline',
 	# flow_classify lib depends on pkt framework table lib
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index 3ebc4e64c..6e19e669a 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -92,6 +92,7 @@ _LDLIBS-$(CONFIG_RTE_LIBRTE_EAL)            += -lrte_eal
 _LDLIBS-$(CONFIG_RTE_LIBRTE_CMDLINE)        += -lrte_cmdline
 _LDLIBS-$(CONFIG_RTE_LIBRTE_REORDER)        += -lrte_reorder
 _LDLIBS-$(CONFIG_RTE_LIBRTE_SCHED)          += -lrte_sched
+_LDLIBS-$(CONFIG_RTE_LIBRTE_TQS)            += -lrte_tqs
 
 ifeq ($(CONFIG_RTE_EXEC_ENV_LINUXAPP),y)
 _LDLIBS-$(CONFIG_RTE_LIBRTE_KNI)            += -lrte_kni
