Merge "[qca-nss-drv] Added statistics for worker threads."
diff --git a/Makefile b/Makefile
index 276487b..4cab556 100644
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,7 @@
 			nss_phys_if.o \
 			nss_pm.o \
 			nss_profiler.o \
+			nss_project.o \
 			nss_portid.o \
 			nss_ppe.o \
 			nss_pppoe.o \
diff --git a/exports/nss_api_if.h b/exports/nss_api_if.h
index a5cd4f0..d23e18c 100644
--- a/exports/nss_api_if.h
+++ b/exports/nss_api_if.h
@@ -65,6 +65,7 @@
 #include "nss_vlan.h"
 #include "nss_wifili_if.h"
 #include "nss_dscp2pri.h"
+#include "nss_project.h"
 #endif
 
 /**
diff --git a/exports/nss_project.h b/exports/nss_project.h
new file mode 100644
index 0000000..a1a2cc9
--- /dev/null
+++ b/exports/nss_project.h
@@ -0,0 +1,176 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+/*
+ * @file nss_project.h
+ *	NSS project interface definitions.
+ */
+
+#ifndef __NSS_PROJECT_H
+#define __NSS_PROJECT_H
+
+/**
+ * @addtogroup nss_project_subsystem
+ * @{
+ */
+
+ /**
+  * Maximum number of IRQs for which a message will have statistics.
+  *
+  * Must be defined on firmware and host such that NSS_PROJECT_IRQS_PER_MESSAGE *
+  * sizeof(struct nss_project_irq_stats) + 8 + sizeof(struct nss_cmn_msg) is smaller
+  * than the maximum payload size of an sk_buff (1792), 8 being the number of
+  * bytes needed to store the thread number and number of statistics written.
+  */
+#define NSS_PROJECT_IRQS_PER_MESSAGE 32
+
+/**
+ * nss_project_message_types
+ *	Project message types.
+ */
+enum nss_project_message_types {
+	NSS_PROJECT_MSG_WT_STATS_ENABLE,
+			/**< Message to enable or disable worker thread statistics. */
+	NSS_PROJECT_MSG_WT_STATS_NOTIFY,
+			/**< NSS to HLOS message containing worker thread statistics. */
+	NSS_PROJECT_MSG_MAX,
+};
+
+/**
+ * nss_project_error_types
+ *	Project error types.
+ */
+enum nss_project_error_types {
+	NSS_PROJECT_ERROR_UNKNOWN_MSG,
+			/**< Unrecognized message type. */
+	NSS_PROJECT_ERROR_WT_STATS_UNSUPPORTED,
+			/**< The firmware does not support worker thread statistics. */
+	NSS_PROJECT_ERROR_WT_STATS_REDUNDANT_ENABLE,
+			/**< The firmware received a redundant request to enable worker thread statistics. */
+	NSS_PROJECT_ERROR_MAX,
+};
+
+/**
+ * nss_project_msg_wt_stats_enable
+ *	Enables or disables worker thread statistics collection.
+ */
+struct nss_project_msg_wt_stats_enable {
+
+	/*
+	 * NSS to HLOS
+	 */
+	uint32_t worker_thread_count;
+			/**< Number of worker threads supported by this core. */
+	uint32_t irq_count;
+			/**< Number of IRQs supported by this core. */
+
+	/*
+	 * HLOS to NSS
+	 */
+	bool enable;	/**< True to enable, false to disable. */
+};
+
+/**
+ * nss_project_irq_stats
+ *	Statistics for an individual IRQ on a worker thread.
+ */
+struct nss_project_irq_stats {
+	uint64_t count;		/**< Number of times callback has been executed */
+	uint32_t callback;	/**< Address of the callback function */
+	uint32_t irq;		/**< IRQ number to which callback function is bound */
+	uint32_t ticks_min;	/**< Fewest ticks taken in callback function */
+	uint32_t ticks_avg;	/**< Exponential moving average of ticks */
+	uint32_t ticks_max;	/**< Maximum ticks */
+	uint32_t insn_min;	/**< Fewest instructions executed in callback function */
+	uint32_t insn_avg;	/**< Exponential moving average of instruction count */
+	uint32_t insn_max;	/**< Maximum instructions */
+};
+
+/**
+ * nss_project_msg_wt_stats_notify
+ *	Message containing statistics for active worker_thread IRQs.
+ */
+struct nss_project_msg_wt_stats_notify {
+	uint32_t threadno;	/**< The thread whose stats are contained. */
+	uint32_t stats_written;	/**< The number of statistics written to the array. */
+	struct nss_project_irq_stats stats[NSS_PROJECT_IRQS_PER_MESSAGE];
+				/**< The per-IRQ statistics for the worker thread */
+};
+
+/**
+ * nss_project_msg
+ *	General message structure for project messages.
+ */
+struct nss_project_msg {
+	struct nss_cmn_msg cm;	/**< Common message header. */
+
+	/**
+	 * Payload of a message to or from the project code.
+	 */
+	union {
+		struct nss_project_msg_wt_stats_enable wt_stats_enable;
+				/**< Enable or disable worker thread statistics. */
+		struct nss_project_msg_wt_stats_notify wt_stats_notify;
+				/**< One-way worker thread statistics message. */
+	} msg;
+};
+
+/**
+ * Callback function for receiving project messages.
+ *
+ * @datatypes
+ * nss_project_msg
+ *
+ * @param[in] app_data  Pointer to the application context of the message.
+ * @param[in] msg       Pointer to the project message.
+ */
+typedef void (*nss_project_msg_callback_t)(void *app_data, struct nss_project_msg *msg);
+
+/**
+ * nss_project_register_sysctl
+ *	Registers the project sysctl table to the sysctl tree.
+ *
+ * @return
+ * None.
+ */
+void nss_project_register_sysctl(void);
+
+/**
+ * nss_project_unregister_sysctl
+ *	De-registers the project sysctl table from the sysctl tree.
+ *
+ * @return
+ * None.
+ *
+ * @dependencies
+ * The system control must have been previously registered.
+ */
+void nss_project_unregister_sysctl(void);
+
+/**
+ * nss_project_register_handler
+ *	Registers the project message handler.
+ *
+ * @return
+ * None.
+ */
+void nss_project_register_handler(struct nss_ctx_instance *nss_ctx);
+
+/**
+ * @}
+ */
+
+#endif /* __NSS_PROJECT_H */
diff --git a/nss_core.h b/nss_core.h
index b6ba153..2608bca 100644
--- a/nss_core.h
+++ b/nss_core.h
@@ -190,6 +190,7 @@
  */
 #define NSS_PPPOE_NUM_SESSION_PER_INTERFACE 4
 					/* Number of maximum simultaneous PPPoE sessions per physical interface */
