From d7e56d6a839247fdf8c5a76d36819d9bdfd50d07 Mon Sep 17 00:00:00 2001
From: MD Danish Anwar <danishanwar@ti.com>
Date: Tue, 16 Aug 2022 11:57:43 +0530
Subject: [PATCH] net: ti: icssg_prueth: Fix overflowing of stats

ICSSG has hardware registers for providing statistics like total rx bytes,
total tx bytes, etc. These registers are of 32 bits and hence in case of 1G
link, they overflows in around 32 seconds. The behaviour of these registers
is such that they don't roll back to 0 after overflow but rather stay at
UINT_MAX.

These registers support a feature where the value written to them is
subtracted from the register. This feature can be utilized to fix the
overflowing of stats.

This solution uses a Workqueues based solution where a function gets
called before the registers overflow, this function saves the register
values in local variables and resets the hardware registers.

The ethtool callback now returns the values in the local variables
rather than reading the hardware registers directly.

Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
---
 drivers/net/ethernet/ti/icssg_ethtool.c | 46 ++++++++++++++++++++++---
 drivers/net/ethernet/ti/icssg_prueth.c  | 12 +++++++
 drivers/net/ethernet/ti/icssg_prueth.h  |  4 +++
 3 files changed, 57 insertions(+), 5 deletions(-)

diff --git a/drivers/net/ethernet/ti/icssg_ethtool.c b/drivers/net/ethernet/ti/icssg_ethtool.c
index 6aa10d9ac2e6..bb6f4010af22 100644
--- a/drivers/net/ethernet/ti/icssg_ethtool.c
+++ b/drivers/net/ethernet/ti/icssg_ethtool.c
@@ -8,6 +8,8 @@
 #include "icssg_prueth.h"
 #include <linux/regmap.h>
 
+#define STATS_TIME_LIMIT_MS 25000000
+
 static u32 stats_base[] = {	0x54c,	/* Slice 0 stats start */
 				0xb18,	/* Slice 1 stats start */
 };
@@ -290,24 +292,58 @@ static void emac_get_strings(struct net_device *ndev, u32 stringset, u8 *data)
 	}
 }
 
-static void emac_get_ethtool_stats(struct net_device *ndev,
-				   struct ethtool_stats *stats, u64 *data)
+static void emac_update_hardware_stats(struct prueth_emac *emac)
 {
-	struct prueth_emac *emac = netdev_priv(ndev);
 	struct prueth *prueth = emac->prueth;
-	int i;
 	int slice = prueth_emac_slice(emac);
 	u32 base = stats_base[slice];
 	u32 val;
+	int i;
 
 	for (i = 0; i < ARRAY_SIZE(icssg_ethtool_stats); i++) {
 		regmap_read(prueth->miig_rt,
 			    base + icssg_ethtool_stats[i].offset,
 			    &val);
-		data[i] = val;
+		regmap_write(prueth->miig_rt,
+			     base + icssg_ethtool_stats[i].offset,
+			     val);
+
+		emac->stats[i] += val;
 	}
 }
 
+void emac_stats_work_handler(struct work_struct *work)
+{
+	struct prueth_emac *emac = container_of(work, struct prueth_emac,
+						stats_work.work);
+	emac_update_hardware_stats(emac);
+
+	queue_delayed_work(system_long_wq, &emac->stats_work,
+			   msecs_to_jiffies(STATS_TIME_LIMIT_MS / emac->speed));
+}
+
+void emac_ethtool_stats_init(struct prueth_emac *emac)
+{
+	if (!emac->stats) {
+		struct device *dev = emac->prueth->dev;
+
+		emac->stats = devm_kzalloc(dev, ARRAY_SIZE(icssg_ethtool_stats) *
+					   sizeof(*emac->stats), GFP_KERNEL);
+	}
+}
+
+static void emac_get_ethtool_stats(struct net_device *ndev,
+				   struct ethtool_stats *stats, u64 *data)
+{
+	struct prueth_emac *emac = netdev_priv(ndev);
+	int i;
+
+	emac_update_hardware_stats(emac);
+
+	for (i = 0; i < ARRAY_SIZE(icssg_ethtool_stats); i++)
+		data[i] = emac->stats[i];
+}
+
 static int emac_get_ts_info(struct net_device *ndev,
 			    struct ethtool_ts_info *info)
 {
diff --git a/drivers/net/ethernet/ti/icssg_prueth.c b/drivers/net/ethernet/ti/icssg_prueth.c
index 50bb35e93360..8fd6c24f7bd2 100644
--- a/drivers/net/ethernet/ti/icssg_prueth.c
+++ b/drivers/net/ethernet/ti/icssg_prueth.c
@@ -9,6 +9,7 @@
 #include <linux/bitops.h>
 #include <linux/clk.h>
 #include <linux/etherdevice.h>
+#include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/genalloc.h>
 #include <linux/if_vlan.h>
@@ -1831,6 +1832,9 @@ static int emac_ndo_open(struct net_device *ndev)
 		icssg_set_pvid(emac->prueth, emac->port_vlan, emac->port_id);
 		emac_set_port_state(emac, ICSSG_EMAC_PORT_VLAN_AWARE_ENABLE);
 	}
+
+	queue_work(system_long_wq, &emac->stats_work.work);
+
 	return 0;
 
 reset_tx_chan:
@@ -1934,6 +1938,11 @@ static int emac_ndo_stop(struct net_device *ndev)
 		icss_iep_exit(emac->iep);
 
 	cancel_work_sync(&emac->rx_mode_work);
+
+	/* Destroying the queued work in ndo_stop() */
+
+	cancel_delayed_work_sync(&emac->stats_work);
+
 	/* stop PRUs */
 	prueth_emac_stop(emac);
 
@@ -2251,6 +2260,9 @@ static int prueth_netdev_init(struct prueth *prueth,
 	}
 	INIT_WORK(&emac->rx_mode_work, emac_ndo_set_rx_mode_work);
 
+	emac_ethtool_stats_init(emac);
+	INIT_DELAYED_WORK(&emac->stats_work, emac_stats_work_handler);
+
 	ret = pruss_request_mem_region(prueth->pruss,
 				       port == PRUETH_PORT_MII0 ?
 				       PRUSS_MEM_DRAM0 : PRUSS_MEM_DRAM1,
diff --git a/drivers/net/ethernet/ti/icssg_prueth.h b/drivers/net/ethernet/ti/icssg_prueth.h
index 9c0d948ed700..e761fb3d4c51 100644
--- a/drivers/net/ethernet/ti/icssg_prueth.h
+++ b/drivers/net/ethernet/ti/icssg_prueth.h
@@ -190,6 +190,8 @@ struct prueth_emac {
 
 	struct prueth_qos qos;
 	struct work_struct ts_work;
+	struct delayed_work stats_work;
+	u64 *stats;
 };
 
 /**
@@ -352,4 +354,6 @@ void icssg_set_pvid(struct prueth *prueth, u8 vid, u8 port);
 	container_of(pnapi, struct prueth_tx_chn, napi_tx)
 
 u64 prueth_iep_gettime(void *clockops_data, struct ptp_system_timestamp *sts);
+void emac_stats_work_handler(struct work_struct *work);
+void emac_ethtool_stats_init(struct prueth_emac *emac);
 #endif /* __NET_TI_ICSSG_PRUETH_H */
