[qca-edma] Add stats support for per-precedence stats

Add support for per-precedence counters for ingress/egress packets

Change-Id: I230a5e1cee5ea17ae57ade48d1f8984a3e583147
Signed-off-by: Rakesh Nair <ranair@codeaurora.org>
diff --git a/Makefile b/Makefile
index 8d6404d..c2ad52d 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,7 @@
 
 essedma-objs +=    edma_axi.o \
                    edma.o \
+                   edma_stats.o \
                    edma_ethtool.o
 
 ESSEDMA_INCLUDE = -I$(obj)
diff --git a/edma.c b/edma.c
index 4e83c2e..3932454 100644
--- a/edma.c
+++ b/edma.c
@@ -15,12 +15,15 @@
 
 #include <linux/platform_device.h>
 #include <linux/if_vlan.h>
+#include <linux/kernel.h>
 #include "ess_edma.h"
 #include "edma.h"
 
 extern struct net_device *edma_netdev[EDMA_MAX_PORTID_SUPPORTED];
 bool edma_stp_rstp;
 u16 edma_ath_eth_type;
+extern u8 edma_dscp2ac_tbl[EDMA_PRECEDENCE_MAX];
+extern u8 edma_per_prec_stats_enable;
 
 /* edma_skb_priority_offset()
  *	get edma skb priority
@@ -795,6 +798,14 @@
 				erdr->pending_fill = ret_count;
 			}
 
+			/*
+			 * We increment per-precedence counters for the rx packets
+			 */
+			if (edma_per_prec_stats_enable) {
+				edma_cinfo->edma_ethstats.rx_prec[priority]++;
+				edma_cinfo->edma_ethstats.rx_ac[edma_dscp2ac_tbl[priority]]++;
+			}
+
 			/* At this point skb should go to stack */
 			napi_gro_receive(napi, skb);
 		}
@@ -1372,6 +1383,37 @@
 		}
 	}
 
+	/* If sysctl support for per-precedence stats are enabled */
+	if (edma_per_prec_stats_enable) {
+		struct iphdr *ip_hdr = NULL;
+		struct ipv6hdr *ip6_hdr = NULL;
+		uint8_t precedence = 0xff;
+
+		if (likely(htons(ETH_P_IP) == skb->protocol)) {
+			ip_hdr = (struct iphdr *)skb_network_header(skb);
+			if (ip_hdr && ((ip_hdr->protocol == IPPROTO_UDP) || (ip_hdr->protocol == IPPROTO_TCP)))
+				 precedence = ip_hdr->tos >> EDMA_DSCP_PREC_SHIFT;
+
+			/* Increment per-precedence counters for tx packets
+			 * and set the precedence in the TPD.
+			 */
+			edma_cinfo->edma_ethstats.tx_prec[precedence]++;
+			edma_cinfo->edma_ethstats.tx_ac[edma_dscp2ac_tbl[precedence]]++;
+			tpd->word3 |= precedence << EDMA_TPD_PRIO_SHIFT;
+		} else if (htons(ETH_P_IPV6) == skb->protocol) {
+			ip6_hdr = (struct ipv6hdr *)skb_network_header(skb);
+			if (ip6_hdr && ((ip6_hdr->nexthdr == IPPROTO_UDP) || (ip6_hdr->nexthdr == IPPROTO_TCP)))
+				precedence = ip6_hdr->priority >> EDMA_DSCP6_PREC_SHIFT;
+
+			/* Increment per-precedence counters for tx packets
+			 * and set the precedence in the TPD for v6 packets.
+			 */
+			edma_cinfo->edma_ethstats.tx_prec[precedence]++;
+			edma_cinfo->edma_ethstats.tx_ac[edma_dscp2ac_tbl[precedence]]++;
+			tpd->word3 |= precedence << EDMA_TPD_PRIO_SHIFT;
+		}
+	}
+
 	/* If tpd or sw_desc is still unitiialized then we need to return */
 	if ((!tpd) || (!sw_desc))
 		return -EINVAL;
diff --git a/edma.h b/edma.h
index f58f7fe..330d261 100644
--- a/edma.h
+++ b/edma.h
@@ -89,6 +89,9 @@
 /* tpd word 3 bit 18-28 */
 #define EDMA_TPD_PORT_BITMAP_SHIFT 18
 
+/* tpd word 3 bit 29-31 */
+#define EDMA_TPD_PRIO_SHIFT 29
+
 #define EDMA_TPD_FROM_CPU_SHIFT 25
 
 #define EDMA_FROM_CPU_MASK 0x80
@@ -179,6 +182,16 @@
 
 #define EDMA_GMAC_NO_MDIO_PHY	PHY_MAX_ADDR
 
