qcacmn: Enable TSO Stats for Lithium based products

Add support to account for TSO jumbo packets on the
Tx path and print the statistics using dumpStats 3.

Change-Id: I6cc446df5c84e3ac436d922935fcd559e0704ec5
CRs-Fixed: 2356244
diff --git a/dp/inc/cdp_txrx_ops.h b/dp/inc/cdp_txrx_ops.h
index a2d096b..bfa2839 100644
--- a/dp/inc/cdp_txrx_ops.h
+++ b/dp/inc/cdp_txrx_ops.h
@@ -1165,7 +1165,8 @@
  * @stats:
  */
 struct cdp_mob_stats_ops {
-	void (*clear_stats)(uint16_t bitmap);
+	QDF_STATUS
+		(*clear_stats)(struct cdp_soc *soc, uint8_t bitmap);
 	int (*stats)(uint8_t vdev_id, char *buffer, unsigned buf_len);
 };
 
diff --git a/dp/inc/cdp_txrx_stats.h b/dp/inc/cdp_txrx_stats.h
index 69814a0..d367e02 100644
--- a/dp/inc/cdp_txrx_stats.h
+++ b/dp/inc/cdp_txrx_stats.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2017,2019 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
@@ -25,22 +25,23 @@
 #define _CDP_TXRX_STATS_H_
 #include <cdp_txrx_ops.h>
 
-static inline void
-cdp_clear_stats(ol_txrx_soc_handle soc, uint16_t bitmap)
+static inline QDF_STATUS
+cdp_clear_stats(ol_txrx_soc_handle soc, uint8_t bitmap)
 {
 
 	if (!soc || !soc->ops) {
 		QDF_TRACE(QDF_MODULE_ID_CDP, QDF_TRACE_LEVEL_DEBUG,
 				"%s: Invalid Instance", __func__);
 		QDF_BUG(0);
-		return;
+		return QDF_STATUS_E_INVAL;
 	}
 
 	if (!soc->ops->mob_stats_ops ||
 	    !soc->ops->mob_stats_ops->clear_stats)
-		return;
+		return QDF_STATUS_E_INVAL;
 
-	soc->ops->mob_stats_ops->clear_stats(bitmap);
+	return soc->ops->mob_stats_ops->clear_stats((struct cdp_soc *)soc,
+						    bitmap);
 }
 
 static inline int
diff --git a/dp/inc/cdp_txrx_stats_struct.h b/dp/inc/cdp_txrx_stats_struct.h
index ca32697..9a86e2e 100644
--- a/dp/inc/cdp_txrx_stats_struct.h
+++ b/dp/inc/cdp_txrx_stats_struct.h
@@ -23,6 +23,11 @@
  */
 #ifndef _CDP_TXRX_STATS_STRUCT_H_
 #define _CDP_TXRX_STATS_STRUCT_H_
+
+#ifdef FEATURE_TSO_STATS
+#include <qdf_types.h>
+#endif /* FEATURE_TSO_STATS */
+
 #define TXRX_STATS_LEVEL_OFF   0
 #define TXRX_STATS_LEVEL_BASIC 1
 #define TXRX_STATS_LEVEL_FULL  2
@@ -171,6 +176,15 @@
 	RU_996_INDEX,
 };
 
+#ifdef FEATURE_TSO_STATS
+/* Number of TSO Packet Statistics captured */
+#define CDP_MAX_TSO_PACKETS 5
+/* Information for Number of Segments for a TSO Packet captured */
+#define CDP_MAX_TSO_SEGMENTS 2
+/* Information for Number of Fragments for a TSO Segment captured */
+#define CDP_MAX_TSO_FRAGMENTS 6
+#endif /* FEATURE_TSO_STATS */
+
 /* Different Packet Types */
 enum cdp_packet_type {
 	DOT11_A = 0,
@@ -444,6 +458,68 @@
 	uint32_t mpdu_tried;
 };
 