+
 /*
  * NSS Frequency Defines and Values
  *
@@ -1444,6 +1445,13 @@
 };
 
 /*
+ * Holds statistics for every worker thread on a core
+ */
+struct nss_worker_thread_stats {
+	struct nss_project_irq_stats *irq_stats;
+};
+
+/*
  * NSS context instance (one per NSS core)
  */
 struct nss_ctx_instance {
@@ -1479,6 +1487,10 @@
 					/* Current MTU value of physical interface */
 	uint64_t stats_n2h[NSS_STATS_N2H_MAX];
 					/* N2H node stats: includes node, n2h, pbuf in this order */
+	uint32_t worker_thread_count;	/* Number of NSS core worker threads for statistics */
+	uint32_t irq_count;		/* Number of NSS core IRQs for statistics */
+	struct nss_worker_thread_stats *wt_stats;
+					/* Worker thread statistics */
 	struct nss_rx_cb_list nss_rx_interface_handlers[NSS_MAX_CORES][NSS_MAX_NET_INTERFACES];
 					/* NSS interface callback handlers */
 	struct nss_subsystem_dataplane_register subsys_dp_register[NSS_MAX_NET_INTERFACES];
@@ -1559,6 +1571,8 @@
 	struct dentry *virt_if_dentry;		/* virt_if stats dentry */
 	struct dentry *tx_rx_virt_if_dentry;	/* tx_rx_virt_if stats dentry. Will be deprecated soon */
 	struct dentry *wifili_dentry;		/* wifili stats dentry */
+	struct dentry *project_dentry;		/* per-project stats dentry */
+
 	struct nss_ctx_instance nss[NSS_MAX_CORES];
 						/* NSS contexts */
 	/*
diff --git a/nss_hal/nss_hal.c b/nss_hal/nss_hal.c
index 13d4de5..28372f2 100644
--- a/nss_hal/nss_hal.c
+++ b/nss_hal/nss_hal.c
@@ -333,6 +333,7 @@
 	 */
 	nss_dynamic_interface_register_handler(nss_ctx);
 	nss_n2h_register_handler(nss_ctx);
+	nss_project_register_handler(nss_ctx);
 
 	/*
 	 * Check functionalities are supported by this NSS core
@@ -499,6 +500,7 @@
 #if (NSS_FREQ_SCALE_SUPPORT == 1)
 		nss_freq_register_handler();
 #endif
+
 		nss_lso_rx_register_handler(nss_ctx);
 	}
 
diff --git a/nss_init.c b/nss_init.c
index 1bee547..433d508 100644
--- a/nss_init.c
+++ b/nss_init.c
@@ -745,6 +745,11 @@
 	nss_dscp2pri_register_sysctl();
 
 	/*
+	 * Register sysctl for project config
+	 */
+	nss_project_register_sysctl();
+
+	/*
 	 * Setup Runtime Sample values
 	 */
 	nss_runtime_samples.freq_scale_index = 1;
@@ -831,6 +836,7 @@
 	nss_ipv6_unregister_sysctl();
 
 	nss_dscp2pri_unregister_sysctl();
+	nss_project_unregister_sysctl();
 	nss_data_plane_destroy_delay_work();
 
 	/*
diff --git a/nss_project.c b/nss_project.c
new file mode 100644
index 0000000..237b1be
--- /dev/null
+++ b/nss_project.c
@@ -0,0 +1,361 @@
+/*
+ **************************************************************************
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+/*
+ * @file nss_project.h
+ *	NSS project APIs.
+ */
+#include "nss_tx_rx_common.h"
+
+static int nss_project_wt_stats_enable;
+
+/*
+ * nss_project_free_wt_stats()
+ *	Frees a number of allocated worker thread statistics.
+ */
+static void nss_project_free_wt_stats(struct nss_worker_thread_stats *wt_stats, int num_alloc)
+{
+	int i;
+
+	if (!wt_stats) {
+		return;
+	}
+
+	for (i = 0; i < num_alloc; i++) {
+		kfree(wt_stats[i].irq_stats);
+	}
+	kfree(wt_stats);
+}
+
+/*
+ * nss_project_alloc_wt_stats()
+ * 	Allocates worker thread stats for a given  number of threads and IRQs.
+ */
+static struct nss_worker_thread_stats *nss_project_alloc_wt_stats(uint32_t thread_count, uint32_t irq_count)
+{
+	struct nss_worker_thread_stats *wt_stats;
+	int i;
+
+	wt_stats = kzalloc(thread_count * sizeof(struct nss_worker_thread_stats), GFP_ATOMIC);
+	if (unlikely(!wt_stats)) {
+		return NULL;
+	}
+
+	for (i = 0; i < thread_count; i++) {
+		wt_stats[i].irq_stats =
+			kzalloc(irq_count * sizeof(struct nss_project_irq_stats), GFP_ATOMIC);
+		if (unlikely(!wt_stats[i].irq_stats)) {
+			nss_project_free_wt_stats(wt_stats, i);
+			return NULL;
+		}
+	}
+
+	return wt_stats;
+}
+
+/*
+ * nss_project_wt_stats_enable_callback()
+ *	Callback function for wt stats enable messages
+ */
+static void nss_project_wt_stats_enable_callback(void *app_data, struct nss_project_msg *msg)
+{
+	struct nss_ctx_instance *nss_ctx = (struct nss_ctx_instance *)app_data;
+	struct nss_project_msg_wt_stats_enable *stats_enable = &msg->msg.wt_stats_enable;
+	struct nss_worker_thread_stats *stats_temp;
+
+	NSS_VERIFY_CTX_MAGIC(nss_ctx);
+	if (msg->cm.response != NSS_CMN_RESPONSE_ACK) {
+		return;
+	}
+
+	nss_info("%p: Received response ACK for worker thread stats enable msg.\n", nss_ctx);
+
+	/*
+	 * If statistics have already been allocated, nothing else to do.
+	 */
+	if (nss_ctx->wt_stats) {
+		return;
+	}
+
+	stats_temp = nss_project_alloc_wt_stats(stats_enable->worker_thread_count,
+						stats_enable->irq_count);
+	if (unlikely(!stats_temp)) {
+		nss_warning("%p: Unable to allocate worker thread statistics.\n", nss_ctx);
+		return;
+	}
+
+	spin_lock_bh(&nss_ctx->nss_top->stats_lock);
+	nss_ctx->wt_stats = stats_temp;
+	nss_ctx->worker_thread_count = stats_enable->worker_thread_count;
+	nss_ctx->irq_count = stats_enable->irq_count;
+	spin_unlock_bh(&nss_ctx->nss_top->stats_lock);
+}
+
+/*
+ * nss_project_wt_stats_send_enable()
+ *	Sends message to firmware to enable or disable worker_thread statistics collection.
+ */
+static nss_tx_status_t nss_project_wt_stats_send_enable(struct nss_ctx_instance *nss_ctx, bool enable)
+{
+	struct nss_project_msg *npm;
+	struct sk_buff *nbuf;
+	int32_t status;
+
+	NSS_VERIFY_CTX_MAGIC(nss_ctx);
+	if (unlikely(nss_ctx->state != NSS_CORE_STATE_INITIALIZED)) {
+		nss_warning("%p: project msg dropped as core not ready\n", nss_ctx);
+		return NSS_TX_FAILURE_NOT_READY;
+	}
+
+	/*
+	 * Allocate the sk_buff and use its payload as an nss_project_msg
+	 */
+	nbuf = dev_alloc_skb(NSS_NBUF_PAYLOAD_SIZE);
+	if (unlikely(!nbuf)) {
+		NSS_PKT_STATS_INCREMENT(nss_ctx, &nss_ctx->nss_top->stats_drv[NSS_STATS_DRV_NBUF_ALLOC_FAILS]);
+		nss_warning("%p: msg dropped as command allocation failed\n", nss_ctx);
+		return NSS_TX_FAILURE;
+	}
+
+	npm = (struct nss_project_msg *)skb_put(nbuf, NSS_NBUF_PAYLOAD_SIZE);
+
+	/*
+	 * Populate the message
+	 */
+	memset(npm, 0, sizeof(struct nss_project_msg));
+	nss_cmn_msg_init(&(npm->cm), NSS_PROJECT_INTERFACE,
+		NSS_PROJECT_MSG_WT_STATS_ENABLE,
+		sizeof(struct nss_project_msg_wt_stats_enable),
+		(void *)nss_project_wt_stats_enable_callback,
+		(void *)nss_ctx);
+	npm->msg.wt_stats_enable.enable = enable;
+
+	/*
+	 * Send the sk_buff
+	 */
+	status = nss_core_send_buffer(nss_ctx, 0, nbuf, NSS_IF_CMD_QUEUE, H2N_BUFFER_CTRL, 0);
+	if (status != NSS_CORE_STATUS_SUCCESS) {
+		dev_kfree_skb_any(nbuf);
+		nss_warning("%p: unable to enqueue project msg\n", nss_ctx);
+		return NSS_TX_FAILURE;
+	}
+
+	nss_hal_send_interrupt(nss_ctx, NSS_H2N_INTR_DATA_COMMAND_QUEUE);
+
+	NSS_PKT_STATS_INCREMENT(nss_ctx, &nss_ctx->nss_top->stats_drv[NSS_STATS_DRV_TX_CMD_REQ]);
+	return NSS_TX_SUCCESS;
+}
+
+/*
+ * nss_project_wt_stats_update()
+ *	Updates stored statistics with the data found in the notify.
+ */
+static void nss_project_wt_stats_update(struct nss_ctx_instance *nss_ctx,
+			struct nss_project_msg_wt_stats_notify *stats_notify)
+{
+	struct nss_worker_thread_stats *wt_stats;
+	int i;
+
+	if (unlikely(!nss_ctx->wt_stats)) {
+		nss_warning("%p: Worker thread statistics not yet allocated.\n", nss_ctx);
+		return;
+	}
+
+	if (unlikely(stats_notify->threadno >= nss_ctx->worker_thread_count)) {
+		nss_warning("%p: Invalid WT number %d\n", nss_ctx, stats_notify->threadno);
+		return;
+	}
+
+	if (unlikely(stats_notify->stats_written > NSS_PROJECT_IRQS_PER_MESSAGE)) {
+		nss_warning("%p: Invalid worker thread stats written count %d\n",
+				nss_ctx, stats_notify->stats_written);
+		return;
+	}
+
+	wt_stats = &(nss_ctx->wt_stats[stats_notify->threadno]);
+
+	if (unlikely(!wt_stats->irq_stats)) {
+		nss_warning("%p: Worker thread statistics not allocated for thread %d\n",
+				nss_ctx, stats_notify->threadno);
+		return;
+	}
+
+	spin_lock_bh(&nss_ctx->nss_top->stats_lock);
+	for (i = 0; i < stats_notify->stats_written; ++i) {
+		int irq = stats_notify->stats[i].irq;
+		if (unlikely(irq >= nss_ctx->irq_count)) {
+			nss_warning("%p: Invalid IRQ number %d\n", nss_ctx, irq);
+			continue;
+		}
+
+		wt_stats->irq_stats[irq] = stats_notify->stats[i];
+	}
+	spin_unlock_bh(&nss_ctx->nss_top->stats_lock);
+}
+
+/*
+ * nss_project_msg_handler()
+ *	Handles metadata messages on the project interface.
+ */
+static void nss_project_msg_handler(struct nss_ctx_instance *nss_ctx,
+	struct nss_cmn_msg *ncm, __attribute__((unused))void *app_data)
+{
+	struct nss_project_msg *npm = (struct nss_project_msg *)ncm;
+	nss_project_msg_callback_t cb;
+
+	/*
+	 * Sanity checks on message
+	 */
+	if (npm->cm.type >= NSS_PROJECT_MSG_MAX) {
+		nss_warning("%p: message type out of range: %d\n", nss_ctx, npm->cm.type);
+		return;
+	}
+
+	if (nss_cmn_get_msg_len(&(npm->cm)) > sizeof(struct nss_project_msg)) {
+		nss_warning("%p: message length is invalid: %d\n", nss_ctx, nss_cmn_get_msg_len(&(npm->cm)));
+		return;
+	}
+
+	switch (npm->cm.type) {
+	case NSS_PROJECT_MSG_WT_STATS_NOTIFY:
+		nss_project_wt_stats_update(nss_ctx, &(npm->msg.wt_stats_notify));
+		return;
+	}
+
+	nss_core_log_msg_failures(nss_ctx, ncm);
+
+	if (!ncm->cb) {
+		return;
+	}
+
+	cb = (nss_project_msg_callback_t)ncm->cb;
+	cb((void *)nss_ctx, npm);
+}
+
+/*
+ * nss_project_wt_stats_handler()
+ *	Sysctl handler for wt_stats.
+ *
+ * Uses proc_dointvec to process data. For a write operation, also sends worker
+ * thread stats enable messages containing the new value to each NSS core.
+ */
+static int nss_project_wt_stats_handler(struct ctl_table *ctl, int write,
+	void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	int ret;
+	int i;
+
+	ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
+
+	/*
+	 * In case of error, stop now.
+	 */
+	if (ret) {
+		return ret;
+	}
+
+	/*
+	 * No additional behavior necessary for a read operation.
+	 */
+	if (!write) {
+		return ret;
+	}
+
+	/*
+	 * If a value was written, send a message containing that value to each
+	 * NSS core.
+	 */
+	for (i = 0; i < NSS_MAX_CORES; ++i) {
+		nss_project_wt_stats_send_enable(&(nss_top_main.nss[i]),
+			nss_project_wt_stats_enable);
+	}
+	return ret;
+
+}
+
+/*
+ * Tree of ctl_tables used to put the wt_stats proc node in the correct place in
+ * the file system. Allows the command $ echo 1 > proc/sys/dev/nss/project/wt_stats
+ * to enable worker thread statistics (echoing 0 into the same target will disable).
+ */
+static struct ctl_table nss_project_table[] = {
+	{
+		.procname		= "wt_stats",
+		.data			= &nss_project_wt_stats_enable,
+		.maxlen			= sizeof(int),
+		.mode			= 0644,
+		.proc_handler		= &nss_project_wt_stats_handler,
+	},
+	{ }
+};
+
+static struct ctl_table nss_project_dir[] = {
+	{
+		.procname		= "project",
+		.mode			= 0555,
+		.child			= nss_project_table,
+	},
+	{ }
+};
+
+static struct ctl_table nss_project_root_dir[] = {
+	{
+		.procname		= "nss",
+		.mode			= 0555,
+		.child			= nss_project_dir,
+	},
+	{ }
+};
+
+static struct ctl_table nss_project_root[] = {
+	{
+		.procname		= "dev",
+		.mode			= 0555,
+		.child			= nss_project_root_dir,
+	},
+	{ }
+};
+
+static struct ctl_table_header *nss_project_header;
+
+/*
+ * nss_project_register_sysctl()
+ *	Registers any sysctl handlers for the project.
+ */
+void nss_project_register_sysctl(void)
+{
+	nss_project_header = register_sysctl_table(nss_project_root);
+}
+
+/*
+ * nss_project_unregister_sysctl()
+ *	De-registers any sysctl handlers for the project.
+ */
+void nss_project_unregister_sysctl(void)
+{
+	if (nss_project_header) {
+		unregister_sysctl_table(nss_project_header);
+	}
+}
+
+/*
+ * nss_project_register_handler()
+ *	Registers the handler for NSS->HLOS messages
+ */
+void nss_project_register_handler(struct nss_ctx_instance *nss_ctx)
+{
+	nss_core_register_handler(nss_ctx, NSS_PROJECT_INTERFACE, nss_project_msg_handler, NULL);
+}
diff --git a/nss_stats.c b/nss_stats.c
index 2df2c92..d8adebc 100644
--- a/nss_stats.c
+++ b/nss_stats.c
@@ -43,9 +43,11 @@
  * Private data for every file descriptor
  */
 struct nss_stats_data {
-	uint32_t if_num;	/**< Interface number for stats */
-	uint32_t index;		/**< Index for GRE_REDIR stats */
-	uint32_t edma_id;	/**< EDMA port ID or ring ID */
+	uint32_t if_num;	/* Interface number for stats */
+	uint32_t index;		/* Index for GRE_REDIR stats */
+	uint32_t edma_id;	/* EDMA port ID or ring ID */
+	struct nss_ctx_instance *nss_ctx;
+				/* The core for project stats */
 };
 
 /*
@@ -4215,6 +4217,89 @@
 }
 
 /*
+ * nss_stats_wt_read()
+ *	Reads and formats worker thread statistics and outputs them to ubuf
+ */
+static ssize_t nss_stats_wt_read(struct file *fp, char __user *ubuf, size_t sz, loff_t *ppos)
+{
+	struct nss_stats_data *data = fp->private_data;
+	struct nss_ctx_instance *nss_ctx = data->nss_ctx;
+	struct nss_project_irq_stats *shadow;
+	uint32_t thread_count = nss_ctx->worker_thread_count;
+	uint32_t irq_count = nss_ctx->irq_count;
+
+	/*
+	 * Three lines for each IRQ
+	 */
+	uint32_t max_output_lines = thread_count * 3 * irq_count;
+	size_t size_al = max_output_lines * NSS_STATS_MAX_STR_LENGTH;
+	size_t size_wr = 0;
+	ssize_t bytes_read = 0;
+	char *lbuf;
+	int i;
+	int j;
+
+	lbuf = kzalloc(size_al, GFP_KERNEL);
+	if (unlikely(!lbuf)) {
+		nss_warning("Could not allocate memory for local statistics buffer\n");
+		return 0;
+	}
+
+	shadow = kzalloc(thread_count * irq_count * sizeof(struct nss_project_irq_stats), GFP_KERNEL);
+	if (unlikely(!shadow)) {
+		nss_warning("Could not allocate memory for stats shadow\n");
+		kfree(lbuf);
+		return 0;
+	}
+
+	spin_lock_bh(&nss_top_main.stats_lock);
+	if (unlikely(!nss_ctx->wt_stats)) {
+		spin_unlock_bh(&nss_top_main.stats_lock);
+		nss_warning("Worker thread statistics not allocated\n");
+		kfree(lbuf);
+		kfree(shadow);
+		return 0;
+	}
+	for (i = 0; i < thread_count; ++i) {
+
+		/*
+		 * The statistics shadow is an array with thread_count * irq_count
+		 * items in it. Each item is located at the index:
+		 *	(thread number) * (irq_count) + (irq number)
+		 * thus simulating a two-dimensional array.
+		 */
+		for (j = 0; j < irq_count; ++j) {
+			shadow[i * irq_count + j] = nss_ctx->wt_stats[i].irq_stats[j];
+		}
+	}
+	spin_unlock_bh(&nss_top_main.stats_lock);
+
+	for (i = 0; i < thread_count; ++i) {
+		for (j = 0; j < irq_count; ++j) {
+			struct nss_project_irq_stats *is = &(shadow[i * irq_count + j]);
+			if (!(is->count)) {
+				continue;
+			}
+
+			size_wr += scnprintf(lbuf + size_wr, size_al - size_wr,
+				"t-%d:irq-%d callback: 0x%x, count: %llu\n",
+				i, j, is->callback, is->count);
+			size_wr += scnprintf(lbuf + size_wr, size_al - size_wr,
+				"t-%d:irq-%d tick min: %10u  avg: %10u  max:%10u\n",
+				i, j, is->ticks_min, is->ticks_avg, is->ticks_max);
+			size_wr += scnprintf(lbuf + size_wr, size_al - size_wr,
+				"t-%d:irq-%d insn min: %10u  avg: %10u  max:%10u\n\n",
+				i, j, is->insn_min, is->insn_avg, is->insn_max);
+		}
+	}
+	bytes_read = simple_read_from_buffer(ubuf, sz, ppos, lbuf, strlen(lbuf));
+	kfree(lbuf);
+	kfree(shadow);
+
+	return bytes_read;
+}
+
+/*
  * nss_stats_open()
  */
 static int nss_stats_open(struct inode *inode, struct file *filp)
@@ -4229,6 +4314,7 @@
 	data->if_num = NSS_DYNAMIC_IF_START;
 	data->index = 0;
 	data->edma_id = (nss_ptr_t)inode->i_private;
+	data->nss_ctx = (struct nss_ctx_instance *)(inode->i_private);
 	filp->private_data = data;
 
 	return 0;
@@ -4429,6 +4515,10 @@
 NSS_STATS_DECLARE_FILE_OPERATIONS(wifili)
 
 /*
+ * wt_stats_ops
+ */
+NSS_STATS_DECLARE_FILE_OPERATIONS(wt)
+/*
  * nss_stats_init()
  * 	Enable NSS statistics
  */
@@ -4458,6 +4548,9 @@
 	struct dentry *ppe_cpu_d = NULL;
 	struct dentry *ppe_exception_d = NULL;
 	struct dentry *ppe_nonexception_d = NULL;
+	struct dentry *core_dentry = NULL;
+	struct dentry *wt_dentry = NULL;
+
 
 	char file_name[10];
 
@@ -4984,6 +5077,37 @@
 		return;
 	}
 
+	/*
+	 * Per-project stats
+	 */
+	nss_top_main.project_dentry = debugfs_create_dir("project",
+						nss_top_main.stats_dentry);
+	if (unlikely(nss_top_main.project_dentry == NULL)) {
+		nss_warning("Failed to create qca-nss-drv/stats/project directory in debugfs");
+		return;
+	}
+
+	for (i = 0; i < NSS_MAX_CORES; ++i) {
+		memset(file_name, 0, sizeof(file_name));
+		scnprintf(file_name, sizeof(file_name), "core%d", i);
+		core_dentry = debugfs_create_dir(file_name,
+						nss_top_main.project_dentry);
+		if (unlikely(core_dentry == NULL)) {
+			nss_warning("Failed to create qca-nss-drv/stats/project/core%d directory in debugfs", i);
+			return;
+		}
+
+		wt_dentry = debugfs_create_file("worker_threads",
+						0400,
+						core_dentry,
+						&(nss_top_main.nss[i]),
+						&nss_stats_wt_ops);
+		if (unlikely(wt_dentry == NULL)) {
+			nss_warning("Failed to create qca-nss-drv/stats/project/core%d/worker_threads file in debugfs", i);
+			return;
+		}
+	}
+
 	nss_log_init();
 }