+#define EDMA_PRECEDENCE_MAX 8
+
+#define EDMA_AC_BK 0	/* Access Category: Background */
+#define EDMA_AC_BE 1	/* Access Category: Best Effort */
+#define EDMA_AC_VI 2	/* Access Category: Video */
+#define EDMA_AC_VO 3	/* Access Category: Voice */
+#define EDMA_AC_MAX 4
+
+#define EDMA_DSCP2AC_INPUT_PARAMS_MAX 2
+
 extern int ssdk_rfs_ipct_rule_set(__be32 ip_src, __be32 ip_dst,
 				  __be16 sport, __be16 dport,
 				  uint8_t proto, u16 loadbalance, bool action);
@@ -234,6 +247,10 @@
 	u64 rx_q7_byte;
 	u64 tx_desc_error;
 	u64 rx_alloc_fail_ctr;
+	u64 tx_prec[EDMA_PRECEDENCE_MAX];
+	u64 rx_prec[EDMA_PRECEDENCE_MAX];
+	u64 rx_ac[EDMA_AC_MAX];
+	u64 tx_ac[EDMA_AC_MAX];
 };
 
 struct edma_mdio_data {
@@ -465,4 +482,14 @@
 void edma_change_rx_coalesce(int usecs);
 void edma_get_tx_rx_coalesce(u32 *reg_val);
 void edma_clear_irq_status(void);
+
+int edma_dscp2ac_mapping_update(struct ctl_table *table, int write,
+				void __user *buffer, size_t *lenp,
+				loff_t *ppos);
+int edma_per_prec_stats_enable_handler(struct ctl_table *table, int write,
+				       void __user *buffer, size_t *lenp,
+				       loff_t *ppos);
+int edma_prec_stats_reset_handler(struct ctl_table *table, int write,
+				  void __user *buffer, size_t *lenp,
+				  loff_t *ppos);
 #endif /* _EDMA_H_ */
diff --git a/edma_axi.c b/edma_axi.c
index c6722bd..f4ec659 100644
--- a/edma_axi.c
+++ b/edma_axi.c
@@ -73,6 +73,11 @@
 char edma_tx_irq[16][64];
 char edma_rx_irq[8][64];
 struct net_device *edma_netdev[EDMA_MAX_PORTID_SUPPORTED];
+
+extern u8 edma_dscp2ac_tbl[EDMA_PRECEDENCE_MAX];
+extern u8 edma_per_prec_stats_enable;
+extern u8 edma_prec_stats_reset;
+
 static struct phy_device *edma_phydev[EDMA_MAX_PORTID_SUPPORTED];
 static int edma_link_detect_bmp;
 static int phy_dev_state[EDMA_MAX_PORTID_SUPPORTED];
@@ -1004,6 +1009,27 @@
 		.mode           = 0644,
 		.proc_handler   = edma_disable_rss_func
 	},
+	{
+		.procname       = "dscp2ac",
+		.data           = &edma_dscp2ac_tbl,
+		.maxlen         = sizeof(int),
+		.mode           = 0644,
+		.proc_handler   = edma_dscp2ac_mapping_update
+	},
+	{
+		.procname       = "per_prec_stats_enable",
+		.data           = &edma_per_prec_stats_enable,
+		.maxlen         = sizeof(int),
+		.mode           = 0644,
+		.proc_handler   = edma_per_prec_stats_enable_handler,
+	},
+	{
+		.procname       = "per_prec_stats_reset",
+		.data           = &edma_prec_stats_reset,
+		.maxlen         = sizeof(int),
+		.mode           = 0644,
+		.proc_handler   = edma_prec_stats_reset_handler,
+	},
 	{}
 };
 
@@ -1597,6 +1623,12 @@
 	edma_stats_timer.function = edma_statistics_timer; /* timer handler */
 	add_timer(&edma_stats_timer);
 
+	/*
+	 * Initialize dscp2ac mapping table
+	 */
+	for (i = 0 ; i < EDMA_PRECEDENCE_MAX ; i++)
+		edma_dscp2ac_tbl[i] = EDMA_AC_BE;
+
 	return 0;
 
 edma_phy_attach_fail:
diff --git a/edma_ethtool.c b/edma_ethtool.c
index b257d68..4c20e40 100644
--- a/edma_ethtool.c
+++ b/edma_ethtool.c
@@ -78,6 +78,30 @@
 	{"rx_q7_byte", EDMA_STAT(rx_q7_byte)},
 	{"tx_desc_error", EDMA_STAT(tx_desc_error)},
 	{"rx_alloc_fail_ctr", EDMA_STAT(rx_alloc_fail_ctr)},