+#ifdef FEATURE_TSO_STATS
+/**
+ * struct cdp_tso_seg_histogram - Segment histogram for TCP Packets
+ * @segs_1: packets with single segments
+ * @segs_2_5: packets with 2-5 segments
+ * @segs_6_10: packets with 6-10 segments
+ * @segs_11_15: packets with 11-15 segments
+ * @segs_16_20: packets with 16-20 segments
+ * @segs_20_plus: packets with 20 plus segments
+ */
+struct cdp_tso_seg_histogram {
+	uint64_t segs_1;
+	uint64_t segs_2_5;
+	uint64_t segs_6_10;
+	uint64_t segs_11_15;
+	uint64_t segs_16_20;
+	uint64_t segs_20_plus;
+};
+
+/**
+ * struct cdp_tso_packet_info - Stats for TSO segments within a TSO packet
+ * @tso_seg: TSO Segment information
+ * @num_seg: Number of segments
+ * @tso_packet_len: Size of the tso packet
+ * @tso_seg_idx: segment number
+ */
+struct cdp_tso_packet_info {
+	struct qdf_tso_seg_t tso_seg[CDP_MAX_TSO_SEGMENTS];
+	uint8_t num_seg;
+	size_t tso_packet_len;
+	uint32_t tso_seg_idx;
+};
+
+/**
+ * struct cdp_tso_info - stats for tso packets
+ * @tso_packet_info: TSO packet information
+ */
+struct cdp_tso_info {
+	struct cdp_tso_packet_info tso_packet_info[CDP_MAX_TSO_PACKETS];
+};
+#endif /* FEATURE_TSO_STATS */
+
+/**
+ * struct cdp_tso_stats -  TSO stats information
+ * @num_tso_pkts: Total number of TSO Packets
+ * @tso_comp: Total tso packet completions
+ * @dropped_host: TSO packets dropped by host
+ * @dropped_target: TSO packets_dropped by target
+ * @tso_info: Per TSO packet counters
+ * @seg_histogram: TSO histogram stats
+ */
+struct cdp_tso_stats {
+	struct cdp_pkt_info num_tso_pkts;
+	uint32_t tso_comp;
+	struct cdp_pkt_info dropped_host;
+	uint32_t dropped_target;
+#ifdef FEATURE_TSO_STATS
+	struct cdp_tso_info tso_info;
+	struct cdp_tso_seg_histogram seg_histogram;
+#endif /* FEATURE_TSO_STATS */
+};
+
 /* struct cdp_tx_stats - tx stats
  * @cdp_pkt_info comp_pkt: Pkt Info for which completions were received
  * @cdp_pkt_info ucast: Unicast Packet Count
@@ -771,15 +847,6 @@
 		uint32_t invalid_raw_pkt_datatype;
 	} raw;
 
-	/* TSO packets info */
-	struct {
-		uint32_t num_seg;
-		struct cdp_pkt_info tso_pkt;
-		struct cdp_pkt_info non_tso_pkts;
-		struct cdp_pkt_info  dropped_host;
-		uint32_t dropped_target;
-	} tso;
-
 	/* Scatter Gather packet info */
 	struct {
 		struct cdp_pkt_info sg_pkt;
@@ -821,17 +888,20 @@
 	uint32_t cce_classified;
 	uint32_t cce_classified_raw;
 	struct cdp_pkt_info sniffer_rcvd;
+	struct cdp_tso_stats tso_stats;
 };
 
 /* struct cdp_vdev_stats - vdev stats structure
  * @tx_i: ingress tx stats
  * @tx: cdp tx stats
  * @rx: cdp rx stats
+ * @tso_stats: tso stats
  */
 struct cdp_vdev_stats {
 	struct cdp_tx_ingress_stats tx_i;
 	struct cdp_tx_stats tx;
 	struct cdp_rx_stats rx;
+	struct cdp_tso_stats tso_stats;
 };
 
 /* struct cdp_peer_stats - peer stats structure
@@ -1381,6 +1451,8 @@
 		uint32_t data_rx_ppdu;
 		uint32_t data_users[OFDMA_NUM_USERS];
 	} ul_ofdma;
+
+	struct cdp_tso_stats tso_stats;
 };
 
 #ifdef QCA_ENH_V3_STATS_SUPPORT
diff --git a/dp/wifi3.0/dp_internal.h b/dp/wifi3.0/dp_internal.h
index 14e0120..c669e9b 100644
--- a/dp/wifi3.0/dp_internal.h
+++ b/dp/wifi3.0/dp_internal.h
@@ -315,7 +315,6 @@
 		} \
 	}  while (0)
 
-
 #else
 #define DP_HIST_INIT()
 #define DP_HIST_PACKET_COUNT_INC(_pdev_id)
@@ -323,7 +322,134 @@
 #define DP_RX_HISTOGRAM_UPDATE(_pdev, _p_cntrs)
 #define DP_RX_HIST_STATS_PER_PDEV()
 #define DP_TX_HIST_STATS_PER_PDEV()
-#endif
+#endif /* DISABLE_DP_STATS */
+
+#ifdef FEATURE_TSO_STATS
+/**
+ * dp_init_tso_stats() - Clear tso stats
+ * @pdev: pdev handle
+ *
+ * Return: None
+ */
+static inline
+void dp_init_tso_stats(struct dp_pdev *pdev)
+{
+	if (pdev) {
+		qdf_mem_zero(&((pdev)->stats.tso_stats),
+			     sizeof((pdev)->stats.tso_stats));
+		qdf_atomic_init(&pdev->tso_idx);
+	}
+}
+
+/**
+ * dp_stats_tso_segment_histogram_update() - TSO Segment Histogram
+ * @pdev: pdev handle
+ * @_p_cntrs: number of tso segments for a tso packet
+ *
+ * Return: None
+ */
+void dp_stats_tso_segment_histogram_update(struct dp_pdev *pdev,
+					   uint8_t _p_cntrs);
+
+/**
+ * dp_tso_segment_update() - Collect tso segment information
+ * @pdev: pdev handle
+ * @stats_idx: tso packet number
+ * @idx: tso segment number
+ * @seg: tso segment
+ *
+ * Return: None
+ */
+void dp_tso_segment_update(struct dp_pdev *pdev,
+			   uint32_t stats_idx,
+			   uint8_t idx,
+			   struct qdf_tso_seg_t seg);
+
+/**
+ * dp_tso_packet_update() - TSO Packet information
+ * @pdev: pdev handle
+ * @stats_idx: tso packet number
+ * @msdu: nbuf handle
+ * @num_segs: tso segments
+ *
+ * Return: None
+ */
+void dp_tso_packet_update(struct dp_pdev *pdev, uint32_t stats_idx,
+			  qdf_nbuf_t msdu, uint16_t num_segs);
+
+/**
+ * dp_tso_segment_stats_update() - TSO Segment stats
+ * @pdev: pdev handle
+ * @stats_seg: tso segment list
+ * @stats_idx: tso packet number
+ *
+ * Return: None
+ */
+void dp_tso_segment_stats_update(struct dp_pdev *pdev,
+				 struct qdf_tso_seg_elem_t *stats_seg,
+				 uint32_t stats_idx);
+
+/**
+ * dp_print_tso_stats() - dump tso statistics
+ * @soc:soc handle
+ * @level: verbosity level
+ *
+ * Return: None
+ */
+void dp_print_tso_stats(struct dp_soc *soc,
+			enum qdf_stats_verbosity_level level);
+
+/**
+ * dp_txrx_clear_tso_stats() - clear tso stats
+ * @soc: soc handle
+ *
+ * Return: None
+ */
+void dp_txrx_clear_tso_stats(struct dp_soc *soc);
+#else
+static inline
+void dp_init_tso_stats(struct dp_pdev *pdev)
+{
+}
+
+static inline
+void dp_stats_tso_segment_histogram_update(struct dp_pdev *pdev,
+					   uint8_t _p_cntrs)
+{
+}
+
+static inline
+void dp_tso_segment_update(struct dp_pdev *pdev,
+			   uint32_t stats_idx,
+			   uint32_t idx,
+			   struct qdf_tso_seg_t seg)
+{
+}
+
+static inline
+void dp_tso_packet_update(struct dp_pdev *pdev, uint32_t stats_idx,
+			  qdf_nbuf_t msdu, uint16_t num_segs)
+{
+}
+
+static inline
+void dp_tso_segment_stats_update(struct dp_pdev *pdev,
+				 struct qdf_tso_seg_elem_t *stats_seg,
+				 uint32_t stats_idx)
+{
+}
+
+static inline
+void dp_print_tso_stats(struct dp_soc *soc,
+			enum qdf_stats_verbosity_level level)
+{
+}
+
+static inline
+void dp_txrx_clear_tso_stats(struct dp_soc *soc)
+{
+}
+#endif /* FEATURE_TSO_STATS */
 
 #define DP_HTT_T2H_HP_PIPE 5
 static inline void dp_update_pdev_stats(struct dp_pdev *tgtobj,
@@ -478,9 +604,6 @@
 	DP_STATS_AGGR_PKT(tgtobj, srcobj, tx_i.inspect_pkts);
 	DP_STATS_AGGR_PKT(tgtobj, srcobj, tx_i.raw.raw_pkt);
 	DP_STATS_AGGR(tgtobj, srcobj, tx_i.raw.dma_map_error);
-	DP_STATS_AGGR_PKT(tgtobj, srcobj, tx_i.tso.tso_pkt);
-	DP_STATS_AGGR(tgtobj, srcobj, tx_i.tso.dropped_host.num);
-	DP_STATS_AGGR(tgtobj, srcobj, tx_i.tso.dropped_target);
 	DP_STATS_AGGR(tgtobj, srcobj, tx_i.sg.dropped_host.num);
 	DP_STATS_AGGR(tgtobj, srcobj, tx_i.sg.dropped_target);
 	DP_STATS_AGGR_PKT(tgtobj, srcobj, tx_i.sg.sg_pkt);
@@ -511,8 +634,6 @@
 		tgtobj->stats.tx_i.dropped.desc_na.num +
 		tgtobj->stats.tx_i.dropped.res_full;
 
-	tgtobj->stats.tx_i.tso.num_seg =
-		srcobj->stats.tx_i.tso.num_seg;
 }
 
 static inline void dp_update_vdev_stats(struct cdp_vdev_stats *tgtobj,
diff --git a/dp/wifi3.0/dp_main.c b/dp/wifi3.0/dp_main.c
index dcb62cb..c397411 100644
--- a/dp/wifi3.0/dp_main.c
+++ b/dp/wifi3.0/dp_main.c
@@ -3598,6 +3598,8 @@
 
 	pdev->num_tx_allowed = wlan_cfg_get_num_tx_desc(soc->wlan_cfg_ctx);
 
+	dp_init_tso_stats(pdev);
+
 	if (dp_htt_ppdu_stats_attach(pdev) != QDF_STATUS_SUCCESS)
 		goto fail1;
 
@@ -8328,7 +8330,7 @@
 		break;
 
 	case CDP_TXRX_TSO_STATS:
-		/* TODO: NOT IMPLEMENTED */
+		dp_print_tso_stats(soc, level);
 		break;
 
 	case CDP_DUMP_TX_FLOW_POOL_INFO:
@@ -8353,6 +8355,38 @@
 
 }
 
