Subject: [PATCH] Interface Stat Clearing Framework, skge support, ethtool support

From: Phil Dibowitz <[email-masked]>
Date: Sat, 29 Apr 2006 20:16:54 -0700

Hey folks,

A few months back I posted an in-progress patch for adding a clear_stats
framework similar to the get_stats framework and implimenting support
for it in the skge driver (the one NIC I have access to), as well as
adding the ethtool support for it.

While a few people said they didn't see the need for it, other people
did see the need for it, and I know it's a common request on many
sysadmin mailing lists I'm on.

Since no one seemed to have any technical issue with the patch, I've
cleaned up the patch, tested it, and fixed a few minor issues.

Unless someone has an objection, I'd think this would be useful to a lot
of people.

There are two patches attached:

interface_stats_clear.patch - the kernel patch against 2.6.17-rc3-git2
ethtool3-clearstats.patch - the ethtool patch to add the -z flag to
		support it.

If the kernel patch gets accepted I'll send a more complete ethtool
patch with documentation updates, etc.

Thanks.
-- 
Phil Dibowitz                             [email-masked]
Freeware and Technical Pages              Insanity Palace of Metallica
http://www.phildev.net/                   http://www.ipom.com/

"Be who you are and say what you feel, because those who mind don't
matter and those who matter don't mind."
 - Dr. Suess

This patch adds support for clearing interface statistics using the ethtool interface adding a new 0x23 command. It adds a clear_stats function pointer to the net_device struct, and then impliments local functions in the driver much the say get_stats works. The ethtool funtion pointer points to the same functions. The driver-local functions are currently only implimented in the skge driver.

Signed-off-by: Phil Dibowitz <[email-masked]>

---

diff -puN include/linux/netdevice.h~interface_stats_clear include/linux/netdevice.h
--- linux-2.6.17-rc3-git2/include/linux/netdevice.h~interface_stats_clear	2006-04-29 19:44:41.000000000 -0700
+++ linux-2.6.17-rc3-git2-phil/include/linux/netdevice.h	2006-04-29 19:44:41.000000000 -0700
@@ -319,6 +319,7 @@ struct net_device

 	struct net_device_stats* (*get_stats)(struct net_device *dev);
+	void (*clear_stats)(struct net_device *dev);
 	struct iw_statistics*	(*get_wireless_stats)(struct net_device *dev);
 
 	/* List of functions to handle Wireless Extensions (instead of ioctl).
diff -puN drivers/net/skge.c~interface_stats_clear drivers/net/skge.c
--- linux-2.6.17-rc3-git2/drivers/net/skge.c~interface_stats_clear	2006-04-29 19:44:41.000000000 -0700
+++ linux-2.6.17-rc3-git2-phil/drivers/net/skge.c	2006-04-29 19:44:41.000000000 -0700
@@ -44,7 +44,7 @@
 #include "skge.h"
 
 #define DRV_NAME		"skge"
-#define DRV_VERSION		"1.5"
+#define DRV_VERSION		"1.6"
 #define PFX			DRV_NAME " "
 
 #define DEFAULT_TX_RING_SIZE	128
@@ -97,6 +97,8 @@ static int xm_phy_write(struct skge_hw *
 static int gm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val);
 static void genesis_get_stats(struct skge_port *skge, u64 *data);
 static void yukon_get_stats(struct skge_port *skge, u64 *data);
+static void genesis_clear_stats(struct skge_port *skge);
+static void yukon_clear_stats(struct skge_port *skge);
 static void yukon_init(struct skge_hw *hw, int port);
 static void genesis_mac_init(struct skge_hw *hw, int port);
 static void genesis_link_up(struct skge_port *skge);
@@ -366,6 +368,15 @@ static struct net_device_stats *skge_get
 	return &skge->net_stats;
 }
 
+static void skge_clear_stats(struct net_device *dev)
+{
+	struct skge_port *skge = netdev_priv(dev);
+	if (skge->hw->chip_id == CHIP_ID_GENESIS)
+		genesis_clear_stats(skge);
+	else
+		yukon_clear_stats(skge);
+}
+
 static void skge_get_strings(struct net_device *dev, u32 stringset, u8 *data)
 {
 	int i;
@@ -722,6 +733,7 @@ static struct ethtool_ops skge_ethtool_o
 	.phys_id	= skge_phys_id,
 	.get_stats_count = skge_get_stats_count,
 	.get_ethtool_stats = skge_get_ethtool_stats,
+	.clear_ethtool_stats = skge_clear_stats,
 	.get_perm_addr	= ethtool_op_get_perm_addr,
 };
 
@@ -1383,6 +1395,20 @@ static void genesis_get_stats(struct skg
 		data[i] = xm_read32(hw, port, skge_stats[i].xmac_offset);
 }
 
+static void genesis_clear_stats(struct skge_port *skge)
+{
+	struct skge_hw *hw = skge->hw;
+	int port = skge->port;
+
+	/*
+	 * This is based on reading other parts of the driver
+	 * and is not yet tested.
+	 */
+
+	xm_write16(hw, port, XM_STAT_CMD, 0 | XM_SC_CLR_RXC
+			| XM_SC_CLR_TXC);
+}
+
 static void genesis_mac_intr(struct skge_hw *hw, int port)
 {
 	struct skge_port *skge = netdev_priv(hw->dev[port]);
@@ -1871,6 +1897,21 @@ static void yukon_get_stats(struct skge_
 					  skge_stats[i].gma_offset);
 }
 