+	{"rx_prec_0", EDMA_STAT(rx_prec[0])},
+	{"rx_prec_1", EDMA_STAT(rx_prec[1])},
+	{"rx_prec_2", EDMA_STAT(rx_prec[2])},
+	{"rx_prec_3", EDMA_STAT(rx_prec[3])},
+	{"rx_prec_4", EDMA_STAT(rx_prec[4])},
+	{"rx_prec_5", EDMA_STAT(rx_prec[5])},
+	{"rx_prec_6", EDMA_STAT(rx_prec[6])},
+	{"rx_prec_7", EDMA_STAT(rx_prec[7])},
+	{"rx_ac_bk", EDMA_STAT(rx_ac[EDMA_AC_BK])},
+	{"rx_ac_be", EDMA_STAT(rx_ac[EDMA_AC_BE])},
+	{"rx_ac_vi", EDMA_STAT(rx_ac[EDMA_AC_VI])},
+	{"rx_ac_vo", EDMA_STAT(rx_ac[EDMA_AC_VO])},
+	{"tx_prec_0", EDMA_STAT(tx_prec[0])},
+	{"tx_prec_1", EDMA_STAT(tx_prec[1])},
+	{"tx_prec_2", EDMA_STAT(tx_prec[2])},
+	{"tx_prec_3", EDMA_STAT(tx_prec[3])},
+	{"tx_prec_4", EDMA_STAT(tx_prec[4])},
+	{"tx_prec_5", EDMA_STAT(tx_prec[5])},
+	{"tx_prec_6", EDMA_STAT(tx_prec[6])},
+	{"tx_prec_7", EDMA_STAT(tx_prec[7])},
+	{"tx_ac_bk", EDMA_STAT(tx_ac[EDMA_AC_BK])},
+	{"tx_ac_be", EDMA_STAT(tx_ac[EDMA_AC_BE])},
+	{"tx_ac_vi", EDMA_STAT(tx_ac[EDMA_AC_VI])},
+	{"tx_ac_vo", EDMA_STAT(tx_ac[EDMA_AC_VO])},
 };
 
 #define EDMA_STATS_LEN ARRAY_SIZE(edma_gstrings_stats)
