[qca-nss-drv] : Add SKB Recycler Support

Added support to recycle Data packets sent from host if they can be
recycled.

Change-Id: I66319b5b1f82d19555a0c055ff1bd947238a9d80
Signed-off-by: Anish Nataraj <anishn@codeaurora.org>
diff --git a/Makefile b/Makefile
index 64e8562..fb1b5d1 100644
--- a/Makefile
+++ b/Makefile
@@ -72,7 +72,7 @@
 
 ccflags-y += -I$(obj)/nss_hal/include -I$(obj)/nss_data_plane/include -I$(obj)/exports -DNSS_DEBUG_LEVEL=0 -DNSS_PKT_STATS_ENABLED=1
 
-ccflags-y += -DNSS_PM_DEBUG_LEVEL=0
+ccflags-y += -DNSS_PM_DEBUG_LEVEL=0 -DNSS_SKB_RECYCLE_SUPPORT=1
 
 ifneq ($(findstring 3.4, $(KERNELVERSION)),)
 NSS_CCFLAGS = -DNSS_DT_SUPPORT=0 -DNSS_FW_DBG_SUPPORT=1 -DNSS_PM_SUPPORT=1 -DNSS_EMPTY_BUFFER_SIZE=1984
diff --git a/Makefile.fsm b/Makefile.fsm
index 1865d84..785397d 100644
--- a/Makefile.fsm
+++ b/Makefile.fsm
@@ -64,5 +64,5 @@
 ccflags-y += -I$(obj)/exports
 ccflags-y += -I$(obj)/nss_hal/fsm9010 -DNSS_HAL_FSM9010_SUPPORT
 ccflags-y += -DNSS_DEBUG_LEVEL=0 -DNSS_EMPTY_BUFFER_SIZE=1984 -DNSS_PKT_STATS_ENABLED=1
-ccflags-y += -DNSS_DT_SUPPORT=1 -DNSS_PM_SUPPORT=0 -DNSS_FW_DBG_SUPPORT=0
-ccflags-y += -DNSS_FREQ_SCALE_SUPPORT=0 -DNSS_FABRIC_SCALING_SUPPORT=0
+ccflags-y += -DNSS_DT_SUPPORT=1 -DNSS_PM_SUPPORT=0 -DNSS_FW_DBG_SUPPORT=0 -DNSS_SKB_RECYCLE_SUPPORT=0
+ccflags-y += -DNSS_PPP_SUPPORT=0 -DNSS_FREQ_SCALE_SUPPORT=0 -DNSS_FABRIC_SCALING_SUPPORT=0
diff --git a/nss_core.c b/nss_core.c
index 5162187..8bc64e3 100644
--- a/nss_core.c
+++ b/nss_core.c
@@ -29,6 +29,24 @@
 
 #define NSS_CORE_JUMBO_LINEAR_BUF_SIZE 128
 
+#if (NSS_SKB_RECYCLE_SUPPORT == 1)
+/*
+ * We have validated the skb recycling code within the NSS for the
+ * following kernel versions. Before enabling the driver in new kernels,
+ * the skb recycle code must be checked against Linux skb handling.
+ *
+ * Tested on: 3.4, 3.10, 3.14, 3.18 and 4.4.
+ */
+#if (!( \
+(((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0)))) || \
+(((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0)))) || \
+(((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0)))) || \
+(((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)))) || \
+(((LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0))))))
+#error "Check skb recycle code in this file to match Linux version"
+#endif
+#endif /* NSS_SKB_RECYCLE_SUPPORT */
+
 static int max_ipv4_conn = NSS_DEFAULT_NUM_CONN;
 module_param(max_ipv4_conn, int, S_IRUGO);
 MODULE_PARM_DESC(max_ipv4_conn, "Max number of IPv4 connections");
@@ -1657,6 +1675,115 @@
 	}
 }
 