+/**
+ * dp_txrx_clear_dump_stats() - clear dumpStats
+ * @soc- soc handle
+ * @value - stats option
+ *
+ * Return: 0 - Success, non-zero - failure
+ */
+static
+QDF_STATUS dp_txrx_clear_dump_stats(struct cdp_soc *psoc, uint8_t value)
+{
+	struct dp_soc *soc =
+		(struct dp_soc *)psoc;
+	QDF_STATUS status = QDF_STATUS_SUCCESS;
+
+	if (!soc) {
+		dp_err("%s: soc is NULL", __func__);
+		return QDF_STATUS_E_INVAL;
+	}
+
+	switch (value) {
+	case CDP_TXRX_TSO_STATS:
+		dp_txrx_clear_tso_stats(soc);
+		break;
+
+	default:
+		status = QDF_STATUS_E_INVAL;
+		break;
+	}
+
+	return status;
+}
+
 #ifdef QCA_LL_TX_FLOW_CONTROL_V2
 /**
  * dp_update_flow_control_parameters() - API to store datapath
@@ -9531,7 +9565,7 @@
 };
 
 static struct cdp_mob_stats_ops dp_ops_mob_stats = {
-	/* WIFI 3.0 DP NOT IMPLEMENTED YET */
+	.clear_stats = dp_txrx_clear_dump_stats,
 };
 
 /*
diff --git a/dp/wifi3.0/dp_stats.c b/dp/wifi3.0/dp_stats.c
index 4077be0..8825af0 100644
--- a/dp/wifi3.0/dp_stats.c
+++ b/dp/wifi3.0/dp_stats.c
@@ -4624,6 +4624,62 @@
 	}
 }
 
+#ifdef FEATURE_TSO_STATS
+/**
+ * dp_print_tso_seg_stats - tso segment stats
+ * @pdev: pdev handle
+ * @id: tso packet id
+ *
+ * Return: None
+ */
+static void dp_print_tso_seg_stats(struct dp_pdev *pdev, uint32_t id)
+{
+	uint8_t num_seg;
+	uint32_t segid;
+
+	/* TSO LEVEL 2 - SEGMENT INFO */
+	num_seg = pdev->stats.tso_stats.tso_info.tso_packet_info[id].num_seg;
+	for (segid = 0; segid < CDP_MAX_TSO_SEGMENTS && segid < num_seg; segid++) {
+		DP_PRINT_STATS(
+			  "Segment id:[%u] fragments: %u | Segment Length %u | TCP Seq no.: %u | ip_id: %u",
+			  segid,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].num_frags,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].total_len,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.tcp_seq_num,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.ip_id);
+		DP_PRINT_STATS(
+			  "fin: %u syn: %u rst: %u psh: %u ack: %u urg: %u ece: %u cwr: %u ns: %u",
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.fin,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.syn,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.rst,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.psh,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.ack,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.urg,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.ece,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.cwr,
+			  pdev->stats.tso_stats.tso_info.tso_packet_info[id]
+			  .tso_seg[segid].tso_flags.ns);
+	}
+}
+#else
+static inline
+void dp_print_tso_seg_stats(struct dp_pdev *pdev, uint32_t id)
+{
+}
+#endif /* FEATURE_TSO_STATS */
+
 /**
  * dp_print_mon_ring_stats_from_hal() - Print stat for monitor rings based
  *					on target
@@ -5369,15 +5425,6 @@
 		       pdev->stats.tx_i.sg.dropped_host.num);
 	DP_PRINT_STATS("	Dropped By Target = %d",
 		       pdev->stats.tx_i.sg.dropped_target);
-	DP_PRINT_STATS("TSO:");
-	DP_PRINT_STATS("	Number of Segments = %d",
-		       pdev->stats.tx_i.tso.num_seg);
-	DP_PRINT_STATS("	Packets = %d",
-		       pdev->stats.tx_i.tso.tso_pkt.num);
-	DP_PRINT_STATS("	Bytes = %llu",
-		       pdev->stats.tx_i.tso.tso_pkt.bytes);
-	DP_PRINT_STATS("	Dropped By Host = %d",
-		       pdev->stats.tx_i.tso.dropped_host.num);
 	DP_PRINT_STATS("Mcast Enhancement:");
 	DP_PRINT_STATS("	Packets = %d",
 		       pdev->stats.tx_i.mcast_en.mcast_pkt.num);
@@ -5712,3 +5759,161 @@
 	DP_PRINT_STATS("REO Error(0-14):%s", reo_error);
 }
 
+#ifdef FEATURE_TSO_STATS
+void dp_print_tso_stats(struct dp_soc *soc,
+			enum qdf_stats_verbosity_level level)
+{
+	uint8_t loop_pdev;
+	uint32_t id;
+	struct dp_pdev *pdev;
+
+	for (loop_pdev = 0; loop_pdev < soc->pdev_count; loop_pdev++) {
+		pdev = soc->pdev_list[loop_pdev];
+		DP_PRINT_STATS("TSO Statistics\n");
+		DP_PRINT_STATS(
+			  "From stack: %d | Successful completions: %d | TSO Packets: %d | TSO Completions: %d",
+			  pdev->stats.tx_i.rcvd.num,
+			  pdev->stats.tx.tx_success.num,
+			  pdev->stats.tso_stats.num_tso_pkts.num,
+			  pdev->stats.tso_stats.tso_comp);
+
+		for (id = 0; id < CDP_MAX_TSO_PACKETS; id++) {
+			/* TSO LEVEL 1 - PACKET INFO */
+			DP_PRINT_STATS(
+				  "Packet_Id:[%u]: Packet Length %lu | No. of segments: %u",
+				  id,
+				  pdev->stats.tso_stats.tso_info
+				  .tso_packet_info[id].tso_packet_len,
+				  pdev->stats.tso_stats.tso_info
+				  .tso_packet_info[id].num_seg);
+			/* TSO LEVEL 2 */
+			if (level == QDF_STATS_VERBOSITY_LEVEL_HIGH)
+				dp_print_tso_seg_stats(pdev, id);
+		}
+
+		DP_PRINT_STATS(
+			  "TSO Histogram: Single: %llu | 2-5 segs: %llu | 6-10: %llu segs | 11-15 segs: %llu | 16-20 segs: %llu | 20+ segs: %llu",
+			  pdev->stats.tso_stats.seg_histogram.segs_1,
+			  pdev->stats.tso_stats.seg_histogram.segs_2_5,
+			  pdev->stats.tso_stats.seg_histogram.segs_6_10,
+			  pdev->stats.tso_stats.seg_histogram.segs_11_15,
+			  pdev->stats.tso_stats.seg_histogram.segs_16_20,
+			  pdev->stats.tso_stats.seg_histogram.segs_20_plus);
+	}
+}
+
+void dp_stats_tso_segment_histogram_update(struct dp_pdev *pdev,
+					   uint8_t _p_cntrs)
+{
+	if (_p_cntrs == 1) {
+		DP_STATS_INC(pdev,
+			     tso_stats.seg_histogram.segs_1, 1);
+	} else if (_p_cntrs >= 2 && _p_cntrs <= 5) {
+		DP_STATS_INC(pdev,
+			     tso_stats.seg_histogram.segs_2_5, 1);
+	} else if (_p_cntrs > 5 && _p_cntrs <= 10) {
+		DP_STATS_INC(pdev,
+			     tso_stats.seg_histogram.segs_6_10, 1);
+	} else if (_p_cntrs > 10 && _p_cntrs <= 15) {
+		DP_STATS_INC(pdev,
+			     tso_stats.seg_histogram.segs_11_15, 1);
+	} else if (_p_cntrs > 15 && _p_cntrs <= 20) {
+		DP_STATS_INC(pdev,
+			     tso_stats.seg_histogram.segs_16_20, 1);
+	} else if (_p_cntrs > 20) {
+		DP_STATS_INC(pdev,
+			     tso_stats.seg_histogram.segs_20_plus, 1);
+	}
+}
+
+void dp_tso_segment_update(struct dp_pdev *pdev,
+			   uint32_t stats_idx,
+			   uint8_t idx,
+			   struct qdf_tso_seg_t seg)
+{
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].num_frags,
+		     seg.num_frags);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].total_len,
+		     seg.total_len);
+
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.tso_enable,
+		     seg.tso_flags.tso_enable);
+
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.fin,
+		     seg.tso_flags.fin);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.syn,
+		     seg.tso_flags.syn);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.rst,
+		     seg.tso_flags.rst);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.psh,
+		     seg.tso_flags.psh);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.ack,
+		     seg.tso_flags.ack);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.urg,
+		     seg.tso_flags.urg);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.ece,
+		     seg.tso_flags.ece);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.cwr,
+		     seg.tso_flags.cwr);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.ns,
+		     seg.tso_flags.ns);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.tcp_seq_num,
+		     seg.tso_flags.tcp_seq_num);
+	DP_STATS_UPD(pdev, tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_seg[idx].tso_flags.ip_id,
+		     seg.tso_flags.ip_id);
+}
+
+void dp_tso_packet_update(struct dp_pdev *pdev, uint32_t stats_idx,
+			  qdf_nbuf_t msdu, uint16_t num_segs)
+{
+	DP_STATS_UPD(pdev,
+		     tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .num_seg,
+		     num_segs);
+
+	DP_STATS_UPD(pdev,
+		     tso_stats.tso_info.tso_packet_info[stats_idx]
+		     .tso_packet_len,
+		     qdf_nbuf_get_tcp_payload_len(msdu));
+}
+
+void dp_tso_segment_stats_update(struct dp_pdev *pdev,
+				 struct qdf_tso_seg_elem_t *stats_seg,
+				 uint32_t stats_idx)
+{
+	uint8_t tso_seg_idx = 0;
+
+	while (stats_seg  && (tso_seg_idx < CDP_MAX_TSO_SEGMENTS)) {
+		dp_tso_segment_update(pdev, stats_idx,
+				      tso_seg_idx,
+				      stats_seg->seg);
+		++tso_seg_idx;
+		stats_seg = stats_seg->next;
+	}
+}
+
+void dp_txrx_clear_tso_stats(struct dp_soc *soc)
+{
+	uint8_t loop_pdev;
+	struct dp_pdev *pdev;
+
+	for (loop_pdev = 0; loop_pdev < soc->pdev_count; loop_pdev++) {
+		pdev = soc->pdev_list[loop_pdev];
+		dp_init_tso_stats(pdev);
+	}
+}
+#endif /* FEATURE_TSO_STATS */
diff --git a/dp/wifi3.0/dp_tx.c b/dp/wifi3.0/dp_tx.c
index 86e59dd..878d22a 100644
--- a/dp/wifi3.0/dp_tx.c
+++ b/dp/wifi3.0/dp_tx.c
@@ -215,6 +215,7 @@
 			dp_tso_num_seg_free(soc, tx_desc->pool_id,
 					    tx_desc->tso_num_desc);
 			tx_desc->tso_num_desc = NULL;