+static void yukon_clear_stats(struct skge_port *skge)
+{
+	struct skge_hw *hw = skge->hw;
+	int port = skge->port;
+	u16 reg;
+	int i;
+
+	reg = gma_read16(hw, port, GM_PHY_ADDR);
+	/* this read is important, or we sometimes get no effect */
+	gma_write16(hw, port, GM_PHY_ADDR, reg | GM_PAR_MIB_CLR);
+	for (i = 0; i < GM_MIB_CNT_SIZE; i++)
+		gma_read16(hw, port, GM_MIB_CNT_BASE + 8*i);
+	gma_write16(hw, port, GM_PHY_ADDR, reg);
+}
+
 static void yukon_mac_intr(struct skge_hw *hw, int port)
 {
 	struct net_device *dev = hw->dev[port];
@@ -3183,6 +3224,7 @@ static struct net_device *skge_devinit(s
 	dev->do_ioctl = skge_ioctl;
 	dev->hard_start_xmit = skge_xmit_frame;
 	dev->get_stats = skge_get_stats;
+	dev->clear_stats = skge_clear_stats;
 	if (hw->chip_id == CHIP_ID_GENESIS)
 		dev->set_multicast_list = genesis_set_multicast;
 	else
diff -puN include/linux/ethtool.h~interface_stats_clear include/linux/ethtool.h
--- linux-2.6.17-rc3-git2/include/linux/ethtool.h~interface_stats_clear	2006-04-29 19:44:41.000000000 -0700
+++ linux-2.6.17-rc3-git2-phil/include/linux/ethtool.h	2006-04-29 19:44:41.000000000 -0700
@@ -365,6 +365,7 @@ struct ethtool_ops {
 	int	(*phys_id)(struct net_device *, u32);
 	int	(*get_stats_count)(struct net_device *);
 	void	(*get_ethtool_stats)(struct net_device *, struct ethtool_stats *, u64 *);
+	void    (*clear_ethtool_stats)(struct net_device *);
 	int	(*get_perm_addr)(struct net_device *, struct ethtool_perm_addr *, u8 *);
 	int	(*begin)(struct net_device *);
 	void	(*complete)(struct net_device *);
@@ -408,6 +409,7 @@ struct ethtool_ops {
 #define ETHTOOL_GPERMADDR	0x00000020 /* Get permanent hardware address */
 #define ETHTOOL_GUFO		0x00000021 /* Get UFO enable (ethtool_value) */
 #define ETHTOOL_SUFO		0x00000022 /* Set UFO enable (ethtool_value) */
+#define ETHTOOL_CSTATS		0x00000023 /* Clear NIC-specific statistics */
 
 /* compatibility with older code */
 #define SPARC_ETH_GSET		ETHTOOL_GSET
diff -puN net/core/ethtool.c~interface_stats_clear net/core/ethtool.c
--- linux-2.6.17-rc3-git2/net/core/ethtool.c~interface_stats_clear	2006-04-29 19:44:41.000000000 -0700
+++ linux-2.6.17-rc3-git2-phil/net/core/ethtool.c	2006-04-29 19:44:41.000000000 -0700
@@ -741,6 +741,17 @@ static int ethtool_get_stats(struct net_
 	return ret;
 }
 
+static int ethtool_clear_stats(struct net_device *dev, void __user *useraddr)
+{
+	struct ethtool_ops *ops = dev->ethtool_ops;
+	if (!ops->clear_ethtool_stats)
+		return -EOPNOTSUPP;
+
+	ops->clear_ethtool_stats(dev);
+
+	return 0;
+}
+
 static int ethtool_get_perm_addr(struct net_device *dev, void __user *useraddr)
 {
 	struct ethtool_perm_addr epaddr;
@@ -906,6 +917,9 @@ int dev_ethtool(struct ifreq *ifr)
 	case ETHTOOL_SUFO:
 		rc = ethtool_set_ufo(dev, useraddr);
 		break;
+	case ETHTOOL_CSTATS:
+		rc = ethtool_clear_stats(dev, useraddr);
+		break;
 	default:
 		rc =  -EOPNOTSUPP;
 	}
_
--- ethtool-3/ethtool.c.b4phil	2006-04-29 19:01:18.000000000 -0700
+++ ethtool-3/ethtool.c	2006-04-29 19:01:36.000000000 -0700
@@ -66,6 +66,7 @@
 static int do_goffload(int fd, struct ifreq *ifr);
 static int do_soffload(int fd, struct ifreq *ifr);
 static int do_gstats(int fd, struct ifreq *ifr);
+static int do_cstats(int fd, struct ifreq *ifr);
 static int send_ioctl(int fd, struct ifreq *ifr);
 static int check_for_pre24_kernel();
 
@@ -242,6 +243,7 @@
 	MODE_GOFFLOAD,
 	MODE_SOFFLOAD,
 	MODE_GSTATS,
+	MODE_CSTATS,
 } mode = MODE_GSET;
 
 static int goffload_changed = 0;
@@ -470,6 +472,8 @@
 				mode = MODE_TEST;
 			else if (!strcmp(argp[i], "-S"))
 				mode = MODE_GSTATS;
+			else if (!strcmp(argp[i], "-z"))
+				mode = MODE_CSTATS;
 			else if (!strcmp(argp[i], "-h"))
 				show_usage(0);
 			else
@@ -492,6 +496,7 @@
 			    (mode == MODE_GOFFLOAD) ||
 			    (mode == MODE_SOFFLOAD) ||
 			    (mode == MODE_GSTATS) ||
+			    (mode == MODE_CSTATS) ||
 			    (mode == MODE_PHYS_ID)) {
 				devname = argp[i];
 				break;
@@ -1266,6 +1271,8 @@
 		return do_soffload(fd, &ifr);
 	} else if (mode == MODE_GSTATS) {
 		return do_gstats(fd, &ifr);
+	} else if (mode == MODE_CSTATS) {
+		return do_cstats(fd, &ifr);
 	}
 
 	return 69;
@@ -2010,6 +2017,79 @@
 	return 0;
 }
 
+static int do_cstats(int fd, struct ifreq *ifr)
+{
+	struct ethtool_drvinfo drvinfo;
+	struct ethtool_gstrings *strings;
+	struct ethtool_stats *stats;
+	unsigned int n_stats, sz_str, sz_stats, i;
+	int err;
+
+	drvinfo.cmd = ETHTOOL_GDRVINFO;
+	ifr->ifr_data = (caddr_t)&drvinfo;
+	err = send_ioctl(fd, ifr);
+	if (err < 0) {
+		perror("Cannot get driver information");
+		return 71;
+	}
+
+	n_stats = drvinfo.n_stats;
+	if (n_stats < 1) {
+		fprintf(stderr, "no stats available\n");
+		return 94;
+	}
+
+	sz_str = n_stats * ETH_GSTRING_LEN;
+	sz_stats = n_stats * sizeof(u64);
+
+	strings = calloc(1, sz_str + sizeof(struct ethtool_gstrings));
+	stats = calloc(1, sz_stats + sizeof(struct ethtool_stats));
+	if (!strings || !stats) {
+		fprintf(stderr, "no memory available\n");
+		return 95;
+	}
+
+	strings->cmd = ETHTOOL_GSTRINGS;
+	strings->string_set = ETH_SS_STATS;
+	strings->len = n_stats;
+	ifr->ifr_data = (caddr_t) strings;
+	err = send_ioctl(fd, ifr);
+	if (err < 0) {
+		perror("Cannot get stats strings information");
+		free(strings);
+		free(stats);
+		return 96;
+	}
+
+	stats->cmd = ETHTOOL_CSTATS;
+	stats->n_stats = n_stats;
+	ifr->ifr_data = (caddr_t) stats;
+	err = send_ioctl(fd, ifr);
+	if (err < 0) {
+		perror("Cannot get stats information");
+		free(strings);
+		free(stats);
+		return 97;
+	}
+
+	/* todo - pretty-print the strings per-driver */
+	/*
+	fprintf(stdout, "NIC statistics:\n");
+	for (i = 0; i < n_stats; i++) {
+		char s[ETH_GSTRING_LEN];
+
+		strncpy(s, &strings->data[i * ETH_GSTRING_LEN],
+			ETH_GSTRING_LEN);
+		fprintf(stdout, "     %s: %llu\n",
+			s, stats->data[i]);
+	}
+	*/
+	free(strings);
+	free(stats);
+
+	return 0;
+}
+
 static int send_ioctl(int fd, struct ifreq *ifr)
 {
 	int err;
--- ethtool-3/ethtool-copy.h.b4phil	2006-04-29 19:01:26.000000000 -0700
+++ ethtool-3/ethtool-copy.h	2006-04-29 19:01:36.000000000 -0700
@@ -283,6 +283,7 @@
 #define ETHTOOL_GSTATS		0x0000001d /* get NIC-specific statistics */
 #define ETHTOOL_GTSO		0x0000001e /* Get TSO enable (ethtool_value) */
 #define ETHTOOL_STSO		0x0000001f /* Set TSO enable (ethtool_value) */
+#define ETHTOOL_CSTATS		0x00000023 /* get NIC-specific statistics */
 
 /* compatibility with older code */
 #define SPARC_ETH_GSET		ETHTOOL_GSET