+#if (NSS_SKB_RECYCLE_SUPPORT == 1)
+/*
+ * nss_skb_can_recycle
+ *	check if skb can be recyled
+ */
+static inline bool nss_skb_can_recycle(struct nss_ctx_instance *nss_ctx,
+	uint32_t if_num, struct sk_buff *nbuf, int min_skb_size)
+{
+	/*
+	 * Don't re-use if this is a virtual interface.
+	 */
+	if (nss_cmn_interface_is_virtual(nss_ctx, if_num)) {
+		return false;
+	}
+
+	/*
+	 * If we have to call a destructor, we can't re-use the buffer?
+	 */
+	if (unlikely(nbuf->destructor != NULL)) {
+		return false;
+	}
+
+	/*
+	 * check skb user count
+	 */
+	if (likely(atomic_read(&nbuf->users) == 1)) {
+		smp_rmb();
+	} else if (likely(!atomic_dec_and_test(&nbuf->users))) {
+		return false;
+	}
+
+#if IS_ENABLED(CONFIG_NF_CONNTRACK)
+	/*
+	 * This check is added to avoid deadlock from nf_conntrack
+	 * when ecm is trying to flush a rule.
+	 */
+	if (unlikely(nbuf->nfct)) {
+		return false;
+	}
+#endif
+
+#ifdef CONFIG_BRIDGE_NETFILTER
+	/*
+	 * This check is added to avoid deadlock from nf_bridge
+	 * when ecm is trying to flush a rule.
+	 */
+	if (unlikely(nbuf->nf_bridge)) {
+		return false;
+	}
+#endif
+
+#ifdef CONFIG_XFRM
+	/*
+	 * If skb has security parameters set do not reuse
+	 */
+	if (unlikely(nbuf->sp)) {
+		return false;
+	}
+#endif
+
+	if (unlikely(irqs_disabled()))
+		return false;
+
+	if (unlikely(skb_shinfo(nbuf)->tx_flags & SKBTX_DEV_ZEROCOPY))
+		return false;
+
+	if (unlikely(skb_is_nonlinear(nbuf)))
+		return false;
+
+	if (unlikely(nbuf->fclone != SKB_FCLONE_UNAVAILABLE))
+		return false;
+
+	min_skb_size = SKB_DATA_ALIGN(min_skb_size + NET_SKB_PAD);
+	if (unlikely(skb_end_pointer(nbuf) - nbuf->head < min_skb_size))
+		return false;
+
+	if (unlikely(skb_cloned(nbuf)))
+		return false;
+
+	return true;
+}
+
+/*
+ * nss_skb_recycle - clean up an skb for reuse
+ *	Recycles the skb to be reused as a receive buffer.
+ *
+ * NOTE: This function does any necessary reference count dropping, and
+ * cleans up the skbuff as if its allocated fresh.
+ */
+void nss_skb_recycle(struct sk_buff *nbuf)
+{
+	struct skb_shared_info *shinfo;
+
+	/*
+	 * Reset all the necessary head state information from skb which
+	 * we found can be recycled for NSS.
+	 */
+	skb_dst_drop(nbuf);
+
+	shinfo = skb_shinfo(nbuf);
+	memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
+	atomic_set(&shinfo->dataref, 1);
+
+	memset(nbuf, 0, offsetof(struct sk_buff, tail));
+	nbuf->data = nbuf->head + NET_SKB_PAD;
+	skb_reset_tail_pointer(nbuf);
+}
+#endif
+
 /*
  * nss_core_send_buffer_simple_skb()
  *	Sends one skb to NSS FW
@@ -1671,6 +1798,10 @@
 	uint16_t mask;
 	uint32_t frag0phyaddr;
 
+#if (NSS_SKB_RECYCLE_SUPPORT == 1)
+	uint16_t sz;
+#endif
+
 	bit_flags = flags | H2N_BIT_FLAG_FIRST_SEGMENT | H2N_BIT_FLAG_LAST_SEGMENT;
 	if (likely(nbuf->ip_summed == CHECKSUM_PARTIAL)) {
 		bit_flags |= H2N_BIT_FLAG_GEN_IP_TRANSPORT_CHECKSUM;
@@ -1681,6 +1812,43 @@
 	mask = desc_if->size - 1;
 	desc = &desc_ring[hlos_index];
 
+#if (NSS_SKB_RECYCLE_SUPPORT == 1)
+	/*
+	 * Check if the skb is recyclable without resetting its fields.
+	 */
+	if (unlikely(!nss_skb_can_recycle(nss_ctx, if_num, nbuf, nss_ctx->max_buf_size))) {
+		goto no_reuse;
+	}
+
+	/*
+	 * We are going to do both Tx and then Rx on this buffer, unmap the Tx
+	 * and then map Rx over the entire buffer.
+	 */
+	sz = max((uint16_t)(nbuf->tail - nbuf->head), (uint16_t)(nss_ctx->max_buf_size + NET_SKB_PAD));
+	frag0phyaddr = (uint32_t)dma_map_single(NULL, nbuf->head, sz, DMA_TO_DEVICE);
+	if (unlikely(dma_mapping_error(NULL, frag0phyaddr))) {
+		goto no_reuse;
+	}
+
+	/*
+	 * We are allowed to re-use the packet
+	 */
+	bit_flags |= H2N_BIT_FLAG_BUFFER_REUSE;
+	nss_core_write_one_descriptor(desc, buffer_type, frag0phyaddr, if_num,
+		(uint32_t)nbuf, (uint16_t)(nbuf->data - nbuf->head), nbuf->len,
+		sz, (uint32_t)nbuf->priority, mss, bit_flags);
+
+	/*
+	 * We are done using the skb fields and can recycle it now
+	 */
+	nss_skb_recycle(nbuf);
+
+	NSS_PKT_STATS_INCREMENT(nss_ctx, &nss_ctx->nss_top->stats_drv[NSS_STATS_DRV_TX_BUFFER_REUSE]);
+	return 1;
+
+no_reuse:
+#endif
+
 	frag0phyaddr = 0;
 	frag0phyaddr = (uint32_t)dma_map_single(NULL, nbuf->head, (nbuf->tail - nbuf->head), DMA_TO_DEVICE);
 	if (unlikely(dma_mapping_error(NULL, frag0phyaddr))) {