+			DP_STATS_INC(tx_desc->pdev, tso_stats.tso_comp, 1);
 		}
 
 		/* Add the tso segment into the free list*/
@@ -497,6 +498,28 @@
 	}
 }
 
+#ifdef FEATURE_TSO_STATS
+/**
+ * dp_tso_get_stats_idx: Retrieve the tso packet id
+ * @pdev - pdev handle
+ *
+ * Return: id
+ */
+static uint32_t dp_tso_get_stats_idx(struct dp_pdev *pdev)
+{
+	uint32_t stats_idx;
+
+	stats_idx = (((uint32_t)qdf_atomic_inc_return(&pdev->tso_idx))
+						% CDP_MAX_TSO_PACKETS);
+	return stats_idx;
+}
+#else
+static int dp_tso_get_stats_idx(struct dp_pdev *pdev)
+{
+	return 0;
+}
+#endif /* FEATURE_TSO_STATS */
+
 /**
  * dp_tx_free_remaining_tso_desc() - do dma unmap for tso segments if any,
  *				     free the tso segments descriptor and
@@ -542,9 +565,9 @@
 	struct qdf_tso_seg_elem_t *tso_seg;
 	int num_seg = qdf_nbuf_get_tso_num_seg(msdu);
 	struct dp_soc *soc = vdev->pdev->soc;
+	struct dp_pdev *pdev = vdev->pdev;
 	struct qdf_tso_info_t *tso_info;
 	struct qdf_tso_num_seg_elem_t *tso_num_seg;
-
 	tso_info = &msdu_info->u.tso_info;
 	tso_info->curr_seg = NULL;
 	tso_info->tso_seg_list = NULL;
@@ -605,6 +628,12 @@
 
 	tso_info->curr_seg = tso_info->tso_seg_list;
 
+	tso_info->msdu_stats_idx = dp_tso_get_stats_idx(pdev);
+	dp_tso_packet_update(pdev, tso_info->msdu_stats_idx,
+			     msdu, msdu_info->num_seg);
+	dp_tso_segment_stats_update(pdev, tso_info->tso_seg_list,
+				    tso_info->msdu_stats_idx);
+	dp_stats_tso_segment_histogram_update(pdev, msdu_info->num_seg);
 	return QDF_STATUS_SUCCESS;
 }
 #else
@@ -2244,11 +2273,11 @@
 	 */
 	if (qdf_nbuf_is_tso(nbuf)) {
 		dp_verbose_debug("TSO frame %pK", vdev);
-		DP_STATS_INC_PKT(vdev, tx_i.tso.tso_pkt, 1,
-				qdf_nbuf_len(nbuf));
+		DP_STATS_INC_PKT(vdev->pdev, tso_stats.num_tso_pkts, 1,
+				 qdf_nbuf_len(nbuf));
 
 		if (dp_tx_prepare_tso(vdev, nbuf, &msdu_info)) {
-			DP_STATS_INC_PKT(vdev, tx_i.tso.dropped_host, 1,
+			DP_STATS_INC_PKT(vdev->pdev, tso_stats.dropped_host, 1,
 					 qdf_nbuf_len(nbuf));
 			return nbuf;
 		}
diff --git a/dp/wifi3.0/dp_types.h b/dp/wifi3.0/dp_types.h
index 7120cfd..bb5a562 100644
--- a/dp/wifi3.0/dp_types.h
+++ b/dp/wifi3.0/dp_types.h
@@ -1700,6 +1700,11 @@
 	 */
 	struct dp_rx_fst *rx_fst;
 #endif /* WLAN_SUPPORT_RX_FLOW_TAG */
+
+#ifdef FEATURE_TSO_STATS
+	/* TSO Id to index into TSO packet information */
+	qdf_atomic_t tso_idx;
+#endif /* FEATURE_TSO_STATS */
 };
 
 struct dp_peer;
diff --git a/qdf/inc/qdf_nbuf.h b/qdf/inc/qdf_nbuf.h
index 1c5f207..154d595 100644
--- a/qdf/inc/qdf_nbuf.h
+++ b/qdf/inc/qdf_nbuf.h
@@ -3149,6 +3149,17 @@
 }
 
 /**
+ * qdf_nbuf_get_tcp_payload_len() - function to return the tso payload len
+ * @nbuf: network buffer
+ *
+ * Return: size of the tso packet
+ */
+static inline size_t qdf_nbuf_get_tcp_payload_len(qdf_nbuf_t nbuf)
+{
+	return __qdf_nbuf_get_tcp_payload_len(nbuf);
+}
+
+/**
  * qdf_nbuf_get_tso_num_seg() - function to calculate the number
  * of TCP segments within the TSO jumbo packet
  * @nbuf:   TSO jumbo network buffer to be segmented
diff --git a/qdf/linux/src/i_qdf_nbuf.h b/qdf/linux/src/i_qdf_nbuf.h
index 90bd851..9dae8a7 100644
--- a/qdf/linux/src/i_qdf_nbuf.h
+++ b/qdf/linux/src/i_qdf_nbuf.h
@@ -1453,9 +1453,23 @@
 			  bool is_last_seg);
 
 #ifdef FEATURE_TSO
+/**
+ * __qdf_nbuf_get_tcp_payload_len() - function to return the tcp
+ *                                    payload len
+ * @skb: buffer
+ *
+ * Return: size
+ */
+size_t __qdf_nbuf_get_tcp_payload_len(struct sk_buff *skb);
 uint32_t __qdf_nbuf_get_tso_num_seg(struct sk_buff *skb);
 
 #else
+static inline
+size_t __qdf_nbuf_get_tcp_payload_len(struct sk_buff *skb)
+{
+	return 0;
+}
+
 static inline uint32_t __qdf_nbuf_get_tso_num_seg(struct sk_buff *skb)
 {
 	return 0;
diff --git a/qdf/linux/src/qdf_nbuf.c b/qdf/linux/src/qdf_nbuf.c
index e7691b8..7feb537 100644
--- a/qdf/linux/src/qdf_nbuf.c
+++ b/qdf/linux/src/qdf_nbuf.c
@@ -3138,6 +3138,19 @@
 }
 qdf_export_symbol(__qdf_nbuf_unmap_tso_segment);
 
+size_t __qdf_nbuf_get_tcp_payload_len(struct sk_buff *skb)
+{
+	size_t packet_len;
+
+	packet_len = skb->len -
+		((skb_transport_header(skb) - skb_mac_header(skb)) +
+		 tcp_hdrlen(skb));
+
+	return packet_len;
+}
+
+qdf_export_symbol(__qdf_nbuf_get_tcp_payload_len);
+
 /**
  * __qdf_nbuf_get_tso_num_seg() - function to divide a TSO nbuf
  * into segments