diff --git a/edma_stats.c b/edma_stats.c
new file mode 100644
index 0000000..961e56c
--- /dev/null
+++ b/edma_stats.c
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ */
+
+#include "edma.h"
+#include "ess_edma.h"
+
+#define EDMA_SYSCTL_RBUF_MAX_SIZE 300
+#define EDMA_SYSCTL_WBUF_MAX_SIZE 7
+
+extern struct net_device *edma_netdev[EDMA_MAX_PORTID_SUPPORTED];
+
+const char *edma_access_category[] = {
+			"BK",
+			"BE",
+			"VI",
+			"VO",
+};
+
+u8 edma_dscp2ac_tbl[EDMA_PRECEDENCE_MAX];
+u8 edma_per_prec_stats_enable __read_mostly = 0;
+u8 edma_prec_stats_reset __read_mostly = 0;
+
+/* edma_dscp2ac_mapping_update()
+ *	Map dscp to user-provided Access category values
+ */
+int edma_dscp2ac_mapping_update(struct ctl_table *table, int write,
+				       void __user *buffer, size_t *lenp,
+				       loff_t *ppos)
+{
+	char *tokens[EDMA_DSCP2AC_INPUT_PARAMS_MAX];
+	char *readbuf = NULL;
+	size_t add_len = 0;
+	int len, i, ret;
+	u8 precedence, ac;
+	char writebuf[EDMA_SYSCTL_WBUF_MAX_SIZE];
+	loff_t w_offset = 0;
+	char *str;
+	int count;
+
+	if (write) {
+		add_len = simple_write_to_buffer(writebuf, sizeof(writebuf), &w_offset,  buffer, EDMA_SYSCTL_WBUF_MAX_SIZE - 1);
+		writebuf[add_len] = '\0';
+
+		count = 0;
+		str = writebuf;
+		tokens[count] = strsep(&str, " ");
+		while (tokens[count] != NULL) {
+			count++;
+			if (count == EDMA_DSCP2AC_INPUT_PARAMS_MAX) {
+				break;
+			}
+
+			tokens[count] = strsep(&str, " ");
+		}
+
+		ret = sscanf(tokens[0], "%hhu", &precedence);
+		if (ret != 1) {
+			pr_err("Failed to read precedence token\n");
+			return -EFAULT;
+		}
+
+		ret = sscanf(tokens[1], "%hhu", &ac);
+		if (ret != 1) {
+			pr_err("Failed to read AC token\n");
+			return -EFAULT;
+		}
+
+		/* Use first 3-bits of the 6-bit input DSCP for precedence */
+		precedence = precedence >> 3;
+
+		if (precedence > 7 || ac > 3 ) {
+			pr_info(" Invalid Input. Valid Range: precedence - 0-7, ac - 0-3\n");
+			return -EINVAL;
+		}
+
+		/* Update access category in the global dscp table */
+		edma_dscp2ac_tbl[precedence] = ac;
+
+		return 0;
+	}
+
+	readbuf = vmalloc(EDMA_SYSCTL_RBUF_MAX_SIZE);
+	if (!readbuf) {
+		pr_err("Failed to allocate Memory\n");
+		return -EINVAL;
+	}
+
+	len = scnprintf(readbuf + add_len, 45 ,"%s \n", "precedence:   0   1   2   3   4   5   6   7\n");
+	add_len += len;
+	len = scnprintf(readbuf + add_len, 15, "%s", "AC:          ");
+	add_len += len;
+
+	for (i = 0; i < EDMA_PRECEDENCE_MAX; i++) {
+		len = scnprintf(readbuf + add_len, 6, " %s ", edma_access_category[edma_dscp2ac_tbl[i]]);
+		add_len += len;
+	}
+
+	len = scnprintf(readbuf + add_len, 4, "\n\n");
+	add_len += len;
+
+	/* Add Usage/help details in the read buffer */
+	len = scnprintf(readbuf + add_len, 40, "Usage: echo \"<dscp> <AC>\" > dscp2ac\n");
+	add_len += len;
+	len = scnprintf(readbuf + add_len, 37, "dscp: 6 bits dscp value in Decimal\n");
+	add_len += len;
+	len = scnprintf(readbuf + add_len, 55, "AC: 0 --> BK\n    1 --> BE\n    2 --> VI\n    3 --> VO\n");
+	add_len += len;
+
+	add_len = simple_read_from_buffer(buffer, *lenp, ppos, readbuf, add_len);
+	*lenp = add_len;
+
+	vfree(readbuf);
+
+	return 0;
+}
+
+/* edma_per_prec_stats_enable_handler()
+ *	Enable per precedence statistics
+ */
+int edma_per_prec_stats_enable_handler(struct ctl_table *table, int write,
+				   void __user *buffer, size_t *lenp,
+				   loff_t *ppos)
+{
+	int ret;
+
+	ret = proc_dointvec(table, write, buffer, lenp, ppos);
+
+	if ((!write) || (ret))
+		return ret;
+
+	switch (edma_per_prec_stats_enable) {
+	case 0:
+	case 1:
+		break;
+	default:
+		pr_err("Invalid input. Valid values: <0|1>\n");
+		ret = -1;
+		break;
+	}
+
+	return ret;
+}
+
+/* edma_prec_stats_reset_handler()
+ *	Reset per-precedence statistics
+ */
+int edma_prec_stats_reset_handler(struct ctl_table *table, int write,
+				   void __user *buffer, size_t *lenp,
+				   loff_t *ppos)
+{
+	struct edma_adapter *adapter;
+	struct edma_common_info *edma_cinfo;
+	int ret;
+
+	if (!edma_netdev[0]) {
+		pr_err("Invalid Netdevice\n");
+		return -1;
+	}
+
+	adapter = netdev_priv(edma_netdev[0]);
+	edma_cinfo = adapter->edma_cinfo;
+
+	ret = proc_dointvec(table, write, buffer, lenp, ppos);
+
+	if ((!write) || (ret))
+		return ret;
+
+	switch (edma_prec_stats_reset) {
+	case 0:
+		break;
+	case 1:
+		memset(&edma_cinfo->edma_ethstats.tx_prec, 0, sizeof(u64) * EDMA_PRECEDENCE_MAX);
+		memset(&edma_cinfo->edma_ethstats.rx_prec, 0, sizeof(u64) * EDMA_PRECEDENCE_MAX);
+		memset(&edma_cinfo->edma_ethstats.tx_ac, 0, sizeof(u64) * EDMA_AC_MAX);
+		memset(&edma_cinfo->edma_ethstats.rx_ac, 0, sizeof(u64) * EDMA_AC_MAX);
+		break;
+	default:
+		pr_err("Invalid input\n");
+		ret = -1;
+		break;
+	}
+
+	return ret;
+}
+
diff --git a/ess_edma.h b/ess_edma.h
index bd5ef0b..e5a5738 100644
--- a/ess_edma.h
+++ b/ess_edma.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 - 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014 - 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
@@ -333,4 +333,8 @@
 #define EDMA_RRD_PORT_TYPE_MASK 0x1F
 #define EDMA_RRD_L4OFFSET_SHIFT 4
 #define EDMA_RRD_L4OFFSET_MASK 0xFF
+
+/* Shift Bit positions for 3-bit precedence */
+#define EDMA_DSCP_PREC_SHIFT 5
+#define EDMA_DSCP6_PREC_SHIFT 1
 #endif /* _ESS_EDMA_H_ */